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