Completed
Push — master ( 407844...9daecf )
by Ivan
02:25
created

Schema   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 4
dl 0
loc 282
rs 9.36
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
F table() 0 259 36
A tables() 0 20 2
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
trait Schema
14
{
15
    public function table(
16
        string $table,
17
        bool $detectRelations = true
18
    ) : Table
19
    {
20
        static $tables = [];
21
        if (isset($tables[$table])) {
22
            return $tables[$table];
23
        }
24
25
        $columns = Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
26
            ->query(
27
                "SELECT * FROM all_tab_cols WHERE table_name = ? AND owner = ?",
28
                [ strtoupper($table), $this->name() ]
0 ignored issues
show
Bug introduced by
It seems like name() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
29
            ))
30
            ->map(function ($v) {
31
                $new = [];
32
                foreach ($v as $kk => $vv) {
33
                    $new[strtoupper($kk)] = $vv;
34
                }
35
                return $new;
36
            })
37
            ->mapKey(function ($v) { return $v['COLUMN_NAME']; })
38
            ->map(function ($v) {
39
                $v['length'] = null;
40
                if (!isset($v['DATA_TYPE'])) {
41
                    return $v;
42
                }
43
                $type = strtolower($v['DATA_TYPE']);
44
                switch ($type) {
45
                    case 'clob': // unlimited
46
                        break;
47
                    default:
48
                        if (strpos($type, 'char') !== false && strpos($type, '(') !== false) {
49
                            // extract length from varchar
50
                            $v['length'] = (int)explode(')', (explode('(', $type)[1] ?? ''))[0];
51
                            $v['length'] = $v['length'] > 0 ? $v['length'] : null;
52
                        }
53
                        break;
54
                }
55
                return $v;
56
            })
57
            ->toArray();
58
        if (!count($columns)) {
59
            throw new DBException('Table not found by name');
60
        }
61
        $owner = $this->connection['opts']['schema'] ?? $this->name(); // used to be the current column's OWNER
0 ignored issues
show
Bug introduced by
The property connection does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
It seems like name() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
62
        $pkname = Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
63
            ->query(
64
                "SELECT constraint_name FROM all_constraints
65
                WHERE table_name = ? AND constraint_type = ? AND owner = ?",
66
                [ strtoupper($table), 'P', $owner ]
67
            ))
68
            ->map(function ($v) {
69
                $new = [];
70
                foreach ($v as $kk => $vv) {
71
                    $new[strtoupper($kk)] = $vv;
72
                }
73
                return $new;
74
            })
75
            ->pluck('CONSTRAINT_NAME')
76
            ->value();
77
        $primary = [];
78
        if ($pkname) {
79
            $primary = Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
80
                ->query(
81
                    "SELECT column_name FROM all_cons_columns
82
                    WHERE table_name = ? AND constraint_name = ? AND owner = ?",
83
                    [ strtoupper($table), $pkname, $owner ]
84
                ))
85
                ->map(function ($v) {
86
                    $new = [];
87
                    foreach ($v as $kk => $vv) {
88
                        $new[strtoupper($kk)] = $vv;
89
                    }
90
                    return $new;
91
                })
92
                ->pluck('COLUMN_NAME')
93
                ->toArray();
94
        }
95
        $tables[$table] = $definition = (new Table($table))
96
            ->addColumns($columns)
97
            ->setPrimaryKey($primary)
98
            ->setComment('');
99
100
        if ($detectRelations) {
101
            // relations where the current table is referenced
102
            // assuming current table is on the "one" end having "many" records in the referencing table
103
            // resulting in a "hasMany" or "manyToMany" relationship (if a pivot table is detected)
104
            $relations = [];
105
            foreach (Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
106
                ->query(
107
                    "SELECT ac.TABLE_NAME, ac.CONSTRAINT_NAME, cc.COLUMN_NAME, cc.POSITION
108
                    FROM all_constraints ac
109
                    LEFT JOIN all_cons_columns cc ON cc.OWNER = ac.OWNER AND cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
110
                    WHERE ac.OWNER = ? AND ac.R_OWNER = ? AND ac.R_CONSTRAINT_NAME = ? AND ac.CONSTRAINT_TYPE = ?
111
                    ORDER BY cc.POSITION",
112
                    [ $owner, $owner, $pkname, 'R' ]
113
                ))
114
                ->map(function ($v) {
115
                    $new = [];
116
                    foreach ($v as $kk => $vv) {
117
                        $new[strtoupper($kk)] = $vv;
118
                    }
119
                    return $new;
120
                })
121
                 as $relation
122
            ) {
123
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['TABLE_NAME'];
124
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$primary[(int)$relation['POSITION']-1]] = $relation['COLUMN_NAME'];
125
            }
