GithubHelp home page GithubHelp logo

aura.sql's People

Contributors

agentile avatar arabcoders avatar auroraeosrose avatar brandonsavage avatar cxj avatar frederikbosch avatar harikt avatar henriquemoody avatar iansltx avatar jacobemerick avatar jakeasmith avatar jblotus avatar jklein avatar jlacoude avatar joebengalen avatar kenjis avatar koriym avatar maxakawizard avatar mbrevda avatar nyamsprod avatar pavarnos avatar pborreli avatar pmjones avatar qrzysio avatar ramirovarandas avatar srjlewis avatar stanlemon avatar stof avatar themy3 avatar tzappa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aura.sql's Issues

Problem with queries which contain blank strings

Using a query of the following format

UPDATE table SET `value`=:value, `blank`='', `value2` = :value2, `blank2` = '', `value3`=:value3 WHERE id = :id

And passing in an array formatted as:

$arr = ['value'=> 'string', 'id'=> 1, 'value2'=> 'string', 'value3'=>'string'];

I receive the PDO " number of bound variables does not match number of tokens" error.

Examining the situation the Rebuilder returns a valid query (i.e. the query that went in) but the array has been truncated to:

Array
(
    [value] => string
    [value3] => string
    [id] => 1
)

This happens because the preg_split in rebuildStatement is splitting these "empty string" declarations incorrectly. The more empty strings in the query the more the problems. I am uncertain if the order of the strings matters.

I have added a temporary fix in my fork which adds:

$statement = str_replace("''", "_NULL_", $statement);
...
$statement = str_replace("_NULL_", "''", $statement);

Either side of the list($statement, $values) = $rebuilder->__invoke($statement, $values); line. This could also be added inside the Rebuilder.

Is this a legitimate problem or is there a better way to be writing the original query such that ExtendedPDO doesn't support the method in question?

New release / tag

Could you release a new version of Aura.Sql? I am specifically interested in the extraction of the Rebuilder since I already have own methods for direct fetching. Thanks for the wonderful work.

Query builder orWhere

When using the orWhere() method of the Select query builder, parethesis () are not put around the where conditions, making the query result inconsistent.

There are some ways to solve it, like adding a method to open and close the OR condition, or maybe trying to detect the orWhere begin and end.

If you need some help to do it or test, msg me back.

Scope of variables

Hi Paul ,
As you have already mentioned in irc , this is just an initial commit , you can ignore if I am also wrong :-) .

Currently in many places , some variables are never assigned . For eg: currently for getRead() , see $slaves . In constructor we are accepting and setting $slaves , $masters array.

// pick a random slave, or a random master if no slaves, or default if no masters
public function getRead()
{
    if ($slaves) {
        return $this->getRandomSlave();
    } elseif ($masters) {
        return $this->getRandomMaster();
    } else {
        return $this->getDefault();
    }
}

So I guess the below implementation will be the correct way .

// pick a random slave, or a random master if no slaves, or default if no masters
public function getRead()
{
    if (!empty($this->slaves)) {
        return $this->getRandomSlave();
    } elseif (!empty($this->masters)) {
        return $this->getRandomMaster();
    } else {
        return $this->getDefault();
    }
}

getRandomSlave() and getRandomSlave() are having $key which is not assigned . See below.

// converts a random $this->masters entry to a Connection object
public function getRandomSlave()
{
    if (! $key) {
        $key = array_rand($this->slaves);
    } elseif (! isset($this->slaves[$key])) {
        throw new Exception\NoSuchConnection;
    }

    if (! $this->conn['slaves'][$key] instanceof Connection) {
        list($adapter, $params) = $this->merge($this->slaves[$key]);
        $this->conn['slaves'][$key] = $this->factory->newInstance($adapter, $params);
    }
    return $this->conn['slaves'][$key];
}

My commit is over

harikt@794181f

As told for the getRandomSlave() and getRandomSlave() , I have not made any changes . Please have a look .

Thanks

how to use Aura.Sql.Adapter.Abstract::update to auto increase field???

method declare:

public function update($table, array $cols, $cond, array $data = array())

if i want to increase count field by 1, i will use update() like this:

