add support for one-dimensional arrays; type input parameters
authorMichael Wallner <mike@php.net>
Wed, 15 May 2013 05:37:40 +0000 (07:37 +0200)
committerMichael Wallner <mike@php.net>
Wed, 15 May 2013 05:37:40 +0000 (07:37 +0200)
lib/pq/Gateway/Cell.php
lib/pq/Gateway/Table.php
lib/pq/Gateway/Table/Attributes.php [new file with mode: 0644]
lib/pq/Query/Expressible.php
tests/lib/pq/Gateway/CellTest.php
tests/lib/pq/Gateway/RowTest.php
tests/lib/pq/Gateway/RowsetTest.php
tests/lib/pq/Gateway/TableTest.php
tests/setup.inc

index 33f94c8fe609a47aa204027d0fbe13e3a13b5be1..3c69a1294d015111db7629aee7234cb5e550dc45 100644 (file)
@@ -4,7 +4,7 @@ namespace pq\Gateway;
 
 use \pq\Query\Expressible;
 
-class Cell extends Expressible
+class Cell extends Expressible implements \ArrayAccess
 {
        /**
         * @var \pq\Gateway\Row
@@ -39,7 +39,7 @@ class Cell extends Expressible
         * @return bool
         */
        function isDirty() {
-               return $this->dirty;
+               return (bool) $this->dirty;
        }
        
        /**
@@ -73,4 +73,37 @@ class Cell extends Expressible
                return $this;
        }
        
+       function offsetGet($o) {
+               if (isset($this->data) && !is_array($this->data)) {
+                       throw new \UnexpectedValueException("Cell data is not an array");
+               }
+               return $this->data[$o];
+       }
+
+       function offsetSet($o, $v) {
+               if (isset($this->data) && !is_array($this->data)) {
+                       throw new \UnexpectedValueException("Cell data is not an array");
+               }
+               if (isset($o)) {
+                       $this->data[$o] = $v;
+               } else {
+                       $this->data[] = $v;
+               }
+               $this->dirty = true;
+       }
+
+       function offsetExists($o) {
+               if (isset($this->data) && !is_array($this->data)) {
+                       throw new \UnexpectedValueException("Cell data is not an array");
+               }
+               return isset($this->data[$o]);
+       }
+
+       function offsetUnset($o) {
+               if (isset($this->data) && !is_array($this->data)) {
+                       throw new \UnexpectedValueException("Cell data is not an array");
+               }
+               unset($this->data[$o]);
+               $this->dirty = true;
+       }
 }
index a25b2ad3fd6e07008bf59f27caa56239293756ac..e4adbb7a532642e23005871f27c6e16ea100f436 100644 (file)
@@ -53,6 +53,11 @@ class Table
         */
        protected $identity;
        
+       /**
+        * @var \pq\Gateway\Table\Attributes
+        */
+       protected $attributes;
+       
        /**
         * @var \pq\Gateway\Table\Relations
         */
@@ -205,6 +210,13 @@ class Table
                return $this->identity;
        }
        
+       function getAttributes() {
+               if (!isset($this->attributes)) {
+                       $this->attributes = new Table\Attributes($this);
+               }
+               return $this->attributes;
+       }
+       
        /**
         * Get foreign key relations
         * @param string $to fkey
@@ -430,7 +442,7 @@ class Table
                        $params = array();
                        foreach ($data as $key => $val) {
                                $query->write($first ? "(" : ",", $key);
-                               $params[] = $query->param($val);
+                               $params[] = $query->param($val, $this->getAttributes()->getColumn($key)->type);
                                $first and $first = false;
                        }
                        $query->write(") VALUES (", $params, ")");
@@ -456,7 +468,8 @@ class Table
                $query->write("UPDATE", $this->conn->quoteName($this->name));
                $first = true;
                foreach ($data as $key => $val) {
-                       $query->write($first ? "SET" : ",", $key, "=", $query->param($val));
+                       $query->write($first ? "SET" : ",", $key, "=", 
+                               $query->param($val, $this->getAttributes()->getColumn($key)->type));
                        $first and $first = false;
                }
                $query->write("WHERE")->criteria($where);
diff --git a/lib/pq/Gateway/Table/Attributes.php b/lib/pq/Gateway/Table/Attributes.php
new file mode 100644 (file)
index 0000000..39c55c9
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+namespace pq\Gateway\Table;
+
+use \pq\Gateway\Table;
+
+const ATTRIBUTES_SQL = <<<SQL
+       select 
+                attnum         as index
+               ,attname        as name
+               ,atttypid       as type
+               ,atthasdef      as hasdefault
+               ,not attnotnull as nullable
+       from
+                pg_attribute 
+       where attrelid = \$1::regclass 
+       and   attnum   > 0
+SQL;
+
+class Attributes
+{
+       /**
+        * @var array
+        */
+       protected $columns = array();
+       
+       /**
+        * @param \pq\Gateway\Table $table
+        */
+       function __construct(Table $table) {
+               $cache = $table->getMetadataCache();
+               if (!($this->columns = $cache->get("$table#attributes"))) {
+                       $table->getQueryExecutor()->execute(
+                               new \pq\Query\Writer(ATTRIBUTES_SQL, array($table->getName())), 
+                               function($result) use($table, $cache) {
+                                       foreach ($result->fetchAll(\pq\Result::FETCH_OBJECT) as $c) {
+                                               $this->columns[$c->index] = $this->columns[$c->name] = $c;
+                                       }
+                                       $cache->set("$table#attributes", $this->columns);
+                               }
+                       );
+               }
+       }
+       
+       /**
+        * @implements \Countable
+        * @return int
+        */
+       function count() {
+               return count($this->columns);
+       }
+       
+       /**
+        * Get all columns
+        * @return array
+        */
+       function getColumns() {
+               return $this->columns;
+       }
+       
+       /**
+        * Get a single column
+        * @param string $c
+        * @return object
+        */
+       function getColumn($c) {
+               if (!isset($this->columns[$c])) {
+                       throw new \OutOfBoundsException("Unknown column $c");
+               }
+               return $this->columns[$c];
+       }
+}
index ffcf7c2c0f0936d0b693ece11846b85261492156..dc2db66fdfffacd62ad9bfaca65fb58850d2c167 100644 (file)
@@ -63,10 +63,12 @@ class Expressible implements ExpressibleInterface
                } elseif (!isset($op) && is_numeric($data)) {
                        $this->data->add(new Expr("+ $data"));
                } else {
-                       $data = $this->row->getTable()->getConnection()->quote($data);
+                       if (!is_array($data)) {
+                               $data = $this->row->getTable()->getConnection()->quote($data);
+                       }
                        $this->data->add(new Expr("%s %s", isset($op) ? $op : "||", $data));
                }
                
                return $this;