126
            foreach ($relations as $data) {
127
                $rtable = $this->table($data['table'], true);
128
                $columns = [];
129
                foreach ($rtable->getColumns() as $column) {
130
                    if (!in_array($column, $data['keymap'])) {
131
                        $columns[] = $column;
132
                    }
133
                }
134
                $foreign = [];
135
                $usedcol = [];
136
                if (count($columns)) {
137
                    foreach (Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
138
                        ->query(
139
                            "SELECT
140
                                cc.COLUMN_NAME, ac.CONSTRAINT_NAME, rc.TABLE_NAME AS REFERENCED_TABLE_NAME, ac.R_CONSTRAINT_NAME
141
                            FROM all_constraints ac
142
                            JOIN all_constraints rc ON rc.CONSTRAINT_NAME = ac.R_CONSTRAINT_NAME AND rc.OWNER = ac.OWNER
143
                            LEFT JOIN all_cons_columns cc ON cc.OWNER = ac.OWNER AND cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
144
                            WHERE
145
                                ac.OWNER = ? AND ac.R_OWNER = ? AND ac.TABLE_NAME = ? AND ac.CONSTRAINT_TYPE = ? AND
146
                                cc.COLUMN_NAME IN (??)
147
                            ORDER BY POSITION",
148
                            [ $owner, $owner, $data['table'], 'R', $columns ]
149
                        ))
150
                        ->map(function ($v) {
151
                            $new = [];
152
                            foreach ($v as $kk => $vv) {
153
                                $new[strtoupper($kk)] = $vv;
154
                            }
155
                            return $new;
156
                        }) as $relation
157
                    ) {
158
                        $foreign[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
159
                        $foreign[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['R_CONSTRAINT_NAME'];
160
                        $usedcol[] = $relation['COLUMN_NAME'];
161
                    }
162
                }
163
                if (count($foreign) === 1 && !count(array_diff($columns, $usedcol))) {
164
                    $foreign = current($foreign);
165
                    $rcolumns = Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
166
                        ->query(
167
                            "SELECT COLUMN_NAME FROM all_cons_columns WHERE OWNER = ? AND CONSTRAINT_NAME = ? ORDER BY POSITION",
168
                            [ $owner, current($foreign['keymap']) ]
169
                        ))
170
                        ->map(function ($v) {
171
                            $new = [];
172
                            foreach ($v as $kk => $vv) {
173
                                $new[strtoupper($kk)] = $vv;
174
                            }
175
                            return $new;
176
                        })
177
                        ->pluck('COLUMN_NAME')
178
                        ->toArray();
179
                    foreach ($foreign['keymap'] as $column => $related) {
180
                        $foreign['keymap'][$column] = array_shift($rcolumns);
181
                    }
182
                    $relname = $foreign['table'];
183
                    $cntr = 1;
184
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
185
                        $relname = $foreign['table'] . '_' . (++ $cntr);
186
                    }
187
                    $definition->addRelation(
188
                        new TableRelation(
189
                            $relname,
190
                            $this->table($foreign['table'], true),
191
                            $data['keymap'],
192
                            true,
193
                            $rtable,
194
                            $foreign['keymap']
195
                        )
196
                    );
197
                } else {
198
                    $relname = $data['table'];
199
                    $cntr = 1;
200
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
201
                        $relname = $data['table'] . '_' . (++ $cntr);
202
                    }
203
                    $definition->addRelation(
204
                        new TableRelation(
205
                            $relname,
206
                            $this->table($data['table'], true),
207
                            $data['keymap'],
208
                            true
209
                        )
210
                    );
211
                }
212
            }
213
            // relations where the current table references another table
214
            // assuming current table is linked to "one" record in the referenced table
215
            // resulting in a "belongsTo" relationship
216
            $relations = [];
217
            foreach (Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
218
                ->query(
219
                    "SELECT ac.CONSTRAINT_NAME, cc.COLUMN_NAME, rc.TABLE_NAME AS REFERENCED_TABLE_NAME, ac.R_CONSTRAINT_NAME
220
                    FROM all_constraints ac
221
                    JOIN all_constraints rc ON rc.CONSTRAINT_NAME = ac.R_CONSTRAINT_NAME AND rc.OWNER = ac.OWNER
222
                    LEFT JOIN all_cons_columns cc ON cc.OWNER = ac.OWNER AND cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME
223
                    WHERE ac.OWNER = ? AND ac.R_OWNER = ? AND ac.TABLE_NAME = ? AND ac.CONSTRAINT_TYPE = ?
224
                    ORDER BY cc.POSITION",
225
                    [ $owner, $owner, strtoupper($table), 'R' ]
226
                ))
227
                ->map(function ($v) {
228
                    $new = [];
229
                    foreach ($v as $kk => $vv) {
230
                        $new[strtoupper($kk)] = $vv;
231
                    }
232
                    return $new;
233
                })
234
                as $relation
235
            ) {
236
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
237
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['R_CONSTRAINT_NAME'];
238
            }
239
            foreach ($relations as $name => $data) {
240
                $rcolumns = Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
241
                    ->query(
242
                        "SELECT COLUMN_NAME FROM all_cons_columns WHERE OWNER = ? AND CONSTRAINT_NAME = ? ORDER BY POSITION",
243
                        [ $owner, current($data['keymap']) ]
244
                    ))
245
                    ->map(function ($v) {
246
                        $new = [];
247
                        foreach ($v as $kk => $vv) {
248
                            $new[strtoupper($kk)] = $vv;
249
                        }
250
                        return $new;
251
                    })
252
                    ->pluck('COLUMN_NAME')
253
                    ->toArray();
254
                foreach ($data['keymap'] as $column => $related) {
255
                    $data['keymap'][$column] = array_shift($rcolumns);
256
                }
257
                $relname = $data['table'];
258
                $cntr = 1;
259
                while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
260
                    $relname = $data['table'] . '_' . (++ $cntr);
261
                }
262
                $definition->addRelation(
263
                    new TableRelation(
264
                        $relname,
265
                        $this->table($data['table'], true),
266
                        $data['keymap'],
267
                        false
268
                    )
269
                );
270
            }
271
        }
272
        return $definition->toLowerCase();
273
    }
274
    public function tables() : array
275
    {
276
        return Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
277
            ->query(
278
                "SELECT TABLE_NAME FROM ALL_TABLES where OWNER = ?",
279
                [$this->connection['name']]
280
            ))
281
            ->map(function ($v) {
282
                $new = [];
283
                foreach ($v as $kk => $vv) {
284
                    $new[strtoupper($kk)] = $vv;
285
                }
286
                return $new;
287
            })
288
            ->pluck('TABLE_NAME')
289
            ->map(function ($v) {
290
                return $this->table($v);
291
            })
292
            ->toArray();
293
    }
294
}