Completed
Push — master ( cc37b1...6647f2 )
by mw
38:04
created

SQLiteTableBuilder::doCreateTable()   D

Complexity

Conditions 9
Paths 48

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 21
c 1
b 0
f 0
nc 48
nop 2
dl 0
loc 36
rs 4.909
1
<?php
2
3
namespace SMW\SQLStore\TableBuilder;
4
5
/**
6
 * @license GNU GPL v2+
7
 * @since 2.5
8
 *
9
 * @author mwjames
10
 */
11
class SQLiteTableBuilder extends TableBuilder {
12
13
	/**
14
	 * @since 2.5
15
	 *
16
	 * {@inheritDoc}
17
	 */
18
	public function getStandardFieldType( $input ) {
19
20
		switch ( $input ) {
21
			case 'id':
22
			return 'INTEGER'; // like page_id in MW page table
23
			case 'id primary':
24
			return 'INTEGER' . ' NOT NULL PRIMARY KEY AUTOINCREMENT'; // like page_id in MW page table
25
			case 'namespace':
26
			return 'INT(11)'; // like page_namespace in MW page table
27
			case 'title':
28
			return 'VARBINARY(255)'; // like page_title in MW page table
29
			case 'iw':
30
			return 'TEXT'; // like iw_prefix in MW interwiki table
31
			case 'blob':
32
			return 'MEDIUMBLOB'; // larger blobs of character data, usually not subject to SELECT conditions
33
			case 'boolean':
34
			return 'TINYINT(1)';
35
			case 'double':
36
			return 'DOUBLE';
37
			case 'integer':
38
			return 'INT(8)';
39
			case 'usage count':
40
			return 'INT(8)';
41
			case 'integer unsigned':
42
			return 'INTEGER';
43
			case 'sort':
44
			return 'VARCHAR(255)';
45
		}
46
47
		return false;
48
	}
49
50
	/** Create */
51
52
	/**
53
	 * @since 2.5
54
	 *
55
	 * {@inheritDoc}
56
	 */
57
	protected function doCreateTable( $tableName, array $tableOptions = null ) {
58
59
		$mode = '';
60
		$option = '';
61
62
		$ftsOptions = null;
63
		$tableName = $this->connection->tableName( $tableName );
64
65
		if ( isset( $tableOptions['ftSearchOptions']['sqlite'] ) ) {
66
			$ftsOptions = $tableOptions['ftSearchOptions']['sqlite'];
67
		}
68
69
		// Filter extra module options
70
		// @see https://www.sqlite.org/fts3.html#fts4_options
71
		if ( $ftsOptions !== null && is_array( $ftsOptions ) ) {
72
			$mode = isset( $ftsOptions[0] ) ? $ftsOptions[0] : '';
73
			$option = isset( $ftsOptions[1] ) ? $ftsOptions[1] : '';
74
		} elseif ( $ftsOptions !== null ) {
75
			$mode = $ftsOptions;
76
		}
77
78
		$fieldSql = array();
79
		$fields = $tableOptions['fields'];
80
81
		foreach ( $fields as $fieldName => $fieldType ) {
82
			$fieldSql[] = "$fieldName  $fieldType";
83
		}
84
85
		if ( $mode === '' ) {
86
			$sql = 'CREATE TABLE ' . $tableName .'(' . implode( ',', $fieldSql ) . ') ';
87
		} else {
88
			$sql = 'CREATE VIRTUAL TABLE ' . $tableName . ' USING ' . strtolower( $mode ) .'(' . implode( ',', $fieldSql ) . $option . ') ';
89
		}
90
91
		$this->connection->query( $sql, __METHOD__ );
92
	}
93
94
	/** Update */
95
96
	/**
97
	 * @since 2.5
98
	 *
99
	 * {@inheritDoc}
100
	 */
101
	protected function doUpdateTable( $tableName, array $tableOptions = null ) {
102
103
		$tableName = $this->connection->tableName( $tableName );
104
		$currentFields = $this->getCurrentFields( $tableName );
105
106
		$fields = $tableOptions['fields'];
107
		$position = 'FIRST';
108
109
		// Loop through all the field definitions, and handle each definition for either postgres or MySQL.
110
		foreach ( $fields as $fieldName => $fieldType ) {
111
			$this->doUpdateField( $tableName, $fieldName, $fieldType, $currentFields, $position, $tableOptions );
112
113
			$position = "AFTER $fieldName";
114
			$currentFields[$fieldName] = false;
115
		}
116
117
		// The updated fields have their value set to false, so if a field has a value
118
		// that differs from false, it's an obsolete one that should be removed.
119
		foreach ( $currentFields as $fieldName => $value ) {
120
			if ( $value !== false ) {
121
				$this->doDropField( $tableName, $fieldName );
122
			}
123
		}
124
	}
125
126
	private function getCurrentFields( $tableName ) {
127
128
		$sql = 'PRAGMA table_info(' . $tableName . ')';
129
130
		$res = $this->connection->query( $sql, __METHOD__ );
131
		$currentFields = array();
132
133
		foreach ( $res as $row ) {
134
			$row->Field = $row->name;
135
			$row->Type = $row->type;
136
			$type = $row->type;
137
138
			if ( $row->notnull == '1' ) {
139
				$type .= ' NOT NULL';
140
			}
141
142
			if ( $row->pk == '1' ) {
143
				$type .= ' PRIMARY KEY AUTOINCREMENT';
144
			}
145
146
			$currentFields[$row->Field] = $type;
147
		}
148
149
		return $currentFields;
150
	}
151
152
	private function doUpdateField( $tableName, $fieldName, $fieldType, $currentFields, $position, array $tableOptions ) {
0 ignored issues
show
Unused Code introduced by
The parameter $tableOptions is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
153
154
		if ( !array_key_exists( $fieldName, $currentFields ) ) {
155
			$this->doCreateField( $tableName, $fieldName, $position, $fieldType );
156
		} elseif ( $currentFields[$fieldName] != $fieldType ) {
157
			$this->doUpdateFieldType( $tableName, $fieldName, $position, $currentFields[$fieldName], $fieldType );
158
		} else {
159
			$this->reportMessage( "   ... field $fieldName is fine.\n" );
160
		}
161
	}
162
163
	private function doCreateField( $tableName, $fieldName, $position, $fieldType ) {
0 ignored issues
show
Unused Code introduced by
The parameter $position is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
164
165
		if ( strpos( $tableName, 'ft_search' ) !== false ) {
166
			return $this->reportMessage( "   ... virtual tables can not be altered in SQLite ...\n" );
167
		}
168
169
		$this->reportMessage( "   ... creating field $fieldName ... " );
170
		$this->connection->query( "ALTER TABLE $tableName ADD `$fieldName` $fieldType", __METHOD__ );
171
		$this->reportMessage( "done.\n" );
172
	}
173
174
	private function doUpdateFieldType( $tableName, $fieldName, $position, $oldFieldType, $newFieldType ) {
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $fieldName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $position is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $oldFieldType is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $newFieldType is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
175
		$this->reportMessage( "   ... changing field type is not supported in SQLite (http://www.sqlite.org/omitted.html) \n" );
176
		$this->reportMessage( "       Please delete and reinitialize the tables to remove obsolete data, or just keep it.\n" );
177
	}
178
179
	private function doDropField( $tableName, $fieldName ) {
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
180
		$this->reportMessage( "   ... deleting obsolete field $fieldName is not possible in SQLite.\n" );
181
		$this->reportMessage( "       Please could delete and reinitialize the tables to remove obsolete data, or just keep it.\n" );
182
		$this->reportMessage( "done.\n" );
183
	}
184
185
	/** Index */
186
187
	/**
188
	 * @since 2.5
189
	 *
190
	 * {@inheritDoc}
191
	 */
192
	protected function doCreateIndicies( $tableName, array $indexOptions = null ) {
193
194
		$indicies = $indexOptions['indicies'];
195
196
		// First remove possible obsolete indicies
197
		$this->doDropObsoleteIndicies( $tableName, $indicies );
198
199
		// Add new indexes.
200
		foreach ( $indicies as $indexName => $index ) {
201
			// If the index is an array, it contains the column
202
			// name as first element, and index type as second one.
203
			if ( is_array( $index ) ) {
204
				$columns = $index[0];
205
				$indexType = count( $index ) > 1 ? $index[1] : 'INDEX';
206
			} else {
207
				$columns = $index;
208
				$indexType = 'INDEX';
209
			}
210
211
			$this->doCreateIndex( $tableName, $indexType, $indexName, $columns, $indexOptions );
212
		}
213
	}
214
215
	private function doDropObsoleteIndicies( $tableName, array &$indicies ) {
0 ignored issues
show
Unused Code introduced by
The parameter $indicies is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
216
217
		$currentIndicies = $this->getIndexInfo( $tableName );
218
219
		// TODO We do not currently get the right column definitions in
220
		// SQLite; hence we can only drop all indexes. Wasteful.
221
		foreach ( $currentIndicies as $indexName => $indexColumn ) {
222
			$this->doDropIndex( $tableName, $indexName, $indexColumn );
223
		}
224
	}
225
226
	private function getIndexInfo( $tableName ) {
227
228
		$tableName = $this->connection->tableName( $tableName );
229
		$indices = array();
230
231
		$res = $this->connection->query( 'PRAGMA index_list(' . $tableName . ')', __METHOD__ );
232
233
		if ( !$res ) {
234
			return array();
235
		}
236
237
		foreach ( $res as $row ) {
238
			/// FIXME The value should not be $row->name below?!
239
			if ( !array_key_exists( $row->name, $indices ) ) {
240
				$indices[$row->name] = $row->name;
241
			} else {
242
				$indices[$row->name] .= ',' . $row->name;
243
			}
244
		}
245
246
		return $indices;
247
	}
248
249
	private function doDropIndex( $tableName, $indexName, $columns ) {
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
250
		$this->reportMessage( "   ... removing index $columns ..." );
251
		$this->connection->query( 'DROP INDEX ' . $indexName, __METHOD__ );
252
		$this->reportMessage( "done.\n" );
253
	}
254
255
	private function doCreateIndex( $tableName, $indexType, $indexName, $columns, array $indexOptions ) {
0 ignored issues
show
Unused Code introduced by
The parameter $indexOptions is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
256
257
		if ( $indexType === 'FULLTEXT' ) {
258
			return $this->reportMessage( "   ... skipping the fulltext index creation ..." );
259
		}
260
261
		if ( strpos( $tableName, 'ft_search' ) !== false ) {
262
			return $this->reportMessage( "   ... virtual tables can not be altered in SQLite ...\n" );
263
		}
264
265
		$tableName = $this->connection->tableName( $tableName );
266
		$indexName = "{$tableName}_index{$indexName}";
267
268
		$this->reportMessage( "   ... creating new index $columns ..." );
269
		$this->connection->query( "CREATE $indexType $indexName ON $tableName ($columns)", __METHOD__ );
270
		$this->reportMessage( "done.\n" );
271
	}
272
273
	/** Drop */
274
275
	/**
276
	 * @since 2.5
277
	 *
278
	 * {@inheritDoc}
279
	 */
280
	protected function doDropTable( $tableName ) {
281
		$this->connection->query( 'DROP TABLE ' . $this->connection->tableName( $tableName ), __METHOD__ );
282
	}
283
284
}
285