From 01bd45d05ce58796db7540d60671b8cff5d46bff Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Mon, 4 Mar 2013 18:51:17 +0100 Subject: [PATCH] flush --- lib/pq/Gateway/Cell.php | 89 ++++++++++++++++++++++ lib/pq/Gateway/Row.php | 108 ++++++++++++++++++++++++--- lib/pq/Gateway/Rowset.php | 151 ++++++++++++++++++++++++++++++++++++-- lib/pq/Gateway/Table.php | 102 +++++++++++-------------- lib/pq/Query/Expr.php | 35 ++++++++- lib/pq/Query/Writer.php | 55 ++++++++++++-- 6 files changed, 452 insertions(+), 88 deletions(-) diff --git a/lib/pq/Gateway/Cell.php b/lib/pq/Gateway/Cell.php index 1955552..730efb0 100644 --- a/lib/pq/Gateway/Cell.php +++ b/lib/pq/Gateway/Cell.php @@ -2,6 +2,95 @@ namespace pq\Gateway; +use \pq\Query\Expr; + class Cell { + /** + * @var \pq\Gateway\Row + */ + protected $row; + + /** + * @var string + */ + protected $name; + + /** + * @var mixed + */ + protected $data; + + /** + * @param \pq\Gateway\Row $row + * @param string $name + * @param mixed $data + */ + function __construct(Row $row, $name, $data) { + $this->row = $row; + $this->name = $name; + $this->data = $data; + } + + /** + * Get value as string + * @return string + */ + function __toString() { + return (string) $this->data; + } + + /** + * Test whether the value is an unevaluated expression + * @return bool + */ + function isExpr() { + return $this->data instanceof Expr; + } + + /** + * Get value + * @return mixed + */ + function get() { + return $this->data; + } + + /** + * Modify the value in this cell + * @param mixed $data + * @param string $op a specific operator + * @return \pq\Gateway\Cell + */ + function mod($data, $op = null) { + if (!($this->data instanceof Expr)) { + if (!isset($this->data)) { + $this->data = new Expr($this->name); + } elseif (is_numeric($this->data)) { + $this->data = new Expr($this->data); + } else { + $this->data = new Expr("%s", $this->row->getTable()->getConnection()->quote($this->data)); + } + } + + if ($data instanceof Expr) { + $this->data->add($data); + } elseif (!isset($op) && is_numeric($data)) { + $this->data->add(new Expr("+ $data")); + } else { + $data = $this->row->getTable()->getConnection()->quote($data); + $this->data->add(new Expr("%s %s"), isset($op) ? $op : "||", $data); + } + return $this; + } + + /** + * Set the value in this cell + * @param mixed $data + * @return \pq\Gateway\Cell + */ + function set($data) { + $this->data = $data; + return $this; + } } diff --git a/lib/pq/Gateway/Row.php b/lib/pq/Gateway/Row.php index 981a58d..885c95a 100644 --- a/lib/pq/Gateway/Row.php +++ b/lib/pq/Gateway/Row.php @@ -2,7 +2,7 @@ namespace pq\Gateway; -class Row +class Row implements \JsonSerializable { /** * @var \pq\Gateway\Table @@ -22,34 +22,122 @@ class Row /** * @param \pq\Gateway\Table $table * @param array $data + * @param bool $prime whether to mark all columns as modified */ - function __construct(Table $table, array $data = null) { + function __construct(Table $table, array $data = null, $prime = false) { $this->table = $table; $this->data = $data; + + if ($prime) { + $this->prime(); + } + } + + /** + * Copy constructor + * @param array $data + * @return \pq\Gateway\Row + */ + function __invoke(array $data) { + $that = clone $this; + $that->data = $data; + return $that->prime(); + } + + /** + * @implements JsonSerializable + * @return array + */ + function jsonSerialize() { + return $this->data; + } + + /** + * @return \pq\Gateway\Table + */ + function getTable() { + return $this->table; + } + + /** + * @return array + */ + function getData() { + return $this->data; + } + + /** + * Fill modified cells + * @return \pq\Gateway\Row + */ + protected function prime() { + $this->mods = array(); + foreach ($this->data as $key => $val) { + $this->mods[$key] = new Cell($this, $key, $val); + } + return $this; } + /** + * Transform data array to where criteria + * @param array $data + * @return array + */ + protected function criteria() { + $where = array(); + array_walk($this->data, function($v, $k) use (&$where) { + $where["$k="] = $v; + }); + return $where; + } + + /** + * Get a cell + * @param string $p + * @return \pq\Gateway\Cell + */ function __get($p) { - if (!isset($this->mod[$p])) { - $this->mod[$p] = new Cell($this, $p); + if (!isset($this->mods[$p])) { + $this->mods[$p] = new Cell($this, $p, $this->data[$p]); } - return $this->mod[$p]; + return $this->mods[$p]; + } + + /** + * Set a cell value + * @param string $p + * @param mixed $v + */ + function __set($p, $v) { + $this->__get($p)->set(($v instanceof Cell) ? $v->get() : $v); } + /** + * Create this row in the database + * @return \pq\Gateway\Row + */ function create() { - $this->data = $this->table->create($this->mods)->getIterator()->current()->data; + $this->data = $this->table->create($this->mods)->current()->data; $this->mods = array(); return $this; } + /** + * Update this row in the database + * @return \pq\Gateway\Row + */ function update() { - $this->data = $this->table->update($this->data, $this->mods)->getIterator()->current()->data; + $this->data = $this->table->update($this->criteria(), $this->mods)->current()->data; $this->mods = array(); return $this; } + /** + * Delete this row in the database + * @return \pq\Gateway\Row + */ function delete() { - $this->data = $this->table->delete($this->data, "*")->getIterator()->current()->data; - $this->mods = array(); - return $this; + $this->data = $this->table->delete($this->criteria(), "*")->current()->data; + return $this->prime(); } } diff --git a/lib/pq/Gateway/Rowset.php b/lib/pq/Gateway/Rowset.php index 4cf062f..2527b02 100644 --- a/lib/pq/Gateway/Rowset.php +++ b/lib/pq/Gateway/Rowset.php @@ -2,35 +2,170 @@ namespace pq\Gateway; -class Rowset implements \IteratorAggregate +class Rowset implements \SeekableIterator, \Countable, \JsonSerializable { /** * @var \pq\Gateway\Table */ protected $table; + /** + * @var int + */ + protected $index = 0; + /** * @var array */ protected $rows; + /** + * @var string + */ + protected $row; + /** * @param \pq\Gateway\Table $table * @param \pq\Result $result */ - function __construct(Table $table, \pq\Result $result, $rowClass = "\\pq\\Gateway\\Row") { + function __construct(Table $table, \pq\Result $result, $row = "\\pq\\Gateway\\Row") { $this->table = $table; - while (($row = $result->fetchRow(\pq\Result::FETCH_ASSOC))) { - $this->rows[] = new $rowClass($this->table, $row); + $this->row = $row; + + $this->hydrate($result); + } + + /** + * Copy constructor + * @param \pq\Result $result + * @return \pq\Gateway\Rowset + */ + function __invoke(\pq\Result $result) { + $that = clone $this; + $that->hydrate($result); + return $that; + } + + /** + * + * @param \pq\Result $result + * @return array + */ + protected function hydrate(\pq\Result $result) { + $this->index = 0; + $this->rows = array(); + $row = $this->row; + + if (is_callable($row)) { + while (($data = $result->fetchRow(\pq\Result::FETCH_ASSOC))) { + $this->rows[] = $row($data); + } + } else { + while (($data = $result->fetchRow(\pq\Result::FETCH_ASSOC))) { + $this->rows[] = new $row($this->table, $data); + } + } + return $this; + } + + /** + * @return \pq\Gateway\Table + */ + function getTable() { + return $this->table; + } + + function create() { + array_map(function ($row) { + $row->create(); + }, $this->rows); + return $this; + } + + function update() { + array_map(function ($row) { + $row->update(); + }, $this->rows); + return $this; + } + + function delete() { + array_map(function ($row) { + $row->delete(); + }, $this->rows); + return $this; + } + + /** + * @implements JsonSerilaizable + */ + function jsonSerialize() { + return array_map(function($row) { + return $row->jsonSerialize(); + }, $this->rows); + } + + /** + * @implements \Iterator + */ + function rewind() { + $this->index = 0; + } + /** + * @implements \Iterator + */ + function next() { + ++$this->index; + } + /** + * @implements \Iterator + * @return bool + */ + function valid() { + return $this->index < count($this->rows); + } + /** + * @implements \Iterator + * @return \pq\Gateway\Row + */ + function current() { + return $this->rows[$this->index]; + } + /** + * @implements \Iterator + * @return int + */ + function key() { + return $this->index; + } + + /** + * @implements SeekableIterator + * @param mixed $pos + */ + function seek($pos) { + /* only index for now */ + $this->index = $pos; + + if (!$this->valid()) { + throw new \OutOfBoundsException("Invalid seek position ($pos)"); } } /** - * @implements \IteratorAggregate - * @return \pq\Gateway\ArrayIterator + * @implements \Countable + * @return int + */ + function count() { + return count($this->rows); + } + + /** + * Get the rows of this rowset + * @return array */ - function getIterator() { - return new \ArrayIterator($this->rows); + function getRows() { + return $this->rows; } /** diff --git a/lib/pq/Gateway/Table.php b/lib/pq/Gateway/Table.php index 295e7a2..0807626 100644 --- a/lib/pq/Gateway/Table.php +++ b/lib/pq/Gateway/Table.php @@ -9,7 +9,7 @@ class Table /** * @var \pq\Connection */ - protected $connection; + protected $conn; /** * @var string @@ -22,49 +22,51 @@ class Table protected $rowset; /** - * @param \pq\Connection $c + * @param \pq\Connection $conn * @param string $name */ - function __construct(\pq\Connection $c, $name, $rowset = "\\pq\\Gateway\\Rowset") { - $this->connection = $c; - $this->name = $name; + function __construct(\pq\Connection $conn, $name, $rowset = "\\pq\\Gateway\\Rowset") { + $this->conn = $conn; + $this->name = $name; $this->rowset = $rowset; } /** - * Accessor to read-only properties - * @param string $p + * @return \pq\Connection */ - function __get($p) { - return $this->$p; + function getConnection() { + return $this->conn; + } + + /** + * @return string + */ + function getName() { + return $this->name; } /** + * Execute the query * @param \pq\Query\Writer $query - * @param array $criteria - * @param string $join + * @return mixed */ - protected function criteria(QueryWriter $query, array $criteria, $join = "AND") { - $joinable = false; - $query->write("("); - foreach ($criteria as $lop => $rop) { - if (is_array($rop)) { - if ($joinable) { - $query->write(")", $join, "("); - } - $this->criteria($query, $rop, is_int($lop) ? "AND" : $lop); - } else { - if ($joinable) { - $query->write(")", $join, "("); - } - if (!is_int($lop)) { - $query->write($lop); - } - $query->write($query->param($rop)); - } - $joinable or $joinable = true; + protected function execute(QueryWriter $query) { + $result = $query->exec($this->conn); + + if ($result->status != \pq\Result::TUPLES_OK) { + return $result; + } + + if (is_callable($this->rowset)) { + return call_user_func($this->rowset, $result); } - $query->write(")"); + + if ($this->rowset) { + $rowset = $this->rowset; + return new $rowset($this, $result); + } + + return $result; } /** @@ -76,9 +78,9 @@ class Table * @return \pq\Result */ function find(array $where = null, $order = null, $limit = 0, $offset = 0) { - $query = new QueryWriter("SELECT * FROM ". $this->connection->quoteName($this->name)); + $query = new QueryWriter("SELECT * FROM ". $this->conn->quoteName($this->name)); if ($where) { - $this->criteria($query->write("WHERE"), $where); + $query->write("WHERE")->criteria($where); } if ($order) { $query->write("ORDER BY", $order); @@ -87,7 +89,7 @@ class Table $query->write("LIMIT", $limit); } $query->write("OFFSET", $offset); - return new Rowset($this, $query->exec($this->connection)); + return $this->execute($query); } /** @@ -98,7 +100,7 @@ class Table */ function create(array $data, $returning = "*") { $params = array(); - $query = new QueryWriter("INSERT INTO ".$this->connection->quoteName($this->name)." ("); + $query = new QueryWriter("INSERT INTO ".$this->conn->quoteName($this->name)." ("); foreach ($data as $key => $val) { $query->write($key); $params[] = $query->param($val); @@ -107,12 +109,7 @@ class Table if (strlen($returning)) { $query->write("RETURNING", $returning); } - $result = $query->exec($this->connection); - if ($result->status == \pq\Result::TUPLES_OK) { - $rowset = $this->rowset; - return new $rowset($this, $result); - } - return $result; + return $this->execute($query); } /** @@ -123,20 +120,15 @@ class Table * @retunr \pq\Result */ function update(array $where, array $data, $returning = "*") { - $query = new QueryWriter("UPDATE ".$this->connection->quoteName($this->name)." SET"); + $query = new QueryWriter("UPDATE ".$this->conn->quoteName($this->name)." SET"); foreach ($data as $key => $val) { $query->write($key, "=", $query->param($val)); } - $this->criteria($query->write("WHERE"), $where); + $query->write("WHERE")->criteria($where); if (strlen($returning)) { $query->write("RETURNING", $returning); } - $result = $query->exec($this->connection); - if ($result->status == \pq\Result::TUPLES_OK) { - $rowset = $this->rowset; - return new $rowset($this, $result); - } - return $result; + return $this->execute($query); } /** @@ -146,17 +138,11 @@ class Table * @return pq\Result */ function delete(array $where, $returning = null) { - $query = new QueryWriter("DELETE FROM ".$this->connection->quoteName($this->name)); - $this->criteria($query->write("WHERE"), $where); + $query = new QueryWriter("DELETE FROM ".$this->conn->quoteName($this->name)); + $query->write("WHERE")->criteria($where); if (strlen($returning)) { $query->write("RETURNING", $returning); } - $result = $query->exec($this->connection); - if ($result->status == \pq\Result::TUPLES_OK) { - $rowset = $this->rowset; - return new $rowset($this, $result); - } - return $result; + return $this->execute($query); } } - diff --git a/lib/pq/Query/Expr.php b/lib/pq/Query/Expr.php index d02b13f..ae87c1f 100644 --- a/lib/pq/Query/Expr.php +++ b/lib/pq/Query/Expr.php @@ -8,6 +8,11 @@ class Expr * @var string */ protected $expression; + + /** + * @var \pq\Query\Expr + */ + protected $next; /** * @param string $e the expression or a format string followed by arguments @@ -15,10 +20,9 @@ class Expr */ function __construct($e) { if (func_num_args() > 1) { - $this->expression = call_user_func_array("sprintf", func_get_args()); - } else { - $this->expression = $e; + $e = call_user_func_array("sprintf", func_get_args()); } + $this->expression = trim($e); } /** @@ -26,6 +30,29 @@ class Expr * @return string */ function __toString() { - return (string) $this->expression; + return (string) $this->expression . $this->next; + } + + /** + * Check for NULL + * @return bool + */ + function isNull() { + return !strcasecmp($this->expression, "null"); + } + + /** + * Append an expresssion + * @param \pq\Query\Expr $next + * @return \pq\Query\Expr $this + * @throws \UnexpectedValueException if any expr is NULL + */ + function add(Expr $next) { + if ($this->isNull() || $next->isNull()) { + throw new \UnexpectedValueException("Cannot add anything to NULL"); + } + for ($that = $this; $that->next; $that = $that->next); + $that->next = $next; + return $this; } } diff --git a/lib/pq/Query/Writer.php b/lib/pq/Query/Writer.php index 61589c3..e25a1fc 100644 --- a/lib/pq/Query/Writer.php +++ b/lib/pq/Query/Writer.php @@ -28,9 +28,9 @@ class Writer * @param array $types the types of the params */ function __construct($query = "", array $params = array(), array $types = array()) { - $this->query = $query; + $this->query = $query; $this->params = $params; - $this->types = $types; + $this->types = $types; } /** @@ -40,6 +40,16 @@ class Writer function __toString() { return $this->query; } + + /** + * Reduce arguments to write() + * @param string $q + * @param mixed $v + * @return string + */ + protected function reduce($q, $v) { + return $q . " " . (is_array($v) ? implode(", ", $v) : $v); + } /** * Get the query params @@ -62,9 +72,9 @@ class Writer * @return \pq\Query\Writer */ function reset() { - $this->query = ""; + $this->query = ""; $this->params = array(); - $this->types = array(); + $this->types = array(); return $this; } @@ -73,9 +83,7 @@ class Writer * @return \pq\Query\Writer */ function write() { - $this->query .= array_reduce(func_get_args(), function($q, $v) { - return $q . " " . (is_array($v) ? implode(", ", $v) : $v); - }); + $this->query .= array_reduce(func_get_args(), array($this, "reduce")); return $this; } @@ -89,10 +97,39 @@ class Writer if ($param instanceof Expr) { return (string) $param; } + $this->params[] = $param; - $this->types[] = $type; + $this->types[] = $type; + return "\$".count($this->params); } + + /** + * Write nested AND/OR criteria + * @param array $criteria + * @return \pq\Query\Writer + */ + function criteria(array $criteria) { + if ((list($left, $right) = each($criteria))) { + array_shift($criteria); + $this->write("("); + if (is_array($right)) { + $this->criteria($right); + } else { + $this->write("(", $left, $this->param($right), ")"); + } + foreach ($criteria as $left => $right) { + $this->write(is_int($left) && is_array($right) ? "OR" : "AND"); + if (is_array($right)) { + $this->criteria($right); + } else { + $this->write("(", $left, $this->param($right), ")"); + } + } + $this->write(")"); + } + return $this; + } /** * Execute the query through \pq\Connection::execParams($this, $this->params, $this->types) @@ -100,6 +137,8 @@ class Writer * @return \pq\Result */ function exec(\pq\Connection $c) { + fprintf(STDERR, "Q: %s\n", $this); + fprintf(STDERR, "P: %s\n", implode(", ", $this->params)); return $c->execParams($this, $this->params, $this->types); } } -- 2.30.2