PageRenderTime 44ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Db/Metadata/Source/SqliteMetadata.php

https://bitbucket.org/saifshuvo/zf2
PHP | 392 lines | 324 code | 53 blank | 15 comment | 34 complexity | 6d541119c88097f8571fbe2572d3040d MD5 | raw file
  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-2013 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Db\Metadata\Source;
  10. use Zend\Db\Adapter\Adapter;
  11. use Zend\Db\ResultSet\ResultSetInterface;
  12. class SqliteMetadata extends AbstractSource
  13. {
  14. protected function loadSchemaData()
  15. {
  16. if (isset($this->data['schemas'])) {
  17. return;
  18. }
  19. $this->prepareDataHierarchy('schemas');
  20. $results = $this->fetchPragma('database_list');
  21. foreach ($results as $row) {
  22. $schemas[] = $row['name'];
  23. }
  24. $this->data['schemas'] = $schemas;
  25. }
  26. protected function loadTableNameData($schema)
  27. {
  28. if (isset($this->data['table_names'][$schema])) {
  29. return;
  30. }
  31. $this->prepareDataHierarchy('table_names', $schema);
  32. // FEATURE: Filename?
  33. $p = $this->adapter->getPlatform();
  34. $sql = 'SELECT "name", "type", "sql" FROM ' . $p->quoteIdentifierChain(array($schema, 'sqlite_master'))
  35. . ' WHERE "type" IN (\'table\',\'view\') AND "name" NOT LIKE \'sqlite_%\'';
  36. $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
  37. $tables = array();
  38. foreach ($results->toArray() as $row) {
  39. if ('table' == $row['type']) {
  40. $table = array(
  41. 'table_type' => 'BASE TABLE',
  42. 'view_definition' => null, // VIEW only
  43. 'check_option' => null, // VIEW only
  44. 'is_updatable' => null, // VIEW only
  45. );
  46. } else {
  47. $table = array(
  48. 'table_type' => 'VIEW',
  49. 'view_definition' => null,
  50. 'check_option' => 'NONE',
  51. 'is_updatable' => false,
  52. );
  53. // Parse out extra data
  54. if (null !== ($data = $this->parseView($row['sql']))) {
  55. $table = array_merge($table, $data);
  56. }
  57. }
  58. $tables[$row['name']] = $table;
  59. }
  60. $this->data['table_names'][$schema] = $tables;
  61. }
  62. protected function loadColumnData($table, $schema)
  63. {
  64. if (isset($this->data['columns'][$schema][$table])) {
  65. return;
  66. }
  67. $this->prepareDataHierarchy('columns', $schema, $table);
  68. $this->prepareDataHierarchy('sqlite_columns', $schema, $table);
  69. $p = $this->adapter->getPlatform();
  70. $results = $this->fetchPragma('table_info', $table, $schema);
  71. $columns = array();
  72. foreach ($results as $row) {
  73. $columns[$row['name']] = array(
  74. // cid appears to be zero-based, ordinal position needs to be one-based
  75. 'ordinal_position' => $row['cid'] + 1,
  76. 'column_default' => $row['dflt_value'],
  77. 'is_nullable' => !((bool) $row['notnull']),
  78. 'data_type' => $row['type'],
  79. 'character_maximum_length' => null,
  80. 'character_octet_length' => null,
  81. 'numeric_precision' => null,
  82. 'numeric_scale' => null,
  83. 'numeric_unsigned' => null,
  84. 'erratas' => array(),
  85. );
  86. // TODO: populate character_ and numeric_values with correct info
  87. }
  88. $this->data['columns'][$schema][$table] = $columns;
  89. $this->data['sqlite_columns'][$schema][$table] = $results;
  90. }
  91. protected function loadConstraintData($table, $schema)
  92. {
  93. if (isset($this->data['constraints'][$schema][$table])) {
  94. return;
  95. }
  96. $this->prepareDataHierarchy('constraints', $schema, $table);
  97. $this->loadColumnData($table, $schema);
  98. $primaryKey = array();
  99. foreach ($this->data['sqlite_columns'][$schema][$table] as $col) {
  100. if ((bool) $col['pk']) {
  101. $primaryKey[] = $col['name'];
  102. }
  103. }
  104. if (empty($primaryKey)) {
  105. $primaryKey = null;
  106. }
  107. $constraints = array();
  108. $indexes = $this->fetchPragma('index_list', $table, $schema);
  109. foreach ($indexes as $index) {
  110. if (!((bool) $index['unique'])) {
  111. continue;
  112. }
  113. $constraint = array(
  114. 'constraint_name' => $index['name'],
  115. 'constraint_type' => 'UNIQUE',
  116. 'table_name' => $table,
  117. 'columns' => array(),
  118. );
  119. $info = $this->fetchPragma('index_info', $index['name'], $schema);
  120. foreach ($info as $column) {
  121. $constraint['columns'][] = $column['name'];
  122. }
  123. if ($primaryKey === $constraint['columns']) {
  124. $constraint['constraint_type'] = 'PRIMARY KEY';
  125. $primaryKey = null;
  126. }
  127. $constraints[$constraint['constraint_name']] = $constraint;
  128. }
  129. if (null !== $primaryKey) {
  130. $constraintName = '_zf_' . $table . '_PRIMARY';
  131. $constraints[$constraintName] = array(
  132. 'constraint_name' => $constraintName,
  133. 'constraint_type' => 'PRIMARY KEY',
  134. 'table_name' => $table,
  135. 'columns' => $primaryKey,
  136. );
  137. }
  138. $foreignKeys = $this->fetchPragma('foreign_key_list', $table, $schema);
  139. $id = $name = null;
  140. foreach ($foreignKeys as $fk) {
  141. if ($id !== $fk['id']) {
  142. $id = $fk['id'];
  143. $name = '_zf_' . $table . '_FOREIGN_KEY_' . ($id + 1);
  144. $constraints[$name] = array(
  145. 'constraint_name' => $name,
  146. 'constraint_type' => 'FOREIGN KEY',
  147. 'table_name' => $table,
  148. 'columns' => array(),
  149. 'referenced_table_schema' => $schema,
  150. 'referenced_table_name' => $fk['table'],
  151. 'referenced_columns' => array(),
  152. // TODO: Verify match, on_update, and on_delete values conform to SQL Standard
  153. 'match_option' => strtoupper($fk['match']),
  154. 'update_rule' => strtoupper($fk['on_update']),
  155. 'delete_rule' => strtoupper($fk['on_delete']),
  156. );
  157. }
  158. $constraints[$name]['columns'][] = $fk['from'];
  159. $constraints[$name]['referenced_columns'][] = $fk['to'];
  160. }
  161. $this->data['constraints'][$schema][$table] = $constraints;
  162. }
  163. protected function loadTriggerData($schema)
  164. {
  165. if (isset($this->data['triggers'][$schema])) {
  166. return;
  167. }
  168. $this->prepareDataHierarchy('triggers', $schema);
  169. $p = $this->adapter->getPlatform();
  170. $sql = 'SELECT "name", "tbl_name", "sql" FROM '
  171. . $p->quoteIdentifierChain(array($schema, 'sqlite_master'))
  172. . ' WHERE "type" = \'trigger\'';
  173. $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
  174. $triggers = array();
  175. foreach ($results->toArray() as $row) {
  176. $trigger = array(
  177. 'trigger_name' => $row['name'],
  178. 'event_manipulation' => null, // in $row['sql']
  179. 'event_object_catalog' => null,
  180. 'event_object_schema' => $schema,
  181. 'event_object_table' => $row['tbl_name'],
  182. 'action_order' => 0,
  183. 'action_condition' => null, // in $row['sql']
  184. 'action_statement' => null, // in $row['sql']
  185. 'action_orientation' => 'ROW',
  186. 'action_timing' => null, // in $row['sql']
  187. 'action_reference_old_table' => null,
  188. 'action_reference_new_table' => null,
  189. 'action_reference_old_row' => 'OLD',
  190. 'action_reference_new_row' => 'NEW',
  191. 'created' => null,
  192. );
  193. // Parse out extra data
  194. if (null !== ($data = $this->parseTrigger($row['sql']))) {
  195. $trigger = array_merge($trigger, $data);
  196. }
  197. $triggers[$trigger['trigger_name']] = $trigger;
  198. }
  199. $this->data['triggers'][$schema] = $triggers;
  200. }
  201. protected function fetchPragma($name, $value = null, $schema = null)
  202. {
  203. $p = $this->adapter->getPlatform();
  204. $sql = 'PRAGMA ';
  205. if (null !== $schema) {
  206. $sql .= $p->quoteIdentifier($schema) . '.';
  207. }
  208. $sql .= $name;
  209. if (null !== $value) {
  210. $sql .= '(' . $p->quoteTrustedValue($value) . ')';
  211. }
  212. $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
  213. if ($results instanceof ResultSetInterface) {
  214. return $results->toArray();
  215. }
  216. return array();
  217. }
  218. protected function parseView($sql)
  219. {
  220. static $re = null;
  221. if (null === $re) {
  222. $identifier = $this->getIdentifierRegularExpression();
  223. $identifierList = $this->getIdentifierListRegularExpression();
  224. $identifierChain = $this->getIdentifierChainRegularExpression();
  225. $re = $this->buildRegularExpression(array(
  226. 'CREATE',
  227. array('TEMP|TEMPORARY'),
  228. 'VIEW',
  229. array('IF', 'NOT', 'EXISTS'),
  230. $identifierChain,
  231. 'AS',
  232. '(?<view_definition>.+)',
  233. array(';'),
  234. ));
  235. }
  236. if (!preg_match($re, $sql, $matches)) {
  237. return null;
  238. }
  239. return array(
  240. 'view_definition' => $matches['view_definition'],
  241. );
  242. }
  243. protected function parseTrigger($sql)
  244. {
  245. static $re = null;
  246. if (null === $re) {
  247. $identifier = $this->getIdentifierRegularExpression();
  248. $identifierList = $this->getIdentifierListRegularExpression();
  249. $identifierChain = $this->getIdentifierChainRegularExpression();
  250. $re = $this->buildRegularExpression(array(
  251. 'CREATE',
  252. array('TEMP|TEMPORARY'),
  253. 'TRIGGER',
  254. array('IF', 'NOT', 'EXISTS'),
  255. $identifierChain,
  256. array('(?<action_timing>BEFORE|AFTER|INSTEAD\\s+OF)',),
  257. '(?<event_manipulation>DELETE|INSERT|UPDATE)',
  258. array('OF', '(?<column_usage>' . $identifierList . ')'),
  259. 'ON',
  260. '(?<event_object_table>' . $identifier . ')',
  261. array('FOR', 'EACH', 'ROW'),
  262. array('WHEN', '(?<action_condition>.+)'),
  263. '(?<action_statement>BEGIN',
  264. '.+',
  265. 'END)',
  266. array(';'),
  267. ));
  268. }
  269. if (!preg_match($re, $sql, $matches)) {
  270. return null;
  271. }
  272. $data = array();
  273. foreach ($matches as $key => $value) {
  274. if (is_string($key)) {
  275. $data[$key] = $value;
  276. }
  277. }
  278. // Normalize data and populate defaults, if necessary
  279. $data['event_manipulation'] = strtoupper($data['event_manipulation']);
  280. if (empty($data['action_condition'])) {
  281. $data['action_condition'] = null;
  282. }
  283. if (!empty($data['action_timing'])) {
  284. $data['action_timing'] = strtoupper($data['action_timing']);
  285. if ('I' == $data['action_timing'][0]) {
  286. // normalize the white-space between the two words
  287. $data['action_timing'] = 'INSTEAD OF';
  288. }
  289. } else {
  290. $data['action_timing'] = 'AFTER';
  291. }
  292. unset($data['column_usage']);
  293. return $data;
  294. }
  295. protected function buildRegularExpression(array $re)
  296. {
  297. foreach ($re as &$value) {
  298. if (is_array($value)) {
  299. $value = '(?:' . implode('\\s*+', $value) . '\\s*+)?';
  300. } else {
  301. $value .= '\\s*+';
  302. }
  303. }
  304. unset($value);
  305. $re = '/^' . implode('\\s*+', $re) . '$/';
  306. return $re;
  307. }
  308. protected function getIdentifierRegularExpression()
  309. {
  310. static $re = null;
  311. if (null === $re) {
  312. $re = '(?:' . implode('|', array(
  313. '"(?:[^"\\\\]++|\\\\.)*+"',
  314. '`(?:[^`]++|``)*+`',
  315. '\\[[^\\]]+\\]',
  316. '[^\\s\\.]+',
  317. )) . ')';
  318. }
  319. return $re;
  320. }
  321. protected function getIdentifierChainRegularExpression()
  322. {
  323. static $re = null;
  324. if (null === $re) {
  325. $identifier = $this->getIdentifierRegularExpression();
  326. $re = $identifier . '(?:\\s*\\.\\s*' . $identifier . ')*+';
  327. }
  328. return $re;
  329. }
  330. protected function getIdentifierListRegularExpression()
  331. {
  332. static $re = null;
  333. if (null === $re) {
  334. $identifier = $this->getIdentifierRegularExpression();
  335. $re = $identifier . '(?:\\s*,\\s*' . $identifier . ')*+';
  336. }
  337. return $re;
  338. }
  339. }