r66684 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r66683‎ | r66684 | r66685 >
Date:09:02, 20 May 2010
Author:jeroendedauw
Status:deferred
Tags:
Comment:
Thoroughly cleaned up SMWSQLHelpers class.
Modified paths:
  • /trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SQLHelpers.php (modified) (history)

Diff [purge]

Index: trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SQLHelpers.php
@@ -63,202 +63,252 @@
6464 * Only if the type of some field changes will its order be adjusted explicitly.
6565 *
6666 * @param string $tableName The table name. Does not need to have been passed to DatabaseBase->tableName yet.
67 - * @param array $columns The field names to put indexes on
 67+ * @param array $columns The fields and their types the table should have.
6868 * @param DatabaseBase $db
69 - * @param $reportTo
70 - *
71 - * TODO: split up megamoth srysly o_O
 69+ * @param $reportTo Object to report back to.
7270 */
7371 public static function setupTable( $tableName, array $fields, DatabaseBase $db, $reportTo = null ) {
7472 global $wgDBname, $wgDBtype, $wgDBTableOptions;
7573
7674 $tableName = $db->tableName( $tableName );
7775
78 - self::reportProgress( "Setting up table $tableName ...\n", $reportTo );
 76+ self::reportProgress( "Checking table $tableName ...\n", $reportTo );
7977
8078 if ( $db->tableExists( $tableName ) === false ) { // create new table
81 - $this->createTable( $tableName, $fields, $db, $reportTo );
82 - } else { // check table signature
83 - self::reportProgress( " ... table exists already, checking structure ...\n", $reportTo );
84 -
85 - if ( $wgDBtype == 'postgres' ) { // postgresql
86 - // use the data dictionary in postgresql to get an output comparable to DESCRIBE
87 - // To find out what kind of magic takes place here (and to remove the bugs included), simply use:
88 - // psql
89 - // \set ECHO_HIDDEN
90 - // \d <tablename>
91 - $sql = 'SELECT a.attname as "Field", '
92 - . ' upper(pg_catalog.format_type(a.atttypid, a.atttypmod)) as "Type", '
93 - . ' (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128) '
94 - . ' FROM pg_catalog.pg_attrdef d '
95 - . ' WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) as "Extra", '
96 - . ' case when a.attnotnull THEN \'NO\'::text else \'YES\'::text END as "Null", a.attnum '
97 - . ' FROM pg_catalog.pg_attribute a '
98 - . ' WHERE a.attrelid = ('
99 - . ' SELECT c.oid '
100 - . ' FROM pg_catalog.pg_class c '
101 - . ' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace '
102 - . ' WHERE c.relname ~ \'^(' . $tableName . ')$\' '
103 - . ' AND pg_catalog.pg_table_is_visible(c.oid) '
104 - . ' LIMIT 1 '
105 - . ' ) AND a.attnum > 0 AND NOT a.attisdropped '
106 - . ' ORDER BY a.attnum';
107 - } else { // MySQL
108 - $sql = 'DESCRIBE ' . $tableName;
 79+ self::reportProgress( " Table not found, now creating...\n", $reportTo );
 80+ self::createTable( $tableName, $fields, $db, $reportTo );
 81+ self::reportProgress( " ... done.\n", $reportTo );
 82+ } else {
 83+ self::reportProgress( " Table already exists, checking structure ...\n", $reportTo );
 84+ self::updateTable( $tableName, $fields, $db, $reportTo );
 85+ self::reportProgress( " ... done.\n", $reportTo );
 86+ }
 87+ }
 88+
 89+ /**
 90+ * Creates a new database table with the specified fields.
 91+ *
 92+ * @param string $tableName The table name.
 93+ * @param array $columns The fields and their types the table should have.
 94+ * @param DatabaseBase $db
 95+ * @param $reportTo Object to report back to.
 96+ */
 97+ protected static function createTable( $tableName, array $fields, DatabaseBase $db, $reportTo ) {
 98+ global $wgDBtype, $wgDBTableOptions, $wgDBname;
 99+
 100+ $sql = 'CREATE TABLE ' . ( $wgDBtype == 'postgres' ? '' : "`$wgDBname`." ) . $tableName . ' (';
 101+
 102+ $fieldSql = array();
 103+
 104+ foreach ( $fields as $fieldName => $fieldType ) {
 105+ $fieldSql[] = "$fieldName $fieldType";
 106+ }
 107+
 108+ $sql .= implode( ',', $fieldSql ) . ') ';
 109+ if ( $wgDBtype != 'postgres' ) $sql .= $wgDBTableOptions;
 110+
 111+ $db->query( $sql, __METHOD__ );
 112+ }
 113+
 114+ /**
 115+ * Update a table given an array of field names and field types.
 116+ *
 117+ * @param string $tableName The table name.
 118+ * @param array $columns The fields and their types the table should have.
 119+ * @param DatabaseBase $db
 120+ * @param $reportTo Object to report back to.
 121+ */
 122+ protected static function updateTable( $tableName, array $fields, DatabaseBase $db, $reportTo ) {
 123+ global $wgDBtype;
 124+
 125+ $currentFields = self::getFields( $tableName, $db, $reportTo );
 126+
 127+ $isPostgres = $wgDBtype == 'postgres';
 128+
 129+ if ( !$isPostgres ) $position = 'FIRST';
 130+
 131+ // Loop through all the field definitions, and handle each definition for either postgres or MySQL.
 132+ foreach ( $fields as $fieldName => $fieldType ) {
 133+ if ( $isPostgres ) {
 134+ self::updatePostgresField( $tableName, $fieldName, $fieldType, $currentFields, $db, $reportTo );
109135 }
 136+ else {
 137+ self::updateMySqlField( $tableName, $fieldName, $fieldType, $currentFields, $db, $reportTo, $position );
 138+ $position = "AFTER $fieldName";
 139+ }
110140
111 - $res = $db->query( $sql, __METHOD__ );
112 - $curfields = array();
113 - $result = array();
114 -
115 - while ( $row = $db->fetchObject( $res ) ) {
116 - $type = strtoupper( $row->Type );
 141+ $currentFields[$fieldName] = false;
 142+ }
 143+
 144+ // The updated fields have their value set to false, so if a field has a value
 145+ // that differs from false, it's an obsolete one that should be removed.
 146+ foreach ( $currentFields as $fieldName => $value ) {
 147+ if ( $value !== false ) {
 148+ SMWSQLHelpers::reportProgress( " ... deleting obsolete field $fieldName ... ", $reportTo );
117149
118 - if ( $wgDBtype == 'postgres' ) { // postgresql
119 - if ( eregi( '^nextval\\(.+\\)$', $row->Extra ) ) {
120 - $type = 'SERIAL NOT NULL';
121 - } elseif ( $row->Null != 'YES' ) {
122 - $type .= ' NOT NULL';
123 - }
124 - } else { // mysql
125 - if ( substr( $type, 0, 8 ) == 'VARCHAR(' ) {
126 - $type .= ' binary'; // just assume this to be the case for VARCHAR, though DESCRIBE will not tell us
127 - }
128 -
129 - if ( $row->Null != 'YES' ) {
130 - $type .= ' NOT NULL';
131 - }
132 -
133 - if ( $row->Key == 'PRI' ) { /// FIXME: updating "KEY" is not possible, the below query will fail in this case.
134 - $type .= ' KEY';
135 - }
136 -
137 - if ( $row->Extra == 'auto_increment' ) {
138 - $type .= ' AUTO_INCREMENT';
139 - }
 150+ if ( $isPostgres ) {
 151+ $db->query( "ALTER TABLE \"" . $tableName . "\" DROP COLUMN \"" . $fieldName . "\"", __METHOD__ );
140152 }
 153+ else {
 154+ $db->query( "ALTER TABLE $tableName DROP COLUMN `$fieldName`", __METHOD__ );
 155+ }
141156
142 - $curfields[$row->Field] = $type;
 157+ SMWSQLHelpers::reportProgress( "done.\n", $reportTo );
143158 }
 159+ }
 160+ }
144161
 162+ /**
 163+ * Returns an array of fields (as keys) and their types (as values).
 164+ *
 165+ * @param string $tableName The table name.
 166+ * @param DatabaseBase $db
 167+ * @param $reportTo Object to report back to.
 168+ *
 169+ * @return array
 170+ */
 171+ protected static function getFields( $tableName, DatabaseBase $db, $reportTo ) {
 172+ global $wgDBtype;
 173+
 174+ if ( $wgDBtype == 'postgres' ) {
 175+ // Use the data dictionary in postgresql to get an output comparable to DESCRIBE.
 176+ $sql = <<<EOT
 177+SELECT
 178+ a.attname as "Field",
 179+ upper(pg_catalog.format_type(a.atttypid, a.atttypmod)) as "Type",
 180+ (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)
 181+ FROM pg_catalog.pg_attrdef d
 182+ WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) as "Extra",
 183+ case when a.attnotnull THEN \'NO\'::text else \'YES\'::text END as "Null", a.attnum
 184+ FROM pg_catalog.pg_attribute a
 185+ WHERE a.attrelid = (
 186+ SELECT c.oid
 187+ FROM pg_catalog.pg_class c
 188+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
 189+ WHERE c.relname ~ \'^(' . $tableName . ')$\'
 190+ AND pg_catalog.pg_table_is_visible(c.oid)
 191+ LIMIT 1
 192+ ) AND a.attnum > 0 AND NOT a.attisdropped
 193+ ORDER BY a.attnum
 194+EOT;
 195+ } else { // MySQL
 196+ $sql = 'DESCRIBE ' . $tableName;
 197+ }
 198+
 199+ $res = $db->query( $sql, __METHOD__ );
 200+ $curfields = array();
 201+ $result = array();
 202+
 203+ while ( $row = $db->fetchObject( $res ) ) {
 204+ $type = strtoupper( $row->Type );
 205+
145206 if ( $wgDBtype == 'postgres' ) { // postgresql
146 - foreach ( $fields as $name => $type ) {
147 - $keypos = strpos( $type, ' PRIMARY KEY' );
148 -
149 - if ( $keypos > 0 ) {
150 - $type = substr( $type, 0, $keypos );
151 - }
152 -
153 - if ( !array_key_exists( $name, $curfields ) ) {
154 - SMWSQLHelpers::reportProgress( " ... creating column $name ... ", $reportTo );
155 -
156 - $db->query( "ALTER TABLE $tableName ADD \"" . $name . "\" $type", __METHOD__ );
157 - $result[$name] = 'new';
158 -
159 - SMWSQLHelpers::reportProgress( "done \n", $reportTo );
160 - } elseif ( $curfields[$name] != $type ) {
161 - SMWSQLHelpers::reportProgress( " ... changing type of column $name from '$curfields[$name]' to '$type' ... ", $reportTo );
162 - $notnullposnew = strpos( $type, ' NOT NULL' );
163 -
164 - if ( $notnullposnew > 0 ) {
165 - $type = substr( $type, 0, $notnullposnew );
166 - }
167 -
168 - $notnullposold = strpos( $curfields[$name], ' NOT NULL' );
169 - $typeold = ( $notnullposold > 0 ) ? substr( $curfields[$name], 0, $notnullposold ):$curfields[$name];
170 -
171 - if ( $typeold != $type ) {
172 - $db->query( "ALTER TABLE \"" . $tableName . "\" ALTER COLUMN \"" . $name . "\" TYPE " . $type, __METHOD__ );
173 - }
174 -
175 - if ( $notnullposold != $notnullposnew ) {
176 - $db->query( "ALTER TABLE \"" . $tableName . "\" ALTER COLUMN \"" . $name . "\" " . ( $notnullposnew > 0 ? 'SET':'DROP' ) . " NOT NULL", __METHOD__ );
177 - }
178 -
179 - $result[$name] = 'up';
180 - $curfields[$name] = false;
181 - SMWSQLHelpers::reportProgress( "done.\n", $reportTo );
182 - } else {
183 - SMWSQLHelpers::reportProgress( " ... column $name is fine\n", $reportTo );
184 - $curfields[$name] = false;
185 - }
 207+ if ( eregi( '^nextval\\(.+\\)$', $row->Extra ) ) {
 208+ $type = 'SERIAL NOT NULL';
 209+ } elseif ( $row->Null != 'YES' ) {
 210+ $type .= ' NOT NULL';
186211 }
 212+ } else { // mysql
 213+ if ( substr( $type, 0, 8 ) == 'VARCHAR(' ) {
 214+ $type .= ' binary'; // just assume this to be the case for VARCHAR, though DESCRIBE will not tell us
 215+ }
187216
188 - foreach ( $curfields as $name => $value ) {
189 - if ( $value !== false ) {
190 - SMWSQLHelpers::reportProgress( " ... deleting obsolete column $name ... ", $reportTo );
191 -
192 - $db->query( "ALTER TABLE \"" . $tableName . "\" DROP COLUMN \"" . $name . "\"", __METHOD__ );
193 - $result[$name] = 'del';
194 -
195 - SMWSQLHelpers::reportProgress( "done.\n", $reportTo );
196 - }
 217+ if ( $row->Null != 'YES' ) {
 218+ $type .= ' NOT NULL';
197219 }
198 - } else { // mysql
199 - $position = 'FIRST';
200 -
201 - foreach ( $fields as $name => $type ) {
202 - if ( !array_key_exists( $name, $curfields ) ) {
203 - SMWSQLHelpers::reportProgress( " ... creating column $name ... ", $reportTo );
204 -
205 - $db->query( "ALTER TABLE $tableName ADD `$name` $type $position", __METHOD__ );
206 - $result[$name] = 'new';
207 -
208 - SMWSQLHelpers::reportProgress( "done \n", $reportTo );
209 - } elseif ( $curfields[$name] != $type ) {
210 - SMWSQLHelpers::reportProgress( " ... changing type of column $name from '$curfields[$name]' to '$type' ... ", $reportTo );
211 -
212 - $db->query( "ALTER TABLE $tableName CHANGE `$name` `$name` $type $position", __METHOD__ );
213 - $result[$name] = 'up';
214 - $curfields[$name] = false;
215 -
216 - SMWSQLHelpers::reportProgress( "done.\n", $reportTo );
217 - } else {
218 - SMWSQLHelpers::reportProgress( " ... column $name is fine\n", $reportTo );
219 -
220 - $curfields[$name] = false;
 220+
 221+ if ( $row->Key == 'PRI' ) { /// FIXME: updating "KEY" is not possible, the below query will fail in this case.
 222+ $type .= ' KEY';
221223 }
222 - $position = "AFTER $name";
223 - }
224 -
225 - foreach ( $curfields as $name => $value ) {
226 - if ( $value !== false ) { // not encountered yet --> delete
227 - SMWSQLHelpers::reportProgress( " ... deleting obsolete column $name ... ", $reportTo );
228 - $db->query( "ALTER TABLE $tableName DROP COLUMN `$name`", __METHOD__ );
229 - $result[$name] = 'del';
230 - SMWSQLHelpers::reportProgress( "done.\n", $reportTo );
 224+
 225+ if ( $row->Extra == 'auto_increment' ) {
 226+ $type .= ' AUTO_INCREMENT';
231227 }
232 - }
233228 }
234 -
235 - SMWSQLHelpers::reportProgress( " ... table $tableName set up successfully.\n", $reportTo );
236229
237 - return $result;
 230+ $curfields[$row->Field] = $type;
238231 }
 232+
 233+ return $curfields;
239234 }
240235
241 - private static function createTable( $tableName, array $fields, DatabaseBase $db, $reportTo ) {
242 - global $wgDBtype, $wgDBTableOptions, $wgDBname;
 236+ /**
 237+ * Update a single field given it's name and type and an array of current fields. Postgres version.
 238+ *
 239+ * @param string $tableName The table name.
 240+ * @param string $name The field name.
 241+ * @param string $type The field type and attributes.
 242+ * @param array $currentFields List of fields as they have been found in the database.
 243+ * @param DatabaseBase $db
 244+ * @param object $reportTo Object to report back to.
 245+ */
 246+ protected static function updatePostgresField( $tableName, $name, $type, array $currentFields, DatabaseBase $db, $reportTo ) {
 247+ $keypos = strpos( $type, ' PRIMARY KEY' );
243248
244 - $sql = 'CREATE TABLE ' . ( $wgDBtype == 'postgres' ? '' : "`$wgDBname`." ) . $tableName . ' (';
 249+ if ( $keypos > 0 ) {
 250+ $type = substr( $type, 0, $keypos );
 251+ }
245252
246 - $fieldSql = array();
247 -
248 - foreach ( $fields as $fieldName => $fieldType ) {
249 - $fieldSql[] = "$fieldName $fieldType";
 253+ if ( !array_key_exists( $name, $currentFields ) ) {
 254+ self::reportProgress( " ... creating field $name ... ", $reportTo );
 255+
 256+ $db->query( "ALTER TABLE $tableName ADD \"" . $name . "\" $type", __METHOD__ );
 257+
 258+ self::reportProgress( "done.\n", $reportTo );
 259+ } elseif ( $currentFields[$name] != $type ) {
 260+ self::reportProgress( " ... changing type of field $name from '$currentFields[$name]' to '$type' ... ", $reportTo );
 261+ $notnullposnew = strpos( $type, ' NOT NULL' );
 262+
 263+ if ( $notnullposnew > 0 ) {
 264+ $type = substr( $type, 0, $notnullposnew );
 265+ }
 266+
 267+ $notnullposold = strpos( $currentFields[$name], ' NOT NULL' );
 268+ $typeold = ( $notnullposold > 0 ) ? substr( $currentFields[$name], 0, $notnullposold ) : $currentFields[$name];
 269+
 270+ if ( $typeold != $type ) {
 271+ $db->query( "ALTER TABLE \"" . $tableName . "\" ALTER COLUMN \"" . $name . "\" TYPE " . $type, __METHOD__ );
 272+ }
 273+
 274+ if ( $notnullposold != $notnullposnew ) {
 275+ $db->query( "ALTER TABLE \"" . $tableName . "\" ALTER COLUMN \"" . $name . "\" " . ( $notnullposnew > 0 ? 'SET' : 'DROP' ) . " NOT NULL", __METHOD__ );
 276+ }
 277+
 278+ self::reportProgress( "done.\n", $reportTo );
 279+ } else {
 280+ self::reportProgress( " ... field $name is fine.\n", $reportTo );
250281 }
251 -
252 - $sql .= implode( ',', $fieldSql ) . ') ';
253 - if ( $wgDBtype != 'postgres' ) $sql .= $wgDBTableOptions;
254 -
255 - $db->query( $sql, __METHOD__ );
256 -
257 - self::reportProgress( " ... new table created\n", $reportTo );
258282 }
259283
260 - private static function updateTable() {
261 -
262 - }
 284+ /**
 285+ * Update a single field given it's name and type and an array of current fields. MySQL version.
 286+ *
 287+ * @param string $tableName The table name.
 288+ * @param string $name The field name.
 289+ * @param string $type The field type and attributes.
 290+ * @param array $currentFields List of fields as they have been found in the database.
 291+ * @param DatabaseBase $db
 292+ * @param object $reportTo Object to report back to.
 293+ * @param string $position
 294+ */
 295+ protected static function updateMySqlField( $tableName, $name, $type, array $currentFields, DatabaseBase $db, $reportTo, $position ) {
 296+ if ( !array_key_exists( $name, $currentFields ) ) {
 297+ self::reportProgress( " ... creating field $name ... ", $reportTo );
 298+
 299+ $db->query( "ALTER TABLE $tableName ADD `$name` $type $position", __METHOD__ );
 300+ $result[$name] = 'new';
 301+
 302+ self::reportProgress( "done.\n", $reportTo );
 303+ } elseif ( $currentFields[$name] != $type ) {
 304+ self::reportProgress( " ... changing type of field $name from '$currentFields[$name]' to '$type' ... ", $reportTo );
 305+
 306+ $db->query( "ALTER TABLE $tableName CHANGE `$name` `$name` $type $position", __METHOD__ );
 307+ $result[$name] = 'up';
 308+ self::reportProgress( "done.\n", $reportTo );
 309+ } else {
 310+ self::reportProgress( " ... field $name is fine.\n", $reportTo );
 311+ }
 312+ }
263313
264314 /**
265315 * Make sure that each of the column descriptions in the given array is indexed by *one* index
@@ -342,8 +392,13 @@
343393 return true;
344394 }
345395
346 - /// If a receiver is given, report the given message to its reportProgress method.
347 - protected static function reportProgress( $msg, $receiver = null ) {
 396+ /**
 397+ * Reports the given message to the reportProgress method of the $receiver.
 398+ *
 399+ * @param string $msg
 400+ * @param object $receiver
 401+ */
 402+ protected static function reportProgress( $msg, $receiver ) {
348403 if ( $receiver !== null ) $receiver->reportProgress( $msg );
349404 }
350405

Status & tagging log