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