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

MySQLTableBuilder::getStandardFieldType()   C

Complexity

Conditions 13
Paths 13

Size

Total Lines 31
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 27
c 1
b 0
f 0
nc 13
nop 1
dl 0
loc 31
rs 5.1234

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 MySQLTableBuilder 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 'INT(8) UNSIGNED'; // like page_id in MW page table
23
			case 'id primary':
24
			return 'INT(8) UNSIGNED' . ' NOT NULL KEY AUTO_INCREMENT'; // 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 'VARBINARY(32)'; // 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) UNSIGNED';
41
			case 'integer unsigned':
42
			return 'INT(8) UNSIGNED';
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
		$tableName = $this->connection->tableName( $tableName );
60
61
		$sql = 'CREATE TABLE ' . $this->getSQLFromDBName( $tableOptions ) . $tableName . ' (';
0 ignored issues
show
Bug introduced by
It seems like $tableOptions defined by parameter $tableOptions on line 57 can also be of type null; however, SMW\SQLStore\TableBuilde...der::getSQLFromDBName() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
62
63
		$fieldSql = array();
64
		$fields = $tableOptions['fields'];
65
66
		foreach ( $fields as $fieldName => $fieldType ) {
67
			$fieldSql[] = "$fieldName  $fieldType";
68
		}
69
70
		$sql .= implode( ',', $fieldSql ) . ') ';
71
		$sql .= $this->getSQLFromDBTableOptions( $tableOptions );
72
73
		$this->connection->query( $sql, __METHOD__ );
74
	}
75
76
	private function getSQLFromDBName( 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...
77
		global $wgDBname;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
78
		return "`$wgDBname`.";
79
	}
80
81
	private function getSQLFromDBTableOptions( array $tableOptions ) {
82
83
		if ( isset( $tableOptions['ftSearchOptions']['mysql'] ) ) {
84
85
			$tableOption = $tableOptions['ftSearchOptions']['mysql'];
86
87
			// By convention the first index has table specific relevance
88
			if ( is_array( $tableOption ) ) {
89
				$tableOption = isset( $tableOption[0] ) ? $tableOption[0] : '';
90
			}
91
92
			return $tableOption;
93
		}
94
95
		// This replacement is needed for compatibility, see http://bugs.mysql.com/bug.php?id=17501
96
		if ( isset( $tableOptions['wgDBTableOptions'] ) ) {
97
			return str_replace( 'TYPE', 'ENGINE', $tableOptions['wgDBTableOptions'] );
98
		}
99
	}
100
101
	/** Update */
102
103
	/**
104
	 * @since 2.5
105
	 *
106
	 * {@inheritDoc}
107
	 */
108
	protected function doUpdateTable( $tableName, array $tableOptions = null ) {
109
110
		$tableName = $this->connection->tableName( $tableName );
111
		$currentFields = $this->getCurrentFields( $tableName );
112
113
		$fields = $tableOptions['fields'];
114
		$position = 'FIRST';
115
116
		// Loop through all the field definitions, and handle each definition for either postgres or MySQL.
117
		foreach ( $fields as $fieldName => $fieldType ) {
118
			$this->doUpdateField( $tableName, $fieldName, $fieldType, $currentFields, $position, $tableOptions );
119
120
			$position = "AFTER $fieldName";
121
			$currentFields[$fieldName] = false;
122
		}
123
124
		// The updated fields have their value set to false, so if a field has a value
125
		// that differs from false, it's an obsolete one that should be removed.
126
		foreach ( $currentFields as $fieldName => $value ) {
127
			if ( $value !== false ) {
128
				$this->doDropField( $tableName, $fieldName );
129
			}
130
		}
131
	}
