/classes/fSchema.php
PHP | 3157 lines | 2249 code | 319 blank | 589 comment | 318 complexity | c1a92bec7e4ab06e4c8e2ae4f356e0f0 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- * Gets schema information for the selected database
- *
- * @copyright Copyright (c) 2007-2012 Will Bond
- * @author Will Bond [wb] <will@flourishlib.com>
- * @license http://flourishlib.com/license
- *
- * @package Flourish
- * @link http://flourishlib.com/fSchema
- *
- * @version 1.0.0b51
- * @changes 1.0.0b51 Fixed handling of getting tables in table creation order when a table references itself, fixed default value detection for the last column in a MySQL table [wb, 2012-01-12]
- * @changes 1.0.0b50 Fixed detection of explicitly named SQLite foreign key constraints [wb, 2011-08-23]
- * @changes 1.0.0b49 Added support for spatial/geometric data types in MySQL and PostgreSQL [wb, 2011-05-26]
- * @changes 1.0.0b48 Fixed a bug with ::getTables() not working on MySQL 4.x, fixed ::getKeys() to always return a reset array [wb, 2011-05-24]
- * @changes 1.0.0b47 Backwards Compatibility Break - ::getTables(), ::getColumnInfo(), ::getDatabases(), ::getKeys() and ::getRelationships() now return database, schema, table and column names in lowercase, added the `$creation_order` parameter to ::getTables(), fixed bugs with getting column and key information from MSSQL, Oracle and SQLite [wb, 2011-05-09]
- * @changes 1.0.0b46 Enhanced SQLite schema detection to cover situations where `UNIQUE` constraints are defined separately from the table and when comments are used in `CREATE TABLE` statements [wb, 2011-02-06]
- * @changes 1.0.0b45 Fixed Oracle auto incrementing detection to work with `INSERT OR UPDATE` triggers, fixed detection of dynamic default date/time/timestamp values for DB2 and Oracle [wb, 2010-12-04]
- * @changes 1.0.0b44 Fixed the list of valid elements for ::getColumnInfo() [wb, 2010-11-28]
- * @changes 1.0.0b43 Added the `comment` element to the information returned by ::getColumnInfo() [wb, 2010-11-28]
- * @changes 1.0.0b42 Fixed a bug with MySQL detecting default `ON DELETE` clauses [wb, 2010-10-19]
- * @changes 1.0.0b41 Fixed handling MySQL table names that require quoting [wb, 2010-08-24]
- * @changes 1.0.0b40 Fixed bugs in the documentation and error message of ::getColumnInfo() about what are valid elements [wb, 2010-07-21]
- * @changes 1.0.0b39 Fixed a regression where key detection SQL was not compatible with PostgreSQL 8.1 [wb, 2010-04-13]
- * @changes 1.0.0b38 Added Oracle support to ::getDatabases() [wb, 2010-04-13]
- * @changes 1.0.0b37 Fixed ::getDatabases() for MSSQL [wb, 2010-04-09]
- * @changes 1.0.0b36 Fixed PostgreSQL to properly report explicit `NULL` default values via ::getColumnInfo() [wb, 2010-03-30]
- * @changes 1.0.0b35 Added `max_length` values for various text and blob data types across all databases [wb, 2010-03-29]
- * @changes 1.0.0b34 Added `min_value` and `max_value` attributes to ::getColumnInfo() to specify the valid range for numeric columns [wb, 2010-03-16]
- * @changes 1.0.0b33 Changed it so that PostgreSQL unique indexes containing functions are ignored since they can't be properly detected at this point [wb, 2010-03-14]
- * @changes 1.0.0b32 Fixed ::getTables() to not include views for MySQL [wb, 2010-03-14]
- * @changes 1.0.0b31 Fixed the creation of the default caching key for ::enableCaching() [wb, 2010-03-02]
- * @changes 1.0.0b30 Fixed the class to work with lower privilege Oracle accounts and added detection of Oracle number columns [wb, 2010-01-25]
- * @changes 1.0.0b29 Added on_delete and on_update elements to one-to-one relationship info retrieved by ::getRelationships() [wb, 2009-12-16]
- * @changes 1.0.0b28 Fixed a bug with detecting some multi-column unique constraints in SQL Server databases [wb, 2009-11-13]
- * @changes 1.0.0b27 Added a parameter to ::enableCaching() to provide a key token that will allow cached values to be shared between multiple databases with the same schema [wb, 2009-10-28]
- * @changes 1.0.0b26 Added the placeholder element to the output of ::getColumnInfo(), added support for PostgreSQL, MSSQL and Oracle "schemas", added support for parsing quoted SQLite identifiers [wb, 2009-10-22]
- * @changes 1.0.0b25 One-to-one relationships utilizing the primary key as a foreign key are now properly detected [wb, 2009-09-22]
- * @changes 1.0.0b24 Fixed MSSQL support to work with ODBC database connections [wb, 2009-09-18]
- * @changes 1.0.0b23 Fixed a bug where one-to-one relationships were being listed as many-to-one [wb, 2009-07-21]
- * @changes 1.0.0b22 PostgreSQL UNIQUE constraints that are created as indexes and not table constraints are now properly detected [wb, 2009-07-08]
- * @changes 1.0.0b21 Added support for the UUID data type in PostgreSQL [wb, 2009-06-18]
- * @changes 1.0.0b20 Add caching of merged info, improved performance of ::getColumnInfo() [wb, 2009-06-15]
- * @changes 1.0.0b19 Fixed a couple of bugs with ::setKeysOverride() [wb, 2009-06-04]
- * @changes 1.0.0b18 Added missing support for MySQL mediumint columns [wb, 2009-05-18]
- * @changes 1.0.0b17 Fixed a bug with ::clearCache() not properly reseting the tables and databases list [wb, 2009-05-13]
- * @changes 1.0.0b16 Backwards Compatibility Break - ::setCacheFile() changed to ::enableCaching() and now requires an fCache object, ::flushInfo() renamed to ::clearCache(), added Oracle support [wb, 2009-05-04]
- * @changes 1.0.0b15 Added support for the three different types of identifier quoting in SQLite [wb, 2009-03-28]
- * @changes 1.0.0b14 Added support for MySQL column definitions containing the COLLATE keyword [wb, 2009-03-28]
- * @changes 1.0.0b13 Fixed a bug with detecting PostgreSQL columns having both a CHECK constraint and a UNIQUE constraint [wb, 2009-02-27]
- * @changes 1.0.0b12 Fixed detection of multi-column primary keys in MySQL [wb, 2009-02-27]
- * @changes 1.0.0b11 Fixed an issue parsing MySQL tables with comments [wb, 2009-02-25]
- * @changes 1.0.0b10 Added the ::getDatabases() method [wb, 2009-02-24]
- * @changes 1.0.0b9 Now detects unsigned and zerofill MySQL data types that do not have a parenthetical part [wb, 2009-02-16]
- * @changes 1.0.0b8 Mapped the MySQL data type `'set'` to `'varchar'`, however valid values are not implemented yet [wb, 2009-02-01]
- * @changes 1.0.0b7 Fixed a bug with detecting MySQL timestamp columns [wb, 2009-01-28]
- * @changes 1.0.0b6 Fixed a bug with detecting MySQL columns that accept `NULL` [wb, 2009-01-19]
- * @changes 1.0.0b5 ::setColumnInfo(): fixed a bug with not grabbing the real database schema first, made general improvements [wb, 2009-01-19]
- * @changes 1.0.0b4 Added support for MySQL binary data types, numeric data type options unsigned and zerofill, and per-column character set definitions [wb, 2009-01-17]
- * @changes 1.0.0b3 Fixed detection of the data type of MySQL timestamp columns, added support for dynamic default date/time values [wb, 2009-01-11]
- * @changes 1.0.0b2 Fixed a bug with detecting multi-column unique keys in MySQL [wb, 2009-01-03]
- * @changes 1.0.0b The initial implementation [wb, 2007-09-25]
- */
- class fSchema
- {
- /**
- * The place to cache to
- *
- * @var fCache
- */
- private $cache = NULL;
-
- /**
- * The cache prefix to use for cache entries
- *
- * @var string
- */
- private $cache_prefix;
-
- /**
- * The cached column info
- *
- * @var array
- */
- private $column_info = array();
-
- /**
- * The column info to override
- *
- * @var array
- */
- private $column_info_override = array();
-
- /**
- * A reference to an instance of the fDatabase class
- *
- * @var fDatabase
- */
- private $database = NULL;
-
- /**
- * The databases on the current database server
- *
- * @var array
- */
- private $databases = NULL;
-
- /**
- * The cached key info
- *
- * @var array
- */
- private $keys = array();
-
- /**
- * The key info to override
- *
- * @var array
- */
- private $keys_override = array();
-
- /**
- * The merged column info
- *
- * @var array
- */
- private $merged_column_info = array();
-
- /**
- * The merged key info
- *
- * @var array
- */
- private $merged_keys = array();
-
- /**
- * The relationships in the database
- *
- * @var array
- */
- private $relationships = array();
-
- /**
- * The tables in the database
- *
- * @var array
- */
- private $tables = NULL;
-
-
- /**
- * Sets the database
- *
- * @param fDatabase $database The fDatabase instance
- * @return fSchema
- */
- public function __construct($database)
- {
- $this->database = $database;
- }
-
-
- /**
- * All requests that hit this method should be requests for callbacks
- *
- * @internal
- *
- * @param string $method The method to create a callback for
- * @return callback The callback for the method requested
- */
- public function __get($method)
- {
- return array($this, $method);
- }
-
-
- /**
- * Checks to see if a column is part of a single-column `UNIQUE` key
- *
- * @param string $table The table the column is located in
- * @param string $column The column to check
- * @return boolean If the column is part of a single-column unique key
- */
- private function checkForSingleColumnUniqueKey($table, $column)
- {
- foreach ($this->merged_keys[$table]['unique'] as $key) {
- if (array($column) == $key) {
- return TRUE;
- }
- }
- if (array($column) == $this->merged_keys[$table]['primary']) {
- return TRUE;
- }
- return FALSE;
- }
-
-
- /**
- * Clears all of the schema info out of the object and, if set, the fCache object
- *
- * @internal
- *
- * @return void
- */
- public function clearCache()
- {
- $this->column_info = array();
- $this->databases = NULL;
- $this->keys = array();
- $this->merged_column_info = array();
- $this->merged_keys = array();
- $this->relationships = array();
- $this->tables = NULL;
- if ($this->cache) {
- $prefix = $this->makeCachePrefix();
- $this->cache->delete($prefix . 'column_info');
- $this->cache->delete($prefix . 'databases');
- $this->cache->delete($prefix . 'keys');
- $this->cache->delete($prefix . 'merged_column_info');
- $this->cache->delete($prefix . 'merged_keys');
- $this->cache->delete($prefix . 'relationships');
- $this->cache->delete($prefix . 'tables');
- }
- }
-
-
- /**
- * Returns an ordered array of table names, in a valid table creation order
- *
- * @param string $filter_table The only return this table and tables that rely on it
- * @return array An array of table names
- */
- private function determineTableCreationOrder($filter_table=NULL)
- {
- $found = array();
- $ignored_found = array();
-
- $current_tables = $this->getTables();
- // Prevent an infinite loop
- $last_tables = array();
- while ($current_tables && $current_tables != $last_tables) {
- $remaining_tables = array();
- foreach ($current_tables as $table) {
- $foreign_keys = $this->getKeys($table, 'foreign');
- if (!$foreign_keys) {
- if ($filter_table !== NULL) {
- if ($table == $filter_table) {
- $found[] = $table;
- } else {
- $ignored_found[] = $table;
- }
- } else {
- $found[] = $table;
- }
-
- } else {
- $all_dependencies_met = TRUE;
- $found_dependencies = 0;
- foreach ($foreign_keys as $foreign_key) {
- if ($table != $foreign_key['foreign_table'] && !in_array($foreign_key['foreign_table'], $found) && !in_array($foreign_key['foreign_table'], $ignored_found)) {
- $all_dependencies_met = FALSE;
- break;
- } elseif (in_array($foreign_key['foreign_table'], $found)) {
- $found_dependencies++;
- }
- }
- if ($all_dependencies_met) {
- if ($filter_table !== NULL) {
- if ($found_dependencies || $table == $filter_table) {
- $found[] = $table;
- } else {
- $ignored_found[] = $table;
- }
- } else {
- $found[] = $table;
- }
-
- } else {
- $remaining_tables[] = $table;
- }
- }
- }
- $last_tables = $current_tables;
- $current_tables = $remaining_tables;
- }
-
- return $found;
- }
-
-
- /**
- * Sets the schema to be cached to the fCache object specified
- *
- * @param fCache $cache The cache to cache to
- * @param string $key_token Internal use only! (this will be used in the cache key to uniquely identify the cache for this fSchema object)
- * @return void
- */
- public function enableCaching($cache, $key_token=NULL)
- {
- $this->cache = $cache;
-
- if ($key_token !== NULL) {
- $this->cache_prefix = 'fSchema::' . $this->database->getType() . '::' . $key_token . '::';
- }
- $prefix = $this->makeCachePrefix();
-
- $this->column_info = $this->cache->get($prefix . 'column_info', array());
- $this->databases = $this->cache->get($prefix . 'databases', NULL);
- $this->keys = $this->cache->get($prefix . 'keys', array());
-
- if (!$this->column_info_override && !$this->keys_override) {
- $this->merged_column_info = $this->cache->get($prefix . 'merged_column_info', array());
- $this->merged_keys = $this->cache->get($prefix . 'merged_keys', array());
- $this->relationships = $this->cache->get($prefix . 'relationships', array());
- }
-
- $this->tables = $this->cache->get($prefix . 'tables', NULL);
- }
-
-
- /**
- * Gets the column info from the database for later access
- *
- * @param string $table The table to fetch the column info for
- * @return void
- */
- private function fetchColumnInfo($table)
- {
- if (isset($this->column_info[$table])) {
- return;
- }
-
- switch ($this->database->getType()) {
- case 'db2':
- $column_info = $this->fetchDB2ColumnInfo($table);
- break;
-
- case 'mssql':
- $column_info = $this->fetchMSSQLColumnInfo($table);
- break;
-
- case 'mysql':
- $column_info = $this->fetchMySQLColumnInfo($table);
- break;
-
- case 'oracle':
- $column_info = $this->fetchOracleColumnInfo($table);
- break;
-
- case 'postgresql':
- $column_info = $this->fetchPostgreSQLColumnInfo($table);
- break;
-
- case 'sqlite':
- $column_info = $this->fetchSQLiteColumnInfo($table);
- break;
- }
-
- if (!$column_info) {
- return;
- }
-
- $this->column_info[$table] = $column_info;
- if ($this->cache) {
- $this->cache->set($this->makeCachePrefix() . 'column_info', $this->column_info);
- }
- }
-
-
- /**
- * Gets the column info from a DB2 database
- *
- * @param string $table The table to fetch the column info for
- * @return array The column info for the table specified - see ::getColumnInfo() for details
- */
- private function fetchDB2ColumnInfo($table)
- {
- $column_info = array();
-
- $schema = strtolower($this->database->getUsername());
- if (strpos($table, '.') !== FALSE) {
- list ($schema, $table) = explode('.', $table);
- }
-
- $data_type_mapping = array(
- 'smallint' => 'integer',
- 'integer' => 'integer',
- 'bigint' => 'integer',
- 'timestamp' => 'timestamp',
- 'date' => 'date',
- 'time' => 'time',
- 'varchar' => 'varchar',
- 'long varchar' => 'varchar',
- 'vargraphic' => 'varchar',
- 'long vargraphic' => 'varchar',
- 'character' => 'char',
- 'graphic' => 'char',
- 'real' => 'float',
- 'decimal' => 'float',
- 'numeric' => 'float',
- 'blob' => 'blob',
- 'clob' => 'text',
- 'dbclob' => 'text'
- );
-
- $max_min_values = array(
- 'smallint' => array('min' => new fNumber(-32768), 'max' => new fNumber(32767)),
- 'integer' => array('min' => new fNumber(-2147483648), 'max' => new fNumber(2147483647)),
- 'bigint' => array('min' => new fNumber('-9223372036854775808'), 'max' => new fNumber('9223372036854775807'))
- );
-
- // Get the column info
- $sql = "SELECT
- LOWER(C.COLNAME) AS \"COLUMN\",
- C.TYPENAME AS TYPE,
- C.NULLS AS NULLABLE,
- C.DEFAULT,
- C.LENGTH AS MAX_LENGTH,
- C.SCALE,
- CASE WHEN C.IDENTITY = 'Y' AND (C.GENERATED = 'D' OR C.GENERATED = 'A') THEN '1' ELSE '0' END AS AUTO_INCREMENT,
- CH.TEXT AS \"CONSTRAINT\",
- C.REMARKS AS \"COMMENT\"
- FROM
- SYSCAT.COLUMNS AS C LEFT JOIN
- SYSCAT.COLCHECKS AS CC ON
- C.TABSCHEMA = CC.TABSCHEMA AND
- C.TABNAME = CC.TABNAME AND
- C.COLNAME = CC.COLNAME AND
- CC.USAGE = 'R' LEFT JOIN
- SYSCAT.CHECKS AS CH ON
- C.TABSCHEMA = CH.TABSCHEMA AND
- C.TABNAME = CH.TABNAME AND
- CH.TYPE = 'C' AND
- CH.CONSTNAME = CC.CONSTNAME
- WHERE
- LOWER(C.TABSCHEMA) = %s AND
- LOWER(C.TABNAME) = %s
- ORDER BY
- C.COLNO ASC";
-
- $result = $this->database->query($sql, strtolower($schema), strtolower($table));
-
- foreach ($result as $row) {
-
- $info = array();
-
- foreach ($data_type_mapping as $data_type => $mapped_data_type) {
- if (stripos($row['type'], $data_type) === 0) {
- if (isset($max_min_values[$data_type])) {
- $info['min_value'] = $max_min_values[$data_type]['min'];
- $info['max_value'] = $max_min_values[$data_type]['max'];
- }
- $info['type'] = $mapped_data_type;
- break;
- }
- }
-
- // Handle decimal places and min/max for numeric/decimals
- if (in_array(strtolower($row['type']), array('decimal', 'numeric'))) {
- $info['decimal_places'] = $row['scale'];
- $before_digits = str_pad('', $row['max_length'] - $row['scale'], '9');
- $after_digits = str_pad('', $row['scale'], '9');
- $max_min = $before_digits . ($after_digits ? '.' : '') . $after_digits;
- $info['min_value'] = new fNumber('-' . $max_min);
- $info['max_value'] = new fNumber($max_min);
- }
-
- if (!isset($info['type'])) {
- $info['type'] = $row['type'];
- }
-
- // Handle the special data for varchar columns
- if (in_array($info['type'], array('char', 'varchar', 'text', 'blob'))) {
- $info['max_length'] = $row['max_length'];
- }
-
- // The generally accepted practice for boolean on DB2 is a CHAR(1) with a CHECK constraint
- if ($info['type'] == 'char' && $info['max_length'] == 1 && !empty($row['constraint'])) {
- if (is_resource($row['constraint'])) {
- $row['constraint'] = stream_get_contents($row['constraint']);
- }
- if (preg_match('/^\s*"?' . preg_quote($row['column'], '/') . '"?\s+in\s+\(\s*(\'0\',\s*\'1\'|\'1\',\s*\'0\')\s*\)\s*$/i', $row['constraint'])) {
- $info['type'] = 'boolean';
- $info['max_length'] = NULL;
- }
- }
-
- // If the column has a constraint, look for valid values
- if (in_array($info['type'], array('char', 'varchar')) && !empty($row['constraint'])) {
- if (preg_match('/^\s*"?' . preg_quote($row['column'], '/') . '"?\s+in\s+\((.*?)\)\s*$/i', $row['constraint'], $match)) {
- if (preg_match_all("/(?<!')'((''|[^']+)*)'/", $match[1], $matches, PREG_PATTERN_ORDER)) {
- $info['valid_values'] = str_replace("''", "'", $matches[1]);
- }
- }
- }
-
- // Handle auto increment
- if ($row['auto_increment']) {
- $info['auto_increment'] = TRUE;
- }
-
- // Handle default values
- if ($row['default'] !== NULL) {
- if ($row['default'] == 'NULL') {
- $info['default'] = NULL;
- } elseif (in_array($info['type'], array('timestamp', 'date', 'time')) && $row['default'][0] != "'") {
- $info['default'] = str_replace(' ', '_', $row['default']);
- } elseif (in_array($info['type'], array('char', 'varchar', 'text', 'timestamp', 'date', 'time')) ) {
- $info['default'] = substr($row['default'], 1, -1);
- } elseif ($info['type'] == 'boolean') {
- $info['default'] = (boolean) substr($row['default'], 1, -1);
- } else {
- $info['default'] = $row['default'];
- }
- }
-
- // Handle not null
- $info['not_null'] = ($row['nullable'] == 'N') ? TRUE : FALSE;
-
- $info['comment'] = $row['comment'];
-
- $column_info[$row['column']] = $info;
- }
-
- return $column_info;
- }
-
-
- /**
- * Fetches the key info for a DB2 database
- *
- * @return array The keys arrays for every table in the database - see ::getKeys() for details
- */
- private function fetchDB2Keys()
- {
- $keys = array();
-
- $default_schema = strtolower($this->database->getUsername());
-
- $tables = $this->getTables();
- foreach ($tables as $table) {
- $keys[$table] = array();
- $keys[$table]['primary'] = array();
- $keys[$table]['unique'] = array();
- $keys[$table]['foreign'] = array();
- }
-
- $params = array();
-
- $sql = "(SELECT
- LOWER(RTRIM(R.TABSCHEMA)) AS \"SCHEMA\",
- LOWER(R.TABNAME) AS \"TABLE\",
- R.CONSTNAME AS CONSTRAINT_NAME,
- 'foreign' AS \"TYPE\",
- LOWER(K.COLNAME) AS \"COLUMN\",
- LOWER(RTRIM(R.REFTABSCHEMA)) AS FOREIGN_SCHEMA,
- LOWER(R.REFTABNAME) AS FOREIGN_TABLE,
- LOWER(FK.COLNAME) AS FOREIGN_COLUMN,
- CASE R.DELETERULE WHEN 'C' THEN 'cascade' WHEN 'A' THEN 'no_action' WHEN 'R' THEN 'restrict' ELSE 'set_null' END AS ON_DELETE,
- CASE R.UPDATERULE WHEN 'A' THEN 'no_action' WHEN 'R' THEN 'restrict' END AS ON_UPDATE,
- K.COLSEQ
- FROM
- SYSCAT.REFERENCES AS R INNER JOIN
- SYSCAT.KEYCOLUSE AS K ON
- R.CONSTNAME = K.CONSTNAME AND
- R.TABSCHEMA = K.TABSCHEMA AND
- R.TABNAME = K.TABNAME INNER JOIN
- SYSCAT.KEYCOLUSE AS FK ON
- R.REFKEYNAME = FK.CONSTNAME AND
- R.REFTABSCHEMA = FK.TABSCHEMA AND
- R.REFTABNAME = FK.TABNAME
- WHERE ";
-
- $conditions = array();
- foreach ($tables as $table) {
- if (strpos($table, '.') === FALSE) {
- $table = $default_schema . '.' . $table;
- }
- list ($schema, $table) = explode('.', strtolower($table));
- $conditions[] = "LOWER(R.TABSCHEMA) = %s AND LOWER(R.TABNAME) = %s";
- $params[] = $schema;
- $params[] = $table;
- }
- $sql .= '((' . join(') OR( ', $conditions) . '))';
-
- $sql .= "
- ) UNION (
- SELECT
- LOWER(RTRIM(I.TABSCHEMA)) AS \"SCHEMA\",
- LOWER(I.TABNAME) AS \"TABLE\",
- LOWER(I.INDNAME) AS CONSTRAINT_NAME,
- CASE I.UNIQUERULE WHEN 'U' THEN 'unique' ELSE 'primary' END AS \"TYPE\",
- LOWER(C.COLNAME) AS \"COLUMN\",
- NULL AS FOREIGN_SCHEMA,
- NULL AS FOREIGN_TABLE,
- NULL AS FOREIGN_COLUMN,
- NULL AS ON_DELETE,
- NULL AS ON_UPDATE,
- C.COLSEQ
- FROM
- SYSCAT.INDEXES AS I INNER JOIN
- SYSCAT.INDEXCOLUSE AS C ON I.INDSCHEMA = C.INDSCHEMA AND I.INDNAME = C.INDNAME
- WHERE
- I.UNIQUERULE IN ('U', 'P') AND
- ";
-
- $conditions = array();
- foreach ($tables as $table) {
- if (strpos($table, '.') === FALSE) {
- $table = $default_schema . '.' . $table;
- }
- list ($schema, $table) = explode('.', strtolower($table));
- $conditions[] = "LOWER(I.TABSCHEMA) = %s AND LOWER(I.TABNAME) = %s";
- $params[] = $schema;
- $params[] = $table;
- }
- $sql .= '((' . join(') OR( ', $conditions) . '))';
-
- $sql .= "
- )
- ORDER BY 4, 1, 2, 3, 11";
-
- $result = $this->database->query($sql, $params);
-
- $last_name = '';
- $last_table = '';
- $last_type = '';
- foreach ($result as $row) {
-
- if ($row['constraint_name'] != $last_name) {
-
- if ($last_name) {
- if ($last_type == 'foreign' || $last_type == 'unique') {
- $keys[$last_table][$last_type][] = $temp;
- } else {
- $keys[$last_table][$last_type] = $temp;
- }
- }
-
- $temp = array();
- if ($row['type'] == 'foreign') {
-
- $temp['column'] = $row['column'];
- $temp['foreign_table'] = $row['foreign_table'];
- if ($row['foreign_schema'] != $default_schema) {
- $temp['foreign_table'] = $row['foreign_schema'] . '.' . $temp['foreign_table'];
- }
- $temp['foreign_column'] = $row['foreign_column'];
- $temp['on_delete'] = 'no_action';
- $temp['on_update'] = 'no_action';
-
- if (!empty($row['on_delete'])) {
- $temp['on_delete'] = $row['on_delete'];
- }
- if (!empty($row['on_update'])) {
- $temp['on_update'] = $row['on_update'];
- }
-
- } else {
- $temp[] = $row['column'];
- }
-
- $last_table = $row['table'];
- if ($row['schema'] != $default_schema) {
- $last_table = $row['schema'] . '.' . $last_table;
- }
- $last_name = $row['constraint_name'];
- $last_type = $row['type'];
-
- } else {
- $temp[] = $row['column'];
- }
- }
-
- if (isset($temp)) {
- if ($last_type == 'foreign' || $last_type == 'unique') {
- $keys[$last_table][$last_type][] = $temp;
- } else {
- $keys[$last_table][$last_type] = $temp;
- }
- }
-
- return $keys;
- }
-
-
- /**
- * Gets the `PRIMARY KEY`, `FOREIGN KEY` and `UNIQUE` key constraints from the database
- *
- * @return void
- */
- private function fetchKeys()
- {
- if ($this->keys) {
- return;
- }
-
- switch ($this->database->getType()) {
- case 'db2':
- $keys = $this->fetchDB2Keys();
- break;
-
- case 'mssql':
- $keys = $this->fetchMSSQLKeys();
- break;
-
- case 'mysql':
- $keys = $this->fetchMySQLKeys();
- break;
-
- case 'oracle':
- $keys = $this->fetchOracleKeys();
- break;
-
- case 'postgresql':
- $keys = $this->fetchPostgreSQLKeys();
- break;
-
- case 'sqlite':
- $keys = $this->fetchSQLiteKeys();
- break;
- }
-
- $this->keys = $keys;
- if ($this->cache) {
- $this->cache->set($this->makeCachePrefix() . 'keys', $this->keys);
- }
- }
-
-
- /**
- * Gets the column info from a MSSQL database
- *
- * @param string $table The table to fetch the column info for
- * @return array The column info for the table specified - see ::getColumnInfo() for details
- */
- private function fetchMSSQLColumnInfo($table)
- {
- $column_info = array();
-
- $schema = 'dbo';
- if (strpos($table, '.') !== FALSE) {
- list ($schema, $table) = explode('.', $table);
- }
-
- $data_type_mapping = array(
- 'bit' => 'boolean',
- 'tinyint' => 'integer',
- 'smallint' => 'integer',
- 'int' => 'integer',
- 'bigint' => 'integer',
- 'timestamp' => 'integer',
- 'datetime' => 'timestamp',
- 'smalldatetime' => 'timestamp',
- 'datetime2' => 'timestamp',
- 'date' => 'date',
- 'time' => 'time',
- 'varchar' => 'varchar',
- 'nvarchar' => 'varchar',
- 'uniqueidentifier' => 'varchar',
- 'char' => 'char',
- 'nchar' => 'char',
- 'real' => 'float',
- 'float' => 'float',
- 'money' => 'float',
- 'smallmoney' => 'float',
- 'decimal' => 'float',
- 'numeric' => 'float',
- 'binary' => 'blob',
- 'varbinary' => 'blob',
- 'image' => 'blob',
- 'text' => 'text',
- 'ntext' => 'text',
- 'xml' => 'text'
- );
-
- $max_min_values = array(
- 'tinyint' => array('min' => new fNumber(0), 'max' => new fNumber(255)),
- 'smallint' => array('min' => new fNumber(-32768), 'max' => new fNumber(32767)),
- 'int' => array('min' => new fNumber(-2147483648), 'max' => new fNumber(2147483647)),
- 'bigint' => array('min' => new fNumber('-9223372036854775808'), 'max' => new fNumber('9223372036854775807')),
- 'smallmoney' => array('min' => new fNumber('-214748.3648'), 'max' => new fNumber('214748.3647')),
- 'money' => array('min' => new fNumber('-922337203685477.5808'), 'max' => new fNumber('922337203685477.5807'))
- );
-
- // Get the column info
- $sql = "SELECT
- LOWER(c.column_name) AS 'column',
- c.data_type AS 'type',
- c.is_nullable AS nullable,
- c.column_default AS 'default',
- c.character_maximum_length AS max_length,
- c.numeric_precision AS precision,
- c.numeric_scale AS decimal_places,
- CASE
- WHEN
- COLUMNPROPERTY(OBJECT_ID(QUOTENAME(c.table_schema) + '.' + QUOTENAME(c.table_name)), c.column_name, 'IsIdentity') = 1 AND
- OBJECTPROPERTY(OBJECT_ID(QUOTENAME(c.table_schema) + '.' + QUOTENAME(c.table_name)), 'IsMSShipped') = 0
- THEN '1'
- ELSE '0'
- END AS auto_increment,
- cc.check_clause AS 'constraint',
- CAST(ex.value AS VARCHAR(7500)) AS 'comment'
- FROM
- INFORMATION_SCHEMA.COLUMNS AS c LEFT JOIN
- INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu ON
- c.column_name = ccu.column_name AND
- c.table_name = ccu.table_name AND
- c.table_catalog = ccu.table_catalog LEFT JOIN
- INFORMATION_SCHEMA.CHECK_CONSTRAINTS AS cc ON
- ccu.constraint_name = cc.constraint_name AND
- ccu.constraint_catalog = cc.constraint_catalog";
-
- if (version_compare($this->database->getVersion(), 9, '<')) {
- $sql .= " LEFT JOIN sysproperties AS ex ON ex.id = OBJECT_ID(QUOTENAME(c.table_schema) + '.' + QUOTENAME(c.table_name)) AND ex.smallid = COLUMNPROPERTY(OBJECT_ID(QUOTENAME(c.table_schema) + '.' + QUOTENAME(c.table_name)), c.column_name, 'ColumnId') AND ex.name = 'MS_Description' AND OBJECTPROPERTY(OBJECT_ID(QUOTENAME(c.table_schema) + '.' + QUOTENAME(c.table_name)), 'IsMsShipped') = 0 ";
- } else {
- $sql .= " LEFT JOIN SYS.EXTENDED_PROPERTIES AS ex ON ex.major_id = OBJECT_ID(QUOTENAME(c.table_schema) + '.' + QUOTENAME(c.table_name)) AND ex.minor_id = COLUMNPROPERTY(OBJECT_ID(QUOTENAME(c.table_schema) + '.' + QUOTENAME(c.table_name)), c.column_name, 'ColumnId') AND ex.name = 'MS_Description' AND OBJECTPROPERTY(OBJECT_ID(QUOTENAME(c.table_schema) + '.' + QUOTENAME(c.table_name)), 'IsMsShipped') = 0 ";
- }
-
- $sql .= "
- WHERE
- LOWER(c.table_name) = %s AND
- LOWER(c.table_schema) = %s AND
- c.table_catalog = DB_NAME()";
-
- $result = $this->database->query($sql, strtolower($table), strtolower($schema));
-
- foreach ($result as $row) {
-
- $info = array();
-
- foreach ($data_type_mapping as $data_type => $mapped_data_type) {
- if (stripos($row['type'], $data_type) === 0) {
- if (isset($max_min_values[$data_type])) {
- $info['min_value'] = $max_min_values[$data_type]['min'];
- $info['max_value'] = $max_min_values[$data_type]['max'];
- }
- $info['type'] = $mapped_data_type;
- break;
- }
- }
-
- // Handle decimal places and min/max for numeric/decimals
- if (in_array(strtolower($row['type']), array('decimal', 'numeric'))) {
- $info['decimal_places'] = $row['decimal_places'];
- $before_digits = str_pad('', $row['precision'] - $row['decimal_places'], '9');
- $after_digits = str_pad('', $row['decimal_places'], '9');
- $max_min = $before_digits . ($after_digits ? '.' : '') . $after_digits;
- $info['min_value'] = new fNumber('-' . $max_min);
- $info['max_value'] = new fNumber($max_min);
- }
-
- if (!isset($info['type'])) {
- $info['type'] = $row['type'];
- }
-
- // Handle decimal places for money/smallmoney
- if (in_array($row['type'], array('money', 'smallmoney'))) {
- $info['decimal_places'] = 2;
- }
-
- // Handle the special data for varchar columns
- if (in_array($info['type'], array('char', 'varchar', 'text', 'blob'))) {
- if ($row['type'] == 'uniqueidentifier') {
- $row['max_length'] = 32;
- } elseif ($row['max_length'] == -1) {
- $row['max_length'] = $row['type'] == 'nvarchar' ? 1073741823 : 2147483647;
- }
- $info['max_length'] = $row['max_length'];
- }
-
- // If the column has a constraint, look for valid values
- if (in_array($info['type'], array('char', 'varchar')) && !empty($row['constraint'])) {
- if (preg_match('#^\(((?:(?: OR )?\[[^\]]+\]\s*=\s*\'(?:\'\'|[^\']+)*\')+)\)$#D', $row['constraint'], $matches)) {
- $valid_values = explode(' OR ', $matches[1]);
- foreach ($valid_values as $key => $value) {
- $value = preg_replace('#^\s*\[' . preg_quote($row['column'], '#') . '\]\s*=\s*\'(.*)\'\s*$#', '\1', $value);
- $valid_values[$key] = str_replace("''", "'", $value);
- }
- // SQL Server turns CHECK constraint values into a reversed list, so we fix it here
- $info['valid_values'] = array_reverse($valid_values);
- }
- }
-
- // Handle auto increment
- if ($row['auto_increment']) {
- $info['auto_increment'] = TRUE;
- }
-
- // Handle default values
- if ($row['default'] !== NULL) {
- if ($row['default'] == '(getdate())') {
- $info['default'] = 'CURRENT_TIMESTAMP';
- } elseif (in_array($info['type'], array('char', 'varchar', 'text', 'timestamp')) ) {
- $info['default'] = substr($row['default'], 2, -2);
- } elseif ($info['type'] == 'boolean') {
- $info['default'] = (boolean) substr($row['default'], 2, -2);
- } elseif (in_array($info['type'], array('integer', 'float')) ) {
- $info['default'] = str_replace(array('(', ')'), '', $row['default']);
- } else {
- $info['default'] = pack('H*', substr($row['default'], 3, -1));
- }
- }
-
- // Handle not null
- $info['not_null'] = ($row['nullable'] == 'NO') ? TRUE : FALSE;
-
- $info['comment'] = $row['comment'];
-
- $column_info[$row['column']] = $info;
- }
-
- return $column_info;
- }
-
-
- /**
- * Fetches the key info for an MSSQL database
- *
- * @return array The key info arrays for every table in the database - see ::getKeys() for details
- */
- private function fetchMSSQLKeys()
- {
- $keys = array();
-
- $tables = $this->getTables();
- foreach ($tables as $table) {
- $keys[$table] = array();
- $keys[$table]['primary'] = array();
- $keys[$table]['unique'] = array();
- $keys[$table]['foreign'] = array();
- }
-
- $sql = "SELECT
- LOWER(c.table_schema) AS \"schema\",
- LOWER(c.table_name) AS \"table\",
- kcu.constraint_name AS constraint_name,
- CASE c.constraint_type
- WHEN 'PRIMARY KEY' THEN 'primary'
- WHEN 'FOREIGN KEY' THEN 'foreign'
- WHEN 'UNIQUE' THEN 'unique'
- END AS 'type',
- LOWER(kcu.column_name) AS 'column',
- LOWER(ccu.table_schema) AS foreign_schema,
- LOWER(ccu.table_name) AS foreign_table,
- LOWER(ccu.column_name) AS foreign_column,
- REPLACE(LOWER(rc.delete_rule), ' ', '_') AS on_delete,
- REPLACE(LOWER(rc.update_rule), ' ', '_') AS on_update
- FROM
- INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS c INNER JOIN
- INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON
- c.table_name = kcu.table_name AND
- c.constraint_name = kcu.constraint_name LEFT JOIN
- INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc ON
- c.constraint_name = rc.constraint_name LEFT JOIN
- INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu ON
- ccu.constraint_name = rc.unique_constraint_name
- WHERE
- c.constraint_catalog = DB_NAME() AND
- c.table_name != 'sysdiagrams'
- ORDER BY
- LOWER(c.table_schema),
- LOWER(c.table_name),
- c.constraint_type,
- LOWER(kcu.constraint_name),
- kcu.ordinal_position,
- LOWER(kcu.column_name)";
-
- $result = $this->database->query($sql);
-
- $last_name = '';
- $last_table = '';
- $last_type = '';
- foreach ($result as $row) {
-
- if ($row['constraint_name'] != $last_name) {
-
- if ($last_name) {
- if ($last_type == 'foreign' || $last_type == 'unique') {
- if (!isset($keys[$last_table][$last_type])) {
- $keys[$last_table][$last_type] = array();
- }
- $keys[$last_table][$last_type][] = $temp;
- } else {
- $keys[$last_table][$last_type] = $temp;
- }
- }
-
- $temp = array();
- if ($row['type'] == 'foreign') {
-
- $temp['column'] = $row['column'];
- $temp['foreign_table'] = $row['foreign_table'];
- if ($row['foreign_schema'] != 'dbo') {
- $temp['foreign_table'] = $row['foreign_schema'] . '.' . $temp['foreign_table'];
- }
- $temp['foreign_column'] = $row['foreign_column'];
- $temp['on_delete'] = 'no_action';
- $temp['on_update'] = 'no_action';
- if (!empty($row['on_delete'])) {
- $temp['on_delete'] = $row['on_delete'];
- }
- if (!empty($row['on_update'])) {
- $temp['on_update'] = $row['on_update'];
- }
-
- } else {
- $temp[] = $row['column'];
- }
-
- $last_table = $row['table'];
- if ($row['schema'] != 'dbo') {
- $last_table = $row['schema'] . '.' . $last_table;
- }
- $last_name = $row['constraint_name'];
- $last_type = $row['type'];
-
- } else {
- $temp[] = $row['column'];
- }
- }
-
- if (isset($temp)) {
- if ($last_type == 'foreign' || $last_type == 'unique') {
- if (!isset($keys[$last_table][$last_type])) {
- $keys[$last_table][$last_type] = array();
- }
- $keys[$last_table][$last_type][] = $temp;
- } else {
- $keys[$last_table][$last_type] = $temp;
- }
- }
-
- return $keys;
- }
-
-
- /**
- * Gets the column info from a MySQL database
- *
- * @param string $table The table to fetch the column info for
- * @return array The column info for the table specified - see ::getColumnInfo() for details
- */
- private function fetchMySQLColumnInfo($table)
- {
- $data_type_mapping = array(
- 'tinyint' => 'integer',
- 'smallint' => 'integer',
- 'mediumint' => 'integer',
- 'int' => 'integer',
- 'bigint' => 'integer',
- 'datetime' => 'timestamp',
- 'timestamp' => 'timestamp',
- 'date' => 'date',
- 'time' => 'time',
- 'enum' => 'varchar',
- 'set' => 'varchar',
- 'varchar' => 'varchar',
- 'char' => 'char',
- 'float' => 'float',
- 'double' => 'float',
- 'decimal' => 'float',
- 'binary' => 'blob',
- 'varbinary' => 'blob',
- 'tinyblob' => 'blob',
- 'blob' => 'blob',
- 'mediumblob' => 'blob',
- 'longblob' => 'blob',
- 'tinytext' => 'text',
- 'text' => 'text',
- 'mediumtext' => 'text',
- 'longtext' => 'text',
- 'geometry' => 'blob',
- 'point' => 'blob',
- 'linestring' => 'blob',
- 'polygon' => 'blob'
- );
-
- $max_min_values = array(
- 'tinyint' => array('min' => new fNumber(-128), 'max' => new fNumber(127)),
- 'unsigned tinyint' => array('min' => new fNumber(0), 'max' => new fNumber(255)),
- 'smallint' => array('min' => new fNumber(-32768), 'max' => new fNumber(32767)),
- 'unsigned smallint' => array('min' => new fNumber(0), 'max' => new fNumber(65535)),
- 'mediumint' => array('min' => new fNumber(-8388608), 'max' => new fNumber(8388607)),
- 'unsigned mediumint' => array('min' => new fNumber(0), 'max' => new fNumber(16777215)),
- 'int' => array('min' => new fNumber(-2147483648), 'max' => new fNumber(2147483647)),
- 'unsigned int' => array('min' => new fNumber(0), 'max' => new fNumber('4294967295')),
- 'bigint' => array('min' => new fNumber('-9223372036854775808'), 'max' => new fNumber('9223372036854775807')),
- 'unsigned bigint' => array('min' => new fNumber(0), 'max' => new fNumber('18446744073709551615'))
- );
-
- $column_info = array();
-
- $result = $this->database->query('SHOW CREATE TABLE %r', $table);
-
- try {
- $row = $result->fetchRow();
- $create_sql = $row['Create Table'];
- } catch (fNoRowsException $e) {
- return array();
- }
-
- preg_match_all('#(?<=,|\()\s+(?:"|\`)(\w+)(?:"|\`)\s+(?:([a-z]+)(?:\(([^)]+)\))?( unsigned)?(?: zerofill)?)(?: character set [^ ]+)?(?: collate [^ ]+)?(?: NULL)?( NOT NULL)?(?: DEFAULT ((?:[^, \'\n]*|\'(?:\'\'|[^\']+)*\')))?( auto_increment)?( COMMENT \'(?:\'\'|[^\']+)*\')?( ON UPDATE CURRENT_TIMESTAMP)?\s*(?:,|\s*(?=\)))#mi', $create_sql, $matches, PREG_SET_ORDER);
-
- foreach ($matches as $match) {
-
- $info = array();
-
- foreach ($data_type_mapping as $data_type => $mapped_data_type) {
- if (stripos($match[2], $data_type) === 0) {
- if ($match[2] == 'tinyint' && $match[3] == 1) {
- $mapped_data_type = 'boolean';
-
- } elseif (preg_match('#((?:unsigned )?(?:tiny|small|medium|big)?int)#', (isset($match[4]) ? $match[4] . ' ' : '') . $data_type, $int_match)) {
- if (isset($max_min_values[$int_match[1]])) {
- $info['min_value'] = $max_min_values[$int_match[1]]['min'];
- $info['max_value'] = $max_min_values[$int_match[1]]['max'];
- }
- }
-
- $info['type'] = $mapped_data_type;
- break;
- }
- }
- if (!isset($info['type'])) {
- $info['type'] = preg_replace('#^([a-z ]+).*$#iD', '\1', $match[2]);
- }
-
- switch ($match[2]) {
- case 'tinyblob':
- case 'tinytext':
- $info['max_length'] = 255;
- break;
-
- case 'blob':
- case 'text':
- $info['max_length'] = 65535;
- break;
-
- case 'mediumblob':
- case 'mediumtext':
- $info['max_length'] = 16777215;
- break;
-
- case 'longblob':
- case 'longtext':
- $info['max_length'] = 4294967295;
- break;
- }
-
- if (stripos($match[2], 'enum') === 0) {
- $info['valid_values'] = preg_replace("/^'|'\$/D", '', explode(",", $match[3]));
- $match[3] = 0;
- foreach ($info['valid_values'] as $valid_value) {
- if (strlen(utf8_decode($valid_value)) > $match[3]) {
- $match[3] = strlen(utf8_decode($valid_value));
- }
- }
- }
-
- // The set data type is currently only supported as a varchar
- // with a max length of all valid values concatenated by ,s
- if (stripos($match[2], 'set') === 0) {
- $values = preg_replace("/^'|'\$/D", '', explode(",", $match[3]));
- $match[3] = strlen(join(',', $values));
- }
-
- // Type specific information
- if (in_array($info['type'], array('char', 'varchar'))) {
- $info['max_length'] = $match[3];
- }
-
- // Grab the number of decimal places
- if (stripos($match[2], 'decimal') === 0) {
- if (preg_match('#^\s*(\d+)\s*,\s*(\d+)\s*$#D', $match[3], $data_type_info)) {
- $info['decimal_places'] = $data_type_info[2];
- $before_digits = str_pad('', $data_type_info[1] - $info['decimal_places'], '9');
- $after_digits = str_pad('', $info['decimal_places'], '9');
- $max_min = $before_digits . ($after_digits ? '.' : '') . $after_digits;
- $info['min_value'] = new fNumber('-' . $max_min);
- $info['max_value'] = new fNumber($max_min);
- }
- }
-
- // Not null
- $info['not_null'] = (!empty($match[5])) ? TRUE : FALSE;
-
- // Default values
- if (!empty($match[6]) && $match[6] != 'NULL') {
- $info['default'] = preg_replace("/^'|'\$/D", '', $match[6]);
- }
-
- if ($info['type'] == 'boolean' && isset($info['default'])) {
- $info['default'] = (boolean) $info['default'];
- }
-
- // Auto increment fields
- if (!empty($match[7])) {
- $info['auto_increment'] = TRUE;
- }
-
- // Column comments
- if (!empty($match[8])) {
- $info['comment'] = str_replace("''", "'", substr($match[8], 10, -1));
- }
-
- $column_info[strtolower($match[1])] = $info;
- }
-
- return $column_info;
- }
-
-
- /**
- * Fetches the keys for a MySQL database
- *
- * @return array The keys arrays for every table in the database - see ::getKeys() for details
- */
- private function fetchMySQLKeys()
- {
- $tables = $this->getTables();
- $keys = array();
-
- foreach ($tables as $table) {
-
- $keys[$table] = array();
- $keys[$table]['primary'] = array();
- $keys[$table]['foreign'] = array();
- $keys[$table]['unique'] = array();
-
- $result = $this->database->query('SHOW CREATE TABLE %r', $table);
- $row = $result->fetchRow();
-
- // Primary keys
- preg_match_all('/PRIMARY KEY\s+\("(.*?)"\),?\n/U', $row['Create Table'], $matches, PREG_SET_ORDER);
- if (!empty($matches)) {
- $keys[$table]['primary'] = explode('","', strtolower($matches[0][1]));
- }
-
- // Unique keys
- preg_match_all('/UNIQUE KEY\s+"([^"]+)"\s+\("(.*?)"\),?\n/U', $row['Create Table'], $matches, PREG_SET_ORDER);
- foreach ($matches as $match) {
- $keys[$table]['unique'][] = explode('","', strtolower($match[2]));
- }
-
- // Foreign keys
- preg_match_all('#FOREIGN KEY \("([^"]+)"\) REFERENCES "([^"]+)" \("([^"]+)"\)(?:\sON\sDELETE\s(SET\sNULL|SET\sDEFAULT|CASCADE|NO\sACTION|RESTRICT))?(?:\sON\sUPDATE\s(SET\sNULL|SET\sDEFAULT|CASCADE|NO\sACTION|RESTRICT))?#', $row['Create Table'], $matches, PREG_SET_ORDER);
- foreach ($matches as $match) {
- $temp = array(
- 'column' => strtolower($match[1]),
- 'foreign_table' => strtolower($match[2]),
- 'foreign_column' => strtolower($match[3]),
- 'on_delete' => 'no_action',
- 'on_update' => 'no_action'
- );
- if (!empty($match[4])) {
- $temp['on_delete'] = strtolower(str_replace(' ', '_', $match[4]));
- }
- if (!empty($match[5])) {
- $temp['on_update'] = strtolower(str_replace(' ', '_', $match[5]));
- }
- $keys[$table]['foreign'][] = $temp;
- }
- }
-
- return $keys;
- }
-
-
- /**
- * Gets the column info from an Oracle database
- *
- * @param string $table The table to fetch the column info for
- * @return array The column info for the table specified - see ::getColumnInfo() for details
- */
- private function fetchOracleColumnInfo($table)
- {
- $table = strtolower($table);
-
- $schema = strtolower($this->database->getUsername());
- if (strpos($table, '.') !== FALSE) {
- list ($schema, $table) = explode('.', $table);
- }
-
- $column_info = array();
-
- $data_type_mapping = array(
- 'boolean' => 'boolean',
- 'number' => 'integer',
- 'integer' => 'integer',
- 'timestamp' => 'timestamp',
- 'date' => 'date',
- 'varchar2' => 'varchar',
- 'nvarchar2' => 'varchar',
- 'char' => 'char',
- 'nchar' => 'char',
- 'float' => 'float',
- 'binary_float' => 'float',
- 'binary_double' => 'float',
- 'blob' => 'blob',
- 'bfile' => 'varchar',
- 'clob' => 'text',
- 'nclob' => 'text'
- );
-
- $sql = "SELECT
- LOWER(ATC.COLUMN_NAME) COLUMN_NAME,
- CASE
- WHEN
- ATC.DATA_TYPE = 'NUMBER' AND
- ATC.DATA_PRECISION IS NULL AND
- ATC.DATA_SCALE = 0
- THEN
- 'integer'
- WHEN
- ATC.DATA_TYPE = 'NUMBER' AND
- ATC.DATA_PRECISION = 1 AND
- ATC.DATA_SCALE = 0
- THEN
- 'boolean'
- WHEN
- ATC.DATA_TYPE = 'NUMBER' AND
- ATC.DATA_PRECISION IS NOT NULL AND
- ATC.DATA_SCALE != 0 AND
- ATC.DATA_SCALE IS NOT NULL
- THEN
- 'float'
- ELSE
- LOWER(ATC.DATA_TYPE)
- END DATA_TYPE,
- CASE
- WHEN
- ATC.CHAR_LENGTH <> 0
- THEN
- ATC.CHAR_LENGTH
- WHEN
- ATC.DATA_TYPE = 'NUMBER' AND
- ATC.DATA_PRECISION != 1 AND
- ATC.DATA_SCALE != 0 AND
- ATC.DATA_PRECISION IS NOT NULL
- THEN
- ATC.DATA_SCALE
- ELSE
- NULL
- END LENGTH,
- ATC.DATA_PRECISION PRECISION,
- ATC.NULLABLE,
- ATC.DATA_DEFAULT,
- AC.SEARCH_CONDITION CHECK_CONSTRAINT,
- ACCM.COMMENTS
- FROM
- ALL_TAB_COLUMNS ATC LEFT JOIN
- ALL_CONS_COLUMNS ACC ON
- ATC.OWNER = ACC.OWNER AND
- ATC.COLUMN_NAME = ACC.COLUMN_NAME AND
- ATC.TABLE_NAME = ACC.TABLE_NAME AND
- ACC.POSITION IS NULL LEFT JOIN
- ALL_CONSTRAINTS AC ON
- AC.OWNER = ACC.OWNER AND
- AC.CONSTRAINT_NAME = ACC.CONSTRAINT_NAME AND
- AC.CONSTRAINT_TYPE = 'C' AND
- AC.STATUS = 'ENABLED' LEFT JOIN
- ALL_COL_COMMENTS ACCM ON
- ATC.OWNER = ACCM.OWNER AND
- ATC.COLUMN_NAME = ACCM.COLUMN_NAME AND
- ATC.TABLE_NAME = ACCM.TABLE_NAME
- WHERE
- LOWER(ATC.TABLE_NAME) = %s AND
- LOWER(ATC.OWNER) = %s
- ORDER BY
- ATC.TABLE_NAME ASC,
- ATC.COLUMN_ID ASC";
-
- $result = $this->database->query($sql, $table, $schema);
-
- foreach ($result as $row) {
-
- $column = $row['column_name'];
-
- // Since Oracle stores check constraints in LONG columns, it is
- // not possible to check or modify the constraints in SQL which
- // ends up causing multiple rows with duplicate data except for
- // the check constraint
- $duplicate = FALSE;
-
- if (isset($column_info[$column])) {
- $info = $column_info[$column];
- $duplicate = TRUE;
- } else {
- $info = array();
- }
-
- if (!$duplicate) {
- // Get the column type
- foreach ($data_type_mapping as $data_type => $mapped_data_type) {
- if (stripos($row['data_type'], $data_type) === 0) {
- $info['type'] = $mapped_data_type;
- break;
- }
- }
-
- if (!isset($info['type'])) {
- $info['type'] = $row['data_type'];
- }
-
- if (in_array($info['type'], array('blob', 'text'))) {
- $info['max_length'] = 4294967295;
- }
-
- if ($row['data_type'] == 'float' && $row['precision']) {
- $row['length'] = (int) $row['length'];
- $before_digits = str_pad('', $row['precision'] - $row['length'], '9');
- $after_digits = str_pad('', $row['length'], '9');
- $max_min = $before_digits . ($after_digits ? '.' : '') . $after_digits;
- $info['min_value'] = new fNumber('-' . $max_min);
- $info['max_value'] = new fNumber($max_min);
- }
-
- // Handle the length of decimal/numeric fields
- if ($info['type'] == 'float' && $row['length']) {
- $info['decimal_places'] = (int) $row['length'];
- }
-
- // Handle the special data for varchar fields
- if (in_array($info['type'], array('char', 'varchar'))) {
- $info['max_length'] = (int) $row['length'];
- }
- }
-
- // Handle check constraints that are just simple lists
- if (in_array($info['type'], array('varchar', 'char')) && $row['check_constraint']) {
- if (preg_match('/^\s*"?' . preg_quote($column, '/') . '"?\s+in\s+\((.*?)\)\s*$/i', $row['check_constraint'], $match)) {
- if (preg_match_all("/(?<!')'((''|[^']+)*)'/", $match[1], $matches, PREG_PATTERN_ORDER)) {
- $info['valid_values'] = str_replace("''", "'", $matches[1]);
- }
- } elseif (preg_match('/^\s*"?' . preg_quote($column, '/') . '"?\s*=\s*\'((\'\'|[^\']+)*)\'(\s+OR\s+"?' . preg_quote($column, '/') . '"?\s*=\s*\'((\'\'|[^\']+)*)\')*\s*$/i', $row['check_constraint'], $match)) {
- if (preg_match_all("/(?<!')'((''|[^']+)*)'/", $row['check_constraint'], $matches, PREG_PATTERN_ORDER)) {
- $info['valid_values'] = str_replace("''", "'", $matches[1]);
- }
- }
- }
-
- if (!$duplicate) {
- // Handle default values
- if ($row['data_default'] !== NULL && trim($row['data_default']) != 'NULL') {
- if (…
Large files files are truncated, but you can click here to view the full file