예제 #1
0
 public static function convert($file)
 {
     $file_name = realpath($file);
     $fp_slonik = fopen($file_name, 'r');
     if (!$fp_slonik) {
         throw new exception("failed to open slonik script " . $file . " for conversion");
     }
     $doc = new SimpleXMLElement('<dbsteward></dbsteward>');
     $line = '';
     while (($c = fgetc($fp_slonik)) !== FALSE) {
         switch ($c) {
             // catch lines starting with # comments and ignore them
             case '#':
                 // only if line hasn't started yet
                 if (strlen(trim($line)) == 0) {
                     $comment = TRUE;
                 }
                 break;
                 // convert newlines to spaces
             // convert newlines to spaces
             case "\n":
                 $c = ' ';
                 // newline encountered, so comment is over
                 $comment = FALSE;
                 break;
                 // the statement terminated
             // the statement terminated
             case ';':
                 // not in comment state?
                 if (!$comment) {
                     self::parse_line($doc, $line);
                     $line = '';
                 }
                 break;
                 // by default, add character to the line
             // by default, add character to the line
             default:
                 // late-start # comment line
                 $trimmed_line = trim($line);
                 if ($trimmed_line[0] == '#') {
                     $comment = TRUE;
                 } else {
                     if (!$comment) {
                         $line .= $c;
                     }
                 }
                 break;
         }
     }
     fclose($fp_slonik);
     //xml_parser::validate_xml($doc->asXML());
     return xml_parser::format_xml($doc->saveXML());
 }
예제 #2
0
 public function xml_convert($files)
 {
     if (!is_array($files)) {
         $files = array($files);
     }
     for ($i = 0; $i < count($files); $i++) {
         $file_name = $files[$i];
         $converted_file_name = $file_name . '.xmlconverted';
         dbsteward::info("Upconverting XML definition file: " . $file_name);
         dbsteward::info("Upconvert XML output file: " . $converted_file_name);
         $doc = simplexml_load_file($file_name);
         xml_parser::sql_format_convert($doc);
         $converted_xml = xml_parser::format_xml($doc->asXML());
         $converted_xml = str_replace('pgdbxml>', 'dbsteward>', $converted_xml);
         file_put_contents($converted_file_name, $converted_xml);
     }
 }
