use \pq\Query\Expressible;
-class Cell extends Expressible
+class Cell extends Expressible implements \ArrayAccess
{
/**
* @var \pq\Gateway\Row
* @return bool
*/
function isDirty() {
- return $this->dirty;
+ return (bool) $this->dirty;
}
/**
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;
+ }
}
*/
protected $identity;
+ /**
+ * @var \pq\Gateway\Table\Attributes
+ */
+ protected $attributes;
+
/**
* @var \pq\Gateway\Table\Relations
*/
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
$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, ")");
$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);
--- /dev/null
+<?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];
+ }
+}
} 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
+ }
+}
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());
$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;
+ }
}
"created" => new \pq\DateTime("tomorrow"),
"counter" => "1",
"number" => "1.1",
- "data" => "tomorrow"
+ "data" => "tomorrow",
+ "list" => array(1,2,3),
+ "prop" => null
),
$row->getData()
);
}
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();
}
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")
$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);
+ }
}
"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() {
"created" => new \pq\DateTime("today"),
"counter" => 0,
"number" => 0,
- "data" => "today"
+ "data" => "today",
+ "list" => array(0,1,2),
+ "prop" => null
), $rowset->current()->getData());
}
}
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;
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);