From 409fca54acfee2db6c62540a8f67b1adfa695a38 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Wed, 15 May 2013 07:37:40 +0200 Subject: [PATCH] add support for one-dimensional arrays; type input parameters --- lib/pq/Gateway/Cell.php | 37 ++++++++++++++- lib/pq/Gateway/Table.php | 17 ++++++- lib/pq/Gateway/Table/Attributes.php | 72 +++++++++++++++++++++++++++++ lib/pq/Query/Expressible.php | 8 ++-- tests/lib/pq/Gateway/CellTest.php | 64 ++++++++++++++++++++++++- tests/lib/pq/Gateway/RowTest.php | 4 +- tests/lib/pq/Gateway/RowsetTest.php | 18 ++++++-- tests/lib/pq/Gateway/TableTest.php | 8 +++- tests/setup.inc | 12 +++-- 9 files changed, 221 insertions(+), 19 deletions(-) create mode 100644 lib/pq/Gateway/Table/Attributes.php diff --git a/lib/pq/Gateway/Cell.php b/lib/pq/Gateway/Cell.php index 33f94c8..3c69a12 100644 --- a/lib/pq/Gateway/Cell.php +++ b/lib/pq/Gateway/Cell.php @@ -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; + } } diff --git a/lib/pq/Gateway/Table.php b/lib/pq/Gateway/Table.php index a25b2ad..e4adbb7 100644 --- a/lib/pq/Gateway/Table.php +++ b/lib/pq/Gateway/Table.php @@ -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 index 0000000..39c55c9 --- /dev/null +++ b/lib/pq/Gateway/Table/Attributes.php @@ -0,0 +1,72 @@ + 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]; + } +} diff --git a/lib/pq/Query/Expressible.php b/lib/pq/Query/Expressible.php index ffcf7c2..dc2db66 100644 --- a/lib/pq/Query/Expressible.php +++ b/lib/pq/Query/Expressible.php @@ -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 + } +} diff --git a/tests/lib/pq/Gateway/CellTest.php b/tests/lib/pq/Gateway/CellTest.php index 9235a39..de57a4f 100644 --- a/tests/lib/pq/Gateway/CellTest.php +++ b/tests/lib/pq/Gateway/CellTest.php @@ -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; + } } diff --git a/tests/lib/pq/Gateway/RowTest.php b/tests/lib/pq/Gateway/RowTest.php index 1d33963..c4adf8c 100644 --- a/tests/lib/pq/Gateway/RowTest.php +++ b/tests/lib/pq/Gateway/RowTest.php @@ -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() ); diff --git a/tests/lib/pq/Gateway/RowsetTest.php b/tests/lib/pq/Gateway/RowsetTest.php index 6f16595..49e1853 100644 --- a/tests/lib/pq/Gateway/RowsetTest.php +++ b/tests/lib/pq/Gateway/RowsetTest.php @@ -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); + } } diff --git a/tests/lib/pq/Gateway/TableTest.php b/tests/lib/pq/Gateway/TableTest.php index a43634c..3f7b35c 100644 --- a/tests/lib/pq/Gateway/TableTest.php +++ b/tests/lib/pq/Gateway/TableTest.php @@ -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()); } } diff --git a/tests/setup.inc b/tests/setup.inc index 47998cf..6f1b705 100644 --- a/tests/setup.inc +++ b/tests/setup.inc @@ -3,13 +3,17 @@ const PQ_TEST_DSN = ""; const PQ_TEST_SETUP_SQL = <<