Completed
Branch feature/pre-split (4ff102)
by Anton
03:27
created

PostgresTable::fetchColumns()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 19
nc 3
nop 0
dl 0
loc 32
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * Spiral Framework, Core Components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\Database\Drivers\Postgres\Schemas;
8
9
use Psr\Log\LoggerInterface;
10
use Spiral\Database\Entities\AbstractHandler as Behaviour;
11
use Spiral\Database\Schemas\Prototypes\AbstractColumn;
12
use Spiral\Database\Schemas\Prototypes\AbstractIndex;
13
use Spiral\Database\Schemas\Prototypes\AbstractReference;
14
use Spiral\Database\Schemas\Prototypes\AbstractTable;
15
16
17
class PostgresTable extends AbstractTable
18
{
19
    /**
20
     * Found table sequences.
21
     *
22
     * @var array
23
     */
24
    private $sequences = [];
25
26
    /**
27
     * Sequence object name usually defined only for primary keys and required by ORM to correctly
28
     * resolve inserted row id.
29
     *
30
     * @var string|null
31
     */
32
    private $primarySequence = null;
33
34
    /**
35
     * Sequence object name usually defined only for primary keys and required by ORM to correctly
36
     * resolve inserted row id.
37
     *
38
     * @return string|null
39
     */
40
    public function getSequence()
41
    {
42
        return $this->primarySequence;
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     *
48
     * SQLServer will reload schemas after successful savw.
49
     */
50
    public function save(
51
        int $behaviour = Behaviour::DO_ALL,
52
        LoggerInterface $logger = null,
53
        bool $reset = true
54
    ) {
55
        parent::save($behaviour, $logger, $reset);
56
57
        if ($reset) {
58
            foreach ($this->fetchColumns() as $column) {
59
                $currentColumn = $this->current->findColumn($column->getName());
60
                if (!empty($currentColumn) && $column->compare($currentColumn)) {
61
                    //Ensure constrained columns
62
                    $this->current->registerColumn($column);
63
                }
64
            }
65
        }
66
    }
67
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    protected function fetchColumns(): array
73
    {
74
        //Required for constraints fetch
75
        $tableOID = $this->driver->query('SELECT oid FROM pg_class WHERE relname = ?', [
76
            $this->getName(),
77
        ])->fetchColumn();
78
79
        $query = $this->driver->query(
80
            'SELECT * FROM information_schema.columns JOIN pg_type ON (pg_type.typname = columns.udt_name) WHERE table_name = ?',
81
            [$this->getName()]
82
        );
83
84
        $result = [];
85
        foreach ($query->bind('column_name', $name) as $schema) {
86
            if (preg_match(
87
                '/^nextval\([\'"]([a-z0-9_"]+)[\'"](?:::regclass)?\)$/i',
88
                $schema['column_default'],
89
                $matches
90
            )) {
91
                //Column is sequential
92
                $this->sequences[$name] = $matches[1];
93
            }
94
95
            $result[] = PostgresColumn::createInstance(
96
                $this->getName(),
97
                $schema + ['tableOID' => $tableOID],
98
                $this->driver
99
            );
100
        }
101
102
        return $result;
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    protected function fetchIndexes(bool $all = false): array
109
    {
110
        $query = "SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = ?";
111
112
        $result = [];
113
        foreach ($this->driver->query($query, [$this->getName()]) as $schema) {
114
            $conType = $this->driver->query(
115
                'SELECT contype FROM pg_constraint WHERE conname = ?',
116
                [$schema['indexname']]
117
            )->fetchColumn();
118
119
            if ($conType == 'p') {
120
                //Skipping primary keys
121
                continue;
122
            }
123
124
            $result[] = PostgresIndex::createInstance($this->getName(), $schema);
125
        }
126
127
        return $result;
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    protected function fetchReferences(): array
134
    {
135
        //Mindblowing
136
        $query = 'SELECT tc.constraint_name, tc.table_name, kcu.column_name, rc.update_rule, '
137
            . 'rc.delete_rule, ccu.table_name AS foreign_table_name, '
138
            . "ccu.column_name AS foreign_column_name\n"
139
            . "FROM information_schema.table_constraints AS tc\n"
140
            . "JOIN information_schema.key_column_usage AS kcu\n"
141
            . "   ON tc.constraint_name = kcu.constraint_name\n"
142
            . "JOIN information_schema.constraint_column_usage AS ccu\n"
143
            . "   ON ccu.constraint_name = tc.constraint_name\n"
144
            . "JOIN information_schema.referential_constraints AS rc\n"
145
            . "   ON rc.constraint_name = tc.constraint_name\n"
146
            . "WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = ?";
147
148
        $result = [];
149
150
        foreach ($this->driver->query($query, [$this->getName()]) as $schema) {
151
            $result[] = PostgresReference::createInstance(
152
                $this->getName(),
153
                $this->getPrefix(),
154
                $schema
155
            );
156
        }
157
158
        return $result;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    protected function fetchPrimaryKeys(): array
165
    {
166
        $query = "SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = ?";
167
168
        foreach ($this->driver->query($query, [$this->getName()]) as $schema) {
169
            $conType = $this->driver->query(
170
                'SELECT contype FROM pg_constraint WHERE conname = ?',
171
                [$schema['indexname']]
172
            )->fetchColumn();
173
174
            if ($conType != 'p') {
175
                //Skipping primary keys
176
                continue;
177
            }
178
179
            //To simplify definitions
180
            $index = PostgresIndex::createInstance($this->getName(), $schema);
181
182
            if (is_array($this->primarySequence) && count($index->getColumns()) === 1) {
183
                $column = $index->getColumns()[0];
184
185
                if (isset($this->sequences[$column])) {
186
                    //We found our primary sequence
187
                    $this->primarySequence = $this->sequences[$column];
188
                }
189
            }
190
191
            return $index->getColumns();
192
        }
193
194
        return [];
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    protected function createColumn(string $name): AbstractColumn
201
    {
202
        return new PostgresColumn($this->getName(), $name, $this->driver->getTimezone());
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208
    protected function createIndex(string $name): AbstractIndex
209
    {
210
        return new PostgresIndex($this->getName(), $name);
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216
    protected function createForeign(string $name): AbstractReference
217
    {
218
        return new PostgresReference($this->getName(), $this->getPrefix(), $name);
219
    }
220
}