Passed
Branch main (45b422)
by Andreas
01:40
created

PDO::quote()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 13
c 3
b 0
f 0
dl 0
loc 20
ccs 12
cts 12
cp 1
rs 9.2222
cc 6
nc 6
nop 2
crap 6
1
<?php
2
/**
3
 * Licensed to CRATE Technology GmbH("Crate") under one or more contributor
4
 * license agreements.  See the NOTICE file distributed with this work for
5
 * additional information regarding copyright ownership.  Crate licenses
6
 * this file to you under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.  You may
8
 * obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
15
 * License for the specific language governing permissions and limitations
16
 * under the License.
17
 *
18
 * However, if you have executed another commercial license agreement
19
 * with Crate these terms will supersede the license and you may use the
20
 * software solely pursuant to the terms of the relevant commercial agreement.
21
 */
22
23
declare(strict_types=1);
24
25
namespace Crate\PDO;
26
27
use Crate\PDO\Exception\InvalidArgumentException;
28
use Crate\PDO\Exception\PDOException;
29
use Crate\PDO\Http\ServerInterface;
30
use Crate\PDO\Http\ServerPool;
31
use Crate\Stdlib\ArrayUtils;
32
use PDO as BasePDO;
33
34
class PDO extends BasePDO implements PDOInterface
35
{
36
    public const VERSION     = '1.1.0';
37
    public const DRIVER_NAME = 'crate';
38
39
    public const DSN_REGEX = '/^(?:crate:)(?:((?:[\w\.-]+:\d+\,?)+))\/?([\w]+)?$/';
40
41
    public const CRATE_ATTR_HTTP_BASIC_AUTH = 1000;
42
    public const CRATE_ATTR_DEFAULT_SCHEMA  = 1001;
43
44
    public const CRATE_ATTR_SSL_MODE                                       = 1008;
45
    public const CRATE_ATTR_SSL_MODE_DISABLED                              = 1;
46
    public const CRATE_ATTR_SSL_MODE_ENABLED_BUT_WITHOUT_HOST_VERIFICATION = 2;
47
    public const CRATE_ATTR_SSL_MODE_REQUIRED                              = 3;
48
49
    public const CRATE_ATTR_SSL_KEY_PATH      = 1002;
50
    public const CRATE_ATTR_SSL_KEY_PASSWORD  = 1003;
51
    public const CRATE_ATTR_SSL_CERT_PATH     = 1004;
52
    public const CRATE_ATTR_SSL_CERT_PASSWORD = 1005;
53
    public const CRATE_ATTR_SSL_CA_PATH       = 1006;
54
    public const CRATE_ATTR_SSL_CA_PASSWORD   = 1007;
55
56
    public const PARAM_FLOAT     = 6;
57
    public const PARAM_DOUBLE    = 7;
58
    public const PARAM_LONG      = 8;
59
    public const PARAM_ARRAY     = 9;
60
    public const PARAM_OBJECT    = 10;
61
    public const PARAM_TIMESTAMP = 11;
62
    public const PARAM_IP        = 12;
63
64
    /**
65
     * @var array
66
     */
67
    private $attributes = [
68
        'defaultFetchMode' => self::FETCH_BOTH,
69
        'errorMode'        => self::ERRMODE_SILENT,
70
        'sslMode'          => self::CRATE_ATTR_SSL_MODE_DISABLED,
71
        'statementClass'   => PDOStatement::class,
72
        'timeout'          => 0.0,
73
        'auth'             => [],
74
        'defaultSchema'    => 'doc',
75
    ];
76
77
    /**
78
     * @var Http\ServerInterface
79
     */
80
    private $server;
81
82
    /**
83
     * @var PDOStatement|null
84
     */
85
    private $lastStatement;
86
87
    /**
88
     * @var callable
89
     */
90
    private $request;
91
92
    /**
93
     * {@inheritDoc}
94
     *
95
     * @param string     $dsn      The HTTP endpoint to call
96
     * @param null       $username Username for basic auth
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $username is correct as it would always require null to be passed?
Loading history...
97
     * @param null       $passwd   Password for basic auth
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $passwd is correct as it would always require null to be passed?
Loading history...
98
     * @param null|array $options  Attributes to set on the PDO
99
     */
100 16
    public function __construct($dsn, $username = null, $passwd = null, $options = [])
101
    {
102 16
        $dsnParts = self::parseDSN($dsn);
103 16
        $servers  = self::serversFromDsnParts($dsnParts);
104
105 16
        $this->setServer(new ServerPool($servers));
106
107 16
        foreach ((array)$options as $attribute => $value) {
108 1
            $this->setAttribute($attribute, $value);
109
        }
110
111 16
        if (!empty($username)) {
112 2
            $this->setAttribute(self::CRATE_ATTR_HTTP_BASIC_AUTH, [$username, $passwd]);
113
        }
114
115 16
        if (!empty($dsnParts[1])) {
116
            $this->setAttribute(self::CRATE_ATTR_DEFAULT_SCHEMA, $dsnParts[1]);
117
        }
118
119
        // Define a callback that will be used in the PDOStatements
120
        // This way we don't expose this as a public api to the end users.
121
        $this->request = function (PDOStatement $statement, $sql, array $parameters) {
122
123 1
            $this->lastStatement = $statement;
124
125
            try {
126 1
                return $this->server->execute($sql, $parameters);
127
            } catch (Exception\RuntimeException $e) {
128
                if ($this->getAttribute(self::ATTR_ERRMODE) === self::ERRMODE_EXCEPTION) {
129
                    throw new Exception\PDOException($e->getMessage(), $e->getCode());
130
                }
131
132
                if ($this->getAttribute(self::ATTR_ERRMODE) === self::ERRMODE_WARNING) {
133
                    trigger_error(sprintf('[%d] %s', $e->getCode(), $e->getMessage()), E_USER_WARNING);
134
                }
135
136
                // should probably wrap this in a error object ?
137
                return [
138
                    'code'    => $e->getCode(),
139
                    'message' => $e->getMessage(),
140
                ];
141
            }
142
        };
143 16
    }
144
145
    /**
146
     * Change the server implementation
147
     *
148
     * @param ServerInterface $server
149
     */
150 13
    public function setServer(ServerInterface $server): void
151
    {
152 13
        $this->server = $server;
153 13
        $this->server->configure($this);
154 13
    }
155
156
    /**
157
     * Extract servers and optional custom schema from DSN string
158
     *
159
     * @param string $dsn The DSN string
160
     *
161
     * @throws \Crate\PDO\Exception\PDOException on an invalid DSN string
162
     *
163
     * @return array An array of ['host:post,host:port,...', 'schema']
164
     */
165 47
    private static function parseDSN($dsn)
166
    {
167 47
        $matches = [];
168
169 47
        if (!preg_match(static::DSN_REGEX, $dsn, $matches)) {
170 4
            throw new PDOException(sprintf('Invalid DSN %s', $dsn));
171
        }
172
173 43
        return array_slice($matches, 1);
174
    }
175
176
    /**
177
     * Extract host:port pairs out of the DSN parts
178
     *
179
     * @param array $dsnParts The parts of the parsed DSN string
180
     *
181
     * @return array An array of host:port strings
182
     */
183 42
    private static function serversFromDsnParts($dsnParts)
184
    {
185 42
        return explode(',', trim($dsnParts[0], ','));
186
    }
187
188
    /**
189
     * {@inheritDoc}
190
     */
191 2
    public function prepare($statement, $options = null)
192
    {
193 2
        $options = ArrayUtils::toArray($options);
194
195 2
        if (isset($options[self::ATTR_CURSOR])) {
196
            trigger_error(sprintf('%s not supported', __METHOD__), E_USER_WARNING);
197
198
            return true;
199
        }
200
201 2
        return new PDOStatement($this, $this->request, $statement, $options);
202
    }
203
204
    /**
205
     * {@inheritDoc}
206
     */
207 1
    public function beginTransaction()
208
    {
209 1
        return true;
210
    }
211
212
    /**
213
     * {@inheritDoc}
214
     */
215 1
    public function commit()
216
    {
217 1
        return true;
218
    }
219
220
    /**
221
     * {@inheritDoc}
222
     */
223 1
    public function rollBack()
224
    {
225 1
        throw new Exception\UnsupportedException;
226
    }
227
228
    /**
229
     * {@inheritDoc}
230
     */
231 1
    public function inTransaction()
232
    {
233 1
        return false;
234
    }
235
236
    /**
237
     * {@inheritDoc}
238
     */
239
    public function exec($statement)
240
    {
241
        $statement = $this->prepare($statement);
242
        $result    = $statement->execute();
243
244
        return $result === false ? false : $statement->rowCount();
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250 1
    public function query($statement)
251
    {
252 1
        $statement = $this->prepare($statement);
253 1
        $result    = $statement->execute();
254
255 1
        return $result === false ? false : $statement;
256
    }
257
258
    /**
259
     * {@inheritDoc}
260
     */
261 1
    public function lastInsertId($name = null)
262
    {
263 1
        throw new Exception\UnsupportedException;
264
    }
265
266
    /**
267
     * {@inheritDoc}
268
     */
269
    public function errorCode()
270
    {
271
        return $this->lastStatement === null ? null : $this->lastStatement->errorCode();
272
    }
273
274
    /**
275
     * {@inheritDoc}
276
     */
277
    public function errorInfo()
278
    {
279
        return $this->lastStatement === null ? null : $this->lastStatement->errorInfo();
280
    }
281
282
    /**
283
     * {@inheritDoc}
284
     *
285
     * @throws \Crate\PDO\Exception\PDOException
286
     * @throws \Crate\PDO\Exception\InvalidArgumentException
287
     */
288 15
    public function setAttribute($attribute, $value)
289
    {
290
        switch ($attribute) {
291 15
            case self::ATTR_DEFAULT_FETCH_MODE:
292 1
                $this->attributes['defaultFetchMode'] = $value;
293 1
                break;
294
295 14
            case self::ATTR_ERRMODE:
296 1
                $this->attributes['errorMode'] = $value;
297 1
                break;
298
299 13
            case self::ATTR_TIMEOUT:
300 2
                $this->attributes['timeout'] = (int)$value;
301 2
                break;
302
303 12
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
304 2
                if (!is_array($value) && $value !== null) {
305
                    throw new InvalidArgumentException(
306
                        'Value probided to CRATE_ATTR_HTTP_BASIC_AUTH must be null or an array'
307
                    );
308
                }
309
310 2
                $this->attributes['auth'] = $value;
311 2
                break;
312
313 12
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
314 2
                $this->attributes['defaultSchema'] = $value;
315 2
                break;
316
317 10
            case self::CRATE_ATTR_SSL_MODE:
318 9
                $this->attributes['sslMode'] = $value;
319 9
                break;
320
321 7
            case self::CRATE_ATTR_SSL_CA_PATH:
322 2
                $this->attributes['sslCa'] = $value;
323 2
                break;
324
325 6
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
326 1
                $this->attributes['sslCaPassword'] = $value;
327 1
                break;
328
329 5
            case self::CRATE_ATTR_SSL_CERT_PATH:
330 2
                $this->attributes['sslCert'] = $value;
331 2
                break;
332
333 4
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
334 1
                $this->attributes['sslCertPassword'] = $value;
335 1
                break;
336
337 3
            case self::CRATE_ATTR_SSL_KEY_PATH:
338 2
                $this->attributes['sslKey'] = $value;
339 2
                break;
340
341 2
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
342 1
                $this->attributes['sslKeyPassword'] = $value;
343 1
                break;
344
345
            default:
346 1
                throw new Exception\PDOException('Unsupported driver attribute');
347
        }
348
349
        // A setting changed so we need to reconfigure the server pool
350 14
        $this->server->configure($this);
351 14
    }
352
353
    /**
354
     * {@inheritDoc}
355
     *
356
     * @throws \Crate\PDO\Exception\PDOException
357
     */
358 23
    public function getAttribute($attribute)
359
    {
360
        switch ($attribute) {
361 23
            case self::ATTR_PERSISTENT:
362 1
                return false;
363
364 23
            case self::ATTR_PREFETCH:
365 1
                return false;
366
367 23
            case self::ATTR_CLIENT_VERSION:
368 1
                return self::VERSION;
369
370 23
            case self::ATTR_SERVER_VERSION:
371
                return $this->server->getServerVersion();
372
373 23
            case self::ATTR_SERVER_INFO:
374
                return $this->server->getServerInfo();
375
376 23
            case self::ATTR_TIMEOUT:
377 23
                return $this->attributes['timeout'];
378
379 23
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
380 23
                return $this->attributes['auth'];
381
382 23
            case self::ATTR_DEFAULT_FETCH_MODE:
383 2
                return $this->attributes['defaultFetchMode'];
384
385 23
            case self::ATTR_ERRMODE:
386 1
                return $this->attributes['errorMode'];
387
388 23
            case self::ATTR_DRIVER_NAME:
389 1
                return static::DRIVER_NAME;
390
391 23
            case self::ATTR_STATEMENT_CLASS:
392
                return [$this->attributes['statementClass']];
393
394 23
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
395 23
                return $this->attributes['defaultSchema'];
396
397 23
            case self::CRATE_ATTR_SSL_MODE:
398 23
                return $this->attributes['sslMode'];
399
400 23
            case self::CRATE_ATTR_SSL_CA_PATH:
401 23
                return $this->attributes['sslCa'] ?? null;
402
403 23
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
404 23
                return $this->attributes['sslCaPassword'] ?? null;
405
406 23
            case self::CRATE_ATTR_SSL_CERT_PATH:
407 23
                return $this->attributes['sslCert'] ?? null;
408
409 23
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
410 23
                return $this->attributes['sslCertPassword'] ?? null;
411
412 23
            case self::CRATE_ATTR_SSL_KEY_PATH:
413 23
                return $this->attributes['sslKey'] ?? null;
414
415 23
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
416 23
                return $this->attributes['sslKeyPassword'] ?? null;
417
418
            default:
419
                // PHP Switch is a lose comparison
420 2
                if ($attribute === self::ATTR_AUTOCOMMIT) {
421 1
                    return true;
422
                }
423
424 1
                throw new Exception\PDOException(sprintf('Unsupported driver attribute: %s', $attribute));
425
        }
426
    }
427
428
    /**
429
     * {@inheritDoc}
430
     */
431 4
    public function quote($string, $parameter_type = self::PARAM_STR)
432
    {
433
        switch ($parameter_type) {
434 4
            case self::PARAM_INT:
435 1
                return (int)$string;
436
437 4
            case self::PARAM_BOOL:
438 1
                return (bool)$string;
439
440 4
            case self::PARAM_NULL:
441 1
                return null;
442
443 3
            case self::PARAM_LOB:
444 1
                throw new Exception\UnsupportedException('This is not supported by crate.io');
445
446 2
            case self::PARAM_STR:
447 1
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
448
449
            default:
450 1
                throw new Exception\InvalidArgumentException('Unknown param type');
451
        }
452
    }
453
454
    /**
455
     * {@inheritDoc}
456
     */
457 1
    public static function getAvailableDrivers()
458
    {
459 1
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
460
    }
461
462
    public function getServerVersion()
463
    {
464
        return $this->server->getServerVersion();
465
    }
466
467
    public function getServerInfo()
468
    {
469
        return $this->getServerVersion();
470
    }
471
}
472