summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/common.inc154
-rw-r--r--includes/database.inc157
-rw-r--r--includes/database.mysql-common.inc409
-rw-r--r--includes/database.mysql.inc4
-rw-r--r--includes/database.mysqli.inc3
-rw-r--r--includes/database.pgsql.inc418
-rw-r--r--includes/module.inc39
7 files changed, 1179 insertions, 5 deletions
diff --git a/includes/common.inc b/includes/common.inc
index feb28de30..fb7f21a41 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -2514,6 +2514,160 @@ function drupal_common_themes() {
}
/**
+ * @ingroup schemaapi
+ * @{
+ */
+
+/**
+ * Get the schema defintion of a table, or the whole database schema.
+ * The returned schema will include any modifications made by any
+ * module that implements hook_schema_alter().
+ *
+ * @param $name
+ * The name of the table. If not given, the schema of all tables is returned.
+ * @param $rebuild
+ * If true, the schema will be rebuilt instead of retreived from the cache.
+ */
+function drupal_get_schema($name = NULL, $rebuild = FALSE) {
+ static $schema = array();
+
+ if (empty($schema) || $rebuild) {
+ // Try to load the schema from cache.
+ if (!$rebuild && $cached = cache_get('schema')) {
+ $schema = $cached->data;
+ }
+ // Otherwise, rebuild the schema cache.
+ else {
+ // Load the .schema files.
+ module_load_all_includes('schema');
+
+ // Invoke hook_schema for all modules.
+ foreach (module_implements('schema') as $module) {
+ $current = module_invoke($module, 'schema');
+ _drupal_initialize_schema($module, $current);
+ $schema = array_merge($current, $schema);
+ }
+
+ drupal_alter('schema', $schema);
+ cache_set('schema', $schema);
+ }
+ }
+
+ if (!isset($name)) {
+ return $schema;
+ }
+ elseif (isset($schema[$name])) {
+ return $schema[$name];
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Create all tables that a module defines in its hook_schema().
+ *
+ * Note: This function does not pass the module's schema through
+ * hook_schema_alter(). The module's tables will be created exactly
+ * as the module defines them.
+ *
+ * @param $module
+ * The module for which the tables will be created.
+ */
+function drupal_install_schema($module) {
+ $schema = drupal_get_schema_unprocessed($module);
+ _drupal_initialize_schema($module, $schema);
+
+ $ret = array();
+ foreach ($schema as $table) {
+ db_create_table($ret, $table);
+ }
+}
+
+/**
+ * Remove all tables that a module defines in its hook_schema().
+ *
+ * Note: This function does not pass the module's schema through
+ * hook_schema_alter(). The module's tables will be created exactly
+ * as the module defines them.
+ *
+ * @param $module
+ * The module for which the tables will be removed.
+ */
+function drupal_uninstall_schema($module) {
+ $schema = drupal_get_schema_unprocessed($module);
+ _drupal_initialize_schema($module, $schema);
+
+ $ret = array();
+ foreach ($schema as $table) {
+ db_drop_table($ret, $table['name']);
+ }
+}
+
+/**
+ * Returns the unprocessed and unaltered version of a module's schema.
+ *
+ * Use this function only if you explicitly need the original
+ * specification of a schema, as it was defined in a module's
+ * hook_schema(). No additional default values will be set,
+ * hook_schema_alter() is not invoked and these unprocessed
+ * definitions won't be cached.
+ *
+ * This function can be used to retrieve a schema specification in
+ * hook_schema(), so it allows you to derive your tables from existing
+ * specifications.
+ *
+ * It is also used by drupal_install_schema() and
+ * drupal_uninstall_schema() to ensure that a module's tables are
+ * created exactly as specified without any changes introduced by a
+ * module that implements hook_schema_alter().
+ *
+ * @param $module
+ * The module to which the table belongs.
+ * @param $table
+ * The name of the table. If not given, the module's complete schema
+ * is returned.
+ */
+function drupal_get_schema_unprocessed($module, $table = NULL) {
+ // Load the .schema file.
+ module_load_include('schema', $module);
+ $schema = module_invoke($module, 'schema');
+
+ if (!is_null($table) && isset($schema[$table])) {
+ return $schema[$table];
+ }
+ else {
+ return $schema;
+ }
+}
+
+/**
+ * Fill in required default values for table definitions returned by
+ * hook_schema().
+ *
+ * @param $module
+ * The module for which hook_schema() was invoked.
+ * @param $schema
+ * The schema definition array as it was returned by the module's
+ * hook_schema().
+ */
+function _drupal_initialize_schema($module, &$schema) {
+ // Set the name and module key for all tables.
+ foreach ($schema as $name => $table) {
+ if (empty($table['module'])) {
+ $schema[$name]['module'] = $module;
+ }
+ if (!isset($table['name'])) {
+ $schema[$name]['name'] = $name;
+ }
+ }
+}
+
+/**
+ * @} End of "ingroup schemaapi".
+ */
+
+/**
* Parse Drupal info file format.
*
* Files should use an ini-like format to specify values.
diff --git a/includes/database.inc b/includes/database.inc
index 3e095e334..6e4bdeace 100644
--- a/includes/database.inc
+++ b/includes/database.inc
@@ -44,6 +44,22 @@
*/
/**
+ * Perform an SQL query and return success or failure.
+ *
+ * @param $sql
+ * A string containing a complete SQL query. %-substitution
+ * parameters are not supported.
+ * @return
+ * An array containing the keys:
+ * success: a boolean indicating whether the query succeeded
+ * query: the SQL query executed, passed through check_plain()
+ */
+function update_sql($sql) {
+ $result = db_query($sql, true);
+ return array('success' => $result !== FALSE, 'query' => check_plain($sql));
+}
+
+/**
* Append a database prefix to all tables in a query.
*
* Queries sent to Drupal should wrap all table names in curly brackets. This
@@ -317,3 +333,144 @@ function db_escape_table($string) {
* @} End of "defgroup database".
*/
+/**
+ * @defgroup schemaapi Schema API
+ * @{
+ *
+ * A Drupal schema definition is an array structure representing one or
+ * more tables and their related keys and indexes. A schema is defined by
+ * hook_schema(), which usually lives in a modulename.schema file.
+ *
+ * By implenting hook_schema() and specifying the tables your module
+ * declares, you can easily create and drop these tables on all
+ * supported database engines. You don't have to deal with the
+ * different SQL dialects for table creation and alteration of the
+ * supported database engines.
+ *
+ * hook_schema() should return an array with a key for each table that
+ * the module defines.
+ *
+ * The following keys in the table definition are processed during
+ * table creation:
+ *
+ * - 'fields': An associative array ('fieldname' => specification)
+ * that describes the table's database columns. The specification
+ * is also an array. The following specification parameters are defined:
+ *
+ * - 'type': The generic datatype: 'varchar', 'int', 'serial'
+ * 'float', 'numeric', 'text', 'blob' or 'datetime'. Most types
+ * just map to the according database engine specific
+ * datatypes. Use 'serial' for auto incrementing fields. This
+ * will expand to 'int auto_increment' on mysql.
+ * - 'size': The data size: 'tiny', 'small', 'medium', 'normal',
+ * 'big'. This is a hint about the largest value the field will
+ * store and determines which of the database engine specific
+ * datatypes will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT).
+ * 'normal', the default, selects the base type (e.g. on MySQL,
+ * INT, VARCHAR, BLOB, etc.).
+ *
+ * Not all sizes are available for all data types. See
+ * db_type_map() for possible combinations.
+ * - 'not null': If true, no NULL values will be allowed in this
+ * database column. Defaults to false.
+ * - 'default': The field's default value. The PHP type of the
+ * value matters: '', '0', and 0 are all different. If you
+ * specify '0' as the default value for a type 'int' field it
+ * will not work because '0' is a string containing the
+ * character "zero", not an integer.
+ * - 'length': The maximal length of a type 'varchar' or 'text'
+ * field. Ignored for other field types.
+ * - 'unsigned': A boolean indicating whether a type 'int', 'float'
+ * and 'numeric' only is signed or unsigned. Defaults to
+ * FALSE. Ignored for other field types.
+ * - 'precision', 'scale': For type 'numeric' fields, indicates
+ * the precision (total number of significant digits) and scale
+ * (decimal digits right of the decimal point). Both values are
+ * mandatory. Ignored for other field types.
+ *
+ * All parameters apart from 'type' are optional except that type
+ * 'numeric' columns must specify 'precision' and 'scale'.
+ *
+ * - 'primary key': An array of one or more key column specifers (see below)
+ * that form the primary key.
+ * - 'unique key': An associative array of unique keys ('keyname' =>
+ * specification). Each specification is an array of one or more
+ * key column specifiers (see below) that form a unique key on the table.
+ * - 'indexes': An associative array of indexes ('indexame' =>
+ * specification). Each specification is an array of one or more
+ * key column specifiers (see below) that form an index on the
+ * table.
+ *
+ * A key column specifier is either a string naming a column or an
+ * array of two elements, column name and length, specifying a prefix
+ * of the named column.
+ *
+ * As an example, here is a SUBSET of the schema definition for
+ * Drupal's 'node' table. It show four fields (nid, vid, type, and
+ * title), the primary key on field 'nid', a unique key named 'vid' on
+ * field 'vid', and two indexes, one named 'nid' on field 'nid' and
+ * one named 'node_title_type' on the field 'title' and the first four
+ * bytes of the field 'type':
+ *
+ * $schema['node'] = array(
+ * 'fields' => array(
+ * 'nid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
+ * 'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+ * 'type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+ 'title' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
+ * ),
+ * 'primary key' => array('nid'),
+ * 'unique keys' => array(
+ * 'vid' => array('vid')
+ * ),
+ * 'indexes' => array(
+ * 'nid' => array('nid'),
+ * 'node_title_type' => array('title', array('type', 4)),
+ * ),
+ * );
+ *
+ * @see drupal_install_schema()
+ */
+
+ /**
+ * Create a new table from a Drupal table definition.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * A valid and processed table schema definition array.
+ */
+function db_create_table(&$ret, $table) {
+ $statements = db_create_table_sql($table);
+ foreach ($statements as $statement) {
+ $ret[] = update_sql($statement);
+ }
+}
+
+/**
+ * Return an array of field names from an array of key/index column
+ * specifiers. This is usually an identity function but if a
+ * key/index uses a column prefix specification, this function
+ * extracts just the name.
+ *
+ * @param $fields
+ * An array of key/index column specifiers.
+ * @return
+ * An array of field names.
+ */
+function db_field_names($fields) {
+ $ret = array();
+ foreach ($fields as $field) {
+ if (is_array($field)) {
+ $ret[] = $field[0];
+ }
+ else {
+ $ret[] = $field;
+ }
+ }
+ return $ret;
+}
+
+/**
+ * @} End of "defgroup schemaapi".
+ */
diff --git a/includes/database.mysql-common.inc b/includes/database.mysql-common.inc
new file mode 100644
index 000000000..c35c90d45
--- /dev/null
+++ b/includes/database.mysql-common.inc
@@ -0,0 +1,409 @@
+<?php
+
+// $Id$
+
+/**
+ * @file
+ * Functions shared between mysql and mysqli database engines.
+ */
+
+/**
+ * @ingroup schemaapi
+ * @{
+ */
+
+/**
+ * Generate SQL to create a new table from a Drupal schema definition.
+ *
+ * @param $table
+ * A valid Drupal table definition array.
+ * @return
+ * An array of SQL statements to create the table.
+ */
+function db_create_table_sql($table) {
+
+ if (empty($table['mysql_suffix'])) {
+ $table['mysql_suffix'] = "/*!40100 DEFAULT CHARACTER SET UTF8 */";
+ }
+
+ $sql = "CREATE TABLE {". $table['name'] ."} (\n";
+
+ // Add the SQL statement for each field.
+ foreach ($table['fields'] as $name => $field) {
+ $sql .= _db_create_field_sql($name, _db_process_field($field)) .", \n";
+ }
+
+ // Process keys & indexes.
+ if (!empty($table['primary key'])) {
+ $sql .= " PRIMARY KEY (". _db_create_key_sql($table['primary key']) ."), \n";
+ }
+ if (!empty($table['unique keys'])) {
+ foreach ($table['unique keys'] as $key => $fields)
+ $sql .= " UNIQUE KEY $key (". _db_create_key_sql($fields) ."), \n";
+ }
+ if (!empty($table['indexes'])) {
+ foreach ($table['indexes'] as $index => $fields)
+ $sql .= " INDEX $index (". _db_create_key_sql($fields) ."), \n";
+ }
+
+ // Remove the last comma and space.
+ $sql = substr($sql, 0, -3) ."\n) ";
+
+ $sql .= $table['mysql_suffix'];
+
+ return array($sql);
+}
+
+function _db_create_key_sql($fields) {
+ $ret = array();
+ foreach ($fields as $field) {
+ if (is_array($field)) {
+ $ret[] = $field[0] .'('. $field[1] .')';
+ }
+ else {
+ $ret[] = $field;
+ }
+ }
+ return implode(', ', $ret);
+}
+
+/**
+ * Set database-engine specific properties for a field.
+ *
+ * @param $field
+ * A field description array, as specified in the schema documentation.
+ */
+function _db_process_field($field) {
+
+ if (!isset($field['size'])) {
+ $field['size'] = 'normal';
+ }
+
+ // Set the correct database-engine specific datatype.
+ if (!isset($field['mysql_type'])) {
+ $map = db_type_map();
+ $field['mysql_type'] = $map[$field['type'] .':'. $field['size']];
+ }
+
+ if ($field['type'] == 'serial') {
+ $field['auto_increment'] = TRUE;
+ }
+
+ return $field;
+}
+
+/**
+ * Create an SQL string for a field to be used in table creation or alteration.
+ *
+ * Before passing a field out of a schema definition into this function it has
+ * to be processed by _db_process_field().
+ *
+ * @param $name
+ * Name of the field.
+ * @param $spec
+ * The field specification, as per the schema data structure format.
+ */
+function _db_create_field_sql($name, $spec) {
+ $sql = "`". $name ."` ". $spec['mysql_type'];
+
+ if (isset($spec['length'])) {
+ $sql .= '('. $spec['length'] .')';
+ }
+ elseif (isset($spec['precision']) && isset($spec['scale'])) {
+ $sql .= '('. $spec['scale'] .', '. $spec['precision'] .')';
+ }
+
+ if (!empty($spec['unsigned'])) {
+ $sql .= ' unsigned';
+ }
+
+ if (!empty($spec['not null'])) {
+ $sql .= ' NOT NULL';
+ }
+
+ if (!empty($spec['auto_increment'])) {
+ $sql .= ' auto_increment';
+ }
+
+ if (isset($spec['default'])) {
+ if (is_string($spec['default'])) {
+ $spec['default'] = "'". $spec['default'] ."'";
+ }
+ $sql .= ' DEFAULT '. $spec['default'];
+ }
+
+ if (empty($spec['not null']) && !isset($spec['default'])) {
+ $sql .= ' DEFAULT NULL';
+ }
+
+ return $sql;
+}
+
+/**
+ * This maps a generic data type in combination with its data size
+ * to the engine-specific data type.
+ */
+function db_type_map() {
+ // Put :normal last so it gets preserved by array_flip. This makes
+ // it much easier for modules (such as schema.module) to map
+ // database types back into schema types.
+ $map = array(
+ 'varchar:normal' => 'VARCHAR',
+
+ 'text:tiny' => 'SMALLTEXT',
+ 'text:small' => 'SMALLTEXT',
+ 'text:medium' => 'MEDIUMTEXT',
+ 'text:big' => 'LONGTEXT',
+ 'text:normal' => 'TEXT',
+
+ 'serial:tiny' => 'TINYINT',
+ 'serial:small' => 'SMALLINT',
+ 'serial:medium' => 'MEDIUMINT',
+ 'serial:big' => 'BIGINT',
+ 'serial:normal' => 'INT',
+
+ 'int:tiny' => 'TINYINT',
+ 'int:small' => 'SMALLINT',
+ 'int:medium' => 'MEDIUMINT',
+ 'int:big' => 'BIGINT',
+ 'int:normal' => 'INT',
+
+ 'float:tiny' => 'FLOAT',
+ 'float:small' => 'FLOAT',
+ 'float:medium' => 'FLOAT',
+ 'float:big' => 'DOUBLE',
+ 'float:normal' => 'FLOAT',
+
+ 'numeric:normal' => 'NUMERIC',
+
+ 'blob:big' => 'LONGBLOB',
+ 'blob:normal' => 'BLOB',
+
+ 'datetime:normal' => 'DATETIME',
+ );
+ return $map;
+}
+
+/**
+ * Drop a table.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be dropped.
+ */
+function db_drop_table(&$ret, $table) {
+ $ret[] = update_sql('DROP TABLE {'. $table .'}');
+}
+
+/**
+ * Add a new field to a table.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * Name of the table to be altered.
+ * @param $field
+ * Name of the field to be added.
+ * @param $spec
+ * The field specification array, as taken from a schema definition
+ */
+function db_add_field(&$ret, $table, $field, $spec) {
+ $query = 'ALTER TABLE {'. $table .'} ADD '. $field .' ';
+ $query .= _db_create_field_sql($field, _db_process_field($spec));
+ $ret[] = update_sql($query);
+}
+
+/**
+ * Drop a field.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $field
+ * The field to be dropped.
+ */
+function db_drop_field(&$ret, $table, $field) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP '. $field);
+}
+
+/**
+ * Set the default value for a field.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $field
+ * The field to be altered.
+ * @param $default
+ * Default value to be set. NULL for 'default NULL'.
+ */
+function db_field_set_default(&$ret, $table, $field, $default) {
+ if ($default == NULL) {
+ $default = 'NULL';
+ }
+ else {
+ $default = is_string($default) ? "'$default'" : $default;
+ }
+
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' SET DEFAULT '. $default);
+}
+
+/**
+ * Set a field to have no default value.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $field
+ * The field to be altered.
+ */
+function db_field_set_no_default(&$ret, $table, $field) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' DROP DEFAULT');
+}
+
+/**
+ * Add a primary key.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $fields
+ * Fields for the primary key.
+ */
+function db_add_primary_key(&$ret, $table, $fields) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD PRIMARY KEY ('.
+ _db_create_key_sql($fields) .')');
+}
+
+/**
+ * Drop the primary key.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ */
+function db_drop_primary_key(&$ret, $table) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP PRIMARY KEY');
+}
+
+/**
+ * Add a unique key.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $name
+ * The name of the key.
+ * @param $fields
+ * An array of field names.
+ */
+function db_add_unique_key(&$ret, $table, $name, $fields) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD UNIQUE KEY '.
+ $name .' ('. _db_create_key_sql($fields) .')');
+}
+
+/**
+ * Drop a unique key.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $name
+ * The name of the key.
+ */
+function db_drop_unique_key(&$ret, $table, $name) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP KEY '. $name);
+}
+
+/**
+ * Add an index.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $name
+ * The name of the index.
+ * @param $fields
+ * An array of field names.
+ */
+function db_add_index(&$ret, $table, $name, $fields) {
+ $query = 'ALTER TABLE {'. $table .'} ADD INDEX '. $name .' (';
+
+ foreach ($fields as $current) {
+ $query .= $current .', ';
+ }
+
+ // Remove the last comma, add a closing bracket.
+ $query = substr($query, 0, -2) .')';
+
+ $ret[] = update_sql($query);
+}
+
+/**
+ * Drop an index.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $name
+ * The name of the index.
+ */
+function db_drop_index(&$ret, $table, $name) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP INDEX '. $name);
+}
+
+/**
+ * Change a field definition.
+ *
+ * Remember that changing a field definition involves adding a new field
+ * and dropping an old one. This means that any indices, primary keys and
+ * sequences from serial-type fields are dropped and might need to be
+ * recreated.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * Name of the table.
+ * @param $field
+ * Name of the field to change.
+ * @param $field_new
+ * New name for the field (set to the same as $field if you don't want to change the name).
+ * @param $spec
+ * The field specification for the new field.
+ */
+function db_change_field(&$ret, $table, $field, $field_new, $spec) {
+ $ret[] = update_sql("ALTER TABLE {". $table ."} CHANGE $field ".
+ _db_create_field_sql($field_new, _db_process_field($spec)));
+}
+
+/**
+ * Update a field definition to match its schema. If the field is
+ * involved in any keys or indexes, recreate them.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * Name of the table.
+ * @param $field
+ * Name of the field to update.
+ */
+function db_update_field(&$ret, $table, $field) {
+ $spec = drupal_get_schema($table);
+ db_change_field($ret, $table, $field, $field, $spec['fields'][$field]);
+}
+
+/**
+ * @} End of "ingroup schemaapi".
+ */
+
+?> \ No newline at end of file
diff --git a/includes/database.mysql.inc b/includes/database.mysql.inc
index 1fab19191..166017621 100644
--- a/includes/database.mysql.inc
+++ b/includes/database.mysql.inc
@@ -11,6 +11,8 @@
* @{
*/
+// Include functions shared between mysql and mysqli.
+require_once './includes/database.mysql-common.inc';
/**
* Report database status.
@@ -437,5 +439,3 @@ function db_distinct_field($table, $field, $query) {
/**
* @} End of "ingroup database".
*/
-
-
diff --git a/includes/database.mysqli.inc b/includes/database.mysqli.inc
index 04f8cc9aa..ad58a6f24 100644
--- a/includes/database.mysqli.inc
+++ b/includes/database.mysqli.inc
@@ -15,6 +15,9 @@
* @{
*/
+// Include functions shared between mysql and mysqli.
+require_once './includes/database.mysql-common.inc';
+
/**
* Report database status.
*/
diff --git a/includes/database.pgsql.inc b/includes/database.pgsql.inc
index be5e4447a..3e050d8d1 100644
--- a/includes/database.pgsql.inc
+++ b/includes/database.pgsql.inc
@@ -436,4 +436,422 @@ function db_distinct_field($table, $field, $query) {
* @} End of "ingroup database".
*/
+/**
+ * @ingroup schemaapi
+ * @{
+ */
+
+/**
+ * This maps a generic data type in combination with its data size
+ * to the engine-specific data type.
+ */
+function db_type_map() {
+ // Put :normal last so it gets preserved by array_flip. This makes
+ // it much easier for modules (such as schema.module) to map
+ // database types back into schema types.
+ $map = array(
+ 'varchar:normal' => 'varchar',
+
+ 'text:tiny' => 'text',
+ 'text:small' => 'text',
+ 'text:medium' => 'text',
+ 'text:big' => 'text',
+ 'text:normal' => 'text',
+
+ 'int:tiny' => 'smallint',
+ 'int:small' => 'smallint',
+ 'int:medium' => 'int',
+ 'int:big' => 'bigint',
+ 'int:normal' => 'int',
+
+ 'float:tiny' => 'real',
+ 'float:small' => 'real',
+ 'float:medium' => 'real',
+ 'float:big' => 'double precision',
+ 'float:normal' => 'real',
+
+ 'numeric:normal' => 'numeric',
+
+ 'blob:big' => 'bytea',
+ 'blob:normal' => 'bytea',
+
+ 'datetime:normal' => 'timestamp',
+
+ 'serial:tiny' => 'serial',
+ 'serial:small' => 'serial',
+ 'serial:medium' => 'serial',
+ 'serial:big' => 'bigserial',
+ 'serial:normal' => 'serial',
+ );
+ return $map;
+}
+
+/**
+ * Generate SQL to create a new table from a Drupal schema definition.
+ *
+ * @param $table
+ * A valid Drupal table definition array.
+ * @return
+ * An array of SQL statements to create the table.
+ */
+function db_create_table_sql($table) {
+ $sql_fields = array();
+ foreach ($table['fields'] as $name => $field) {
+ $sql_fields[] = _db_create_field_sql($name, _db_process_field($field));
+ }
+
+ $sql_keys = array();
+ if (isset($table['primary key']) && is_array($table['primary key'])) {
+ $sql_keys[] = 'PRIMARY KEY ('. implode(', ', $table['primary key']) .')';
+ }
+ if (isset($table['unique keys']) && is_array($table['unique keys'])) {
+ foreach ($table['unique keys'] as $keyname => $key) {
+ $sql_keys[] = 'CONSTRAINT {'. $table['name'] .'}_'. $keyname .'_key UNIQUE ('. implode(', ', $key) .')';
+ }
+ }
+
+ $sql = "CREATE TABLE {". $table['name'] ."} (\n\t";
+ $sql .= implode(",\n\t", $sql_fields);
+ if (count($sql_keys) > 0) {
+ $sql .= ",\n\t";
+ }
+ $sql .= implode(",\n\t", $sql_keys);
+ $sql .= "\n)";
+ $statements[] = $sql;
+
+ if (isset($table['indexes']) && is_array($table['indexes'])) {
+ foreach ($table['indexes'] as $keyname => $key) {
+ $statements[] = _db_create_index_sql($table['name'], $keyname, $key);
+ }
+ }
+
+ return $statements;
+}
+
+function _db_create_index_sql($table, $name, $fields) {
+ $query = 'CREATE INDEX {'. $table .'}_'. $name .'_idx ON {'. $table .'} (';
+ $query .= _db_create_key_sql($fields) .')';
+ return $query;
+}
+
+function _db_create_key_sql($fields) {
+ $ret = array();
+ foreach ($fields as $field) {
+ if (is_array($field)) {
+ $ret[] = 'substr('. $field[0] .', 1, '. $field[1] .')';
+ }
+ else {
+ $ret[] = $field;
+ }
+ }
+ return implode(', ', $ret);
+}
+
+/**
+ * Set database-engine specific properties for a field.
+ *
+ * @param $field
+ * A field description array, as specified in the schema documentation.
+ */
+function _db_process_field($field) {
+ if (!isset($field['size'])) {
+ $field['size'] = 'normal';
+ }
+ // Set the correct database-engine specific datatype.
+ if (!isset($field['pgsql_type'])) {
+ $map = db_type_map();
+ $field['pgsql_type'] = $map[$field['type'] .':'. $field['size']];
+ }
+ if ($field['type'] == 'serial') {
+ unset($field['not null']);
+ }
+ return $field;
+}
+
+/**
+ * Create an SQL string for a field to be used in table creation or alteration.
+ *
+ * Before passing a field out of a schema definition into this function it has
+ * to be processed by _db_process_field().
+ *
+ * @param $name
+ * Name of the field.
+ * @param $spec
+ * The field specification, as per the schema data structure format.
+ */
+function _db_create_field_sql($name, $spec) {
+ $sql = $name .' '. $spec['pgsql_type'];
+
+ if ($spec['type'] == 'serial') {
+ unset($spec['not null']);
+ }
+ if (!empty($spec['unsigned'])) {
+ if ($spec['type'] == 'serial') {
+ $sql .= " CHECK ($name >= 0)";
+ }
+ else {
+ $sql .= '_unsigned';
+ }
+ }
+
+ if (!empty($spec['length'])) {
+ $sql .= '('. $spec['length'] .')';
+ }
+ elseif (isset($spec['precision']) && isset($spec['scale'])) {
+ $sql .= '('. $spec['scale'] .', '. $spec['precision'] .')';
+ }
+
+ if (isset($spec['not null']) && $spec['not null']) {
+ $sql .= ' NOT NULL';
+ }
+ if (isset($spec['default'])) {
+ $default = is_string($spec['default']) ? "'". $spec['default'] ."'" : $spec['default'];
+ $sql .= " default $default";
+ }
+
+ return $sql;
+}
+
+/**
+ * Drop a table.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be dropped.
+ */
+function db_drop_table(&$ret, $table) {
+ $ret[] = update_sql('DROP TABLE {'. $table .'}');
+}
+
+/**
+ * Add a new field to a table.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * Name of the table to be altered.
+ * @param $field
+ * Name of the field to be added.
+ * @param $spec
+ * The field specification array, as taken from a schema definition
+ */
+function db_add_field(&$ret, $table, $field, $spec) {
+ $query = 'ALTER TABLE {'. $table .'} ADD COLUMN ';
+ $query .= _db_create_field_sql($field, _db_process_field($spec));
+ $ret[] = update_sql($query);
+}
+
+/**
+ * Drop a field.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $field
+ * The field to be dropped.
+ */
+function db_drop_field(&$ret, $table, $field) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP COLUMN '. $field);
+}
+
+/**
+ * Set the default value for a field.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $field
+ * The field to be altered.
+ * @param $default
+ * Default value to be set. NULL for 'default NULL'.
+ */
+function db_field_set_default(&$ret, $table, $field, $default) {
+ if ($default == NULL) {
+ $default = 'NULL';
+ }
+ else {
+ $default = is_string($default) ? "'$default'" : $default;
+ }
+
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' SET DEFAULT '. $default);
+}
+
+/**
+ * Set a field to have no default value.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $field
+ * The field to be altered.
+ */
+function db_field_set_no_default(&$ret, $table, $field) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' DROP DEFAULT');
+}
+
+/**
+ * Add a primary key.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $fields
+ * Fields for the primary key.
+ */
+function db_add_primary_key(&$ret, $table, $fields) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD PRIMARY KEY ('.
+ implode(',', $fields) .')');
+}
+
+/**
+ * Drop the primary key.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ */
+function db_drop_primary_key(&$ret, $table) {
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP CONSTRAINT {'. $table .'}_pkey');
+}
+
+/**
+ * Add a unique key.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $name
+ * The name of the key.
+ * @param $fields
+ * An array of field names.
+ */
+function db_add_unique_key(&$ret, $table, $name, $fields) {
+ $name = '{'. $table .'}_'. $name .'_key';
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD CONSTRAINT '.
+ $name .' UNIQUE ('. implode(',', $fields) .')');
+}
+
+/**
+ * Drop a unique key.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $name
+ * The name of the key.
+ */
+function db_drop_unique_key(&$ret, $table, $name) {
+ $name = '{'. $table .'}_'. $name .'_key';
+ $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP CONSTRAINT '. $name);
+}
+
+/**
+ * Add an index.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $name
+ * The name of the index.
+ * @param $fields
+ * An array of field names.
+ */
+function db_add_index(&$ret, $table, $name, $fields) {
+ $ret[] = update_sql(_db_create_index_sql($table, $name, $fields));
+}
+
+/**
+ * Drop an index.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * The table to be altered.
+ * @param $name
+ * The name of the index.
+ */
+function db_drop_index(&$ret, $table, $name) {
+ $name = '{'. $table .'}_'. $name .'_idx';
+ $ret[] = update_sql('DROP INDEX '. $name);
+}
+
+/**
+ * Change a field definition.
+ *
+ * Remember that changing a field definition involves adding a new field
+ * and dropping an old one. This means that any indices, primary keys and
+ * sequences from serial-type fields are dropped and might need to be
+ * recreated.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * Name of the table.
+ * @param $field
+ * Name of the field to change.
+ * @param $field_new
+ * New name for the field (set to the same as $field if you don't want to change the name).
+ * @param $spec
+ * The field specification for the new field.
+ */
+function db_change_field(&$ret, $table, $field, $field_new, $spec) {
+ $ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $field TO ". $field ."_old");
+ $not_null = isset($spec['not null']) ? $spec['not null'] : FALSE;
+ unset($spec['not null']);
+ db_add_field($ret, $table, "$field_new", $spec);
+ $ret[] = update_sql("UPDATE {". $table ."} SET $field_new = ". $field ."_old");
+ if ($not_null) {
+ $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $field_new SET NOT NULL");
+ }
+ db_drop_field($ret, $table, $field .'_old');
+}
+
+/**
+ * Update a field definition to match its schema. If the field is
+ * involved in any keys or indexes, recreate them if necessary.
+ *
+ * @param $ret
+ * Array to which query results will be added.
+ * @param $table
+ * Name of the table.
+ * @param $field
+ * Name of the field to update.
+ */
+function db_update_field(&$ret, $table, $field) {
+ $spec = drupal_get_schema($table);
+
+ db_change_field($ret, $table, $field, $field, $spec['fields'][$field]);
+ if (isset($spec['primary key'])) {
+ if (array_search($field, db_field_names($spec['primary key'])) !== FALSE) {
+ db_add_primary_key($ret, $table, $spec['primary key']);
+ }
+ }
+ if (isset($spec['unique keys'])) {
+ foreach ($spec['unique keys'] as $name => $fields) {
+ if (array_search($field, db_field_names($fields)) !== FALSE) {
+ db_add_unique_key($ret, $table, $fields);
+ }
+ }
+ }
+ if (isset($spec['indexes'])) {
+ foreach ($spec['indexes'] as $name => $fields) {
+ if (array_search($field, db_field_names($fields)) !== FALSE) {
+ db_add_index($ret, $table, $fields);
+ }
+ }
+ }
+}
+
+/**
+ * @} End of "ingroup schemaapi".
+ */
diff --git a/includes/module.inc b/includes/module.inc
index 5ff8f5575..dcec75f5c 100644
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -189,9 +189,42 @@ function module_load_install($module) {
// Make sure the installation API is available
include_once './includes/install.inc';
- $install_file = './'. drupal_get_path('module', $module) .'/'. $module .'.install';
- if (is_file($install_file)) {
- include_once $install_file;
+ module_load_include('install', $module);
+}
+
+/**
+ * Load a module include file.
+ *
+ * @param $type
+ * The include file's type (file extension).
+ * @param $module
+ * The module to which the include file belongs.
+ * @param $name
+ * Optionally, specify the file name. If not set, the module's name is used.
+ */
+function module_load_include($type, $module, $name = NULL) {
+ if (empty($name)) {
+ $name = $module;
+ }
+
+ $file = './'. drupal_get_path('module', $module) ."/$name.$type";
+
+ if (is_file($file)) {
+ require_once $file;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Load an include file for each of the modules that have been enabled in
+ * the system table.
+ */
+function module_load_all_includes($type, $name = NULL) {
+ $modules = module_list();
+ foreach ($modules as $module) {
+ module_load_include($type, $module, $name);
}
}