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 Neevo\BaseStatement;
17 use Neevo\Connection;
18 use Neevo\DriverException;
19 use Neevo\DriverInterface;
20 use Neevo\ImplementationException;
21 use Neevo\Manager;
22 use Neevo\Parser;
23 use PDO;
24 use PDOException;
25 use PDOStatement;
26
27
28 29 30 31 32 33 34 35 36 37 38 39 40
41 class PDODriver extends Parser implements DriverInterface {
42
43
44
45 private $resource;
46
47
48 private $driverName;
49
50
51 private $affectedRows;
52
53
54 55 56 57
58 public function __construct(BaseStatement $statement = null){
59 if(!extension_loaded("pdo"))
60 throw new DriverException("Cannot instantiate Neevo PDO driver - PHP extension 'pdo' not loaded.");
61 if($statement instanceof BaseStatement)
62 parent::__construct($statement);
63 }
64
65
66 67 68 69 70
71 public function connect(array $config){
72 Connection::alias($config, 'resource', 'pdo');
73
74
75 $defaults = array(
76 'dsn' => null,
77 'resource' => null,
78 'username' => null,
79 'password' => null,
80 'options' => array(),
81 );
82
83 $config += $defaults;
84
85
86 if($config['resource'] instanceof PDO)
87 $this->resource = $config['resource'];
88 else try{
89 $this->resource = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
90 } catch(PDOException $e){
91 throw new DriverException($e->getMessage(), $e->getCode());
92 }
93
94 if(!$this->resource)
95 throw new DriverException('Connection failed.');
96
97 $this->driverName = $this->resource->getAttribute(PDO::ATTR_DRIVER_NAME);
98 }
99
100
101 102 103
104 public function closeConnection(){
105 @$this->resource = null;
106 }
107
108
109 110 111 112 113
114 public function freeResultSet($resultSet){
115 return $resultSet = null;
116 }
117
118
119 120 121 122 123 124
125 public function runQuery($queryString){
126
127 $cmd = strtoupper(substr(trim($queryString), 0, 6));
128 static $list = array('UPDATE' => 1, 'DELETE' => 1, 'INSERT' => 1, 'REPLAC' => 1);
129 $this->affectedRows = false;
130
131 if(isset($list[$cmd])){
132 $this->affectedRows = $this->resource->exec($queryString);
133
134 if($this->affectedRows === false){
135 $error = $this->resource->errorInfo();
136 throw new DriverException("SQLSTATE[$error[0]]: $error[2]", $error[1], $queryString);
137 } else
138 return true;
139 }
140
141 $result = $this->resource->query($queryString);
142
143 if($result === false){
144 $error = $this->resource->errorInfo();
145 throw new DriverException("SQLSTATE[$error[0]]: $error[2]", $error[1], $queryString);
146 }else
147 return $result;
148 }
149
150
151 152 153 154
155 public function beginTransaction($savepoint = null){
156 if(!$this->resource->beginTransaction()){
157 $error = $this->resource->errorInfo();
158 throw new DriverException("SQLSTATE[$error[0]]: $error[2]", $error[1]);
159 }
160 }
161
162
163 164 165 166
167 public function commit($savepoint = null){
168 if(!$this->resource->commit()){
169 $error = $this->resource->errorInfo();
170 throw new DriverException("SQLSTATE[$error[0]]: $error[2]", $error[1]);
171 }
172 }
173
174
175 176 177 178
179 public function rollback($savepoint = null){
180 if(!$this->resource->rollBack()){
181 $error = $this->resource->errorInfo();
182 throw new DriverException("SQLSTATE[$error[0]]: $error[2]", $error[1]);
183 }
184 }
185
186
187 188 189 190 191
192 public function fetch($resultSet){
193 return $resultSet->fetch(PDO::FETCH_ASSOC);
194 }
195
196
197 198 199 200 201 202 203
204 public function seek($resultSet, $offset){
205 throw new ImplementationException('Cannot seek on unbuffered result.');
206 }
207
208
209 210 211 212
213 public function getInsertId(){
214 return $this->resource->lastInsertId();
215 }
216
217
218 219 220 221
222 public function randomizeOrder(BaseStatement $statement){
223 switch($this->driverName){
224 case 'mysql':
225 case 'pgsql':
226 $random = 'RAND()';
227
228 case 'sqlite':
229 case 'sqlite2':
230 $random = 'RANDOM()';
231
232 case 'odbc':
233 $random = 'Rnd(id)';
234
235 case 'oci':
236 $random = 'dbms_random.value';
237
238 case 'mssql':
239 $random = 'NEWID()';
240 }
241 $statement->order($random);
242 }
243
244
245 246 247 248 249
250 public function getNumRows($resultSet){
251 $resultSet->rowCount();
252 }
253
254
255 256 257 258
259 public function getAffectedRows(){
260 return $this->affectedRows;
261 }
262
263
264 265 266 267 268 269 270
271 public function escape($value, $type){
272 switch($type){
273 case Manager::BOOL:
274 return $this->resource->quote($value, PDO::PARAM_BOOL);
275
276 case Manager::TEXT:
277 return $this->resource->quote($value, PDO::PARAM_STR);
278
279 case Manager::IDENTIFIER:
280 switch($this->driverName){
281 case 'mysql':
282 return str_replace('`*`', '*', '`' . str_replace('.', '`.`', str_replace('`', '``', $value)) . '`');
283
284 case 'pgsql':
285 return '"' . str_replace('.', '"."', str_replace('"', '""', $value)) . '"';
286
287 case 'sqlite':
288 case 'sqlite2':
289 return str_replace('[*]', '*', '[' . str_replace('.', '].[', $value) . ']');
290
291 case 'odbc':
292 case 'oci':
293 case 'mssql':
294 return '[' . str_replace(array('[', ']'), array('[[', ']]'), $value) . ']';
295
296 default:
297 return $value;
298 }
299
300 case Manager::BINARY:
301 return $this->resource->quote($value, PDO::PARAM_LOB);
302
303 case Manager::DATETIME:
304 return ($value instanceof DateTime) ? $value->format("'Y-m-d H:i:s'") : date("'Y-m-d H:i:s'", $value);
305
306 default:
307 throw new InvalidArgumentException('Unsupported data type.');
308 break;
309 }
310 }
311
312
313 314 315 316 317 318 319
320 public function unescape($value, $type){
321 if($type === Manager::BINARY)
322 return $value;
323 throw new InvalidArgumentException('Unsupported data type.');
324 }
325
326
327 328 329 330 331 332 333
334 public function getPrimaryKey($table, $resultSet = null){
335 if($resultSet instanceof PDOStatement){
336 $i = 0;
337 while($col = $resultSet->getColumnMeta($i++)){
338 if(in_array('primary_key', $col['flags']))
339 $primaryKey = $col['name'];
340 }
341 }
342 if(isset($primaryKey))
343 return $primaryKey;
344 throw new ImplementationException;
345 }
346
347
348 349 350 351 352 353 354 355
356 public function getColumnTypes($resultSet, $table){
357 $types = array();
358 $i = 0;
359 while($col = $resultSet->getColumnMeta($i++))
360 $types[$col['name']] = strtolower($col['native_type']);
361
362 if(empty($types))
363 throw new ImplementationException;
364 return $types;
365 }
366
367
368 369 370 371
372 protected function parseUpdateStmt(){
373 $sql = parent::parseUpdateStmt();
374 if($this->driverName === 'mysql')
375 return $this->applyLimit($sql . $this->clauses[3]);
376 return $sql;
377 }
378
379
380 381 382 383
384 protected function parseDeleteStmt(){
385 $sql = parent::parseDeleteStmt();
386 if($this->driverName === 'mysql')
387 return $this->applyLimit($sql . $this->clauses[3]);
388 return $sql;
389 }
390
391
392 393 394 395 396 397
398 protected function applyLimit($sql){
399 list($limit, $offset) = $this->stmt->getLimit();
400 if((int) $limit < 1 && (int) $offset < 1)
401 return $sql;
402
403 switch($this->stmt->getConnection()->getDriver()->getDriverName()){
404 case 'mysql':
405 case 'pgsql':
406 case 'sqlite':
407 case 'sqlite2':
408 if((int) $limit > 0){
409 $sql .= "\nLIMIT " . (int) $limit;
410 if((int) $offset > 0)
411 $sql .= ' OFFSET ' . (int) $offset;
412 }
413 return $sql;
414
415 case 'odbc':
416 case 'mssql':
417 if($offset < 1)
418 return 'SELECT TOP ' . (int) $limit . " *\nFROM (\n\t"
419 . implode("\n\t", explode("\n", $sql)) . "\n)";
420
421 default:
422 throw new DriverException('PDO or selected driver does not allow apllying limit or offset.');
423 }
424 }
425
426
427 428 429 430
431 public function getResource(){
432 return $this->resource;
433 }
434
435
436 437 438 439
440 public function getDriverName(){
441 return $this->driverName;
442 }
443
444
445 }
446