flush
authorMichael Wallner <mike@php.net>
Mon, 4 Mar 2013 17:51:17 +0000 (18:51 +0100)
committerMichael Wallner <mike@php.net>
Mon, 4 Mar 2013 17:51:17 +0000 (18:51 +0100)
lib/pq/Gateway/Cell.php
lib/pq/Gateway/Row.php
lib/pq/Gateway/Rowset.php
lib/pq/Gateway/Table.php
lib/pq/Query/Expr.php
lib/pq/Query/Writer.php

index 1955552c3858508672746b4f252e2c33fd3c3a7f..730efb07bc4a36c1058bd77a3878a5bd6e755d85 100644 (file)
@@ -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;
+       }
 }
index 981a58d9afad2db64c150420b32c23138478d1b6..885c95abd5a166b51cb35e6281aa72280e6a67f7 100644 (file)
@@ -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();
        }
 }
index 4cf062f6f149a1c1ffbd37d956bc01c3f5dc2d0e..2527b029a1f629cad222161c05e73cf84540a216 100644 (file)
 
 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;
        }
        
        /**
index 295e7a2c60f5db4aa7c5dccd607d07c401c36e5c..0807626f42156bf6e09e0b1be50c0f7dfec0664b 100644 (file)
@@ -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);
        }
 }
-
index d02b13fea5d07781a9f136eb249fff5de8963796..ae87c1f92abe4b4bdd4d20f1cc6d5c2d9f9b6ce0 100644 (file)
@@ -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;
        }
 }
index 61589c3a1cce081006e4b23793cf4b0d9b0bdfde..e25a1fc7d15c71b991b05de81339771b4f531e36 100644 (file)
@@ -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);
        }
 }