PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/moodle/lib/xmldb/classes/generators/db2/db2.class.php

https://bitbucket.org/geek745/moodle-db2
PHP | 1434 lines | 188 code | 64 blank | 1182 comment | 19 complexity | f8701a02c89ad60a0ea7fb44e505a346 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause, LGPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php // $Id: db2.class.php,v 1.95 2009/10/20 21:01:30 hblove1 Exp $
  2. ///////////////////////////////////////////////////////////////////////////
  3. // //
  4. // NOTICE OF COPYRIGHT //
  5. // //
  6. // Moodle - Modular Object-Oriented Dynamic Learning Environment //
  7. // http://moodle.com //
  8. // //
  9. // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
  10. // (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com //
  11. // //
  12. // This program is free software; you can redistribute it and/or modify //
  13. // it under the terms of the GNU General Public License as published by //
  14. // the Free Software Foundation; either version 2 of the License, or //
  15. // (at your option) any later version. //
  16. // //
  17. // This program is distributed in the hope that it will be useful, //
  18. // but WITHOUT ANY WARRANTY; without even the implied warranty of //
  19. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
  20. // GNU General Public License for more details: //
  21. // //
  22. // http://www.gnu.org/copyleft/gpl.html //
  23. // //
  24. ///////////////////////////////////////////////////////////////////////////
  25. /// This class generate SQL code to be used against DB2
  26. /// It extends XMLDBgenerator so everything can be
  27. /// overriden as needed to generate correct SQL.
  28. class XMLDBdb2 extends XMLDBgenerator {
  29. /// Only set values that are different from the defaults present in XMLDBgenerator
  30. var $quote_string = ''; // String used to quote names
  31. // var $quote_all = false; // To decide if we want to quote all the names or only the reserved ones
  32. // var $statement_end = ';'; // String to be automatically added at the end of each statement
  33. // based on research: ftp://ftp.software.ibm.com/ps/products/db2/info/vr8/pdf/letter/db2s1e80.pdf pg. 553
  34. var $statement_end = '';
  35. // var $integer_to_number = false; // To create all the integers as NUMBER(x) (also called DECIMAL, NUMERIC...)
  36. // var $float_to_number = false; // To create all the floats as NUMBER(x) (also called DECIMAL, NUMERIC...)
  37. // var $number_type = 'NUMERIC'; // Proper type for NUMBER(x) in this DB
  38. // based on research: ftp://ftp.software.ibm.com/ps/products/db2/info/vr8/pdf/letter/db2s1e80.pdf pg. 94
  39. var $number_type = 'DECIMAL';
  40. // var $unsigned_allowed = true; // To define in the generator must handle unsigned information
  41. // based on research: ftp://ftp.software.ibm.com/ps/products/db2/info/vr8/pdf/letter/db2s1e80.pdf pg. 94
  42. var $unsigned_allowed = false; // To define in the generator must handle unsigned information
  43. // var $default_for_char = null; // To define the default to set for NOT NULLs CHARs without default (null=do nothing)
  44. var $default_for_char = '';
  45. // var $drop_default_clause_required = false; //To specify if the generator must use some DEFAULT clause to drop defaults
  46. // var $drop_default_clause = ''; //The DEFAULT clause required to drop defaults
  47. // var $default_after_null = true; //To decide if the default clause of each field must go after the null clause
  48. // var $specify_nulls = false; //To force the generator if NULL clauses must be specified. It shouldn't be necessary
  49. //but some mssql drivers require them or everything is created as NOT NULL :-(
  50. // var $primary_key_name = null; //To force primary key names to one string (null=no force)
  51. // var $primary_keys = true; // Does the generator build primary keys
  52. // var $unique_keys = false; // Does the generator build unique keys
  53. // var $foreign_keys = false; // Does the generator build foreign keys
  54. // *********************************************************** //
  55. // ******************** COME BACK TO THIS ******************** //
  56. // ************************ DROP KEYS ************************ //
  57. // *********************************************************** //
  58. // var $drop_primary_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME'; // Template to drop PKs
  59. // with automatic replace for TABLENAME and KEYNAME
  60. // var $drop_unique_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME'; // Template to drop UKs
  61. // with automatic replace for TABLENAME and KEYNAME
  62. // var $drop_foreign_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME'; // Template to drop FKs
  63. // with automatic replace for TABLENAME and KEYNAME
  64. // var $sequence_extra_code = true; //Does the generator need to add extra code to generate the sequence fields
  65. var $sequence_extra_code = false; //Does the generator need to add extra code to generate the sequence fields
  66. // var $sequence_name = 'auto_increment'; //Particular name for inline sequences in this generator
  67. // based on research: http://publib.boulder.ibm.com/infocenter/db2luw/v8//index.jsp
  68. var $sequence_name = 'GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)';
  69. // var $sequence_name_small = false; //Different name for small (4byte) sequences or false if same
  70. // var $sequence_only = false; //To avoid to output the rest of the field specs, leaving only the name and the sequence_name variable
  71. // var $enum_inline_code = true; //Does the generator need to add inline code in the column definition
  72. // var $enum_extra_code = true; //Does the generator need to add extra code to generate code for the enums in the table
  73. // based on research: http://stackoverflow.com/questions/336997/which-database-systems-support-an-enum-data-type-which-dont
  74. var $enum_inline_code = false; //Does the generator need to add inline code in the column definition
  75. // var $add_table_comments = true; // Does the generator need to add code for table comments
  76. var $add_table_comments = false;
  77. // var $add_after_clause = false; // Does the generator need to add the after clause for fields
  78. // var $prefix_on_names = true; //Does the generator need to prepend the prefix to all the key/index/sequence/trigger/check names
  79. // var $names_max_length = 30; //Max length for key/index/sequence/trigger/check names (keep 30 for all!)
  80. // var $concat_character = '||'; //Characters to be used as concatenation operator. If not defined
  81. //MySQL CONCAT function will be used
  82. // var $concat_character = null;
  83. // *********************************************************** //
  84. // ******************** COME BACK TO THIS ******************** //
  85. // ********************** RENAME TABLES ********************** //
  86. // *********************************************************** //
  87. // var $rename_table_sql = 'ALTER TABLE OLDNAME RENAME TO NEWNAME'; //SQL sentence to rename one table, both
  88. //OLDNAME and NEWNAME are dinamically replaced
  89. // var $rename_table_extra_code = false; //Does the generator need to add code after table rename
  90. // *********************************************************** //
  91. // ******************** COME BACK TO THIS ******************** //
  92. // *********************** DROP TABLES *********************** //
  93. // *********************************************************** //
  94. // var $drop_table_sql = 'DROP TABLE TABLENAME'; //SQL sentence to drop one table
  95. //TABLENAME is dinamically replaced
  96. // var $drop_table_extra_code = false; //Does the generator need to add code after table drop
  97. // *********************************************************** //
  98. // ******************** COME BACK TO THIS ******************** //
  99. // ********************** ALTER COLUMNS ********************** //
  100. // *********************************************************** //
  101. // var $alter_column_sql = 'ALTER TABLE TABLENAME ALTER COLUMN COLUMNSPECS'; //The SQL template to alter columns
  102. // var $alter_column_skip_default = false; //The generator will skip the default clause on alter columns
  103. // var $alter_column_skip_type = false; //The generator will skip the type clause on alter columns
  104. // var $alter_column_skip_notnull = false; //The generator will skip the null/notnull clause on alter columns
  105. // *********************************************************** //
  106. // ******************** COME BACK TO THIS ******************** //
  107. // ********************** RENAME COLUMN ********************** //
  108. // *********************************************************** //
  109. // var $rename_column_sql = 'ALTER TABLE TABLENAME RENAME COLUMN OLDFIELDNAME TO NEWFIELDNAME';
  110. ///TABLENAME, OLDFIELDNAME and NEWFIELDNAME are dianmically replaced
  111. // var $rename_column_extra_code = false; //Does the generator need to add code after column rename
  112. // *********************************************************** //
  113. // ******************** COME BACK TO THIS ******************** //
  114. // ************************* INDEXES ************************* //
  115. // *********************** DROP,RENAME *********************** //
  116. // *********************************************************** //
  117. // var $drop_index_sql = 'DROP INDEX INDEXNAME'; //SQL sentence to drop one index
  118. //TABLENAME, INDEXNAME are dinamically replaced
  119. // var $rename_index_sql = 'ALTER INDEX OLDINDEXNAME RENAME TO NEWINDEXNAME'; //SQL sentence to rename one index
  120. //TABLENAME, OLDINDEXNAME, NEWINDEXNAME are dinamically replaced
  121. // *********************************************************** //
  122. // ******************** COME BACK TO THIS ******************** //
  123. // *********************** RENAME KEYS *********************** //
  124. // *********************************************************** //
  125. // var $rename_key_sql = 'ALTER TABLE TABLENAME CONSTRAINT OLDKEYNAME RENAME TO NEWKEYNAME'; //SQL sentence to rename one key
  126. //TABLENAME, OLDKEYNAME, NEWKEYNAME are dinamically replaced
  127. // var $prefix; // Prefix to be used for all the DB objects
  128. // var $reserved_words; // List of reserved words (in order to quote them properly)
  129. /**
  130. * Creates one new XMLDBdb2
  131. */
  132. function XMLDBdb2() {
  133. parent::XMLDBgenerator();
  134. $this->prefix = '';
  135. $this->reserved_words = $this->getReservedWords();
  136. }
  137. /// ALL THESE FUNCTION ARE SHARED BY ALL THE XMLDGenerator classes
  138. /**
  139. * Set the prefix
  140. *//*
  141. function setPrefix($prefix) {
  142. if ($this->prefix_on_names) { // Only if we want prefix on names
  143. $this->prefix = $prefix;
  144. }
  145. }*/
  146. /**
  147. * Given one XMLDBTable, returns it's correct name, depending of all the parametrization
  148. *
  149. * @param XMLDBTable table whose name we want
  150. * @param boolean to specify if the name must be quoted (if reserved word, only!)
  151. * @return string the correct name of the table
  152. *//*
  153. function getTableName($xmldb_table, $quoted = true) {
  154. $prefixtouse = $this->prefix;
  155. /// Determinate if this table must have prefix or no
  156. if (in_array($xmldb_table->getName(), $this->getTablesWithoutPrefix())) {
  157. $prefixtouse = '';
  158. }
  159. /// Add Schema name for DB2
  160. $prefixtouse = $this->schema . '.' . $prefixtouse;
  161. /// Get the name
  162. $tablename = $prefixtouse . $xmldb_table->getName();
  163. /// Apply quotes conditionally
  164. if ($quoted) {
  165. $tablename = $this->getEncQuoted($tablename);
  166. }
  167. return $tablename;
  168. }
  169. /**
  170. * Given one correct XMLDBTable, returns the SQL statements
  171. * to create it (inside one array)
  172. *//*
  173. function getCreateTableSQL($xmldb_table) {
  174. $results = array(); //Array where all the sentences will be stored
  175. /// Table header
  176. $table = 'CREATE TABLE ' . $this->getTableName($xmldb_table) . ' (';
  177. if (!$xmldb_fields = $xmldb_table->getFields()) {
  178. return $results;
  179. }
  180. /// Prevent tables without prefix to be duplicated (part of MDL-6614)
  181. if (in_array($xmldb_table->getName(), $this->getTablesWithoutPrefix()) &&
  182. table_exists($xmldb_table)) {
  183. return $results; // false here would break the install, empty array is better ;-)
  184. }
  185. /// Add the fields, separated by commas
  186. foreach ($xmldb_fields as $xmldb_field) {
  187. $table .= "\n " . $this->getFieldSQL($xmldb_field);
  188. $table .= ',';
  189. }
  190. /// Add the keys, separated by commas
  191. if ($xmldb_keys = $xmldb_table->getKeys()) {
  192. foreach ($xmldb_keys as $xmldb_key) {
  193. if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {
  194. $table .= "\nCONSTRAINT " . $keytext . ',';
  195. }
  196. /// If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE too
  197. if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE) {
  198. ///Duplicate the key
  199. $xmldb_key->setType(XMLDB_KEY_UNIQUE);
  200. if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {
  201. $table .= "\nCONSTRAINT " . $keytext . ',';
  202. }
  203. }
  204. }
  205. }
  206. /// Add enum extra code if needed
  207. if ($this->enum_extra_code) {
  208. /// Iterate over fields looking for enums
  209. foreach ($xmldb_fields as $xmldb_field) {
  210. if ($xmldb_field->getEnum()) {
  211. $table .= "\n" . $this->getEnumExtraSQL($xmldb_table, $xmldb_field) . ',';
  212. }
  213. }
  214. }
  215. /// Table footer, trim the latest comma
  216. $table = trim($table,',');
  217. $table .= "\n)";
  218. /// Add the CREATE TABLE to results
  219. $results[] = $table;
  220. /// Add comments if specified and it exists
  221. if ($this->add_table_comments && $xmldb_table->getComment()) {
  222. $comment = $this->getCommentSQL ($xmldb_table);
  223. /// Add the COMMENT to results
  224. $results = array_merge($results, $comment);
  225. }
  226. /// Add the indexes (each one, one statement)
  227. if ($xmldb_indexes = $xmldb_table->getIndexes()) {
  228. foreach ($xmldb_indexes as $xmldb_index) {
  229. ///Only process all this if the index doesn't exist in DB
  230. if (!index_exists($xmldb_table, $xmldb_index)) {
  231. if ($indextext = $this->getCreateIndexSQL($xmldb_table, $xmldb_index)) {
  232. $results = array_merge($results, $indextext);
  233. }
  234. }
  235. }
  236. }
  237. /// Also, add the indexes needed from keys, based on configuration (each one, one statement)
  238. if ($xmldb_keys = $xmldb_table->getKeys()) {
  239. foreach ($xmldb_keys as $xmldb_key) {
  240. /// If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
  241. /// automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)
  242. if (!$this->getKeySQL($xmldb_table, $xmldb_key) || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
  243. /// Create the interim index
  244. $index = new XMLDBIndex('anyname');
  245. $index->setFields($xmldb_key->getFields());
  246. ///Only process all this if the index doesn't exist in DB
  247. if (!index_exists($xmldb_table, $index)) {
  248. $createindex = false; //By default
  249. switch ($xmldb_key->getType()) {
  250. case XMLDB_KEY_UNIQUE:
  251. case XMLDB_KEY_FOREIGN_UNIQUE:
  252. $index->setUnique(true);
  253. $createindex = true;
  254. break;
  255. case XMLDB_KEY_FOREIGN:
  256. $index->setUnique(false);
  257. $createindex = true;
  258. break;
  259. }
  260. if ($createindex) {
  261. if ($indextext = $this->getCreateIndexSQL($xmldb_table, $index)) {
  262. /// Add the INDEX to the array
  263. $results = array_merge($results, $indextext);
  264. }
  265. }
  266. }
  267. }
  268. }
  269. }
  270. /// Add sequence extra code if needed
  271. if ($this->sequence_extra_code) {
  272. /// Iterate over fields looking for sequences
  273. foreach ($xmldb_fields as $xmldb_field) {
  274. if ($xmldb_field->getSequence()) {
  275. /// returns an array of statements needed to create one sequence
  276. $sequence_sentences = $this->getCreateSequenceSQL($xmldb_table, $xmldb_field);
  277. /// Add the SEQUENCE to the array
  278. $results = array_merge($results, $sequence_sentences);
  279. }
  280. }
  281. }
  282. return $results;
  283. }
  284. /**
  285. * Given one correct XMLDBIndex, returns the SQL statements
  286. * needed to create it (in array)
  287. *//*
  288. function getCreateIndexSQL ($xmldb_table, $xmldb_index) {
  289. $unique = '';
  290. $suffix = 'ix';
  291. if ($xmldb_index->getUnique()) {
  292. $unique = ' UNIQUE';
  293. $suffix = 'uix';
  294. }
  295. $index = 'CREATE' . $unique . ' INDEX ';
  296. $index .= $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_index->getFields()), $suffix);
  297. $index .= ' ON ' . $this->getTableName($xmldb_table);
  298. $index .= ' (' . implode(', ', $this->getEncQuoted($xmldb_index->getFields())) . ')';
  299. return array($index);
  300. }
  301. /**
  302. * Given one correct XMLDBField, returns the complete SQL line to create it
  303. *//*
  304. function getFieldSQL($xmldb_field, $skip_type_clause = false, $skip_default_clause = false, $skip_notnull_clause = false) {
  305. /// First of all, convert integers to numbers if defined
  306. if ($this->integer_to_number) {
  307. if ($xmldb_field->getType() == XMLDB_TYPE_INTEGER) {
  308. $xmldb_field->setType(XMLDB_TYPE_NUMBER);
  309. }
  310. }
  311. /// Same for floats
  312. if ($this->float_to_number) {
  313. if ($xmldb_field->getType() == XMLDB_TYPE_FLOAT) {
  314. $xmldb_field->setType(XMLDB_TYPE_NUMBER);
  315. }
  316. }
  317. /// The name
  318. $field = $this->getEncQuoted($xmldb_field->getName());
  319. /// The type and length only if we don't want to skip it
  320. if (!$skip_type_clause) {
  321. /// The type and length (if the field isn't enum)
  322. if (!$xmldb_field->getEnum() || $this->enum_inline_code == false) {
  323. $field .= ' ' . $this->getTypeSQL($xmldb_field->getType(), $xmldb_field->getLength(), $xmldb_field->getDecimals());
  324. } else {
  325. /// call to custom function
  326. $field .= ' ' . $this->getEnumSQL($xmldb_field);
  327. }
  328. }
  329. /// The unsigned if supported
  330. if ($this->unsigned_allowed && ($xmldb_field->getType() == XMLDB_TYPE_INTEGER ||
  331. $xmldb_field->getType() == XMLDB_TYPE_NUMBER ||
  332. $xmldb_field->getType() == XMLDB_TYPE_FLOAT)) {
  333. if ($xmldb_field->getUnsigned()) {
  334. $field .= ' unsigned';
  335. }
  336. }
  337. /// Calculate the not null clause
  338. $notnull = '';
  339. /// Only if we don't want to skip it
  340. if (!$skip_notnull_clause) {
  341. if ($xmldb_field->getNotNull()) {
  342. $notnull = ' NOT NULL';
  343. } else {
  344. if ($this->specify_nulls) {
  345. $notnull = ' NULL';
  346. }
  347. }
  348. }
  349. /// Calculate the default clause
  350. if (!$skip_default_clause) { //Only if we don't want to skip it
  351. $default = $this->getDefaultClause($xmldb_field);
  352. } else {
  353. $default = '';
  354. }
  355. /// Based on default_after_null, set both clauses properly
  356. if ($this->default_after_null) {
  357. $field .= $notnull . $default;
  358. } else {
  359. $field .= $default . $notnull;
  360. }
  361. /// The sequence
  362. if ($xmldb_field->getSequence()) {
  363. if($xmldb_field->getLength()<=9 && $this->sequence_name_small) {
  364. $sequencename=$this->sequence_name_small;
  365. } else {
  366. $sequencename=$this->sequence_name;
  367. }
  368. $field .= ' ' . $sequencename;
  369. if ($this->sequence_only) {
  370. /// We only want the field name and sequence name to be printed
  371. /// so, calculate it and return
  372. return $this->getEncQuoted($xmldb_field->getName()) . ' ' . $sequencename;
  373. }
  374. }
  375. return $field;
  376. }
  377. /**
  378. * Given one correct XMLDBKey, returns its specs
  379. *//*
  380. function getKeySQL ($xmldb_table, $xmldb_key) {
  381. $key = '';
  382. switch ($xmldb_key->getType()) {
  383. case XMLDB_KEY_PRIMARY:
  384. if ($this->primary_keys) {
  385. if ($this->primary_key_name !== null) {
  386. $key = $this->getEncQuoted($this->primary_key_name);
  387. } else {
  388. $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'pk');
  389. }
  390. $key .= ' PRIMARY KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
  391. }
  392. break;
  393. case XMLDB_KEY_UNIQUE:
  394. if ($this->unique_keys) {
  395. $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'uk');
  396. $key .= ' UNIQUE (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
  397. }
  398. break;
  399. case XMLDB_KEY_FOREIGN:
  400. case XMLDB_KEY_FOREIGN_UNIQUE:
  401. if ($this->foreign_keys) {
  402. $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'fk');
  403. $key .= ' FOREIGN KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
  404. $key .= ' REFERENCES ' . $this->getEncQuoted($this->prefix . $xmldb_key->getRefTable());
  405. $key .= ' (' . implode(', ', $this->getEncQuoted($xmldb_key->getRefFields())) . ')';
  406. }
  407. break;
  408. }
  409. return $key;
  410. }
  411. /**
  412. * Give one XMLDBField, returns the correct "default value" for the current configuration
  413. *//*
  414. function getDefaultValue ($xmldb_field) {
  415. $default = null;
  416. if ($xmldb_field->getDefault() !== NULL) {
  417. if ($xmldb_field->getType() == XMLDB_TYPE_CHAR ||
  418. $xmldb_field->getType() == XMLDB_TYPE_TEXT) {
  419. $default = "'" . addslashes($xmldb_field->getDefault()) . "'";
  420. } else {
  421. $default = $xmldb_field->getDefault();
  422. }
  423. } else {
  424. /// We force default '' for not null char columns without proper default
  425. /// some day this should be out!
  426. if ($this->default_for_char !== NULL &&
  427. $xmldb_field->getType() == XMLDB_TYPE_CHAR &&
  428. $xmldb_field->getNotNull()) {
  429. $default = "'" . $this->default_for_char . "'";
  430. } else {
  431. /// If the DB requires to explicity define some clause to drop one default, do it here
  432. /// never applying defaults to TEXT and BINARY fields
  433. if ($this->drop_default_clause_required &&
  434. $xmldb_field->getType() != XMLDB_TYPE_TEXT &&
  435. $xmldb_field->getType() != XMLDB_TYPE_BINARY && !$xmldb_field->getNotNull()) {
  436. $default = $this->drop_default_clause;
  437. }
  438. }
  439. }
  440. return $default;
  441. }
  442. /**
  443. * Given one XMLDBField, returns the correct "default clause" for the current configuration
  444. *//*
  445. function getDefaultClause ($xmldb_field) {
  446. $defaultvalue = $this->getDefaultValue ($xmldb_field);
  447. if ($defaultvalue !== null) {
  448. return ' DEFAULT ' . $defaultvalue;
  449. } else {
  450. return null;
  451. }
  452. }
  453. /**
  454. * Given one correct XMLDBTable and the new name, returns the SQL statements
  455. * to rename it (inside one array)
  456. *//*
  457. function getRenameTableSQL($xmldb_table, $newname) {
  458. $results = array(); //Array where all the sentences will be stored
  459. $newt = new XMLDBTable($newname); //Temporal table for name calculations
  460. $rename = str_replace('OLDNAME', $this->getTableName($xmldb_table), $this->rename_table_sql);
  461. $rename = str_replace('NEWNAME', $this->getTableName($newt), $rename);
  462. $results[] = $rename;
  463. /// Call to getRenameTableExtraSQL() if $rename_table_extra_code is enabled. It will add sequence regeneration code.
  464. if ($this->rename_table_extra_code) {
  465. $extra_sentences = $this->getRenameTableExtraSQL($xmldb_table, $newname);
  466. $results = array_merge($results, $extra_sentences);
  467. }
  468. return $results;
  469. }
  470. /**
  471. * Given one correct XMLDBTable and the new name, returns the SQL statements
  472. * to drop it (inside one array)
  473. *//*
  474. function getDropTableSQL($xmldb_table) {
  475. $results = array(); //Array where all the sentences will be stored
  476. $drop = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_table_sql);
  477. $results[] = $drop;
  478. /// call to getDropTableExtraSQL() if $drop_table_extra_code is enabled. It will add sequence/trigger drop code.
  479. if ($this->drop_table_extra_code) {
  480. $extra_sentences = $this->getDropTableExtraSQL($xmldb_table);
  481. $results = array_merge($results, $extra_sentences);
  482. }
  483. return $results;
  484. }
  485. /**
  486. * Given one XMLDBTable and one XMLDBField, return the SQL statements needded to add the field to the table
  487. *//*
  488. function getAddFieldSQL($xmldb_table, $xmldb_field) {
  489. $results = array();
  490. /// Get the quoted name of the table and field
  491. $tablename = $this->getTableName($xmldb_table);
  492. /// Build the standard alter table add
  493. $altertable = 'ALTER TABLE ' . $tablename . ' ADD ' .
  494. $this->getFieldSQL($xmldb_field, $this->alter_column_skip_type,
  495. $this->alter_column_skip_default,
  496. $this->alter_column_skip_notnull);
  497. /// Add the after clause if necesary
  498. if ($this->add_after_clause && $xmldb_field->getPrevious()) {
  499. $altertable .= ' after ' . $this->getEncQuoted($xmldb_field->getPrevious());
  500. }
  501. $results[] = $altertable;
  502. /// If the DB has extra enum code
  503. if ($this->enum_extra_code) {
  504. /// If it's enum add the extra code
  505. if ($xmldb_field->getEnum()) {
  506. $results[] = 'ALTER TABLE ' . $tablename . ' ADD ' . $this->getEnumExtraSQL($xmldb_table, $xmldb_field);
  507. }
  508. }
  509. return $results;
  510. }
  511. /**
  512. * Given one XMLDBTable and one XMLDBField, return the SQL statements needded to drop the field from the table
  513. *//*
  514. function getDropFieldSQL($xmldb_table, $xmldb_field) {
  515. $results = array();
  516. /// Get the quoted name of the table and field
  517. $tablename = $this->getTableName($xmldb_table);
  518. $fieldname = $this->getEncQuoted($xmldb_field->getName());
  519. /// Build the standard alter table drop
  520. $results[] = 'ALTER TABLE ' . $tablename . ' DROP COLUMN ' . $fieldname;
  521. return $results;
  522. }
  523. /**
  524. * Given one XMLDBTable and one XMLDBField, return the SQL statements needded to alter the field in the table
  525. *//*
  526. function getAlterFieldSQL($xmldb_table, $xmldb_field) {
  527. $results = array();
  528. /// Always specify NULLs in alter fields because we can change not nulls to nulls
  529. $this->specify_nulls = true;
  530. /// Get the quoted name of the table and field
  531. $tablename = $this->getTableName($xmldb_table);
  532. $fieldname = $this->getEncQuoted($xmldb_field->getName());
  533. /// Build de alter sentence using the alter_column_sql template
  534. $alter = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->alter_column_sql);
  535. $alter = str_replace('COLUMNSPECS', $this->getFieldSQL($xmldb_field, $this->alter_column_skip_type,
  536. $this->alter_column_skip_default,
  537. $this->alter_column_skip_notnull), $alter);
  538. /// Add the after clause if necesary
  539. if ($this->add_after_clause && $xmldb_field->getPrevious()) {
  540. $alter .= ' after ' . $this->getEncQuoted($xmldb_field->getPrevious());
  541. }
  542. /// Build the standard alter table modify
  543. $results[] = $alter;
  544. return $results;
  545. }
  546. /**
  547. * Given one XMLDBTable and one XMLDBField, return the SQL statements needded to modify the enum of the field in the table
  548. *//*
  549. function getModifyEnumSQL($xmldb_table, $xmldb_field) {
  550. $results = array();
  551. /// Get the quoted name of the table and field
  552. $tablename = $this->getTableName($xmldb_table);
  553. $fieldname = $this->getEncQuoted($xmldb_field->getName());
  554. /// Decide if we are going to create or to drop the enum (based exclusively in the values passed!)
  555. if (!$xmldb_field->getEnum()) {
  556. $results = $this->getDropEnumSQL($xmldb_table, $xmldb_field); //Drop
  557. } else {
  558. $results = $this->getCreateEnumSQL($xmldb_table, $xmldb_field); //Create/modify
  559. }
  560. return $results;
  561. }
  562. /**
  563. * Given one XMLDBTable and one XMLDBField, return the SQL statements needded to modify the default of the field in the table
  564. *//*
  565. function getModifyDefaultSQL($xmldb_table, $xmldb_field) {
  566. $results = array();
  567. /// Get the quoted name of the table and field
  568. $tablename = $this->getTableName($xmldb_table);
  569. $fieldname = $this->getEncQuoted($xmldb_field->getName());
  570. /// Decide if we are going to create/modify or to drop the default
  571. if ($xmldb_field->getDefault() === null) {
  572. $results = $this->getDropDefaultSQL($xmldb_table, $xmldb_field); //Drop
  573. } else {
  574. $results = $this->getCreateDefaultSQL($xmldb_table, $xmldb_field); //Create/modify
  575. }
  576. return $results;
  577. }
  578. /**
  579. * Given one correct XMLDBField and the new name, returns the SQL statements
  580. * to rename it (inside one array)
  581. *//*
  582. function getRenameFieldSQL($xmldb_table, $xmldb_field, $newname) {
  583. $results = array(); //Array where all the sentences will be stored
  584. /// Although this is checked in ddllib - rename_field() - double check
  585. /// that we aren't trying to rename one "id" field. Although it could be
  586. /// implemented (if adding the necessary code to rename sequences, defaults,
  587. /// triggers... and so on under each getRenameFieldExtraSQL() function, it's
  588. /// better to forbide it, mainly because this field is the default PK and
  589. /// in the future, a lot of FKs can be pointing here. So, this field, more
  590. /// or less, must be considered inmutable!
  591. if ($xmldb_field->getName() == 'id') {
  592. return array();
  593. }
  594. $rename = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_column_sql);
  595. $rename = str_replace('OLDFIELDNAME', $this->getEncQuoted($xmldb_field->getName()), $rename);
  596. $rename = str_replace('NEWFIELDNAME', $this->getEncQuoted($newname), $rename);
  597. $results[] = $rename;
  598. /// Call to getRenameFieldExtraSQL() if $rename_column_extra_code is enabled (will add some required sentences)
  599. if ($this->rename_column_extra_code) {
  600. $extra_sentences = $this->getRenameFieldExtraSQL($xmldb_table, $xmldb_field, $newname);
  601. $results = array_merge($results, $extra_sentences);
  602. }
  603. return $results;
  604. }
  605. /**
  606. * Given one XMLDBTable and one XMLDBKey, return the SQL statements needded to add the key to the table
  607. * note that undelying indexes will be added as parametrised by $xxxx_keys and $xxxx_index parameters
  608. *//*
  609. function getAddKeySQL($xmldb_table, $xmldb_key) {
  610. $results = array();
  611. /// Just use the CreateKeySQL function
  612. if ($keyclause = $this->getKeySQL($xmldb_table, $xmldb_key)) {
  613. $key = 'ALTER TABLE ' . $this->getTableName($xmldb_table) .
  614. ' ADD CONSTRAINT ' . $keyclause;
  615. $results[] = $key;
  616. }
  617. /// If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
  618. /// automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)
  619. if (!$keyclause || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
  620. /// Only if they don't exist
  621. if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN) { ///Calculate type of index based on type ok key
  622. $indextype = XMLDB_INDEX_NOTUNIQUE;
  623. } else {
  624. $indextype = XMLDB_INDEX_UNIQUE;
  625. }
  626. $xmldb_index = new XMLDBIndex('anyname');
  627. $xmldb_index->setAttributes($indextype, $xmldb_key->getFields());
  628. if (!index_exists($xmldb_table, $xmldb_index)) {
  629. $results = array_merge($results, $this->getAddIndexSQL($xmldb_table, $xmldb_index));
  630. }
  631. }
  632. /// If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE too
  633. if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {
  634. ///Duplicate the key
  635. $xmldb_key->setType(XMLDB_KEY_UNIQUE);
  636. $results = array_merge($results, $this->getAddKeySQL($xmldb_table, $xmldb_key));
  637. }
  638. /// Return results
  639. return $results;
  640. }
  641. /**
  642. * Given one XMLDBTable and one XMLDBIndex, return the SQL statements needded to drop the index from the table
  643. *//*
  644. function getDropKeySQL($xmldb_table, $xmldb_key) {
  645. $results = array();
  646. /// Get the key name (note that this doesn't introspect DB, so could cause some problems sometimes!)
  647. /// TODO: We'll need to overwrite the whole getDropKeySQL() method inside each DB to do the proper queries
  648. /// against the dictionary or require ADOdb to support it or change the find_key_name() method to
  649. /// perform DB introspection directly. But, for now, as we aren't going to enable referential integrity
  650. /// it won't be a problem at all
  651. $dbkeyname = find_key_name($xmldb_table, $xmldb_key);
  652. /// Only if such type of key generation is enabled
  653. $dropkey = false;
  654. switch ($xmldb_key->getType()) {
  655. case XMLDB_KEY_PRIMARY:
  656. if ($this->primary_keys) {
  657. $template = $this->drop_primary_key;
  658. $dropkey = true;
  659. }
  660. break;
  661. case XMLDB_KEY_UNIQUE:
  662. if ($this->unique_keys) {
  663. $template = $this->drop_unique_key;
  664. $dropkey = true;
  665. }
  666. break;
  667. case XMLDB_KEY_FOREIGN_UNIQUE:
  668. case XMLDB_KEY_FOREIGN:
  669. if ($this->foreign_keys) {
  670. $template = $this->drop_foreign_key;
  671. $dropkey = true;
  672. }
  673. break;
  674. }
  675. /// If we have decided to drop the key, let's do it
  676. if ($dropkey) {
  677. /// Replace TABLENAME, CONSTRAINTTYPE and KEYNAME as needed
  678. $dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $template);
  679. $dropsql = str_replace('KEYNAME', $dbkeyname, $dropsql);
  680. $results[] = $dropsql;
  681. }
  682. /// If we aren't dropping the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
  683. /// automatically by the RDBMS) drop the underlying (created by us) index (if exists)
  684. if (!$dropkey || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
  685. /// Only if they exist
  686. $xmldb_index = new XMLDBIndex('anyname');
  687. $xmldb_index->setAttributes(XMLDB_INDEX_UNIQUE, $xmldb_key->getFields());
  688. if (index_exists($xmldb_table, $xmldb_index)) {
  689. $results = array_merge($results, $this->getDropIndexSQL($xmldb_table, $xmldb_index));
  690. }
  691. }
  692. /// If the key is XMLDB_KEY_FOREIGN_UNIQUE, drop the UNIQUE too
  693. if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {
  694. ///Duplicate the key
  695. $xmldb_key->setType(XMLDB_KEY_UNIQUE);
  696. $results = array_merge($results, $this->getDropKeySQL($xmldb_table, $xmldb_key));
  697. }
  698. /// Return results
  699. return $results;
  700. }
  701. /**
  702. * Given one XMLDBTable and one XMLDBKey, return the SQL statements needded to rename the key in the table
  703. * Experimental! Shouldn't be used at all!
  704. *//*
  705. function getRenameKeySQL($xmldb_table, $xmldb_key, $newname) {
  706. $results = array();
  707. /// Get the real key name
  708. $dbkeyname = find_key_name($xmldb_table, $xmldb_key);
  709. /// Check we are really generating this type of keys
  710. if (($xmldb_key->getType() == XMLDB_KEY_PRIMARY && !$this->primary_keys) ||
  711. ($xmldb_key->getType() == XMLDB_KEY_UNIQUE && !$this->unique_keys) ||
  712. ($xmldb_key->getType() == XMLDB_KEY_FOREIGN && !$this->foreign_keys) ||
  713. ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && !$this->unique_keys && !$this->foreign_keys)) {
  714. /// We aren't generating this type of keys, delegate to child indexes
  715. $xmldb_index = new XMLDBIndex($xmldb_key->getName());
  716. $xmldb_index->setFields($xmldb_key->getFields());
  717. return $this->getRenameIndexSQL($xmldb_table, $xmldb_index, $newname);
  718. }
  719. /// Arrived here so we are working with keys, lets rename them
  720. /// Replace TABLENAME and KEYNAME as needed
  721. $renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_key_sql);
  722. $renamesql = str_replace('OLDKEYNAME', $dbkeyname, $renamesql);
  723. $renamesql = str_replace('NEWKEYNAME', $newname, $renamesql);
  724. /// Some DB doesn't support key renaming so this can be empty
  725. if ($renamesql) {
  726. $results[] = $renamesql;
  727. }
  728. return $results;
  729. }
  730. /**
  731. * Given one XMLDBTable and one XMLDBIndex, return the SQL statements needded to add the index to the table
  732. *//*
  733. function getAddIndexSQL($xmldb_table, $xmldb_index) {
  734. /// Just use the CreateIndexSQL function
  735. return $this->getCreateIndexSQL($xmldb_table, $xmldb_index);
  736. }
  737. /**
  738. * Given one XMLDBTable and one XMLDBIndex, return the SQL statements needded to drop the index from the table
  739. *//*
  740. function getDropIndexSQL($xmldb_table, $xmldb_index) {
  741. $results = array();
  742. /// Get the real index name
  743. $dbindexname = find_index_name($xmldb_table, $xmldb_index);
  744. /// Replace TABLENAME and INDEXNAME as needed
  745. $dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_index_sql);
  746. $dropsql = str_replace('INDEXNAME', $dbindexname, $dropsql);
  747. $results[] = $dropsql;
  748. return $results;
  749. }
  750. /**
  751. * Given one XMLDBTable and one XMLDBIndex, return the SQL statements needded to rename the index in the table
  752. * Experimental! Shouldn't be used at all!
  753. *//*
  754. function getRenameIndexSQL($xmldb_table, $xmldb_index, $newname) {
  755. $results = array();
  756. /// Get the real index name
  757. $dbindexname = find_index_name($xmldb_table, $xmldb_index);
  758. /// Replace TABLENAME and INDEXNAME as needed
  759. $renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_index_sql);
  760. $renamesql = str_replace('OLDINDEXNAME', $dbindexname, $renamesql);
  761. $renamesql = str_replace('NEWINDEXNAME', $newname, $renamesql);
  762. /// Some DB doesn't support index renaming (MySQL) so this can be empty
  763. if ($renamesql) {
  764. $results[] = $renamesql;
  765. }
  766. return $results;
  767. }
  768. /**
  769. * Given three strings (table name, list of fields (comma separated) and suffix),
  770. * create the proper object name quoting it if necessary.
  771. *
  772. * IMPORTANT: This function must be used to CALCULATE NAMES of objects TO BE CREATED,
  773. * NEVER TO GUESS NAMES of EXISTING objects!!!
  774. *//*
  775. function getNameForObject($tablename, $fields, $suffix='') {
  776. $name = '';
  777. /// Implement one basic cache to avoid object name duplication
  778. /// and to speed up repeated queries for the same objects
  779. if (!isset($used_names)) {
  780. static $used_names = array();
  781. }
  782. /// If this exact object has been requested, return it
  783. if (array_key_exists($tablename.'-'.$fields.'-'.$suffix, $used_names)) {
  784. return $used_names[$tablename.'-'.$fields.'-'.$suffix];
  785. }
  786. /// Use standard naming. See http://docs.moodle.org/en/XMLDB_key_and_index_naming
  787. $tablearr = explode ('_', $tablename);
  788. foreach ($tablearr as $table) {
  789. $name .= substr(trim($table),0,4);
  790. }
  791. $name .= '_';
  792. $fieldsarr = explode (',', $fields);
  793. foreach ($fieldsarr as $field) {
  794. $name .= substr(trim($field),0,3);
  795. }
  796. /// Prepend the prefix
  797. $name = $this->prefix . $name;
  798. $name = substr(trim($name), 0, $this->names_max_length - 1 - strlen($suffix)); //Max names_max_length
  799. /// Add the suffix
  800. $namewithsuffix = $name;
  801. if ($suffix) {
  802. $namewithsuffix = $namewithsuffix . '_' . $suffix;
  803. }
  804. /// If the calculated name is in the cache, or if we detect it by introspecting the DB let's modify if
  805. if (in_array($namewithsuffix, $used_names) || $this->isNameInUse($namewithsuffix, $suffix, $tablename)) {
  806. $counter = 2;
  807. /// If have free space, we add 2
  808. if (strlen($namewithsuffix) < $this->names_max_length) {
  809. $newname = $name . $counter;
  810. /// Else replace the last char by 2
  811. } else {
  812. $newname = substr($name, 0, strlen($name)-1) . $counter;
  813. }
  814. $newnamewithsuffix = $newname;
  815. if ($suffix) {
  816. $newnamewithsuffix = $newnamewithsuffix . '_' . $suffix;
  817. }
  818. /// Now iterate until not used name is found, incrementing the counter
  819. while (in_array($newnamewithsuffix, $used_names) || $this->isNameInUse($newnamewithsuffix, $suffix, $tablename)) {
  820. $counter++;
  821. $newname = substr($name, 0, strlen($newname)-1) . $counter;
  822. $newnamewithsuffix = $newname;
  823. if ($suffix) {
  824. $newnamewithsuffix = $newnamewithsuffix . '_' . $suffix;
  825. }
  826. }
  827. $namewithsuffix = $newnamewithsuffix;
  828. }
  829. /// Add the name to the cache
  830. $used_names[$tablename.'-'.$fields.'-'.$suffix] = $namewithsuffix;
  831. /// Quote it if necessary (reserved words)
  832. $namewithsuffix = $this->getEncQuoted($namewithsuffix);
  833. return $namewithsuffix;
  834. }
  835. /**
  836. * Given any string (or one array), enclose it by the proper quotes
  837. * if it's a reserved word
  838. *//*
  839. function getEncQuoted($input) {
  840. if (is_array($input)) {
  841. foreach ($input as $key=>$content) {
  842. $input[$key] = $this->getEncQuoted($content);
  843. }
  844. return $input;
  845. } else {
  846. /// Always lowercase
  847. $input = strtolower($input);
  848. /// if reserved or quote_all, quote it
  849. if ($this->quote_all || in_array($input, $this->reserved_words)) {
  850. $input = $this->quote_string . $input . $this->quote_string;
  851. }
  852. return $input;
  853. }
  854. }
  855. /**
  856. * Given one XMLDB Statement, build the needed SQL insert sentences to execute it
  857. *//*
  858. function getExecuteInsertSQL($statement) {
  859. $results = array(); //Array where all the sentences will be stored
  860. if ($sentences = $statement->getSentences()) {
  861. foreach ($sentences as $sentence) {
  862. /// Get the list of fields
  863. $fields = $statement->getFieldsFromInsertSentence($sentence);
  864. /// Get the values of fields
  865. $values = $statement->getValuesFromInsertSentence($sentence);
  866. /// Look if we have some CONCAT value and transform it dinamically
  867. foreach($values as $key => $value) {
  868. /// Trim single quotes
  869. $value = trim($value,"'");
  870. if (stristr($value, 'CONCAT') !== false){
  871. /// Look for data between parentesis
  872. preg_match("/CONCAT\s*\((.*)\)$/is", trim($value), $matches);
  873. if (isset($matches[1])) {
  874. $part = $matches[1];
  875. /// Convert the comma separated string to an array
  876. $arr = XMLDBObject::comma2array($part);
  877. if ($arr) {
  878. $value = $this->getConcatSQL($arr);
  879. }
  880. }
  881. }
  882. /// Values to be sent to DB must be properly escaped
  883. $value = addslashes($value);
  884. /// Back trimmed quotes
  885. $value = "'" . $value . "'";
  886. /// Back to the array
  887. $values[$key] = $value;
  888. }
  889. /// Iterate over fields, escaping them if necessary
  890. foreach($fields as $key => $field) {
  891. $fields[$key] = $this->getEncQuoted($field);
  892. }
  893. /// Build the final SQL sentence and add it to the array of results
  894. $sql = 'INSERT INTO ' . $this->getEncQuoted($this->prefix . $statement->getTable()) .
  895. '(' . implode(', ', $fields) . ') ' .
  896. 'VALUES (' . implode(', ', $values) . ')';
  897. $results[] = $sql;
  898. }
  899. }
  900. return $results;
  901. }
  902. /**
  903. * Given one array of elements, build de proper CONCAT expresion, based
  904. * in the $concat_character setting. If such setting is empty, then
  905. * MySQL's CONCAT function will be used instead
  906. *//*
  907. function getConcatSQL($elements) {
  908. /// Replace double quoted elements by single quotes
  909. foreach($elements as $key => $element) {
  910. $element = trim($element);
  911. if (substr($element, 0, 1) == '"' &&
  912. substr($element, -1, 1) == '"') {
  913. $elements[$key] = "'" . trim($element, '"') . "'";
  914. }
  915. }
  916. /// Now call the standard sql_concat() DML function
  917. return call_user_func_array('sql_concat', $elements);
  918. }
  919. /**
  920. * Given one string (or one array), ends it with statement_end
  921. *//*
  922. function getEndedStatements ($input) {
  923. if (is_array($input)) {
  924. foreach ($input as $key=>$content) {
  925. $input[$key] = $this->getEndedStatements($content);
  926. }
  927. return $input;
  928. } else {
  929. $input = trim($input) . $this->statement_end;
  930. return $input;
  931. }
  932. }
  933. /**
  934. * Returns the name (string) of the sequence used in the table for the autonumeric pk
  935. * Only some DB have this implemented
  936. *//*
  937. function getSequenceFromDB($xmldb_table) {
  938. return false;
  939. }
  940. /**
  941. * Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg)
  942. * return if such name is currently in use (true) or no (false)
  943. * (MySQL requires the whole XMLDBTable object to be specified, so we add it always)
  944. * (invoked from getNameForObject()
  945. * Only some DB have this implemented
  946. *//*
  947. function isNameInUse($object_name, $type, $table_name) {
  948. return false; //For generators not implementing introspecion,
  949. //we always return with the name being free to be used
  950. }
  951. /// ALL THESE FUNCTION MUST BE CUSTOMISED BY ALL THE XMLDGenerator classes
  952. /**
  953. * Given one XMLDB Type, lenght and decimals, returns the DB proper SQL type
  954. */
  955. function getTypeSQL ($xmldb_type, $xmldb_length=null, $xmldb_decimals=null) {
  956. switch ($xmldb_type) {
  957. case XMLDB_TYPE_INTEGER:
  958. //Integer
  959. if (empty($xmldb_length)) {
  960. $xmldb_length = 10;
  961. }
  962. if ($xmldb_length > 9) {
  963. $dbtype = 'BIGINT';
  964. } else if ($xmldb_length > 4) {
  965. $dbt

Large files files are truncated, but you can click here to view the full file