1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\Db\Metadata\Source;
11
12use Zend\Db\Adapter\Adapter;
13use Zend\Db\Metadata\MetadataInterface;
14use Zend\Db\Metadata\Object;
15
16abstract class AbstractSource implements MetadataInterface
17{
18    const DEFAULT_SCHEMA = '__DEFAULT_SCHEMA__';
19
20    /**
21     *
22     * @var Adapter
23     */
24    protected $adapter = null;
25
26    /**
27     *
28     * @var string
29     */
30    protected $defaultSchema = null;
31
32    /**
33     *
34     * @var array
35     */
36    protected $data = array();
37
38    /**
39     * Constructor
40     *
41     * @param Adapter $adapter
42     */
43    public function __construct(Adapter $adapter)
44    {
45        $this->adapter = $adapter;
46        $this->defaultSchema = ($adapter->getCurrentSchema()) ?: self::DEFAULT_SCHEMA;
47    }
48
49    /**
50     * Get schemas
51     *
52     */
53    public function getSchemas()
54    {
55        $this->loadSchemaData();
56
57        return $this->data['schemas'];
58    }
59
60    /**
61     * Get table names
62     *
63     * @param  string $schema
64     * @param  bool   $includeViews
65     * @return string[]
66     */
67    public function getTableNames($schema = null, $includeViews = false)
68    {
69        if ($schema === null) {
70            $schema = $this->defaultSchema;
71        }
72
73        $this->loadTableNameData($schema);
74
75        if ($includeViews) {
76            return array_keys($this->data['table_names'][$schema]);
77        }
78
79        $tableNames = array();
80        foreach ($this->data['table_names'][$schema] as $tableName => $data) {
81            if ('BASE TABLE' == $data['table_type']) {
82                $tableNames[] = $tableName;
83            }
84        }
85        return $tableNames;
86    }
87
88    /**
89     * Get tables
90     *
91     * @param  string $schema
92     * @param  bool   $includeViews
93     * @return Object\TableObject[]
94     */
95    public function getTables($schema = null, $includeViews = false)
96    {
97        if ($schema === null) {
98            $schema = $this->defaultSchema;
99        }
100
101        $tables = array();
102        foreach ($this->getTableNames($schema, $includeViews) as $tableName) {
103            $tables[] = $this->getTable($tableName, $schema);
104        }
105        return $tables;
106    }
107
108    /**
109     * Get table
110     *
111     * @param  string $tableName
112     * @param  string $schema
113     * @return Object\TableObject
114     */
115    public function getTable($tableName, $schema = null)
116    {
117        if ($schema === null) {
118            $schema = $this->defaultSchema;
119        }
120
121        $this->loadTableNameData($schema);
122
123        if (!isset($this->data['table_names'][$schema][$tableName])) {
124            throw new \Exception('Table "' . $tableName . '" does not exist');
125        }
126
127        $data = $this->data['table_names'][$schema][$tableName];
128        switch ($data['table_type']) {
129            case 'BASE TABLE':
130                $table = new Object\TableObject($tableName);
131                break;
132            case 'VIEW':
133                $table = new Object\ViewObject($tableName);
134                $table->setViewDefinition($data['view_definition']);
135                $table->setCheckOption($data['check_option']);
136                $table->setIsUpdatable($data['is_updatable']);
137                break;
138            default:
139                throw new \Exception('Table "' . $tableName . '" is of an unsupported type "' . $data['table_type'] . '"');
140        }
141        $table->setColumns($this->getColumns($tableName, $schema));
142        $table->setConstraints($this->getConstraints($tableName, $schema));
143        return $table;
144    }
145
146    /**
147     * Get view names
148     *
149     * @param string $schema
150     * @return array
151     */
152    public function getViewNames($schema = null)
153    {
154        if ($schema === null) {
155            $schema = $this->defaultSchema;
156        }
157
158        $this->loadTableNameData($schema);
159
160        $viewNames = array();
161        foreach ($this->data['table_names'][$schema] as $tableName => $data) {
162            if ('VIEW' == $data['table_type']) {
163                $viewNames[] = $tableName;
164            }
165        }
166        return $viewNames;
167    }
168
169    /**
170     * Get views
171     *
172     * @param string $schema
173     * @return array
174     */
175    public function getViews($schema = null)
176    {
177        if ($schema === null) {
178            $schema = $this->defaultSchema;
179        }
180
181        $views = array();
182        foreach ($this->getViewNames($schema) as $tableName) {
183            $views[] = $this->getTable($tableName, $schema);
184        }
185        return $views;
186    }
187
188    /**
189     * Get view
190     *
191     * @param string $viewName
192     * @param string $schema
193     * @return \Zend\Db\Metadata\Object\TableObject
194     */
195    public function getView($viewName, $schema = null)
196    {
197        if ($schema === null) {
198            $schema = $this->defaultSchema;
199        }
200
201        $this->loadTableNameData($schema);
202
203        $tableNames = $this->data['table_names'][$schema];
204        if (isset($tableNames[$viewName]) && 'VIEW' == $tableNames[$viewName]['table_type']) {
205            return $this->getTable($viewName, $schema);
206        }
207        throw new \Exception('View "' . $viewName . '" does not exist');
208    }
209
210    /**
211     * Gt column names
212     *
213     * @param  string $table
214     * @param  string $schema
215     * @return array
216     */
217    public function getColumnNames($table, $schema = null)
218    {
219        if ($schema === null) {
220            $schema = $this->defaultSchema;
221        }
222
223        $this->loadColumnData($table, $schema);
224
225        if (!isset($this->data['columns'][$schema][$table])) {
226            throw new \Exception('"' . $table . '" does not exist');
227        }
228
229        return array_keys($this->data['columns'][$schema][$table]);
230    }
231
232    /**
233     * Get columns
234     *
235     * @param  string $table
236     * @param  string $schema
237     * @return array
238     */
239    public function getColumns($table, $schema = null)
240    {
241        if ($schema === null) {
242            $schema = $this->defaultSchema;
243        }
244
245        $this->loadColumnData($table, $schema);
246
247        $columns = array();
248        foreach ($this->getColumnNames($table, $schema) as $columnName) {
249            $columns[] = $this->getColumn($columnName, $table, $schema);
250        }
251        return $columns;
252    }
253
254    /**
255     * Get column
256     *
257     * @param  string $columnName
258     * @param  string $table
259     * @param  string $schema
260     * @return Object\ColumnObject
261     */
262    public function getColumn($columnName, $table, $schema = null)
263    {
264        if ($schema === null) {
265            $schema = $this->defaultSchema;
266        }
267
268        $this->loadColumnData($table, $schema);
269
270        if (!isset($this->data['columns'][$schema][$table][$columnName])) {
271            throw new \Exception('A column by that name was not found.');
272        }
273
274        $info = $this->data['columns'][$schema][$table][$columnName];
275
276        $column = new Object\ColumnObject($columnName, $table, $schema);
277        $props = array(
278            'ordinal_position', 'column_default', 'is_nullable',
279            'data_type', 'character_maximum_length', 'character_octet_length',
280            'numeric_precision', 'numeric_scale', 'numeric_unsigned',
281            'erratas'
282        );
283        foreach ($props as $prop) {
284            if (isset($info[$prop])) {
285                $column->{'set' . str_replace('_', '', $prop)}($info[$prop]);
286            }
287        }
288
289        $column->setOrdinalPosition($info['ordinal_position']);
290        $column->setColumnDefault($info['column_default']);
291        $column->setIsNullable($info['is_nullable']);
292        $column->setDataType($info['data_type']);
293        $column->setCharacterMaximumLength($info['character_maximum_length']);
294        $column->setCharacterOctetLength($info['character_octet_length']);
295        $column->setNumericPrecision($info['numeric_precision']);
296        $column->setNumericScale($info['numeric_scale']);
297        $column->setNumericUnsigned($info['numeric_unsigned']);
298        $column->setErratas($info['erratas']);
299
300        return $column;
301    }
302
303    /**
304     * Get constraints
305     *
306     * @param  string $table
307     * @param  string $schema
308     * @return array
309     */
310    public function getConstraints($table, $schema = null)
311    {
312        if ($schema === null) {
313            $schema = $this->defaultSchema;
314        }
315
316        $this->loadConstraintData($table, $schema);
317
318        $constraints = array();
319        foreach (array_keys($this->data['constraints'][$schema][$table]) as $constraintName) {
320            $constraints[] = $this->getConstraint($constraintName, $table, $schema);
321        }
322
323        return $constraints;
324    }
325
326    /**
327     * Get constraint
328     *
329     * @param  string $constraintName
330     * @param  string $table
331     * @param  string $schema
332     * @return Object\ConstraintObject
333     */
334    public function getConstraint($constraintName, $table, $schema = null)
335    {
336        if ($schema === null) {
337            $schema = $this->defaultSchema;
338        }
339
340        $this->loadConstraintData($table, $schema);
341
342        if (!isset($this->data['constraints'][$schema][$table][$constraintName])) {
343            throw new \Exception('Cannot find a constraint by that name in this table');
344        }
345
346        $info = $this->data['constraints'][$schema][$table][$constraintName];
347        $constraint = new Object\ConstraintObject($constraintName, $table, $schema);
348
349        foreach (array(
350            'constraint_type'         => 'setType',
351            'match_option'            => 'setMatchOption',
352            'update_rule'             => 'setUpdateRule',
353            'delete_rule'             => 'setDeleteRule',
354            'columns'                 => 'setColumns',
355            'referenced_table_schema' => 'setReferencedTableSchema',
356            'referenced_table_name'   => 'setReferencedTableName',
357            'referenced_columns'      => 'setReferencedColumns',
358            'check_clause'            => 'setCheckClause',
359        ) as $key => $setMethod) {
360            if (isset($info[$key])) {
361                $constraint->{$setMethod}($info[$key]);
362            }
363        }
364
365        return $constraint;
366    }
367
368    /**
369     * Get constraint keys
370     *
371     * @param  string $constraint
372     * @param  string $table
373     * @param  string $schema
374     * @return array
375     */
376    public function getConstraintKeys($constraint, $table, $schema = null)
377    {
378        if ($schema === null) {
379            $schema = $this->defaultSchema;
380        }
381
382        $this->loadConstraintReferences($table, $schema);
383
384        // organize references first
385        $references = array();
386        foreach ($this->data['constraint_references'][$schema] as $refKeyInfo) {
387            if ($refKeyInfo['constraint_name'] == $constraint) {
388                $references[$refKeyInfo['constraint_name']] = $refKeyInfo;
389            }
390        }
391
392        $this->loadConstraintDataKeys($schema);
393
394        $keys = array();
395        foreach ($this->data['constraint_keys'][$schema] as $constraintKeyInfo) {
396            if ($constraintKeyInfo['table_name'] == $table && $constraintKeyInfo['constraint_name'] === $constraint) {
397                $keys[] = $key = new Object\ConstraintKeyObject($constraintKeyInfo['column_name']);
398                $key->setOrdinalPosition($constraintKeyInfo['ordinal_position']);
399                if (isset($references[$constraint])) {
400                    //$key->setReferencedTableSchema($constraintKeyInfo['referenced_table_schema']);
401                    $key->setForeignKeyUpdateRule($references[$constraint]['update_rule']);
402                    $key->setForeignKeyDeleteRule($references[$constraint]['delete_rule']);
403                    //$key->setReferencedTableSchema($references[$constraint]['referenced_table_schema']);
404                    $key->setReferencedTableName($references[$constraint]['referenced_table_name']);
405                    $key->setReferencedColumnName($references[$constraint]['referenced_column_name']);
406                }
407            }
408        }
409
410        return $keys;
411    }
412
413    /**
414     * Get trigger names
415     *
416     * @param string $schema
417     * @return array
418     */
419    public function getTriggerNames($schema = null)
420    {
421        if ($schema === null) {
422            $schema = $this->defaultSchema;
423        }
424
425        $this->loadTriggerData($schema);
426
427        return array_keys($this->data['triggers'][$schema]);
428    }
429
430    /**
431     * Get triggers
432     *
433     * @param string $schema
434     * @return array
435     */
436    public function getTriggers($schema = null)
437    {
438        if ($schema === null) {
439            $schema = $this->defaultSchema;
440        }
441
442        $triggers = array();
443        foreach ($this->getTriggerNames($schema) as $triggerName) {
444            $triggers[] = $this->getTrigger($triggerName, $schema);
445        }
446        return $triggers;
447    }
448
449    /**
450     * Get trigger
451     *
452     * @param string $triggerName
453     * @param string $schema
454     * @return Object\TriggerObject
455     */
456    public function getTrigger($triggerName, $schema = null)
457    {
458        if ($schema === null) {
459            $schema = $this->defaultSchema;
460        }
461
462        $this->loadTriggerData($schema);
463
464        if (!isset($this->data['triggers'][$schema][$triggerName])) {
465            throw new \Exception('Trigger "' . $triggerName . '" does not exist');
466        }
467
468        $info = $this->data['triggers'][$schema][$triggerName];
469
470        $trigger = new Object\TriggerObject();
471
472        $trigger->setName($triggerName);
473        $trigger->setEventManipulation($info['event_manipulation']);
474        $trigger->setEventObjectCatalog($info['event_object_catalog']);
475        $trigger->setEventObjectSchema($info['event_object_schema']);
476        $trigger->setEventObjectTable($info['event_object_table']);
477        $trigger->setActionOrder($info['action_order']);
478        $trigger->setActionCondition($info['action_condition']);
479        $trigger->setActionStatement($info['action_statement']);
480        $trigger->setActionOrientation($info['action_orientation']);
481        $trigger->setActionTiming($info['action_timing']);
482        $trigger->setActionReferenceOldTable($info['action_reference_old_table']);
483        $trigger->setActionReferenceNewTable($info['action_reference_new_table']);
484        $trigger->setActionReferenceOldRow($info['action_reference_old_row']);
485        $trigger->setActionReferenceNewRow($info['action_reference_new_row']);
486        $trigger->setCreated($info['created']);
487
488        return $trigger;
489    }
490
491    /**
492     * Prepare data hierarchy
493     *
494     * @param string $type
495     * @param string $key ...
496     */
497    protected function prepareDataHierarchy($type)
498    {
499        $data = &$this->data;
500        foreach (func_get_args() as $key) {
501            if (!isset($data[$key])) {
502                $data[$key] = array();
503            }
504            $data = &$data[$key];
505        }
506    }
507
508    /**
509     * Load schema data
510     */
511    protected function loadSchemaData()
512    {
513    }
514
515    /**
516     * Load table name data
517     *
518     * @param string $schema
519     */
520    protected function loadTableNameData($schema)
521    {
522        if (isset($this->data['table_names'][$schema])) {
523            return;
524        }
525
526        $this->prepareDataHierarchy('table_names', $schema);
527    }
528
529    /**
530     * Load column data
531     *
532     * @param string $table
533     * @param string $schema
534     */
535    protected function loadColumnData($table, $schema)
536    {
537        if (isset($this->data['columns'][$schema][$table])) {
538            return;
539        }
540
541        $this->prepareDataHierarchy('columns', $schema, $table);
542    }
543
544    /**
545     * Load constraint data
546     *
547     * @param string $table
548     * @param string $schema
549     */
550    protected function loadConstraintData($table, $schema)
551    {
552        if (isset($this->data['constraints'][$schema])) {
553            return;
554        }
555
556        $this->prepareDataHierarchy('constraints', $schema);
557    }
558
559    /**
560     * Load constraint data keys
561     *
562     * @param string $schema
563     */
564    protected function loadConstraintDataKeys($schema)
565    {
566        if (isset($this->data['constraint_keys'][$schema])) {
567            return;
568        }
569
570        $this->prepareDataHierarchy('constraint_keys', $schema);
571    }
572
573    /**
574     * Load constraint references
575     *
576     * @param string $table
577     * @param string $schema
578     */
579    protected function loadConstraintReferences($table, $schema)
580    {
581        if (isset($this->data['constraint_references'][$schema])) {
582            return;
583        }
584
585        $this->prepareDataHierarchy('constraint_references', $schema);
586    }
587
588    /**
589     * Load trigger data
590     *
591     * @param string $schema
592     */
593    protected function loadTriggerData($schema)
594    {
595        if (isset($this->data['triggers'][$schema])) {
596            return;
597        }
598
599        $this->prepareDataHierarchy('triggers', $schema);
600    }
601}
602