"; $sql->gen('SET SQL_QUOTE_SHOW_CREATE = 1'); $qry = 'SHOW CREATE TABLE `'.$prefix.$table_name."`"; if (!($z = $sql->gen($qry))) { return FALSE; } $row = $sql->fetch('num'); $tmp = str_replace("`", "", stripslashes($row[1])).';'; // Add semicolon to work with our parser $count = preg_match_all("#CREATE\s+?TABLE\s+?`?({$prefix}{$table_name})`?\s+?\((.*?)\)\s+?(?:TYPE|ENGINE)\s*\=\s*(.*?);#is", $tmp, $matches, PREG_SET_ORDER); if ($count === FALSE) { return "Error occurred"; } if (!$count) { return "No matches"; } if(isset($matches[0][2]) && is_string($matches[0][2])) { $matches[0][2] = trim($matches[0][2]); } return $matches; } /** * Routine to do first-level parse of table structure *--------------------------------------------------- * Given the name of a file, returns an array, with each element being a table creation definition. * Tracks the last file read - only reads it once * If the file name is an empty string, uses a previously read/set buffer * * @param string $table_name - If specified, returns only that table's info; otherwise returns a list of all tables * The table name must include a prefix where appropriate (although not required with standard E107 table definition files) * @param string $file_name * @return string|array * - if error, returns a brief text message * - if successful, returns an array of table definitions, each of which is itself an array: * [0] - The complete string which creates a table (unless a prefix needs adding to the table name), including terminating ';' * [1] - The table name. Any backticks are stripped * [2] - Field definitions, with the surrounding (...) stripped * [3] - The 'TYPE' field ('TYPE=' is stripped) and any AUTO-INCREMENT definition or other text. */ function get_table_def($table_name = '', $file_name = "") { if ($file_name != '') { // Read in and buffer a new file (if we've not already got one) if ($this->last_file != $file_name) { if (!is_readable($file_name)) { return "No file"; } $temp = file_get_contents($file_name); // Strip any php header $temp = preg_replace("#\<\?php.*?\?\>#mis", '', $temp); // Strip any comments (only /*...*/ supported $this->file_buffer = preg_replace("#\/\*.*?\*\/#mis", '', $temp); $this->last_file = $file_name; } } if (!$table_name) { $table_name = '\w+?'; } // Regex should be identical to that in get_current_table (apart from the source text variable name) $count = preg_match_all("#CREATE\s+?TABLE\s+?`?({$table_name})`?\s+?\((.*?)\)\s+?(?:TYPE|ENGINE)\s*\=\s*(.*?);#is", $this->file_buffer, $matches, PREG_SET_ORDER); if ($count === false) { return "Error occurred"; } if (!$count) { return "No matches"; } return $matches; } // Parses the block of lines which make up the field and index definitions // Returns an array where each entry is the definitions of a field or index /** * @param $text * @return array|false */ function parse_field_defs($text) { $text = (string) $text; $ans = array( ); $text = str_replace("\r", "\n", $text); $field_lines = explode("\n", $text); $defs = array(); foreach ($field_lines as $fv) { unset($defs); $fv = trim(str_replace(' ', ' ', $fv)); $fv = str_replace('`', '', $fv); if (substr($fv, -1) == ',') { $fv = trim(substr($fv, 0, -1)); } // echo "Line: ".$fv."
"; if ($fv) { $fd = explode(' ', $fv); switch (strtoupper($fd[0])) { case 'PRIMARY': if (strtoupper($fd[1]) == 'KEY') $defs['type'] = 'pkey'; $defs['name'] = $fd[2]; break; case 'UNIQUE': if (count($fd) < 3) { $this->errors[] = "Truncated definition after UNIQUE {$fv}: ".$fd[1]."
"; } elseif (strtoupper($fd[1]) == 'KEY') { $defs['type'] = 'ukey'; $defs['name'] = $fd[2]; if (isset($fd[3])) $defs['keyfield'] = $fd[3]; else $defs['keyfield'] = '['.$fd[2].']'; } else { $this->errors[] = "Unrecognised word after UNIQUE in definition {$fv}: ".$fd[1]."
"; } break; case 'FULLTEXT': if (count($fd) < 3) { $this->errors[] = "Truncated definition after FULLTEXT {$fv}: ".$fd[1]."
"; } elseif (strtoupper($fd[1]) == 'KEY') { $defs['type'] = 'ftkey'; $defs['name'] = $fd[2]; if (isset($fd[3])) $defs['keyfield'] = $fd[3]; else $defs['keyfield'] = '['.$fd[2].']'; } else { $this->errors[] = "Unrecognised word after FULLTEXT in definition {$fv}: ".$fd[1]."
"; } break; case 'KEY': $defs['type'] = 'key'; $defs['name'] = $fd[1]; if (isset($fd[2])) { $defs['keyfield'] = $fd[2]; } else { $defs['keyfield'] = '['.$fd[1].']'; } break; default: // Must be a DB field name $defs['type'] = 'field'; $defs['name'] = $fd[0]; $defs['fieldtype'] = $fd[1]; $i = 2; // First unused field if ((strpos($fd[1], 'int') === 0) || (strpos($fd[1], 'tinyint') === 0) || (strpos($fd[1], 'smallint') === 0) || (strpos($fd[1], 'bigint') === 0)) { if (isset($fd[2]) && (strtoupper($fd[2]) == 'UNSIGNED')) { $defs['vartype'] = $fd[2]; $i++; } } while ($i < count($fd)) { switch (strtoupper($fd[$i])) { case 'NOT': if (isset($fd[$i + 1]) && strtoupper($fd[$i + 1]) == 'NULL') { $i++; $defs['nulltype'] = 'NOT NULL'; } else { // Syntax error $this->errors[] = "Unrecognised word in definition {$i} after 'NOT': ".$fd[$i + 1]."
"; } break; case 'DEFAULT': if (isset($fd[$i + 1])) { $i++; $defs['default'] = $fd[$i]; } break; case 'COLLATE': $i++; // Just skip over - we ignore collation break; case 'AUTO_INCREMENT': $defs['autoinc'] = TRUE; break; default: if(E107_DBG_SQLDETAILS) { $mes = e107::getMessage(); $mes->add("db_table_admin_class.php :: parse_field_defs() Line: 230 - Unknown definition {$i}: ".$fd[$i], E_MESSAGE_DEBUG); } } $i++; } } if (count($defs) > 1) { $ans[] = $defs; } else { $this->errors[] = "Partial definition
"; } } } if (!count($ans)) { return FALSE; } return $ans; } // Utility routine - given our array-based definition, create a string MySQL field definition /** * @param $list * @return mixed|string */ function make_def($list) { switch ($list['type']) { case 'key': return 'KEY '.$list['name'].' ('.str_replace(array( '(', ')' ), '', $list['keyfield']).')'; case 'ukey': return 'UNIQUE KEY '.$list['name'].' ('.str_replace(array( '(', ')' ), '', $list['keyfield']).')'; case 'ftkey': return 'FULLTEXT KEY '.$list['name'].' ('.str_replace(array( '(', ')' ), '', $list['keyfield']).')'; case 'pkey': return 'PRIMARY KEY ('.$list['name'].')'; case 'field': // Require a field - got a key. so add a field at the end $def = $list['name']; if (isset($list['fieldtype'])) { $def .= ' '.$list['fieldtype']; } if (isset($list['vartype'])) { $def .= ' '.$list['vartype']; } if (isset($list['nulltype'])) { $def .= ' '.$list['nulltype']; } if (isset($list['default'])) { $def .= ' default '.$list['default']; } if (vartrue($list['autoinc'])) { $def .= ' auto_increment'; } return $def; } return "Cannot generate definition for: ".$list['type'].' '.$list['name']; } // Compare two field/index lists as generated by parse_field_defs // If $stop_on_error is TRUE, returns TRUE if the same, false if different // Return a text list of differences, plus an array of MySQL queries to fix // List1 is the reference, List 2 is the actual // This version looks ahead on a failed match, and moves a field up in the table if already defined - should retain as much as possible /** * @param $list1 * @param $list2 * @param $stop_on_error * @return array[]|bool */ function compare_field_lists($list1, $list2, $stop_on_error = FALSE) { $i = 0; // Counts records in list1 (required format) $j = 0; // Counts records in $created_list (our 'table so far' list) $error_list = array( ); // Free text list of differences $change_list = array( ); // MySQL statements to implement changes $created_list = array( ); // List of field defs that we build up (just names) while ($i < count($list1)) { if (count($list2) == 0) { // Missing field at end if ($stop_on_error) { return FALSE; } $error_list[] = 'Missing field at end: '.$list1[$i]['name']; $change_list[] = 'ADD '.$this->make_def($list1[$i]); $created_list[$j] = $list1[$i]['name']; $j++; } elseif ($list1[$i]['type'] == $list2[0]['type']) { // Worth doing a compare - fields are same type // echo $i.': compare - '.$list1[$i]['name'].', '.$list2[0]['name'].'
'; if (strcasecmp($list1[$i]['name'], $list2[0]['name']) != 0) { // Names differ, so need to add or subtract a field. // echo $i.': names differ - '.$list1[$i]['name'].', '.$list2[0]['name'].'
'; if ($stop_on_error) { return FALSE; } $found = FALSE; for ($k = $i + 1, $kMax = count($list1); $k < $kMax; $k++) { // echo "Compare ".$list1[$k]['name'].' with '.$list2[0]['name']; if (strcasecmp($list1[$k]['name'], $list2[0]['name']) == 0) { // Field in list2 found later in list1; do nothing // echo " - match
"; $found = TRUE; break; } // echo " - no match
"; } if (!$found) { // Field in existing DB no longer required $error_list[] = 'Obsolete field: '.$list2[0]['name']; $change_list[] = 'DROP '.($list2[0]['type'] == 'field' ? '' : 'INDEX ').$list2[0]['name']; array_shift($list2); continue; } $found = FALSE; for ($k = 0, $kMax = count($list2); $k < $kMax; $k++) { // echo "Compare ".$list1[$i]['name'].' with '.$list2[$k]['name']; if (strcasecmp($list1[$i]['name'], $list2[$k]['name']) == 0) { // Field found; we need to move it up // echo " - match
"; $found = TRUE; break; } // echo " - no match
"; } if ($found) { $error_list[] = 'Field out of position: '.$list2[$k]['name']; $change_list[] = 'MODIFY '.$this->make_def($list1[$i]).(count($created_list) ? ' AFTER '.$created_list[count($created_list) - 1] : ' FIRST'); array_splice($list2, $k, 1); // Finished with this element - delete it, and renumber the keys $created_list[$j] = $list1[$i]['name']; $j++; // The above also amends any parameters as necessary } else { // Need to insert a field $error_list[] = 'Missing field: '.$list1[$i]['name'].' (found: '.$list2[0]['type'].' '.$list2[0]['name'].')'; switch ($list1[$i]['type']) { case 'key': case 'ukey': case 'ftkey': case 'pkey': // Require a key $change_list[] = 'ADD '.$this->make_def($list1[$i]); $error_list[] = 'Missing index: '.$list1[$i]['name']; $created_list[$j] = $list1[$i]['name']; $j++; break; case 'field': $change_list[] = 'ADD '.$this->make_def($list1[$i]).(count($created_list) ? ' AFTER '.$created_list[count($created_list) - 1] : ' FIRST'); $error_list[] = 'Missing field: '.$list1[$i]['name'].' (found: '.$list2[0]['type'].' '.$list2[0]['name'].')'; $created_list[$j] = $list1[$i]['name']; $j++; break; } } } else { // Field/index is present as required; may be changes though // Any difference and we need to update the table // echo $i.': name match - '.$list1[$i]['name'].'
'; foreach ($list1[$i] as $fi=>$v) { $t = $list2[0][$fi]; if (stripos($v, 'varchar') !== FALSE) { $v = substr($v, 3); } // Treat char, varchar the same if (stripos($t, 'varchar') !== FALSE) { $t = substr($t, 3); } // Treat char, varchar the same if (strcasecmp($t, $v) !== 0) { if ($stop_on_error) { return FALSE; } $error_list[] = 'Incorrect definition: '.$fi.' = '.$v; $change_list[] = 'MODIFY '.$this->make_def($list1[$i]); break; } } array_shift($list2); $created_list[$j] = $list1[$i]['name']; $j++; } } else { // Field type has changed. We know fields come before indexes. So something's missing // echo $i.': types differ - '.$list1[$i]['type'].' '.$list1[$i]['name'].', '.$list2[$k]['type'].' '.$list2[$k]['name'].'
'; if ($stop_on_error) { return FALSE; } switch ($list1[$i]['type']) { case 'key': case 'ukey': case 'ftkey': case 'pkey': // Require a key - got a field, or a key of a different type while ((count($list2) > 0) && ($list2[0]['type'] == 'field')) { $error_list[] = 'Extra field: '.$list2[0]['name']; $change_list[] = 'DROP '.$list2[0]['name']; array_shift($list2); } if ((count($list2) == 0) || ($list1[$i]['type'] != $list2[0]['type'])) { // need to add a key $change_list[] = 'ADD '.$this->make_def($list1[$i]); $error_list[] = 'Missing index: '.$list1[$i]['name']; $created_list[$j] = $list1[$i]['name']; $j++; } break; case 'field': // Require a field - got a key. so add a field at the end $error_list[] = 'Missing field: '.$list1[$i]['name'].' (found: '.$list2[0]['type'].' '.$list2[0]['name'].')'; $change_list[] = 'ADD '.$this->make_def($list1[$i]); break; default: $error_list[] = 'Unknown field type: '.$list1[$i]['type']; $change_list[] = ''; // Null entry to keep them in step } } // End - missing or extra field $i++; // On to next field } if (count($list2)) { // Surplus fields in actual table // Echo count($list2)." fields at end to delete
"; foreach ($list2 as $f) { switch ($f['type']) { case 'key': case 'ukey': case 'ftkey': case 'pkey': // Require a key - got a field $error_list[] = 'Extra index: '.$list2[0]['name']; $change_list[] = 'DROP INDEX '.$list2[0]['name']; break; case 'field': $error_list[] = 'Extra field: '.$list2[0]['name']; $change_list[] = 'DROP '.$list2[0]['name']; break; } } } if ($stop_on_error) return TRUE; // If doing a simple comparison and we get to here, all matches return array( $error_list, $change_list ); } /** * @param $result * @return string */ function make_changes_list($result) { if (!is_array($result)) { return "Not an array
"; } $text = ""; for ($i = 0, $iMax = count($result[0]); $i < $iMax; $i++) { $text .= ""; $text .= ""; $text .= "\n"; } $text .= "
{$result[0][$i]}{$result[1][$i]}


