Table.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\DB\Ddl;
  7. use Magento\Framework\DB\Adapter\AdapterInterface;
  8. /**
  9. * Data Definition for table
  10. *
  11. * @api
  12. * @since 100.0.2
  13. */
  14. class Table
  15. {
  16. /**
  17. * Types of columns
  18. */
  19. const TYPE_BOOLEAN = 'boolean';
  20. const TYPE_SMALLINT = 'smallint';
  21. const TYPE_INTEGER = 'integer';
  22. const TYPE_BIGINT = 'bigint';
  23. const TYPE_FLOAT = 'float';
  24. const TYPE_NUMERIC = 'numeric';
  25. const TYPE_DECIMAL = 'decimal';
  26. const TYPE_DATE = 'date';
  27. const TYPE_TIMESTAMP = 'timestamp';
  28. // Capable to support date-time from 1970 + auto-triggers in some RDBMS
  29. const TYPE_DATETIME = 'datetime';
  30. // Capable to support long date-time before 1970
  31. const TYPE_TEXT = 'text';
  32. // A real blob, stored as binary inside DB
  33. const TYPE_BLOB = 'blob';
  34. // Used for back compatibility, when query param can't use statement options
  35. const TYPE_VARBINARY = 'varbinary';
  36. /**
  37. * Default and maximal TEXT and BLOB columns sizes we can support for different DB systems.
  38. */
  39. const DEFAULT_TEXT_SIZE = 1024;
  40. const MAX_TEXT_SIZE = 2147483648;
  41. const MAX_VARBINARY_SIZE = 2147483648;
  42. /**
  43. * Default values for timestamps - fill with current timestamp on inserting record, on changing and both cases
  44. */
  45. const TIMESTAMP_INIT_UPDATE = 'TIMESTAMP_INIT_UPDATE';
  46. const TIMESTAMP_INIT = 'TIMESTAMP_INIT';
  47. const TIMESTAMP_UPDATE = 'TIMESTAMP_UPDATE';
  48. /**
  49. * Actions used for foreign keys
  50. */
  51. const ACTION_CASCADE = 'CASCADE';
  52. const ACTION_SET_NULL = 'SET NULL';
  53. const ACTION_NO_ACTION = 'NO ACTION';
  54. const ACTION_RESTRICT = 'RESTRICT';
  55. const ACTION_SET_DEFAULT = 'SET DEFAULT';
  56. /**
  57. * Column option 'default'
  58. *
  59. * @var string
  60. */
  61. const OPTION_DEFAULT = 'default';
  62. /**
  63. * Column option 'identity'
  64. *
  65. * @var string
  66. */
  67. const OPTION_IDENTITY = 'identity';
  68. /**
  69. * Column option 'length'
  70. *
  71. * @var string
  72. */
  73. const OPTION_LENGTH = 'length';
  74. /**
  75. * Column option 'nullable'
  76. *
  77. * @var string
  78. */
  79. const OPTION_NULLABLE = 'nullable';
  80. /**
  81. * Column option 'precision'
  82. *
  83. * @var string
  84. */
  85. const OPTION_PRECISION = 'precision';
  86. /**
  87. * Column option 'primary'
  88. *
  89. * @var string
  90. */
  91. const OPTION_PRIMARY = 'primary';
  92. /**
  93. * Column option 'scale'
  94. *
  95. * @var string
  96. */
  97. const OPTION_SCALE = 'scale';
  98. /**
  99. * Column option 'type'
  100. *
  101. * @var string
  102. */
  103. const OPTION_TYPE = 'type';
  104. /**
  105. * Column option 'unsigned'
  106. *
  107. * @var string
  108. */
  109. const OPTION_UNSIGNED = 'unsigned';
  110. /**
  111. * Name of table
  112. *
  113. * @var string
  114. */
  115. protected $_tableName;
  116. /**
  117. * Schema name
  118. *
  119. * @var string
  120. */
  121. protected $_schemaName;
  122. /**
  123. * Comment for Table
  124. *
  125. * @var string
  126. */
  127. protected $_tableComment;
  128. /**
  129. * Column descriptions for a table
  130. *
  131. * Is an associative array keyed by the uppercase column name
  132. * The value of each array element is an associative array
  133. * with the following keys:
  134. *
  135. * COLUMN_NAME => string; column name
  136. * COLUMN_POSITION => number; ordinal position of column in table
  137. * DATA_TYPE => string; constant datatype of column
  138. * DEFAULT => string; default expression of column, null if none
  139. * NULLABLE => boolean; true if column can have nulls
  140. * LENGTH => number; length of CHAR/VARCHAR/INT
  141. * SCALE => number; scale of NUMERIC/DECIMAL
  142. * PRECISION => number; precision of NUMERIC/DECIMAL
  143. * UNSIGNED => boolean; unsigned property of an integer type
  144. * PRIMARY => boolean; true if column is part of the primary key
  145. * PRIMARY_POSITION => integer; position of column in primary key
  146. * IDENTITY => integer; true if column is auto-generated with unique values
  147. * COMMENT => string; column description
  148. *
  149. * @var array
  150. */
  151. protected $_columns = [];
  152. /**
  153. * Index descriptions for a table
  154. *
  155. * Is an associative array keyed by the uppercase index name
  156. * The value of each array element is an associative array
  157. * with the following keys:
  158. *
  159. * INDEX_NAME => string; index name
  160. * COLUMNS => array; array of index columns
  161. * TYPE => string; Optional special index type
  162. *
  163. * COLUMNS is an associative array keyed by the uppercase column name
  164. * The value of each COLUMNS array element is an associative array
  165. * with the following keys:
  166. *
  167. * NAME => string; The column name
  168. * SIZE => int|null; Length of index column (always null if index is unique)
  169. * POSITION => int; Position in index
  170. *
  171. * @var array
  172. */
  173. protected $_indexes = [];
  174. /**
  175. * Foreign key descriptions for a table
  176. *
  177. * Is an associative array keyed by the uppercase foreign key name
  178. * The value of each array element is an associative array
  179. * with the following keys:
  180. *
  181. * FK_NAME => string; The foreign key name
  182. * COLUMN_NAME => string; The column name in table
  183. * REF_TABLE_NAME => string; Reference table name
  184. * REF_COLUMN_NAME => string; Reference table column name
  185. * ON_DELETE => string; Integrity Actions, default NO ACTION
  186. * ON_UPDATE => string; Integrity Actions, default NO ACTION
  187. *
  188. * Valid Integrity Actions:
  189. * CASCADE | SET NULL | NONE | RESTRICT | SET DEFAULT
  190. *
  191. * @var array
  192. */
  193. protected $_foreignKeys = [];
  194. /**
  195. * Additional table options
  196. *
  197. * @var array
  198. */
  199. protected $_options = ['type' => 'INNODB', 'charset' => 'utf8', 'collate' => 'utf8_general_ci'];
  200. /**
  201. * Set table name
  202. *
  203. * @param string $name
  204. * @return $this
  205. */
  206. public function setName($name)
  207. {
  208. $this->_tableName = $name;
  209. if ($this->_tableComment === null) {
  210. $this->_tableComment = $name;
  211. }
  212. return $this;
  213. }
  214. /**
  215. * Set schema name
  216. *
  217. * @param string $name
  218. * @return $this
  219. */
  220. public function setSchema($name)
  221. {
  222. $this->_schemaName = $name;
  223. return $this;
  224. }
  225. /**
  226. * Set comment for table
  227. *
  228. * @param string $comment
  229. * @return $this
  230. */
  231. public function setComment($comment)
  232. {
  233. $this->_tableComment = $comment;
  234. return $this;
  235. }
  236. /**
  237. * Retrieve name of table
  238. *
  239. * @return string
  240. * @throws \Zend_Db_Exception
  241. */
  242. public function getName()
  243. {
  244. if ($this->_tableName === null) {
  245. throw new \Zend_Db_Exception('Table name is not defined');
  246. }
  247. return $this->_tableName;
  248. }
  249. /**
  250. * Get schema name
  251. *
  252. * @return string|null
  253. */
  254. public function getSchema()
  255. {
  256. return $this->_schemaName;
  257. }
  258. /**
  259. * Return comment for table
  260. *
  261. * @return string
  262. */
  263. public function getComment()
  264. {
  265. return $this->_tableComment;
  266. }
  267. /**
  268. * Adds column to table.
  269. *
  270. * $options contains additional options for columns. Supported values are:
  271. * - 'unsigned', for number types only. Default: FALSE.
  272. * - 'precision', for numeric and decimal only. Default: taken from $size, if not set there then 0.
  273. * - 'scale', for numeric and decimal only. Default: taken from $size, if not set there then 10.
  274. * - 'default'. Default: not set.
  275. * - 'nullable'. Default: TRUE.
  276. * - 'primary', add column to primary index. Default: do not add.
  277. * - 'primary_position', only for column in primary index. Default: count of primary columns + 1.
  278. * - 'identity' or 'auto_increment'. Default: FALSE.
  279. *
  280. * @param string $name the column name
  281. * @param string $type the column data type
  282. * @param string|int|array $size the column length
  283. * @param array $options array of additional options
  284. * @param string $comment column description
  285. * @return $this
  286. * @throws \Zend_Db_Exception
  287. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  288. * @SuppressWarnings(PHPMD.NPathComplexity)
  289. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  290. */
  291. public function addColumn($name, $type, $size = null, $options = [], $comment = null)
  292. {
  293. $position = count($this->_columns);
  294. $default = false;
  295. $nullable = true;
  296. $length = null;
  297. $scale = null;
  298. $precision = null;
  299. $unsigned = false;
  300. $primary = false;
  301. $primaryPosition = 0;
  302. $identity = false;
  303. // Prepare different properties
  304. switch ($type) {
  305. case self::TYPE_BOOLEAN:
  306. break;
  307. case self::TYPE_SMALLINT:
  308. case self::TYPE_INTEGER:
  309. case self::TYPE_BIGINT:
  310. if (!empty($options['unsigned'])) {
  311. $unsigned = true;
  312. }
  313. break;
  314. case self::TYPE_FLOAT:
  315. if (!empty($options['unsigned'])) {
  316. $unsigned = true;
  317. }
  318. break;
  319. case self::TYPE_DECIMAL:
  320. case self::TYPE_NUMERIC:
  321. $match = [];
  322. $scale = 0;
  323. $precision = 10;
  324. // parse size value
  325. if (is_array($size)) {
  326. if (count($size) == 2) {
  327. $size = array_values($size);
  328. $precision = $size[0];
  329. $scale = $size[1];
  330. }
  331. } elseif (preg_match('#^(\d+),(\d+)$#', $size, $match)) {
  332. $precision = $match[1];
  333. $scale = $match[2];
  334. }
  335. // check options
  336. if (isset($options['precision'])) {
  337. $precision = $options['precision'];
  338. }
  339. if (isset($options['scale'])) {
  340. $scale = $options['scale'];
  341. }
  342. if (!empty($options['unsigned'])) {
  343. $unsigned = true;
  344. }
  345. break;
  346. case self::TYPE_DATE:
  347. case self::TYPE_DATETIME:
  348. case self::TYPE_TIMESTAMP:
  349. break;
  350. case self::TYPE_TEXT:
  351. case self::TYPE_BLOB:
  352. case self::TYPE_VARBINARY:
  353. $length = $size;
  354. break;
  355. default:
  356. throw new \Zend_Db_Exception('Invalid column data type "' . $type . '"');
  357. }
  358. if (array_key_exists('default', $options)) {
  359. $default = $options['default'];
  360. }
  361. if (array_key_exists('nullable', $options)) {
  362. $nullable = (bool)$options['nullable'];
  363. }
  364. if (!empty($options['primary'])) {
  365. $primary = true;
  366. if (isset($options['primary_position'])) {
  367. $primaryPosition = (int)$options['primary_position'];
  368. } else {
  369. $primaryPosition = 0;
  370. foreach ($this->_columns as $v) {
  371. if ($v['PRIMARY']) {
  372. $primaryPosition++;
  373. }
  374. }
  375. }
  376. }
  377. if (!empty($options['identity']) || !empty($options['auto_increment'])) {
  378. $identity = true;
  379. }
  380. if ($comment === null) {
  381. $comment = ucfirst($name);
  382. }
  383. $upperName = strtoupper($name);
  384. $this->_columns[$upperName] = [
  385. 'COLUMN_NAME' => $name,
  386. 'COLUMN_TYPE' => $type,
  387. 'COLUMN_POSITION' => $position,
  388. 'DATA_TYPE' => $type,
  389. 'DEFAULT' => $default,
  390. 'NULLABLE' => $nullable,
  391. 'LENGTH' => $length,
  392. 'SCALE' => $scale,
  393. 'PRECISION' => $precision,
  394. 'UNSIGNED' => $unsigned,
  395. 'PRIMARY' => $primary,
  396. 'PRIMARY_POSITION' => $primaryPosition,
  397. 'IDENTITY' => $identity,
  398. 'COMMENT' => $comment,
  399. ];
  400. return $this;
  401. }
  402. /**
  403. * Add Foreign Key to table
  404. *
  405. * @param string $fkName the foreign key name
  406. * @param string $column the foreign key column name
  407. * @param string $refTable the reference table name
  408. * @param string $refColumn the reference table column name
  409. * @param string $onDelete the action on delete row
  410. * @return $this
  411. * @throws \Zend_Db_Exception
  412. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  413. */
  414. public function addForeignKey($fkName, $column, $refTable, $refColumn, $onDelete = null)
  415. {
  416. $upperName = strtoupper($fkName);
  417. // validate column name
  418. if (!isset($this->_columns[strtoupper($column)])) {
  419. throw new \Zend_Db_Exception('Undefined column "' . $column . '"');
  420. }
  421. switch ($onDelete) {
  422. case self::ACTION_CASCADE:
  423. case self::ACTION_RESTRICT:
  424. case self::ACTION_SET_DEFAULT:
  425. case self::ACTION_SET_NULL:
  426. break;
  427. default:
  428. $onDelete = self::ACTION_NO_ACTION;
  429. }
  430. $this->_foreignKeys[$upperName] = [
  431. 'FK_NAME' => $fkName,
  432. 'COLUMN_NAME' => $column,
  433. 'REF_TABLE_NAME' => $refTable,
  434. 'REF_COLUMN_NAME' => $refColumn,
  435. 'ON_DELETE' => $onDelete
  436. ];
  437. return $this;
  438. }
  439. /**
  440. * Add index to table
  441. *
  442. * @param string $indexName the index name
  443. * @param array|string $fields array of columns or column string
  444. * @param array $options array of additional options
  445. * @return $this
  446. * @throws \Zend_Db_Exception
  447. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  448. */
  449. public function addIndex($indexName, $fields, $options = [])
  450. {
  451. $idxType = AdapterInterface::INDEX_TYPE_INDEX;
  452. $position = 0;
  453. $columns = [];
  454. if (!is_array($fields)) {
  455. $fields = [$fields];
  456. }
  457. foreach ($fields as $columnData) {
  458. $columnSize = null;
  459. $columnPos = $position;
  460. if (is_string($columnData)) {
  461. $columnName = $columnData;
  462. } elseif (is_array($columnData)) {
  463. if (!isset($columnData['name'])) {
  464. throw new \Zend_Db_Exception('Invalid index column data');
  465. }
  466. $columnName = $columnData['name'];
  467. if (!empty($columnData['size'])) {
  468. $columnSize = (int)$columnData['size'];
  469. }
  470. if (!empty($columnData['position'])) {
  471. $columnPos = (int)$columnData['position'];
  472. }
  473. } else {
  474. continue;
  475. }
  476. $columns[strtoupper(
  477. $columnName
  478. )] = [
  479. 'NAME' => $columnName,
  480. 'SIZE' => $columnSize,
  481. 'POSITION' => $columnPos,
  482. ];
  483. $position++;
  484. }
  485. if (empty($columns)) {
  486. throw new \Zend_Db_Exception('Columns for index are not defined');
  487. }
  488. if (!empty($options['type'])) {
  489. $idxType = $options['type'];
  490. }
  491. $this->_indexes[strtoupper(
  492. $indexName
  493. )] = [
  494. 'INDEX_NAME' => $indexName,
  495. 'COLUMNS' => $this->_normalizeIndexColumnPosition($columns),
  496. 'TYPE' => $idxType,
  497. ];
  498. return $this;
  499. }
  500. /**
  501. * Retrieve array of table columns
  502. *
  503. * @param bool $normalized
  504. * @see $this->_columns
  505. * @return array
  506. */
  507. public function getColumns($normalized = true)
  508. {
  509. if ($normalized) {
  510. return $this->_normalizeColumnPosition($this->_columns);
  511. }
  512. return $this->_columns;
  513. }
  514. /**
  515. * Set column, formatted according to DDL Table format, into columns structure
  516. *
  517. * @param array $column
  518. * @see $this->_columns
  519. * @return $this
  520. */
  521. public function setColumn($column)
  522. {
  523. $upperName = strtoupper($column['COLUMN_NAME']);
  524. $this->_columns[$upperName] = $column;
  525. return $this;
  526. }
  527. /**
  528. * Retrieve array of table indexes
  529. *
  530. * @see $this->_indexes
  531. * @return array
  532. */
  533. public function getIndexes()
  534. {
  535. return $this->_indexes;
  536. }
  537. /**
  538. * Retrieve array of table foreign keys
  539. *
  540. * @see $this->_foreignKeys
  541. * @return array
  542. */
  543. public function getForeignKeys()
  544. {
  545. return $this->_foreignKeys;
  546. }
  547. /**
  548. * Set table option
  549. *
  550. * @param string $key
  551. * @param string $value
  552. * @return $this
  553. */
  554. public function setOption($key, $value)
  555. {
  556. $this->_options[$key] = $value;
  557. return $this;
  558. }
  559. /**
  560. * Retrieve table option value by option name
  561. * Return null if option does not exits
  562. *
  563. * @param string $key
  564. * @return null|string
  565. */
  566. public function getOption($key)
  567. {
  568. if (!isset($this->_options[$key])) {
  569. return null;
  570. }
  571. return $this->_options[$key];
  572. }
  573. /**
  574. * Retrieve array of table options
  575. *
  576. * @return array
  577. */
  578. public function getOptions()
  579. {
  580. return $this->_options;
  581. }
  582. /**
  583. * Index column position comparison function
  584. *
  585. * @param array $a
  586. * @param array $b
  587. * @return int
  588. */
  589. protected function _sortIndexColumnPosition($a, $b)
  590. {
  591. return $a['POSITION'] - $b['POSITION'];
  592. }
  593. /**
  594. * table column position comparison function
  595. *
  596. * @param array $a
  597. * @param array $b
  598. * @return int
  599. */
  600. protected function _sortColumnPosition($a, $b)
  601. {
  602. return $a['COLUMN_POSITION'] - $b['COLUMN_POSITION'];
  603. }
  604. /**
  605. * Normalize positon of index columns array
  606. *
  607. * @param array $columns
  608. * @return array
  609. */
  610. protected function _normalizeIndexColumnPosition($columns)
  611. {
  612. uasort($columns, [$this, '_sortIndexColumnPosition']);
  613. $position = 0;
  614. foreach (array_keys($columns) as $columnId) {
  615. $columns[$columnId]['POSITION'] = $position;
  616. $position++;
  617. }
  618. return $columns;
  619. }
  620. /**
  621. * Normalize positon of table columns array
  622. *
  623. * @param array $columns
  624. * @return array
  625. */
  626. protected function _normalizeColumnPosition($columns)
  627. {
  628. uasort($columns, [$this, '_sortColumnPosition']);
  629. $position = 0;
  630. foreach (array_keys($columns) as $columnId) {
  631. $columns[$columnId]['COLUMN_POSITION'] = $position;
  632. $position++;
  633. }
  634. return $columns;
  635. }
  636. }