132
133
	private function getCurrentFields( $tableName ) {
134
135
		$sql = 'DESCRIBE ' . $tableName;
136
137
		$res = $this->connection->query( $sql, __METHOD__ );
138
		$currentFields = array();
139
140
		foreach ( $res as $row ) {
141
			$type = strtoupper( $row->Type );
142
143
			if ( substr( $type, 0, 8 ) == 'VARCHAR(' ) {
144
				$type .= ' binary'; // just assume this to be the case for VARCHAR, though DESCRIBE will not tell us
145
			}
146
147
			if ( $row->Null != 'YES' ) {
148
				$type .= ' NOT NULL';
149
			}
150
151
			if ( $row->Key == 'PRI' ) { /// FIXME: updating "KEY" is not possible, the below query will fail in this case.
152
				$type .= ' KEY';
153
			}
154
155
			if ( $row->Extra == 'auto_increment' ) {
156
				$type .= ' AUTO_INCREMENT';
157
			}
158
159
			$currentFields[$row->Field] = $type;
160
		}
161
162
		return $currentFields;
163
	}
164
165
	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...
166
167
		if ( !array_key_exists( $fieldName, $currentFields ) ) {
168
			$this->doCreateField( $tableName, $fieldName, $position, $fieldType );
169
		} elseif ( $currentFields[$fieldName] != $fieldType ) {
170
			$this->doUpdateFieldType( $tableName, $fieldName, $position, $currentFields[$fieldName], $fieldType );
171
		} else {
172
			$this->reportMessage( "   ... field $fieldName is fine.\n" );
173
		}
174
	}
175
176
	private function doCreateField( $tableName, $fieldName, $position, $fieldType ) {
177
		$this->reportMessage( "   ... creating field $fieldName ... " );
178
		$this->connection->query( "ALTER TABLE $tableName ADD `$fieldName` $fieldType $position", __METHOD__ );
179
		$this->reportMessage( "done.\n" );
180
	}
181
182
	private function doUpdateFieldType( $tableName, $fieldName, $position, $oldFieldType, $newFieldType ) {
183
		$this->reportMessage( "   ... changing type of field $fieldName from '$oldFieldType' to '$newFieldType' ... " );
184
185
		// To avoid Error: 1068 Multiple primary key defined when a PRIMARY is involved
186
		if ( strpos( $newFieldType, 'AUTO_INCREMENT' ) !== false ) {
187
			$this->connection->query( "ALTER TABLE $tableName DROP PRIMARY KEY", __METHOD__ );
188
		}
189
190
		$this->connection->query( "ALTER TABLE $tableName CHANGE `$fieldName` `$fieldName` $newFieldType $position", __METHOD__ );
191
192
		// http://stackoverflow.com/questions/1873085/how-to-convert-from-varbinary-to-char-varchar-in-mysql
193
		// http://bugs.mysql.com/bug.php?id=34564
194
		if ( strpos( $oldFieldType, 'VARBINARY' ) !== false && strpos( $newFieldType, 'VARCHAR' ) !== false ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
195
		//	$this->connection->query( "SELECT CAST($fieldName AS CHAR) from $tableName", __METHOD__ );
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
196
		}
197
198
		$this->reportMessage( "done.\n" );
199
	}
200
201
	private function doDropField( $tableName, $fieldName ) {
202
		$this->reportMessage( "   ... deleting obsolete field $fieldName ... " );
203
		$this->connection->query( "ALTER TABLE $tableName DROP COLUMN `$fieldName`", __METHOD__ );
204
		$this->reportMessage( "done.\n" );
205
	}
206
207
	/** Index */
208
209
	/**
210
	 * @since 2.5
211
	 *
212
	 * {@inheritDoc}
213
	 */
214
	protected function doCreateIndicies( $tableName, array $indexOptions = null ) {
215
216
		$indicies = $indexOptions['indicies'];
217
218
		// First remove possible obsolete indicies
219
		$this->doDropObsoleteIndicies( $tableName, $indicies );
220
221
		// Add new indexes.
222
		foreach ( $indicies as $indexName => $index ) {
223
			// If the index is an array, it contains the column
224
			// name as first element, and index type as second one.
225
			if ( is_array( $index ) ) {
226
				$columns = $index[0];
227
				$indexType = count( $index ) > 1 ? $index[1] : 'INDEX';
228
			} else {
229
				$columns = $index;
230
				$indexType = 'INDEX';
231
			}
232
233
			$this->doCreateIndex( $tableName, $indexType, $indexName, $columns, $indexOptions );
234
		}
235
	}
