You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
10KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\db\oci;
  8. use yii\base\InvalidCallException;
  9. use yii\db\Connection;
  10. use yii\db\TableSchema;
  11. use yii\db\ColumnSchema;
  12. /**
  13. * Schema is the class for retrieving metadata from an Oracle database
  14. *
  15. * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
  16. * sequence object. This property is read-only.
  17. *
  18. * @author Qiang Xue <qiang.xue@gmail.com>
  19. * @since 2.0
  20. */
  21. class Schema extends \yii\db\Schema
  22. {
  23. /**
  24. * @inheritdoc
  25. */
  26. public function init()
  27. {
  28. parent::init();
  29. if ($this->defaultSchema === null) {
  30. $this->defaultSchema = strtoupper($this->db->username);
  31. }
  32. }
  33. /**
  34. * @inheritdoc
  35. */
  36. public function releaseSavepoint($name)
  37. {
  38. // does nothing as Oracle does not support this
  39. }
  40. /**
  41. * @inheritdoc
  42. */
  43. public function quoteSimpleTableName($name)
  44. {
  45. return strpos($name, '"') !== false ? $name : '"' . $name . '"';
  46. }
  47. /**
  48. * @inheritdoc
  49. */
  50. public function createQueryBuilder()
  51. {
  52. return new QueryBuilder($this->db);
  53. }
  54. /**
  55. * @inheritdoc
  56. */
  57. public function loadTableSchema($name)
  58. {
  59. $table = new TableSchema();
  60. $this->resolveTableNames($table, $name);
  61. if ($this->findColumns($table)) {
  62. $this->findConstraints($table);
  63. return $table;
  64. } else {
  65. return null;
  66. }
  67. }
  68. /**
  69. * Resolves the table name and schema name (if any).
  70. *
  71. * @param TableSchema $table the table metadata object
  72. * @param string $name the table name
  73. */
  74. protected function resolveTableNames($table, $name)
  75. {
  76. $parts = explode('.', str_replace('"', '', $name));
  77. if (isset($parts[1])) {
  78. $table->schemaName = $parts[0];
  79. $table->name = $parts[1];
  80. } else {
  81. $table->schemaName = $this->defaultSchema;
  82. $table->name = $name;
  83. }
  84. $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
  85. }
  86. /**
  87. * Collects the table column metadata.
  88. * @param TableSchema $table the table schema
  89. * @return boolean whether the table exists
  90. */
  91. protected function findColumns($table)
  92. {
  93. $schemaName = $table->schemaName;
  94. $tableName = $table->name;
  95. $sql = <<<EOD
  96. SELECT a.column_name, a.data_type ||
  97. case
  98. when data_precision is not null
  99. then '(' || a.data_precision ||
  100. case when a.data_scale > 0 then ',' || a.data_scale else '' end
  101. || ')'
  102. when data_type = 'DATE' then ''
  103. when data_type = 'NUMBER' then ''
  104. else '(' || to_char(a.data_length) || ')'
  105. end as data_type,
  106. a.nullable, a.data_default,
  107. ( SELECT D.constraint_type
  108. FROM ALL_CONS_COLUMNS C
  109. inner join ALL_constraints D on D.OWNER = C.OWNER and D.constraint_name = C.constraint_name
  110. WHERE C.OWNER = B.OWNER
  111. and C.table_name = B.object_name
  112. and C.column_name = A.column_name
  113. and D.constraint_type = 'P') as Key,
  114. com.comments as column_comment
  115. FROM ALL_TAB_COLUMNS A
  116. inner join ALL_OBJECTS B ON b.owner = a.owner and ltrim(B.OBJECT_NAME) = ltrim(A.TABLE_NAME)
  117. LEFT JOIN all_col_comments com ON (A.owner = com.owner AND A.table_name = com.table_name AND A.column_name = com.column_name)
  118. WHERE
  119. a.owner = '{$schemaName}'
  120. and (b.object_type = 'TABLE' or b.object_type = 'VIEW')
  121. and b.object_name = '{$tableName}'
  122. ORDER by a.column_id
  123. EOD;
  124. try {
  125. $columns = $this->db->createCommand($sql)->queryAll();
  126. } catch (\Exception $e) {
  127. return false;
  128. }
  129. if (empty($columns)) {
  130. return false;
  131. }
  132. foreach ($columns as $column) {
  133. $c = $this->createColumn($column);
  134. $table->columns[$c->name] = $c;
  135. if ($c->isPrimaryKey) {
  136. $table->primaryKey[] = $c->name;
  137. $table->sequenceName = $this->getTableSequenceName($table->name);
  138. $c->autoIncrement = true;
  139. }
  140. }
  141. return true;
  142. }
  143. /**
  144. * Sequence name of table
  145. *
  146. * @param $tablename
  147. * @internal param \yii\db\TableSchema $table ->name the table schema
  148. * @return string whether the sequence exists
  149. */
  150. protected function getTableSequenceName($tablename){
  151. $seq_name_sql="select ud.referenced_name as sequence_name
  152. from user_dependencies ud
  153. join user_triggers ut on (ut.trigger_name = ud.name)
  154. where ut.table_name='{$tablename}'
  155. and ud.type='TRIGGER'
  156. and ud.referenced_type='SEQUENCE'";
  157. return $this->db->createCommand($seq_name_sql)->queryScalar();
  158. }
  159. /**
  160. * @Overrides method in class 'Schema'
  161. * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php -> Oracle does not support this
  162. *
  163. * Returns the ID of the last inserted row or sequence value.
  164. * @param string $sequenceName name of the sequence object (required by some DBMS)
  165. * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
  166. * @throws InvalidCallException if the DB connection is not active
  167. */
  168. public function getLastInsertID($sequenceName = '')
  169. {
  170. if ($this->db->isActive) {
  171. // get the last insert id from the master connection
  172. return $this->db->useMaster(function (Connection $db) use ($sequenceName) {
  173. return $db->createCommand("SELECT {$sequenceName}.CURRVAL FROM DUAL")->queryScalar();
  174. });
  175. } else {
  176. throw new InvalidCallException('DB Connection is not active.');
  177. }
  178. }
  179. /**
  180. * Creates ColumnSchema instance
  181. *
  182. * @param array $column
  183. * @return ColumnSchema
  184. */
  185. protected function createColumn($column)
  186. {
  187. $c = $this->createColumnSchema();
  188. $c->name = $column['COLUMN_NAME'];
  189. $c->allowNull = $column['NULLABLE'] === 'Y';
  190. $c->isPrimaryKey = strpos($column['KEY'], 'P') !== false;
  191. $c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT'];
  192. $this->extractColumnType($c, $column['DATA_TYPE']);
  193. $this->extractColumnSize($c, $column['DATA_TYPE']);
  194. $c->phpType = $this->getColumnPhpType($c);
  195. if (!$c->isPrimaryKey) {
  196. if (stripos($column['DATA_DEFAULT'], 'timestamp') !== false) {
  197. $c->defaultValue = null;
  198. } else {
  199. $c->defaultValue = $c->phpTypecast($column['DATA_DEFAULT']);
  200. }
  201. }
  202. return $c;
  203. }
  204. /**
  205. * Finds constraints and fills them into TableSchema object passed
  206. * @param TableSchema $table
  207. */
  208. protected function findConstraints($table)
  209. {
  210. $sql = <<<EOD
  211. SELECT D.constraint_type as CONSTRAINT_TYPE, C.COLUMN_NAME, C.position, D.r_constraint_name,
  212. E.table_name as table_ref, f.column_name as column_ref,
  213. C.table_name
  214. FROM ALL_CONS_COLUMNS C
  215. inner join ALL_constraints D on D.OWNER = C.OWNER and D.constraint_name = C.constraint_name
  216. left join ALL_constraints E on E.OWNER = D.r_OWNER and E.constraint_name = D.r_constraint_name
  217. left join ALL_cons_columns F on F.OWNER = E.OWNER and F.constraint_name = E.constraint_name and F.position = c.position
  218. WHERE C.OWNER = '{$table->schemaName}'
  219. and C.table_name = '{$table->name}'
  220. and D.constraint_type <> 'P'
  221. order by d.constraint_name, c.position
  222. EOD;
  223. $command = $this->db->createCommand($sql);
  224. foreach ($command->queryAll() as $row) {
  225. if ($row['CONSTRAINT_TYPE'] === 'R') {
  226. $name = $row["COLUMN_NAME"];
  227. $table->foreignKeys[$name] = [$row["TABLE_REF"], $row["COLUMN_REF"]];
  228. }
  229. }
  230. }
  231. /**
  232. * @inheritdoc
  233. */
  234. protected function findTableNames($schema = '')
  235. {
  236. if ($schema === '') {
  237. $sql = <<<EOD
  238. SELECT table_name, '{$schema}' as table_schema FROM user_tables
  239. EOD;
  240. $command = $this->db->createCommand($sql);
  241. } else {
  242. $sql = <<<EOD
  243. SELECT object_name as table_name, owner as table_schema FROM all_objects
  244. WHERE object_type = 'TABLE' AND owner=:schema
  245. EOD;
  246. $command = $this->db->createCommand($sql);
  247. $command->bindParam(':schema', $schema);
  248. }
  249. $rows = $command->queryAll();
  250. $names = [];
  251. foreach ($rows as $row) {
  252. $names[] = $row['TABLE_NAME'];
  253. }
  254. return $names;
  255. }
  256. /**
  257. * Extracts the data types for the given column
  258. * @param ColumnSchema $column
  259. * @param string $dbType DB type
  260. */
  261. protected function extractColumnType($column, $dbType)
  262. {
  263. $column->dbType = $dbType;
  264. if (strpos($dbType, 'FLOAT') !== false) {
  265. $column->type = 'double';
  266. } elseif (strpos($dbType, 'NUMBER') !== false || strpos($dbType, 'INTEGER') !== false) {
  267. if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) {
  268. $values = explode(',', $matches[1]);
  269. if (isset($values[1]) && (((int) $values[1]) > 0)) {
  270. $column->type = 'double';
  271. } else {
  272. $column->type = 'integer';
  273. }
  274. } else {
  275. $column->type = 'double';
  276. }
  277. } elseif (strpos($dbType, 'BLOB') !== false) {
  278. $column->type = 'binary';
  279. } elseif (strpos($dbType, 'CLOB') !== false) {
  280. $column->type = 'text';
  281. } else {
  282. $column->type = 'string';
  283. }
  284. }
  285. /**
  286. * Extracts size, precision and scale information from column's DB type.
  287. * @param ColumnSchema $column
  288. * @param string $dbType the column's DB type
  289. */
  290. protected function extractColumnSize($column, $dbType)
  291. {
  292. if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) {
  293. $values = explode(',', $matches[1]);
  294. $column->size = $column->precision = (int) $values[0];
  295. if (isset($values[1])) {
  296. $column->scale = (int) $values[1];
  297. }
  298. }
  299. }
  300. }