1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Lagdo\DbAdmin\Driver\PgSql\Db; |
4
|
|
|
|
5
|
|
|
use Lagdo\DbAdmin\Driver\Db\Grammar as AbstractGrammar; |
6
|
|
|
|
7
|
|
|
class Grammar extends AbstractGrammar |
8
|
|
|
{ |
9
|
|
|
/** |
10
|
|
|
* @inheritDoc |
11
|
|
|
*/ |
12
|
|
|
public function escapeId($idf) |
13
|
|
|
{ |
14
|
|
|
return '"' . str_replace('"', '""', $idf) . '"'; |
15
|
|
|
} |
16
|
|
|
|
17
|
|
|
private function constraints(string $table) |
18
|
|
|
{ |
19
|
|
|
$constraints = []; |
20
|
|
|
$query = "SELECT conname, consrc FROM pg_catalog.pg_constraint " . |
21
|
|
|
"INNER JOIN pg_catalog.pg_namespace ON pg_constraint.connamespace = pg_namespace.oid " . |
22
|
|
|
"INNER JOIN pg_catalog.pg_class ON pg_constraint.conrelid = pg_class.oid " . |
23
|
|
|
"AND pg_constraint.connamespace = pg_class.relnamespace WHERE pg_constraint.contype = 'c' " . |
24
|
|
|
// "-- handle only CONSTRAINTs here, not TYPES " . |
25
|
|
|
"AND conrelid != 0 AND nspname = current_schema() AND relname = " . |
26
|
|
|
$this->driver->quote($table) . "ORDER BY connamespace, conname"; |
27
|
|
|
foreach ($this->driver->rows($query) as $row) |
28
|
|
|
{ |
29
|
|
|
$constraints[$row['conname']] = $row['consrc']; |
30
|
|
|
} |
31
|
|
|
return $constraints; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @inheritDoc |
36
|
|
|
*/ |
37
|
|
|
public function sqlForForeignKeys(string $table) |
38
|
|
|
{ |
39
|
|
|
$query = ""; |
40
|
|
|
|
41
|
|
|
$status = $this->driver->tableStatus($table); |
42
|
|
|
$fkeys = $this->driver->foreignKeys($table); |
43
|
|
|
ksort($fkeys); |
44
|
|
|
|
45
|
|
|
foreach ($fkeys as $fkey_name => $fkey) { |
46
|
|
|
$query .= "ALTER TABLE ONLY " . $this->escapeId($status->schema) . "." . |
47
|
|
|
$this->escapeId($status->name) . " ADD CONSTRAINT " . $this->escapeId($fkey_name) . |
48
|
|
|
" {$fkey->definition} " . ($fkey->deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE') . ";\n"; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
return ($query ? "$query\n" : $query); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @inheritDoc |
56
|
|
|
*/ |
57
|
|
|
public function sqlForCreateTable(string $table, bool $autoIncrement, string $style) |
58
|
|
|
{ |
59
|
|
|
$clauses = []; |
60
|
|
|
$sequences = []; |
61
|
|
|
|
62
|
|
|
$status = $this->driver->tableStatus($table); |
63
|
|
|
if ($status !== null && $this->driver->isView($status)) { |
64
|
|
|
$view = $this->driver->view($table); |
65
|
|
|
return rtrim("CREATE VIEW " . $this->escapeId($table) . " AS $view[select]", ";"); |
66
|
|
|
} |
67
|
|
|
$fields = $this->driver->fields($table); |
68
|
|
|
$indexes = $this->driver->indexes($table); |
69
|
|
|
ksort($indexes); |
70
|
|
|
$constraints = $this->constraints($table); |
71
|
|
|
|
72
|
|
|
if (empty($status) || empty($fields)) { |
73
|
|
|
return ''; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
$query = "CREATE TABLE " . $this->escapeId($status->schema) . "." . |
77
|
|
|
$this->escapeId($status->name) . " (\n "; |
78
|
|
|
|
79
|
|
|
// fields' definitions |
80
|
|
|
foreach ($fields as $field_name => $field) { |
81
|
|
|
$part = $this->escapeId($field->name) . ' ' . $field->fullType . |
82
|
|
|
$this->driver->defaultValue($field) . ($field->attnotnull ? " NOT NULL" : ""); |
83
|
|
|
$clauses[] = $part; |
84
|
|
|
|
85
|
|
|
// sequences for fields |
86
|
|
|
if (preg_match('~nextval\(\'([^\']+)\'\)~', $field->default, $matches)) { |
87
|
|
|
$sequence_name = $matches[1]; |
88
|
|
|
$sq = reset($this->driver->rows($this->driver->minVersion(10) ? |
89
|
|
|
"SELECT *, cache_size AS cache_value FROM pg_sequences " . |
90
|
|
|
"WHERE schemaname = current_schema() AND sequencename = " . |
91
|
|
|
$this->driver->quote($sequence_name) : "SELECT * FROM $sequence_name")); |
92
|
|
|
$sequences[] = ($style == "DROP+CREATE" ? "DROP SEQUENCE IF EXISTS $sequence_name;\n" : "") . |
93
|
|
|
"CREATE SEQUENCE $sequence_name INCREMENT $sq[increment_by] MINVALUE $sq[min_value] MAXVALUE $sq[max_value]" . |
94
|
|
|
($autoIncrement && $sq['last_value'] ? " START $sq[last_value]" : "") . " CACHE $sq[cache_value];"; |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
// adding sequences before table definition |
99
|
|
|
if (!empty($sequences)) { |
100
|
|
|
$query = implode("\n\n", $sequences) . "\n\n$query"; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
// primary + unique keys |
104
|
|
|
foreach ($indexes as $index_name => $index) { |
105
|
|
|
switch ($index->type) { |
106
|
|
|
case 'UNIQUE': |
107
|
|
|
$clauses[] = "CONSTRAINT " . $this->escapeId($index_name) . |
108
|
|
|
" UNIQUE (" . implode(', ', array_map(function ($column) { |
109
|
|
|
return $this->escapeId($column); |
110
|
|
|
}, $index->columns)) . ")"; |
111
|
|
|
break; |
112
|
|
|
case 'PRIMARY': |
113
|
|
|
$clauses[] = "CONSTRAINT " . $this->escapeId($index_name) . |
114
|
|
|
" PRIMARY KEY (" . implode(', ', array_map(function ($column) { |
115
|
|
|
return $this->escapeId($column); |
116
|
|
|
}, $index->columns)) . ")"; |
117
|
|
|
break; |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
foreach ($constraints as $conname => $consrc) { |
122
|
|
|
$clauses[] = "CONSTRAINT " . $this->escapeId($conname) . " CHECK $consrc"; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
$query .= implode(",\n ", $clauses) . "\n) WITH (oids = " . ($status->oid ? 'true' : 'false') . ");"; |
126
|
|
|
|
127
|
|
|
// "basic" indexes after table definition |
128
|
|
|
foreach ($indexes as $index_name => $index) { |
129
|
|
|
if ($index->type == 'INDEX') { |
130
|
|
|
$columns = []; |
131
|
|
|
foreach ($index->columns as $key => $val) { |
132
|
|
|
$columns[] = $this->escapeId($val) . ($index->descs[$key] ? " DESC" : ""); |
133
|
|
|
} |
134
|
|
|
$query .= "\n\nCREATE INDEX " . $this->escapeId($index_name) . " ON " . |
135
|
|
|
$this->escapeId($status->schema) . "." . $this->escapeId($status->name) . |
136
|
|
|
" USING btree (" . implode(', ', $columns) . ");"; |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
// coments for table & fields |
141
|
|
|
if ($status->comment) { |
142
|
|
|
$query .= "\n\nCOMMENT ON TABLE " . $this->escapeId($status->schema) . "." . |
143
|
|
|
$this->escapeId($status->name) . " IS " . $this->driver->quote($status->comment) . ";"; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
foreach ($fields as $field_name => $field) { |
147
|
|
|
if ($field->comment) { |
148
|
|
|
$query .= "\n\nCOMMENT ON COLUMN " . $this->escapeId($status->schema) . "." . |
149
|
|
|
$this->escapeId($status->name) . "." . $this->escapeId($field_name) . |
150
|
|
|
" IS " . $this->driver->quote($field->comment) . ";"; |
151
|
|
|
} |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
return rtrim($query, ';'); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* @inheritDoc |
159
|
|
|
*/ |
160
|
|
|
public function sqlForTruncateTable(string $table) |
161
|
|
|
{ |
162
|
|
|
return "TRUNCATE " . $this->table($table); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @inheritDoc |
167
|
|
|
*/ |
168
|
|
|
public function sqlForCreateTrigger(string $table) |
169
|
|
|
{ |
170
|
|
|
$status = $this->driver->tableStatus($table); |
171
|
|
|
$query = ""; |
172
|
|
|
foreach ($this->driver->triggers($table) as $trg_id => $trg) { |
173
|
|
|
$trigger = $this->driver->trigger($trg_id, $status->name); |
174
|
|
|
$query .= "\nCREATE TRIGGER " . $this->escapeId($trigger['Trigger']) . |
175
|
|
|
" $trigger[Timing] $trigger[Events] ON " . $this->escapeId($status->schema) . "." . |
176
|
|
|
$this->escapeId($status->name) . " $trigger[Type] $trigger[Statement];;\n"; |
177
|
|
|
} |
178
|
|
|
return $query; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* @inheritDoc |
184
|
|
|
*/ |
185
|
|
|
public function sqlForUseDatabase(string $database) |
186
|
|
|
{ |
187
|
|
|
return "\connect " . $this->escapeId($database); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|