database = $database;
}
/**
* The table list, generated by the tableList() function.
* Used by the requireTable() function.
*
* @var array
*/
protected $tableList;
/**
* Keeps track whether we are currently updating the schema.
*
* @var boolean
*/
protected $schemaIsUpdating = false;
/**
* Large array structure that represents a schema update transaction
*
* @var array
*/
protected $schemaUpdateTransaction;
/**
* Enable supression of database messages.
*/
public function quiet() {
$this->supressOutput = true;
}
/**
* Execute the given SQL query.
* This abstract function must be defined by subclasses as part of the actual implementation.
* It should return a subclass of SS_Query as the result.
*
* @param string $sql The SQL query to execute
* @param int $errorLevel The level of error reporting to enable for the query
* @return SS_Query
*/
public function query($sql, $errorLevel = E_USER_ERROR) {
return $this->database->query($sql, $errorLevel);
}
/**
* Execute the given SQL parameterised query with the specified arguments
*
* @param string $sql The SQL query to execute. The ? character will denote parameters.
* @param array $parameters An ordered list of arguments.
* @param int $errorLevel The level of error reporting to enable for the query
* @return SS_Query
*/
public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) {
return $this->database->preparedQuery($sql, $parameters, $errorLevel);
}
/**
* Initiates a schema update within a single callback
*
* @var callable $callback
* @throws Exception
*/
public function schemaUpdate($callback) {
// Begin schema update
$this->schemaIsUpdating = true;
// Update table list
$this->tableList = array();
$tables = $this->tableList();
foreach ($tables as $table) {
$this->tableList[strtolower($table)] = $table;
}
// Clear update list for client code to mess around with
$this->schemaUpdateTransaction = array();
$error = null;
try {
// Yield control to client code
$callback();
// If the client code has cancelled the update then abort
if(!$this->isSchemaUpdating()) return;
// End schema update
foreach ($this->schemaUpdateTransaction as $tableName => $changes) {
$advancedOptions = isset($changes['advancedOptions']) ? $changes['advancedOptions'] : null;
switch ($changes['command']) {
case 'create':
$this->createTable($tableName, $changes['newFields'], $changes['newIndexes'],
$changes['options'], $advancedOptions);
break;
case 'alter':
$this->alterTable($tableName, $changes['newFields'], $changes['newIndexes'],
$changes['alteredFields'], $changes['alteredIndexes'],
$changes['alteredOptions'], $advancedOptions);
break;
}
}
} catch(Exception $ex) {
$error = $ex;
}
// finally {
$this->schemaUpdateTransaction = null;
$this->schemaIsUpdating = false;
// }
if($error) throw $error;
}
/**
* Cancels the schema updates requested during (but not after) schemaUpdate() call.
*/
public function cancelSchemaUpdate() {
$this->schemaUpdateTransaction = null;
$this->schemaIsUpdating = false;
}
/**
* Returns true if we are during a schema update.
*
* @return boolean
*/
function isSchemaUpdating() {
return $this->schemaIsUpdating;
}
/**
* Returns true if schema modifications were requested during (but not after) schemaUpdate() call.
*
* @return boolean
*/
public function doesSchemaNeedUpdating() {
return (bool) $this->schemaUpdateTransaction;
}
// Transactional schema altering functions - they don't do anything except for update schemaUpdateTransaction
/**
* Instruct the schema manager to record a table creation to later execute
*
* @param string $table Name of the table
* @param array $options Create table options (ENGINE, etc.)
* @param array $advanced_options Advanced table creation options
*/
public function transCreateTable($table, $options = null, $advanced_options = null) {
$this->schemaUpdateTransaction[$table] = array(
'command' => 'create',
'newFields' => array(),
'newIndexes' => array(),
'options' => $options,
'advancedOptions' => $advanced_options
);
}
/**
* Instruct the schema manager to record a table alteration to later execute
*
* @param string $table Name of the table
* @param array $options Create table options (ENGINE, etc.)
* @param array $advanced_options Advanced table creation options
*/
public function transAlterTable($table, $options, $advanced_options) {
$this->transInitTable($table);
$this->schemaUpdateTransaction[$table]['alteredOptions'] = $options;
$this->schemaUpdateTransaction[$table]['advancedOptions'] = $advanced_options;
}
/**
* Instruct the schema manager to record a field to be later created
*
* @param string $table Name of the table to hold this field
* @param string $field Name of the field to create
* @param string $schema Field specification as a string
*/
public function transCreateField($table, $field, $schema) {
$this->transInitTable($table);
$this->schemaUpdateTransaction[$table]['newFields'][$field] = $schema;
}
/**
* Instruct the schema manager to record an index to be later created
*
* @param string $table Name of the table to hold this index
* @param string $index Name of the index to create
* @param array $schema Already parsed index specification
*/
public function transCreateIndex($table, $index, $schema) {
$this->transInitTable($table);
$this->schemaUpdateTransaction[$table]['newIndexes'][$index] = $schema;
}
/**
* Instruct the schema manager to record a field to be later updated
*
* @param string $table Name of the table to hold this field
* @param string $field Name of the field to update
* @param string $schema Field specification as a string
*/
public function transAlterField($table, $field, $schema) {
$this->transInitTable($table);
$this->schemaUpdateTransaction[$table]['alteredFields'][$field] = $schema;
}
/**
* Instruct the schema manager to record an index to be later updated
*
* @param string $table Name of the table to hold this index
* @param string $index Name of the index to update
* @param array $schema Already parsed index specification
*/
public function transAlterIndex($table, $index, $schema) {
$this->transInitTable($table);
$this->schemaUpdateTransaction[$table]['alteredIndexes'][$index] = $schema;
}
/**
* Handler for the other transXXX methods - mark the given table as being altered
* if it doesn't already exist
*
* @param string $table Name of the table to initialise
*/
protected function transInitTable($table) {
if (!isset($this->schemaUpdateTransaction[$table])) {
$this->schemaUpdateTransaction[$table] = array(
'command' => 'alter',
'newFields' => array(),
'newIndexes' => array(),
'alteredFields' => array(),
'alteredIndexes' => array(),
'alteredOptions' => ''
);
}
}
/**
* Generate the following table in the database, modifying whatever already exists
* as necessary.
*
* @todo Change detection for CREATE TABLE $options other than "Engine"
*
* @param string $table The name of the table
* @param array $fieldSchema A list of the fields to create, in the same form as DataObject::$db
* @param array $indexSchema A list of indexes to create. See {@link requireIndex()}
* The values of the array can be one of:
* - true: Create a single column index on the field named the same as the index.
* - array('fields' => array('A','B','C'), 'type' => 'index/unique/fulltext'): This gives you full
* control over the index.
* @param boolean $hasAutoIncPK A flag indicating that the primary key on this table is an autoincrement type
* @param string $options SQL statement to append to the CREATE TABLE call.
* @param array $extensions List of extensions
*/
public function requireTable($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK = true,
$options = array(), $extensions = false
) {
if (!isset($this->tableList[strtolower($table)])) {
$this->transCreateTable($table, $options, $extensions);
$this->alterationMessage("Table $table: created", "created");
} else {
if (Config::inst()->get('DBSchemaManager', 'check_and_repair_on_build')) {
$this->checkAndRepairTable($table, $options);
}
// Check if options changed
$tableOptionsChanged = false;
// Check for DB constant on the schema class
$dbIDName = sprintf('%s::ID', get_class($this));
$dbID = defined($dbIDName) ? constant($dbIDName) : null;
if ($dbID && isset($options[$dbID])) {
if (preg_match('/ENGINE=([^\s]*)/', $options[$dbID], $alteredEngineMatches)) {
$alteredEngine = $alteredEngineMatches[1];
$tableStatus = $this->query(sprintf('SHOW TABLE STATUS LIKE \'%s\'', $table))->first();
$tableOptionsChanged = ($tableStatus['Engine'] != $alteredEngine);
}
}
if ($tableOptionsChanged || ($extensions && $this->database->supportsExtensions($extensions))) {
$this->transAlterTable($table, $options, $extensions);
}
}
//DB ABSTRACTION: we need to convert this to a db-specific version:
if(!isset($fieldSchema['ID'])) {
$this->requireField($table, 'ID', $this->IdColumn(false, $hasAutoIncPK));
}
// Create custom fields
if ($fieldSchema) {
foreach ($fieldSchema as $fieldName => $fieldSpec) {
//Is this an array field?
$arrayValue = '';
if (strpos($fieldSpec, '[') !== false) {
//If so, remove it and store that info separately
$pos = strpos($fieldSpec, '[');
$arrayValue = substr($fieldSpec, $pos);
$fieldSpec = substr($fieldSpec, 0, $pos);
}
$fieldObj = Object::create_from_string($fieldSpec, $fieldName);
$fieldObj->arrayValue = $arrayValue;
$fieldObj->setTable($table);
if($fieldObj instanceof PrimaryKey) {
$fieldObj->setAutoIncrement($hasAutoIncPK);
}
$fieldObj->requireField();
}
}
// Create custom indexes
if ($indexSchema) {
foreach ($indexSchema as $indexName => $indexDetails) {
$this->requireIndex($table, $indexName, $indexDetails);
}
}
}
/**
* If the given table exists, move it out of the way by renaming it to _obsolete_(tablename).
* @param string $table The table name.
*/
public function dontRequireTable($table) {
if (isset($this->tableList[strtolower($table)])) {
$suffix = '';
while (isset($this->tableList[strtolower("_obsolete_{$table}$suffix")])) {
$suffix = $suffix
? ($suffix + 1)
: 2;
}
$this->renameTable($table, "_obsolete_{$table}$suffix");
$this->alterationMessage("Table $table: renamed to _obsolete_{$table}$suffix", "obsolete");
}
}
/**
* Generate the given index in the database, modifying whatever already exists as necessary.
*
* The keys of the array are the names of the index.
* The values of the array can be one of:
* - true: Create a single column index on the field named the same as the index.
* - array('type' => 'index|unique|fulltext', 'value' => 'FieldA, FieldB'): This gives you full
* control over the index.
*
* @param string $table The table name.
* @param string $index The index name.
* @param string|array|boolean $spec The specification of the index in any
* loose format. See requireTable() for more information.
*/
public function requireIndex($table, $index, $spec) {
// Detect if adding to a new table
$newTable = !isset($this->tableList[strtolower($table)]);
// Force spec into standard array format
$spec = $this->parseIndexSpec($index, $spec);
$specString = $this->convertIndexSpec($spec);
// Check existing index
if (!$newTable) {
$indexKey = $this->indexKey($table, $index, $spec);
$indexList = $this->indexList($table);
if (isset($indexList[$indexKey])) {
// $oldSpec should be in standard array format
$oldSpec = $indexList[$indexKey];
$oldSpecString = $this->convertIndexSpec($oldSpec);
}
}
// Initiate either generation or modification of index
if ($newTable || !isset($indexList[$indexKey])) {
// New index
$this->transCreateIndex($table, $index, $spec);
$this->alterationMessage("Index $table.$index: created as $specString", "created");
} else if ($oldSpecString != $specString) {
// Updated index
$this->transAlterIndex($table, $index, $spec);
$this->alterationMessage(
"Index $table.$index: changed to $specString (from $oldSpecString)",
"changed"
);
}
}
/**
* Splits a spec string safely, considering quoted columns, whitespace,
* and cleaning brackets
*
* @param string $spec The input index specification string
* @return array List of columns in the spec
*/
protected function explodeColumnString($spec) {
// Remove any leading/trailing brackets and outlying modifiers
// E.g. 'unique (Title, "QuotedColumn");' => 'Title, "QuotedColumn"'
$containedSpec = preg_replace('/(.*\(\s*)|(\s*\).*)/', '', $spec);
// Split potentially quoted modifiers
// E.g. 'Title, "QuotedColumn"' => array('Title', 'QuotedColumn')
return preg_split('/"?\s*,\s*"?/', trim($containedSpec, '(") '));
}
/**
* Builds a properly quoted column list from an array
*
* @param array $columns List of columns to implode
* @return string A properly quoted list of column names
*/
protected function implodeColumnList($columns) {
if(empty($columns)) return '';
return '"' . implode('","', $columns) . '"';
}
/**
* Given an index specification in the form of a string ensure that each
* column name is property quoted, stripping brackets and modifiers.
* This index may also be in the form of a "CREATE INDEX..." sql fragment
*
* @param string $spec The input specification or query. E.g. 'unique (Column1, Column2)'
* @return string The properly quoted column list. E.g. '"Column1", "Column2"'
*/
protected function quoteColumnSpecString($spec) {
$bits = $this->explodeColumnString($spec);
return $this->implodeColumnList($bits);
}
/**
* Given an index spec determines the index type
*
* @param array|string $spec
* @return string
*/
protected function determineIndexType($spec) {
// check array spec
if(is_array($spec) && isset($spec['type'])) {
return $spec['type'];
} elseif (!is_array($spec) && preg_match('/(?\w+)\s*\(/', $spec, $matchType)) {
return strtolower($matchType['type']);
} else {
return 'index';
}
}
/**
* Converts an array or string index spec into a universally useful array
*
* @see convertIndexSpec() for approximate inverse
* @param string|array $spec
* @return array The resulting spec array with the required fields name, type, and value
*/
protected function parseIndexSpec($name, $spec) {
// Support $indexes = array('ColumnName' => true) for quick indexes
if ($spec === true) {
return array(
'name' => $name,
'value' => $this->quoteColumnSpecString($name),
'type' => 'index'
);
}
// Do minimal cleanup on any already parsed spec
if(is_array($spec)) {
$spec['value'] = $this->quoteColumnSpecString($spec['value']);
$spec['type'] = empty($spec['type']) ? 'index' : trim($spec['type']);
return $spec;
}
// Nicely formatted spec!
return array(
'name' => $name,
'value' => $this->quoteColumnSpecString($spec),
'type' => $this->determineIndexType($spec)
);
}
/**
* This takes the index spec which has been provided by a class (ie static $indexes = blah blah)
* and turns it into a proper string.
* Some indexes may be arrays, such as fulltext and unique indexes, and this allows database-specific
* arrays to be created. See {@link requireTable()} for details on the index format.
*
* @see http://dev.mysql.com/doc/refman/5.0/en/create-index.html
* @see parseIndexSpec() for approximate inverse
*
* @param string|array $indexSpec
*/
protected function convertIndexSpec($indexSpec) {
// Return already converted spec
if (!is_array($indexSpec)) return $indexSpec;
// Combine elements into standard string format
return "{$indexSpec['type']} ({$indexSpec['value']})";
}
/**
* Returns true if the given table is exists in the current database
*
* @param string $table Name of table to check
* @return boolean Flag indicating existence of table
*/
abstract public function hasTable($tableName);
/**
* Return true if the table exists and already has a the field specified
*
* @param string $tableName - The table to check
* @param string $fieldName - The field to check
* @return bool - True if the table exists and the field exists on the table
*/
public function hasField($tableName, $fieldName) {
if (!$this->hasTable($tableName)) return false;
$fields = $this->fieldList($tableName);
return array_key_exists($fieldName, $fields);
}
/**
* Generate the given field on the table, modifying whatever already exists as necessary.
*
* @param string $table The table name.
* @param string $field The field name.
* @param array|string $spec The field specification. If passed in array syntax, the specific database
* driver takes care of the ALTER TABLE syntax. If passed as a string, its assumed to
* be prepared as a direct SQL framgment ready for insertion into ALTER TABLE. In this case you'll
* need to take care of database abstraction in your DBField subclass.
*/
public function requireField($table, $field, $spec) {
//TODO: this is starting to get extremely fragmented.
//There are two different versions of $spec floating around, and their content changes depending
//on how they are structured. This needs to be tidied up.
$fieldValue = null;
$newTable = false;
// backwards compatibility patch for pre 2.4 requireField() calls
$spec_orig = $spec;
if (!is_string($spec)) {
$spec['parts']['name'] = $field;
$spec_orig['parts']['name'] = $field;
//Convert the $spec array into a database-specific string
$spec = $this->{$spec['type']}($spec['parts'], true);
}
// Collations didn't come in until MySQL 4.1. Anything earlier will throw a syntax error if you try and use
// collations.
// TODO: move this to the MySQLDatabase file, or drop it altogether?
if (!$this->database->supportsCollations()) {
$spec = preg_replace('/ *character set [^ ]+( collate [^ ]+)?( |$)/', '\\2', $spec);
}
if (!isset($this->tableList[strtolower($table)])) $newTable = true;
if (is_array($spec)) {
$specValue = $this->$spec_orig['type']($spec_orig['parts']);
} else {
$specValue = $spec;
}
// We need to get db-specific versions of the ID column:
if ($spec_orig == $this->IdColumn() || $spec_orig == $this->IdColumn(true)) {
$specValue = $this->IdColumn(true);
}
if (!$newTable) {
$fieldList = $this->fieldList($table);
if (isset($fieldList[$field])) {
if (is_array($fieldList[$field])) {
$fieldValue = $fieldList[$field]['data_type'];
} else {
$fieldValue = $fieldList[$field];
}
}
}
// Get the version of the field as we would create it. This is used for comparison purposes to see if the
// existing field is different to what we now want
if (is_array($spec_orig)) {
$spec_orig = $this->{$spec_orig['type']}($spec_orig['parts']);
}
if ($newTable || $fieldValue == '') {
$this->transCreateField($table, $field, $spec_orig);
$this->alterationMessage("Field $table.$field: created as $spec_orig", "created");
} else if ($fieldValue != $specValue) {
// If enums/sets are being modified, then we need to fix existing data in the table.
// Update any records where the enum is set to a legacy value to be set to the default.
foreach (array('enum', 'set') as $enumtype) {
if (preg_match("/^$enumtype/i", $specValue)) {
$newStr = preg_replace("/(^$enumtype\s*\(')|('$\).*)/i", "", $spec_orig);
$new = preg_split("/'\s*,\s*'/", $newStr);
$oldStr = preg_replace("/(^$enumtype\s*\(')|('$\).*)/i", "", $fieldValue);
$old = preg_split("/'\s*,\s*'/", $newStr);
$holder = array();
foreach ($old as $check) {
if (!in_array($check, $new)) {
$holder[] = $check;
}
}
if (count($holder)) {
$default = explode('default ', $spec_orig);
$default = $default[1];
$query = "UPDATE \"$table\" SET $field=$default WHERE $field IN (";
for ($i = 0; $i + 1 < count($holder); $i++) {
$query .= "'{$holder[$i]}', ";
}
$query .= "'{$holder[$i]}')";
$this->query($query);
$amount = $this->database->affectedRows();
$this->alterationMessage("Changed $amount rows to default value of field $field"
. " (Value: $default)");
}
}
}
$this->transAlterField($table, $field, $spec_orig);
$this->alterationMessage(
"Field $table.$field: changed to $specValue (from {$fieldValue})",
"changed"
);
}
}
/**
* If the given field exists, move it out of the way by renaming it to _obsolete_(fieldname).
*
* @param string $table
* @param string $fieldName
*/
public function dontRequireField($table, $fieldName) {
$fieldList = $this->fieldList($table);
if (array_key_exists($fieldName, $fieldList)) {
$suffix = '';
while (isset($fieldList[strtolower("_obsolete_{$fieldName}$suffix")])) {
$suffix = $suffix
? ($suffix + 1)
: 2;
}
$this->renameField($table, $fieldName, "_obsolete_{$fieldName}$suffix");
$this->alterationMessage(
"Field $table.$fieldName: renamed to $table._obsolete_{$fieldName}$suffix",
"obsolete"
);
}
}
/**
* Show a message about database alteration
*
* @param string $message to display
* @param string $type one of [created|changed|repaired|obsolete|deleted|error]
*/
public function alterationMessage($message, $type = "") {
if (!$this->supressOutput) {
if (Director::is_cli()) {
switch ($type) {
case "created":
case "changed":
case "repaired":
$sign = "+";
break;
case "obsolete":
case "deleted":
$sign = '-';
break;
case "notice":
$sign = '*';
break;
case "error":
$sign = "!";
break;
default:
$sign = " ";
}
$message = strip_tags($message);
echo " $sign $message\n";
} else {
switch ($type) {
case "created":
$color = "green";
break;
case "obsolete":
$color = "red";
break;
case "notice":
$color = "orange";
break;
case "error":
$color = "red";
break;
case "deleted":
$color = "red";
break;
case "changed":
$color = "blue";
break;
case "repaired":
$color = "blue";
break;
default:
$color = "";
}
echo "$message";
}
}
}
/**
* This returns the data type for the id column which is the primary key for each table
*
* @param boolean $asDbValue
* @param boolean $hasAutoIncPK
* @return string
*/
abstract public function IdColumn($asDbValue = false, $hasAutoIncPK = true);
/**
* Checks a table's integrity and repairs it if necessary.
*
* @param string $tableName The name of the table.
* @return boolean Return true if the table has integrity after the method is complete.
*/
abstract public function checkAndRepairTable($tableName);
/**
* Returns the values of the given enum field
*
* @param string $tableName Name of table to check
* @param string $fieldName name of enum field to check
* @return array List of enum values
*/
abstract public function enumValuesForField($tableName, $fieldName);
/*
* This is a lookup table for data types.
* For instance, Postgres uses 'INT', while MySQL uses 'UNSIGNED'
* So this is a DB-specific list of equivilents.
*
* @param string $type
* @return string
*/
abstract public function dbDataType($type);
/**
* Retrieves the list of all databases the user has access to
*
* @return array List of database names
*/
abstract public function databaseList();
/**
* Determine if the database with the specified name exists
*
* @param string $name Name of the database to check for
* @return boolean Flag indicating whether this database exists
*/
abstract public function databaseExists($name);
/**
* Create a database with the specified name
*
* @param string $name Name of the database to create
* @return boolean True if successful
*/
abstract public function createDatabase($name);
/**
* Drops a database with the specified name
*
* @param string $name Name of the database to drop
*/
abstract public function dropDatabase($name);
/**
* Alter an index on a table.
*
* @param string $tableName The name of the table.
* @param string $indexName The name of the index.
* @param string $indexSpec The specification of the index, see {@link SS_Database::requireIndex()}
* for more details.
* @todo Find out where this is called from - Is it even used? Aren't indexes always dropped and re-added?
*/
abstract public function alterIndex($tableName, $indexName, $indexSpec);
/**
* Determines the key that should be used to identify this index
* when retrieved from DBSchemaManager->indexList.
* In some connectors this is the database-visible name, in others the
* usercode-visible name.
*
* @param string $table
* @param string $index
* @param array $spec
* @return string Key for this index
*/
abstract protected function indexKey($table, $index, $spec);
/**
* Return the list of indexes in a table.
*
* @param string $table The table name.
* @return array[array] List of current indexes in the table, each in standard
* array form. The key for this array should be predictable using the indexKey
* method
*/
abstract public function indexList($table);
/**
* Returns a list of all tables in the database.
* Keys are table names in lower case, values are table names in case that
* database expects.
*
* @return array
*/
abstract public function tableList();
/**
* Create a new table.
*
* @param string $table The name of the table
* @param array $fields A map of field names to field types
* @param array $indexes A map of indexes
* @param array $options An map of additional options. The available keys are as follows:
* - 'MSSQLDatabase'/'MySQLDatabase'/'PostgreSQLDatabase' - database-specific options such as "engine" for MySQL.
* - 'temporary' - If true, then a temporary table will be created
* @param $advancedOptions Advanced creation options
* @return string The table name generated. This may be different from the table name, for example with temporary
* tables.
*/
abstract public function createTable($table, $fields = null, $indexes = null, $options = null,
$advancedOptions = null);
/**
* Alter a table's schema.
*
* @param string $table The name of the table to alter
* @param array $newFields New fields, a map of field name => field schema
* @param array $newIndexes New indexes, a map of index name => index type
* @param array $alteredFields Updated fields, a map of field name => field schema
* @param array $alteredIndexes Updated indexes, a map of index name => index type
* @param array $alteredOptions
* @param array $advancedOptions
*/
abstract public function alterTable($table, $newFields = null, $newIndexes = null, $alteredFields = null,
$alteredIndexes = null, $alteredOptions = null, $advancedOptions = null);
/**
* Rename a table.
*
* @param string $oldTableName The old table name.
* @param string $newTableName The new table name.
*/
abstract public function renameTable($oldTableName, $newTableName);
/**
* Create a new field on a table.
*
* @param string $table Name of the table.
* @param string $field Name of the field to add.
* @param string $spec The field specification, eg 'INTEGER NOT NULL'
*/
abstract public function createField($table, $field, $spec);
/**
* Change the database column name of the given field.
*
* @param string $tableName The name of the tbale the field is in.
* @param string $oldName The name of the field to change.
* @param string $newName The new name of the field
*/
abstract public function renameField($tableName, $oldName, $newName);
/**
* Get a list of all the fields for the given table.
* Returns a map of field name => field spec.
*
* @param string $table The table name.
* @return array
*/
abstract public function fieldList($table);
/**
*
* This allows the cached values for a table's field list to be erased.
* If $tablename is empty, then the whole cache is erased.
*
* @param string $tableName
*
* @return boolean
*/
public function clearCachedFieldlist($tableName = false) {
return true;
}
}