-       }               
-}
\ No newline at end of file
+       }
+}
index 9235a39ec81487278d4ecb79494384505f33088f..de57a4fbcc7f25e91eaa6628382a88d3599c5adf 100644 (file)
@@ -34,7 +34,7 @@ class CellTest extends \PHPUnit_Framework_TestCase {
        public function testBasic() {
                $row = $this->table->find(null, "id desc", 1)->current();
                foreach ($row->getData() as $key => $val) {
-                       $this->assertEquals($val, (string) $row->$key);
+                       $this->assertEquals($val, $row->$key->get());
                        $this->assertFalse($row->$key->isExpr());
                        $this->assertFalse($row->$key->isDirty());
                        $this->assertSame($val, $row->$key->get());
@@ -61,4 +61,66 @@ class CellTest extends \PHPUnit_Framework_TestCase {
                $refs->seek(1)->current()->test = $rows->seek(0)->current();
                $refs->update();
        }
+       
+       public function testArray() {
+               $row = $this->table->find(["id="=>1])->current();
+               $this->assertEquals([-1,0,1], $row->list->get());
+               $row->list[] = 4;
+               $row->list[2] = null;
+               $row->update();
+               $this->assertEquals([-1,0,null,4], $row->list->get());
+       }
+       
+       public function testMultiArray() {
+               $row = $this->table->find(["id="=>2])->current();
+               $this->assertEquals([0,1,2], $row->list->get());
+               $row->list = [$row->list->get()];
+               $row->update();
+               $this->assertEquals([[0,1,2]], $row->list->get());
+               $this->setExpectedException("PHPUnit_Framework_Error_Notice", "Indirect modification of overloaded element of pq\Gateway\Cell has no effect");
+               $row->list[0][0] = -1;
+       }
+               
+       public function testHstore() {
+               $this->conn->setConverter(new Hstore(new \pq\Types($this->conn)));
+               $row = $this->table->find(["id="=>3])->current();
+               $this->assertEquals(null, $row->prop->get());
+               $data = array("foo" => "bar", "a" => 1, "b" => 2);
+               $row->prop = $data;
+               $row->update();
+               $this->assertEquals($data, $row->prop->get());
+               $row->prop["a"] = null;
+               $row->update();
+               $data["a"] = null;
+               $this->assertEquals($data, $row->prop->get());
+               unset($data["a"], $row->prop["a"]);
+               $row->update();
+               $this->assertEquals($data, $row->prop->get());
+       }       
+}
+
+class Hstore implements \pq\ConverterInterface
+{
+       protected $types;
+       function __construct(\pq\Types $types) {
+               $this->types = $types;
+       }
+       function convertTypes() {
+               return [$this->types["hstore"]->oid];
+       }
+       function convertFromString($string) {
+               return eval("return [$string];");
+       }
+       function convertToString($data) {
+               $string = "";
+               foreach ($data as $k => $v) {
+                       $string .= "\"".addslashes($k)."\"=>";
+                       if (isset($v)) {
+                               $string .= "\"".addslashes($v)."\",";
+                       } else {
+                               $string .= "NULL,";
+                       }
+               }
+               return $string;
+       }
 }
index 1d339639c27d5cda471ce5a68589750b9a3e1b23..c4adf8ce6a1482b041e84082bf051874e2bab073 100644 (file)
@@ -38,7 +38,9 @@ class RowTest extends \PHPUnit_Framework_TestCase {
                                "created" => new \pq\DateTime("tomorrow"),
                                "counter" => "1",
                                "number" => "1.1",
-                               "data" => "tomorrow"
+                               "data" => "tomorrow",
+                               "list" => array(1,2,3),
+                               "prop" => null
                        ),
                        $row->getData()
                );
index 6f16595994316ba4a49c0da7b89d275ca57bf463..49e1853b35088968b3e2cc4c853c38f34c223b4d 100644 (file)
@@ -71,7 +71,7 @@ class RowsetTest extends \PHPUnit_Framework_TestCase {
        }
        
        public function testCreateFail() {
-               $this->setExpectedException("\\pq\\Exception");
+               $this->setExpectedException("\\OutOfBoundsException");
                $rowset = new Rowset($this->table);
                $rowset->append(new Row($this->table, array("foo" => "bar"), true));
                $rowset->create();
@@ -111,9 +111,9 @@ class RowsetTest extends \PHPUnit_Framework_TestCase {
        }
 
        public function testJsonSerialize() {
-               $json = sprintf('[{"id":"1","created":"%s","counter":"-1","number":"-1.1","data":"yesterday"}'
-                       .',{"id":"2","created":"%s","counter":"0","number":"0","data":"today"}'
-                       .',{"id":"3","created":"%s","counter":"1","number":"1.1","data":"tomorrow"}]',
+               $json = sprintf('[{"id":"1","created":"%s","counter":"-1","number":"-1.1","data":"yesterday","list":[-1,0,1],"prop":null}'
+                       .',{"id":"2","created":"%s","counter":"0","number":"0","data":"today","list":[0,1,2],"prop":null}'
+                       .',{"id":"3","created":"%s","counter":"1","number":"1.1","data":"tomorrow","list":[1,2,3],"prop":null}]',
                        new \pq\DateTime("yesterday"),
                        new \pq\DateTime("today"),
                        new \pq\DateTime("tomorrow")
@@ -155,4 +155,14 @@ class RowsetTest extends \PHPUnit_Framework_TestCase {
                $this->assertSame(array(), $rowset3->getRows());
                $this->assertCount(1, $rowset->filter(function($row) { return $row->id->get() == 1; }));
        }
+       
+       public function testApplyAppend() {
+               $rowset1 = $this->table->find(null, null, 1);
+               $rowset2 = $this->table->find(null, null, 1, 1);
+               $this->assertCount(1, $rowset1);
+               $this->assertCount(1, $rowset2);
+               $rowset2->apply(array($rowset1, "append"));
+               $this->assertCount(1, $rowset2);
+               $this->assertCount(2, $rowset1);
+       }
 }
index a43634cf203114fee18a5f9a54d4ade57cce53c8..3f7b35c500ff8d004d726f735bf90026461ae999 100644 (file)
@@ -71,10 +71,12 @@ class TableTest extends \PHPUnit_Framework_TestCase {
                        "counter" => 2,
                        "number" => 2.2,
                        "data" => "this is a test",
+                       "list" => array(3,2,1),
+                       "prop" => null
                );
                $row = $this->table->update(array("id = " => $row->id), $data)->current();
                $data = array("id" => $row->id->get()) + $data;
-               $this->assertEquals(array_map(function($v){return strval($v);}, $data), $row->getData());
+               $this->assertEquals($data, $row->getData());
        }
 
        public function testDelete() {
@@ -91,7 +93,9 @@ class TableTest extends \PHPUnit_Framework_TestCase {
                        "created" => new \pq\DateTime("today"),
                        "counter" => 0,
                        "number" => 0,
-                       "data" => "today"
+                       "data" => "today",
+                       "list" => array(0,1,2),
+                       "prop" => null
                ), $rowset->current()->getData());
        }
 }
index 47998cf7353c1c7b40c877f66285cd6f4a9945f1..6f1b70501be8c91f2ba96a9fb5a88ea41338723d 100644 (file)
@@ -3,13 +3,17 @@
 const PQ_TEST_DSN = "";
 
 const PQ_TEST_SETUP_SQL = <<<SQL
+       create extension if not exists hstore;
+       
        drop table if exists test cascade;
        create table test (
                id serial primary key, 
                created timestamp, 
                counter int, 
                number decimal, 
-               data text
+               data text,
+               list int[],
+               prop hstore
        );
        
        drop table if exists reftest cascade;
@@ -18,9 +22,9 @@ const PQ_TEST_SETUP_SQL = <<<SQL
                another_test_id integer not null references test on delete cascade
        );
        
-       insert into test values (default, 'yesterday', -1, -1.1, 'yesterday');
-       insert into test values (default, 'today', 0, 0, 'today');
-       insert into test values (default, 'tomorrow', 1, 1.1, 'tomorrow');
+       insert into test values (default, 'yesterday', -1, -1.1, 'yesterday', '{-1,0,1}');
+       insert into test values (default, 'today', 0, 0, 'today', '{0,1,2}');
+       insert into test values (default, 'tomorrow', 1, 1.1, 'tomorrow', '{1,2,3}');
        
        insert into reftest values (1,3);
        insert into reftest values (2,2);