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