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

dg / dibi / 22284472534

22 Feb 2026 08:11PM UTC coverage: 76.31% (-0.2%) from 76.552%
22284472534

push

github

dg
phpstan

56 of 106 new or added lines in 23 files covered. (52.83%)

82 existing lines in 8 files now uncovered.

1762 of 2309 relevant lines covered (76.31%)

0.76 hits per line

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

88.31
/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
                                throw new Dibi\DriverException($info[0]['message'] ?? 'Unknown error.', $info[0]['code'] ?? 0);
×
68
                        }
69

70
                        sqlsrv_configure('WarningsReturnAsErrors', 1);
1✔
71
                }
72
        }
1✔
73

74

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

83

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

93
                if (is_resource($res)) {
1✔
94
                        $this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res));
1✔
95
                        return sqlsrv_num_fields($res)
1✔
96
                                ? $this->createResultDriver($res)
1✔
97
                                : null;
1✔
98

99
                } else {
100
                        $info = sqlsrv_errors();
1✔
101
                        throw new Dibi\DriverException($info[0]['message'] ?? 'Unknown error.', $info[0]['code'] ?? 0, $sql);
1✔
102
                }
103
        }
104

105

106
        /**
107
         * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
108
         */
109
        public function getAffectedRows(): ?int
110
        {
111
                return $this->affectedRows;
1✔
112
        }
113

114

115
        /**
116
         * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
117
         */
118
        public function getInsertId(?string $sequence): ?int
1✔
119
        {
120
                $res = sqlsrv_query($this->connection, 'SELECT SCOPE_IDENTITY()');
1✔
121
                if (is_resource($res)) {
1✔
122
                        $row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC);
1✔
123
                        return $row ? Dibi\Helpers::intVal($row[0]) : null;
1✔
124
                }
125

UNCOV
126
                return null;
×
127
        }
128

129

130
        /**
131
         * Begins a transaction (if supported).
132
         * @throws Dibi\DriverException
133
         */
134
        public function begin(?string $savepoint = null): void
1✔
135
        {
136
                sqlsrv_begin_transaction($this->connection);
1✔
137
        }
1✔
138

139

140
        /**
141
         * Commits statements in a transaction.
142
         * @throws Dibi\DriverException
143
         */
144
        public function commit(?string $savepoint = null): void
1✔
145
        {
146
                sqlsrv_commit($this->connection);
1✔
147
        }
1✔
148

149

150
        /**
151
         * Rollback changes in a transaction.
152
         * @throws Dibi\DriverException
153
         */
154
        public function rollback(?string $savepoint = null): void
1✔
155
        {
156
                sqlsrv_rollback($this->connection);
1✔
157
        }
1✔
158

159

160
        /**
161
         * Returns the connection resource.
162
         * @return resource|null
163
         */
164
        public function getResource(): mixed
165
        {
166
                return is_resource($this->connection) ? $this->connection : null;
1✔
167
        }
168

169

170
        /**
171
         * Returns the connection reflector.
172
         */
173
        public function getReflector(): Dibi\Reflector
174
        {
175
                return new SqlsrvReflector($this);
1✔
176
        }
177

178

179
        /**
180
         * Result set driver factory.
181
         * @param  resource  $resource
182
         */
183
        public function createResultDriver($resource): SqlsrvResult
184
        {
185
                return new SqlsrvResult($resource);
1✔
186
        }
187

188

189
        /********************* SQL ****************d*g**/
190

191

192
        /**
193
         * Encodes data for use in a SQL statement.
194
         */
195
        public function escapeText(string $value): string
1✔
196
        {
197
                return "N'" . str_replace("'", "''", $value) . "'";
1✔
198
        }
199

200

201
        public function escapeBinary(string $value): string
202
        {
UNCOV
203
                return '0x' . bin2hex($value);
×
204
        }
205

206

207
        public function escapeIdentifier(string $value): string
1✔
208
        {
209
                // @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
210
                return '[' . str_replace(']', ']]', $value) . ']';
1✔
211
        }
212

213

214
        public function escapeBool(bool $value): string
1✔
215
        {
216
                return $value ? '1' : '0';
1✔
217
        }
218

219

220
        public function escapeDate(\DateTimeInterface $value): string
1✔
221
        {
222
                return $value->format("'Y-m-d'");
1✔
223
        }
224

225

226
        public function escapeDateTime(\DateTimeInterface $value): string
1✔
227
        {
228
                return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
1✔
229
        }
230

231

232
        public function escapeDateInterval(\DateInterval $value): string
233
        {
UNCOV
234
                throw new Dibi\NotImplementedException;
×
235
        }
236

237

238
        /**
239
         * Encodes string for use in a LIKE statement.
240
         */
241
        public function escapeLike(string $value, int $pos): string
1✔
242
        {
243
                $value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
1✔
244
                return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
1✔
245
        }
246

247

248
        /**
249
         * Injects LIMIT/OFFSET to the SQL query.
250
         */
251
        public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
1✔
252
        {
253
                if ($limit < 0 || $offset < 0) {
1✔
254
                        throw new Dibi\NotSupportedException('Negative offset or limit.');
1✔
255

256
                } elseif ($limit !== null) {
1✔
257
                        // requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
258
                        $sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
1✔
259
                } elseif ($offset) {
1✔
260
                        // requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
261
                        $sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
1✔
262
                }
263
        }
1✔
264
}
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