예제 #3
0
 public static function extract_schema($host, $port, $database, $user, $password)
 {
     $databases = explode(',', $database);
     dbsteward::notice("Connecting to mysql5 host " . $host . ':' . $port . ' database ' . $database . ' as ' . $user);
     // if not supplied, ask for the password
     if ($password === FALSE) {
         echo "Password: "******"Analyzing database {$database}");
         $db->use_database($database);
         $node_schema = $doc->addChild('schema');
         $node_schema['name'] = $database;
         $node_schema['owner'] = 'ROLE_OWNER';
         // extract global and schema permissions under the public schema
         foreach ($db->get_global_grants($user) as $db_grant) {
             $node_grant = $node_schema->addChild('grant');
             // There are 28 permissions encompassed by the GRANT ALL statement
             $node_grant['operation'] = $db_grant->num_ops == 28 ? 'ALL' : $db_grant->operations;
             $node_grant['role'] = self::translate_role_name($user, $doc);
             if ($db_grant->is_grantable) {
                 $node_grant['with'] = 'GRANT';
             }
         }
         $enum_types = array();
         $enum_type = function ($obj, $mem, $values) use(&$enum_types) {
             // if that set of values is defined by a previous enum, use that
             foreach ($enum_types as $name => $enum) {
                 if ($enum === $values) {
                     return $name;
                 }
             }
             // otherwise, make a new one
             $name = "enum_" . md5(implode('_', $values));
             $enum_types[$name] = $values;
             return $name;
         };
         foreach ($db->get_tables() as $db_table) {
             dbsteward::info("Analyze table options/partitions " . $db_table->table_name);
             $node_table = $node_schema->addChild('table');
             $node_table['name'] = $db_table->table_name;
             $node_table['owner'] = 'ROLE_OWNER';
             // because mysql doesn't have object owners
             $node_table['description'] = $db_table->table_comment;
             $node_table['primaryKey'] = '';
             if (stripos($db_table->create_options, 'partitioned') !== FALSE && ($partition_info = $db->get_partition_info($db_table))) {
                 $node_partition = $node_table->addChild('tablePartition');
                 $node_partition['sqlFormat'] = 'mysql5';
                 $node_partition['type'] = $partition_info->type;
                 switch ($partition_info->type) {
                     case 'HASH':
                     case 'LINEAR HASH':
                         $opt = $node_partition->addChild('tablePartitionOption');
                         $opt->addAttribute('name', 'expression');
                         $opt->addAttribute('value', $partition_info->expression);
                         $opt = $node_partition->addChild('tablePartitionOption');
                         $opt->addAttribute('name', 'number');
                         $opt->addAttribute('value', $partition_info->number);
                         break;
                     case 'KEY':
                     case 'LINEAR KEY':
                         $opt = $node_partition->addChild('tablePartitionOption');
                         $opt->addAttribute('name', 'columns');
                         $opt->addAttribute('value', $partition_info->columns);
                         $opt = $node_partition->addChild('tablePartitionOption');
                         $opt->addAttribute('name', 'number');
                         $opt->addAttribute('value', $partition_info->number);
                         break;
                     case 'LIST':
                     case 'RANGE':
                     case 'RANGE COLUMNS':
                         $opt = $node_partition->addChild('tablePartitionOption');
                         $opt->addAttribute('name', $partition_info->type == 'RANGE COLUMNS' ? 'columns' : 'expression');
                         $opt->addAttribute('value', $partition_info->expression);
                         foreach ($partition_info->segments as $segment) {
                             $node_seg = $node_partition->addChild('tablePartitionSegment');
                             $node_seg->addAttribute('name', $segment->name);
                             $node_seg->addAttribute('value', $segment->value);
                         }
                         break;
                 }
             }
             foreach ($db->get_table_options($db_table) as $name => $value) {
                 if (strcasecmp($name, 'auto_increment') === 0 && !static::$use_auto_increment_table_options) {
                     // don't extract auto_increment tableOptions if we're not using them
                     continue;
                 }
                 $node_option = $node_table->addChild('tableOption');
                 $node_option['sqlFormat'] = 'mysql5';
                 $node_option['name'] = $name;
                 $node_option['value'] = $value;
             }
             dbsteward::info("Analyze table columns " . $db_table->table_name);
             foreach ($db->get_columns($db_table) as $db_column) {
                 $node_column = $node_table->addChild('column');
                 $node_column['name'] = $db_column->column_name;
                 if (!empty($db_column->column_comment)) {
                     $node_column['description'] = $db_column->column_comment;
                 }
                 // returns FALSE if not serial, int/bigint if it is
                 $type = $db->is_serial_column($db_table, $db_column);
                 if (!$type) {
                     $type = $db_column->column_type;
                     if (stripos($type, 'enum') === 0) {
                         $values = $db->parse_enum_values($db_column->column_type);
                         $type = $enum_type($db_table->table_name, $db_column->column_name, $values);
                     }
                     if ($db_column->is_auto_increment) {
                         $type .= ' AUTO_INCREMENT';
                     }
                 }
                 if ($db_column->is_auto_update) {
                     $type .= ' ON UPDATE CURRENT_TIMESTAMP';
                 }
                 $node_column['type'] = $type;
                 // @TODO: if there are serial sequences/triggers for the column then convert to serial
                 if ($db_column->column_default !== NULL) {
                     $node_column['default'] = mysql5::escape_default_value($db_column->column_default);
                 } elseif (strcasecmp($db_column->is_nullable, 'YES') === 0) {
                     $node_column['default'] = 'NULL';
                 }
                 $node_column['null'] = strcasecmp($db_column->is_nullable, 'YES') === 0 ? 'true' : 'false';
             }
             // get all plain and unique indexes
             dbsteward::info("Analyze table indexes " . $db_table->table_name);
             foreach ($db->get_indices($db_table) as $db_index) {
                 // don't process primary key indexes here
                 if (strcasecmp($db_index->index_name, 'PRIMARY') === 0) {
                     continue;
                 }
                 // implement unique indexes on a single column as unique column, but only if the index name is the column name
                 if ($db_index->unique && count($db_index->columns) == 1 && strcasecmp($db_index->columns[0], $db_index->index_name) === 0) {
                     $column = $db_index->columns[0];
                     $node_column = dbx::get_table_column($node_table, $column);
                     if (!$node_column) {
                         throw new Exception("Unexpected: Could not find column node {$column} for unique index {$db_index->index_name}");
                     } else {
                         $node_column = $node_column[0];
                     }
                     $node_column['unique'] = 'true';
                 } else {
                     $node_index = $node_table->addChild('index');
                     $node_index['name'] = $db_index->index_name;
                     $node_index['using'] = strtolower($db_index->index_type);
                     $node_index['unique'] = $db_index->unique ? 'true' : 'false';
                     $i = 1;
                     foreach ($db_index->columns as $column_name) {
                         $node_index->addChild('indexDimension', $column_name)->addAttribute('name', $column_name . '_' . $i++);
                     }
                 }
             }
             // get all primary/foreign keys
             dbsteward::info("Analyze table constraints " . $db_table->table_name);
             foreach ($db->get_constraints($db_table) as $db_constraint) {
                 if (strcasecmp($db_constraint->constraint_type, 'primary key') === 0) {
                     $node_table['primaryKey'] = implode(',', $db_constraint->columns);
                 } elseif (strcasecmp($db_constraint->constraint_type, 'foreign key') === 0) {
                     // mysql sees foreign keys as indexes pointing at indexes.
                     // it's therefore possible for a compound index to point at a compound index
                     if (!$db_constraint->referenced_columns || !$db_constraint->referenced_table_name) {
                         throw new Exception("Unexpected: Foreign key constraint {$db_constraint->constraint_name} does not refer to any foreign columns");
                     }
                     if (count($db_constraint->referenced_columns) == 1 && count($db_constraint->columns) == 1) {
                         // not a compound index, define the FK inline in the column
                         $column = $db_constraint->columns[0];
                         $ref_column = $db_constraint->referenced_columns[0];
                         $node_column = dbx::get_table_column($node_table, $column);
                         if (!$node_column) {
                             throw new Exception("Unexpected: Could not find column node {$column} for foreign key constraint {$db_constraint->constraint_name}");
                         }
                         $node_column['foreignSchema'] = $db_constraint->referenced_table_schema;
                         $node_column['foreignTable'] = $db_constraint->referenced_table_name;
                         $node_column['foreignColumn'] = $ref_column;
                         unset($node_column['type']);
                         // inferred from referenced column
                         $node_column['foreignKeyName'] = $db_constraint->constraint_name;
                         // RESTRICT is the default, leave it implicit if possible
                         if (strcasecmp($db_constraint->delete_rule, 'restrict') !== 0) {
                             $node_column['foreignOnDelete'] = str_replace(' ', '_', $db_constraint->delete_rule);
                         }
                         if (strcasecmp($db_constraint->update_rule, 'restrict') !== 0) {
                             $node_column['foreignOnUpdate'] = str_replace(' ', '_', $db_constraint->update_rule);
                         }
                     } elseif (count($db_constraint->referenced_columns) > 1 && count($db_constraint->referenced_columns) == count($db_constraint->columns)) {
                         $node_fkey = $node_table->addChild('foreignKey');
                         $node_fkey['columns'] = implode(', ', $db_constraint->columns);
                         $node_fkey['foreignSchema'] = $db_constraint->referenced_table_schema;
                         $node_fkey['foreignTable'] = $db_constraint->referenced_table_name;
                         $node_fkey['foreignColumns'] = implode(', ', $db_constraint->referenced_columns);
                         $node_fkey['constraintName'] = $db_constraint->constraint_name;
                         // RESTRICT is the default, leave it implicit if possible
                         if (strcasecmp($db_constraint->delete_rule, 'restrict') !== 0) {
                             $node_fkey['onDelete'] = str_replace(' ', '_', $db_constraint->delete_rule);
                         }
                         if (strcasecmp($db_constraint->update_rule, 'restrict') !== 0) {
                             $node_fkey['onUpdate'] = str_replace(' ', '_', $db_constraint->update_rule);
                         }
                     } else {
                         var_dump($db_constraint);
                         throw new Exception("Unexpected: Foreign key constraint {$db_constraint->constraint_name} has mismatched columns");
                     }
                 } elseif (strcasecmp($db_constraint->constraint_type, 'unique') === 0) {
                     dbsteward::warning("Ignoring UNIQUE constraint '{$db_constraint->constraint_name}' because they are implemented as indices");
                 } elseif (strcasecmp($db_constraint->constraint_type, 'check') === 0) {
                     // @TODO: implement CHECK constraints
                 } else {
                     throw new exception("unknown constraint_type {$db_constraint->constraint_type}");
                 }
             }
             foreach ($db->get_table_grants($db_table, $user) as $db_grant) {
                 dbsteward::info("Analyze table permissions " . $db_table->table_name);
                 $node_grant = $node_table->addChild('grant');
                 $node_grant['operation'] = $db_grant->operations;
                 $node_grant['role'] = self::translate_role_name($user, $doc);
                 if ($db_grant->is_grantable) {
                     $node_grant['with'] = 'GRANT';
                 }
             }
         }
         foreach ($db->get_sequences() as $db_seq) {
             $node_seq = $node_schema->addChild('sequence');
             $node_seq['name'] = $db_seq->name;
             $node_seq['owner'] = 'ROLE_OWNER';
             $node_seq['start'] = $db_seq->start_value;
             $node_seq['min'] = $db_seq->min_value;
             $node_seq['max'] = $db_seq->max_value;
             $node_seq['inc'] = $db_seq->increment;
             $node_seq['cycle'] = $db_seq->cycle ? 'true' : 'false';
             // the sequences table is a special case, since it's not picked up in the tables loop
             $seq_table = $db->get_table(mysql5_sequence::TABLE_NAME);
             foreach ($db->get_table_grants($seq_table, $user) as $db_grant) {
                 $node_grant = $node_seq->addChild('grant');
                 $node_grant['operation'] = $db_grant->operations;
                 $node_grant['role'] = self::translate_role_name($doc, $user);
                 if ($db_grant->is_grantable) {
                     $node_grant['with'] = 'GRANT';
                 }
             }
         }
         foreach ($db->get_functions() as $db_function) {
             dbsteward::info("Analyze function " . $db_function->routine_name);
             $node_fn = $node_schema->addChild('function');
             $node_fn['name'] = $db_function->routine_name;
             $node_fn['owner'] = 'ROLE_OWNER';
             $node_fn['returns'] = $type = $db_function->dtd_identifier;
             if (strcasecmp($type, 'enum') === 0) {
                 $node_fn['returns'] = $enum_type($db_function->routine_name, 'returns', $db->parse_enum_values($db_function->dtd_identifier));
             }
             $node_fn['description'] = $db_function->routine_comment;
             if (isset($db_function->procedure) && $db_function->procedure) {
                 $node_fn['procedure'] = 'true';
             }
             // $node_fn['procedure'] = 'false';
             $eval_type = $db_function->sql_data_access;
             // srsly mysql? is_deterministic varchar(3) not null default '', contains YES or NO
             $determinism = strcasecmp($db_function->is_deterministic, 'YES') === 0 ? 'DETERMINISTIC' : 'NOT DETERMINISTIC';
             $node_fn['cachePolicy'] = mysql5_function::get_cache_policy_from_characteristics($determinism, $eval_type);
             $node_fn['mysqlEvalType'] = str_replace(' ', '_', $eval_type);
             // INVOKER is the default, leave it implicit when possible
             if (strcasecmp($db_function->security_type, 'definer') === 0) {
                 $node_fn['securityDefiner'] = 'true';
             }
             foreach ($db_function->parameters as $param) {
                 $node_param = $node_fn->addChild('functionParameter');
                 // not supported in mysql functions, even though it's provided?
                 // $node_param['direction'] = strtoupper($param->parameter_mode);
                 $node_param['name'] = $param->parameter_name;
                 $node_param['type'] = $type = $param->dtd_identifier;
                 if (strcasecmp($type, 'enum') === 0) {
                     $node_param['type'] = $enum_type($db_function->routine_name, $param->parameter_name, $db->parse_enum_values($param->dtd_identifier));
                 }
                 if (isset($param->direction)) {
                     $node_param['direction'] = $param->direction;
                 }
             }
             $node_def = $node_fn->addChild('functionDefinition', $db_function->routine_definition);
             $node_def['language'] = 'sql';
             $node_def['sqlFormat'] = 'mysql5';
         }
         foreach ($db->get_triggers() as $db_trigger) {
             dbsteward::info("Analyze trigger " . $db_trigger->name);
             $node_trigger = $node_schema->addChild('trigger');
             foreach ((array) $db_trigger as $k => $v) {
                 $node_trigger->addAttribute($k, $v);
             }
             $node_trigger->addAttribute('sqlFormat', 'mysql5');
         }
         foreach ($db->get_views() as $db_view) {
             dbsteward::info("Analyze view " . $db_view->view_name);
             if (!empty($db_view->view_name) && empty($db_view->view_query)) {
                 throw new Exception("Found a view in the database with an empty query. User '{$user}' problaby doesn't have SELECT permissions on tables referenced by the view.");
             }
             $node_view = $node_schema->addChild('view');
             $node_view['name'] = $db_view->view_name;
             $node_view['owner'] = 'ROLE_OWNER';
             $node_view->addChild('viewQuery', $db_view->view_query)->addAttribute('sqlFormat', 'mysql5');
         }
         foreach ($enum_types as $name => $values) {
             $node_type = $node_schema->addChild('type');
             $node_type['type'] = 'enum';
             $node_type['name'] = $name;
             foreach ($values as $v) {
                 $node_type->addChild('enum')->addAttribute('name', $v);
             }
         }
     }
     xml_parser::validate_xml($doc->asXML());
     return xml_parser::format_xml($doc->saveXML());
 }
