• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dg / dibi / 22282875037

22 Feb 2026 06:32PM UTC coverage: 76.552% (-0.5%) from 77.004%
22282875037

push

github

dg
phpstan

55 of 96 new or added lines in 23 files covered. (57.29%)

1 existing line in 1 file now uncovered.

1763 of 2303 relevant lines covered (76.55%)

0.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.19
/src/Dibi/Drivers/SqlsrvDriver.php
1
<?php declare(strict_types=1);
2

3
/**
4
 * This file is part of the Dibi, smart database abstraction layer (https://dibi.nette.org)
5
 * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Dibi\Drivers;
9

10
use Dibi;
11
use Dibi\Helpers;
12
use function is_resource, sprintf;
13

14

15
/**
16
 * The driver for Microsoft SQL Server and SQL Azure databases.
17
 *
18
 * Driver options:
19
 *   - host => the MS SQL server host name. It can also include a port number (hostname:port)
20
 *   - username (or user)
21
 *   - password (or pass)
22
 *   - database => the database name to select
23
 *   - options (array) => connection options {@link https://msdn.microsoft.com/en-us/library/cc296161(SQL.90).aspx}
24
 *   - charset => character encoding to set (default is UTF-8)
25
 *   - resource (resource) => existing connection resource
26
 */
27
class SqlsrvDriver implements Dibi\Driver
28
{
29
        /** @var resource */
30
        private $connection;
31
        private ?int $affectedRows;
32

33

34
        /**
35
         * @param  array<string, mixed>  $config
36
         * @throws Dibi\NotSupportedException
37
         */
38
        public function __construct(array $config)
1✔
39
        {
40
                if (!extension_loaded('sqlsrv')) {
1✔
41
                        throw new Dibi\NotSupportedException("PHP extension 'sqlsrv' is not loaded.");
×
42
                }
43

44
                Helpers::alias($config, 'options|UID', 'username');
1✔
45
                Helpers::alias($config, 'options|PWD', 'password');
1✔
46
                Helpers::alias($config, 'options|Database', 'database');
1✔
47
                Helpers::alias($config, 'options|CharacterSet', 'charset');
1✔
48

49
                if (isset($config['resource'])) {
1✔
50
                        $this->connection = $config['resource'];
×
51
                        if (!is_resource($this->connection)) {
×
52
                                throw new \InvalidArgumentException("Configuration option 'resource' is not resource.");
×
53
                        }
54
                } else {
55
                        $options = $config['options'];
1✔
56

57
                        // Default values
58
                        $options['CharacterSet'] ??= 'UTF-8';
1✔
59
                        $options['PWD'] = (string) $options['PWD'];
1✔
60
                        $options['UID'] = (string) $options['UID'];
1✔
61
                        $options['Database'] = (string) $options['Database'];
1✔
62

63
                        sqlsrv_configure('WarningsReturnAsErrors', 0);
1✔
64
                        $this->connection = sqlsrv_connect($config['host'], $options);
1✔
65
                        if (!is_resource($this->connection)) {
1✔
66
                                $info = sqlsrv_errors(SQLSRV_ERR_ERRORS);
×
NEW
67
                                if (!$info) {
×
NEW
68
                                        throw new Dibi\DriverException('Unknown error.');
×
69
                                }
70

UNCOV
71
                                throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
×
72
                        }
73

74
                        sqlsrv_configure('WarningsReturnAsErrors', 1);
1✔
75
                }
76
        }
1✔
77

78

79
        /**
80
         * Disconnects from a database.
81
         */
82
        public function disconnect(): void
83
        {
84
                @sqlsrv_close($this->connection); // @ - connection can be already disconnected
1✔
85
        }
1✔
86

87

88
        /**
89
         * Executes the SQL query.
90
         * @throws Dibi\DriverException
91
         */
92
        public function query(string $sql): ?Dibi\ResultDriver
1✔
93
        {
94
                $this->affectedRows = null;
1✔
95
                $res = sqlsrv_query($this->connection, $sql);
1✔
96

97
                if (!is_resource($res)) {
1✔
98
                        $info = sqlsrv_errors();
1✔
99
                        if (!$info) {
1✔
NEW
100
                                throw new Dibi\DriverException('Unknown error.', 0, $sql);
×
101
                        }
102

103
                        throw new Dibi\DriverException($info[0]['message'], $info[0]['code'], $sql);
1✔
104

105
                } else {
106
                        $this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res));
1✔
107
                        return sqlsrv_num_fields($res)
1✔
108
                                ? $this->createResultDriver($res)
1✔
109
                                : null;
1✔
110
                }
