/** * Parses CREATE TRIGGER command. * * @param database database * @param command CREATE TRIGGER command * * @throws ParserException Thrown if problem occured while parsing the * command. */ public static function parse($database, $command) { if (preg_match(self::PATTERN, trim($command), $matches) > 0) { $trigger_name = trim($matches[1]); $when = $matches[2]; $events = array(); if (strlen($matches[3]) > 0) { $events[] = $matches[3]; } if (strlen($matches[4]) > 0) { $events[] = $matches[4]; } if (strlen($matches[5]) > 0) { $events[] = $matches[5]; } $table_name = trim($matches[6]); $fireOn = $matches[7]; $procedure = $matches[8]; $node_schema =& dbx::get_schema($database, sql_parser::get_schema_name($table_name, $database)); $node_table =& dbx::get_table($node_schema, sql_parser::get_object_name($table_name)); if ($node_table == null) { throw new exception("Failed to find trigger table " . $trigger->get_table_name()); } $node_trigger =& dbx::get_table_trigger($node_schema, $node_table, $trigger_name, true); dbx::set_attribute($node_trigger, 'when', strcasecmp('BEFORE', $when) == 0 ? 'BEFORE' : 'AFTER'); dbx::set_attribute($node_trigger, 'forEach', strcasecmp('ROW', $when) == 0 ? 'ROW' : 'STATEMENT'); dbx::set_attribute($node_trigger, 'function', trim($procedure)); dbx::set_attribute($node_trigger, 'event', implode(', ', $events)); } else { throw new exception("Cannot parse command: " . $command); } }
/** * Parses CREATE TYPE command. * * @param database database * @param command CREATE TYPE command */ public static function parse($database, $command) { $line = $command; // CREATE PROCEDURAL LANGUAGE plpgsql; // CREATE [ PROCEDURAL ] LANGUAGE name // CREATE [ TRUSTED ] [ PROCEDURAL ] LANGUAGE name // HANDLER call_handler [ VALIDATOR valfunction ] if (preg_match(self::PATTERN_CREATE_LANGUAGE, $line, $matches) > 0) { // simplify parsing by killing last semicolon $line = sql_parser::remove_last_semicolon($line); // break up the command by whitespace $chunks = preg_split('/[\\s]+/', $line, -1, PREG_SPLIT_NO_EMPTY); // shift the LANGUAGE keyword off array_shift($chunks); // shift the language name off $language_name = array_shift($chunks); // create language entry $language =& dbx::get_language($database, $language_name, true); // grab the language modifiers while (strcasecmp('LANGUAGE', $chunks[0]) != 0) { if (strcasecmp('CREATE', $chunks[0]) == 0) { // expected first CREATE lead doesn't modify anything } else { if (strcasecmp('TRUSTED', $chunks[0]) == 0) { dbx::set_attribute($language, 'trusted', $chunks[0]); } else { if (strcasecmp('PROCEDURAL', $chunks[0]) == 0) { dbx::set_attribute($language, 'procedural', $chunks[0]); } else { throw new exception("unknown CREATE LANGUAGE modifier: " . $chunks[0]); } } } // shift the lead chunk off now that it has been interpreted array_shift($chunks); } // if there are chunks left, figure out what optional parameteres they are and save them in the language object // make sure it's not the trailing ;, we don't care while (count($chunks) > 0 && trim(implode(' ', $chunks)) != ';') { if (strcasecmp('HANDLER', $chunks[0]) == 0) { dbx::set_attribute($language, 'handler', $chunks[1]); } else { if (strcasecmp('VALIDATOR', $chunks[0]) == 0) { dbx::set_attribute($language, 'validator', $chunks[1]); } else { throw new exception("unknown CREATE LANGUAGE callback: " . $chunks[0]); } } // shift the lead chunk and its value off now that it has been interpreted array_shift($chunks); array_shift($chunks); } } else { throw new exception("Cannot parse command: " . $line); } }
/** * Parses GRANT and REVOKE commands * * @param database database * @param command REVOKE command */ public static function parse($database, $command) { $command = sql_parser::remove_last_semicolon($command); if (preg_match(self::PATTERN_CONFIG_PARAMETER, $command, $matches) > 0) { if (count($matches) != 3) { var_dump($matches); throw new exception("Database configuration parameter call preg exploded into " . count($matches) . ", panic!"); } // just do what the call does push around the name -> value $configuration_parameter =& dbx::get_configuration_parameter($database, $matches[1], true); dbx::set_attribute($configuration_parameter, 'value', $matches[2]); } else { throw new exception("Cannot parse command: " . $command); } }
/** * Parses ALTER SCHEMA command. * * @param database database * @param command ALTER SCHEMA command * */ public static function parse($database, $command) { if (preg_match(self::PATTERN_OWNER, $command, $matches) > 0) { $line = $command; $schema_name = trim($matches[1]); $owner_name = trim($matches[2]); $schema =& dbx::get_schema($database, $schema_name); if ($schema == null) { throw new exception("failed to find schema " . $schema_name . " for alter owner statement"); } dbx::set_attribute($schema, 'owner', $owner_name); } else { throw new exception("Cannot parse command: " . $command); } }
/** * Parses ALTER LANGUAGE command. * * @param database database * @param command ALTER LANGUAGE command * */ public static function parse($database, $command) { if (preg_match(self::PATTERN, $command, $matches) > 0) { $line = $command; $language_name = trim($matches[1]); $owner_name = trim($matches[2]); $language =& dbx::get_language($database, $language_name); if ($language == null) { throw new exception("Language " . $language_name . " not found. Is the create for it missing or after the ALTER LANGUAGE statement ?"); } dbx::set_attribute($language, 'owner', $owner_name); } else { throw new exception("Cannot parse command: " . $command); } }
/** * Parses CREATE SCHEMA command. * * @param database database * @param command CREATE SCHEMA command */ public static function parse($database, $command) { if (preg_match(self::PATTERN_CREATE_SCHEMA, $command, $matches) > 0) { $node_schema = dbx::get_schema($database, $matches[1], true); if (isset($matches[2])) { dbx::set_attribute($node_schema, 'authorization', $matches[2]); } } else { if (preg_match(self::PATTERN_CREATE_SCHEMA_AUTHORIZATION, $command, $matches) > 0) { $node_schema = dbx::get_schema($database, $matches[1], true); dbx::set_attribute($node_schema, 'authorization', $node_schema['name']); } else { throw new exception("Cannot parse command: " . $command); } } }
public static function set_permission($node_object, $action, $operations, $role) { if (!is_object($node_object)) { throw new exception("node_object is not an object, something is wrong, trace calling stack"); } $action = strtolower($action); // see @TODO below, this xpath may need generalized if it is implemented $nodes = $node_object->xpath($action . "[@role='" . $role . "']"); if (count($nodes) == 0) { // no $action for $object not found $node_permission = $node_object->addChild($action); dbx::set_attribute($node_permission, 'role', $role); dbx::set_attribute($node_permission, 'operation', implode(', ', $operations)); } //@TODO: compare to existing $node_object grants with has_permission()? //@TODO: maybe return NOP if node_object already has role operation right defined? }
/** * Parses CREATE INDEX command. * * @param database database * @param command CREATE INDEX command */ public static function parse($database, $command) { if (preg_match(self::CREATE_PATTERN, trim($command), $matches) > 0) { $unique_value = strlen(trim($matches[1])) > 0 ? 'true' : 'false'; $index_name = $matches[2]; $table_name = $matches[3]; $using = trim($matches[4]); if ($index_name == null || $table_name == null || $using == null) { throw new exception("Cannot parse command: " . $command); } $node_schema =& dbx::get_schema($database, sql_parser::get_schema_name(trim($table_name), $database)); $node_table =& dbx::get_table($node_schema, sql_parser::get_object_name(trim($table_name))); if ($node_table == null) { throw new exception("Failed to find table: " . $table_name); } $node_index =& dbx::create_table_index($node_table, $index_name); dbx::set_attribute($node_index, 'using', $using); dbx::set_attribute($node_index, 'unique', $unique_value); } else { throw new exception("Cannot parse command: " . $command); } }
/** * Save declaration information to the node_function * * @param functionName name of the function * @param arguments string containing arguments part of function * declaration */ public static function set_declaration(&$node_schema, &$node_function, $arguments) { if (strlen(trim($arguments)) == 0) { // no arguments to set for the function } else { $arguement_parts = explode(',', trim($arguments)); $args = array(); foreach ($arguement_parts as $part) { $chunks = preg_split("/[\\s]+/", $part, -1, PREG_SPLIT_NO_EMPTY); // per http://www.postgresql.org/docs/8.4/static/sql-createfunction.html // IN is default $direction = 'IN'; $name = ''; // if the first chunk is in or out, push it off as the direction if (strcasecmp($chunks[0], 'IN') == 0 || strcasecmp($chunks[0], 'OUT') == 0) { $direction = strtoupper(array_shift($chunks)); } // only 1 remaining chunk, and no match to known types, cry about it if (count($chunks) < 2 && preg_match(dbsteward::PATTERN_KNOWN_TYPES, implode(' ', $chunks)) == 0) { throw new exception("unknown type encountered: " . implode(' ', $chunks)); } // if the remainder is not a known type, push it off as the parameter name if (preg_match(dbsteward::PATTERN_KNOWN_TYPES, implode(' ', $chunks)) == 0) { $name = array_shift($chunks); } // whatever is left is the type of the parameter $type = implode(' ', $chunks); // is reformed type in the known type list? if (preg_match(dbsteward::PATTERN_KNOWN_TYPES, $type) == 0) { throw new exception("unknown type inferred: " . implode(' ', $chunks)); } $function_parameter =& dbx::get_function_parameter($node_function, $name, true); dbx::set_attribute($function_parameter, 'direction', $direction); dbx::set_attribute($function_parameter, 'type', $type); } } return self::get_declaration($node_schema, $node_function); }
/** * Parses ALTER FUNCTION command. * * @param database database * @param command ALTER FUNCTION command * */ public static function parse($database, $command) { if (preg_match(self::PATTERN_OWNER, $command, $matches) > 0) { $line = $command; $function_name = trim($matches[1]); // make all functionName's fully qualified // default_schema will make set search path induced schemas come through correctly $function_name = sql_parser::get_schema_name($function_name, $database) . '.' . sql_parser::get_object_name($function_name); $arguments = $matches[2]; $owner_name = trim($matches[3]); $node_schema = dbx::get_schema($database, sql_parser::get_schema_name($function_name, $database)); if ($node_schema == null) { throw new exception("Failed to find function schema for " . $function_name); } $node_function = dbx::get_function($node_schema, sql_parser::get_object_name($function_name)); if ($node_function == null) { throw new exception("Failed to find function " . $function_name . " in schema " . $node_schema['name']); } dbx::set_attribute($node_function, 'owner', $owner_name); } else { throw new exception("Cannot parse command: " . $command); } }
/** * Parses definitions that are present after column definition is * closed with ')'. * * @param type type being parsed * @param commands commands being processed * * @return true if the command was the last command for CREATE TYPE, * otherwise false */ private static function parse_post_columns(&$type, $commands) { $line = $commands; if (preg_match(self::PATTERN_WITH_OIDS, $line, $matches) > 0) { dbx::set_attribute($node_table, 'withOIDS', 'true'); $line = sql_parser::removeSubString($line, "WITH OIDS"); } else { if (preg_match(self::PATTERN_WITHOUT_OIDS, $line, $matches) > 0) { dbx::set_attribute($node_table, 'withOIDS', 'false'); $line = sql_parser::removeSubString($line, "WITHOUT OIDS"); } } return $line; }
/** * Parses all rows in CREATE TABLE command. * * @param $node_schema schema table belongs to * @param $node_table table being parsed * @param $command command without 'CREATE TABLE ... (' string */ private static function parse_rows(&$node_schema, &$node_table, $command) { $line = $command; $post_columns = false; while (strlen($line) > 0) { $command_end = sql_parser::get_command_end($line, 0); $subCommand = trim(substr($line, 0, $command_end)); if ($post_columns) { $line = self::parse_post_columns($node_table, $subCommand); break; } else { if (substr($line, $command_end, 1) == ')') { $post_columns = true; } } // look for modifier tokens and act accordingly $tokens = preg_split("/[\\s]+/", $subCommand, -1, PREG_SPLIT_NO_EMPTY); // start at 2, first is always name, second is always type for ($i = 2; $i < count($tokens); $i++) { if (strcasecmp($tokens[$i], 'UNIQUE') == 0) { // CREATE TABLE test_table ( // test_table_id varchar(64) PRIMARY KEY, // test_table_col_c varchar(100) UNIQUE NOT NULL // ); // NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_pkey" for table "test_table" // NOTICE: CREATE TABLE / UNIQUE will create implicit index "test_table_test_table_col_c_key" for table "test_table" dbsteward::debug("NOTICE: CREATE TABLE with UNIQUE column attribute -- creating implicit index \"" . pgsql8_index::index_name(sql_parser::get_object_name($node_table->get_name()), sql_parser::get_object_name($tokens[0]), 'key') . "\" for table \"" . $node_schema->get_name() . '.' . $node_table->get_name() . "\""); $node_index =& dbx::create_table_index($node_table, pgsql8_index::index_name(sql_parser::get_object_name($node_table['name']), sql_parser::get_object_name($tokens[0]), 'key')); dbx::set_attribute($node_index, 'unique', 'true'); dbx::set_attribute($node_index, 'using', 'btree'); $node_index->addChild('indexDimension', sql_parser::get_object_name($tokens[0]))->addAttribute('name', $tokens[0] . '_unq'); // make sure we don't process this token again unset($tokens[$i]); $tokens = array_merge($tokens); $i--; continue; } // @TODO: other cases? // other cases is how you would fix pgsql8_column::parse_definition() throwing 'column definition parse fail' exceptions } $subCommand = implode(' ', $tokens); self::parse_column_defs($node_schema, $node_table, $subCommand); $line = $command_end >= strlen($line) ? "" : substr($line, $command_end + 1); } $line = trim($line); if (strlen($line) > 0) { throw new exception("Cannot parse CREATE TABLE '" . $node_table['name'] . "' - do not know how to parse '" . $line . "'"); } }
/** * Parses all rows in ALTER TABLE command. * * @param table table being parsed * @param commands commands * * @throws ParserException Thrown if problem occured while parsing DDL. */ private static function parse_rows(&$db_doc, &$node_schema, &$node_table, $commands) { $line = $commands; $subCommand = null; while (strlen($line) > 0) { $commandEnd = sql_parser::get_command_end($line, 0); $subCommand = trim(substr($line, 0, $commandEnd)); $line = $commandEnd >= strlen($line) ? "" : substr($line, $commandEnd + 1); if (strlen($subCommand) > 0) { if (preg_match(self::PATTERN_ADD_CONSTRAINT_FOREIGN_KEY, $subCommand, $matches) > 0) { $column_name = trim($matches[3]); $constraint_name = trim($matches[1]); $node_constraint = pgsql8_constraint::get_table_constraint($db_doc, $node_table, $constraint_name, true); dbx::set_attribute($node_constraint, 'definition', trim($matches[2])); $subCommand = ""; } } if (preg_match(self::PATTERN_ADD_CONSTRAINT, $subCommand, $matches) > 0) { $constraint_name = trim($matches[1]); $node_constraint = pgsql8_constraint::get_table_constraint($db_doc, $node_table, $constraint_name, true); dbx::set_attribute($node_constraint, 'definition', trim($matches[2])); $subCommand = ""; } if (strlen($subCommand) > 0) { if (preg_match(self::PATTERN_ADD_PRIMARY_KEY, $subCommand, $matches) > 0) { $definition = trim($matches[1]); $column_name = trim($matches[2]); $constraint_name = $node_table['name'] . '_pkey'; dbx::set_attribute($node_table, 'primaryKey', $column_name); $subCommand = ""; } } if (strlen($subCommand) > 0) { if (preg_match(self::PATTERN_ADD_FOREIGN_KEY, $subCommand, $matches) > 0) { $column_name = trim($matches[2]); $constraint_name = pgsql8::identifier_name($node_schema['name'], $node_table['name'], $column_name, '_fkey'); $node_constraint = pgsql8_constraint::get_table_constraint($db_doc, $node_table, $constraint_name, true); dbx::set_attribute($node_constraint, 'definition', trim($matches[1])); $subCommand = ""; } } if (strlen($subCommand) > 0) { if (preg_match(self::PATTERN_SET_DEFAULT, $subCommand, $matches) > 0) { $column_name = trim($matches[1]); $default_value = trim($matches[2]); if ($node_table->contains_column($column_name)) { $node_column =& dbx::get_column($node_table, $column_name); dbx::set_attribute($node_column, 'default', $default_value); } else { throw new exception("Cannot find column '" . $column_name . " 'in table '" . $node_table['name'] . "'"); } $subCommand = ""; } } if (preg_match(self::PATTERN_ALTER_COLUMN_STATISTICS, $subCommand, $matches) > 0) { $column_name = trim($matches[2]); $value = trim($matches[3]); $node_column =& dbx::get_column($node_table, $column_name); dbx::set_attribute($node_column, 'statistics', $value); $subCommand = ""; } if (preg_match(self::PATTERN_CLUSTER_ON, $subCommand, $matches) > 0) { $indexName = trim($matches[1]); dbx::set_attribute($node_column, 'clusterIndexName', $indexName); $subCommand = ""; } if (strlen($subCommand) > 0) { if (preg_match(self::PATTERN_TRIGGER, $subCommand, $matches) > 0) { $triggerName = trim($matches[2]); throw new exception("@TODO: do something with ALTER TABLE ... ENABLE / DISABLE trigger statements"); $subCommand = ""; } } if (strlen($subCommand) > 0) { throw new exception("Don't know how to parse: " . $subCommand); } } }
/** * Parses CREATE FUNCTION and CREATE OR REPLACE FUNCTION command. * * @param database database * @param command CREATE FUNCTION command */ public static function parse($database, $command) { if (preg_match(self::PATTERN, trim($command), $matches) > 0) { $function_name = trim($matches[1]); // make all functionName's fully qualified // default_schema will make set search path induced schemas come through correctly $function_name = sql_parser::get_schema_name($function_name, $database) . '.' . sql_parser::get_object_name($function_name); $arguments = $matches[2]; $node_schema = dbx::get_schema($database, sql_parser::get_schema_name($function_name, $database)); if ($node_schema == null) { throw new exception("Failed to find function schema for " . $function_name); } $node_function = dbx::get_function($node_schema, sql_parser::get_object_name($function_name), null, true); //@TODO: this may be a problem when there is more than one prototype for a function $function_declaration = pgsql8_function::set_declaration($node_schema, $node_function, $arguments); // check remaining definition for function modifiers by chopping of function declaration $function_close_position = stripos($command, ')'); $function_modifiers = str_replace("\n", ' ', substr($command, $function_close_position + 1)); // kill extra whitespace by regex match $function_modifiers = preg_replace("/\\s+/", " ", $function_modifiers); // kill trailing semicolon $function_modifiers = trim($function_modifiers); if (substr($function_modifiers, -1) == ';') { $function_modifiers = trim(substr($function_modifiers, 0, -1)); } $function_modifiers = ' ' . $function_modifiers . ' '; // AS token (definition) token // AS $_$ BEGIN DO STUFF END $_$ if (($as_pos = stripos($function_modifiers, ' AS ')) !== false) { $end_as_token_pos = strpos($function_modifiers, ' ', $as_pos + 4); $as_token = substr($function_modifiers, $as_pos + 4, $end_as_token_pos - ($as_pos + 4)); $definition_start = strpos($function_modifiers, $as_token, $as_pos) + strlen($as_token); $definition_end = strpos($function_modifiers, $as_token, $definition_start + strlen($as_token)); $definition = substr($function_modifiers, $definition_start, $definition_end - $definition_start); $definition = trim($definition); pgsql8_function::set_definition($node_function, $definition); // cut out what we just found $function_modifiers = substr($function_modifiers, 0, $as_pos) . ' ' . substr($function_modifiers, $definition_end + strlen($as_token)); } // now that the AS <token> (definition) <token> section is gone, parsing is simpler: // RETURNS (type) if (preg_match(self::PATTERN_RETURNS, $function_modifiers, $matches) > 0) { dbx::set_attribute($node_function, 'returns', trim($matches[1])); } // LANGUAGE (languagename) if (preg_match(self::PATTERN_LANGUAGE, $function_modifiers, $matches) > 0) { dbx::set_attribute($node_function, 'language', trim($matches[1])); } // check for IMMUTABLE | STABLE | VOLATILE modifiers if (preg_match('/.*\\s+IMMUTABLE\\s+.*/i', $function_modifiers, $matches) > 0) { dbx::set_attribute($node_function, 'cachePolicy', 'IMMUTABLE'); } if (preg_match('/.*\\s+STABLE\\s+.*/i', $function_modifiers, $matches) > 0) { dbx::set_attribute($node_function, 'cachePolicy', 'STABLE'); } if (preg_match('/.*\\s+VOLATILE\\s+.*/i', $function_modifiers, $matches) > 0) { dbx::set_attribute($node_function, 'cachePolicy', 'VOLATILE'); } // check for SECURITY DEFINER modifier if (preg_match('/.*\\s+SECURITY DEFINER\\s+.*/i', $function_modifiers, $matches) > 0) { dbx::set_attribute($node_function, 'securityDefiner', 'true'); } } else { throw new exception("Cannot parse command: " . $command); } }
/** * Parses definition of the column * * @param definition definition of the column */ public static function parse_definition(&$node_schema, &$node_table, &$node_column, $definition) { $type = $definition; if (preg_match(self::PATTERN_NOT_NULL, $type, $matches) > 0) { $type = trim($matches[1]); dbx::set_attribute($node_column, 'null', 'false'); } else { if (preg_match(self::PATTERN_NULL, $type, $matches) > 0 && preg_match(self::PATTERN_DEFAULT_NULL, $type) == 0) { // PATTERN_NULL match only if it is not a trailing DEFAULT NULL // as that is not a null designation just a default designation $type = trim($matches[1]); dbx::set_attribute($node_column, 'null', 'true'); } } if (preg_match(self::PATTERN_DEFAULT, $type, $matches) > 0) { $type = trim($matches[1]); dbx::set_attribute($node_column, 'default', trim($matches[2])); } // post-parsing sanity checks if (preg_match('/[\\s]+/', $type) > 0) { // type contains whitespace // split the type and look for bad tokens $bad_keywords = array('DEFAULT', 'UNIQUE'); $tokens = preg_split("/[\\s]+/", $type, -1, PREG_SPLIT_NO_EMPTY); foreach ($tokens as $token) { foreach ($bad_keywords as $bad_keyword) { if (strcasecmp($token, $bad_keyword) == 0) { var_dump($definition); throw new exception($node_column['name'] . " column definition parse fail: type '" . $type . "' still contains '" . $bad_keyword . "' keyword -- look at callers for mis-handling of definition parameter"); } } } } dbx::set_attribute($node_column, 'type', $type); // for serial and bigserials, create the accompanying sequence that powers the serial if (preg_match(pgsql8::PATTERN_TABLE_LINKED_TYPES, $type) > 0) { $sequence_name = pgsql8::identifier_name($node_schema['name'], $node_table['name'], $node_column['name'], '_seq'); $node_sequence =& dbx::get_sequence($node_schema, $sequence_name, true); dbx::set_attribute($node_sequence, 'owner', $node_table['owner']); dbx::set_attribute($node_sequence, 'start', '1'); dbx::set_attribute($node_sequence, 'min', '1'); dbx::set_attribute($node_sequence, 'inc', '1'); dbx::set_attribute($node_sequence, 'cycle', 'false'); } }
/** * Processes START WITH instruction. * * @param sequence sequence * @param command command * * @return command without START WITH instruction */ private static function processstart_with(&$node_sequence, $command) { $line = $command; if (preg_match(self::PATTERN_START_WITH, $line, $matches) > 0) { dbx::set_attribute($node_sequence, 'start', trim($matches[1])); $line = preg_replace(self::PATTERN_START_WITH, '', $line); } return $line; }