236
237
	private function doDropObsoleteIndicies( $tableName, array &$indicies ) {
238
239
		$tableName = $this->connection->tableName( $tableName );
240
		$currentIndicies = $this->getIndexInfo( $tableName );
241
242
		foreach ( $currentIndicies as $indexName => $indexColumn ) {
243
			$id = array_search( $indexColumn, $indicies );
244
			if ( $id !== false || $indexName == 'PRIMARY' ) {
245
				$this->reportMessage( "   ... index $indexColumn is fine.\n" );
246
247
				if ( $id !== false ) {
248
					unset( $indicies[$id] );
249
				}
250
251
			} else { // Duplicate or unrequired index.
252
				$this->doDropIndex( $tableName, $indexName, $indexColumn );
253
			}
254
		}
255
	}
256
257
	/**
258
	 * Get the information about all indexes of a table. The result is an
259
	 * array of format indexname => indexcolumns. The latter is a comma
260
	 * separated list.
261
	 *
262
	 * @return array indexname => columns
263
	 */
264
	private function getIndexInfo( $tableName ) {
265
266
		$indices = array();
267
268
		$res = $this->connection->query( 'SHOW INDEX FROM ' . $tableName, __METHOD__ );
269
270
		if ( !$res ) {
271
			return $indices;
272
		}
273
274
		foreach ( $res as $row ) {
275
			if ( !array_key_exists( $row->Key_name, $indices ) ) {
276
				$indices[$row->Key_name] = $row->Column_name;
277
			} else {
278
				$indices[$row->Key_name] .= ',' . $row->Column_name;
279
			}
280
		}
281
282
		return $indices;
283
	}
284
285
	private function doDropIndex( $tableName, $indexName, $columns ) {
286
		$this->reportMessage( "   ... removing index $columns ..." );
287
		$this->connection->query( 'DROP INDEX ' . $indexName . ' ON ' . $tableName, __METHOD__ );
288
		$this->reportMessage( "done.\n" );
289
	}
290
291
	private function doCreateIndex( $tableName, $indexType, $indexName, $columns, array $indexOptions ) {
0 ignored issues
show
Unused Code introduced by
The parameter $indexName 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...
292
293
		$tableName = $this->connection->tableName( $tableName );
294
		$indexOption = '';
295
296
		$this->reportMessage( "   ... creating new index $columns ..." );
297
298
		if ( isset( $indexOptions['ftSearchOptions']['mysql'] ) ) {
299
			$indexOption = $indexOptions['ftSearchOptions']['mysql'];
300
301
			// By convention the second index has index specific relevance
302
			if ( is_array( $indexOption ) ) {
303
				$indexOption = isset( $indexOption[1] ) ? $indexOption[1] : '';
304
			}
305
		}
306
307
		if ( $indexType === 'FULLTEXT' ) {
308
			$this->connection->query( "ALTER TABLE $tableName ADD $indexType $columns ($columns) $indexOption", __METHOD__ );
309
		} else {
310
			$this->connection->query( "ALTER TABLE $tableName ADD $indexType ($columns)", __METHOD__ );
311
		}
312
313
		$this->reportMessage( "done.\n" );
314
	}
315
316
	/** Drop */
317
318
	/**
319
	 * @since 2.5
320
	 *
321
	 * {@inheritDoc}
322
	 */
323
	protected function doDropTable( $tableName ) {
324
		$this->connection->query( 'DROP TABLE ' . $this->connection->tableName( $tableName ), __METHOD__ );
325
	}
326
327
}
328