1: <?php
2:
3: namespace RedBeanPHP\QueryWriter;
4:
5: use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
6: use RedBeanPHP\QueryWriter as QueryWriter;
7: use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
8: use RedBeanPHP\Adapter as Adapter;
9: use RedBeanPHP\RedException\SQL as SQLException;
10:
11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
25: class SQLiteT extends AQueryWriter implements QueryWriter
26: {
27: 28: 29:
30: const C_DATATYPE_INTEGER = 0;
31: const C_DATATYPE_NUMERIC = 1;
32: const C_DATATYPE_TEXT = 2;
33: const C_DATATYPE_SPECIFIED = 99;
34:
35: 36: 37:
38: protected $adapter;
39:
40: 41: 42:
43: protected $quoteCharacter = '`';
44:
45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59:
60: protected function getTable( $type )
61: {
62: $tableName = $this->esc( $type, TRUE );
63: $columns = $this->getColumns( $type );
64: $indexes = $this->getIndexes( $type );
65: $keys = $this->getKeyMapForType( $type );
66:
67: $table = array(
68: 'columns' => $columns,
69: 'indexes' => $indexes,
70: 'keys' => $keys,
71: 'name' => $tableName
72: );
73:
74: $this->tableArchive[$tableName] = $table;
75:
76: return $table;
77: }
78:
79: 80: 81: 82: 83: 84: 85: 86: 87: 88:
89: protected function putTable( $tableMap )
90: {
91: $table = $tableMap['name'];
92: $q = array();
93: $q[] = "DROP TABLE IF EXISTS tmp_backup;";
94:
95: $oldColumnNames = array_keys( $this->getColumns( $table ) );
96:
97: foreach ( $oldColumnNames as $k => $v ) $oldColumnNames[$k] = "`$v`";
98:
99: $q[] = "CREATE TEMPORARY TABLE tmp_backup(" . implode( ",", $oldColumnNames ) . ");";
100: $q[] = "INSERT INTO tmp_backup SELECT * FROM `$table`;";
101: $q[] = "PRAGMA foreign_keys = 0 ";
102: $q[] = "DROP TABLE `$table`;";
103:
104: $newTableDefStr = '';
105: foreach ( $tableMap['columns'] as $column => $type ) {
106: if ( $column != 'id' ) {
107: $newTableDefStr .= ",`$column` $type";
108: }
109: }
110:
111: $fkDef = '';
112: foreach ( $tableMap['keys'] as $key ) {
113: $fkDef .= ", FOREIGN KEY(`{$key['from']}`)
114: REFERENCES `{$key['table']}`(`{$key['to']}`)
115: ON DELETE {$key['on_delete']} ON UPDATE {$key['on_update']}";
116: }
117:
118: $q[] = "CREATE TABLE `$table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT $newTableDefStr $fkDef );";
119:
120: foreach ( $tableMap['indexes'] as $name => $index ) {
121: if ( strpos( $name, 'UQ_' ) === 0 ) {
122: $cols = explode( '__', substr( $name, strlen( 'UQ_' . $table ) ) );
123: foreach ( $cols as $k => $v ) $cols[$k] = "`$v`";
124: $q[] = "CREATE UNIQUE INDEX $name ON `$table` (" . implode( ',', $cols ) . ")";
125: } else $q[] = "CREATE INDEX $name ON `$table` ({$index['name']}) ";
126: }
127:
128: $q[] = "INSERT INTO `$table` SELECT * FROM tmp_backup;";
129: $q[] = "DROP TABLE tmp_backup;";
130: $q[] = "PRAGMA foreign_keys = 1 ";
131:
132: foreach ( $q as $sq ) $this->adapter->exec( $sq );
133: }
134:
135: 136: 137: 138: 139: 140: 141:
142: protected function getIndexes( $type )
143: {
144: $table = $this->esc( $type, TRUE );
145: $indexes = $this->adapter->get( "PRAGMA index_list('$table')" );
146:
147: $indexInfoList = array();
148: foreach ( $indexes as $i ) {
149: $indexInfoList[$i['name']] = $this->adapter->getRow( "PRAGMA index_info('{$i['name']}') " );
150:
151: $indexInfoList[$i['name']]['unique'] = $i['unique'];
152: }
153:
154: return $indexInfoList;
155: }
156:
157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169:
170: protected function buildFK( $type, $targetType, $property, $targetProperty, $constraint = FALSE )
171: {
172: $table = $this->esc( $type, TRUE );
173: $targetTable = $this->esc( $targetType, TRUE );
174: $column = $this->esc( $property, TRUE );
175: $targetColumn = $this->esc( $targetProperty, TRUE );
176:
177: $tables = $this->getTables();
178: if ( !in_array( $targetTable, $tables ) ) return FALSE;
179:
180: if ( !is_null( $this->getForeignKeyForTypeProperty( $table, $column ) ) ) return FALSE;
181: $t = $this->getTable( $table );
182: $consSQL = ( $constraint ? 'CASCADE' : 'SET NULL' );
183: $label = 'from_' . $column . '_to_table_' . $targetTable . '_col_' . $targetColumn;
184: $t['keys'][$label] = array(
185: 'table' => $targetTable,
186: 'from' => $column,
187: 'to' => $targetColumn,
188: 'on_update' => $consSQL,
189: 'on_delete' => $consSQL
190: );
191: $this->putTable( $t );
192: return TRUE;
193: }
194:
195: 196: 197:
198: protected function getKeyMapForType( $type )
199: {
200: $table = $this->esc( $type, TRUE );
201: $keys = $this->adapter->get( "PRAGMA foreign_key_list('$table')" );
202: $keyInfoList = array();
203: foreach ( $keys as $k ) {
204: $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
205: $keyInfoList[$label] = array(
206: 'name' => $label,
207: 'from' => $k['from'],
208: 'table' => $k['table'],
209: 'to' => $k['to'],
210: 'on_update' => $k['on_update'],
211: 'on_delete' => $k['on_delete']
212: );
213: }
214: return $keyInfoList;
215: }
216:
217: 218: 219: 220: 221:
222: public function __construct( Adapter $adapter )
223: {
224: $this->typeno_sqltype = array(
225: SQLiteT::C_DATATYPE_INTEGER => 'INTEGER',
226: SQLiteT::C_DATATYPE_NUMERIC => 'NUMERIC',
227: SQLiteT::C_DATATYPE_TEXT => 'TEXT',
228: );
229:
230: $this->sqltype_typeno = array();
231:
232: foreach ( $this->typeno_sqltype as $k => $v ) {
233: $this->sqltype_typeno[$v] = $k;
234: }
235:
236: $this->adapter = $adapter;
237: }
238:
239: 240: 241: 242: 243: 244:
245: public function getTypeForID()
246: {
247: return self::C_DATATYPE_INTEGER;
248: }
249:
250: 251: 252:
253: public function scanType( $value, $flagSpecial = FALSE )
254: {
255: $this->svalue = $value;
256:
257: if ( $value === NULL ) return self::C_DATATYPE_INTEGER;
258: if ( $value === INF ) return self::C_DATATYPE_TEXT;
259:
260: if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
261:
262: if ( $value === TRUE || $value === FALSE ) return self::C_DATATYPE_INTEGER;
263:
264: if ( is_numeric( $value ) && ( intval( $value ) == $value ) && $value < 2147483648 && $value > -2147483648 ) return self::C_DATATYPE_INTEGER;
265:
266: if ( ( is_numeric( $value ) && $value < 2147483648 && $value > -2147483648)
267: || preg_match( '/\d{4}\-\d\d\-\d\d/', $value )
268: || preg_match( '/\d{4}\-\d\d\-\d\d\s\d\d:\d\d:\d\d/', $value )
269: ) {
270: return self::C_DATATYPE_NUMERIC;
271: }
272:
273: return self::C_DATATYPE_TEXT;
274: }
275:
276: 277: 278:
279: public function addColumn( $table, $column, $type )
280: {
281: $column = $this->check( $column );
282: $table = $this->check( $table );
283: $type = $this->typeno_sqltype[$type];
284:
285: $this->adapter->exec( "ALTER TABLE `$table` ADD `$column` $type " );
286: }
287:
288: 289: 290:
291: public function code( $typedescription, $includeSpecials = FALSE )
292: {
293: $r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99 );
294:
295: return $r;
296: }
297:
298: 299: 300:
301: public function widenColumn( $type, $column, $datatype )
302: {
303: $t = $this->getTable( $type );
304:
305: $t['columns'][$column] = $this->typeno_sqltype[$datatype];
306:
307: $this->putTable( $t );
308: }
309:
310: 311: 312:
313: public function getTables()
314: {
315: return $this->adapter->getCol( "SELECT name FROM sqlite_master
316: WHERE type='table' AND name!='sqlite_sequence';" );
317: }
318:
319: 320: 321:
322: public function createTable( $table )
323: {
324: $table = $this->esc( $table );
325:
326: $sql = "CREATE TABLE $table ( id INTEGER PRIMARY KEY AUTOINCREMENT ) ";
327:
328: $this->adapter->exec( $sql );
329: }
330:
331: 332: 333:
334: public function getColumns( $table )
335: {
336: $table = $this->esc( $table, TRUE );
337:
338: $columnsRaw = $this->adapter->get( "PRAGMA table_info('$table')" );
339:
340: $columns = array();
341: foreach ( $columnsRaw as $r ) $columns[$r['name']] = $r['type'];
342:
343: return $columns;
344: }
345:
346: 347: 348:
349: public function addUniqueConstraint( $type, $properties )
350: {
351: $tableNoQ = $this->esc( $type, TRUE );
352: $name = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $properties );
353: $t = $this->getTable( $type );
354: $t['indexes'][$name] = array( 'name' => $name );
355: try {
356: $this->putTable( $t );
357: } catch( SQLException $e ) {
358: return FALSE;
359: }
360: return TRUE;
361: }
362:
363: 364: 365:
366: public function sqlStateIn( $state, $list )
367: {
368: $stateMap = array(
369: 'HY000' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
370: '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
371: );
372:
373: return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
374: }
375:
376: 377: 378:
379: public function addIndex( $type, $name, $column )
380: {
381: $columns = $this->getColumns( $type );
382: if ( !isset( $columns[$column] ) ) return FALSE;
383:
384: $table = $this->esc( $type );
385: $name = preg_replace( '/\W/', '', $name );
386: $column = $this->esc( $column, TRUE );
387:
388: try {
389: $t = $this->getTable( $type );
390: $t['indexes'][$name] = array( 'name' => $column );
391: $this->putTable( $t );
392: return TRUE;
393: } catch( SQLException $exception ) {
394: return FALSE;
395: }
396: }
397:
398: 399: 400:
401: public function wipe( $type )
402: {
403: $table = $this->esc( $type );
404:
405: $this->adapter->exec( "DELETE FROM $table " );
406: }
407:
408: 409: 410:
411: public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
412: {
413: return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDep );
414: }
415:
416: 417: 418:
419: public function wipeAll()
420: {
421: $this->adapter->exec( 'PRAGMA foreign_keys = 0 ' );
422:
423: foreach ( $this->getTables() as $t ) {
424: try {
425: $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
426: } catch ( SQLException $e ) {
427: }
428:
429: try {
430: $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
431: } catch ( SQLException $e ) {
432: }
433: }
434:
435: $this->adapter->exec( 'PRAGMA foreign_keys = 1 ' );
436: }
437: }
438: