3 * TOracleMetaData class file.
5 * @author Marcos Nobre <marconobre[at]gmail[dot]com>
6 * @link https://github.com/pradosoft/prado
7 * @copyright Copyright © 2005-2016 The PRADO Group
8 * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
9 * @package System.Data.Common.Oracle
13 * Load the base TDbMetaData class.
15 Prado::using('System.Data.Common.TDbMetaData');
16 Prado::using('System.Data.Common.Oracle.TOracleTableInfo');
17 Prado::using('System.Data.Common.Oracle.TOracleTableColumn');
20 * TOracleMetaData loads Oracle database table and column information.
22 * @author Marcos Nobre <marconobre[at]gmail[dot]com>
23 * @package System.Data.Common.Oracle
26 class TOracleMetaData extends TDbMetaData
28 private $_defaultSchema = 'system';
32 * @return string TDbTableInfo class name.
34 protected function getTableInfoClass()
36 return 'TOracleTableInfo';
40 * @param string default schema.
42 public function setDefaultSchema($schema)
44 $this->_defaultSchema=$schema;
48 * @return string default schema.
50 public function getDefaultSchema()
52 return $this->_defaultSchema;
56 * @param string table name with optional schema name prefix, uses default schema name prefix is not provided.
57 * @return array tuple as ($schemaName,$tableName)
59 protected function getSchemaTableName($table)
61 if(count($parts= explode('.', str_replace('"','',$table))) > 1)
62 return array($parts[0], $parts[1]);
64 return array($this->getDefaultSchema(),$parts[0]);
68 * Get the column definitions for given table.
69 * @param string table name.
70 * @return TOracleTableInfo table information.
72 protected function createTableInfo($table)
74 list($schemaName,$tableName) = $this->getSchemaTableName($table);
76 // This query is made much more complex by the addition of the 'attisserial' field.
77 // The subquery to get that field checks to see if there is an internally dependent
78 // sequence on the field.
83 LOWER(a.COLUMN_NAME) as attname,
84 a.DATA_TYPE || DECODE( a.DATA_TYPE, 'NUMBER', '('||a.DATA_PRECISION||','||DATA_SCALE||')' , '') as type,
85 a.DATA_LENGTH as atttypmod,
86 DECODE(a.NULLABLE, 'Y', '0', '1') as attnotnull,
87 DECODE(a.DEFAULT_LENGTH, NULL, '0', '1') as atthasdef,
88 DATA_DEFAULT as adsrc,
93 TABLE_NAME = '{$tableName}'
94 AND OWNER = '{$schemaName}'
97 $this->getDbConnection()->setActive(true);
98 $this->getDbConnection()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
99 $command = $this->getDbConnection()->createCommand($sql);
100 //$command->bindValue(':table', $tableName);
101 //$command->bindValue(':schema', $schemaName);
102 $tableInfo = $this->createNewTableInfo($schemaName, $tableName);
104 foreach($command->query() as $col)
106 $col['index'] = $index++;
107 $this->processColumn($tableInfo, $col);
110 throw new TDbException('dbmetadata_invalid_table_view', $table);
115 * @param string table schema name
116 * @param string table name.
117 * @return TOracleTableInfo
119 protected function createNewTableInfo($schemaName,$tableName)
121 $info['SchemaName'] = $this->assertIdentifier($schemaName);
122 $info['TableName'] = $this->assertIdentifier($tableName);
123 $info['IsView'] = false;
124 if($this->getIsView($schemaName,$tableName)) $info['IsView'] = true;
125 list($primary, $foreign) = $this->getConstraintKeys($schemaName, $tableName);
126 $class = $this->getTableInfoClass();
127 return new $class($info,$primary,$foreign);
131 * @param string table name, schema name or column name.
132 * @return string a valid identifier.
133 * @throws TDbException when table name contains a double quote (").
135 protected function assertIdentifier($name)
137 if(strpos($name, '"')!==false)
139 $ref = 'http://www.oracle.com';
140 throw new TDbException('dbcommon_invalid_identifier_name', $name, $ref);
146 * @param string table schema name
147 * @param string table name.
148 * @return boolean true if the table is a view.
150 protected function getIsView($schemaName,$tableName)
152 $this->getDbConnection()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
157 where OBJECT_NAME = '{$tableName}'
158 and OWNER = '{$schemaName}'
160 $this->getDbConnection()->setActive(true);
161 $command = $this->getDbConnection()->createCommand($sql);
162 //$command->bindValue(':schema',$schemaName);
163 //$command->bindValue(':table', $tableName);
164 return intval($command->queryScalar() === 'VIEW');
168 * @param TOracleTableInfo table information.
169 * @param array column information.
171 protected function processColumn($tableInfo, $col)
173 $columnId = strtolower($col['attname']); //use column name as column Id
175 //$info['ColumnName'] = '"'.$columnId.'"'; //quote the column names!
176 $info['ColumnName'] = $columnId; //NOT quote the column names!
177 $info['ColumnId'] = $columnId;
178 $info['ColumnIndex'] = $col['index'];
179 if(! (bool)$col['attnotnull'] ) $info['AllowNull'] = true;
180 if(in_array($columnId, $tableInfo->getPrimaryKeys())) $info['IsPrimaryKey'] = true;
181 if($this->isForeignKeyColumn($columnId, $tableInfo)) $info['IsForeignKey'] = true;
182 if( (int)$col['atttypmod'] > 0 ) $info['ColumnSize'] = $col['atttypmod']; // - 4;
183 if( (bool)$col['atthasdef'] ) $info['DefaultValue'] = $col['adsrc'];
185 // For a while Oracle Tables has no associated AutoIncrement Triggers
188 if( $col['attisserial'] )
190 if(($sequence = $this->getSequenceName($tableInfo, $col['adsrc']))!==null)
192 $info['SequenceName'] = $sequence;
193 unset($info['DefaultValue']);
198 if(preg_match('/\((\d+)(?:,(\d+))?+\)/', $col['type'], $matches))
200 $info['DbType'] = preg_replace('/\(\d+(?:,\d+)?\)/','',$col['type']);
201 if($this->isPrecisionType($info['DbType']))
203 $info['NumericPrecision'] = intval($matches[1]);
204 if(count($matches) > 2)
205 $info['NumericScale'] = intval($matches[2]);
208 $info['ColumnSize'] = intval($matches[1]);
211 $info['DbType'] = $col['type'];
212 $tableInfo->Columns[$columnId] = new TOracleTableColumn($info);
216 * @return string serial name if found, null otherwise.
218 protected function getSequenceName($tableInfo,$src)
221 if(preg_match('/nextval\([^\']*\'([^\']+)\'[^\)]*\)/i',$src,$matches))
223 if(is_int(strpos($matches[1], '.')))
226 return $tableInfo->getSchemaName().'.'.$matches[1];
231 * @return boolean true if column type if "numeric", "interval" or begins with "time".
233 protected function isPrecisionType($type)
235 $type = strtolower(trim($type));
236 return $type==='number'; // || $type==='interval' || strpos($type, 'time')===0;
240 * Gets the primary and foreign key column details for the given table.
241 * @param string schema name
242 * @param string table name.
243 * @return array tuple ($primary, $foreign)
245 protected function getConstraintKeys($schemaName, $tableName)
247 $this->getDbConnection()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
248 // select decode( a.CONSTRAINT_TYPE, 'P', 'PRIMARY KEY (', 'FOREIGN KEY (' )||b.COLUMN_NAME||')' as consrc,
251 select b.COLUMN_NAME as consrc,
252 a.CONSTRAINT_TYPE as contype
253 from ALL_CONSTRAINTS a, ALL_CONS_COLUMNS b
254 where (a.constraint_name = b.constraint_name AND a.table_name = b.table_name AND a.owner = b.owner)
255 and a.TABLE_NAME = '{$tableName}'
256 and a.OWNER = '{$schemaName}'
257 and a.CONSTRAINT_TYPE in ('P','R')
259 $this->getDbConnection()->setActive(true);
260 $command = $this->getDbConnection()->createCommand($sql);
261 //$command->bindValue(':table', $tableName);
262 //$command->bindValue(':schema', $schemaName);
265 foreach($command->query() as $row)
267 switch( strtolower( $row['contype'] ) )
270 $primary = array_merge( $primary, array(strtolower( $row['consrc'] )) );
272 $arr = $this->getPrimaryKeys($row['consrc']);
273 $primary = array_merge( $primary, array(strtolower( $arr[0] )) );
277 $foreign = array_merge( $foreign, array(strtolower( $row['consrc'] )) );
279 // if(($fkey = $this->getForeignKeys($row['consrc']))!==null)
280 $fkey = $this->getForeignKeys( $row['consrc'] );
281 $foreign = array_merge( $foreign, array(strtolower( $fkey )) );
286 return array($primary,$foreign);
290 * Gets the primary key field names
291 * @param string Oracle primary key definition
292 * @return array primary key field names.
294 protected function getPrimaryKeys($src)
297 if(preg_match('/PRIMARY\s+KEY\s+\(([^\)]+)\)/i', $src, $matches))
298 return preg_split('/,\s+/',$matches[1]);
303 * Gets foreign relationship constraint keys and table name
304 * @param string Oracle foreign key definition
305 * @return array foreign relationship table name and keys, null otherwise
307 protected function getForeignKeys($src)
310 $brackets = '\(([^\)]+)\)';
311 $find = "/FOREIGN\s+KEY\s+{$brackets}\s+REFERENCES\s+([^\(]+){$brackets}/i";
312 if(preg_match($find, $src, $matches))
314 $keys = preg_split('/,\s+/', $matches[1]);
316 foreach(preg_split('/,\s+/', $matches[3]) as $i => $fkey)
317 $fkeys[$keys[$i]] = $fkey;
318 return array('table' => str_replace('"','',$matches[2]), 'keys' => $fkeys);
323 * @param string column name.
324 * @param TOracleTableInfo table information.
325 * @return boolean true if column is a foreign key.
327 protected function isForeignKeyColumn($columnId, $tableInfo)
329 foreach($tableInfo->getForeignKeys() as $fk)
332 //if(in_array($columnId, array_keys($fk['keys'])))
339 * Returns all table names in the database.
340 * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
341 * If not empty, the returned table names will be prefixed with the schema name.
342 * @return array all table names in the database.
344 public function findTableNames($schema='')
349 SELECT table_name, '{$schema}' as table_schema FROM user_tables
351 $command=$this->getDbConnection()->createCommand($sql);
356 SELECT object_name as table_name, owner as table_schema FROM all_objects
357 WHERE object_type = 'TABLE' AND owner=:schema
359 $command=$this->getDbConnection()->createCommand($sql);
360 $command->bindParam(':schema',$schema);
363 $rows=$command->queryAll();
365 foreach($rows as $row)
367 if($schema===$this->getDefaultSchema() || $schema==='')
368 $names[]=$row['TABLE_NAME'];
370 $names[]=$row['TABLE_SCHEMA'].'.'.$row['TABLE_NAME'];