- <?php
- /**
- * Zend Framework (http://framework.zend.com/)
- *
- * @link http://github.com/zendframework/zf2 for the canonical source repository
- * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
- * @license http://framework.zend.com/license/new-bsd New BSD License
- */
-
- namespace Zend\Db\Metadata\Source;
-
- use Zend\Db\Adapter\Adapter;
- use Zend\Db\ResultSet\ResultSetInterface;
-
- class SqliteMetadata extends AbstractSource
- {
- protected function loadSchemaData()
- {
- if (isset($this->data['schemas'])) {
- return;
- }
- $this->prepareDataHierarchy('schemas');
-
- $results = $this->fetchPragma('database_list');
- foreach ($results as $row) {
- $schemas[] = $row['name'];
- }
- $this->data['schemas'] = $schemas;
- }
-
- protected function loadTableNameData($schema)
- {
- if (isset($this->data['table_names'][$schema])) {
- return;
- }
- $this->prepareDataHierarchy('table_names', $schema);
-
- // FEATURE: Filename?
-
- $p = $this->adapter->getPlatform();
-
- $sql = 'SELECT "name", "type", "sql" FROM ' . $p->quoteIdentifierChain(array($schema, 'sqlite_master'))
- . ' WHERE "type" IN (\'table\',\'view\') AND "name" NOT LIKE \'sqlite_%\'';
-
- $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
- $tables = array();
- foreach ($results->toArray() as $row) {
- if ('table' == $row['type']) {
- $table = array(
- 'table_type' => 'BASE TABLE',
- 'view_definition' => null, // VIEW only
- 'check_option' => null, // VIEW only
- 'is_updatable' => null, // VIEW only
- );
- } else {
- $table = array(
- 'table_type' => 'VIEW',
- 'view_definition' => null,
- 'check_option' => 'NONE',
- 'is_updatable' => false,
- );
-
- // Parse out extra data
- if (null !== ($data = $this->parseView($row['sql']))) {
- $table = array_merge($table, $data);
- }
- }
- $tables[$row['name']] = $table;
- }
- $this->data['table_names'][$schema] = $tables;
- }
-
- protected function loadColumnData($table, $schema)
- {
- if (isset($this->data['columns'][$schema][$table])) {
- return;
- }
- $this->prepareDataHierarchy('columns', $schema, $table);
- $this->prepareDataHierarchy('sqlite_columns', $schema, $table);
-
- $p = $this->adapter->getPlatform();
-
-
- $results = $this->fetchPragma('table_info', $table, $schema);
-
- $columns = array();
-
- foreach ($results as $row) {
- $columns[$row['name']] = array(
- // cid appears to be zero-based, ordinal position needs to be one-based
- 'ordinal_position' => $row['cid'] + 1,
- 'column_default' => $row['dflt_value'],
- 'is_nullable' => !((bool) $row['notnull']),
- 'data_type' => $row['type'],
- 'character_maximum_length' => null,
- 'character_octet_length' => null,
- 'numeric_precision' => null,
- 'numeric_scale' => null,
- 'numeric_unsigned' => null,
- 'erratas' => array(),
- );
- // TODO: populate character_ and numeric_values with correct info
- }
-
- $this->data['columns'][$schema][$table] = $columns;
- $this->data['sqlite_columns'][$schema][$table] = $results;
- }
-
- protected function loadConstraintData($table, $schema)
- {
- if (isset($this->data['constraints'][$schema][$table])) {
- return;
- }
-
- $this->prepareDataHierarchy('constraints', $schema, $table);
-
- $this->loadColumnData($table, $schema);
- $primaryKey = array();
-
- foreach ($this->data['sqlite_columns'][$schema][$table] as $col) {
- if ((bool) $col['pk']) {
- $primaryKey[] = $col['name'];
- }
- }
-
- if (empty($primaryKey)) {
- $primaryKey = null;
- }
- $constraints = array();
- $indexes = $this->fetchPragma('index_list', $table, $schema);
- foreach ($indexes as $index) {
- if (!((bool) $index['unique'])) {
- continue;
- }
- $constraint = array(
- 'constraint_name' => $index['name'],
- 'constraint_type' => 'UNIQUE',
- 'table_name' => $table,
- 'columns' => array(),
- );
-
- $info = $this->fetchPragma('index_info', $index['name'], $schema);
-
- foreach ($info as $column) {
- $constraint['columns'][] = $column['name'];
- }
- if ($primaryKey === $constraint['columns']) {
- $constraint['constraint_type'] = 'PRIMARY KEY';
- $primaryKey = null;
- }
- $constraints[$constraint['constraint_name']] = $constraint;
- }
-
- if (null !== $primaryKey) {
- $constraintName = '_zf_' . $table . '_PRIMARY';
- $constraints[$constraintName] = array(
- 'constraint_name' => $constraintName,
- 'constraint_type' => 'PRIMARY KEY',
- 'table_name' => $table,
- 'columns' => $primaryKey,
- );
- }
-
- $foreignKeys = $this->fetchPragma('foreign_key_list', $table, $schema);
-
- $id = $name = null;
- foreach ($foreignKeys as $fk) {
- if ($id !== $fk['id']) {
- $id = $fk['id'];
- $name = '_zf_' . $table . '_FOREIGN_KEY_' . ($id + 1);
- $constraints[$name] = array(
- 'constraint_name' => $name,
- 'constraint_type' => 'FOREIGN KEY',
- 'table_name' => $table,
- 'columns' => array(),
- 'referenced_table_schema' => $schema,
- 'referenced_table_name' => $fk['table'],
- 'referenced_columns' => array(),
- // TODO: Verify match, on_update, and on_delete values conform to SQL Standard
- 'match_option' => strtoupper($fk['match']),
- 'update_rule' => strtoupper($fk['on_update']),
- 'delete_rule' => strtoupper($fk['on_delete']),
- );
- }
- $constraints[$name]['columns'][] = $fk['from'];
- $constraints[$name]['referenced_columns'][] = $fk['to'];
- }
-
- $this->data['constraints'][$schema][$table] = $constraints;
- }
-
- protected function loadTriggerData($schema)
- {
- if (isset($this->data['triggers'][$schema])) {
- return;
- }
-
- $this->prepareDataHierarchy('triggers', $schema);
-
- $p = $this->adapter->getPlatform();
-
- $sql = 'SELECT "name", "tbl_name", "sql" FROM '
- . $p->quoteIdentifierChain(array($schema, 'sqlite_master'))
- . ' WHERE "type" = \'trigger\'';
-
- $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
- $triggers = array();
- foreach ($results->toArray() as $row) {
- $trigger = array(
- 'trigger_name' => $row['name'],
- 'event_manipulation' => null, // in $row['sql']
- 'event_object_catalog' => null,
- 'event_object_schema' => $schema,
- 'event_object_table' => $row['tbl_name'],
- 'action_order' => 0,
- 'action_condition' => null, // in $row['sql']
- 'action_statement' => null, // in $row['sql']
- 'action_orientation' => 'ROW',
- 'action_timing' => null, // in $row['sql']
- 'action_reference_old_table' => null,
- 'action_reference_new_table' => null,
- 'action_reference_old_row' => 'OLD',
- 'action_reference_new_row' => 'NEW',
- 'created' => null,
- );
-
- // Parse out extra data
- if (null !== ($data = $this->parseTrigger($row['sql']))) {
- $trigger = array_merge($trigger, $data);
- }
- $triggers[$trigger['trigger_name']] = $trigger;
- }
-
- $this->data['triggers'][$schema] = $triggers;
- }
-
- protected function fetchPragma($name, $value = null, $schema = null)
- {
- $p = $this->adapter->getPlatform();
-
- $sql = 'PRAGMA ';
-
- if (null !== $schema) {
- $sql .= $p->quoteIdentifier($schema) . '.';
- }
- $sql .= $name;
-
- if (null !== $value) {
- $sql .= '(' . $p->quoteTrustedValue($value) . ')';
- }
-
- $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
- if ($results instanceof ResultSetInterface) {
- return $results->toArray();
- }
- return array();
- }
-
- protected function parseView($sql)
- {
- static $re = null;
- if (null === $re) {
- $identifier = $this->getIdentifierRegularExpression();
- $identifierList = $this->getIdentifierListRegularExpression();
- $identifierChain = $this->getIdentifierChainRegularExpression();
- $re = $this->buildRegularExpression(array(
- 'CREATE',
- array('TEMP|TEMPORARY'),
- 'VIEW',
- array('IF', 'NOT', 'EXISTS'),
- $identifierChain,
- 'AS',
- '(?<view_definition>.+)',
- array(';'),
- ));
- }
-
- if (!preg_match($re, $sql, $matches)) {
- return null;
- }
- return array(
- 'view_definition' => $matches['view_definition'],
- );
- }
-
- protected function parseTrigger($sql)
- {
- static $re = null;
- if (null === $re) {
- $identifier = $this->getIdentifierRegularExpression();
- $identifierList = $this->getIdentifierListRegularExpression();
- $identifierChain = $this->getIdentifierChainRegularExpression();
- $re = $this->buildRegularExpression(array(
- 'CREATE',
- array('TEMP|TEMPORARY'),
- 'TRIGGER',
- array('IF', 'NOT', 'EXISTS'),
- $identifierChain,
- array('(?<action_timing>BEFORE|AFTER|INSTEAD\\s+OF)',),
- '(?<event_manipulation>DELETE|INSERT|UPDATE)',
- array('OF', '(?<column_usage>' . $identifierList . ')'),
- 'ON',
- '(?<event_object_table>' . $identifier . ')',
- array('FOR', 'EACH', 'ROW'),
- array('WHEN', '(?<action_condition>.+)'),
- '(?<action_statement>BEGIN',
- '.+',
- 'END)',
- array(';'),
- ));
- }
-
- if (!preg_match($re, $sql, $matches)) {
- return null;
- }
- $data = array();
-
- foreach ($matches as $key => $value) {
- if (is_string($key)) {
- $data[$key] = $value;
- }
- }
-
- // Normalize data and populate defaults, if necessary
-
- $data['event_manipulation'] = strtoupper($data['event_manipulation']);
- if (empty($data['action_condition'])) {
- $data['action_condition'] = null;
- }
- if (!empty($data['action_timing'])) {
- $data['action_timing'] = strtoupper($data['action_timing']);
- if ('I' == $data['action_timing'][0]) {
- // normalize the white-space between the two words
- $data['action_timing'] = 'INSTEAD OF';
- }
- } else {
- $data['action_timing'] = 'AFTER';
- }
- unset($data['column_usage']);
-
- return $data;
- }
-
- protected function buildRegularExpression(array $re)
- {
- foreach ($re as &$value) {
- if (is_array($value)) {
- $value = '(?:' . implode('\\s*+', $value) . '\\s*+)?';
- } else {
- $value .= '\\s*+';
- }
- }
- unset($value);
- $re = '/^' . implode('\\s*+', $re) . '$/';
- return $re;
- }
-
- protected function getIdentifierRegularExpression()
- {
- static $re = null;
- if (null === $re) {
- $re = '(?:' . implode('|', array(
- '"(?:[^"\\\\]++|\\\\.)*+"',
- '`(?:[^`]++|``)*+`',
- '\\[[^\\]]+\\]',
- '[^\\s\\.]+',
- )) . ')';
- }
-
- return $re;
- }
-
- protected function getIdentifierChainRegularExpression()
- {
- static $re = null;
- if (null === $re) {
- $identifier = $this->getIdentifierRegularExpression();
- $re = $identifier . '(?:\\s*\\.\\s*' . $identifier . ')*+';
- }
- return $re;
- }
-
- protected function getIdentifierListRegularExpression()
- {
- static $re = null;
- if (null === $re) {
- $identifier = $this->getIdentifierRegularExpression();
- $re = $identifier . '(?:\\s*,\\s*' . $identifier . ')*+';
- }
- return $re;
- }
- }