"; return $text; } // Return a table of info from the output of get_table_def /** * @param $result * @return string */ function make_table_list($result) { if (!is_array($result)) { return "Not an array
"; } $text = ""; for ($i = 0, $iMax = count($result); $i < $iMax; $i++) { $text .= ""; $text .= ""; $text .= ""; $text .= "\n"; } $text .= "
{$result[$i][0]}{$result[$i][1]}{$result[$i][2]}{$result[$i][3]}


"; return $text; } // Return a table of info from the output of parse_field_defs() /** * @param $fields * @return string */ function make_field_list($fields) { $text = ""; foreach ($fields as $f) { switch ($f['type']) { case 'pkey': $text .= ""; break; case 'ukey': $text .= ""; break; case 'ftkey': $text .= ""; break; case 'key': $text .= ""; break; case 'field': $text .= ""; if (isset($f['nulltype'])) { $text .= ""; } else { $text .= ""; } if (isset($f['default'])) { $text .= ""; } elseif (isset($f['autoinc'])) { $text .= ""; } else { $text .= ""; } $text .= ""; break; default: $text .= ""; } } $text .= "
PRIMARY KEY{$f['name']} 
UNIQUE KEY{$f['name']}{$f['keyfield']}
FULLTEXT KEY{$f['name']}{$f['keyfield']}
KEY{$f['name']}{$f['keyfield']}
FIELD{$f['name']}{$f['fieldtype']}"; if (isset($f['vartype'])) { $text .= " ".$f['vartype']; } $text .= "{$f['nulltype']} default {$f['default']}AUTO_INCREMENT 
!!Unknown!!{$f['type']} 