111
        }
112

113

114
        /**
115
         * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
116
         */
117
        public function getAffectedRows(): ?int
118
        {
119
                return $this->affectedRows;
1✔
120
        }
121

122

123
        /**
124
         * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
125
         */
126
        public function getInsertId(?string $sequence): ?int
1✔
127
        {
128
                $res = sqlsrv_query($this->connection, 'SELECT SCOPE_IDENTITY()');
1✔
129
                if (is_resource($res)) {
1✔
130
                        $row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC);
1✔
131
                        return $row ? Dibi\Helpers::intVal($row[0]) : null;
1✔
132
                }
133

134
                return null;
×
135
        }
136

137

138
        /**
139
         * Begins a transaction (if supported).
140
         * @throws Dibi\DriverException
141
         */
142
        public function begin(?string $savepoint = null): void
1✔
143
        {
144
                sqlsrv_begin_transaction($this->connection);
1✔
145
        }
1✔
146

147

148
        /**
149
         * Commits statements in a transaction.
150
         * @throws Dibi\DriverException
151
         */
152
        public function commit(?string $savepoint = null): void
1✔
153
        {
154
                sqlsrv_commit($this->connection);
1✔
155
        }
1✔
156

157

158
        /**
159
         * Rollback changes in a transaction.
160
         * @throws Dibi\DriverException
161
         */
162
        public function rollback(?string $savepoint = null): void
1✔
163
        {
164
                sqlsrv_rollback($this->connection);
1✔
165
        }
1✔
166

167

168
        /**
169
         * Returns the connection resource.
170
         * @return resource|null
171
         */
172
        public function getResource(): mixed
173
        {
174
                return is_resource($this->connection) ? $this->connection : null;
1✔
175
        }
176

177

178
        /**
179
         * Returns the connection reflector.
180
         */
181
        public function getReflector(): Dibi\Reflector
182
        {
183
                return new SqlsrvReflector($this);
1✔
184
        }
185

186

187
        /**
188
         * Result set driver factory.
189
         * @param  resource  $resource
190
         */
191
        public function createResultDriver($resource): SqlsrvResult
192
        {
193
                return new SqlsrvResult($resource);
1✔
194
        }
195

196

197
        /********************* SQL ****************d*g**/
198

199

200
        /**
201
         * Encodes data for use in a SQL statement.
202
         */
203
        public function escapeText(string $value): string
1✔
204
        {
205
                return "N'" . str_replace("'", "''", $value) . "'";
1✔
206
        }
207

208

209
        public function escapeBinary(string $value): string
210
        {
211
                return '0x' . bin2hex($value);
×
212
        }
213

214

215
        public function escapeIdentifier(string $value): string
1✔
216
        {
217
                // @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
218
                return '[' . str_replace(']', ']]', $value) . ']';
1✔
219
        }
220

221

222
        public function escapeBool(bool $value): string
1✔
223
        {
224
                return $value ? '1' : '0';
1✔
225
        }
226

227

228
        public function escapeDate(\DateTimeInterface $value): string
1✔
229
        {
230
                return $value->format("'Y-m-d'");
1✔
231
        }
232

233

234
        public function escapeDateTime(\DateTimeInterface $value): string
1✔
235
        {
236
                return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
1✔
237
        }
238

239

240
        public function escapeDateInterval(\DateInterval $value): string
241
        {
242
                throw new Dibi\NotImplementedException;
×
243
        }
244

245

246
        /**
247
         * Encodes string for use in a LIKE statement.
248
         */
249
        public function escapeLike(string $value, int $pos): string
1✔
250
        {
251
                $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
1✔
252
                return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
1✔
253
        }
254

255

256
        /**
257
         * Injects LIMIT/OFFSET to the SQL query.
258
         */
259
        public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
1✔
260
        {
261
                if ($limit < 0 || $offset < 0) {
1✔
262
                        throw new Dibi\NotSupportedException('Negative offset or limit.');
1✔
263

264
                } elseif ($limit !== null) {
1✔
265
                        // requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
266
                        $sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
1✔
267
                } elseif ($offset) {
1✔
268
                        // requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
269
                        $sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
1✔
270
                }
271
        }
1✔
272
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc