Issues (265)

src/Driver/SQLServer/SQLServerDriver.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Driver\SQLServer;
13
14
use Cycle\Database\Config\DriverConfig;
15
use Cycle\Database\Config\SQLServerDriverConfig;
16
use Cycle\Database\Driver\Driver;
17
use Cycle\Database\Driver\PDOStatementInterface;
18
use Cycle\Database\Driver\SQLServer\Query\SQLServerDeleteQuery;
19
use Cycle\Database\Driver\SQLServer\Query\SQLServerInsertQuery;
20
use Cycle\Database\Driver\SQLServer\Query\SQLServerSelectQuery;
21
use Cycle\Database\Driver\SQLServer\Query\SQLServerUpdateQuery;
22
use Cycle\Database\Exception\DriverException;
23
use Cycle\Database\Exception\StatementException;
24
use Cycle\Database\Injection\ParameterInterface;
25
use Cycle\Database\Query\QueryBuilder;
26
27
class SQLServerDriver extends Driver
28
{
29
    /**
30
     * @var non-empty-string
31
     */
32 2
    protected const DATETIME = 'Y-m-d\TH:i:s.000';
33
34 2
    /**
35
     * @param SQLServerDriverConfig $config
36
     *
37
     * @throws DriverException
38
     */
39
    public static function create(DriverConfig $config): static
40
    {
41
        $driver = new static(
42
            $config,
43
            new SQLServerHandler(),
44
            new SQLServerCompiler('[]'),
45 928
            new QueryBuilder(
46
                new SQLServerSelectQuery(),
47 928
                new SQLServerInsertQuery(),
48
                new SQLServerUpdateQuery(),
49 928
                new SQLServerDeleteQuery(),
50 486
            ),
51 4
        );
52
53 486
        if ((int) $driver->getPDO()->getAttribute(\PDO::ATTR_SERVER_VERSION) < 12) {
54
            throw new DriverException('SQLServer driver supports only 12+ version of SQLServer');
55
        }
56 486
57
        return $driver;
58 486
    }
59 96
60 96
    public function getType(): string
61
    {
62
        return 'SQLServer';
63 486
    }
64 2
65
    /**
66
     * Bind parameters into statement. SQLServer need encoding to be specified for binary parameters.
67 486
     */
68
    protected function bindParameters(
69
        \PDOStatement|PDOStatementInterface $statement,
70
        iterable $parameters,
71
    ): \PDOStatement|PDOStatementInterface {
72
        $index = 0;
73
74
        foreach ($parameters as $name => $parameter) {
75
            if (\is_string($name)) {
76
                $index = $name;
77
            } else {
78
                $index++;
79
            }
80
81
            $type = \PDO::PARAM_STR;
82 486
83 486
            if ($parameter instanceof ParameterInterface) {
84
                $type = $parameter->getType();
85
                $parameter = $parameter->getValue();
86 928
            }
87
88
            /** @since PHP 8.1 */
89
            if ($parameter instanceof \BackedEnum) {
0 ignored issues
show
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
90
                $type = \PDO::PARAM_STR;
91
                $parameter = $parameter->value;
92
            }
93
94
            if ($parameter instanceof \DateTimeInterface) {
95
                $parameter = $this->formatDatetime($parameter);
96
            }
97 8
98
            if ($type === \PDO::PARAM_LOB) {
99 8
                /** @psalm-suppress UndefinedConstant */
100
                $statement->bindParam(
101 8
                    $index,
102 8
                    $parameter,
103
                    $type,
104
                    0,
105
                    \PDO::SQLSRV_ENCODING_BINARY,
106
                );
107
108
                unset($parameter);
109
                continue;
110
            }
111 2
112
            // numeric, @see http://php.net/manual/en/pdostatement.bindparam.php
113 2
            $statement->bindValue($index, $parameter, $type);
114
            unset($parameter);
115
        }
116 2
117
        return $statement;
118
    }
119
120
    /**
121
     * Create nested transaction save point.
122
     *
123
     * @link http://en.wikipedia.org/wiki/Savepoint
124
     *
125 6
     * @param int $level   Savepoint name/id, must not contain spaces and be valid database
126
     *                     identifier.
127 6
     */
128
    protected function createSavepoint(int $level): void
129 6
    {
130 6
        $this->logger?->info("Transaction: new savepoint 'SVP{$level}'");
131
132
        $this->execute('SAVE TRANSACTION ' . $this->identifier("SVP{$level}"));
133
    }
134
135 8
    /**
136
     * Commit/release savepoint.
137 8
     *
138
     * @link http://en.wikipedia.org/wiki/Savepoint
139
     *
140
     * @param int $level Savepoint name/id, must not contain spaces and be valid database identifier.
141 8
     */
142 8
    protected function releaseSavepoint(int $level): void
143 8
    {
144
        $this->logger?->info("Transaction: release savepoint 'SVP{$level}'");
145
146
        // SQLServer automatically commits nested transactions with parent transaction
147
    }
148 8
149 2
    /**
150
     * Rollback savepoint.
151
     *
152 6
     * @link http://en.wikipedia.org/wiki/Savepoint
153
     *
154
     * @param int $level Savepoint name/id, must not contain spaces and be valid database identifier.
155
     */
156
    protected function rollbackSavepoint(int $level): void
157
    {
158
        $this->logger?->info("Transaction: rollback savepoint 'SVP{$level}'");
159
160 4
        $this->execute('ROLLBACK TRANSACTION ' . $this->identifier("SVP{$level}"));
161
    }
162 4
163
    protected function mapException(\Throwable $exception, string $query): StatementException
164 4
    {
165 4
        $message = \strtolower($exception->getMessage());
166 4
167
168
        if (
169 4
            \str_contains($message, '0800')
170
            || \str_contains($message, '080p')
171
            || \str_contains($message, 'connection')
172
        ) {
173 4
            return new StatementException\ConnectionException($exception, $query);
174
        }
175
176
        if ((int) $exception->getCode() === 23000) {
177
            return new StatementException\ConstrainException($exception, $query);
178
        }
179
180
        return new StatementException($exception, $query);
181
    }
182
}
183