add support for one-dimensional arrays; type input parameters
[m6w6/pq-gateway] / lib / pq / Gateway / Table.php
1 <?php
2
3 namespace pq\Gateway;
4
5 use \pq\Query\Expr as QueryExpr;
6 use \pq\Query\Writer as QueryWriter;
7 use \pq\Query\Executor as QueryExecutor;
8
9 class Table
10 {
11 /**
12 * @var \pq\Connection
13 */
14 public static $defaultConnection;
15
16 /**
17 * @var callable
18 */
19 public static $defaultResolver;
20
21 /**
22 * @var \pq\Gateway\Table\CacheInterface
23 */
24 public static $defaultMetadataCache;
25
26 /**
27 * @var \pq\Connection
28 */
29 protected $conn;
30
31 /**
32 * @var string
33 */
34 protected $name;
35
36 /**
37 * @var string
38 */
39 protected $rowset = "\\pq\\Gateway\\Rowset";
40
41 /**
42 * @var \pq\Query\WriterIterface
43 */
44 protected $query;
45
46 /**
47 * @var \pq\Query\ExecutorInterface
48 */
49 protected $exec;
50
51 /**
52 * @var \pq\Gateway\Table\Identity
53 */
54 protected $identity;
55
56 /**
57 * @var \pq\Gateway\Table\Attributes
58 */
59 protected $attributes;
60
61 /**
62 * @var \pq\Gateway\Table\Relations
63 */
64 protected $relations;
65
66 /**
67 * @var \pq\Gateway\Table\CacheInterface
68 */
69 protected $metadataCache;
70
71 /**
72 * @var \pq\Gateway\Table\LockInterface
73 */
74 protected $lock;
75
76 /**
77 * @param string $table
78 * @return \pq\Gateway\Table
79 */
80 public static function resolve($table) {
81 if ($table instanceof Table) {
82 return $table;
83 }
84 if (is_callable(static::$defaultResolver)) {
85 if (($resolved = call_user_func(static::$defaultResolver, $table))) {
86 return $resolved;
87 }
88 }
89 return new Table($table);
90 }
91
92 /**
93 * @param string $name
94 * @param \pq\Connection $conn
95 * @param array $dependents
96 */
97 function __construct($name = null, \pq\Connection $conn = null) {
98 if (isset($name)) {
99 $this->name = $name;
100 } elseif (!isset($this->name)) {
101 throw new \InvalidArgumentException("Table must have a name");
102 }
103 $this->conn = $conn ?: static::$defaultConnection ?: new \pq\Connection;
104 }
105
106 /**
107 * Get the complete PostgreSQL connection string
108 * @return string
109 */
110 function __toString() {
111 return sprintf("postgresql://%s:%s@%s:%d/%s?%s#%s",
112 $this->conn->user,
113 $this->conn->pass,
114 $this->conn->host,
115 $this->conn->port,
116 $this->conn->db,
117 $this->conn->options,
118 $this->getName()
119 );
120 }
121
122 /**
123 * Set the rowset prototype
124 * @param mixed $rowset
125 * @return \pq\Gateway\Table
126 */
127 function setRowsetPrototype($rowset) {
128 $this->rowset = $rowset;
129 return $this;
130 }
131
132 /**
133 * Get the rowset prototype
134 * @return mixed
135 */
136 function getRowsetPrototype() {
137 return $this->rowset;
138 }
139
140 /**
141 * Set the query writer
142 * @param \pq\Query\WriterInterface $query
143 * @return \pq\Gateway\Table
144 */
145 function setQueryWriter(\pq\Query\WriterInterface $query) {
146 $this->query = $query;
147 return $this;
148 }
149
150 /**
151 * Get the query writer
152 * @return \pq\Query\WriterInterface
153 */
154 function getQueryWriter() {
155 if (!$this->query) {
156 $this->query = new QueryWriter;
157 }
158 return $this->query;
159 }
160
161 /**
162 * Set the query executor
163 * @param \pq\Query\ExecutorInterface $exec
164 * @return \pq\Gateway\Table
165 */
166 function setQueryExecutor(\pq\Query\ExecutorInterface $exec) {
167 $this->exec = $exec;
168 return $this;
169 }
170
171 /**
172 * Get the query executor
173 * @return \pq\Query\ExecutorInterface
174 */
175 function getQueryExecutor() {
176 if (!$this->exec) {
177 $this->exec = new QueryExecutor($this->conn);
178 }
179 return $this->exec;
180 }
181
182 /**
183 * Get the metadata cache
184 * @return \pq\Gateway\Table\CacheInterface
185 */
186 function getMetadataCache() {
187 if (!isset($this->metadatCache)) {
188 $this->metadataCache = static::$defaultMetadataCache ?: new Table\StaticCache;
189 }
190 return $this->metadataCache;
191 }
192
193 /**
194 * Set the metadata cache
195 * @param \pq\Gateway\Table\CacheInterface $cache
196 */
197 function setMetadataCache(Table\CacheInterface $cache) {
198 $this->metadataCache = $cache;
199 return $this;
200 }
201
202 /**
203 * Get the primary key
204 * @return \pq\Gateway\Table\Identity
205 */
206 function getIdentity() {
207 if (!isset($this->identity)) {
208 $this->identity = new Table\Identity($this);
209 }
210 return $this->identity;
211 }
212
213 function getAttributes() {
214 if (!isset($this->attributes)) {
215 $this->attributes = new Table\Attributes($this);
216 }
217 return $this->attributes;
218 }
219
220 /**
221 * Get foreign key relations
222 * @param string $to fkey
223 * @return \pq\Gateway\Table\Relations|stdClass
224 */
225 function getRelations($to = null) {
226 if (!isset($this->relations)) {
227 $this->relations = new Table\Relations($this);
228 }
229 if (isset($to)) {
230 if (!isset($this->relations->$to)) {
231 return null;
232 }
233 return $this->relations->$to;
234 }
235 return $this->relations;
236 }
237
238 /**
239 * Check whether a certain relation exists
240 * @param string $name
241 * @param string $table
242 * @return bool
243 */
244 function hasRelation($name, $table = null) {
245 if (!($rel = $this->getRelations($name))) {
246 return false;
247 }
248 if (!isset($table)) {
249 return true;
250 }
251 return isset($rel->$table);
252 }
253
254 /**
255 * @return \pq\Connection
256 */
257 function getConnection() {
258 return $this->conn;
259 }
260
261 /**
262 * @return string
263 */
264 function getName() {
265 return $this->name;
266 }
267
268 /**
269 * Set a lock provider
270 * @param \pq\Gateway\Table\LockInterface $lock
271 * @return \pq\Gateway\Table
272 */
273 function setLock(Table\LockInterface $lock) {
274 $this->lock = $lock;
275 return $this;
276 }
277
278 /**
279 * Get any set lock provider
280 * @return \pq\Gateway\Table\LockIntferace
281 */
282 function getLock() {
283 return $this->lock;
284 }
285
286 /**
287 * Execute the query
288 * @param \pq\Query\WriterInterface $query
289 * @return mixed
290 */
291 protected function execute(QueryWriter $query) {
292 return $this->getQueryExecutor()->execute($query, array($this, "onResult"));
293 }
294
295 /**
296 * Retreives the result of an executed query
297 * @param \pq\Result $result
298 * @return mixed
299 */
300 public function onResult(\pq\Result $result = null) {
301 if ($result && $result->status != \pq\Result::TUPLES_OK) {
302 return $result;
303 }
304
305 $rowset = $this->getRowsetPrototype();
306 if (is_callable($rowset)) {
307 return $rowset($result);
308 } elseif ($rowset) {
309 return new $rowset($this, $result);
310 }
311
312 return $result;
313 }
314
315 /**
316 * Find rows in the table
317 * @param array $where
318 * @param array|string $order
319 * @param int $limit
320 * @param int $offset
321 * @param string $lock
322 * @return mixed
323 */
324 function find(array $where = null, $order = null, $limit = 0, $offset = 0, $lock = null) {
325 $query = $this->getQueryWriter()->reset();
326 $query->write("SELECT * FROM", $this->conn->quoteName($this->name));
327 if ($where) {
328 $query->write("WHERE")->criteria($where);
329 }
330 if ($order) {
331 $query->write("ORDER BY", $order);
332 }
333 if ($limit) {
334 $query->write("LIMIT", $limit);
335 }
336 if ($offset) {
337 $query->write("OFFSET", $offset);
338 }
339 if ($lock) {
340 $query->write("FOR", $lock);
341 }
342 return $this->execute($query);
343 }
344
345 /**
346 * Get the child rows of a row by foreign key
347 * @param \pq\Gateway\Row $foreign
348 * @param string $name optional fkey name
349 * @param string $order
350 * @param int $limit
351 * @param int $offset
352 * @return mixed
353 */
354 function of(Row $foreign, $name = null, $order = null, $limit = 0, $offset = 0) {
355 // select * from $this where $this->$foreignColumn = $foreign->$referencedColumn
356
357 if (!isset($name)) {
358 $name = $foreign->getTable()->getName();
359 }
360
361 if (!$foreign->getTable()->hasRelation($name, $this->getName())) {
362 return $this->onResult(null);
363 }
364 $rel = $foreign->getTable()->getRelations($name)->{$this->getName()};
365
366 return $this->find(
367 array($rel->foreignColumn . "=" => $foreign->{$rel->referencedColumn}),
368 $order, $limit, $offset
369 );
370 }
371
372 /**
373 * Get the parent rows of a row by foreign key
374 * @param \pq\Gateway\Row $me
375 * @param string $foreign
376 * @param string $order
377 * @param int $limit
378 * @param int $offset
379 * @return mixed
380 */
381 function by(Row $me, $foreign, $order = null, $limit = 0, $offset = 0) {
382 // select * from $foreign where $foreign->$referencedColumn = $me->$foreignColumn
383
384 if (!$this->hasRelation($foreign, $this->getName())) {
385 return $this->onResult(null);
386 }
387 $rel = $this->getRelations($foreign)->{$this->getName()};
388
389 return static::resolve($rel->referencedTable)->find(
390 array($rel->referencedColumn . "=" => $me->{$rel->foreignColumn}),
391 $order, $limit, $offset
392 );
393 }
394
395 /**
396 * Get rows dependent on other rows by foreign keys
397 * @param array $relations
398 * @param array $where
399 * @param string $order
400 * @param int $limit
401 * @param int $offset
402 * @return mixed
403 */
404 function with(array $relations, array $where = null, $order = null, $limit = 0, $offset = 0) {
405 $qthis = $this->conn->quoteName($this->getName());
406 $query = $this->getQueryWriter()->reset();
407 $query->write("SELECT", "$qthis.*", "FROM", $qthis);
408 foreach ($relations as $relation) {
409 $query->write("JOIN", $relation->foreignTable)->write("ON")->criteria(
410 array(
411 "{$relation->referencedTable}.{$relation->referencedColumn}=" =>
412 new QueryExpr("{$relation->foreignTable}.{$relation->foreignColumn}")
413 )
414 );
415 }
416 if ($where) {
417 $query->write("WHERE")->criteria($where);
418 }
419 if ($order) {
420 $query->write("ORDER BY", $order);
421 }
422 if ($limit) {
423 $query->write("LIMIT", $limit);
424 }
425 if ($offset) {
426 $query->write("OFFSET", $offset);
427 }
428 return $this->execute($query);
429 }
430
431 /**
432 * Insert a row into the table
433 * @param array $data
434 * @param string $returning
435 * @return mixed
436 */
437 function create(array $data = null, $returning = "*") {
438 $query = $this->getQueryWriter()->reset();
439 $query->write("INSERT INTO", $this->conn->quoteName($this->name));
440 if ($data) {
441 $first = true;
442 $params = array();
443 foreach ($data as $key => $val) {
444 $query->write($first ? "(" : ",", $key);
445 $params[] = $query->param($val, $this->getAttributes()->getColumn($key)->type);
446 $first and $first = false;
447 }
448 $query->write(") VALUES (", $params, ")");
449 } else {
450 $query->write("DEFAULT VALUES");
451 }
452
453 if (strlen($returning)) {
454 $query->write("RETURNING", $returning);
455 }
456 return $this->execute($query);
457 }
458
459 /**
460 * Update rows in the table
461 * @param array $where
462 * @param array $data
463 * @param string $returning
464 * @retunr mixed
465 */
466 function update(array $where, array $data, $returning = "*") {
467 $query = $this->getQueryWriter()->reset();
468 $query->write("UPDATE", $this->conn->quoteName($this->name));
469 $first = true;
470 foreach ($data as $key => $val) {
471 $query->write($first ? "SET" : ",", $key, "=",
472 $query->param($val, $this->getAttributes()->getColumn($key)->type));
473 $first and $first = false;
474 }
475 $query->write("WHERE")->criteria($where);
476 if (strlen($returning)) {
477 $query->write("RETURNING", $returning);
478 }
479 return $this->execute($query);
480 }
481
482 /**
483 * Delete rows from the table
484 * @param array $where
485 * @param string $returning
486 * @return mixed
487 */
488 function delete(array $where, $returning = null) {
489 $query = $this->getQueryWriter()->reset();
490 $query->write("DELETE FROM", $this->conn->quoteName($this->name));
491 $query->write("WHERE")->criteria($where);
492 if (strlen($returning)) {
493 $query->write("RETURNING", $returning);
494 }
495 return $this->execute($query);
496 }
497 }