Completed
Push — master ( 5924b0...d78793 )
by Ivan
02:02
created

Driver::test()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 3
eloc 8
nc 3
nop 0
1
<?php
2
3
namespace vakata\database\driver\oracle;
4
5
use \vakata\database\DBException;
6
use \vakata\database\DriverInterface;
7
use \vakata\database\DriverAbstract;
8
use \vakata\database\StatementInterface;
9
use \vakata\database\schema\Table;
10
use \vakata\database\schema\TableRelation;
11
use \vakata\collection\Collection;
12
13
class Driver extends DriverAbstract implements DriverInterface
14
{
15
    protected $lnk = null;
16
    protected $transaction = false;
17
18
    public function __construct(array $connection)
19
    {
20
        $this->connection = $connection;
21
    }
22
    public function __destruct()
23
    {
24
        $this->disconnect();
25
    }
26
    protected function connect()
27
    {
28
        if ($this->lnk === null) {
29
            $this->lnk = @call_user_func(
30
                $this->option('persist') ? '\oci_pconnect' : '\oci_connect',
31
                $this->connection['user'],
32
                $this->connection['pass'],
33
                $this->connection['host'],
34
                $this->option('charset', 'utf8')
35
            );
36
            if ($this->lnk === false) {
37
                throw new DBException('Connect error');
38
            }
39
            $this->query("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'");
40
            if ($timezone = $this->option('timezone')) {
41
                $this->query("ALTER session SET time_zone = '".addslashes($timezone)."'");
42
            }
43
        }
44
    }
45
    public function test() : bool
46
    {
47
        if ($this->lnk) {
48
            return true;
49
        }
50
        try {
51
            $this->connect();
52
            return true;
53
        } catch (\Exception $e) {
54
            return false;
55
        }
56
    }
57
    protected function disconnect()
58
    {
59
        if ($this->lnk !== null) {
60
            \oci_close($this->lnk);
61
        }
62
    }
63
    public function prepare(string $sql) : StatementInterface
64
    {
65
        $this->connect();
66
        $binder = '?';
67
        if (strpos($sql, $binder) !== false) {
68
            $tmp = explode($binder, $sql);
69
            $sql = '';
70
            foreach ($tmp as $i => $v) {
71
                $sql .= $v;
72
                if (isset($tmp[($i + 1)])) {
73
                    $sql .= ':f'.$i;
74
                }
75
            }
76
        }
77
        $temp = \oci_parse($this->lnk, $sql);
78
        if (!$temp) {
79
            $err = \oci_error();
80
            if (!is_array($err)) {
81
                $err = [];
82
            }
83
            throw new DBException('Could not prepare : '.implode(', ', $err).' <'.$sql.'>');
84
        }
85
        return new Statement($temp, $this);
86
    }
87
88
    public function begin() : bool
89
    {
90
         return $this->transaction = true;
91
    }
92
    public function commit() : bool
93
    {
94
        $this->connect();
95
        if (!$this->transaction) {
96
            return false;
97
        }
98
        if (!\oci_commit($this->lnk)) {
99
            return false;
100
        }
101
        $this->transaction = false;
102
        return true;
103
    }
104
    public function rollback() : bool
105
    {
106
        $this->connect();
107
        if (!$this->transaction) {
108
            return false;
109
        }
110
        if (!\oci_rollback($this->lnk)) {
111
            return false;
112
        }
113
        $this->transaction = false;
114
        return true;
115
    }
116
117
    public function isTransaction()
118
    {
119
        return $this->transaction;
120
    }
121
122
    public function lob()
123
    {
124
        return \oci_new_descriptor($this->lnk, \OCI_D_LOB);
125
    }
126
127
    public function table(
128
        string $table,
129
        bool $detectRelations = true
130
    ) : Table
131
    {
132
        static $tables = [];
133
        if (isset($tables[$table])) {
134
            return $tables[$table];
135
        }
136
137
        $columns = Collection::from($this
138
            ->query(
139
                "SELECT * FROM all_tab_cols WHERE table_name = ? AND owner = ?",
140
                [ strtoupper($table), $this->name() ]
141
            ))
142
            ->map(function ($v) {
143
                $new = [];
144
                foreach ($v as $kk => $vv) {
145
                    $new[strtoupper($kk)] = $vv;
146
                }
147
                return $new;
148
            })
149
            ->mapKey(function ($v) { return $v['COLUMN_NAME']; })
150
            ->toArray();
151
        if (!count($columns)) {
152
            throw new DBException('Table not found by name');
153
        }
154
        $owner = $this->name(); // used to be the current column's OWNER
155
        $pkname = Collection::from($this
156
            ->query(
157
                "SELECT constraint_name FROM all_constraints
158
                WHERE table_name = ? AND constraint_type = ? AND owner = ?",
159
                [ strtoupper($table), 'P', $owner ]
160
            ))
161
            ->map(function ($v) {
162
                $new = [];
163
                foreach ($v as $kk => $vv) {
164
                    $new[strtoupper($kk)] = $vv;
165
                }
166
                return $new;
167
            })
168
            ->pluck('CONSTRAINT_NAME')
169
            ->value();
170
        $primary = [];
171
        if ($pkname) {
172
            $primary = Collection::from($this
173
                ->query(
174
                    "SELECT column_name FROM all_cons_columns
175
                    WHERE table_name = ? AND constraint_name = ? AND owner = ?",
176
                    [ strtoupper($table), $pkname, $owner ]
177
                ))
178
                ->map(function ($v) {
179
                    $new = [];
180
                    foreach ($v as $kk => $vv) {
181
                        $new[strtoupper($kk)] = $vv;
182
                    }
183
                    return $new;
184
                })
185
                ->pluck('COLUMN_NAME')
186
                ->toArray();
187
        }
188
        $tables[$table] = $definition = (new Table($table))
189
            ->addColumns($columns)
190
            ->setPrimaryKey($primary)
191
            ->setComment('');
192
193
        if ($detectRelations) {
194
            // relations where the current table is referenced
195
            // assuming current table is on the "one" end having "many" records in the referencing table
196
            // resulting in a "hasMany" or "manyToMany" relationship (if a pivot table is detected)
197
            $relations = [];
198
            foreach (Collection::from($this
199
                ->query(
200
                    "SELECT ac.TABLE_NAME, ac.CONSTRAINT_NAME, cc.COLUMN_NAME, cc.POSITION
201
                    FROM all_constraints ac
202
                    LEFT JOIN all_cons_columns cc ON cc.OWNER = ac.OWNER AND cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
203
                    WHERE ac.OWNER = ? AND ac.R_OWNER = ? AND ac.R_CONSTRAINT_NAME = ? AND ac.CONSTRAINT_TYPE = ?
204
                    ORDER BY cc.POSITION",
205
                    [ $owner, $owner, $pkname, 'R' ]
206
                ))
207
                ->map(function ($v) {
208
                    $new = [];
209
                    foreach ($v as $kk => $vv) {
210
                        $new[strtoupper($kk)] = $vv;
211
                    }
212
                    return $new;
213
                })
214
                 as $relation
215
            ) {
216
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['TABLE_NAME'];
217
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$primary[(int)$relation['POSITION']-1]] = $relation['COLUMN_NAME'];
218
            }
219
            foreach ($relations as $data) {
220
                $rtable = $this->table($data['table'], true);
221
                $columns = [];
222
                foreach ($rtable->getColumns() as $column) {
223
                    if (!in_array($column, $data['keymap'])) {
224
                        $columns[] = $column;
225
                    }
226
                }
227
                $foreign = [];
228
                $usedcol = [];
229
                if (count($columns)) {
230
                    foreach (Collection::from($this
231
                        ->query(
232
                            "SELECT
233
                                cc.COLUMN_NAME, ac.CONSTRAINT_NAME, rc.TABLE_NAME AS REFERENCED_TABLE_NAME, ac.R_CONSTRAINT_NAME
234
                            FROM all_constraints ac
235
                            JOIN all_constraints rc ON rc.CONSTRAINT_NAME = ac.R_CONSTRAINT_NAME AND rc.OWNER = ac.OWNER
236
                            LEFT JOIN all_cons_columns cc ON cc.OWNER = ac.OWNER AND cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
237
                            WHERE
238
                                ac.OWNER = ? AND ac.R_OWNER = ? AND ac.TABLE_NAME = ? AND ac.CONSTRAINT_TYPE = ? AND
239
                                cc.COLUMN_NAME IN (??)
240
                            ORDER BY POSITION",
241
                            [ $owner, $owner, $data['table'], 'R', $columns ]
242
                        ))
243
                        ->map(function ($v) {
244
                            $new = [];
245
                            foreach ($v as $kk => $vv) {
246
                                $new[strtoupper($kk)] = $vv;
247
                            }
248
                            return $new;
249
                        }) as $relation
250
                    ) {
251
                        $foreign[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
252
                        $foreign[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['R_CONSTRAINT_NAME'];
253
                        $usedcol[] = $relation['COLUMN_NAME'];
254
                    }
255
                }
256
                if (count($foreign) === 1 && !count(array_diff($columns, $usedcol))) {
257
                    $foreign = current($foreign);
258
                    $rcolumns = Collection::from($this
259
                        ->query(
260
                            "SELECT COLUMN_NAME FROM all_cons_columns WHERE OWNER = ? AND CONSTRAINT_NAME = ? ORDER BY POSITION",
261
                            [ $owner, current($foreign['keymap']) ]
262
                        ))
263
                        ->map(function ($v) {
264
                            $new = [];
265
                            foreach ($v as $kk => $vv) {
266
                                $new[strtoupper($kk)] = $vv;
267
                            }
268
                            return $new;
269
                        })
270
                        ->pluck('COLUMN_NAME')
271
                        ->toArray();
272
                    foreach ($foreign['keymap'] as $column => $related) {
273
                        $foreign['keymap'][$column] = array_shift($rcolumns);
274
                    }
275
                    $relname = $foreign['table'];
276
                    $cntr = 1;
277
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
278
                        $relname = $foreign['table'] . '_' . (++ $cntr);
279
                    }
280
                    $definition->addRelation(
281
                        new TableRelation(
282
                            $relname,
283
                            $this->table($foreign['table'], true),
284
                            $data['keymap'],
285
                            true,
286
                            $rtable,
287
                            $foreign['keymap']
288
                        )
289
                    );
290
                } else {
291
                    $relname = $data['table'];
292
                    $cntr = 1;
293
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
294
                        $relname = $data['table'] . '_' . (++ $cntr);
295
                    }
296
                    $definition->addRelation(
297
                        new TableRelation(
298
                            $relname,
299
                            $this->table($data['table'], true),
300
                            $data['keymap'],
301
                            true
302
                        )
303
                    );
304
                }
305
            }
306
            // relations where the current table references another table
307
            // assuming current table is linked to "one" record in the referenced table
308
            // resulting in a "belongsTo" relationship
309
            $relations = [];
310
            foreach (Collection::from($this
311
                ->query(
312
                    "SELECT ac.CONSTRAINT_NAME, cc.COLUMN_NAME, rc.TABLE_NAME AS REFERENCED_TABLE_NAME, ac.R_CONSTRAINT_NAME
313
                    FROM all_constraints ac
314
                    JOIN all_constraints rc ON rc.CONSTRAINT_NAME = ac.R_CONSTRAINT_NAME AND rc.OWNER = ac.OWNER
315
                    LEFT JOIN all_cons_columns cc ON cc.OWNER = ac.OWNER AND cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
316
                    WHERE ac.OWNER = ? AND ac.R_OWNER = ? AND ac.TABLE_NAME = ? AND ac.CONSTRAINT_TYPE = ?
317
                    ORDER BY cc.POSITION",
318
                    [ $owner, $owner, strtoupper($table), 'R' ]
319
                ))
320
                ->map(function ($v) {
321
                    $new = [];
322
                    foreach ($v as $kk => $vv) {
323
                        $new[strtoupper($kk)] = $vv;
324
                    }
325
                    return $new;
326
                })
327
                as $relation
328
            ) {
329
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
330
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['R_CONSTRAINT_NAME'];
331
            }
332
            foreach ($relations as $name => $data) {
333
                $rcolumns = Collection::from($this
334
                    ->query(
335
                        "SELECT COLUMN_NAME FROM all_cons_columns WHERE OWNER = ? AND CONSTRAINT_NAME = ? ORDER BY POSITION",
336
                        [ $owner, current($data['keymap']) ]
337
                    ))
338
                    ->map(function ($v) {
339
                        $new = [];
340
                        foreach ($v as $kk => $vv) {
341
                            $new[strtoupper($kk)] = $vv;
342
                        }
343
                        return $new;
344
                    })
345
                    ->pluck('COLUMN_NAME')
346
                    ->toArray();
347
                foreach ($data['keymap'] as $column => $related) {
348
                    $data['keymap'][$column] = array_shift($rcolumns);
349
                }
350
                $relname = $data['table'];
351
                $cntr = 1;
352
                while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
353
                    $relname = $data['table'] . '_' . (++ $cntr);
354
                }
355
                $definition->addRelation(
356
                    new TableRelation(
357
                        $relname,
358
                        $this->table($data['table'], true),
359
                        $data['keymap'],
360
                        false
361
                    )
362
                );
363
            }
364
        }
365
        return $definition->toLowerCase();
366
    }
367
    public function tables() : array
368
    {
369
        return Collection::from($this
370
            ->query(
371
                "SELECT TABLE_NAME FROM ALL_TABLES where OWNER = ?",
372
                [$this->connection['name']]
373
            ))
374
            ->map(function ($v) {
375
                $new = [];
376
                foreach ($v as $kk => $vv) {
377
                    $new[strtoupper($kk)] = $vv;
378
                }
379
                return $new;
380
            })
381
            ->pluck('TABLE_NAME')
382
            ->map(function ($v) {
383
                return $this->table($v);
384
            })
385
            ->toArray();
386
    }
387
}
388