Completed
Push — master ( b71ad6...2211ef )
by Ivan
08:51
created

Driver::lob()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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
12
class Driver extends DriverAbstract implements DriverInterface
13
{
14
    protected $connection;
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
    protected function disconnect()
46
    {
47
        if ($this->lnk !== null) {
48
            @\oci_close($this->lnk);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
49
        }
50
    }
51
    public function prepare(string $sql) : StatementInterface
52
    {
53
        $this->connect();
54
        $binder = '?';
55
        if (strpos($sql, $binder) !== false) {
56
            $tmp = explode($binder, $sql);
57
            $sql = '';
58
            foreach ($tmp as $i => $v) {
59
                $sql .= $v;
60
                if (isset($tmp[($i + 1)])) {
61
                    $sql .= ':f'.$i;
62
                }
63
            }
64
        }
65
        $temp = \oci_parse($this->lnk, $sql);
66
        if (!$temp) {
67
            $err = \oci_error();
68
            if (!$err) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $err of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
69
                $err = [];
70
            }
71
            throw new DBException('Could not prepare : '.implode(', ', $err).' <'.$sql.'>');
72
        }
73
        return new Statement($temp, $this);
74
    }
75
76
    public function begin() : bool
77
    {
78
         return $this->transaction = true;
79
    }
80
    public function commit() : bool
81
    {
82
        $this->connect();
83
        if (!$this->transaction) {
84
            return false;
85
        }
86
        if (!\oci_commit($this->lnk)) {
87
            return false;
88
        }
89
        $this->transaction = false;
90
        return true;
91
    }
92
    public function rollback() : bool
93
    {
94
        $this->connect();
95
        if (!$this->transaction) {
96
            return false;
97
        }
98
        if (!\oci_rollback($this->lnk)) {
99
            return false;
100
        }
101
        $this->transaction = false;
102
        return true;
103
    }
104
105
    public function isTransaction()
106
    {
107
        return $this->transaction;
108
    }
109
110
    public function lob()
111
    {
112
        return \oci_new_descriptor($this->lnk, \OCI_D_LOB);
113
    }
114
115
    public function table(
116
        string $table,
117
        bool $detectRelations = true
118
    ) : Table
