mirror of
https://github.com/torrentpier/torrentpier-lts.git
synced 2025-03-01 15:21:02 +03:00
391 lines
13 KiB
PHP
391 lines
13 KiB
PHP
<?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) {
|
|
$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;
|
|
}
|
|
}
|