update("tbl_name", array('count' => 'count+1", "uid=1");

it will generate sql like:

set count='count+1'

cause data type of field count is "int", so it will convert 'count+1' to 0.

so what can i do?

Performance of fetchTableCols()

Actively fetching the table column meta-data on demand, every time the method is called, seems like a pretty big performance pitfall.

How about adding a getTableCols() method, which would cache the table columns (in memory) for the connection, once fetched for the first time?

Or is there some reason this responsibility is deliberately left to the consumer?

sqlsrv PDO Exception

I'm running php 5.5.20 in windows with the sqlsrv extensions enabled (thread safe). When connecting to the DB with an ExtendedPdo object, I get the following error:
PDOException: SQLSTATE[IMSSP]: The given attribute is only supported on the PDOStatement object.

I'm sure that this is a problem with the ExtendedPdo object because the following code using Vanilla PDO works just fine:

    $pdo = new \PDO(
         'sqlsrv:Server=my_server ; DataBase=My_DB',
         $userName,
         $password
     );

    $smt = $pdo->prepare('Select Top 10 * From My_table');
    $stmt->execute();
    $result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// yields the appropriate associative array of results

When I try to accomplish the same thing with ExtendedPdo it doesn't work:

    $pdo = new ExtendedPdo(
        'sqlsrv:Server=My_Server ; DataBase=My_DB',
        $userName,
        $password
    );
    $stmt = 'Select Top 10 * From My_table';
    $result = $pdo->fetchAll($stmt);

//yields the error: 
//PDOException: SQLSTATE[IMSSP]: The given attribute is only supported on the PDOStatement object.

With the extended PDO this error seems to get triggered as soon as it tries to establish a connection to the DB. Calling connect(), prepare(), and fetch*() yields exactly the same behavior. Am I doing something wrong or is there a compatibility issue with ExtendedPdo and sqlsrv?

Update

After reading the doc block over the ExtendedPdo Constructor I realized that I could just pass in an already instantiated PDO object. I have tested this and it seems to work fine. Code for illustration:

   $pdo = new \PDO(
        'sqlsrv:Server=My_Server ; DataBase=My_DB',
        $userName,
        $password
    );
    $extended = new ExtendedPdo($pdo);
    $stmt = 'Select Top 10 * from My_table';
    $result = $extended->fetchAll($stmt);
// yields the appropriate array of results.  

While this does work and will make my life a little bit easier, it'd be nice to take advantage of ExtendedPdo's lazy connection feature which means there is still a bug somewhere.

When no options passed

When the $options not passed as array

$di->params['Aura\Sql\ConnectionManager'] = array(
    'factory' => $di->lazyNew('Aura\Sql\ConnectionFactory'),
    'default' => array(
        'adapter'  => 'mysql',
        'dsn'      => array(
            'host' => 'localhost',
            'dbname' => 'auradb',
        ),
        'username' => 'monty',
        'password' => 'some_pass',
        //'options' => array(),
    ),
);

the error is getting

Notice: Undefined index: options in /media/Linux/aurasystem/package/Aura.Sql/src/ConnectionManager.php on line 129 Catchable fatal error: Argument 4 passed to Aura\Sql\Connection\AbstractConnection::__construct() must be an array, null given in /media/Linux/aurasystem/package/Aura.Sql/src/Connection/AbstractConnection.php on line 31

When closely looking the $merge is not working as expected . A print_r( $merged ) will see what values it has . The options is undefined . A quick and dirty fix is

protected function mergeAdapterParams(array $override = array())
{
    $merged  = $this->merge($this->default, $override);
    $adapter = $merged['adapter'];
    /*
     * if no options passed by defaut the options should be an array
     * Currently the merge not working as expected. And the options is undefined .
     * A quick and dirty fix is pass in configs default as options => array()
     * or fixing like the one below
     */
    if(! isset($merged['options']) ) {
        $merged['options'] = array();
    }
    $params  = array(
        'dsn'      => $merged['dsn'],
        'username' => $merged['username'],
        'password' => $merged['password'],
        'options'  => $merged['options'],
    );

    return array($adapter, $params);
}

Still some other issue :( to make a connection , looking .

Resolving Test Failure in Travis

Hi @pmjones ,

In the https://github.com/auraphp/Aura.Sql/blob/master/tests/bootstrap.php , we have currently the username and password for mysql , postgres, but for travis-ci the username and passwords are different .

Can you make the necessary changes on your database system or add the tests/bootstrap.php to .gitignore files , so we don't get any issues ?

You can check the correct values in

https://github.com/harikt/Aura.Sql/blob/travis/.travis.yml

or for your convenience

- DB=mysql:
  adapter: mysql2
  database: test
  username: 
  encoding: utf8
- DB=postgres:
  adapter: postgresql
  database: test
  username: postgres

The https://github.com/harikt/Aura.Sql/blob/travis/tests/bootstrap.php I was using has a username root for mysql , but when I checked recently the root from travis-ci seems missing in their documentation. But still this works .

See http://travis-ci.org/#!/harikt/Aura.Sql

Thank you

instanceof Aura\Sql\Select

How do you convert instanceof Aura\Sql\Select ? The method toString() is protected . __toString() works when we try to echo or print the object .
I was passing a select object to the query. I think we need to change the protected method to public.

Not quote table-fields in Aura.Sql.Select

Hi, i'm using Aura.Sql for a while...

last friday, i check mysql logs for secure purpose. i found a lot of un-quoted fields in sqls. like:

SELECT  id AS `uid`, name, age, sex FROM `rel_rev_file` WHERE age > 25 AND sex = 'female' OR id < 10 GROUP BY sex, age HAVING COUNT(age) > 60 ORDER BY id DESC, age LIMIT 20 OFFSET 40;

it just process field alias and table name, how about other fields???

DriverFactory and Select

Did you missed to add DriverFactory ?
Its still mentioning at scripts/instance.php .
Also the use Aura\Sql\Select;

Problem with ExtendedPDO when using AuraDi

I've found that whe one uses AuraDi to create an instance of ExtendedPDO, the arrays for attributes and driver options doesn't work properly:

# Set parameters for Aura's ExtendedPdo
$di->params['Aura\Sql\ExtendedPdo'] = array(
    'dsn' => "mysql:host=" . $app_config["database"]["host"] . ";dbname=" . $app_config["database"]["dbname"],
    'username' => $app_config["database"]["username"],
    'password' => $app_config["database"]["password"],
    array(),
    array(
        Aura\Sql\ExtendedPdo::ATTR_EMULATE_PREPARES => false,
        Aura\Sql\ExtendedPdo::ATTR_STRINGIFY_FETCHES => false,
    )
);

# Database instance of Aura Sql
$di->set(
    'database',
    function () use ($di) {
        return $di->newInstance('Aura\Sql\ExtendedPdo');
    }
);

The above results in the attributes not being set.

However, if the below is used instead:

$di->set(
    'database',
    function () use ($di) {
        return new Aura\Sql\ExtendedPdo($di->params['Aura\Sql\ExtendedPdo']['dsn'],
                                        $di->params['Aura\Sql\ExtendedPdo']['username'],
                                        $di->params['Aura\Sql\ExtendedPdo']['password'],
                                        array(),
                                        array(
                                            Aura\Sql\ExtendedPdo::ATTR_EMULATE_PREPARES => false,
                                            Aura\Sql\ExtendedPdo::ATTR_STRINGIFY_FETCHES => false,
                                        ));
    }
);

Then it works perfectly.

How could I get it to work correctly with the first example?

Alternatively, perhaps making the arrays named parameters like username, password and dsn are?

Check placeholders parser

I'm porting you parser to my library for sphinxsearch query builder and noticed that queries like 'SELECT :foo' not processed properly because placeholder on the end of the string.

update Now()

I know this will be working, but I didn't get it working like this,

$cols['lastlogin'] = 'NOW()';
$cond = 'id = :id';
$bind['id'] = $row['id'];
$this->connection->update($this->table, $cols, $cond, $bind);

but it works with the newUpdate() on set('lastlogin', 'NOW()')

Typo in ReadMe?

Is this a typo?

Current line Fixed?
$bind = array('foo' => 'bar', 'baz' => 'dib'); $bind = array('foo' => 'baz', 'bar' => 'dib');

First parameter cannot be null when using perform/rebuilder

If the first parameter of a query with numbered placeholders is null, you will get an undefined offset in Rebuilder.php on line 220. The standard PDO library allows the first parameter to be null.

The following code generates the error.

$pdo->perform('SELECT * FROM test WHERE column_one = ?', [null]);

Sequential params on execution queries should be 0-indexed

Currently, fetch*(), perform(), etc. assume a 1-indexed array when dealing with non-named (question-mark) params. The base PDO execute() uses a 0-indexed array. This results in inconsistent behavior between ExtendedPDO and PDO, forcing workarounds like

https://gist.github.com/iansltx/6d0181c835e5afbf2965#file-gistfile1-php-L6

(I feed those results into fetchOne() or fetchAll() depending on whether I'm counting or retrieving results).

It sounds like the inconsistency here is because http://www.php.net//manual/en/pdostatement.bindvalue.php uses a 1-indexed array and http://www.php.net/manual/en/pdostatement.execute.php uses a 0-indexed array, when interacting with non-named params. php.net docs need an edit to clarify that this is what's going on on the execute site, but the editor isn't playing nice with me right now.

Fixing this issue will be backwards incompatible with the hack posted above, though for folks who've used 1-indexed as input instead and have a properly sized array it'd be possible to keep their implementations working.

SELECT FROM

When cols are empty we are not adding * . Does that helps to resolve the error in query ? ( SELECT FROM )
Line 119 adding an else does that fix the issue ?

    // add columns
    if ($this->cols) {
        $text .= $line . implode($csep, $this->cols) . PHP_EOL;
    } else {
        $text .= ' * ' . PHP_EOL;
    }

Extended PDO

Hi Paul,

I was looking into the fetchAll method of the extended PDO and the fetchAll method of the normal PDO.

/**
 * 
 * Fetches a sequential array of rows from the database; the rows
 * are represented as associative arrays.
 * 
 * @param string $statement The SQL statement to prepare and execute.
 * 
 * @param array $values Values to bind to the query.
 * 
 * @param callable $callable A callable to be applied to each of the rows
 * to be returned.
 * 
 * @return array
 * 
 */
public function fetchAll($statement, array $values = array(), $callable = null)
{
    $this->bindValues($values);
    $sth = $this->query($statement);
    $data = $sth->fetchAll(self::FETCH_ASSOC);
    if ($callable) {
        foreach ($data as $key => $row) {
            $data[$key] = call_user_func($callable, $row);
        }
    }
    return $data;
}

http://www.php.net/manual/en/pdostatement.fetchall.php

public array PDOStatement::fetchAll ([ int $fetch_style [, mixed $fetch_argument [, array $ctor_args = array() ]]] )

You can see in ExtendedPdo they will not get a chance to make the class bind to the return values. Will it be good, if we can make optional arguments as that of fetchAll method signature ?

http://php.net/manual/en/pdostatement.setfetchmode.php

This is with respect to my recent experiment on fetchMode.

<?php
require __DIR__ . '/vendor/autoload.php';

class PollChoice 
{
    protected $id;
    protected $poll_id;
    protected $choice_text;
    protected $votes;

    public function __get($key)
    {
        return $this->{$key};
    }

    public function __set($key, $value)
    {
        $this->{$key} = $value;
    }
}

use Aura\Sql\ExtendedPdo;
use Aura\Sql\Profiler;

$pdo = new ExtendedPdo(
    'mysql:host=localhost;dbname=dbname',
    'username',
    'password',
    array(), // driver options as key-value pairs
    array()  // attributes as key-value pairs
);

$sql = 'SELECT * FROM polls_choice WHERE id IN (:id) AND poll_id = :poll_id';

$bind = array(
    'id' => array('1', '2'),
    'poll_id' => '1',
);
$pdo->bindValues($bind);
$results = $pdo->query($sql, PDO::FETCH_CLASS, 'PollChoice');
foreach ($results as $result) {
    echo "Query Result : " . $result->choice_text . PHP_EOL;
}

Let me know your thoughts.

Another setPdo() option

@harikt @stanlemon,

My first instinct was to try to build a PdoFactory with various strategies to allow for different ways to build and inject the PDO object. After about two hours of trying things out, it was a bust, mostly because the ConnectionLocator really depends on the dsn-array. Just as well; it would have been a BC break anyway.

So here's another option, using setPdo() as both of you have suggested; see the diff here:

https://gist.github.com/4199388

This retains the $dsn=null default param, which allows you to construct a connection object without PDO, and then use setPdo() immediately after.

I considered the idea of conditional checks to see if a PDO object has been injected via constructor, but I think It's just not very clean (even though it is flexible). So that's not here.

Note that the connection factory still requires the adapter name, so that it knows what kind of connection to create (it allows a null $dsn as well).

What do we think about this?

-- pmj

How to catch connection exceptions?

How I can catch thrown excepion, e.g related to MySQL server being offline?
I'm experiencing this problem only with this package. It looks like exceptions thrown in ExtendedPdo aren't being catched? or i'm just doing it wrong?

I tried to wrap it inside try...catch block at the very beginning of my php scripts.

try {
    $dbh = new Sql\ExtendedPdo(
        'mysql:host='.GameConfig::$DB_HOST.';dbname='.GameConfig::$DB_DATABASE.'',
        GameConfig::$DB_USER,
        GameConfig::$DB_PASSWORD
    );
} catch(PDOException $e) {
   //do stuff
}

But it still shows me this error:

 *Uncaught* exception 'PDOException' with message 'SQLSTATE[HY000] [2002] Connection refused' in /home/grayfox/www/extel/vendor/aura/sql/src/ExtendedPdo.php:218 Stack trace: #0 

Unit of Work and Mapper

Hey @pmjones ,

I am not good in writing about Mappers , so I prefer you itself to write for Aura.Sql as always .

Sorry for the trouble.

fetchAssoc() seems to return only one (the last) row

The documentation says:

// fetchAssoc() returns an associative array of all rows where the key is the
// first column, and the row arrays are keyed on the column names
$result = $pdo->fetchAssoc($stm, $bind);

However, only one row is returned, nominally the last row, rather than "all rows". Incorrect docs? Bug in code?

fetchNum() method

Hi

It's not really an issue, just a simple question.
I'm little surprised that there's no fetchNum() method. Do you have any plans of implementing it?

Write SQLs can be queried in fetch* methods

$strsql_w = "insert into rel_rev_file (revid, fileid, addtime, modtime) values (333, 9, '2012-04-24 16:00:00', '00-00-00 00:00:00')";

$sql->fetchAll($strsql_w);

it do insert record into table rel_rev_file and throw an exception like this:

[25-Apr-2012 07:41:17 UTC] PHP Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY0
00]: General error' in /home/guweigang/local/httpd/htdocs/cr-api/application/library/Crd/Sql/Adapter/Abst
ract.php:433
Stack trace:
#0 /home/guweigang/local/httpd/htdocs/cr-api/application/library/Crd/Sql/Adapter/Abstract.php(433): PDOSt

atement->fetchAll(2)
#1 /home/guweigang/local/httpd/htdocs/cr-api/application/controllers/Index.php(54): Crd_Sql_Adapter_Abstr

act->fetchAll('insert into `re...')
#2 [internal function]: IndexController->indexAction()
#3 /home/guweigang/local/httpd/htdocs/cr-api/index.php(7): Ap_Application->run()
#4 {main}

thrown in /home/guweigang/local/httpd/htdocs/cr-api/application/library/Crd/Sql/Adapter/Abstract.php on
line 433

LIKE in query

How do you make a LIKE statement ?

SELECT * FROM table WHERE {$column} LIKE :{$column}

Binding variables conflicts with some Postgres SQL queries.

Assume you are trying to execute the following SQL query.

SELECT id
FROM users
WHERE created_at >= ':createdDate 00:00:00'::TIMESTAMP
  AND created_at <= ':createdDate 23:59:59'::TIMESTAMP

The ::type syntax, in Postgres, casts the value to that type (above we are converting the string to a TIMESTAMP). Now, lets try to execute the query and bind a value:

$conn->fetchAll($sql, ['createdDate' => '2015/12/01']);

This gives you an error: "Undefined offset: 0" in src/Rebuilder.php on line 247.

Profiler documentation improvement request

It'd be nice in the README.md to mention that there are likely to be multiple profile entries in the array, numerically indexed, even if only one query has been issued. In my recent experience, I get 2 such entries for my query, both effectively identical, except the second entry actually contains the bind values and the first does not (empty array).

Checking for property at the Column class

When using Twig and trying to access a Column property, it gives an error saying that the method doesn't exists.

For example, if you try to access the column name property:

col.name

It halts saying the method name() doesn't exists.
It can be fixed by just putting an __isset($key) checking if $key is a class property.

Thanks!

Non-standard SQL features

For instance MySQL supports
UPDATE tbl
SET a = 1
WHERE b = 2
LIMIT 5
Limit part is non-standard, but I need it in \Aura\Sql\Query\Update.
Same with SELECT SQL_CALC_FOUND_ROWS etc.
Can I send pull requests regarding those features or Aura\Query objects should support only standard syntax?

with dot in bind

Hi Paul,

I was trying something lately. The where was having a p.id = :p.id , but getting error like

PHP Notice:  Undefined index: p in /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php on line 1037
PHP Stack trace:
PHP   1. {main}() /var/www/github.com/harikt/experiments/closure-class.php:0
PHP   2. PostRepository->fetchOne() /var/www/github.com/harikt/experiments/closure-class.php:255
PHP   3. Aura\Sql\ExtendedPdo->fetchOne() /var/www/github.com/harikt/experiments/closure-class.php:124
PHP   4. Aura\Sql\ExtendedPdo->perform() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:451
PHP   5. Aura\Sql\ExtendedPdo->prepareWithValues() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:628
PHP   6. Aura\Sql\ExtendedPdo->rebuild() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:864
PHP   7. Aura\Sql\ExtendedPdo->prepareValuePlaceholders() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:943
PHP   8. Aura\Sql\ExtendedPdo->prepareNamedPlaceholder() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:976
PHP Fatal error:  Uncaught exception 'PDOException' with message 'SQLSTATE[42S22]: Column not found: 1054 Unknown column 'NULL.id' in 'where clause'' in /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:630
Stack trace:
#0 /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php(630): PDOStatement->execute()
#1 /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php(451): Aura\Sql\ExtendedPdo->perform('SELECT\n    a.na...', Array)
#2 /var/www/github.com/harikt/experiments/closure-class.php(124): Aura\Sql\ExtendedPdo->fetchOne('SELECT\n    a.na...', Array)
#3 /var/www/github.com/harikt/experiments/closure-class.php(255): PostRepository->fetchOne()
#4 {main}
  thrown in /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php on line 630

I had a look into the problem and it is happening at rebuild

      for ($i = 0; $i <= $k; $i += 3) {

            // split into subparts by ":name" and "?"
            $subs = preg_split(
                "/(:[a-zA-Z_][a-zA-Z0-9_]*)|(\?)/m",
                $parts[$i],
                -1,
                PREG_SPLIT_DELIM_CAPTURE
            );

            // check subparts to convert bound arrays to quoted CSV strings
            $subs = $this->prepareValuePlaceholders($subs, $bind);

            // reassemble
            $parts[$i] = implode('', $subs);
        }

The full stuff what I am experimenting is

<?php
require __DIR__ . '/vendor/autoload.php';
use Aura\SqlQuery\QueryFactory;
use Aura\Sql\ExtendedPdo;
use Aura\Sql\Profiler;

class Post
{
    private $id;
    private $title;
    private $body;
    private $author;

    public function __construct($id, $title, $body, $author)
    {
        $this->id = $id;
        $this->title = $title;
        $this->body = $body;
        $this->author = $author;
    }

    public function id()
    {
        return $this->id;
    }

    public function title()
    {
        return $this->title;
    }

    public function body()
    {
        return $this->body;
    }

    public function author()
    {
        if ($this->author instanceof Closure) {
            $author = $this->author;
            $this->author = $author();
        }
        return $this->author;
    }
}

class Author
{
    private $name;
    private $id;

    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    public function name()
    {
        return $this->name;
    }

    public function __tostring()
    {
        return $this->name();
    }
}

class PostRepository
{
    private $author_repository;
    private $table = 'posts';
    private $select;
    private $hydrate;
    private $connection;
    private $query_factory;

    public function __construct($connection, $query_factory, $author_repository)
    {
        $this->connection = $connection;
        $this->query_factory = $query_factory;
        $this->author_repository = $author_repository;
    }

    public function select($hydrate = false)
    {
        $this->hydrate = $hydrate;
        $this->select = $this->query_factory->newSelect();
        $this->select->from($this->table . ' as p');
        if ($hydrate) {
            $this->select->cols(array('a.name', 'p.title', 'p.id', 'p.body', 'p.author_id'));
            $this->select->join(
                'LEFT',
                $this->author_repository->getTable() . ' as a',
                'p.author_id = a.id'
            );
        } else {
            $this->select->cols(array('p.title', 'p.id', 'p.body', 'p.author_id'));
        }
        return $this;
    }

    public function where($where = array())
    {        
        if (! $this->select) {
            throw Exception("No query to execute");
        }
        foreach ($where as $col => $value) {
            if (is_array($value)) {
                $this->select->where("$col IN :$col");
            } else {
                $this->select->where("$col = :$col");
            }
            $this->select->bindValue($col, $value);
        }
        return $this;
    }

    public function fetchOne()
    {
        if (! $this->select) {
            throw Exception("No query to execute");
        }
        $result = $this->connection->fetchOne($this->select->__toString(), $this->select->getBindValues());
        return $this->createObject($result);
    }

    public function fetchAll()
    {
        if (! $this->select) {
            throw Exception("No query to execute");
        }
        $result = $this->connection->fetchAll($this->select->__toString(), $this->select->getBindValues());
        return $this->createObjects($result);
    }

    protected function createObjects($results)
    {
        $objects = array();
        foreach ($results as $result) {
            $objects[] = $this->createObject($result);
        }
        return $objects;
    }

    protected function createObject($result)
    {
        if ($this->hydrate) {
            $author = new Author($result['author_id'], $result['name']);
        } else {
            $author_id = $result['author_id'];
            $author = function () use ($author_id) {
                return $this->author_repository->find($author_id)->fetchOne();
            };
        }
        return new Post($result['id'], $result['title'], $result['body'], $author);
    }

    public function getTable()
    {
        return $this->table;
    }
}

class AuthorRepository
{
    private $table = 'authors';
    private $select;
    private $connection;
    private $query_factory;

    public function __construct($connection, $query_factory)
    {
        $this->connection = $connection;
        $this->query_factory = $query_factory;
    }

    public function select($id)
    {
        $this->select = $this->query_factory->newSelect();
        $this->select->from($this->table)
            ->cols(array('id', 'name'));        
        return $this;
    }

    public function where($where = array())
    {        
        if (! $this->select) {
            throw Exception("No query to execute");
        }
        foreach ($where as $col => $value) {
            if (is_array($value)) {
                $this->select->where("$col IN :$col");
            } else {
                $this->select->where("$col = :$col");
            }
            $this->select->bindValue($col, $value);
        }
        return $this;
    }

    public function fetchOne()
    {
        if (! $this->select) {
            throw Exception("No query to execute");
        }
        $result = $this->connection->fetchOne($this->select->__toString(), $this->select->getBindValues());
        return $this->createObject($result);
    }

    public function fetchAll()
    {
        if (! $this->select) {
            throw Exception("No query to execute");
        }
        $result = $this->connection->fetchAll($this->select->__toString(), $this->select->getBindValues());
        return $this->createObjects($result);
    }

    protected function createObjects($results)
    {
        $objects = array();
        foreach ($results as $result) {
            $objects[] = $this->createObject($result);
        }
        return $objects;
    }

    protected function createObject($result)
    {
        return new Author($result['id'], $result['name']);
    }

    public function getTable()
    {
        return $this->table;
    }
}

$query_factory = new QueryFactory('mysql');
$pdo = new ExtendedPdo(
    'mysql:host=localhost;dbname=closure',
    'root',
    'mysqlroot',
    array(), // driver options as key-value pairs
    array()  // attributes as key-value pairs
);
$pdo->setProfiler(new Profiler);
$pdo->getProfiler()->setActive(true);

$author_repository = new AuthorRepository($pdo, $query_factory);
$post_repository = new PostRepository($pdo, $query_factory, $author_repository);
$post = $post_repository->select(true)
            ->where(array('p.id' => 1))
            ->fetchOne();

echo "Post id " . $post->id() . PHP_EOL;
echo "Post title " . $post->title() . PHP_EOL;
echo "Post content " . $post->body() . PHP_EOL;
echo "Post author " . $post->author() . PHP_EOL;

// no joins
$post = $post_repository->select()
    ->where(array('p.id' => 2))
    ->fetchOne();
echo PHP_EOL . "Post id " . $post->id() . PHP_EOL;
echo "Post title " . $post->title() . PHP_EOL;
echo "Post content " . $post->body() . PHP_EOL;
echo "Post author " . $post->author() . PHP_EOL;

$post = $post_repository->select()
    ->fetchAll();

$profiles = $pdo->getProfiler()->getProfiles();

foreach ($profiles as $profile) {
    echo PHP_EOL . "Function : " . $profile['function'] . PHP_EOL;
    echo PHP_EOL . "Statement : " . PHP_EOL . $profile['statement'] . PHP_EOL;
    echo PHP_EOL . "Bind values : " . PHP_EOL . var_export($profile['bind_values'], true) . PHP_EOL;
}

I don't know whether I should call it as repository or something else.

Interested to hear your thoughts. This is slightly modified than the one in gist https://gist.github.com/harikt/26d244f87db8061eff97 .

Somethings in mind are may be use a __call which can handle the select query instance , so can use the limit etc can be called or may be provide an abstract class.

Looking forward to hear your thoughts.

Sub-transactions

Here's a custom extension I'm using:

class Connection extends ExtendedPdo
{
    /**
     * @var int number of nested calls to transact()
     * @see transact()
     */
    private $transaction_level = 0;

    /**
     * @var bool net result of nested calls to transact()
     * @see transact()
     */
    private $transaction_result;

    /**
     * @param callable $func function to call; must return TRUE to commit or FALSE to roll back
     *
     * @return bool TRUE on success (committed) or FALSE on failure (rolled back)
     *
     * @throws InvalidArgumentException if the provided argument is not a callable function
     * @throws LogicException if an unhandled Exception occurs while calling the provided function
     * @throws UnexpectedValueException if the provided function does not return TRUE or FALSE
     */
    public function transact($func)
    {
        if (! is_callable($func)) {
            throw new InvalidArgumentException("invalid argument: \$func is not callable");
        }

        if ($this->transaction_level === 0) {
            // starting a new stack of transactions - assume success:
            $this->beginTransaction();
            $this->transaction_result = true;
        }

        $this->transaction_level += 1;

        $commit = false; // assume roll back

        try {
            $commit = call_user_func($func);
        } catch (Exception $exception) {
            $commit = false;
        }

        $this->transaction_result = (bool) $commit && $this->transaction_result;

        $this->transaction_level -= 1;

        if ($this->transaction_level === 0 && $this->transaction_result === true) {
            $this->commit();
            return true; // the net transaction is a success!
        }

        if ($this->transaction_result === false) {
            $this->rollBack();
        }

        if (isset($exception)) {
            // re-throw an unhandled Exception:
            throw new LogicException("unhandled Exception during transaction", 0, $exception);
        }

        if (! is_bool($commit)) {
            throw new UnexpectedValueException("\$func must return TRUE (to commit) or FALSE (to roll back)");
        }

        return $this->transaction_result;
    }
}

This new method will track sub-transactions, so you can do something like this:

$connection->transact(function() {
    return true; // commits the transaction (false would roll back the transaction)
});

This eliminates the need to synchronize calls to beginTransaction() and commit() or rollBack() and instead performs transactions "transactionally", heh ;-)

An important point here, is that nested calls to transact() will be counted, and if any nested call returns false (or throws an exception) the entire transaction will correctly roll back.

If it were up to me, there wouldn't be any other way to perform transactions - having to call begin/commit/rollback individually is semantically "broken", since it does resemble a transaction or guarantee (the only correct) execution order.

This is fully unit-tested and works. Let me know if you're interested in integrating this?

Release

Hey @pmjones ,

Do you think we can release 2.0.1 1.0.1 or 2.1 1.1 for the Aura.Sql with the new features?

Parameter extraction is wrong with some SQL syntax

Using PostgreSQL at least three different cases of SQL keywords are considered parameters.

First is the cast operator "::"

$pdo->perform('SELECT 1::TEXT', ['someparameter' => 'b']);
This one can be avoided using the verbose way to cast but the short syntax looks better.
The problem comes from https://github.com/auraphp/Aura.Sql/blob/2.x/src/Rebuilder.php#L159 for which a quick fix could be "/[^:]+(:[a-zA-Z_][a-zA-Z0-9_]*)|(\?)/m"

Second one is using colons in comments:
$pdo->perform(' -- :should not be replaced SELECT 1', ['someparameter' => 'b']);
$pdo->perform(" /* :should not be replaced */ SELECT '2'", ['someparameter' => 'b']);

The third problem comes with the new jsonb operators: ?, ?| and ?&

$pdo->perform('SELECT CAST(\'{"a":1, "b": 2, "c": 3}\' AS JSONB) ? \'b\'', ['someparameter' => 'b']);

This third one can hardly be fixed by the user.

I think the best way to handle those cases and I'm sure a lot more with other RDBMS would be to not use regular expressions to parse the query but a simple state machine. And have one per driver.

git checkout develop error

Hi @pmjones ,

I was trying to checkout to develop branch. But I was getting error: pathspec 'develop' did not match any file(s) known to git. . So I removed my repo and forked again and the same happening :( .

Is it may be due to wrong way done by release script ?

For this reason probably no more bug fixes by others or PR will come. We want to fix ASAP .

hari@hari-Vostro1510:/media/Linux/aurasystem/package$ git clone [email protected]:harikt/Aura.Sql.git
Cloning into Aura.Sql...
remote: Counting objects: 1267, done.
remote: Compressing objects: 100% (594/594), done.
remote: Total 1267 (delta 588), reused 1207 (delta 531)
Receiving objects: 100% (1267/1267), 910.96 KiB | 47 KiB/s, done.
Resolving deltas: 100% (588/588), done.
hari@hari-Vostro1510:/media/Linux/aurasystem/package$ cd Aura.Sql/
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git remote add upstream https://github.com/auraphp/Aura.Sql
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git fetch upstream
From https://github.com/auraphp/Aura.Sql
 * [new branch]      1.0.0-beta2 -> upstream/1.0.0-beta2
 * [new branch]      develop    -> upstream/develop
 * [new branch]      gh-pages   -> upstream/gh-pages
 * [new branch]      master     -> upstream/master
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git checkout develop
error: pathspec 'develop' did not match any file(s) known to git.
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git checkout upstream/develop 
Note: checking out 'upstream/develop'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 47b3e06... update composer.json
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git status
# Not currently on any branch.
nothing to commit (working directory clean)
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git branch
* (no branch)
  master

Gateway mapper

I wished for the v1, we don't have asked for the gateway_name to insert the object. Rather get the class name from the object and use it as gateway_name by default.

Currently as the gateway_name is the first parameter for UnitOfWork.php we cannot alter it also.

$this->unit_of_work->insert('Entity\Article', $source);

For the consideration of v2 mappers.

lastInsertId()

I was looking into the method lastInsertId ,

Postgres have a different way and I am not sure MSSQL has something different too for I don't remember or used it recently .

I feel the method public function lastInsertId($table = '' , $col = '' ) can be moved to AbstractDriver.php , so we don't repeat the same method in Mysql.php https://github.com/auraphp/Aura.Sql/blob/master/src/Aura/Sql/Driver/Mysql.php#L162 and Sqlite.php https://github.com/auraphp/Aura.Sql/blob/master/src/Aura/Sql/Driver/Sqlite.php#L228 .

public function lastInsertId($table = '' , $col = '' )
{
    $pdo = $this->getPdo();
    return $pdo->lastInsertId();
}

And we can override it from Pgsql.php https://github.com/auraphp/Aura.Sql/blob/master/src/Aura/Sql/Driver/Pgsql.php#L234 in the same fashion. Do you think its a good way ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.