Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** This class represent the base generator class where all the needed functions to generate proper SQL are defined.** The rest of classes will inherit, by default, the same logic.* Functions will be overridden as needed to generate correct SQL.** @package core_ddl* @copyright 1999 onwards Martin Dougiamas http://dougiamas.com* 2001-3001 Eloy Lafuente (stronk7) http://contiento.com* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/defined('MOODLE_INTERNAL') || die();/*** Abstract sql generator class, base for all db specific implementations.** @package core_ddl* @copyright 1999 onwards Martin Dougiamas http://dougiamas.com* 2001-3001 Eloy Lafuente (stronk7) http://contiento.com* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/abstract class sql_generator {// Please, avoid editing this defaults in this base class!// It could change the behaviour of the rest of generators// that, by default, inherit this configuration.// To change any of them, do it in extended classes instead./** @var string Used to quote names. */public $quote_string = '"';/** @var string To be automatically added at the end of each statement. */public $statement_end = ';';/** @var bool To decide if we want to quote all the names or only the reserved ones. */public $quote_all = false;/** @var bool To create all the integers as NUMBER(x) (also called DECIMAL, NUMERIC...). */public $integer_to_number = false;/** @var bool To create all the floats as NUMBER(x) (also called DECIMAL, NUMERIC...). */public $float_to_number = false;/** @var string Proper type for NUMBER(x) in this DB. */public $number_type = 'NUMERIC';/** @var string To define the default to set for NOT NULLs CHARs without default (null=do nothing).*/public $default_for_char = null;/** @var bool To specify if the generator must use some DEFAULT clause to drop defaults.*/public $drop_default_value_required = false;/** @var string The DEFAULT clause required to drop defaults.*/public $drop_default_value = '';/** @var bool To decide if the default clause of each field must go after the null clause.*/public $default_after_null = true;/** @var bool To force the generator if NULL clauses must be specified. It shouldn't be necessary.*/public $specify_nulls = false;/** @var string To force primary key names to one string (null=no force).*/public $primary_key_name = null;/** @var bool True if the generator builds primary keys.*/public $primary_keys = true;/** @var bool True if the generator builds unique keys.*/public $unique_keys = false;/** @var bool True if the generator builds foreign keys.*/public $foreign_keys = false;/** @var string Template to drop PKs. 'TABLENAME' and 'KEYNAME' will be replaced from this template.*/public $drop_primary_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME';/** @var string Template to drop UKs. 'TABLENAME' and 'KEYNAME' will be replaced from this template.*/public $drop_unique_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME';/** @var string Template to drop FKs. 'TABLENAME' and 'KEYNAME' will be replaced from this template.*/public $drop_foreign_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME';/** @var bool True if the generator needs to add extra code to generate the sequence fields.*/public $sequence_extra_code = true;/** @var string The particular name for inline sequences in this generator.*/public $sequence_name = 'auto_increment';/** @var string|bool Different name for small (4byte) sequences or false if same.*/public $sequence_name_small = false;/*** @var bool To avoid outputting the rest of the field specs, leaving only the name and the sequence_name returned.* @see getFieldSQL()*/public $sequence_only = false;/** @var bool True if the generator needs to add code for table comments.*/public $add_table_comments = true;/** @var bool True if the generator needs to add the after clause for fields.*/public $add_after_clause = false;/*** @var bool True if the generator needs to prepend the prefix to all the key/index/sequence/trigger/check names.* @see $prefix*/public $prefix_on_names = true;/** @var int Maximum length for key/index/sequence/trigger/check names (keep 30 for all!).*/public $names_max_length = 30;/** @var string Characters to be used as concatenation operator. If not defined, MySQL CONCAT function will be used.*/public $concat_character = '||';/** @var string SQL sentence to rename one table, both 'OLDNAME' and 'NEWNAME' keywords are dynamically replaced.*/public $rename_table_sql = 'ALTER TABLE OLDNAME RENAME TO NEWNAME';/** @var string SQL sentence to drop one table where the 'TABLENAME' keyword is dynamically replaced.*/public $drop_table_sql = 'DROP TABLE TABLENAME';/** @var string The SQL template to alter columns where the 'TABLENAME' and 'COLUMNSPECS' keywords are dynamically replaced.*/public $alter_column_sql = 'ALTER TABLE TABLENAME ALTER COLUMN COLUMNSPECS';/** @var bool The generator will skip the default clause on alter columns.*/public $alter_column_skip_default = false;/** @var bool The generator will skip the type clause on alter columns.*/public $alter_column_skip_type = false;/** @var bool The generator will skip the null/notnull clause on alter columns.*/public $alter_column_skip_notnull = false;/** @var string SQL sentence to rename one column where 'TABLENAME', 'OLDFIELDNAME' and 'NEWFIELDNAME' keywords are dynamically replaced.*/public $rename_column_sql = 'ALTER TABLE TABLENAME RENAME COLUMN OLDFIELDNAME TO NEWFIELDNAME';/** @var string SQL sentence to drop one index where 'TABLENAME', 'INDEXNAME' keywords are dynamically replaced.*/public $drop_index_sql = 'DROP INDEX INDEXNAME';/** @var string SQL sentence to rename one index where 'TABLENAME', 'OLDINDEXNAME' and 'NEWINDEXNAME' are dynamically replaced.*/public $rename_index_sql = 'ALTER INDEX OLDINDEXNAME RENAME TO NEWINDEXNAME';/** @var string SQL sentence to rename one key 'TABLENAME', 'OLDKEYNAME' and 'NEWKEYNAME' are dynamically replaced.*/public $rename_key_sql = 'ALTER TABLE TABLENAME CONSTRAINT OLDKEYNAME RENAME TO NEWKEYNAME';/** @var string The prefix to be used for all the DB objects.*/public $prefix;/** @var array List of reserved words (in order to quote them properly).*/public $reserved_words;/** @var moodle_database The moodle_database instance.*/public $mdb;/** @var Control existing temptables.*/protected $temptables;/*** Creates a new sql_generator.* @param moodle_database $mdb The moodle_database object instance.* @param moodle_temptables $temptables The optional moodle_temptables instance, null by default.*/public function __construct($mdb, $temptables = null) {$this->prefix = $mdb->get_prefix();$this->reserved_words = $this->getReservedWords();$this->mdb = $mdb; // this creates circular reference - the other link must be unset when closing db$this->temptables = $temptables;}/*** Releases all resources.*/public function dispose() {$this->mdb = null;}/*** Given one string (or one array), ends it with $statement_end .** @see $statement_end** @param array|string $input SQL statement(s).* @return array|string*/public function getEndedStatements($input) {if (is_array($input)) {foreach ($input as $key=>$content) {$input[$key] = $this->getEndedStatements($content);}return $input;} else {$input = trim($input).$this->statement_end;return $input;}}/*** Given one xmldb_table, checks if it exists in DB (true/false).** @param mixed $table The table to be searched (string name or xmldb_table instance).* @return boolean true/false*/public function table_exists($table) {if (is_string($table)) {$tablename = $table;} else {// Calculate the name of the table$tablename = $table->getName();}if ($this->temptables->is_temptable($tablename)) {return true;}// Get all tables in moodle database.$tables = $this->mdb->get_tables();return isset($tables[$tablename]);}/*** This function will return the SQL code needed to create db tables and statements.* @see xmldb_structure** @param xmldb_structure $xmldb_structure An xmldb_structure instance.* @return array*/public function getCreateStructureSQL($xmldb_structure) {$results = array();if ($tables = $xmldb_structure->getTables()) {foreach ($tables as $table) {$results = array_merge($results, $this->getCreateTableSQL($table));}}return $results;}/*** Given one xmldb_table, this returns it's correct name, depending of all the parameterization.* eg: This appends $prefix to the table name.** @see $prefix** @param xmldb_table $xmldb_table The table whose name we want.* @param boolean $quoted To specify if the name must be quoted (if reserved word, only!).* @return string The correct name of the table.*/public function getTableName(xmldb_table $xmldb_table, $quoted=true) {// Get the name$tablename = $this->prefix.$xmldb_table->getName();// Apply quotes optionallyif ($quoted) {$tablename = $this->getEncQuoted($tablename);}return $tablename;}/*** Given one correct xmldb_table, returns the SQL statements* to create it (inside one array).** @param xmldb_table $xmldb_table An xmldb_table instance.* @return array An array of SQL statements, starting with the table creation SQL followed* by any of its comments, indexes and sequence creation SQL statements.*/public function getCreateTableSQL($xmldb_table) {if ($error = $xmldb_table->validateDefinition()) {throw new coding_exception($error);}$results = array(); //Array where all the sentences will be stored// Table header$table = 'CREATE TABLE ' . $this->getTableName($xmldb_table) . ' (';if (!$xmldb_fields = $xmldb_table->getFields()) {return $results;}$sequencefield = null;// Add the fields, separated by commasforeach ($xmldb_fields as $xmldb_field) {if ($xmldb_field->getSequence()) {$sequencefield = $xmldb_field->getName();}$table .= "\n " . $this->getFieldSQL($xmldb_table, $xmldb_field);$table .= ',';}// Add the keys, separated by commasif ($xmldb_keys = $xmldb_table->getKeys()) {foreach ($xmldb_keys as $xmldb_key) {if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {$table .= "\nCONSTRAINT " . $keytext . ',';}// If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE tooif ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE) {//Duplicate the key$xmldb_key->setType(XMLDB_KEY_UNIQUE);if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {$table .= "\nCONSTRAINT " . $keytext . ',';}}// make sure sequence field is uniqueif ($sequencefield and $xmldb_key->getType() == XMLDB_KEY_PRIMARY) {$fields = $xmldb_key->getFields();$field = reset($fields);if ($sequencefield === $field) {$sequencefield = null;}}}}// throw error if sequence field does not have unique key definedif ($sequencefield) {throw new ddl_exception('ddsequenceerror', $xmldb_table->getName());}// Table footer, trim the latest comma$table = trim($table,',');$table .= "\n)";// Add the CREATE TABLE to results$results[] = $table;// Add comments if specified and it existsif ($this->add_table_comments && $xmldb_table->getComment()) {$comment = $this->getCommentSQL($xmldb_table);// Add the COMMENT to results$results = array_merge($results, $comment);}// Add the indexes (each one, one statement)if ($xmldb_indexes = $xmldb_table->getIndexes()) {foreach ($xmldb_indexes as $xmldb_index) {//tables do not exist yet, which means indexed can not exist yetif ($indextext = $this->getCreateIndexSQL($xmldb_table, $xmldb_index)) {$results = array_merge($results, $indextext);}}}// Also, add the indexes needed from keys, based on configuration (each one, one statement)if ($xmldb_keys = $xmldb_table->getKeys()) {foreach ($xmldb_keys as $xmldb_key) {// If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated// automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)if (!$this->getKeySQL($xmldb_table, $xmldb_key) || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {// Create the interim index$index = new xmldb_index('anyname');$index->setFields($xmldb_key->getFields());//tables do not exist yet, which means indexed can not exist yet$createindex = false; //By defaultswitch ($xmldb_key->getType()) {case XMLDB_KEY_UNIQUE:case XMLDB_KEY_FOREIGN_UNIQUE:$index->setUnique(true);$createindex = true;break;case XMLDB_KEY_FOREIGN:$index->setUnique(false);$createindex = true;break;}if ($createindex) {if ($indextext = $this->getCreateIndexSQL($xmldb_table, $index)) {// Add the INDEX to the array$results = array_merge($results, $indextext);}}}}}// Add sequence extra code if neededif ($this->sequence_extra_code) {// Iterate over fields looking for sequencesforeach ($xmldb_fields as $xmldb_field) {if ($xmldb_field->getSequence()) {// returns an array of statements needed to create one sequence$sequence_sentences = $this->getCreateSequenceSQL($xmldb_table, $xmldb_field);// Add the SEQUENCE to the array$results = array_merge($results, $sequence_sentences);}}}return $results;}/*** Given one correct xmldb_index, returns the SQL statements* needed to create it (in array).** @param xmldb_table $xmldb_table The xmldb_table instance to create the index on.* @param xmldb_index $xmldb_index The xmldb_index to create.* @return array An array of SQL statements to create the index.* @throws coding_exception Thrown if the xmldb_index does not validate with the xmldb_table.*/public function getCreateIndexSQL($xmldb_table, $xmldb_index) {if ($error = $xmldb_index->validateDefinition($xmldb_table)) {throw new coding_exception($error);}$unique = '';$suffix = 'ix';if ($xmldb_index->getUnique()) {$unique = ' UNIQUE';$suffix = 'uix';}$index = 'CREATE' . $unique . ' INDEX ';$index .= $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_index->getFields()), $suffix);$index .= ' ON ' . $this->getTableName($xmldb_table);$index .= ' (' . implode(', ', $this->getEncQuoted($xmldb_index->getFields())) . ')';return array($index);}/*** Given one correct xmldb_field, returns the complete SQL line to create it.** @param xmldb_table $xmldb_table The table related to $xmldb_field.* @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.* @param string $skip_type_clause The type clause on alter columns, NULL by default.* @param string $skip_default_clause The default clause on alter columns, NULL by default.* @param string $skip_notnull_clause The null/notnull clause on alter columns, NULL by default.* @param string $specify_nulls_clause To force a specific null clause, NULL by default.* @param bool $specify_field_name Flag to specify fieldname in return.* @return string The field generating SQL statement.* @throws coding_exception Thrown when xmldb_field doesn't validate with the xmldb_table.*/public function getFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL, $specify_nulls_clause = NULL, $specify_field_name = true) {if ($error = $xmldb_field->validateDefinition($xmldb_table)) {throw new coding_exception($error);}$skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;$skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;$skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;$specify_nulls_clause = is_null($specify_nulls_clause) ? $this->specify_nulls : $specify_nulls_clause;// First of all, convert integers to numbers if definedif ($this->integer_to_number) {if ($xmldb_field->getType() == XMLDB_TYPE_INTEGER) {$xmldb_field->setType(XMLDB_TYPE_NUMBER);}}// Same for floatsif ($this->float_to_number) {if ($xmldb_field->getType() == XMLDB_TYPE_FLOAT) {$xmldb_field->setType(XMLDB_TYPE_NUMBER);}}$field = ''; // Let's accumulate the whole expression based on params and settings// The nameif ($specify_field_name) {$field .= $this->getEncQuoted($xmldb_field->getName());}// The type and length only if we don't want to skip itif (!$skip_type_clause) {// The type and length$field .= ' ' . $this->getTypeSQL($xmldb_field->getType(), $xmldb_field->getLength(), $xmldb_field->getDecimals());}// note: unsigned is not supported any more since moodle 2.3, all numbers are signed// Calculate the not null clause$notnull = '';// Only if we don't want to skip itif (!$skip_notnull_clause) {if ($xmldb_field->getNotNull()) {$notnull = ' NOT NULL';} else {if ($specify_nulls_clause) {$notnull = ' NULL';}}}// Calculate the default clause$default_clause = '';if (!$skip_default_clause) { //Only if we don't want to skip it$default_clause = $this->getDefaultClause($xmldb_field);}// Based on default_after_null, set both clauses properlyif ($this->default_after_null) {$field .= $notnull . $default_clause;} else {$field .= $default_clause . $notnull;}// The sequenceif ($xmldb_field->getSequence()) {if($xmldb_field->getLength()<=9 && $this->sequence_name_small) {$sequencename=$this->sequence_name_small;} else {$sequencename=$this->sequence_name;}$field .= ' ' . $sequencename;if ($this->sequence_only) {// We only want the field name and sequence name to be printed// so, calculate it and return$sql = $this->getEncQuoted($xmldb_field->getName()) . ' ' . $sequencename;return $sql;}}return $field;}/*** Given one correct xmldb_key, returns its specs.** @param xmldb_table $xmldb_table The table related to $xmldb_key.* @param xmldb_key $xmldb_key The xmldb_key's specifications requested.* @return string SQL statement about the xmldb_key.*/public function getKeySQL($xmldb_table, $xmldb_key) {$key = '';switch ($xmldb_key->getType()) {case XMLDB_KEY_PRIMARY:if ($this->primary_keys) {if ($this->primary_key_name !== null) {$key = $this->getEncQuoted($this->primary_key_name);} else {$key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'pk');}$key .= ' PRIMARY KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';}break;case XMLDB_KEY_UNIQUE:if ($this->unique_keys) {$key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'uk');$key .= ' UNIQUE (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';}break;case XMLDB_KEY_FOREIGN:case XMLDB_KEY_FOREIGN_UNIQUE:if ($this->foreign_keys) {$key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'fk');$key .= ' FOREIGN KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';$key .= ' REFERENCES ' . $this->getEncQuoted($this->prefix . $xmldb_key->getRefTable());$key .= ' (' . implode(', ', $this->getEncQuoted($xmldb_key->getRefFields())) . ')';}break;}return $key;}/*** Give one xmldb_field, returns the correct "default value" for the current configuration** @param xmldb_field $xmldb_field The field.* @return The default value of the field.*/public function getDefaultValue($xmldb_field) {$default = null;if ($xmldb_field->getDefault() !== NULL) {if ($xmldb_field->getType() == XMLDB_TYPE_CHAR ||$xmldb_field->getType() == XMLDB_TYPE_TEXT) {if ($xmldb_field->getDefault() === '') { // If passing empty default, use the $default_for_char one instead$default = "'" . $this->default_for_char . "'";} else {$default = "'" . $this->addslashes($xmldb_field->getDefault()) . "'";}} else {$default = $xmldb_field->getDefault();}} else {// We force default '' for not null char columns without proper default// some day this should be out!if ($this->default_for_char !== NULL &&$xmldb_field->getType() == XMLDB_TYPE_CHAR &&$xmldb_field->getNotNull()) {$default = "'" . $this->default_for_char . "'";} else {// If the DB requires to explicity define some clause to drop one default, do it here// never applying defaults to TEXT and BINARY fieldsif ($this->drop_default_value_required &&$xmldb_field->getType() != XMLDB_TYPE_TEXT &&$xmldb_field->getType() != XMLDB_TYPE_BINARY && !$xmldb_field->getNotNull()) {$default = $this->drop_default_value;}}}return $default;}/*** Given one xmldb_field, returns the correct "default clause" for the current configuration.** @param xmldb_field $xmldb_field The xmldb_field.* @return The SQL clause for generating the default value as in $xmldb_field.*/public function getDefaultClause($xmldb_field) {$defaultvalue = $this->getDefaultValue ($xmldb_field);if ($defaultvalue !== null) {return ' DEFAULT ' . $defaultvalue;} else {return null;}}/*** Given one correct xmldb_table and the new name, returns the SQL statements* to rename it (inside one array).** @param xmldb_table $xmldb_table The table to rename.* @param string $newname The new name to rename the table to.* @return array SQL statement(s) to rename the table.*/public function getRenameTableSQL($xmldb_table, $newname) {$results = array(); //Array where all the sentences will be stored$newt = new xmldb_table($newname); //Temporal table for name calculations$rename = str_replace('OLDNAME', $this->getTableName($xmldb_table), $this->rename_table_sql);$rename = str_replace('NEWNAME', $this->getTableName($newt), $rename);$results[] = $rename;// Call to getRenameTableExtraSQL() override if needed$extra_sentences = $this->getRenameTableExtraSQL($xmldb_table, $newname);$results = array_merge($results, $extra_sentences);return $results;}/*** Given one correct xmldb_table, returns the SQL statements* to drop it (inside one array). Works also for temporary tables.** @param xmldb_table $xmldb_table The table to drop.* @return array SQL statement(s) for dropping the specified table.*/public function getDropTableSQL($xmldb_table) {$results = array(); //Array where all the sentences will be stored$drop = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_table_sql);$results[] = $drop;// call to getDropTableExtraSQL(), override if needed$extra_sentences = $this->getDropTableExtraSQL($xmldb_table);$results = array_merge($results, $extra_sentences);return $results;}/*** Performs any clean up that needs to be done after a table is dropped.** @param xmldb_table $table*/public function cleanup_after_drop(xmldb_table $table): void {if ($this->temptables->is_temptable($table->getName())) {$this->temptables->delete_temptable($table->getName());}}/*** Given one xmldb_table and one xmldb_field, return the SQL statements needed to add the field to the table.** @param xmldb_table $xmldb_table The table related to $xmldb_field.* @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.* @param string $skip_type_clause The type clause on alter columns, NULL by default.* @param string $skip_default_clause The default clause on alter columns, NULL by default.* @param string $skip_notnull_clause The null/notnull clause on alter columns, NULL by default.* @return array The SQL statement for adding a field to the table.*/public function getAddFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {$skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;$skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;$skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;$results = array();// Get the quoted name of the table and field$tablename = $this->getTableName($xmldb_table);// Build the standard alter table add$sql = $this->getFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause,$skip_default_clause,$skip_notnull_clause);$altertable = 'ALTER TABLE ' . $tablename . ' ADD ' . $sql;// Add the after clause if necessaryif ($this->add_after_clause && $xmldb_field->getPrevious()) {$altertable .= ' AFTER ' . $this->getEncQuoted($xmldb_field->getPrevious());}$results[] = $altertable;return $results;}/*** Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop the field from the table.** @param xmldb_table $xmldb_table The table related to $xmldb_field.* @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.* @return array The SQL statement for dropping a field from the table.*/public function getDropFieldSQL($xmldb_table, $xmldb_field) {$results = array();// Get the quoted name of the table and field$tablename = $this->getTableName($xmldb_table);$fieldname = $this->getEncQuoted($xmldb_field->getName());// Build the standard alter table drop$results[] = 'ALTER TABLE ' . $tablename . ' DROP COLUMN ' . $fieldname;return $results;}/*** Given one xmldb_table and one xmldb_field, return the SQL statements needed to alter the field in the table.** @param xmldb_table $xmldb_table The table related to $xmldb_field.* @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.* @param string $skip_type_clause The type clause on alter columns, NULL by default.* @param string $skip_default_clause The default clause on alter columns, NULL by default.* @param string $skip_notnull_clause The null/notnull clause on alter columns, NULL by default.* @return array The field altering SQL statement.*/public function getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {$skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;$skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;$skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;$results = array();// Get the quoted name of the table and field$tablename = $this->getTableName($xmldb_table);$fieldname = $this->getEncQuoted($xmldb_field->getName());// Build de alter sentence using the alter_column_sql template$alter = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->alter_column_sql);$colspec = $this->getFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause,$skip_default_clause,$skip_notnull_clause,true);$alter = str_replace('COLUMNSPECS', $colspec, $alter);// Add the after clause if necessaryif ($this->add_after_clause && $xmldb_field->getPrevious()) {$alter .= ' after ' . $this->getEncQuoted($xmldb_field->getPrevious());}// Build the standard alter table modify$results[] = $alter;return $results;}/*** Given one xmldb_table and one xmldb_field, return the SQL statements needed to modify the default of the field in the table.** @param xmldb_table $xmldb_table The table related to $xmldb_field.* @param xmldb_field $xmldb_field The instance of xmldb_field to get the modified default value from.* @return array The SQL statement for modifying the default value.*/public function getModifyDefaultSQL($xmldb_table, $xmldb_field) {$results = array();// Get the quoted name of the table and field$tablename = $this->getTableName($xmldb_table);$fieldname = $this->getEncQuoted($xmldb_field->getName());// Decide if we are going to create/modify or to drop the defaultif ($xmldb_field->getDefault() === null) {$results = $this->getDropDefaultSQL($xmldb_table, $xmldb_field); //Drop} else {$results = $this->getCreateDefaultSQL($xmldb_table, $xmldb_field); //Create/modify}return $results;}/*** Given one correct xmldb_field and the new name, returns the SQL statements* to rename it (inside one array).** @param xmldb_table $xmldb_table The table related to $xmldb_field.* @param xmldb_field $xmldb_field The instance of xmldb_field to get the renamed field from.* @param string $newname The new name to rename the field to.* @return array The SQL statements for renaming the field.*/public function getRenameFieldSQL($xmldb_table, $xmldb_field, $newname) {$results = array(); //Array where all the sentences will be stored// Although this is checked in database_manager::rename_field() - double check// that we aren't trying to rename one "id" field. Although it could be// implemented (if adding the necessary code to rename sequences, defaults,// triggers... and so on under each getRenameFieldExtraSQL() function, it's// better to forbid it, mainly because this field is the default PK and// in the future, a lot of FKs can be pointing here. So, this field, more// or less, must be considered immutable!if ($xmldb_field->getName() == 'id') {return array();}$rename = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_column_sql);$rename = str_replace('OLDFIELDNAME', $this->getEncQuoted($xmldb_field->getName()), $rename);$rename = str_replace('NEWFIELDNAME', $this->getEncQuoted($newname), $rename);$results[] = $rename;// Call to getRenameFieldExtraSQL(), override if needed$extra_sentences = $this->getRenameFieldExtraSQL($xmldb_table, $xmldb_field, $newname);$results = array_merge($results, $extra_sentences);return $results;}/*** Given one xmldb_table and one xmldb_key, return the SQL statements needed to add the key to the table* note that undelying indexes will be added as parametrised by $xxxx_keys and $xxxx_index parameters.** @param xmldb_table $xmldb_table The table related to $xmldb_key.* @param xmldb_key $xmldb_key The xmldb_key to add.* @return array SQL statement to add the xmldb_key.*/public function getAddKeySQL($xmldb_table, $xmldb_key) {$results = array();// Just use the CreateKeySQL functionif ($keyclause = $this->getKeySQL($xmldb_table, $xmldb_key)) {$key = 'ALTER TABLE ' . $this->getTableName($xmldb_table) .' ADD CONSTRAINT ' . $keyclause;$results[] = $key;}// If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated// automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)if (!$keyclause || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {// Only if they don't existif ($xmldb_key->getType() == XMLDB_KEY_FOREIGN) { //Calculate type of index based on type ok key$indextype = XMLDB_INDEX_NOTUNIQUE;} else {$indextype = XMLDB_INDEX_UNIQUE;}$xmldb_index = new xmldb_index('anyname', $indextype, $xmldb_key->getFields());if (!$this->mdb->get_manager()->index_exists($xmldb_table, $xmldb_index)) {$results = array_merge($results, $this->getAddIndexSQL($xmldb_table, $xmldb_index));}}// If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE tooif ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {//Duplicate the key$xmldb_key->setType(XMLDB_KEY_UNIQUE);$results = array_merge($results, $this->getAddKeySQL($xmldb_table, $xmldb_key));}// Return resultsreturn $results;}/*** Given one xmldb_table and one xmldb_index, return the SQL statements needed to drop the index from the table.** @param xmldb_table $xmldb_table The table related to $xmldb_key.* @param xmldb_key $xmldb_key The xmldb_key to drop.* @return array SQL statement to drop the xmldb_key.*/public function getDropKeySQL($xmldb_table, $xmldb_key) {$results = array();// Get the key name (note that this doesn't introspect DB, so could cause some problems sometimes!)// TODO: We'll need to overwrite the whole getDropKeySQL() method inside each DB to do the proper queries// against the dictionary or require ADOdb to support it or change the find_key_name() method to// perform DB introspection directly. But, for now, as we aren't going to enable referential integrity// it won't be a problem at all$dbkeyname = $this->mdb->get_manager()->find_key_name($xmldb_table, $xmldb_key);// Only if such type of key generation is enabled$dropkey = false;switch ($xmldb_key->getType()) {case XMLDB_KEY_PRIMARY:if ($this->primary_keys) {$template = $this->drop_primary_key;$dropkey = true;}break;case XMLDB_KEY_UNIQUE:if ($this->unique_keys) {$template = $this->drop_unique_key;$dropkey = true;}break;case XMLDB_KEY_FOREIGN_UNIQUE:case XMLDB_KEY_FOREIGN:if ($this->foreign_keys) {$template = $this->drop_foreign_key;$dropkey = true;}break;}// If we have decided to drop the key, let's do itif ($dropkey) {// Replace TABLENAME, CONSTRAINTTYPE and KEYNAME as needed$dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $template);$dropsql = str_replace('KEYNAME', $dbkeyname, $dropsql);$results[] = $dropsql;}// If we aren't dropping the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated// automatically by the RDBMS) drop the underlying (created by us) index (if exists)if (!$dropkey || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {// Only if they exist$xmldb_index = new xmldb_index('anyname', XMLDB_INDEX_UNIQUE, $xmldb_key->getFields());if ($this->mdb->get_manager()->index_exists($xmldb_table, $xmldb_index)) {$results = array_merge($results, $this->getDropIndexSQL($xmldb_table, $xmldb_index));}}// If the key is XMLDB_KEY_FOREIGN_UNIQUE, drop the UNIQUE tooif ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {//Duplicate the key$xmldb_key->setType(XMLDB_KEY_UNIQUE);$results = array_merge($results, $this->getDropKeySQL($xmldb_table, $xmldb_key));}// Return resultsreturn $results;}/*** Given one xmldb_table and one xmldb_key, return the SQL statements needed to rename the key in the table* Experimental! Shouldn't be used at all!** @param xmldb_table $xmldb_table The table related to $xmldb_key.* @param xmldb_key $xmldb_key The xmldb_key to rename.* @param string $newname The xmldb_key's new name.* @return array SQL statement to rename the xmldb_key.*/public function getRenameKeySQL($xmldb_table, $xmldb_key, $newname) {$results = array();// Get the real key name$dbkeyname = $this->mdb->get_manager()->find_key_name($xmldb_table, $xmldb_key);// Check we are really generating this type of keysif (($xmldb_key->getType() == XMLDB_KEY_PRIMARY && !$this->primary_keys) ||($xmldb_key->getType() == XMLDB_KEY_UNIQUE && !$this->unique_keys) ||($xmldb_key->getType() == XMLDB_KEY_FOREIGN && !$this->foreign_keys) ||($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && !$this->unique_keys && !$this->foreign_keys)) {// We aren't generating this type of keys, delegate to child indexes$xmldb_index = new xmldb_index($xmldb_key->getName());$xmldb_index->setFields($xmldb_key->getFields());return $this->getRenameIndexSQL($xmldb_table, $xmldb_index, $newname);}// Arrived here so we are working with keys, lets rename them// Replace TABLENAME and KEYNAME as needed$renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_key_sql);$renamesql = str_replace('OLDKEYNAME', $dbkeyname, $renamesql);$renamesql = str_replace('NEWKEYNAME', $newname, $renamesql);// Some DB doesn't support key renaming so this can be emptyif ($renamesql) {$results[] = $renamesql;}return $results;}/*** Given one xmldb_table and one xmldb_index, return the SQL statements needed to add the index to the table.** @param xmldb_table $xmldb_table The xmldb_table instance to add the index on.* @param xmldb_index $xmldb_index The xmldb_index to add.* @return array An array of SQL statements to add the index.*/public function getAddIndexSQL($xmldb_table, $xmldb_index) {// Just use the CreateIndexSQL functionreturn $this->getCreateIndexSQL($xmldb_table, $xmldb_index);}/*** Given one xmldb_table and one xmldb_index, return the SQL statements needed to drop the index from the table.** @param xmldb_table $xmldb_table The xmldb_table instance to drop the index on.* @param xmldb_index $xmldb_index The xmldb_index to drop.* @return array An array of SQL statements to drop the index.*/public function getDropIndexSQL($xmldb_table, $xmldb_index) {$results = array();// Get the real index name$dbindexnames = $this->mdb->get_manager()->find_index_name($xmldb_table, $xmldb_index, true);// Replace TABLENAME and INDEXNAME as neededif ($dbindexnames) {foreach ($dbindexnames as $dbindexname) {$dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_index_sql);$dropsql = str_replace('INDEXNAME', $this->getEncQuoted($dbindexname), $dropsql);$results[] = $dropsql;}}return $results;}/*** Given one xmldb_table and one xmldb_index, return the SQL statements needed to rename the index in the table* Experimental! Shouldn't be used at all!** @param xmldb_table $xmldb_table The xmldb_table instance to rename the index on.* @param xmldb_index $xmldb_index The xmldb_index to rename.* @param string $newname The xmldb_index's new name.* @return array An array of SQL statements to rename the index.*/function getRenameIndexSQL($xmldb_table, $xmldb_index, $newname) {// Some DB doesn't support index renaming (MySQL) so this can be emptyif (empty($this->rename_index_sql)) {return array();}// Get the real index name$dbindexname = $this->mdb->get_manager()->find_index_name($xmldb_table, $xmldb_index);// Replace TABLENAME and INDEXNAME as needed$renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_index_sql);$renamesql = str_replace('OLDINDEXNAME', $this->getEncQuoted($dbindexname), $renamesql);$renamesql = str_replace('NEWINDEXNAME', $this->getEncQuoted($newname), $renamesql);return array($renamesql);}/*** Given three strings (table name, list of fields (comma separated) and suffix),* create the proper object name quoting it if necessary.** IMPORTANT: This function must be used to CALCULATE NAMES of objects TO BE CREATED,* NEVER TO GUESS NAMES of EXISTING objects!!!** @param string $tablename The table name.* @param string $fields A list of comma separated fields.* @param string $suffix A suffix for the object name.* @return string Object's name.*/public function getNameForObject($tablename, $fields, $suffix='') {$name = '';// Implement one basic cache to avoid object name duplication// along all the request life, but never to return cached results// We need this because sql statements are created before executing// them, hence names doesn't exist "physically" yet in DB, so we need// to known which ones have been used.// We track all the keys used, and the previous counters to make subsequent creates faster.// This may happen a lot with things like bulk backups or restores.static $usednames = array();static $previouscounters = array();// Use standard naming. See http://docs.moodle.org/en/XMLDB_key_and_index_naming$tablearr = explode ('_', $tablename);foreach ($tablearr as $table) {$name .= substr(trim($table),0,4);}$name .= '_';$fieldsarr = explode (',', $fields);foreach ($fieldsarr as $field) {$name .= substr(trim($field),0,3);}// Prepend the prefix$name = trim($this->prefix . $name);// Make sure name does not exceed the maximum name length and add suffix.$maxlengthwithoutsuffix = $this->names_max_length - strlen($suffix) - ($suffix ? 1 : 0);$namewithsuffix = substr($name, 0, $maxlengthwithoutsuffix) . ($suffix ? ('_' . $suffix) : '');if (isset($previouscounters[$name])) {// If we have a counter stored, we will need to modify the key to the next counter location.$counter = $previouscounters[$name] + 1;$namewithsuffix = substr($name, 0, $maxlengthwithoutsuffix - strlen($counter)) .$counter . ($suffix ? ('_' . $suffix) : '');} else {$counter = 1;}// If the calculated name is in the cache, or if we detect it by introspecting the DB let's modify it.while (isset($usednames[$namewithsuffix]) || $this->isNameInUse($namewithsuffix, $suffix, $tablename)) {// Now iterate until not used name is found, incrementing the counter$counter++;$namewithsuffix = substr($name, 0, $maxlengthwithoutsuffix - strlen($counter)) .$counter . ($suffix ? ('_' . $suffix) : '');}// Add the name to the cache. Using key look with isset because it is much faster than in_array.$usednames[$namewithsuffix] = true;$previouscounters[$name] = $counter;// Quote it if necessary (reserved words)$namewithsuffix = $this->getEncQuoted($namewithsuffix);return $namewithsuffix;}/*** Given any string (or one array), enclose it by the proper quotes* if it's a reserved word** @param string|array $input String to quote.* @return string|array Quoted string.*/public function getEncQuoted($input) {if (is_array($input)) {foreach ($input as $key=>$content) {$input[$key] = $this->getEncQuoted($content);}return $input;} else {// Always lowercase$input = strtolower($input);// if reserved or quote_all or has hyphens, quote itif ($this->quote_all || in_array($input, $this->reserved_words) || strpos($input, '-') !== false) {$input = $this->quote_string . $input . $this->quote_string;}return $input;}}/*** Given one XMLDB Statement, build the needed SQL insert sentences to execute it.** @param mixed $statement SQL statement.* @return array Array of sentences in the SQL statement.*/function getExecuteInsertSQL($statement) {$results = array(); //Array where all the sentences will be storedif ($sentences = $statement->getSentences()) {foreach ($sentences as $sentence) {// Get the list of fields$fields = $statement->getFieldsFromInsertSentence($sentence);// Get the values of fields$values = $statement->getValuesFromInsertSentence($sentence);// Look if we have some CONCAT value and transform it dynamicallyforeach($values as $key => $value) {// Trim single quotes$value = trim($value,"'");if (stristr($value, 'CONCAT') !== false){// Look for data between parenthesispreg_match("/CONCAT\s*\((.*)\)$/is", trim($value), $matches);if (isset($matches[1])) {$part = $matches[1];// Convert the comma separated string to an array$arr = xmldb_object::comma2array($part);if ($arr) {$value = $this->getConcatSQL($arr);}}}// Values to be sent to DB must be properly escaped$value = $this->addslashes($value);// Back trimmed quotes$value = "'" . $value . "'";// Back to the array$values[$key] = $value;}// Iterate over fields, escaping them if necessaryforeach($fields as $key => $field) {$fields[$key] = $this->getEncQuoted($field);}// Build the final SQL sentence and add it to the array of results$sql = 'INSERT INTO ' . $this->getEncQuoted($this->prefix . $statement->getTable()) .'(' . implode(', ', $fields) . ') ' .'VALUES (' . implode(', ', $values) . ')';$results[] = $sql;}}return $results;}/*** Given one array of elements, build the proper CONCAT expression, based* in the $concat_character setting. If such setting is empty, then* MySQL's CONCAT function will be used instead.** @param array $elements An array of elements to concatenate.* @return mixed Returns the result of moodle_database::sql_concat() or false.* @uses moodle_database::sql_concat()* @uses call_user_func_array()*/public function getConcatSQL($elements) {// Replace double quoted elements by single quotesforeach($elements as $key => $element) {$element = trim($element);if (substr($element, 0, 1) == '"' &&substr($element, -1, 1) == '"') {$elements[$key] = "'" . trim($element, '"') . "'";}}// Now call the standard $DB->sql_concat() DML functionreturn call_user_func_array(array($this->mdb, 'sql_concat'), array_values($elements));}/*** Returns the name (string) of the sequence used in the table for the autonumeric pk* Only some DB have this implemented.** @param xmldb_table $xmldb_table The xmldb_table instance.* @return bool Returns the sequence from the DB or false.*/public function getSequenceFromDB($xmldb_table) {return false;}/*** Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg).** (MySQL requires the whole xmldb_table object to be specified, so we add it always)** This is invoked from getNameForObject().* Only some DB have this implemented.** @param string $object_name The object's name to check for.* @param string $type The object's type (pk, uk, fk, ck, ix, uix, seq, trg).* @param string $table_name The table's name to check in* @return bool If such name is currently in use (true) or no (false)*/public function isNameInUse($object_name, $type, $table_name) {return false; //For generators not implementing introspection,//we always return with the name being free to be used}// ====== FOLLOWING FUNCTION MUST BE CUSTOMISED BY ALL THE XMLDGenerator classes ========/*** Reset a sequence to the id field of a table.** @param xmldb_table|string $table name of table or the table object.* @return array of sql statements*/abstract public function getResetSequenceSQL($table);/*** Given one correct xmldb_table, returns the SQL statements* to create temporary table (inside one array).** @param xmldb_table $xmldb_table The xmldb_table object instance.* @return array of sql statements*/abstract public function getCreateTempTableSQL($xmldb_table);/*** Given one XMLDB Type, length and decimals, returns the DB proper SQL type.** @param int $xmldb_type The xmldb_type defined constant. XMLDB_TYPE_INTEGER and other XMLDB_TYPE_* constants.* @param int $xmldb_length The length of that data type.* @param int $xmldb_decimals The decimal places of precision of the data type.* @return string The DB defined data type.*/abstract public function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null);/*** Returns the code (array of statements) needed to execute extra statements on field rename.** @param xmldb_table $xmldb_table The xmldb_table object instance.* @param xmldb_field $xmldb_field The xmldb_field object instance.* @return array Array of extra SQL statements to run with a field being renamed.*/public function getRenameFieldExtraSQL($xmldb_table, $xmldb_field) {return array();}/*** Returns the code (array of statements) needed* to create one sequence for the xmldb_table and xmldb_field passed in.** @param xmldb_table $xmldb_table The xmldb_table object instance.* @param xmldb_field $xmldb_field The xmldb_field object instance.* @return array Array of SQL statements to create the sequence.*/public function getCreateSequenceSQL($xmldb_table, $xmldb_field) {return array();}/*** Returns the code (array of statements) needed to add one comment to the table.** @param xmldb_table $xmldb_table The xmldb_table object instance.* @return array Array of SQL statements to add one comment to the table.*/abstract public function getCommentSQL($xmldb_table);/*** Returns the code (array of statements) needed to execute extra statements on table rename.** @param xmldb_table $xmldb_table The xmldb_table object instance.* @param string $newname The new name for the table.* @return array Array of extra SQL statements to rename a table.*/public function getRenameTableExtraSQL($xmldb_table, $newname) {return array();}/*** Returns the code (array of statements) needed to execute extra statements on table drop** @param xmldb_table $xmldb_table The xmldb_table object instance.* @return array Array of extra SQL statements to drop a table.*/public function getDropTableExtraSQL($xmldb_table) {return array();}/*** Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its default* (usually invoked from getModifyDefaultSQL()** Note that this method may be dropped in future.** @param xmldb_table $xmldb_table The xmldb_table object instance.* @param xmldb_field $xmldb_field The xmldb_field object instance.* @return array Array of SQL statements to create a field's default.** @todo MDL-31147 Moodle 2.1 - Drop getDropDefaultSQL()*/abstract public function getDropDefaultSQL($xmldb_table, $xmldb_field);/*** Given one xmldb_table and one xmldb_field, return the SQL statements needed to add its default* (usually invoked from getModifyDefaultSQL()** @param xmldb_table $xmldb_table The xmldb_table object instance.* @param xmldb_field $xmldb_field The xmldb_field object instance.* @return array Array of SQL statements to create a field's default.*/abstract public function getCreateDefaultSQL($xmldb_table, $xmldb_field);/*** Returns an array of reserved words (lowercase) for this DB* You MUST provide the real list for each DB inside every XMLDB class.* @return array An array of database specific reserved words.* @throws coding_exception Thrown if not implemented for the specific DB.*/public static function getReservedWords() {throw new coding_exception('getReservedWords() method needs to be overridden in each subclass of sql_generator');}/*** Returns all reserved words in supported databases.* Reserved words should be lowercase.* @return array ('word'=>array(databases))*/public static function getAllReservedWords() {global $CFG;$generators = array('mysql', 'postgres', 'oracle', 'mssql');$reserved_words = array();foreach($generators as $generator) {$class = $generator . '_sql_generator';require_once("$CFG->libdir/ddl/$class.php");foreach (call_user_func(array($class, 'getReservedWords')) as $word) {$reserved_words[$word][] = $generator;}}ksort($reserved_words);return $reserved_words;}/*** Adds slashes to string.* @param string $s* @return string The escaped string.*/public function addslashes($s) {// do not use php addslashes() because it depends on PHP quote settings!$s = str_replace('\\','\\\\',$s);$s = str_replace("\0","\\\0", $s);$s = str_replace("'", "\\'", $s);return $s;}/*** Get the fields from an index definition that might be null.* @param xmldb_table $xmldb_table the table* @param xmldb_index $xmldb_index the index* @return array list of fields in the index definition that might be null.*/public function get_nullable_fields_in_index($xmldb_table, $xmldb_index) {global $DB;// If we don't have the field info passed in, we need to query it from the DB.$fieldsfromdb = null;$nullablefields = [];foreach ($xmldb_index->getFields() as $fieldname) {if ($field = $xmldb_table->getField($fieldname)) {// We have the field details in the table definition.if ($field->getNotNull() !== XMLDB_NOTNULL) {$nullablefields[] = $fieldname;}} else {// We don't have the table definition loaded. Need to// inspect the database.if ($fieldsfromdb === null) {$fieldsfromdb = $DB->get_columns($xmldb_table->getName(), false);}if (!isset($fieldsfromdb[$fieldname])) {throw new coding_exception('Unknown field ' . $fieldname .' in index ' . $xmldb_index->getName());}if (!$fieldsfromdb[$fieldname]->not_null) {$nullablefields[] = $fieldname;}}}return $nullablefields;}}