1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12:
13: require_once dirname(__FILE__) . '/sqlite.reflector.php';
14:
15:
16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
31: class DibiSqlite3Driver extends DibiObject implements IDibiDriver, IDibiResultDriver
32: {
33:
34: private $connection;
35:
36:
37: private $resultSet;
38:
39:
40: private $fmtDate, $fmtDateTime;
41:
42:
43: private $dbcharset, $charset;
44:
45:
46:
47: 48: 49:
50: public function __construct()
51: {
52: if (!extension_loaded('sqlite3')) {
53: throw new DibiNotSupportedException("PHP extension 'sqlite3' is not loaded.");
54: }
55: }
56:
57:
58:
59: 60: 61: 62: 63:
64: public function connect(array &$config)
65: {
66: DibiConnection::alias($config, 'database', 'file');
67: $this->fmtDate = isset($config['formatDate']) ? $config['formatDate'] : 'U';
68: $this->fmtDateTime = isset($config['formatDateTime']) ? $config['formatDateTime'] : 'U';
69:
70: if (isset($config['resource']) && $config['resource'] instanceof SQLite3) {
71: $this->connection = $config['resource'];
72: } else try {
73: $this->connection = new SQLite3($config['database']);
74:
75: } catch (Exception $e) {
76: throw new DibiDriverException($e->getMessage(), $e->getCode());
77: }
78:
79: $this->dbcharset = empty($config['dbcharset']) ? 'UTF-8' : $config['dbcharset'];
80: $this->charset = empty($config['charset']) ? 'UTF-8' : $config['charset'];
81: if (strcasecmp($this->dbcharset, $this->charset) === 0) {
82: $this->dbcharset = $this->charset = NULL;
83: }
84:
85:
86: $version = SQLite3::version();
87: if ($version['versionNumber'] >= '3006019') {
88: $this->query("PRAGMA foreign_keys = ON");
89: }
90: }
91:
92:
93:
94: 95: 96: 97:
98: public function disconnect()
99: {
100: $this->connection->close();
101: }
102:
103:
104:
105: 106: 107: 108: 109: 110:
111: public function query($sql)
112: {
113: if ($this->dbcharset !== NULL) {
114: $sql = iconv($this->charset, $this->dbcharset . '//IGNORE', $sql);
115: }
116:
117: $res = @$this->connection->query($sql);
118: if ($this->connection->lastErrorCode()) {
119: throw new DibiDriverException($this->connection->lastErrorMsg(), $this->connection->lastErrorCode(), $sql);
120:
121: } elseif ($res instanceof SQLite3Result) {
122: return $this->createResultDriver($res);
123: }
124: }
125:
126:
127:
128: 129: 130: 131:
132: public function getAffectedRows()
133: {
134: return $this->connection->changes();
135: }
136:
137:
138:
139: 140: 141: 142:
143: public function getInsertId($sequence)
144: {
145: return $this->connection->lastInsertRowID();
146: }
147:
148:
149:
150: 151: 152: 153: 154: 155:
156: public function begin($savepoint = NULL)
157: {
158: $this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
159: }
160:
161:
162:
163: 164: 165: 166: 167: 168:
169: public function commit($savepoint = NULL)
170: {
171: $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
172: }
173:
174:
175:
176: 177: 178: 179: 180: 181:
182: public function rollback($savepoint = NULL)
183: {
184: $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
185: }
186:
187:
188:
189: 190: 191: 192:
193: public function getResource()
194: {
195: return $this->connection;
196: }
197:
198:
199:
200: 201: 202: 203:
204: public function getReflector()
205: {
206: return new DibiSqliteReflector($this);
207: }
208:
209:
210:
211: 212: 213: 214: 215:
216: public function createResultDriver(SQLite3Result $resource)
217: {
218: $res = clone $this;
219: $res->resultSet = $resource;
220: return $res;
221: }
222:
223:
224:
225:
226:
227:
228:
229: 230: 231: 232: 233: 234: 235:
236: public function escape($value, $type)
237: {
238: switch ($type) {
239: case dibi::TEXT:
240: return "'" . $this->connection->escapeString($value) . "'";
241:
242: case dibi::BINARY:
243: return "X'" . bin2hex((string) $value) . "'";
244:
245: case dibi::IDENTIFIER:
246: return '[' . strtr($value, '[]', ' ') . ']';
247:
248: case dibi::BOOL:
249: return $value ? 1 : 0;
250:
251: case dibi::DATE:
252: return $value instanceof DateTime ? $value->format($this->fmtDate) : date($this->fmtDate, $value);
253:
254: case dibi::DATETIME:
255: return $value instanceof DateTime ? $value->format($this->fmtDateTime) : date($this->fmtDateTime, $value);
256:
257: default:
258: throw new InvalidArgumentException('Unsupported type.');
259: }
260: }
261:
262:
263:
264: 265: 266: 267: 268: 269:
270: public function escapeLike($value, $pos)
271: {
272: $value = addcslashes($this->connection->escapeString($value), '%_\\');
273: return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
274: }
275:
276:
277:
278: 279: 280: 281: 282: 283: 284:
285: public function unescape($value, $type)
286: {
287: if ($type === dibi::BINARY) {
288: return $value;
289: }
290: throw new InvalidArgumentException('Unsupported type.');
291: }
292:
293:
294:
295: 296: 297: 298: 299: 300: 301:
302: public function applyLimit(&$sql, $limit, $offset)
303: {
304: if ($limit < 0 && $offset < 1) return;
305: $sql .= ' LIMIT ' . $limit . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
306: }
307:
308:
309:
310:
311:
312:
313:
314: 315: 316: 317:
318: public function __destruct()
319: {
320: $this->resultSet && @$this->free();
321: }
322:
323:
324:
325: 326: 327: 328: 329:
330: public function getRowCount()
331: {
332: throw new DibiNotSupportedException('Row count is not available for unbuffered queries.');
333: }
334:
335:
336:
337: 338: 339: 340: 341:
342: public function fetch($assoc)
343: {
344: $row = $this->resultSet->fetchArray($assoc ? SQLITE3_ASSOC : SQLITE3_NUM);
345: $charset = $this->charset === NULL ? NULL : $this->charset . '//TRANSLIT';
346: if ($row && ($assoc || $charset)) {
347: $tmp = array();
348: foreach ($row as $k => $v) {
349: if ($charset !== NULL && is_string($v)) {
350: $v = iconv($this->dbcharset, $charset, $v);
351: }
352: $tmp[str_replace(array('[', ']'), '', $k)] = $v;
353: }
354: return $tmp;
355: }
356: return $row;
357: }
358:
359:
360:
361: 362: 363: 364: 365: 366:
367: public function seek($row)
368: {
369: throw new DibiNotSupportedException('Cannot seek an unbuffered result set.');
370: }
371:
372:
373:
374: 375: 376: 377:
378: public function free()
379: {
380: $this->resultSet->finalize();
381: $this->resultSet = NULL;
382: }
383:
384:
385:
386: 387: 388: 389:
390: public function getResultColumns()
391: {
392: $count = $this->resultSet->numColumns();
393: $columns = array();
394: static $types = array(SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null');
395: for ($i = 0; $i < $count; $i++) {
396: $columns[] = array(
397: 'name' => $this->resultSet->columnName($i),
398: 'table' => NULL,
399: 'fullname' => $this->resultSet->columnName($i),
400: 'nativetype' => $types[$this->resultSet->columnType($i)],
401: );
402: }
403: return $columns;
404: }
405:
406:
407:
408: 409: 410: 411:
412: public function getResultResource()
413: {
414: return $this->resultSet;
415: }
416:
417:
418:
419:
420:
421:
422:
423: 424: 425: 426: 427: 428: 429:
430: public function registerFunction($name, $callback, $numArgs = -1)
431: {
432: $this->connection->createFunction($name, $callback, $numArgs);
433: }
434:
435:
436:
437: 438: 439: 440: 441: 442: 443: 444:
445: public function registerAggregateFunction($name, $rowCallback, $agrCallback, $numArgs = -1)
446: {
447: $this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
448: }
449:
450: }
451: