1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Database\Drivers;
9:
10: use Nette;
11:
12:
13: 14: 15:
16: class SqliteDriver extends Nette\Object implements Nette\Database\ISupplementalDriver
17: {
18:
19: private $connection;
20:
21:
22: private $fmtDateTime;
23:
24:
25: public function __construct(Nette\Database\Connection $connection, array $options)
26: {
27: $this->connection = $connection;
28: $this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U';
29: }
30:
31:
32: public function convertException(\PDOException $e)
33: {
34: $code = isset($e->errorInfo[1]) ? $e->errorInfo[1] : NULL;
35: $msg = $e->getMessage();
36: if ($code !== 19) {
37: return Nette\Database\DriverException::from($e);
38:
39: } elseif (strpos($msg, 'must be unique') !== FALSE
40: || strpos($msg, 'is not unique') !== FALSE
41: || strpos($msg, 'UNIQUE constraint failed') !== FALSE
42: ) {
43: return Nette\Database\UniqueConstraintViolationException::from($e);
44:
45: } elseif (strpos($msg, 'may not be NULL') !== FALSE
46: || strpos($msg, 'NOT NULL constraint failed') !== FALSE
47: ) {
48: return Nette\Database\NotNullConstraintViolationException::from($e);
49:
50: } elseif (strpos($msg, 'foreign key constraint failed') !== FALSE
51: || strpos($msg, 'FOREIGN KEY constraint failed') !== FALSE
52: ) {
53: return Nette\Database\ForeignKeyConstraintViolationException::from($e);
54:
55: } else {
56: return Nette\Database\ConstraintViolationException::from($e);
57: }
58: }
59:
60:
61:
62:
63:
64: 65: 66:
67: public function delimite($name)
68: {
69: return '[' . strtr($name, '[]', ' ') . ']';
70: }
71:
72:
73: 74: 75:
76: public function formatBool($value)
77: {
78: return $value ? '1' : '0';
79: }
80:
81:
82: 83: 84:
85: public function formatDateTime( $value)
86: {
87: return $value->format($this->fmtDateTime);
88: }
89:
90:
91: 92: 93:
94: public function formatDateInterval(\DateInterval $value)
95: {
96: throw new Nette\NotSupportedException;
97: }
98:
99:
100: 101: 102:
103: public function formatLike($value, $pos)
104: {
105: $value = addcslashes(substr($this->connection->quote($value), 1, -1), '%_\\');
106: return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
107: }
108:
109:
110: 111: 112:
113: public function applyLimit(& $sql, $limit, $offset)
114: {
115: if ($limit < 0 || $offset < 0) {
116: throw new Nette\InvalidArgumentException('Negative offset or limit.');
117:
118: } elseif ($limit !== NULL || $offset) {
119: $sql .= ' LIMIT ' . ($limit === NULL ? '-1' : (int) $limit)
120: . ($offset ? ' OFFSET ' . (int) $offset : '');
121: }
122: }
123:
124:
125: 126: 127:
128: public function normalizeRow($row)
129: {
130: foreach ($row as $key => $value) {
131: unset($row[$key]);
132: if ($key[0] === '[' || $key[0] === '"') {
133: $key = substr($key, 1, -1);
134: }
135: $row[$key] = $value;
136: }
137: return $row;
138: }
139:
140:
141:
142:
143:
144: 145: 146:
147: public function getTables()
148: {
149: $tables = array();
150: foreach ($this->connection->query("
151: SELECT name, type = 'view' as view FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'
152: UNION ALL
153: SELECT name, type = 'view' as view FROM sqlite_temp_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'
154: ORDER BY name
155: ") as $row) {
156: $tables[] = array(
157: 'name' => $row->name,
158: 'view' => (bool) $row->view,
159: );
160: }
161:
162: return $tables;
163: }
164:
165:
166: 167: 168:
169: public function getColumns($table)
170: {
171: $meta = $this->connection->query("
172: SELECT sql FROM sqlite_master WHERE type = 'table' AND name = {$this->connection->quote($table)}
173: UNION ALL
174: SELECT sql FROM sqlite_temp_master WHERE type = 'table' AND name = {$this->connection->quote($table)}
175: ")->fetch();
176:
177: $columns = array();
178: foreach ($this->connection->query("PRAGMA table_info({$this->delimite($table)})") as $row) {
179: $column = $row['name'];
180: $pattern = "/(\"$column\"|\[$column\]|$column)\\s+[^,]+\\s+PRIMARY\\s+KEY\\s+AUTOINCREMENT/Ui";
181: $type = explode('(', $row['type']);
182: $columns[] = array(
183: 'name' => $column,
184: 'table' => $table,
185: 'nativetype' => strtoupper($type[0]),
186: 'size' => isset($type[1]) ? (int) $type[1] : NULL,
187: 'unsigned' => FALSE,
188: 'nullable' => $row['notnull'] == '0',
189: 'default' => $row['dflt_value'],
190: 'autoincrement' => (bool) preg_match($pattern, $meta['sql']),
191: 'primary' => $row['pk'] > 0,
192: 'vendor' => (array) $row,
193: );
194: }
195: return $columns;
196: }
197:
198:
199: 200: 201:
202: public function getIndexes($table)
203: {
204: $indexes = array();
205: foreach ($this->connection->query("PRAGMA index_list({$this->delimite($table)})") as $row) {
206: $indexes[$row['name']]['name'] = $row['name'];
207: $indexes[$row['name']]['unique'] = (bool) $row['unique'];
208: $indexes[$row['name']]['primary'] = FALSE;
209: }
210:
211: foreach ($indexes as $index => $values) {
212: $res = $this->connection->query("PRAGMA index_info({$this->delimite($index)})");
213: while ($row = $res->fetch(TRUE)) {
214: $indexes[$index]['columns'][$row['seqno']] = $row['name'];
215: }
216: }
217:
218: $columns = $this->getColumns($table);
219: foreach ($indexes as $index => $values) {
220: $column = $indexes[$index]['columns'][0];
221: foreach ($columns as $info) {
222: if ($column == $info['name']) {
223: $indexes[$index]['primary'] = (bool) $info['primary'];
224: break;
225: }
226: }
227: }
228: if (!$indexes) {
229: foreach ($columns as $column) {
230: if ($column['vendor']['pk']) {
231: $indexes[] = array(
232: 'name' => 'ROWID',
233: 'unique' => TRUE,
234: 'primary' => TRUE,
235: 'columns' => array($column['name']),
236: );
237: break;
238: }
239: }
240: }
241:
242: return array_values($indexes);
243: }
244:
245:
246: 247: 248:
249: public function getForeignKeys($table)
250: {
251: $keys = array();
252: foreach ($this->connection->query("PRAGMA foreign_key_list({$this->delimite($table)})") as $row) {
253: $keys[$row['id']]['name'] = $row['id'];
254: $keys[$row['id']]['local'] = $row['from'];
255: $keys[$row['id']]['table'] = $row['table'];
256: $keys[$row['id']]['foreign'] = $row['to'];
257: $keys[$row['id']]['onDelete'] = $row['on_delete'];
258: $keys[$row['id']]['onUpdate'] = $row['on_update'];
259:
260: if ($keys[$row['id']]['foreign'][0] == NULL) {
261: $keys[$row['id']]['foreign'] = NULL;
262: }
263: }
264: return array_values($keys);
265: }
266:
267:
268: 269: 270:
271: public function getColumnTypes(\PDOStatement $statement)
272: {
273: $types = array();
274: $count = $statement->columnCount();
275: for ($col = 0; $col < $count; $col++) {
276: $meta = $statement->getColumnMeta($col);
277: if (isset($meta['sqlite:decl_type'])) {
278: if ($meta['sqlite:decl_type'] === 'DATE') {
279: $types[$meta['name']] = Nette\Database\IStructure::FIELD_UNIX_TIMESTAMP;
280: } else {
281: $types[$meta['name']] = Nette\Database\Helpers::detectType($meta['sqlite:decl_type']);
282: }
283: } elseif (isset($meta['native_type'])) {
284: $types[$meta['name']] = Nette\Database\Helpers::detectType($meta['native_type']);
285: }
286: }
287: return $types;
288: }
289:
290:
291: 292: 293: 294:
295: public function isSupported($item)
296: {
297: return $item === self::SUPPORT_MULTI_INSERT_AS_SELECT || $item === self::SUPPORT_SUBSELECT || $item === self::SUPPORT_MULTI_COLUMN_AS_OR_COND;
298: }
299:
300: }
301: