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\Manager;
21 use Neevo\Parser;
22 use SQLite3;
23 use SQLite3Result;
24
25
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
44 class SQLite3Driver extends Parser implements DriverInterface {
45
46
47
48 private $dbCharset;
49
50
51 private $charset;
52
53
54 private $updateLimit;
55
56
57 private $resource;
58
59
60 private $affectedRows;
61
62
63 private $tblData = array();
64
65
66 67 68 69
70 public function __construct(BaseStatement $statement = null){
71 if(!extension_loaded("sqlite3"))
72 throw new DriverException("Cannot instantiate Neevo SQLite 3 driver - PHP extension 'sqlite3' not loaded.");
73 if($statement instanceof BaseStatement)
74 parent::__construct($statement);
75 }
76
77
78 79 80 81 82
83 public function connect(array $config){
84 Connection::alias($config, 'database', 'file');
85 Connection::alias($config, 'updateLimit', 'update_limit');
86
87 $defaults = array(
88 'memory' => false,
89 'resource' => null,
90 'updateLimit' => false,
91 'charset' => 'UTF-8',
92 'dbcharset' => 'UTF-8'
93 );
94
95 $config += $defaults;
96
97 if($config['memory'])
98 $config['database'] = ':memory:';
99
100
101 if($config['resource'] instanceof SQLite3)
102 $connection = $config['resource'];
103 elseif(!isset($config['database']))
104 throw new DriverException("No database file selected.");
105 else{
106 try{
107 $connection = new SQLite3($config['database']);
108 } catch(Exception $e){
109 throw new DriverException($e->getMessage(), $e->getCode());
110 }
111 }
112
113 if(!$connection instanceof SQLite3)
114 throw new DriverException("Opening database file '$config[database]' failed.");
115
116 $this->resource = $connection;
117 $this->updateLimit = (bool) $config['updateLimit'];
118
119
120 $this->dbCharset = $config['dbcharset'];
121 $this->charset = $config['charset'];
122 if(strcasecmp($this->dbCharset, $this->charset) === 0)
123 $this->dbCharset = $this->charset = null;
124 }
125
126
127 128 129
130 public function closeConnection(){
131 if($this->resource instanceof SQLite3)
132 $this->resource->close();
133 }
134
135
136 137 138 139 140 141 142
143 public function freeResultSet($resultSet){
144 return true;
145 }
146
147
148 149 150 151 152 153
154 public function runQuery($queryString){
155
156 $this->affectedRows = false;
157 if($this->dbCharset !== null)
158 $queryString = iconv($this->charset, $this->dbCharset . '//IGNORE', $queryString);
159
160 $result = $this->resource->query($queryString);
161
162 if($result === false)
163 throw new DriverException($this->resource->lastErrorMsg(), $this->resource->lastErrorCode(), $queryString);
164
165 $this->affectedRows = $this->resource->changes();
166 return $result;
167 }
168
169
170 171 172 173
174 public function beginTransaction($savepoint = null){
175 $this->runQuery($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
176 }
177
178
179 180 181 182
183 public function commit($savepoint = null){
184 $this->runQuery($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
185 }
186
187
188 189 190 191
192 public function rollback($savepoint = null){
193 $this->runQuery($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
194 }
195
196
197 198 199 200 201
202 public function fetch($resultSet){
203 $row = $resultSet->fetchArray(SQLITE3_ASSOC);
204 $charset = $this->charset === null ? null : $this->charset . '//TRANSLIT';
205
206 if($row){
207 $fields = array();
208 foreach($row as $key => $val){
209 if($charset !== null && is_string($val))
210 $val = iconv($this->dbcharset, $charset, $val);
211 $fields[str_replace(array('[', ']'), '', $key)] = $val;
212 }
213 return $fields;
214 }
215 return $row;
216 }
217
218
219 220 221 222 223 224 225 226 227
228 public function seek($resultSet, $offset){
229 throw new DriverException('Cannot seek on unbuffered result.');
230 }
231
232
233 234 235 236
237 public function getInsertId(){
238 return $this->resource->lastInsertRowID();
239 }
240
241
242 243 244 245
246 public function randomizeOrder(BaseStatement $statement){
247 $statement->order('RANDOM()');
248 }
249
250
251 252 253 254 255 256 257 258
259 public function getNumRows($resultSet){
260 throw new DriverException('Cannot count rows on unbuffered result.');
261 }
262
263
264 265 266 267
268 public function getAffectedRows(){
269 return $this->affectedRows;
270 }
271
272
273 274 275 276 277 278 279
280 public function escape($value, $type){
281 switch($type){
282 case Manager::BOOL:
283 return $value ? 1 : 0;
284
285 case Manager::TEXT:
286 return "'" . $this->resource->escapeString($value) . "'";
287
288 case Manager::IDENTIFIER:
289 return str_replace('[*]', '*', '[' . str_replace('.', '].[', $value) . ']');
290
291 case Manager::BINARY:
292 return "X'" . bin2hex((string) $value) . "'";
293
294 case Manager::DATETIME:
295 return ($value instanceof DateTime) ? $value->format("'Y-m-d H:i:s'") : date("'Y-m-d H:i:s'", $value);
296
297 default:
298 throw new InvalidArgumentException('Unsupported data type');
299 break;
300 }
301 }
302
303
304 305 306 307 308 309 310
311 public function unescape($value, $type){
312 if($type === Manager::BINARY)
313 return $value;
314 throw new InvalidArgumentException('Unsupported data type.');
315 }
316
317
318 319 320 321 322
323 public function getPrimaryKey($table){
324 $key = '';
325 $pos = strpos($table, '.');
326 if($pos !== false)
327 $table = substr($table, $pos + 1);
328 if(isset($this->tblData[$table]))
329 $sql = $this->tblData[$table];
330 else{
331 $q = $this->runQuery("SELECT sql FROM sqlite_master WHERE tbl_name='$table'");
332 $r = $this->fetch($q);
333 if($r === false)
334 return '';
335 $this->tblData[$table] = $sql = $r['sql'];
336 }
337
338 $sql = explode("\n", $sql);
339 foreach($sql as $field){
340 $field = trim($field);
341 if(stripos($field, 'PRIMARY KEY') !== false && $key === '')
342 $key = preg_replace('~^"(\w+)".*$~i', '$1', $field);
343 }
344 return $key;
345 }
346
347
348 349 350 351 352 353
354 public function getColumnTypes($resultSet, $table){
355 if($table === null)
356 return array();
357 if(isset($this->tblData[$table]))
358 $sql = $this->tblData[$table];
359 else{
360 $q = $this->runQuery("SELECT sql FROM sqlite_master WHERE tbl_name='$table'");
361 $r = $this->fetch($q);
362 if($r === false){
363 return array();
364 }
365 $this->tblData[$table] = $sql = $r['sql'];
366 }
367 $sql = explode("\n", $sql);
368
369 $cols = array();
370 foreach($sql as $field){
371 $field = trim($field);
372 preg_match('~^"(\w+)"\s+(integer|real|numeric|text|blob).+$~i', $field, $m);
373 if(isset($m[1], $m[2]))
374 $cols[$m[1]] = $m[2];
375 }
376 return $cols;
377 }
378
379
380 381 382 383
384 protected function parseUpdateStmt(){
385 $sql = parent::parseUpdateStmt();
386 return $this->updateLimit ? $this->applyLimit($sql . $this->clauses[3]) : $sql;
387 }
388
389
390 391 392 393
394 protected function parseDeleteStmt(){
395 $sql = parent::parseDeleteStmt();
396 return $this->updateLimit ? $this->applyLimit($sql . $this->clauses[3]) : $sql;
397 }
398
399
400 }
401