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