예제 #4
0
 /**
  * compare composite db doc to specified database
  *
  * @return string XML
  */
 public static function compare_db_data($db_doc, $host, $port, $database, $user, $password)
 {
     dbsteward::notice("Connecting to pgsql8 host " . $host . ':' . $port . ' database ' . $database . ' as ' . $user);
     // if not supplied, ask for the password
     if ($password === FALSE) {
         // @TODO: mask the password somehow without requiring a PHP extension
         echo "Password: "******"host={$host} port={$port} dbname={$database} user={$user} password={$password}");
     dbsteward::info("Comparing composited dbsteward definition data rows to postgresql database connection table contents");
     // compare the composited dbsteward document to the established database connection
     // effectively looking to see if rows are found that match primary keys, and if their contents are the same
     foreach ($db_doc->schema as $schema) {
         foreach ($schema->table as $table) {
             if (isset($table->rows)) {
                 $table_name = dbsteward::string_cast($schema['name']) . '.' . dbsteward::string_cast($table['name']);
                 $primary_key_cols = self::primary_key_split($table['primaryKey']);
                 $cols = preg_split("/[\\,\\s]+/", $table->rows['columns'], -1, PREG_SPLIT_NO_EMPTY);
                 $col_types = array();
                 foreach ($table->column as $table_column) {
                     $type = '';
                     // foreign keyed columns inherit their foreign reference type
                     if (isset($table_column['foreignTable']) && isset($table_column['foreignColumn'])) {
                         if (strlen($type) > 0) {
                             throw new exception("type of " . $type . " was found for " . dbsteward::string_cast($cols[$j]) . " in table " . dbsteward::string_cast($table['name']) . " but it is foreign keyed!");
                         }
                         $foreign = array();
                         dbx::foreign_key($db_doc, $schema, $table, $table_column, $foreign);
                         // don't need to error-check, foreign_key() is self-checking if it doesnt find the fkey col it will complain
                         $type = $foreign['column']['type'];
                     } else {
                         $type = dbsteward::string_cast($table_column['type']);
                     }
                     if (strlen($type) == 0) {
                         throw new exception($table_name . " column " . $table_column['name'] . " type not found!");
                     }
                     $col_types[dbsteward::string_cast($table_column['name'])] = $type;
                 }
                 foreach ($table->rows->row as $row) {
                     // glue the primary key expression together for the where
                     $primary_key_expression = '';
                     for ($k = 0; $k < count($primary_key_cols); $k++) {
                         $column_name = pgsql8::get_quoted_column_name($primary_key_cols[$k]);
                         $pk_index = array_search($primary_key_cols[$k], $cols);
                         if ($pk_index === FALSE) {
                             throw new exception("failed to find " . $schema['name'] . "." . $table['name'] . " primary key column " . $primary_key_cols[$k] . " in cols list (" . implode(", ", $cols) . ")");
                         }
                         $primary_key_expression .= $column_name . " = " . pgsql8::value_escape($col_types[$primary_key_cols[$k]], $row->col[$pk_index], $db_doc);
                         if ($k < count($primary_key_cols) - 1) {
                             $primary_key_expression .= ' AND ';
                         }
                     }
                     $sql = "SELECT *\n              FROM " . $table_name . "\n              WHERE " . $primary_key_expression;
                     $rs = pgsql8_db::query($sql);
                     // is the row supposed to be deleted?
                     if (strcasecmp('true', $row['delete']) == 0) {
                         if (pg_num_rows($rs) > 0) {
                             dbsteward::notice($table_name . " row marked for DELETE found WHERE " . $primary_key_expression);
                         }
                     } else {
                         if (pg_num_rows($rs) == 0) {
                             dbsteward::notice($table_name . " does not contain row WHERE " . $primary_key_expression);
                         } else {
                             if (pg_num_rows($rs) > 1) {
                                 dbsteward::notice($table_name . " contains more than one row WHERE " . $primary_key_expression);
                                 while (($db_row = pg_fetch($rs)) !== FALSE) {
                                     dbsteward::notice("\t" . implode(', ', $db_row));
                                 }
                             } else {
                                 $db_row = pg_fetch_assoc($rs);
                                 // make sure any aspects of the $row are present in the $db_row
                                 for ($i = 0; $i < count($cols); $i++) {
                                     $xml_value = self::pgdata_homogenize($col_types[$cols[$i]], dbsteward::string_cast($row->col[$i]));
                                     $db_value = self::pgdata_homogenize($col_types[$cols[$i]], dbsteward::string_cast($db_row[$cols[$i]]));
                                     $values_match = FALSE;
                                     // evaluate if they are equal
                                     $values_match = $xml_value == $db_value;
                                     // if they are not PHP equal, and are alternate expressionable, ask the database
                                     if (!$values_match && preg_match('/^time.*|^date.*|^interval/i', $col_types[$cols[$i]]) > 0) {
                                         // do both describe atleast some value (greater than zero len?)
                                         if (strlen($xml_value) > 0 && strlen($db_value) > 0) {
                                             $sql = "SELECT '{$xml_value}'::" . $col_types[$cols[$i]] . " = '{$db_value}'::" . $col_types[$cols[$i]] . " AS equal_eval";
                                             $values_match = pgsql8_db::query_str($sql) == 't';
                                         }
                                     }
                                     if (!$values_match) {
                                         dbsteward::warning($table_name . " row column WHERE (" . $primary_key_expression . ") " . $cols[$i] . " data does not match database row column: '" . $xml_value . "' VS '" . $db_value . "'");
                                     }
                                 }
                             }
                         }
                     }
                 }
             }
         }
     }
     //xml_parser::validate_xml($db_doc->asXML());
     return xml_parser::format_xml($db_doc->saveXML());
 }
예제 #5
0
 public function testRequireSlonyId()
 {
     // make sure if require_slony_id is TRUE then there are no changes
     dbsteward::$require_slony_id = TRUE;
     $this->do_slony_numbering();
     $in_doc_xml = xml_parser::format_xml($this->in_doc->asXML());
     $slonyid_doc_xml = xml_parser::format_xml($this->slonyid_doc->asXML());
     $this->assertNotEquals($in_doc_xml, $slonyid_doc_xml);
 }