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
|
|
|
|
8
|
|
|
namespace yii\db\pgsql; |
9
|
|
|
|
10
|
|
|
use yii\db\Expression; |
11
|
|
|
use yii\db\TableSchema; |
12
|
|
|
use yii\db\ColumnSchema; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* Schema is the class for retrieving metadata from a PostgreSQL database |
16
|
|
|
* (version 9.x and above). |
17
|
|
|
* |
18
|
|
|
* @property string[] $viewNames All view names in the database. This property is read-only. |
19
|
|
|
* |
20
|
|
|
* @author Gevik Babakhani <[email protected]> |
21
|
|
|
* @since 2.0 |
22
|
|
|
*/ |
23
|
|
|
class Schema extends \yii\db\Schema |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var string the default schema used for the current session. |
27
|
|
|
*/ |
28
|
|
|
public $defaultSchema = 'public'; |
29
|
|
|
/** |
30
|
|
|
* @var array mapping from physical column types (keys) to abstract |
31
|
|
|
* column types (values) |
32
|
|
|
* @see http://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE |
33
|
|
|
*/ |
34
|
|
|
public $typeMap = [ |
35
|
|
|
'bit' => self::TYPE_INTEGER, |
36
|
|
|
'bit varying' => self::TYPE_INTEGER, |
37
|
|
|
'varbit' => self::TYPE_INTEGER, |
38
|
|
|
|
39
|
|
|
'bool' => self::TYPE_BOOLEAN, |
40
|
|
|
'boolean' => self::TYPE_BOOLEAN, |
41
|
|
|
|
42
|
|
|
'box' => self::TYPE_STRING, |
43
|
|
|
'circle' => self::TYPE_STRING, |
44
|
|
|
'point' => self::TYPE_STRING, |
45
|
|
|
'line' => self::TYPE_STRING, |
46
|
|
|
'lseg' => self::TYPE_STRING, |
47
|
|
|
'polygon' => self::TYPE_STRING, |
48
|
|
|
'path' => self::TYPE_STRING, |
49
|
|
|
|
50
|
|
|
'character' => self::TYPE_CHAR, |
51
|
|
|
'char' => self::TYPE_CHAR, |
52
|
|
|
'bpchar' => self::TYPE_CHAR, |
53
|
|
|
'character varying' => self::TYPE_STRING, |
54
|
|
|
'varchar' => self::TYPE_STRING, |
55
|
|
|
'text' => self::TYPE_TEXT, |
56
|
|
|
|
57
|
|
|
'bytea' => self::TYPE_BINARY, |
58
|
|
|
|
59
|
|
|
'cidr' => self::TYPE_STRING, |
60
|
|
|
'inet' => self::TYPE_STRING, |
61
|
|
|
'macaddr' => self::TYPE_STRING, |
62
|
|
|
|
63
|
|
|
'real' => self::TYPE_FLOAT, |
64
|
|
|
'float4' => self::TYPE_FLOAT, |
65
|
|
|
'double precision' => self::TYPE_DOUBLE, |
66
|
|
|
'float8' => self::TYPE_DOUBLE, |
67
|
|
|
'decimal' => self::TYPE_DECIMAL, |
68
|
|
|
'numeric' => self::TYPE_DECIMAL, |
69
|
|
|
|
70
|
|
|
'money' => self::TYPE_MONEY, |
71
|
|
|
|
72
|
|
|
'smallint' => self::TYPE_SMALLINT, |
73
|
|
|
'int2' => self::TYPE_SMALLINT, |
74
|
|
|
'int4' => self::TYPE_INTEGER, |
75
|
|
|
'int' => self::TYPE_INTEGER, |
76
|
|
|
'integer' => self::TYPE_INTEGER, |
77
|
|
|
'bigint' => self::TYPE_BIGINT, |
78
|
|
|
'int8' => self::TYPE_BIGINT, |
79
|
|
|
'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal! |
80
|
|
|
|
81
|
|
|
'smallserial' => self::TYPE_SMALLINT, |
82
|
|
|
'serial2' => self::TYPE_SMALLINT, |
83
|
|
|
'serial4' => self::TYPE_INTEGER, |
84
|
|
|
'serial' => self::TYPE_INTEGER, |
85
|
|
|
'bigserial' => self::TYPE_BIGINT, |
86
|
|
|
'serial8' => self::TYPE_BIGINT, |
87
|
|
|
'pg_lsn' => self::TYPE_BIGINT, |
88
|
|
|
|
89
|
|
|
'date' => self::TYPE_DATE, |
90
|
|
|
'interval' => self::TYPE_STRING, |
91
|
|
|
'time without time zone' => self::TYPE_TIME, |
92
|
|
|
'time' => self::TYPE_TIME, |
93
|
|
|
'time with time zone' => self::TYPE_TIME, |
94
|
|
|
'timetz' => self::TYPE_TIME, |
95
|
|
|
'timestamp without time zone' => self::TYPE_TIMESTAMP, |
96
|
|
|
'timestamp' => self::TYPE_TIMESTAMP, |
97
|
|
|
'timestamp with time zone' => self::TYPE_TIMESTAMP, |
98
|
|
|
'timestamptz' => self::TYPE_TIMESTAMP, |
99
|
|
|
'abstime' => self::TYPE_TIMESTAMP, |
100
|
|
|
|
101
|
|
|
'tsquery' => self::TYPE_STRING, |
102
|
|
|
'tsvector' => self::TYPE_STRING, |
103
|
|
|
'txid_snapshot' => self::TYPE_STRING, |
104
|
|
|
|
105
|
|
|
'unknown' => self::TYPE_STRING, |
106
|
|
|
|
107
|
|
|
'uuid' => self::TYPE_STRING, |
108
|
|
|
'json' => self::TYPE_STRING, |
109
|
|
|
'jsonb' => self::TYPE_STRING, |
110
|
|
|
'xml' => self::TYPE_STRING, |
111
|
|
|
]; |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @var array list of ALL view names in the database |
115
|
|
|
*/ |
116
|
|
|
private $_viewNames = []; |
117
|
|
|
|
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Creates a query builder for the PostgreSQL database. |
121
|
|
|
* @return QueryBuilder query builder instance |
122
|
|
|
*/ |
123
|
146 |
|
public function createQueryBuilder() |
124
|
|
|
{ |
125
|
146 |
|
return new QueryBuilder($this->db); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Resolves the table name and schema name (if any). |
130
|
|
|
* @param TableSchema $table the table metadata object |
131
|
|
|
* @param string $name the table name |
132
|
|
|
*/ |
133
|
139 |
|
protected function resolveTableNames($table, $name) |
134
|
|
|
{ |
135
|
139 |
|
$parts = explode('.', str_replace('"', '', $name)); |
136
|
|
|
|
137
|
139 |
|
if (isset($parts[1])) { |
138
|
|
|
$table->schemaName = $parts[0]; |
139
|
|
|
$table->name = $parts[1]; |
140
|
|
|
} else { |
141
|
139 |
|
$table->schemaName = $this->defaultSchema; |
142
|
139 |
|
$table->name = $name; |
143
|
|
|
} |
144
|
|
|
|
145
|
139 |
|
$table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name; |
146
|
139 |
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Quotes a table name for use in a query. |
150
|
|
|
* A simple table name has no schema prefix. |
151
|
|
|
* @param string $name table name |
152
|
|
|
* @return string the properly quoted table name |
153
|
|
|
*/ |
154
|
187 |
|
public function quoteSimpleTableName($name) |
155
|
|
|
{ |
156
|
187 |
|
return strpos($name, '"') !== false ? $name : '"' . $name . '"'; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Loads the metadata for the specified table. |
161
|
|
|
* @param string $name table name |
162
|
|
|
* @return TableSchema|null driver dependent table metadata. Null if the table does not exist. |
163
|
|
|
*/ |
164
|
139 |
|
public function loadTableSchema($name) |
165
|
|
|
{ |
166
|
139 |
|
$table = new TableSchema(); |
167
|
139 |
|
$this->resolveTableNames($table, $name); |
168
|
139 |
|
if ($this->findColumns($table)) { |
169
|
136 |
|
$this->findConstraints($table); |
170
|
|
|
|
171
|
136 |
|
return $table; |
172
|
|
|
} else { |
173
|
8 |
|
return null; |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Returns all schema names in the database, including the default one but not system schemas. |
179
|
|
|
* This method should be overridden by child classes in order to support this feature |
180
|
|
|
* because the default implementation simply throws an exception. |
181
|
|
|
* @return array all schema names in the database, except system schemas |
182
|
|
|
* @since 2.0.4 |
183
|
|
|
*/ |
184
|
1 |
|
protected function findSchemaNames() |
185
|
|
|
{ |
186
|
|
|
$sql = <<<SQL |
187
|
|
|
SELECT ns.nspname AS schema_name |
188
|
|
|
FROM pg_namespace ns |
189
|
|
|
WHERE ns.nspname != 'information_schema' AND ns.nspname NOT LIKE 'pg_%' |
190
|
|
|
ORDER BY ns.nspname |
191
|
1 |
|
SQL; |
192
|
1 |
|
return $this->db->createCommand($sql)->queryColumn(); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Returns all table names in the database. |
197
|
|
|
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. |
198
|
|
|
* @return array all table names in the database. The names have NO schema name prefix. |
199
|
|
|
*/ |
200
|
7 |
|
protected function findTableNames($schema = '') |
201
|
|
|
{ |
202
|
7 |
|
if ($schema === '') { |
203
|
5 |
|
$schema = $this->defaultSchema; |
204
|
5 |
|
} |
205
|
|
|
$sql = <<<SQL |
206
|
|
|
SELECT c.relname AS table_name |
207
|
|
|
FROM pg_class c |
208
|
|
|
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace |
209
|
|
|
WHERE ns.nspname = :schemaName AND c.relkind IN ('r','v','m','f') |
210
|
|
|
ORDER BY c.relname |
211
|
7 |
|
SQL; |
212
|
7 |
|
$command = $this->db->createCommand($sql, [':schemaName' => $schema]); |
213
|
7 |
|
$rows = $command->queryAll(); |
214
|
7 |
|
$names = []; |
215
|
7 |
|
foreach ($rows as $row) { |
216
|
7 |
|
$names[] = $row['table_name']; |
217
|
7 |
|
} |
218
|
|
|
|
219
|
7 |
|
return $names; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Returns all views names in the database. |
224
|
|
|
* @param string $schema the schema of the views. Defaults to empty string, meaning the current or default schema. |
225
|
|
|
* @return array all views names in the database. The names have NO schema name prefix. |
226
|
|
|
* @since 2.0.9 |
227
|
|
|
*/ |
228
|
2 |
|
protected function findViewNames($schema = '') |
229
|
|
|
{ |
230
|
2 |
|
if ($schema === '') { |
231
|
|
|
$schema = $this->defaultSchema; |
232
|
|
|
} |
233
|
|
|
$sql = <<<SQL |
234
|
|
|
SELECT c.relname AS table_name |
235
|
|
|
FROM pg_class c |
236
|
|
|
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace |
237
|
|
|
WHERE ns.nspname = :schemaName AND c.relkind = 'v' |
238
|
|
|
ORDER BY c.relname |
239
|
2 |
|
SQL; |
240
|
2 |
|
$command = $this->db->createCommand($sql, [':schemaName' => $schema]); |
241
|
2 |
|
$rows = $command->queryAll(); |
242
|
2 |
|
$names = []; |
243
|
2 |
|
foreach ($rows as $row) { |
244
|
2 |
|
$names[] = $row['table_name']; |
245
|
2 |
|
} |
246
|
|
|
|
247
|
2 |
|
return $names; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Returns all view names in the database. |
252
|
|
|
* @param string $schema the schema of the views. Defaults to empty string, meaning the current or default schema name. |
253
|
|
|
* If not empty, the returned view names will be prefixed with the schema name. |
254
|
|
|
* @param bool $refresh whether to fetch the latest available view names. If this is false, |
255
|
|
|
* view names fetched previously (if available) will be returned. |
256
|
|
|
* @return string[] all view names in the database. |
257
|
|
|
* @since 2.0.9 |
258
|
|
|
*/ |
259
|
2 |
|
public function getViewNames($schema = '', $refresh = false) |
260
|
|
|
{ |
261
|
2 |
|
if (!isset($this->_viewNames[$schema]) || $refresh) { |
262
|
2 |
|
$this->_viewNames[$schema] = $this->findViewNames($schema); |
263
|
2 |
|
} |
264
|
|
|
|
265
|
2 |
|
return $this->_viewNames[$schema]; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Collects the foreign key column details for the given table. |
270
|
|
|
* @param TableSchema $table the table metadata |
271
|
|
|
*/ |
272
|
136 |
|
protected function findConstraints($table) |
273
|
|
|
{ |
274
|
|
|
|
275
|
136 |
|
$tableName = $this->quoteValue($table->name); |
276
|
136 |
|
$tableSchema = $this->quoteValue($table->schemaName); |
277
|
|
|
|
278
|
|
|
//We need to extract the constraints de hard way since: |
279
|
|
|
//http://www.postgresql.org/message-id/[email protected] |
280
|
|
|
|
281
|
|
|
$sql = <<<SQL |
282
|
|
|
select |
283
|
|
|
ct.conname as constraint_name, |
284
|
|
|
a.attname as column_name, |
285
|
|
|
fc.relname as foreign_table_name, |
286
|
|
|
fns.nspname as foreign_table_schema, |
287
|
|
|
fa.attname as foreign_column_name |
288
|
|
|
from |
289
|
|
|
(SELECT ct.conname, ct.conrelid, ct.confrelid, ct.conkey, ct.contype, ct.confkey, generate_subscripts(ct.conkey, 1) AS s |
290
|
|
|
FROM pg_constraint ct |
291
|
|
|
) AS ct |
292
|
|
|
inner join pg_class c on c.oid=ct.conrelid |
293
|
|
|
inner join pg_namespace ns on c.relnamespace=ns.oid |
294
|
|
|
inner join pg_attribute a on a.attrelid=ct.conrelid and a.attnum = ct.conkey[ct.s] |
295
|
|
|
left join pg_class fc on fc.oid=ct.confrelid |
296
|
|
|
left join pg_namespace fns on fc.relnamespace=fns.oid |
297
|
|
|
left join pg_attribute fa on fa.attrelid=ct.confrelid and fa.attnum = ct.confkey[ct.s] |
298
|
|
|
where |
299
|
|
|
ct.contype='f' |
300
|
136 |
|
and c.relname={$tableName} |
301
|
136 |
|
and ns.nspname={$tableSchema} |
302
|
|
|
order by |
303
|
136 |
|
fns.nspname, fc.relname, a.attnum |
304
|
136 |
|
SQL; |
305
|
|
|
|
306
|
136 |
|
$constraints = []; |
307
|
136 |
|
foreach ($this->db->createCommand($sql)->queryAll() as $constraint) { |
308
|
54 |
|
if ($constraint['foreign_table_schema'] !== $this->defaultSchema) { |
309
|
|
|
$foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name']; |
310
|
|
|
} else { |
311
|
54 |
|
$foreignTable = $constraint['foreign_table_name']; |
312
|
|
|
} |
313
|
54 |
|
$name = $constraint['constraint_name']; |
314
|
54 |
|
if (!isset($constraints[$name])) { |
315
|
54 |
|
$constraints[$name] = [ |
316
|
54 |
|
'tableName' => $foreignTable, |
317
|
54 |
|
'columns' => [], |
318
|
|
|
]; |
319
|
54 |
|
} |
320
|
54 |
|
$constraints[$name]['columns'][$constraint['column_name']] = $constraint['foreign_column_name']; |
321
|
136 |
|
} |
322
|
136 |
|
foreach ($constraints as $constraint) { |
323
|
54 |
|
$table->foreignKeys[] = array_merge([$constraint['tableName']], $constraint['columns']); |
324
|
136 |
|
} |
325
|
136 |
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Gets information about given table unique indexes. |
329
|
|
|
* @param TableSchema $table the table metadata |
330
|
|
|
* @return array with index and column names |
331
|
|
|
*/ |
332
|
1 |
|
protected function getUniqueIndexInformation($table) |
333
|
|
|
{ |
334
|
|
|
$sql = <<<SQL |
335
|
|
|
SELECT |
336
|
|
|
i.relname as indexname, |
337
|
|
|
pg_get_indexdef(idx.indexrelid, k + 1, TRUE) AS columnname |
338
|
|
|
FROM ( |
339
|
|
|
SELECT *, generate_subscripts(indkey, 1) AS k |
340
|
|
|
FROM pg_index |
341
|
|
|
) idx |
342
|
|
|
INNER JOIN pg_class i ON i.oid = idx.indexrelid |
343
|
|
|
INNER JOIN pg_class c ON c.oid = idx.indrelid |
344
|
|
|
INNER JOIN pg_namespace ns ON c.relnamespace = ns.oid |
345
|
|
|
WHERE idx.indisprimary = FALSE AND idx.indisunique = TRUE |
346
|
|
|
AND c.relname = :tableName AND ns.nspname = :schemaName |
347
|
|
|
ORDER BY i.relname, k |
348
|
1 |
|
SQL; |
349
|
|
|
|
350
|
1 |
|
return $this->db->createCommand($sql, [ |
351
|
1 |
|
':schemaName' => $table->schemaName, |
352
|
1 |
|
':tableName' => $table->name, |
353
|
1 |
|
])->queryAll(); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Returns all unique indexes for the given table. |
358
|
|
|
* Each array element is of the following structure: |
359
|
|
|
* |
360
|
|
|
* ```php |
361
|
|
|
* [ |
362
|
|
|
* 'IndexName1' => ['col1' [, ...]], |
363
|
|
|
* 'IndexName2' => ['col2' [, ...]], |
364
|
|
|
* ] |
365
|
|
|
* ``` |
366
|
|
|
* |
367
|
|
|
* @param TableSchema $table the table metadata |
368
|
|
|
* @return array all unique indexes for the given table. |
369
|
|
|
*/ |
370
|
1 |
|
public function findUniqueIndexes($table) |
371
|
|
|
{ |
372
|
1 |
|
$uniqueIndexes = []; |
373
|
|
|
|
374
|
1 |
|
$rows = $this->getUniqueIndexInformation($table); |
375
|
1 |
|
foreach ($rows as $row) { |
376
|
1 |
|
$column = $row['columnname']; |
377
|
1 |
|
if (!empty($column) && $column[0] === '"') { |
378
|
|
|
// postgres will quote names that are not lowercase-only |
379
|
|
|
// https://github.com/yiisoft/yii2/issues/10613 |
380
|
1 |
|
$column = substr($column, 1, -1); |
381
|
1 |
|
} |
382
|
1 |
|
$uniqueIndexes[$row['indexname']][] = $column; |
383
|
1 |
|
} |
384
|
|
|
|
385
|
1 |
|
return $uniqueIndexes; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Collects the metadata of table columns. |
390
|
|
|
* @param TableSchema $table the table metadata |
391
|
|
|
* @return bool whether the table exists in the database |
392
|
|
|
*/ |
393
|
139 |
|
protected function findColumns($table) |
394
|
|
|
{ |
395
|
139 |
|
$tableName = $this->db->quoteValue($table->name); |
396
|
139 |
|
$schemaName = $this->db->quoteValue($table->schemaName); |
397
|
|
|
$sql = <<<SQL |
398
|
|
|
SELECT |
399
|
|
|
d.nspname AS table_schema, |
400
|
|
|
c.relname AS table_name, |
401
|
|
|
a.attname AS column_name, |
402
|
|
|
t.typname AS data_type, |
403
|
|
|
a.attlen AS character_maximum_length, |
404
|
|
|
pg_catalog.col_description(c.oid, a.attnum) AS column_comment, |
405
|
|
|
a.atttypmod AS modifier, |
406
|
|
|
a.attnotnull = false AS is_nullable, |
407
|
|
|
CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default, |
408
|
|
|
coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc, |
409
|
|
|
array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values, |
410
|
|
|
CASE atttypid |
411
|
|
|
WHEN 21 /*int2*/ THEN 16 |
412
|
|
|
WHEN 23 /*int4*/ THEN 32 |
413
|
|
|
WHEN 20 /*int8*/ THEN 64 |
414
|
|
|
WHEN 1700 /*numeric*/ THEN |
415
|
|
|
CASE WHEN atttypmod = -1 |
416
|
|
|
THEN null |
417
|
|
|
ELSE ((atttypmod - 4) >> 16) & 65535 |
418
|
|
|
END |
419
|
|
|
WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/ |
420
|
|
|
WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/ |
421
|
|
|
ELSE null |
422
|
|
|
END AS numeric_precision, |
423
|
|
|
CASE |
424
|
|
|
WHEN atttypid IN (21, 23, 20) THEN 0 |
425
|
|
|
WHEN atttypid IN (1700) THEN |
426
|
|
|
CASE |
427
|
|
|
WHEN atttypmod = -1 THEN null |
428
|
|
|
ELSE (atttypmod - 4) & 65535 |
429
|
|
|
END |
430
|
|
|
ELSE null |
431
|
|
|
END AS numeric_scale, |
432
|
|
|
CAST( |
433
|
|
|
information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t)) |
434
|
|
|
AS numeric |
435
|
|
|
) AS size, |
436
|
|
|
a.attnum = any (ct.conkey) as is_pkey |
437
|
|
|
FROM |
438
|
|
|
pg_class c |
439
|
|
|
LEFT JOIN pg_attribute a ON a.attrelid = c.oid |
440
|
|
|
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum |
441
|
|
|
LEFT JOIN pg_type t ON a.atttypid = t.oid |
442
|
|
|
LEFT JOIN pg_namespace d ON d.oid = c.relnamespace |
443
|
|
|
LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p' |
444
|
|
|
WHERE |
445
|
|
|
a.attnum > 0 and t.typname != '' |
446
|
139 |
|
and c.relname = {$tableName} |
447
|
139 |
|
and d.nspname = {$schemaName} |
448
|
|
|
ORDER BY |
449
|
139 |
|
a.attnum; |
450
|
139 |
|
SQL; |
451
|
|
|
|
452
|
139 |
|
$columns = $this->db->createCommand($sql)->queryAll(); |
453
|
139 |
|
if (empty($columns)) { |
454
|
8 |
|
return false; |
455
|
|
|
} |
456
|
136 |
|
foreach ($columns as $column) { |
457
|
136 |
|
if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) { |
458
|
|
|
$column = array_change_key_case($column, CASE_LOWER); |
459
|
|
|
} |
460
|
136 |
|
$column = $this->loadColumnSchema($column); |
461
|
136 |
|
$table->columns[$column->name] = $column; |
462
|
136 |
|
if ($column->isPrimaryKey) { |
463
|
116 |
|
$table->primaryKey[] = $column->name; |
464
|
116 |
|
if ($table->sequenceName === null && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) { |
465
|
107 |
|
$table->sequenceName = preg_replace(['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], '', $column->defaultValue); |
|
|
|
|
466
|
107 |
|
} |
467
|
116 |
|
$column->defaultValue = null; |
468
|
136 |
|
} elseif ($column->defaultValue) { |
469
|
85 |
|
if ($column->type === 'timestamp' && $column->defaultValue === 'now()') { |
470
|
21 |
|
$column->defaultValue = new Expression($column->defaultValue); |
471
|
85 |
|
} elseif ($column->type === 'boolean') { |
472
|
80 |
|
$column->defaultValue = ($column->defaultValue === 'true'); |
473
|
85 |
|
} elseif (stripos($column->dbType, 'bit') === 0 || stripos($column->dbType, 'varbit') === 0) { |
474
|
21 |
|
$column->defaultValue = bindec(trim($column->defaultValue, 'B\'')); |
475
|
27 |
|
} elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) { |
476
|
21 |
|
$column->defaultValue = $matches[1]; |
477
|
27 |
|
} elseif (preg_match('/^(?:\()?(.*?)(?(1)\))(?:::.+)?$/', $column->defaultValue, $matches)) { |
478
|
5 |
|
if ($matches[1] === 'NULL') { |
479
|
|
|
$column->defaultValue = null; |
480
|
|
|
} else { |
481
|
5 |
|
$column->defaultValue = $column->phpTypecast($matches[1]); |
482
|
|
|
} |
483
|
5 |
|
} else { |
484
|
25 |
|
$column->defaultValue = $column->phpTypecast($column->defaultValue); |
485
|
|
|
} |
486
|
85 |
|
} |
487
|
136 |
|
} |
488
|
|
|
|
489
|
136 |
|
return true; |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* Loads the column information into a [[ColumnSchema]] object. |
494
|
|
|
* @param array $info column information |
495
|
|
|
* @return ColumnSchema the column schema object |
496
|
|
|
*/ |
497
|
136 |
|
protected function loadColumnSchema($info) |
498
|
|
|
{ |
499
|
136 |
|
$column = $this->createColumnSchema(); |
500
|
136 |
|
$column->allowNull = $info['is_nullable']; |
501
|
136 |
|
$column->autoIncrement = $info['is_autoinc']; |
502
|
136 |
|
$column->comment = $info['column_comment']; |
503
|
136 |
|
$column->dbType = $info['data_type']; |
504
|
136 |
|
$column->defaultValue = $info['column_default']; |
505
|
136 |
|
$column->enumValues = ($info['enum_values'] !== null) ? explode(',', str_replace(["''"], ["'"], $info['enum_values'])) : null; |
|
|
|
|
506
|
136 |
|
$column->unsigned = false; // has no meaning in PG |
507
|
136 |
|
$column->isPrimaryKey = $info['is_pkey']; |
508
|
136 |
|
$column->name = $info['column_name']; |
509
|
136 |
|
$column->precision = $info['numeric_precision']; |
510
|
136 |
|
$column->scale = $info['numeric_scale']; |
511
|
136 |
|
$column->size = $info['size'] === null ? null : (int) $info['size']; |
512
|
136 |
|
if (isset($this->typeMap[$column->dbType])) { |
513
|
136 |
|
$column->type = $this->typeMap[$column->dbType]; |
514
|
136 |
|
} else { |
515
|
|
|
$column->type = self::TYPE_STRING; |
516
|
|
|
} |
517
|
136 |
|
$column->phpType = $this->getColumnPhpType($column); |
518
|
|
|
|
519
|
136 |
|
return $column; |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
/** |
523
|
|
|
* @inheritdoc |
524
|
|
|
*/ |
525
|
27 |
|
public function insert($table, $columns) |
526
|
|
|
{ |
527
|
27 |
|
$params = []; |
528
|
27 |
|
$sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); |
529
|
27 |
|
$returnColumns = $this->getTableSchema($table)->primaryKey; |
530
|
27 |
|
if (!empty($returnColumns)) { |
531
|
19 |
|
$returning = []; |
532
|
19 |
|
foreach ((array) $returnColumns as $name) { |
533
|
19 |
|
$returning[] = $this->quoteColumnName($name); |
534
|
19 |
|
} |
535
|
19 |
|
$sql .= ' RETURNING ' . implode(', ', $returning); |
536
|
19 |
|
} |
537
|
|
|
|
538
|
27 |
|
$command = $this->db->createCommand($sql, $params); |
539
|
27 |
|
$command->prepare(false); |
540
|
27 |
|
$result = $command->queryOne(); |
541
|
|
|
|
542
|
27 |
|
return !$command->pdoStatement->rowCount() ? false : $result; |
543
|
|
|
} |
544
|
|
|
} |
545
|
|
|
|
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.