--Ends--
"; return $text; } //-------------------------------------------------- // Update a table to required structure //-------------------------------------------------- // $newStructure is an array element as returned from get_table_def() // If $mlUpdate is TRUE, applies same query to all tables of same language // Return TRUE on success. // Return text string if $justCheck is TRUE and changes needed // Return text string on most failures // Return FALSE on certain failures (generally indicative of code/system problems) /** * @param $newStructure * @param $justCheck * @param $makeNewifNotExist * @param $mlUpdate * @return bool|string */ function update_table_structure($newStructure, $justCheck = FALSE, $makeNewifNotExist = TRUE, $mlUpdate = FALSE) { global $sql; // Pull out table name $debugLevel = E107_DBG_SQLDETAILS; $tableName = $newStructure[1]; if (!$sql->isTable($tableName)) { if ($makeNewifNotExist === FALSE) { return 'Table doesn\'t exist'; } if ($sql->gen($newStructure[0])) { return TRUE; } return 'Error creating new table: '.$tableName; } $reqFields = $this->parse_field_defs($newStructure[2]); // Required field definitions if ($debugLevel) { echo "Required table structure:
".$this->make_field_list($reqFields); } if ((($actualDefs = $this->get_current_table($tableName)) === FALSE) || !is_array($actualDefs)) // Get actual table definition (Adds current default prefix) { return "Couldn't get table structure: {$tableName}
"; } else { // echo $db_parser->make_table_list($actual_defs); $actualFields = $this->parse_field_defs($actualDefs[0][2]); // Split into field definitions if ($debugLevel) { echo 'Actual table structure:
'.$this->make_field_list($actualFields); } $diffs = $this->compare_field_lists($reqFields, $actualFields); // Work out any differences if (count($diffs[0])) { // Changes needed if ($justCheck) { return 'Field changes rqd; table: '.$tableName.'
'; } // Do the changes here if ($debugLevel) { echo "List of changes found:
".$this->make_changes_list($diffs); } $qry = 'ALTER TABLE '.MPREFIX.$tableName.' '.implode(', ', $diffs[1]); if ($debugLevel) { echo 'Update Query used: '.$qry.'
'; } if ($mlUpdate) { $ret = $sql->db_Query_all($qry); // Returns TRUE = success, FALSE = fail } else { $ret = $sql->gen($qry); } if ($ret === FALSE) { return $sql->dbError(__METHOD__); } } return TRUE; // Success even if no changes required } } /** * @param $pathToSqlFile * @param $tableName * @param $addPrefix * @param $renameTable * @return bool|int */ function createTable($pathToSqlFile = '', $tableName = '', $addPrefix = true, $renameTable = '') { // $e107 = e107::getInstance(); $tmp = $this->get_table_def($tableName, $pathToSqlFile); $createText = $tmp[0][0]; $newTableName = ($renameTable ? $renameTable : $tableName); if ($addPrefix) { $newTableName = MPREFIX.$newTableName; } if ($newTableName != $tableName) { $createText = preg_replace('#create +table +(\w*?) +#i', 'CREATE TABLE '.$newTableName.' ', $createText); } return e107::getDb()->gen($createText); } /** * Used by $sql->makeTableDef() to create an e_CACHE_DB.$tableName.'.php' file. * @param array $fieldDefs * @return array as returned by parse_field_defs() */ public function make_field_types($fieldDefs=array()) { $outDefs = array(); foreach ($fieldDefs as $k => $v) { switch ($v['type']) { case 'field' : // if (!empty($v['autoinc'])) // { //break; Probably include autoinc fields in array // } $baseType = preg_replace('#\(.*?\)#', '', $v['fieldtype']); // Should strip any length switch ($baseType) { case 'int' : case 'integer': case 'smallint': case 'shortint' : case 'tinyint' : case 'mediumint': case 'bigint': $outDefs['_FIELD_TYPES'][$v['name']] = 'int'; break; case 'char' : case 'text' : case 'varchar' : case 'tinytext' : case 'mediumtext' : case 'longtext' : case 'enum' : $outDefs['_FIELD_TYPES'][$v['name']] = 'escape'; //XXX toDB() causes serious BC issues. break; } // if($v['name']) if (isset($v['nulltype']) && !isset($v['default'])) { $outDefs['_NOTNULL'][$v['name']] = ''; } break; case 'pkey' : case 'ukey' : case 'key' : case 'ftkey' : break; // Do nothing with keys for now default : $this->errors[] = "Unexpected field type: {$k} => {$v['type']}
"; } } return $outDefs; } }