Grammar::constraints()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 10
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 15
rs 9.9332
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