1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @link http://www.yiiframework.com/ |
5
|
|
|
* @copyright Copyright (c) 2008 Yii Software LLC |
6
|
|
|
* @license http://www.yiiframework.com/license/ |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace edgardmessias\db\firebird; |
10
|
|
|
|
11
|
|
|
use yii\base\InvalidCallException; |
12
|
|
|
use yii\db\Exception; |
13
|
|
|
use yii\db\TableSchema; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Schema represents the Firebird schema information. |
17
|
|
|
* |
18
|
|
|
* @property string[] $indexNames All index names in the Firebird. This property is read-only. |
19
|
|
|
* @property IndexSchema[] $indexSchemas The metadata for all indexes in the Firebird. Each array element is an |
20
|
|
|
* instance of [[IndexSchema]] or its child class. This property is read-only. |
21
|
|
|
* @property array $indexTypes All index types in the Firebird in format: index name => index type. This |
22
|
|
|
* property is read-only. |
23
|
|
|
* @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only. |
24
|
|
|
* |
25
|
|
|
* @author Edgard Lorraine Messias <[email protected]> |
26
|
|
|
* @since 2.0 |
27
|
|
|
*/ |
28
|
|
|
class Schema extends \yii\db\Schema |
29
|
|
|
{ |
30
|
|
|
|
31
|
|
|
private $_lastInsertID = null; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var array map of DB errors and corresponding exceptions |
35
|
|
|
* If left part is found in DB error message exception class from the right part is used. |
36
|
|
|
*/ |
37
|
|
|
public $exceptionMap = [ |
38
|
|
|
'SQLSTATE[23' => 'yii\db\IntegrityException', |
39
|
|
|
'SQLSTATE[HY000]: General error: -803 violation of PRIMARY' => 'yii\db\IntegrityException', |
40
|
|
|
]; |
41
|
|
|
public $reservedWords = [ |
42
|
|
|
'ADD', |
43
|
|
|
'ADMIN', |
44
|
|
|
'ALL', |
45
|
|
|
'ALTER', |
46
|
|
|
'AND', |
47
|
|
|
'ANY', |
48
|
|
|
'AS', |
49
|
|
|
'AT', |
50
|
|
|
'AVG', |
51
|
|
|
'BEGIN', |
52
|
|
|
'BETWEEN', |
53
|
|
|
'BIGINT', |
54
|
|
|
'BIT_LENGTH', |
55
|
|
|
'BLOB', |
56
|
|
|
'BOTH', |
57
|
|
|
'BOOLEAN', |
58
|
|
|
'BY', |
59
|
|
|
'CASE', |
60
|
|
|
'CAST', |
61
|
|
|
'CHAR', |
62
|
|
|
'CHAR_LENGTH', |
63
|
|
|
'CHARACTER', |
64
|
|
|
'CHARACTER_LENGTH', |
65
|
|
|
'CHECK', |
66
|
|
|
'CLOSE', |
67
|
|
|
'COLLATE', |
68
|
|
|
'COLUMN', |
69
|
|
|
'COMMIT', |
70
|
|
|
'CONNECT', |
71
|
99 |
|
'CONSTRAINT', |
72
|
|
|
'CORR', |
73
|
99 |
|
'COUNT', |
74
|
|
|
'COVAR_POP', |
75
|
|
|
'CREATE', |
76
|
|
|
'CROSS', |
77
|
|
|
'CURRENT', |
78
|
|
|
'CURRENT_CONNECTION', |
79
|
2 |
|
'CURRENT_DATE', |
80
|
|
|
'CURRENT_ROLE', |
81
|
2 |
|
'CURRENT_TIME', |
82
|
|
|
'CURRENT_TIMESTAMP', |
83
|
|
|
'CURRENT_TRANSACTION', |
84
|
157 |
|
'CURRENT_USER', |
85
|
|
|
'CURSOR', |
86
|
157 |
|
'DATE', |
87
|
|
|
'DAY', |
88
|
|
|
'DEC', |
89
|
|
|
'DECIMAL', |
90
|
157 |
|
'DECLARE', |
91
|
157 |
|
'DEFAULT', |
92
|
43 |
|
'DELETE', |
93
|
|
|
'DELETING', |
94
|
|
|
'DETERMINISTIC', |
95
|
156 |
|
'DISCONNECT', |
96
|
|
|
'DISTINCT', |
97
|
|
|
'DOUBLE', |
98
|
173 |
|
'DROP', |
99
|
|
|
'ELSE', |
100
|
173 |
|
'END', |
101
|
6 |
|
'ESCAPE', |
102
|
|
|
'EXECUTE', |
103
|
173 |
|
'EXISTS', |
104
|
|
|
'EXTERNAL', |
105
|
|
|
'EXRACT', |
106
|
106 |
|
'FALSE', |
107
|
|
|
'FETCH', |
108
|
106 |
|
'FILTER', |
109
|
106 |
|
'FLOAT', |
110
|
106 |
|
'FOR', |
111
|
104 |
|
'FOREIGN', |
112
|
104 |
|
'FROM', |
113
|
|
|
'FULL', |
114
|
7 |
|
'FUNCTION', |
115
|
|
|
'GDSCODE', |
116
|
|
|
'GLOBAL', |
117
|
93 |
|
'GRANT', |
118
|
|
|
'GROUP', |
119
|
93 |
|
'HAVING', |
120
|
|
|
'HOUR', |
121
|
|
|
'IN', |
122
|
|
|
'INDEX', |
123
|
|
|
'INNER', |
124
|
|
|
'INSENSITIVE', |
125
|
|
|
'INSERT', |
126
|
|
|
'INSERTING', |
127
|
93 |
|
'INT', |
128
|
|
|
'INTEGER', |
129
|
93 |
|
'INTO', |
130
|
|
|
'IS', |
131
|
|
|
'JOIN', |
132
|
|
|
'LEADING', |
133
|
|
|
'LEFT', |
134
|
|
|
'LIKE', |
135
|
|
|
'LONG', |
136
|
|
|
'LOWER', |
137
|
106 |
|
'MAX', |
138
|
|
|
'MAXIMUM_SEGMENT', |
139
|
106 |
|
'MERGE', |
140
|
106 |
|
'MIN', |
141
|
|
|
'MINUTE', |
142
|
|
|
'MONTH', |
143
|
|
|
'NATIONAL', |
144
|
|
|
'NATURAL', |
145
|
106 |
|
'NCHAR', |
146
|
106 |
|
'NO', |
147
|
|
|
'NOT', |
148
|
106 |
|
'NULL', |
149
|
|
|
'NUMERIC', |
150
|
|
|
'OCTET_LENGTH', |
151
|
|
|
'OF', |
152
|
|
|
'OFFSET', |
153
|
|
|
'ON', |
154
|
|
|
'OPEN', |
155
|
|
|
'OR', |
156
|
106 |
|
'ORDER', |
157
|
|
|
'OUTER', |
158
|
|
|
'OVER', |
159
|
|
|
'PARAMETER', |
160
|
|
|
'PASSWORD', |
161
|
|
|
'PLAN', |
162
|
|
|
'POSITION', |
163
|
|
|
'POST_EVENT', |
164
|
|
|
'PRECISION', |
165
|
|
|
'PRIMARY', |
166
|
|
|
'PROCEDURE', |
167
|
|
|
'RDB$DB_KEY', |
168
|
|
|
'RDB$RECORD_VERSION', |
169
|
|
|
'REAL', |
170
|
|
|
'RECORD_VERSION', |
171
|
|
|
'RECREATE', |
172
|
|
|
'RECURSIVE', |
173
|
|
|
'REFERENCES', |
174
|
106 |
|
'REGR_AVGX', |
175
|
|
|
'REGR_AVGY', |
176
|
|
|
'REGR_COUNT', |
177
|
|
|
'REGR_INTERCEPT', |
178
|
|
|
'REGR_R2', |
179
|
|
|
'REGR_SLOPE', |
180
|
|
|
'REGR_SXX', |
181
|
|
|
'REGR_SXY', |
182
|
|
|
'REGR_SYY', |
183
|
|
|
'RELEASE', |
184
|
106 |
|
'RETURN', |
185
|
|
|
'RETURNING_VALUES', |
186
|
106 |
|
'RETURNS', |
187
|
|
|
'REVOKE', |
188
|
106 |
|
'RIGHT', |
189
|
106 |
|
'ROLLBACK', |
190
|
106 |
|
'ROW', |
191
|
|
|
'ROWS', |
192
|
|
|
'ROW_COUNT', |
193
|
|
|
'SAVEPOINT', |
194
|
|
|
'SCROLL', |
195
|
|
|
'SECOND', |
196
|
|
|
'SELECT', |
197
|
|
|
'SENSITIVE', |
198
|
|
|
'SET', |
199
|
|
|
'SIMILAR', |
200
|
|
|
'SOME', |
201
|
104 |
|
'SQLCODE', |
202
|
|
|
'SQLSTATE', |
203
|
104 |
|
'START', |
204
|
|
|
'STDDEV_POP', |
205
|
|
|
'STDDEV_SAMP', |
206
|
|
|
'SUM', |
207
|
104 |
|
'TABLE', |
208
|
104 |
|
'THEN', |
209
|
104 |
|
'TIME', |
210
|
104 |
|
'TIMESTAMP', |
211
|
104 |
|
'TO', |
212
|
|
|
'TRAILING', |
213
|
104 |
|
'TRIGGER', |
214
|
104 |
|
'TRIM', |
215
|
104 |
|
'TRUE', |
216
|
87 |
|
'UNION', |
217
|
87 |
|
'UNIQUE', |
218
|
79 |
|
'UNKNOWN', |
219
|
13 |
|
'UPDATE', |
220
|
13 |
|
'UPDATING', |
221
|
|
|
'UPPER', |
222
|
|
|
'USER', |
223
|
104 |
|
'USING', |
224
|
104 |
|
'VALUE', |
225
|
104 |
|
'VALUES', |
226
|
|
|
'VARCHAR', |
227
|
|
|
'VARIABLE', |
228
|
104 |
|
'VARYING', |
229
|
|
|
'VAR_POP', |
230
|
|
|
'VAR_SAMP', |
231
|
|
|
'VIEW', |
232
|
|
|
'WHEN', |
233
|
|
|
'WHERE', |
234
|
|
|
'WHILE', |
235
|
104 |
|
'WITH', |
236
|
|
|
'YEAR', |
237
|
104 |
|
]; |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @var array mapping from physical column types (keys) to abstract column types (values) |
241
|
|
|
*/ |
242
|
|
|
public $typeMap = [ |
243
|
|
|
'bigint' => self::TYPE_BIGINT, |
244
|
|
|
'char' => self::TYPE_CHAR, |
245
|
|
|
'varchar' => self::TYPE_STRING, |
246
|
104 |
|
'timestamp' => self::TYPE_TIMESTAMP, |
247
|
|
|
'decimal' => self::TYPE_DECIMAL, |
248
|
104 |
|
'float' => self::TYPE_FLOAT, |
249
|
104 |
|
'blob' => self::TYPE_BINARY, |
250
|
104 |
|
'integer' => self::TYPE_INTEGER, |
251
|
104 |
|
'blob sub_type text' => self::TYPE_TEXT, |
252
|
104 |
|
'numeric' => self::TYPE_DECIMAL, |
253
|
104 |
|
'double precision' => self::TYPE_DOUBLE, |
254
|
|
|
'smallint' => self::TYPE_SMALLINT, |
255
|
104 |
|
]; |
256
|
|
|
|
257
|
104 |
|
/** |
258
|
104 |
|
* Creates a query builder for the database. |
259
|
|
|
* This method may be overridden by child classes to create a DBMS-specific query builder. |
260
|
63 |
|
* @return QueryBuilder query builder instance |
261
|
63 |
|
*/ |
262
|
|
|
public function createQueryBuilder() |
263
|
|
|
{ |
264
|
63 |
|
return new QueryBuilder($this->db); |
265
|
|
|
} |
266
|
104 |
|
|
267
|
103 |
|
/** |
268
|
|
|
* @inheritdoc |
269
|
104 |
|
*/ |
270
|
|
|
public function createColumnSchemaBuilder($type, $length = null) |
271
|
104 |
|
{ |
272
|
|
|
return new ColumnSchemaBuilder($type, $length); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
public function quoteSimpleTableName($name) |
276
|
|
|
{ |
277
|
|
|
if ($this->db->tablePrefix !== '') { |
278
|
|
|
return $name; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
$word = strtoupper(str_replace('%', '', $name)); |
282
|
|
|
if (in_array($word, $this->reservedWords)) { |
283
|
|
|
return strpos($name, '"') !== false ? $name : '"' . $name . '"'; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
return $name; |
287
|
104 |
|
} |
288
|
|
|
|
289
|
|
|
public function quoteSimpleColumnName($name) |
290
|
104 |
|
{ |
291
|
104 |
|
if (in_array(strtoupper($name), $this->reservedWords)) { |
292
|
88 |
|
return parent::quoteSimpleColumnName($name); |
293
|
88 |
|
} |
294
|
88 |
|
return $name; |
295
|
88 |
|
} |
296
|
|
|
|
297
|
104 |
|
protected function loadTableSchema($name) |
298
|
104 |
|
{ |
299
|
104 |
|
$table = new TableSchema; |
300
|
104 |
|
$this->resolveTableNames($table, $name); |
301
|
104 |
|
if ($this->findColumns($table)) { |
302
|
|
|
$this->findConstraints($table); |
303
|
|
|
return $table; |
304
|
|
|
} |
305
|
|
|
return null; |
306
|
|
|
} |
307
|
104 |
|
|
308
|
16 |
|
public function getPdoType($data) |
309
|
16 |
|
{ |
310
|
16 |
|
static $typeMap = [ |
311
|
16 |
|
// php type => PDO type |
312
|
16 |
|
'boolean' => \PDO::PARAM_INT, |
313
|
|
|
'integer' => \PDO::PARAM_INT, |
314
|
104 |
|
'string' => \PDO::PARAM_STR, |
315
|
101 |
|
'resource' => \PDO::PARAM_LOB, |
316
|
47 |
|
'NULL' => \PDO::PARAM_NULL, |
317
|
47 |
|
]; |
318
|
3 |
|
$type = gettype($data); |
319
|
3 |
|
|
320
|
3 |
|
return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; |
321
|
3 |
|
} |
322
|
3 |
|
|
323
|
47 |
|
/** |
324
|
46 |
|
* |
325
|
46 |
|
* @param TableSchema $table |
326
|
46 |
|
* @param string $name |
327
|
46 |
|
*/ |
328
|
46 |
|
protected function resolveTableNames($table, $name) |
329
|
|
|
{ |
330
|
4 |
|
$parts = explode('.', str_replace('"', '', $name)); |
331
|
4 |
|
if (isset($parts[1])) { |
332
|
|
|
$table->schemaName = $parts[0]; |
333
|
47 |
|
$table->name = strtolower($parts[1]); |
334
|
89 |
|
$table->fullName = $this->quoteTableName($table->schemaName) . '.' . $this->quoteTableName($table->name); |
335
|
15 |
|
} else { |
336
|
15 |
|
$table->name = strtolower($parts[0]); |
337
|
15 |
|
$table->fullName = $this->quoteTableName($table->name); |
338
|
15 |
|
} |
339
|
15 |
|
} |
340
|
|
|
|
341
|
15 |
|
/** |
342
|
|
|
* Collects the table column metadata. |
343
|
|
|
* |
344
|
104 |
|
* @param TableSchema $table the table metadata |
345
|
|
|
* @return boolean whether the table exists in the database |
346
|
104 |
|
*/ |
347
|
104 |
|
protected function findColumns($table) |
348
|
104 |
|
{ |
349
|
104 |
|
// Zoggo - Converted sql to use join syntax |
350
|
104 |
|
// robregonm - Added isAutoInc |
351
|
|
|
$sql = 'SELECT |
352
|
|
|
rel.rdb$field_name AS fname, |
353
|
|
|
rel.rdb$default_source AS fdefault, |
354
|
|
|
fld.rdb$field_type AS fcodtype, |
355
|
104 |
|
fld.rdb$field_sub_type AS fcodsubtype, |
356
|
|
|
fld.rdb$field_length AS flength, |
357
|
104 |
|
fld.rdb$character_length AS fcharlength, |
358
|
104 |
|
fld.rdb$field_scale AS fscale, |
359
|
63 |
|
fld.rdb$field_precision AS fprecision, |
360
|
63 |
|
rel.rdb$null_flag AS fnull, |
361
|
15 |
|
rel.rdb$description AS fcomment, |
362
|
|
|
fld.rdb$default_value AS fdefault_value,'; |
363
|
63 |
|
|
364
|
|
|
if (version_compare($this->db->firebird_version, '3.0.0', '>=')) { |
365
|
|
|
$sql .= ' |
366
|
|
|
rel.rdb$generator_name AS fgenerator_name,'; |
367
|
104 |
|
} |
368
|
|
|
|
369
|
|
|
$sql .= ' |
370
|
|
|
(SELECT RDB$TRIGGER_SOURCE FROM RDB$TRIGGERS |
371
|
|
|
WHERE RDB$SYSTEM_FLAG = 0 |
372
|
|
|
AND UPPER(RDB$RELATION_NAME)=UPPER(\'' . $table->name . '\') |
373
|
|
|
AND RDB$TRIGGER_TYPE = 1 |
374
|
|
|
AND RDB$TRIGGER_INACTIVE = 0 |
375
|
104 |
|
AND (UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=GEN_ID%\' |
376
|
|
|
OR UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=NEXTVALUEFOR%\')) |
377
|
|
|
AS fautoinc |
378
|
|
|
FROM |
379
|
|
|
rdb$relation_fields rel |
380
|
|
|
JOIN rdb$fields fld ON rel.rdb$field_source=fld.rdb$field_name |
381
|
|
|
WHERE |
382
|
|
|
UPPER(rel.rdb$relation_name)=UPPER(\'' . $table->name . '\') |
383
|
|
|
ORDER BY |
384
|
|
|
rel.rdb$field_position;'; |
385
|
|
|
try { |
386
|
|
|
$columns = $this->db->createCommand($sql)->queryAll(); |
387
|
|
|
if (empty($columns)) { |
388
|
|
|
return false; |
389
|
|
|
} |
390
|
|
|
} catch (Exception $e) { |
391
|
104 |
|
return false; |
392
|
|
|
} |
393
|
104 |
|
$sql = 'SELECT |
394
|
|
|
idx.rdb$field_name AS fname |
395
|
|
|
FROM |
396
|
|
|
rdb$relation_constraints rc |
397
|
|
|
JOIN rdb$index_segments idx ON idx.rdb$index_name=rc.rdb$index_name |
398
|
104 |
|
WHERE rc.rdb$constraint_type=\'PRIMARY KEY\' |
399
|
104 |
|
AND UPPER(rc.rdb$relation_name)=UPPER(\'' . $table->name . '\')'; |
400
|
|
|
try { |
401
|
|
|
$pkeys = $this->db->createCommand($sql)->queryColumn(); |
402
|
29 |
|
} catch (Exception $e) { |
403
|
29 |
|
return false; |
404
|
|
|
} |
405
|
29 |
|
$pkeys = array_map('rtrim', $pkeys); |
406
|
29 |
|
$pkeys = array_map('strtolower', $pkeys); |
407
|
29 |
|
foreach ($columns as $key => $column) { |
408
|
|
|
$column = array_map('strtolower', $column); |
409
|
|
|
$columns[$key]['fprimary'] = in_array(rtrim($column['fname']), $pkeys); |
410
|
29 |
|
} |
411
|
|
|
foreach ($columns as $column) { |
412
|
104 |
|
$c = $this->loadColumnSchema($column); |
413
|
104 |
|
if ($table->sequenceName === null && $c->autoIncrement) { |
414
|
|
|
$matches = []; |
415
|
5 |
|
|
416
|
|
|
if (isset($column['fgenerator_name']) && $column['fgenerator_name']) { |
417
|
|
|
$table->sequenceName = $column['fgenerator_name']; |
418
|
|
|
} elseif (preg_match("/NEW.{$c->name}\s*=\s*GEN_ID\((\w+)/i", $column['fautoinc'], $matches)) { |
419
|
|
|
$table->sequenceName = $matches[1]; |
420
|
|
|
} elseif (preg_match("/NEW.{$c->name}\s*=\s*NEXT\s+VALUE\s+FOR\s+(\w+)/i", $column['fautoinc'], $matches)) { |
421
|
|
|
$table->sequenceName = $matches[1]; |
422
|
5 |
|
} |
423
|
|
|
} |
424
|
5 |
|
$table->columns[$c->name] = $c; |
425
|
|
|
if ($c->isPrimaryKey) { |
426
|
|
|
$table->primaryKey[] = $c->name; |
427
|
|
|
} |
428
|
|
|
} |
429
|
5 |
|
return (count($table->columns) > 0); |
430
|
5 |
|
} |
431
|
|
|
|
432
|
5 |
|
/** |
433
|
|
|
* @return ColumnSchema |
434
|
|
|
* @throws \yii\base\InvalidConfigException |
435
|
|
|
*/ |
436
|
|
|
protected function createColumnSchema() |
437
|
|
|
{ |
438
|
|
|
return \Yii::createObject('\edgardmessias\db\firebird\ColumnSchema'); |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** |
442
|
|
|
* Creates a table column. |
443
|
|
|
* |
444
|
|
|
* @param array $column column metadata |
445
|
|
|
* @return ColumnSchema normalized column metadata |
446
|
|
|
*/ |
447
|
|
|
protected function loadColumnSchema($column) |
448
|
|
|
{ |
449
|
|
|
$c = $this->createColumnSchema(); |
450
|
1 |
|
$c->name = strtolower(rtrim($column['fname'])); |
451
|
|
|
$c->allowNull = (int) $column['fnull'] !== 1; |
452
|
|
|
$c->isPrimaryKey = $column['fprimary']; |
453
|
|
|
$c->autoIncrement = (isset($column['fgenerator_name']) && $column['fgenerator_name']) || (boolean) $column['fautoinc']; |
454
|
|
|
$c->comment = $column['fcomment'] === null ? '' : $column['fcomment']; |
455
|
|
|
|
456
|
|
|
$c->type = self::TYPE_STRING; |
457
|
|
|
|
458
|
1 |
|
$defaultValue = null; |
459
|
1 |
|
if (!empty($column['fdefault'])) { |
460
|
1 |
|
// remove whitespace, 'DEFAULT ' prefix and surrounding single quotes; all optional |
461
|
1 |
|
if (preg_match("/\s*(DEFAULT\s+){0,1}('(.*)'|(.*))\s*/i", $column['fdefault'], $parts)) { |
462
|
1 |
|
$defaultValue = array_pop($parts); |
463
|
1 |
|
} |
464
|
|
|
// handle escaped single quotes like in "funny''quoted''string" |
465
|
1 |
|
$defaultValue = str_replace('\'\'', '\'', $defaultValue); |
466
|
|
|
} |
467
|
|
|
if ($defaultValue === null) { |
468
|
|
|
$defaultValue = $column['fdefault_value']; |
469
|
|
|
} |
470
|
|
|
$dbType = ''; |
471
|
|
|
$baseTypes = [ |
472
|
|
|
7 => 'SMALLINT', |
473
|
|
|
8 => 'INTEGER', |
474
|
|
|
16 => 'INT64', |
475
|
|
|
9 => 'QUAD', |
476
|
2 |
|
10 => 'FLOAT', |
477
|
|
|
11 => 'D_FLOAT', |
478
|
2 |
|
17 => 'BOOLEAN', |
479
|
2 |
|
27 => 'DOUBLE PRECISION', |
480
|
1 |
|
12 => 'DATE', |
481
|
1 |
|
13 => 'TIME', |
482
|
1 |
|
35 => 'TIMESTAMP', |
483
|
1 |
|
261 => 'BLOB', |
484
|
|
|
40 => 'CSTRING', |
485
|
1 |
|
45 => 'BLOB_ID', |
486
|
|
|
]; |
487
|
2 |
|
$baseCharTypes = [ |
488
|
|
|
37 => 'VARCHAR', |
489
|
|
|
14 => 'CHAR', |
490
|
|
|
]; |
491
|
|
|
if (array_key_exists((int) $column['fcodtype'], $baseTypes)) { |
492
|
13 |
|
$dbType = $baseTypes[(int) $column['fcodtype']]; |
493
|
|
|
} elseif (array_key_exists((int) $column['fcodtype'], $baseCharTypes)) { |
494
|
13 |
|
$c->size = (int) $column['fcharlength']; |
495
|
13 |
|
$c->precision = $c->size; |
496
|
13 |
|
$dbType = $baseCharTypes[(int) $column['fcodtype']] . "($c->size)"; |
497
|
13 |
|
} |
498
|
13 |
|
switch ((int) $column['fcodtype']) { |
499
|
12 |
|
case 7: |
500
|
12 |
|
case 8: |
501
|
12 |
|
switch ((int) $column['fcodsubtype']) { |
502
|
|
|
case 1: |
503
|
12 |
|
$c->precision = (int) $column['fprecision']; |
504
|
|
|
$c->size = $c->precision; |
505
|
|
|
$c->scale = abs((int) $column['fscale']); |
|
|
|
|
506
|
13 |
|
$dbType = "NUMERIC({$c->precision},{$c->scale})"; |
507
|
13 |
|
break; |
508
|
13 |
|
case 2: |
509
|
|
|
$c->precision = (int) $column['fprecision']; |
510
|
13 |
|
$c->size = $c->precision; |
511
|
|
|
$c->scale = abs((int) $column['fscale']); |
512
|
|
|
$dbType = "DECIMAL({$c->precision},{$c->scale})"; |
513
|
13 |
|
break; |
514
|
12 |
|
} |
515
|
12 |
|
break; |
516
|
10 |
|
case 16: |
517
|
12 |
|
switch ((int) $column['fcodsubtype']) { |
518
|
|
|
case 1: |
519
|
|
|
$c->precision = (int) $column['fprecision']; |
520
|
|
|
$c->size = $c->precision; |
521
|
13 |
|
$c->scale = abs((int) $column['fscale']); |
522
|
|
|
$dbType = "NUMERIC({$c->precision},{$c->scale})"; |
523
|
|
|
break; |
524
|
|
|
case 2: |
525
|
|
|
$c->precision = (int) $column['fprecision']; |
526
|
|
|
$c->size = $c->precision; |
527
|
|
|
$c->scale = abs((int) $column['fscale']); |
528
|
2 |
|
$dbType = "DECIMAL({$c->precision},{$c->scale})"; |
529
|
|
|
break; |
530
|
2 |
|
default: |
531
|
|
|
$dbType = 'BIGINT'; |
532
|
|
|
break; |
533
|
|
|
} |
534
|
2 |
|
break; |
535
|
2 |
|
case 261: |
536
|
|
|
switch ((int) $column['fcodsubtype']) { |
537
|
|
|
case 1: |
538
|
1 |
|
$dbType = 'BLOB SUB_TYPE TEXT'; |
539
|
1 |
|
$c->size = null; |
540
|
|
|
break; |
541
|
|
|
} |
542
|
|
|
break; |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
$c->dbType = strtolower($dbType); |
546
|
|
|
|
547
|
|
|
$c->type = self::TYPE_STRING; |
548
|
|
|
if (preg_match('/^([\w\ ]+)(?:\(([^\)]+)\))?/', $c->dbType, $matches)) { |
549
|
|
|
$type = strtolower($matches[1]); |
550
|
|
|
if (isset($this->typeMap[$type])) { |
551
|
|
|
$c->type = $this->typeMap[$type]; |
552
|
|
|
} |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
|
556
|
|
|
$c->phpType = $this->getColumnPhpType($c); |
557
|
|
|
|
558
|
|
|
$c->defaultValue = null; |
559
|
|
|
if ($defaultValue !== null) { |
560
|
|
|
if (in_array($c->type, [self::TYPE_DATE, self::TYPE_DATETIME, self::TYPE_TIME, self::TYPE_TIMESTAMP]) |
561
|
|
|
&& preg_match('/(CURRENT_|NOW|NULL|TODAY|TOMORROW|YESTERDAY)/i', $defaultValue)) { |
562
|
|
|
$c->defaultValue = new \yii\db\Expression(trim($defaultValue)); |
563
|
|
|
} else { |
564
|
|
|
$c->defaultValue = $c->phpTypecast($defaultValue); |
565
|
|
|
} |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
return $c; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
/** |
572
|
|
|
* Collects the foreign key column details for the given table. |
573
|
|
|
* |
574
|
|
|
* @param TableSchema $table the table metadata |
575
|
|
|
*/ |
576
|
|
|
protected function findConstraints($table) |
577
|
|
|
{ |
578
|
|
|
// Zoggo - Converted sql to use join syntax |
579
|
|
|
$sql = 'SELECT |
580
|
|
|
a.rdb$constraint_name as fconstraint, |
581
|
|
|
c.rdb$relation_name AS ftable, |
582
|
|
|
d.rdb$field_name AS pfield, |
583
|
|
|
e.rdb$field_name AS ffield |
584
|
|
|
FROM |
585
|
|
|
rdb$ref_constraints b |
586
|
|
|
JOIN rdb$relation_constraints a ON a.rdb$constraint_name=b.rdb$constraint_name |
587
|
|
|
JOIN rdb$relation_constraints c ON b.rdb$const_name_uq=c.rdb$constraint_name |
588
|
|
|
JOIN rdb$index_segments d ON c.rdb$index_name=d.rdb$index_name |
589
|
|
|
JOIN rdb$index_segments e ON a.rdb$index_name=e.rdb$index_name AND e.rdb$field_position = d.rdb$field_position |
590
|
|
|
WHERE |
591
|
|
|
a.rdb$constraint_type=\'FOREIGN KEY\' AND |
592
|
|
|
UPPER(a.rdb$relation_name)=UPPER(\'' . $table->name . '\') '; |
593
|
|
|
try { |
594
|
|
|
$fkeys = $this->db->createCommand($sql)->queryAll(); |
595
|
|
|
} catch (Exception $e) { |
596
|
|
|
return false; |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
$constraints = []; |
600
|
|
|
foreach ($fkeys as $fkey) { |
601
|
|
|
// Zoggo - Added strtolower here to guarantee that values are |
602
|
|
|
// returned lower case. Otherwise gii generates wrong code. |
603
|
|
|
$fkey = array_map('rtrim', $fkey); |
604
|
|
|
$fkey = array_map('strtolower', $fkey); |
605
|
|
|
|
606
|
|
|
if (!isset($constraints[$fkey['fconstraint']])) { |
607
|
|
|
$constraints[$fkey['fconstraint']] = [ |
608
|
|
|
$fkey['ftable'] |
609
|
|
|
]; |
610
|
|
|
} |
611
|
|
|
$constraints[$fkey['fconstraint']][$fkey['ffield']] = $fkey['pfield']; |
612
|
|
|
} |
613
|
|
|
$table->foreignKeys = $constraints; |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
protected function findTableNames($schema = '') |
617
|
|
|
{ |
618
|
|
|
$sql = 'SELECT |
619
|
|
|
rdb$relation_name |
620
|
|
|
FROM |
621
|
|
|
rdb$relations |
622
|
|
|
WHERE |
623
|
|
|
(rdb$system_flag is null OR rdb$system_flag=0)'; |
624
|
|
|
try { |
625
|
|
|
$tables = $this->db->createCommand($sql)->queryColumn(); |
626
|
|
|
} catch (Exception $e) { |
627
|
|
|
return false; |
628
|
|
|
} |
629
|
|
|
|
630
|
|
|
$tables = array_map('rtrim', $tables); |
631
|
|
|
$tables = array_map('strtolower', $tables); |
632
|
|
|
|
633
|
|
|
return $tables; |
634
|
|
|
} |
635
|
|
|
|
636
|
|
|
/** |
637
|
|
|
* Returns all unique indexes for the given table. |
638
|
|
|
* Each array element is of the following structure: |
639
|
|
|
* |
640
|
|
|
* ~~~ |
641
|
|
|
* [ |
642
|
|
|
* 'IndexName1' => ['col1' [, ...]], |
643
|
|
|
* 'IndexName2' => ['col2' [, ...]], |
644
|
|
|
* ] |
645
|
|
|
* ~~~ |
646
|
|
|
* |
647
|
|
|
* @param TableSchema $table the table metadata |
648
|
|
|
* @return array all unique indexes for the given table. |
649
|
|
|
* @since 2.0.4 |
650
|
|
|
*/ |
651
|
|
|
public function findUniqueIndexes($table) |
652
|
|
|
{ |
653
|
|
|
$query = ' |
654
|
|
|
SELECT id.RDB$INDEX_NAME as index_name, ids.RDB$FIELD_NAME as column_name |
655
|
|
|
FROM RDB$INDICES id |
656
|
|
|
INNER JOIN RDB$INDEX_SEGMENTS ids ON ids.RDB$INDEX_NAME = id.RDB$INDEX_NAME |
657
|
|
|
WHERE id.RDB$UNIQUE_FLAG = 1 |
658
|
|
|
AND id.RDB$SYSTEM_FLAG = 0 |
659
|
|
|
AND UPPER(id.RDB$RELATION_NAME) = UPPER(\'' . $table->name . '\') |
660
|
|
|
ORDER BY id.RDB$RELATION_NAME, id.RDB$INDEX_NAME, ids.RDB$FIELD_POSITION'; |
661
|
|
|
$result = []; |
662
|
|
|
$command = $this->db->createCommand($query); |
663
|
|
|
foreach ($command->queryAll() as $row) { |
664
|
|
|
$result[strtolower(rtrim($row['index_name']))][] = strtolower(rtrim($row['column_name'])); |
665
|
|
|
} |
666
|
|
|
return $result; |
667
|
|
|
} |
668
|
|
|
|
669
|
|
|
/** |
670
|
|
|
* Sets the isolation level of the current transaction. |
671
|
|
|
* @param string $level The transaction isolation level to use for this transaction. |
672
|
|
|
* This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]] |
673
|
|
|
* and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used |
674
|
|
|
* after `SET TRANSACTION ISOLATION LEVEL`. |
675
|
|
|
* @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels |
676
|
|
|
*/ |
677
|
|
|
public function setTransactionIsolationLevel($level) |
678
|
|
|
{ |
679
|
|
|
if ($level == \yii\db\Transaction::READ_UNCOMMITTED) { |
680
|
|
|
parent::setTransactionIsolationLevel('READ COMMITTED RECORD_VERSION'); |
681
|
|
|
} elseif ($level == \yii\db\Transaction::REPEATABLE_READ) { |
682
|
|
|
parent::setTransactionIsolationLevel('SNAPSHOT'); |
683
|
|
|
} elseif ($level == \yii\db\Transaction::SERIALIZABLE) { |
684
|
|
|
parent::setTransactionIsolationLevel('SNAPSHOT TABLE STABILITY'); |
685
|
|
|
} else { |
686
|
|
|
parent::setTransactionIsolationLevel($level); |
687
|
|
|
} |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
/** |
691
|
|
|
* @inheritdoc |
692
|
|
|
*/ |
693
|
|
|
public function insert($table, $columns) |
694
|
|
|
{ |
695
|
|
|
$this->_lastInsertID = false; |
696
|
|
|
$params = []; |
697
|
|
|
$sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); |
698
|
|
|
$returnColumns = $this->getTableSchema($table)->primaryKey; |
699
|
|
|
if (!empty($returnColumns)) { |
700
|
|
|
$returning = []; |
701
|
|
|
foreach ((array) $returnColumns as $name) { |
702
|
|
|
$returning[] = $this->quoteColumnName($name); |
703
|
|
|
} |
704
|
|
|
$sql .= ' RETURNING ' . implode(', ', $returning); |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
$command = $this->db->createCommand($sql, $params); |
708
|
|
|
$command->prepare(false); |
709
|
|
|
$result = $command->queryOne(); |
710
|
|
|
|
711
|
|
|
if (!$command->pdoStatement->rowCount()) { |
712
|
|
|
return false; |
713
|
|
|
} else { |
714
|
|
|
if (!empty($returnColumns)) { |
715
|
|
|
foreach ((array) $returnColumns as $name) { |
716
|
|
|
if ($this->getTableSchema($table)->getColumn($name)->autoIncrement) { |
717
|
|
|
$this->_lastInsertID = $result[$name]; |
718
|
|
|
break; |
719
|
|
|
} |
720
|
|
|
} |
721
|
|
|
} |
722
|
|
|
return $result; |
723
|
|
|
} |
724
|
|
|
} |
725
|
|
|
|
726
|
|
|
/** |
727
|
|
|
* @inheritdoc |
728
|
|
|
*/ |
729
|
|
|
public function getLastInsertID($sequenceName = '') |
730
|
|
|
{ |
731
|
|
|
if (!$this->db->isActive) { |
732
|
|
|
throw new InvalidCallException('DB Connection is not active.'); |
733
|
|
|
} |
734
|
|
|
|
735
|
|
|
if ($sequenceName !== '') { |
736
|
|
|
return $this->db->createCommand('SELECT GEN_ID(' . $this->db->quoteTableName($sequenceName) . ', 0 ) FROM RDB$DATABASE;')->queryScalar(); |
|
|
|
|
737
|
|
|
} |
738
|
|
|
|
739
|
|
|
if ($this->_lastInsertID !== false) { |
740
|
|
|
return $this->_lastInsertID; |
|
|
|
|
741
|
|
|
} |
742
|
|
|
return null; |
743
|
|
|
} |
744
|
|
|
} |
745
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.