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;
+ }
}
namespace pq\Gateway;
-class Row
+class Row implements \JsonSerializable
{
/**
* @var \pq\Gateway\Table
/**
* @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();
}
}
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;
}
/**
/**
* @var \pq\Connection
*/
- protected $connection;
+ protected $conn;
/**
* @var string
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;
}
/**
* @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);
$query->write("LIMIT", $limit);
}
$query->write("OFFSET", $offset);
- return new Rowset($this, $query->exec($this->connection));
+ return $this->execute($query);
}
/**
*/
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);
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);
}
/**
* @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);
}
/**
* @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);
}
}
-
* @var string
*/
protected $expression;
+
+ /**
+ * @var \pq\Query\Expr
+ */
+ protected $next;
/**
* @param string $e the expression or a format string followed by arguments
*/
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);
}
/**
* @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;
}
}
* @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;
}
/**
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
* @return \pq\Query\Writer
*/
function reset() {
- $this->query = "";
+ $this->query = "";
$this->params = array();
- $this->types = array();
+ $this->types = array();
return $this;
}
* @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;
}
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)
* @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);
}
}