1 <?php
2 3 4 5 6 7 8 9 10
11
12 namespace Neevo\Drivers;
13
14 use DateTime;
15 use InvalidArgumentException;
16 use mysqli;
17 use mysqli_result;
18 use Neevo\BaseStatement;
19 use Neevo\DriverException;
20 use Neevo\DriverInterface;
21 use Neevo\Manager;
22 use Neevo\Parser;
23
24
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
44 class MySQLiDriver extends Parser implements DriverInterface {
45
46
47
48 private $resource;
49
50
51 private $unbuffered;
52
53
54 private $affectedRows;
55
56
57 58 59 60
61 public function __construct(BaseStatement $statement = null){
62 if(!extension_loaded("mysqli"))
63 throw new DriverException("Cannot instantiate Neevo MySQLi driver - PHP extension 'mysqli' not loaded.");
64 if($statement instanceof BaseStatement)
65 parent::__construct($statement);
66 }
67
68
69 70 71 72 73
74 public function connect(array $config){
75
76
77 $defaults = array(
78 'resource' => null,
79 'charset' => 'utf8',
80 'username' => ini_get('mysqli.default_user'),
81 'password' => ini_get('mysqli.default_pw'),
82 'database' => '',
83 'socket' => ini_get('mysqli.default_socket'),
84 'port' => ini_get('mysqli.default_port'),
85 'host' => ini_get('mysqli.default_host'),
86 'persistent' => false,
87 'unbuffered' => false
88 );
89
90 $config += $defaults;
91
92
93 if($config['resource'] instanceof mysqli)
94 $this->resource = $config['resource'];
95 else
96 $this->resource = new mysqli($config['host'], $config['username'], $config['password'], $config['database'], $config['port'], $config['socket']);
97
98 if($this->resource->connect_errno)
99 throw new DriverException($this->resource->connect_error, $this->resource->connect_errno);
100
101
102 if($this->resource instanceof mysqli){
103 $ok = @$this->resource->set_charset($config['charset']);
104 if(!$ok)
105 $this->runQuery("SET NAMES " . $config['charset']);
106 }
107
108 $this->unbuffered = $config['unbuffered'];
109 }
110
111
112 113 114
115 public function closeConnection(){
116 @$this->resource->close();
117 }
118
119
120 121 122 123 124
125 public function freeResultSet($resultSet){
126 if($resultSet instanceof mysqli_result)
127 $resultSet->free();
128 }
129
130
131 132 133 134 135 136
137 public function runQuery($queryString){
138
139 $this->affectedRows = false;
140 $result = $this->resource->query($queryString, $this->unbuffered ? MYSQLI_USE_RESULT : MYSQLI_STORE_RESULT);
141
142 $error = str_replace('You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use', 'Syntax error', $this->resource->error);
143 if($error && $result === false)
144 throw new DriverException($error, $this->resource->errno, $queryString);
145
146 $this->affectedRows = $this->resource->affected_rows;
147 return $result;
148 }
149
150
151 152 153 154
155 public function beginTransaction($savepoint = null){
156 $this->runQuery($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
157 }
158
159
160 161 162 163
164 public function commit($savepoint = null){
165 $this->runQuery($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
166 }
167
168
169 170 171 172
173 public function rollback($savepoint = null){
174 $this->runQuery($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
175 }
176
177
178 179 180 181 182
183 public function fetch($resultSet){
184 return $resultSet->fetch_assoc();
185 }
186
187
188 189 190 191 192 193 194
195 public function seek($resultSet, $offset){
196 if($this->unbuffered)
197 throw new DriverException('Cannot seek on unbuffered result.');
198 return $resultSet->data_seek($offset);
199 }
200
201
202 203 204 205
206 public function getInsertId(){
207 return $this->resource->insert_id;
208 }
209
210
211 212 213 214
215 public function randomizeOrder(BaseStatement $statement){
216 $statement->order('RAND()');
217 }
218
219
220 221 222 223 224 225
226 public function getNumRows($resultSet){
227 if($this->unbuffered)
228 throw new DriverException('Cannot count rows on unbuffered result.');
229 if($resultSet instanceof mysqli_result)
230 return $resultSet->num_rows;
231 return false;
232 }
233
234
235 236 237 238
239 public function getAffectedRows(){
240 return $this->affectedRows;
241 }
242
243
244 245 246 247 248 249 250
251 public function escape($value, $type){
252 switch($type){
253 case Manager::BOOL:
254 return $value ? 1 : 0;
255
256 case Manager::TEXT:
257 return "'" . $this->resource->real_escape_string($value) . "'";
258
259 case Manager::IDENTIFIER:
260 return str_replace('`*`', '*', '`' . str_replace('.', '`.`', str_replace('`', '``', $value)) . '`');
261
262 case Manager::BINARY:
263 return "_binary'" . mysqli_real_escape_string($this->resource, $value) . "'";
264
265 case Manager::DATETIME:
266 return ($value instanceof DateTime) ? $value->format("'Y-m-d H:i:s'") : date("'Y-m-d H:i:s'", $value);
267
268 default:
269 throw new InvalidArgumentException('Unsupported data type.');
270 break;
271 }
272 }
273
274
275 276 277 278 279 280 281
282 public function unescape($value, $type){
283 if($type === Manager::BINARY)
284 return $value;
285 throw new InvalidArgumentException('Unsupported data type.');
286 }
287
288
289 290 291 292 293
294 public function getPrimaryKey($table){
295 $key = '';
296 $q = $this->runQuery('SHOW FULL COLUMNS FROM ' . $table);
297 while($col = $this->fetch($q)){
298 if(strtolower($col['Key']) === 'pri' && $key === '')
299 $key = $col['Field'];
300 }
301 return $key;
302 }
303
304
305 306 307 308 309 310
311 public function getColumnTypes($resultSet, $table){
312 static $colTypes;
313 if(empty($colTypes)){
314 $constants = get_defined_constants(true);
315 foreach($constants['mysqli'] as $type => $code){
316 if(strncmp($type, 'MYSQLI_TYPE_', 12) === 0)
317 $colTypes[$code] = strtolower(substr($type, 12));
318 }
319 $colTypes[MYSQLI_TYPE_LONG] = $colTypes[MYSQLI_TYPE_SHORT] = $colTypes[MYSQLI_TYPE_TINY] = 'int';
320 }
321
322 $cols = array();
323 while($field = $resultSet->fetch_field()){
324 $cols[$field->name] = $colTypes[$field->type];
325 }
326 return $cols;
327 }
328
329
330 331 332 333
334 protected function parseUpdateStmt(){
335 $sql = parent::parseUpdateStmt();
336 return $this->applyLimit($sql . $this->clauses[3]);
337 }
338
339
340 341 342 343
344 protected function parseDeleteStmt(){
345 $sql = parent::parseDeleteStmt();
346 return $this->applyLimit($sql . $this->clauses[3]);
347 }
348
349
350 }
351