119
    {
120
        static $tables = [];
121
        if (isset($tables[$table])) {
122
            return $tables[$table];
123
        }
124
125
        $columns = Collection::from($this
126
            ->query(
127
                "SELECT * FROM all_tab_cols WHERE table_name = ? AND owner = ?",
128
                [ strtoupper($table), $this->name() ]
129
            ))
130
            ->map(function ($v) {
131
                $new = [];
132
                foreach ($v as $kk => $vv) {
133
                    $new[strtoupper($kk)] = $vv;
134
                }
135
                return $new;
136
            })
137
            ->mapKey(function ($v) { return $v['COLUMN_NAME']; })
138
            ->toArray();
139
        if (!count($columns)) {
140
            throw new DBException('Table not found by name');
141
        }
142
        $owner = $this->name(); // current($columns)['OWNER'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
78% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
143
        $pkname = Collection::from($this
144
            ->query(
145
                "SELECT constraint_name FROM all_constraints
146
                WHERE table_name = ? AND constraint_type = ? AND owner = ?",
147
                [ strtoupper($table), 'P', $owner ]
148
            ))
149
            ->map(function ($v) {
150
                $new = [];
151
                foreach ($v as $kk => $vv) {
152
                    $new[strtoupper($kk)] = $vv;
153
                }
154
                return $new;
155
            })
156
            ->pluck('CONSTRAINT_NAME')
157
            ->value();
158
        $primary = [];
159
        if ($pkname) {
160
            $primary = Collection::from($this
161
                ->query(
162
                    "SELECT column_name FROM all_cons_columns
163
                    WHERE table_name = ? AND constraint_name = ? AND owner = ?",
164
                    [ strtoupper($table), $pkname, $owner ]
165
                ))
166
                ->map(function ($v) {
167
                    $new = [];
168
                    foreach ($v as $kk => $vv) {
169
                        $new[strtoupper($kk)] = $vv;
170
                    }
171
                    return $new;
172
                })
173
                ->pluck('COLUMN_NAME')
174
                ->toArray();
175
        }
176
        $tables[$table] = $definition = (new Table($table))
177
            ->addColumns($columns)
178
            ->setPrimaryKey($primary)
179
            ->setComment('');
180
181
        if ($detectRelations) {
182
            // relations where the current table is referenced
183
            // assuming current table is on the "one" end having "many" records in the referencing table
184
            // resulting in a "hasMany" or "manyToMany" relationship (if a pivot table is detected)
185
            $relations = [];
186
            foreach (Collection::from($this
187
                ->query(
188
                    "SELECT ac.TABLE_NAME, ac.CONSTRAINT_NAME, cc.COLUMN_NAME, cc.POSITION
189
                    FROM all_constraints ac
190
                    LEFT JOIN all_cons_columns cc ON cc.OWNER = ac.OWNER AND cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
191
                    WHERE ac.OWNER = ? AND ac.R_OWNER = ? AND ac.R_CONSTRAINT_NAME = ? AND ac.CONSTRAINT_TYPE = ?
192
                    ORDER BY cc.POSITION",
193
                    [ $owner, $owner, $pkname, 'R' ]
194
                ))
195
                ->map(function ($v) {
196
                    $new = [];
197
                    foreach ($v as $kk => $vv) {
198
                        $new[strtoupper($kk)] = $vv;
199
                    }
200
                    return $new;
201
                })
202
                 as $relation
203
            ) {
204
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['TABLE_NAME'];
205
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$primary[(int)$relation['POSITION']-1]] = $relation['COLUMN_NAME'];
206
            }
207
            foreach ($relations as $data) {
208
                $rtable = $this->table($data['table'], true); // ?? $this->addTableByName($data['table'], false);
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
209
                $columns = [];
210
                foreach ($rtable->getColumns() as $column) {
211
                    if (!in_array($column, $data['keymap'])) {
212
                        $columns[] = $column;
213
                    }
214
                }
215
                $foreign = [];
216
                $usedcol = [];
217
                if (count($columns)) {
218
                    foreach (Collection::from($this
219
                        ->query(
220
                            "SELECT
221
                                cc.COLUMN_NAME, ac.CONSTRAINT_NAME, rc.TABLE_NAME AS REFERENCED_TABLE_NAME, ac.R_CONSTRAINT_NAME
222
                            FROM all_constraints ac
223
                            JOIN all_constraints rc ON rc.CONSTRAINT_NAME = ac.R_CONSTRAINT_NAME AND rc.OWNER = ac.OWNER
224
                            LEFT JOIN all_cons_columns cc ON cc.OWNER = ac.OWNER AND cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
225
                            WHERE
226
                                ac.OWNER = ? AND ac.R_OWNER = ? AND ac.TABLE_NAME = ? AND ac.CONSTRAINT_TYPE = ? AND
227
                                cc.COLUMN_NAME IN (??)
228
                            ORDER BY POSITION",
229
                            [ $owner, $owner, $data['table'], 'R', $columns ]
230
                        ))
231
                        ->map(function ($v) {
232
                            $new = [];
233
                            foreach ($v as $kk => $vv) {
234
                                $new[strtoupper($kk)] = $vv;
235
                            }
236
                            return $new;
237
                        }) as $relation
238
                    ) {
239
                        $foreign[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
240
                        $foreign[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['R_CONSTRAINT_NAME'];
241
                        $usedcol[] = $relation['COLUMN_NAME'];
242
                    }
243
                }
244
                if (count($foreign) === 1 && !count(array_diff($columns, $usedcol))) {
245
                    $foreign = current($foreign);
246
                    $rcolumns = Collection::from($this
247
                        ->query(
248
                            "SELECT COLUMN_NAME FROM all_cons_columns WHERE OWNER = ? AND CONSTRAINT_NAME = ? ORDER BY POSITION",
249
                            [ $owner, current($foreign['keymap']) ]
250
                        ))
251
                        ->map(function ($v) {
252
                            $new = [];
253
                            foreach ($v as $kk => $vv) {
254
                                $new[strtoupper($kk)] = $vv;
255
                            }
256
                            return $new;
257
                        })
258
                        ->pluck('COLUMN_NAME')
259
                        ->toArray();
260
                    foreach ($foreign['keymap'] as $column => $related) {
261
                        $foreign['keymap'][$column] = array_shift($rcolumns);
262
                    }
263
                    $relname = $foreign['table'];
264
                    $cntr = 1;
265
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
266
                        $relname = $foreign['table'] . '_' . (++ $cntr);
267
                    }
268
                    $definition->addRelation(
269
                        new TableRelation(
270
                            $relname,
271
                            $this->table($foreign['table'], true),
272
                            $data['keymap'],
273
                            true,
274
                            $rtable,
275
                            $foreign['keymap']
276
                        )
277
                    );
278
                } else {
279
                    $relname = $data['table'];
280
                    $cntr = 1;
281
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
282
                        $relname = $data['table'] . '_' . (++ $cntr);
283
                    }
284
                    $definition->addRelation(
285
                        new TableRelation(
286
                            $relname,
287
                            $this->table($data['table'], true),
288
                            $data['keymap'],
289
                            true
290
                        )
291
                    );
292
                }
293
            }
294
            // relations where the current table references another table
295
            // assuming current table is linked to "one" record in the referenced table
296
            // resulting in a "belongsTo" relationship
297
            $relations = [];
298
            foreach (Collection::from($this
299
                ->query(
300
                    "SELECT ac.CONSTRAINT_NAME, cc.COLUMN_NAME, rc.TABLE_NAME AS REFERENCED_TABLE_NAME, ac.R_CONSTRAINT_NAME
301
                    FROM all_constraints ac
302
                    JOIN all_constraints rc ON rc.CONSTRAINT_NAME = ac.R_CONSTRAINT_NAME AND rc.OWNER = ac.OWNER
303
                    LEFT JOIN all_cons_columns cc ON cc.OWNER = ac.OWNER AND cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
304
                    WHERE ac.OWNER = ? AND ac.R_OWNER = ? AND ac.TABLE_NAME = ? AND ac.CONSTRAINT_TYPE = ?
305
                    ORDER BY cc.POSITION",
306
                    [ $owner, $owner, strtoupper($table), 'R' ]
307
                ))
308
                ->map(function ($v) {
309
                    $new = [];
310
                    foreach ($v as $kk => $vv) {
311
                        $new[strtoupper($kk)] = $vv;
312
                    }
313
                    return $new;
314
                })
315
                as $relation
316
            ) {
317
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
318
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['R_CONSTRAINT_NAME'];
319
            }
320
            foreach ($relations as $name => $data) {
321
                $rcolumns = Collection::from($this
322
                    ->query(
323
                        "SELECT COLUMN_NAME FROM all_cons_columns WHERE OWNER = ? AND CONSTRAINT_NAME = ? ORDER BY POSITION",
324
                        [ $owner, current($data['keymap']) ]
325
                    ))
326
                    ->map(function ($v) {
327
                        $new = [];
328
                        foreach ($v as $kk => $vv) {
329
                            $new[strtoupper($kk)] = $vv;
330
                        }
331
                        return $new;
332
                    })
333
                    ->pluck('COLUMN_NAME')
334
                    ->toArray();
335
                foreach ($data['keymap'] as $column => $related) {
336
                    $data['keymap'][$column] = array_shift($rcolumns);
337
                }
338
                $relname = $data['table'];
339
                $cntr = 1;
340
                while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
341
                    $relname = $data['table'] . '_' . (++ $cntr);
342
                }
343
                $definition->addRelation(
344
                    new TableRelation(
345
                        $relname,
346
                        $this->table($data['table'], true),
347
                        $data['keymap'],
348
                        false
349
                    )
350
                );
351
            }
352
        }
353
        return $definition->toLowerCase();
354
    }
355
    public function tables() : array
356
    {
357
        return Collection::from($this
358
            ->query(
359
                "SELECT TABLE_NAME FROM ALL_TABLES where OWNER = ?",
360
                [$this->connection['name']]
361
            ))
362
            ->map(function ($v) {
363
                $new = [];
364
                foreach ($v as $kk => $vv) {
365
                    $new[strtoupper($kk)] = $vv;
366
                }
367
                return $new;
368
            })
369
            ->pluck('TABLE_NAME')
370
            ->map(function ($v) {
371
                return $this->table($v);
372
            })
373
            ->toArray();
374
    }
375
}
376