Completed
Pull Request — master (#1557)
by
unknown
01:36
created

PdoAdapter::createSchema()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use BadMethodCallException;
32
use Phinx\Db\Table\Table;
33
use Phinx\Db\Util\AlterInstructions;
34
use Phinx\Migration\MigrationInterface;
35
use Symfony\Component\Console\Output\OutputInterface;
36
37
/**
38
 * Phinx PDO Adapter.
39
 *
40
 * @author Rob Morgan <[email protected]>
41
 */
42
abstract class PdoAdapter extends AbstractAdapter
43
{
44
    /**
45
     * @var \PDO|null
46
     */
47
    protected $connection;
48
49
    /**
50
     * Writes a message to stdout if verbose output is on
51 287
     *
52
     * @param string $message The message to show
53 287
     * @return void
54
     */
55 287
    protected function verboseLog($message)
56 3
    {
57 3
        if (!$this->isDryRunEnabled() &&
58
             $this->getOutput()->getVerbosity() < OutputInterface::VERBOSITY_VERY_VERBOSE) {
59 287
            return;
60
        }
61
62
        $this->getOutput()->writeln($message);
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68 193
    public function setOptions(array $options)
69
    {
70 193
        parent::setOptions($options);
71
72
        if (isset($options['connection'])) {
73 193
            $this->setConnection($options['connection']);
74 191
        }
75 191
76 74
        return $this;
77 74
    }
78
79
    /**
80
     * Sets the database connection.
81
     *
82
     * @param \PDO $connection Connection
83
     * @return \Phinx\Db\Adapter\AdapterInterface
84
     */
85
    public function setConnection(\PDO $connection)
86 74
    {
87
        $this->connection = $connection;
88
89
        // Create the schema table if it doesn't already exist
90
        if (!$this->hasSchemaTable()) {
91
            $this->createSchemaTable();
92
        } else {
93 193
            $table = new \Phinx\Db\Table($this->getSchemaTableName(), [], $this);
94
            if (!$table->hasColumn('migration_name')) {
95
                $table
96
                    ->addColumn(
97
                        'migration_name',
98
                        'string',
99
                        ['limit' => 100, 'after' => 'version', 'default' => null, 'null' => true]
100
                    )
101 191
                    ->save();
102
            }
103 191
            if (!$table->hasColumn('breakpoint')) {
104 189
                $table
105 189
                    ->addColumn('breakpoint', 'boolean', ['default' => false])
106 191
                    ->save();
107
            }
108
        }
109
110
        return $this;
111
    }
112 1
113
    /**
114 1
     * Gets the database connection
115
     *
116
     * @return \PDO
117
     */
118
    public function getConnection()
119
    {
120
        if ($this->connection === null) {
121
            $this->connect();
122
        }
123
124
        return $this->connection;
125
    }
126 218
127
    /**
128 218
     * {@inheritdoc}
129 3
     */
130 3
    public function connect()
131
    {
132
    }
133 217
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function disconnect()
138
    {
139
    }
140
141
    /**
142 220
     * {@inheritdoc}
143
     */
144 220
    public function execute($sql)
145
    {
146
        $this->verboseLog($sql);
147
148
        if ($this->isDryRunEnabled()) {
149
            return 0;
150 151
        }
151
152 151
        return $this->getConnection()->exec($sql);
153 151
    }
154
155
    /**
156
     * Returns the Cake\Database connection object using the same underlying
157
     * PDO object as this connection.
158
     *
159 213
     * @return \Cake\Database\Connection
160
     */
161 213
    abstract public function getDecoratedConnection();
162 213
163 213
    /**
164 208
     * {@inheritdoc}
165 208
     */
166 213
    public function getQueryBuilder()
167
    {
168
        return $this->getDecoratedConnection()->newQuery();
169
    }
170
171
    /**
172 1
     * Executes a query and returns PDOStatement.
173
     *
174 1
     * @param string $sql SQL
175 1
     * @return \PDOStatement
176 1
     */
177 1
    public function query($sql)
178
    {
179 1
        return $this->getConnection()->query($sql);
180 1
    }
181 1
182
    /**
183 1
     * {@inheritdoc}
184 1
     */
185 1
    public function fetchRow($sql)
186
    {
187
        $result = $this->query($sql);
188
189
        return $result->fetch();
190 11
    }
191
192 11
    /**
193 11
     * {@inheritdoc}
194 11
     */
195 11
    public function fetchAll($sql)
196
    {
197 11
        $rows = [];
198 11
        $result = $this->query($sql);
199 11
        while ($row = $result->fetch()) {
200
            $rows[] = $row;
201 11
        }
202 11
203 11
        return $rows;
204 11
    }
205 11
206 11
    /**
207
     * {@inheritdoc}
208 11
     */
209 11
    public function insert(Table $table, $row)
210
    {
211 11
        $sql = sprintf(
212 11
            'INSERT INTO %s ',
213 11
            $this->quoteTableName($table->getName())
214
        );
215 11
        $columns = array_keys($row);
216 11
        $sql .= '(' . implode(', ', array_map([$this, 'quoteColumnName'], $columns)) . ')';
217 11
218
        foreach ($row as $column => $value) {
219
            if (is_bool($value)) {
220
                $row[$column] = $this->castToBool($value);
221
            }
222 5
        }
223
224 5
        if ($this->isDryRunEnabled()) {
225
            $sql .= ' VALUES (' . implode(', ', array_map([$this, 'quoteValue'], $row)) . ');';
226 5
            $this->output->writeln($sql);
227
        } else {
228
            $sql .= ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
229
            $stmt = $this->getConnection()->prepare($sql);
230
            $stmt->execute(array_values($row));
231
        }
232 8
    }
233
234 8
    /**
235
     * Quotes a database value.
236 8
     *
237 8
     * @param mixed $value  The value to quote
238 6
     * @return mixed
239 6
     */
240 2
    private function quoteValue($value)
241 1
    {
242 1
        if (is_numeric($value)) {
243 1
            return $value;
244 1
        }
245 8
246
        if ($value === null) {
247 7
            return 'null';
248 7
        }
249 7
250 7
        return $this->getConnection()->quote($value);
251
    }
252 7
253
    /**
254
     * Quotes a database string.
255
     *
256
     * @param string $value  The string to quote
257
     * @return string
258 5
     */
259
    protected function quoteString($value)
260 5
    {
261
        return $this->getConnection()->quote($value);
262 5
    }
263 5
264 5
    /**
265 5
     * {@inheritdoc}
266 5
     */
267 5
    public function bulkinsert(Table $table, $rows)
268 5
    {
269 5
        $sql = sprintf(
270 5
            'INSERT INTO %s ',
271 5
            $this->quoteTableName($table->getName())
272 5
        );
273 5
        $current = current($rows);
274 5
        $keys = array_keys($current);
275 5
        $sql .= '(' . implode(', ', array_map([$this, 'quoteColumnName'], $keys)) . ') VALUES ';
276
277 5
        if ($this->isDryRunEnabled()) {
278 5
            $values = array_map(function ($row) {
279
                return '(' . implode(', ', array_map([$this, 'quoteValue'], $row)) . ')';
280 3
            }, $rows);
281 3
            $sql .= implode(', ', $values) . ';';
282 3
            $this->output->writeln($sql);
283 3
        } else {
284 3
            $count_keys = count($keys);
285 3
            $query = '(' . implode(', ', array_fill(0, $count_keys, '?')) . ')';
286
            $count_vars = count($rows);
287 3
            $queries = array_fill(0, $count_vars, $query);
288
            $sql .= implode(',', $queries);
289
            $stmt = $this->getConnection()->prepare($sql);
290 5
            $vals = [];
291
292
            foreach ($rows as $row) {
293
                foreach ($row as $v) {
294
                    if (is_bool($v)) {
295
                        $vals[] = $this->castToBool($v);
296 1
                    } else {
297
                        $vals[] = $v;
298 1
                    }
299 1
                }
300 1
            }
301 1
302 1
            $stmt->execute($vals);
303 1
        }
304 1
    }
305 1
306 1
    /**
307 1
     * {@inheritdoc}
308 1
     */
309 1
    public function getVersions()
310
    {
311 1
        $rows = $this->getVersionLog();
312
313
        return array_keys($rows);
314
    }
315
316
    /**
317 1
     * {@inheritdoc}
318
     */
319 1
    public function getVersionLog()
320 1
    {
321 1
        $result = [];
322 1
323 1
        switch ($this->options['version_order']) {
324 1
            case \Phinx\Config\Config::VERSION_ORDER_CREATION_TIME:
325 1
                $orderBy = 'version ASC';
326 1
                break;
327 1
            case \Phinx\Config\Config::VERSION_ORDER_EXECUTION_TIME:
328
                $orderBy = 'start_time ASC, version ASC';
329
                break;
330
            default:
331
                throw new \RuntimeException('Invalid version_order configuration option');
332
        }
333
334
        $rows = $this->fetchAll(sprintf('SELECT * FROM %s ORDER BY %s', $this->getSchemaTableName(), $orderBy));
335
        foreach ($rows as $version) {
336
            $result[$version['version']] = $version;
337
        }
338
339
        return $result;
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     */
345
    public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime)
346
    {
347
        if (strcasecmp($direction, MigrationInterface::UP) === 0) {
348
            // up
349 208
            $sql = sprintf(
350
                "INSERT INTO %s (%s, %s, %s, %s, %s) VALUES ('%s', '%s', '%s', '%s', %s);",
351
                $this->quoteTableName($this->getSchemaTableName()),
352 208
                $this->quoteColumnName('version'),
353 208
                $this->quoteColumnName('migration_name'),
354 208
                $this->quoteColumnName('start_time'),
355 208
                $this->quoteColumnName('end_time'),
356 208
                $this->quoteColumnName('breakpoint'),
357 208
                $migration->getVersion(),
358 208
                substr($migration->getName(), 0, 100),
359 208
                $startTime,
360 208
                $endTime,
361 208
                $this->castToBool(false)
362 208
            );
363 208
364 208
            $this->execute($sql);
365 208
        } else {
366 208
            // down
367 208
            $sql = sprintf(
368
                "DELETE FROM %s WHERE %s = '%s'",
369 208
                $this->quoteTableName($this->getSchemaTableName()),
370 208
                $this->quoteColumnName('version'),
371 208
                $migration->getVersion()
372 208
            );
373 208
374
            $this->execute($sql);
375
        }
376
377
        return $this;
378
    }
379 121
380
    /**
381 121
     * {@inheritdoc}
382
     */
383
    public function toggleBreakpoint(MigrationInterface $migration)
384
    {
385
        $this->query(
386
            sprintf(
387
                'UPDATE %1$s SET %2$s = CASE %2$s WHEN %3$s THEN %4$s ELSE %3$s END, %7$s = %7$s WHERE %5$s = \'%6$s\';',
388
                $this->getSchemaTableName(),
389
                $this->quoteColumnName('breakpoint'),
390
                $this->castToBool(true),
391
                $this->castToBool(false),
392
                $this->quoteColumnName('version'),
393
                $migration->getVersion(),
394
                $this->quoteColumnName('start_time')
395
            )
396
        );
397
398
        return $this;
399
    }
400
401
    /**
402
     * {@inheritdoc}
403
     */
404
    public function resetAllBreakpoints()
405
    {
406
        return $this->execute(
407
            sprintf(
408
                'UPDATE %1$s SET %2$s = %3$s, %4$s = %4$s WHERE %2$s <> %3$s;',
409
                $this->getSchemaTableName(),
410
                $this->quoteColumnName('breakpoint'),
411
                $this->castToBool(false),
412
                $this->quoteColumnName('start_time')
413
            )
414
        );
415
    }
416
417
    /**
418
     * {@inheritdoc}
419
     */
420
    public function createSchema($schemaName = 'public')
421
    {
422
        throw new BadMethodCallException('Creating a schema is not supported');
423
    }
424
425
    /**
426
     * {@inheritdoc}
427
     */
428
    public function dropSchema($name)
429
    {
430
        throw new BadMethodCallException('Dropping a schema is not supported');
431
    }
432
433
    /**
434
     * {@inheritdoc}
435
     */
436
    public function getColumnTypes()
437
    {
438
        return [
439
            'string',
440
            'char',
441
            'text',
442
            'smallinteger',
443
            'integer',
444
            'biginteger',
445
            'bit',
446
            'float',
447
            'decimal',
448
            'double',
449
            'datetime',
450
            'timestamp',
451
            'time',
452
            'date',
453
            'blob',
454
            'binary',
455
            'varbinary',
456
            'boolean',
457
            'uuid',
458
            // Geospatial data types
459
            'geometry',
460
            'point',
461
            'linestring',
462
            'polygon',
463
        ];
464
    }
465
466
    /**
467
     * {@inheritdoc}
468
     */
469
    public function castToBool($value)
470
    {
471
        return (bool)$value ? 1 : 0;
472
    }
473
474
    /**
475
     * Retrieve a database connection attribute
476
     * @see http://php.net/manual/en/pdo.getattribute.php
477
     *
478
     * @param int $attribute One of the PDO::ATTR_* constants
479
     * @return mixed
480
     */
481
    public function getAttribute($attribute)
482
    {
483
        return $this->connection->getAttribute($attribute);
484
    }
485
486
    /**
487
     * Get the definition for a `DEFAULT` statement.
488
     *
489
     * @param  mixed $default Default value
490
     * @param string $columnType column type added
491
     * @return string
492
     */
493 View Code Duplication
    protected function getDefaultValueDefinition($default, $columnType = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
494
    {
495
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
496
            $default = $this->getConnection()->quote($default);
497
        } elseif (is_bool($default)) {
498
            $default = $this->castToBool($default);
499
        } elseif ($default !== null && $columnType === static::PHINX_TYPE_BOOLEAN) {
500
            $default = $this->castToBool((bool)$default);
501
        }
502
503
        return isset($default) ? " DEFAULT $default" : '';
504
    }
505
506
    /**
507
     * Executes all the ALTER TABLE instructions passed for the given table
508
     *
509
     * @param string $tableName The table name to use in the ALTER statement
510
     * @param AlterInstructions $instructions The object containing the alter sequence
511
     * @return void
512
     */
513
    protected function executeAlterSteps($tableName, AlterInstructions $instructions)
514
    {
515
        $alter = sprintf('ALTER TABLE %s %%s', $this->quoteTableName($tableName));
516
        $instructions->execute($alter, [$this, 'execute']);
517
    }
518
}
519