// Add statement delimiter if not set when source is SQL Server, Sybase ASE void SqlParser::SqlServerAddStmtDelimiter(bool force) { if(Source(SQL_SQL_SERVER, SQL_SYBASE) == false) return; // Add the delimiter in statements in procedural block only if(_spl_scope == 0 && force == false) { // Special case when a single statement followed by GO, then add ; even if we are no inside procedure scope if(!LookNext("GO", L"GO", 2)) return; } // Check if the delimiter already set Token *semi = GetNextCharToken(';', L';'); if(semi != NULL) { PushBack(semi); return; } // Do not set if comma followed Token *comma = GetNextCharToken(',', L','); if(comma != NULL) { PushBack(comma); return; } AppendNoFormat(GetLastToken(), ";", L";", 1); }
// PRIMARY INDEX hash partitioning clause bool SqlParser::ParseTeradataPrimaryIndex(Token *unique, Token *primary, int obj_scope, Token *last_colname, Token *last_colend) { if(primary == NULL) return false; Token *index = GetNextWordToken("INDEX", L"INDEX", 5); if(index == NULL) return false; Token *open = GetNextCharToken('(', L'('); // List of partitioning columns while(true) { Token *column = GetNextToken(); if(column == NULL) break; Token *comma = GetNextCharToken(',', L','); if(comma == NULL) break; } Token *close = GetNextCharToken(open, ')', L')'); // Add UNIQUE constraint for other databases if(unique != NULL && _target != SQL_TERADATA) { AppendNoFormat(last_colend, ",", L",", 1); Append(last_colend, "\n", L"\n", 1, last_colname); AppendCopy(last_colend, unique); AppendNoFormat(last_colend, " ", L" ", 1); AppendCopy(last_colend, open, close); Token::Remove(unique, false); } // PARTITION BY HASH in Oracle if(_target == SQL_ORACLE) { // In Oracle, temporary tables cannot be partitioned if(obj_scope != SQL_SCOPE_TEMP_TABLE) { Token::Change(primary, "PARTITION", L"PARTITION", 9); Token::Change(index, "BY HASH", L"BY HASH", 7); } else Token::Remove(primary, close); } return true; }
// Get next token if the previous is set Token* SqlParser::GetNextCharToken(Token *prev, const char ch, const wchar_t wch) { if(prev == NULL) return NULL; return GetNextCharToken(ch, wch); }
// Handle GO after a statement in SQL Server bool SqlParser::SqlServerGoDelimiter(bool just_remove) { if(Source(SQL_SQL_SERVER, SQL_SYBASE) == false || Target(SQL_SQL_SERVER, SQL_SYBASE) == true) return false; Token *stmt_end = GetLastToken(); // Optional ; can go before GO Token *semi = GetNextCharToken(';', L';'); Token *go = GetNextWordToken("GO", L"GO", 2); if(go == NULL) { // ; is processed at statement parser if(semi != NULL) PushBack(semi); return false; } // The delimiter ; does not already exist, add it otherwise just remove GO if(semi == NULL && just_remove == false) AppendNoFormat(stmt_end, ";", L";", 1); Token::Change(go, " ", L" ", 1); return true; }
// Get the specified character token or the last selected Token* SqlParser::GetNextCharOrLastToken(const char ch, const wchar_t wch) { Token *out = GetNextCharToken(ch, wch); // Not found if(out == NULL) out = GetLastToken(); return out; }
// DB2 RESULT_SET_LOCATOR declaration bool SqlParser::ParseDb2ResultSetLocatorDeclaration(Token *declare, Token *name, Token *result_set_locator) { if(result_set_locator == NULL) return false; // VARYING keyword /*Token *varying */ (void) GetNextWordToken("VARYING", L"VARYING", 7); _spl_declared_rs_locators.Add(name); // Remove the entire declaration for other databases if(_target != SQL_DB2) Token::Remove(declare, Nvl(GetNextCharToken(';', L';'), GetLastToken())); return true; }
// Any Transact-SQL statement can be followed by ; and GO, any combination of them or nothing at all void SqlParser::SqlServerDelimiter() { if(Source(SQL_SQL_SERVER, SQL_SYBASE) == false || Target(SQL_SQL_SERVER, SQL_SYBASE) == true) return; Token *last = GetLastToken(); // Optional ; can go before GO Token *semi = GetNextCharToken(';', L';'); // Optional GO to terminate the batch Token *go = GetNextWordToken("GO", L"GO", 2); // No ; delimiter if(semi == NULL) AppendNoFormat(last, ";", L";", 1); Token::Remove(go); }
// Parse MySQL CREATE TABLE storage clause bool SqlParser::ParseMysqlStorageClause(Token *table_name, Token **id_start, Token **comment_out) { bool exists = false; // Auto_increment start value Token *auto_start = NULL; while(true) { Token *next = GetNextToken(); if(next == NULL) break; // ENGINE = type if(next->Compare("ENGINE", L"ENGINE", 6) == true) { // Equal sign = is optional in the clause Token *equal = GetNextCharToken('=', L'='); Token *type = GetNextToken(); if(_target != SQL_MYSQL) { Token::Remove(next); Token::Remove(equal); Token::Remove(type); } exists = true; continue; } else // AUTO_INCREMENT = start table option if(next->Compare("AUTO_INCREMENT", L"AUTO_INCREMENT", 14) == true) { // Equal sign = is optional in the clause Token *equal = GetNextCharToken('=', L'='); auto_start = GetNextNumberToken(); if(_target != SQL_MYSQL) { Token::Remove(next); Token::Remove(equal); Token::Remove(auto_start); } exists = true; continue; } else // DEFAULT CHARSET if(next->Compare("DEFAULT", L"DEFAULT", 7) == true) { Token *option = GetNextToken(); if(option == NULL) break; // CHARSET if(option->Compare("CHARSET", L"CHARSET", 7) == true) { Token *equal = GetNextCharToken('=', L'='); Token *value = GetNextIdentToken(); if(_target != SQL_MYSQL) Token::Remove(next, value); } else // CHARACTER SET if(option->Compare("CHARACTER", L"CHARACTER", 9) == true) { Token *set = GetNextWordToken("SET", L"SET", 3); Token *equal = GetNextCharToken('=', L'='); Token *value = GetNextIdentToken(); if(_target != SQL_MYSQL) Token::Remove(next, value); } exists = true; continue; } else // COLLATE = value if(next->Compare("COLLATE", L"COLLATE", 7) == true) { Token *equal = GetNextCharToken('=', L'='); Token *value = GetNextIdentToken(); if(_target != SQL_MYSQL) Token::Remove(next, value); exists = true; continue; } else // COMMENT = 'table comment' if(next->Compare("COMMENT", L"COMMENT", 7) == true) { // Equal sign = is optional in the clause Token *equal = GetNextCharToken('=', L'='); Token *text = GetNextToken(); if(comment_out != NULL) *comment_out = text; // Remove from CREATE TABLE if(_target != SQL_MYSQL) Token::Remove(next, text); exists = true; continue; } else // PACK_KEYS = 0 | 1 | DEFAULT if(next->Compare("PACK_KEYS", L"PACK_KEYS", 9) == true) { // Optional = Token *equal = GetNextCharToken('=', L'='); Token *value = GetNextToken(); if(_target != SQL_MYSQL) Token::Remove(next, value); exists = true; continue; } else // ROW_FORMAT = type | DEFAULT if(next->Compare("ROW_FORMAT", L"ROW_FORMAT", 10) == true) { // Optional = Token *equal = GetNextCharToken('=', L'='); Token *value = GetNextToken(); if(_target != SQL_MYSQL) Token::Remove(next, value); exists = true; continue; } // Not a MySQL stoage clause PushBack(next); break; } if(id_start != NULL) *id_start = auto_start; // Restart sequence for PostgreSQL and Greenplum if(auto_start != NULL && (_target == SQL_POSTGRESQL || _target == SQL_GREENPLUM)) { // Try to get ; Token *semi = GetNextCharToken(';', L';'); Token *last = (semi != NULL) ? semi : GetLastToken(); // Append ALTER SEQUENCE command Append(last, "\n\nALTER SEQUENCE ", L"\n\nALTER SEQUENCE ", 17); // Add sequence name Token *seq_name = AppendIdentifier(table_name, "_seq", L"_seq", 4); Append(last, seq_name); Append(last, " RESTART WITH ", L" RESTART WITH ", 14); AppendCopy(last, auto_start); Append(last, ";", L";", 1); } return exists; }
// Teradata CREATE TABLE options before column definition bool SqlParser::ParseTeradataTableOptions() { bool exists = false; // , goes after table name before even first option while(true) { Token *comma = GetNextCharToken(',', L','); if(comma == NULL) break; // Most options can start with NO keyword to disable option Token *no = GetNextWordToken("NO", L"NO", 2); Token *next = GetNextToken(); if(next == NULL) { if(no != NULL) PushBack(no); break; } // [NO] FALLBACK to store a row copy if(next->Compare("FALLBACK", L"FALLBACK", 8) == true) { if(_target != SQL_TERADATA) Token::Remove(comma, next); exists = true; continue; } else // [NO] BEFORE JOURNAL to store before image if(next->Compare("BEFORE", L"BEFORE", 6) == true) { Token *journal = GetNextWordToken("JOURNAL", L"JOURNAL", 7); if(_target != SQL_TERADATA && journal != NULL) Token::Remove(comma, journal); exists = true; continue; } else // [NO] AFTER JOURNAL to store after image if(next->Compare("AFTER", L"AFTER", 5) == true) { Token *journal = GetNextWordToken("JOURNAL", L"JOURNAL", 7); if(_target != SQL_TERADATA && journal != NULL) Token::Remove(comma, journal); exists = true; continue; } if(no != NULL) PushBack(no); break; } return exists; }
// DB2 for z/OS COMMENT ON tab (col IS 'comment' , ...) bool SqlParser::ParseDb2Comment(Token *comment, Token *on, Token *name) { if(comment == NULL) return false; Token *open = GetNextCharToken('(', L'('); if(open == NULL) return false; int num = 1; // Multipe comma separated comments can be specified while(true) { Token *col = GetNextIdentToken(); if(col == NULL) break; // IS keyword must follow now Token *is = GetNextWordToken("IS", L"IS", 2); if(is == NULL) break; // Comment text Token *text = GetNextToken(); if(_target == SQL_SQL_SERVER) { Append(text, ", 'table', ", L", 'table', ", 11); AppendCopy(text, name); Append(text, ", 'column', ", L", 'column', ", 12); AppendCopy(text, col); Token::Remove(col); Token::Remove(is); } else if(_target == SQL_ORACLE) { if(num == 1) { Append(on, " COLUMN", L" COLUMN", 7); Append(name, ".", L".", 1); AppendCopy(name, col); Token::Remove(col); } } Token *comma = GetNextCharToken(',', L','); if(comma == NULL) break; num++; } Token *close = GetNextCharToken(')', L')'); if(_target == SQL_SQL_SERVER) { Token::Change(comment, "EXECUTE", L"EXECUTE", 7); AppendNoFormat(comment, " sp_addextendedproperty 'Comment',", L" sp_addextendedproperty 'Comment',", 34); Token::Remove(on); Token::Remove(name); Token::Remove(open); Token::Remove(close); } else if(_target == SQL_ORACLE) { Token::Remove(open); Token::Remove(close); } return true; }
// DB2 CREATE DATABASE statement bool SqlParser::Db2CreateDatabase(Token *create, Token * /*database*/, Token * /*name*/) { bool exists = false; // CREATE DATABASE options while(true) { Token *next = GetNextToken(); if(next == NULL) break; // BUFFERPOOL name if(next->Compare("BUFFERPOOL", L"BUFFERPOOL", 10) == true) { Token *name = GetNextToken(); if(Target(SQL_DB2, SQL_ORACLE) == false) Token::Remove(next, name); exists = true; continue; } else // INDEXBP name if(next->Compare("INDEXBP", L"INDEXBP", 7) == true) { Token *name = GetNextToken(); if(Target(SQL_DB2, SQL_ORACLE) == false) Token::Remove(next, name); exists = true; continue; } else // STOGROUP name if(next->Compare("STOGROUP", L"STOGROUP", 8) == true) { Token *name = GetNextToken(); if(Target(SQL_DB2, SQL_ORACLE) == false) Token::Remove(next, name); exists = true; continue; } else // CCSID ASCII | EBCDIC | UNICODE if(next->Compare("CCSID", L"CCSID", 5) == true) { Token *code = GetNextToken(); if(Target(SQL_DB2, SQL_ORACLE) == false) Token::Remove(next, code); exists = true; continue; } PushBack(next); break; } // Comment the entire statement for Oracle if(_target == SQL_ORACLE) { Token *end = GetLastToken(GetNextCharToken(';', L';')); Comment(create, end); } return exists; }
// DB2 identity options in GENERATED ALWAYS or BY DEFAULT AS IDENTITY bool SqlParser::ParseDb2GeneratedClause(Token *create, Token *table_name, Token *column, Token *generated, Token **id_col, Token **id_start, Token **id_inc, bool *id_default) { if(generated == NULL) return false; Token *always = GetNextWordToken("ALWAYS", L"ALWAYS", 6); Token *by = NULL; Token *default_ = NULL; if(always == NULL) { by = GetNextWordToken("BY", L"BY", 2); default_ = GetNextWordToken("DEFAULT", L"DEFAULT", 7); if(id_default != NULL) *id_default = true; } else { if(id_default != NULL) *id_default = false; } Token *as = GetNextWordToken("AS", L"AS", 2); Token *identity = GetNextWordToken("IDENTITY", L"IDENTITY", 8); // Identity parameters are optional Token *open = GetNextCharToken('(', L'('); Token *close = NULL; Token *start_with = NULL; Token *increment_by = NULL; while(true) { bool exists = false; if(open == NULL) break; Token *option = GetNextToken(); if(option == NULL) break; // START WITH if(option->Compare("START", L"START", 5) == true) { /*Token *with */ (void) GetNextWordToken("WITH", L"WITH", 4); start_with = GetNextNumberToken(); exists = true; continue; } else // INCREMENT BY if(option->Compare("INCREMENT", L"INCREMENT", 9) == true) { /*Token *by */ (void) GetNextWordToken("BY", L"BY", 2); increment_by = GetNextNumberToken(); exists = true; continue; } else // MINVALUE if(option->Compare("MINVALUE", L"MINVALUE", 8) == true) { /*Token *value */ (void) GetNextNumberToken(); exists = true; continue; } else // MAXVALUE if(option->Compare("MAXVALUE", L"MAXVALUE", 8) == true) { /*Token *value */ (void) GetNextNumberToken(); exists = true; continue; } else // NO CYCLE and NO ORDER if(option->Compare("NO", L"NO", 2) == true) { /*Token *attr */ (void) GetNextToken(); exists = true; continue; } else // CACHE if(option->Compare("CACHE", L"CACHE", 5) == true) { /*Token *value */ (void) GetNextNumberToken(); exists = true; continue; } else // CYCLE if(option->Compare("CYCLE", L"CYCLE", 5) == true) { exists = true; continue; } else // Looks like comma is optional if(option->Compare(',', L',') == true) { exists = true; continue; } PushBack(option); break; } if(open != NULL) close = GetNextCharToken(')', L')'); // IDENTITY(start, inc) in SQL Server if(_target == SQL_SQL_SERVER) { Token::Change(generated, "IDENTITY(", L"IDENTITY(", 9); AppendCopy(generated, start_with); if(increment_by != NULL) { Append(generated, ", ", L", ", 2); AppendCopy(generated, increment_by); } Append(generated, ")", L")", 1); if(always != NULL) Token::Remove(always, close); else Token::Remove(by, close); } else // Use a sequence and DEFAULT nextval for PostgreSQL and Greenplum if(_target == SQL_POSTGRESQL || _target == SQL_GREENPLUM) { TokenStr seq_name(table_name); AppendIdentifier(seq_name, "_seq", L"_seq", 4); // Generate CREATE SEQUENCE before CREATE TABLE Prepend(create, "CREATE SEQUENCE ", L"CREATE SEQUENCE ", 16); PrependNoFormat(create, &seq_name); if(start_with != NULL) { Prepend(create, " START WITH ", L" START WITH ", 12); PrependCopy(create, start_with); } if(increment_by != NULL) { Prepend(create, " INCREMENT BY ", L" INCREMENT BY ", 14); PrependCopy(create, increment_by); } Prepend(create, ";\n\n", L";\n\n", 3); // Generate DEFAULT nextval('tablename_seq') clause Token::Change(generated, "DEFAULT ", L"DEFAULT ", 8); Append(generated, "nextval ('", L"nextval ('", 10); AppendNoFormat(generated, &seq_name); Append(generated, "')", L"')", 2); if(always != NULL) Token::Remove(always, close); else Token::Remove(by, close); } else // Remove for other databases if(_target != SQL_DB2) { Token::Remove(generated); Token::Remove(always); Token::Remove(by, default_); Token::Remove(as, identity); Token::Remove(open, close); } if(id_col != NULL) *id_col = column; if(id_start != NULL) *id_start = start_with; if(id_inc != NULL) *id_inc = increment_by; return true; }
// Parse specific DB2 AND syntax: (c1, c2, ...) = (v1, v2, ...) bool SqlParser::ParseDb2AndBooleanExpression(Token *open) { if(open == NULL) return false; Token *next = GetNextToken(); if(next == NULL) return false; // Comma must follow after the first token Token *comma = GetNextCharToken(',', L','); if(comma == NULL) { PushBack(next); return false; } ListWM first; first.Add(next, comma); // Select left columns of AND expressions while(true) { Token *nextn = GetNextToken(); if(next == NULL) break; Token *comma = GetNextCharToken(',', L','); first.Add(nextn, comma); if(comma == NULL) break; } Token *close = GetNextCharToken(')', L')'); // = before list of right AND expressions Token *equal = GetNextCharToken('=', L'='); if(equal == NULL) { PushBack(next); return false; } Token *open2 = GetNextCharToken('(', L'('); ListwmItem *i = first.GetFirst(); // Select right columns of AND expressions while(true) { Token *nextn = GetNextToken(); if(next == NULL) break; Token *comma = GetNextCharToken(',', L','); Token *f_col = NULL; Token *f_comma = NULL; Token *f_append = NULL; if(i != NULL) { f_col = (Token*)i->value; f_comma = (Token*)i->value2; f_append = Nvl(f_comma, f_col); } // Oracle does not support this syntax, use c1 = v1 AND ... if(_target == SQL_ORACLE) { AppendNoFormat(f_append, " = ", L" = ", 3); AppendCopy(f_append, nextn); if(comma != NULL) AppendNoFormat(f_append, " AND", L" AND", 4); Token::Remove(f_comma); Token::Remove(nextn); Token::Remove(comma); } if(comma == NULL) break; if(i != NULL) i = i->next; } if(_target == SQL_ORACLE) { Token::Remove(close); Token::Remove(equal); Token::Remove(open2); } // Note: function mustn't parse closing ), it processed by the caller return true; }
// Various SET options such as SET CURRENT SCHEMA bool SqlParser::ParseDb2SetOptions(Token *set) { bool exists = false; if(set == NULL) return false; // CURRENT is optional (SET SCHEMA is allowed) Token *current = GetNextWordToken("CURRENT", L"CURRENT", 7); Token *option = GetNextToken(); if(option == NULL) { PushBack(current); return false; } // SET [CURRENT] PATH = list if(option->Compare("PATH", L"PATH", 4) == true) { // Optional = /*Token *equal */ (void) GetNextCharToken('=', L'='); // Comma-separated list of values while(true) { // Schema name /*Token *name */ (void) GetNextToken(); Token *comma = GetNextCharToken(',', L','); if(comma == NULL) break; } // Remove the statement in Oracle if(_target == SQL_ORACLE) Token::Remove(set, Nvl(GetNextCharToken(';', L';'), GetLastToken())); exists = true; } else // SET [CURRENT] SCHEMA = name if(option->Compare("SCHEMA", L"SCHEMA", 6) == true) { // Optional = Token *equal = GetNextCharToken('=', L'='); // Schema name /*Token *name */ (void) GetNextToken(); // ALTER SESSION SET CURRENT_SCHEMA = name in Oracle if(_target == SQL_ORACLE) { Prepend(set, "ALTER SESSION ", L"ALTER SESSION ", 14); Token::Remove(current); Token::Change(option, "CURRENT_SCHEMA", L"CURRENT_SCHEMA", 14); if(equal == NULL) AppendNoFormat(option, " =", L" =", 2); } exists = true; } // Not a SET option else { PushBack(option); PushBack(current); } return exists; }
// Parse DB2 partitioning clause bool SqlParser::ParseDb2PartitioningClause(Token *partition, Token *by) { if(partition == NULL || by == NULL) return false; // RANGE is optional in DB2 z/OS Token *range = GetNextWordToken("RANGE", L"RANGE", 5); // RANGE keyword is required for Oracle if(range == NULL && _target == SQL_ORACLE) Append(by, " RANGE", L" RANGE", 6); /*Token *open */ (void) GetNextCharToken('(', L'('); // Comma separated list of columns while(true) { Token *column = GetNextToken(); if(column == NULL) break; Token *comma = GetNextCharToken(',', L','); if(comma == NULL) break; } /*Token *close */ (void) GetNextCharToken(')', L')'); /*Token *open2 */ (void) GetNextCharToken('(', L'('); // Comma separated list of partition definitions while(true) { // PARTITION keyword Token *partition2 = GetNextWordToken("PARTITION", L"PARTITION", 9); if(partition2 == NULL) break; // Partition number Token *num = GetNextToken(); if(num == NULL) break; // In Oracle a partition name is required so prepend 'p' to each number if(_target == SQL_ORACLE) PrependNoFormat(num, "p", L"p", 1); // ENDING [AT] Token *ending = GetNextWordToken("ENDING", L"ENDING", 6); Token *at = GetNextWordToken("AT", L"AT", 2); // VALUES LESS THAN in Oracle if(_target == SQL_ORACLE) { Token::Change(ending, "VALUES LESS THAN", L"VALUES LESS THAN", 16); Token::Remove(at); } // Each value is in () /*Token *open3 */ (void) GetNextCharToken('(', L'('); /*Token *limit */ (void) GetNextToken(); /*Token *close3 */ (void) GetNextCharToken(')', L')'); Token *comma = GetNextCharToken(',', L','); if(comma == NULL) break; } /*Token *close2 */ (void) GetNextCharToken(')', L')'); return true; }
// SQL Server SET options, SET ANSI_NULLS ON i.e bool SqlParser::ParseSqlServerSetOptions(Token *set) { if(set == NULL) return false; bool exists = false; bool comment = false; bool remove = false; Token *option = GetNextToken(); if(option == NULL) return false; // SET ANSI_NULLS ON | OFF if(option->Compare("ANSI_NULLS", L"ANSI_NULLS", 10) == true) { /*Token *value */ (void) GetNextToken(); if(_target != SQL_SQL_SERVER) comment = true; exists = true; } else // SET ANSI_PADDING ON | OFF if(option->Compare("ANSI_PADDING", L"ANSI_PADDING", 12) == true) { /*Token *value */ (void) GetNextToken(); if(_target != SQL_SQL_SERVER) comment = true; exists = true; } else // SET NOCOUNT ON | OFF if(option->Compare("NOCOUNT", L"NOCOUNT", 7) == true) { /*Token *value */ (void) GetNextToken(); if(_target != SQL_SQL_SERVER) remove = true; exists = true; } else // SET QUOTED_IDENTIFIER ON | OFF if(option->Compare("QUOTED_IDENTIFIER", L"QUOTED_IDENTIFIER", 17) == true) { /*Token *value */ (void) GetNextToken(); if(_target != SQL_SQL_SERVER) comment = true; exists = true; } // Not a SET option else PushBack(option); if(exists == true) { Token *last = Nvl(GetNextCharToken(';', L';'), GetLastToken()); if(comment == true) Comment(set, last); else if(remove == true) Token::Remove(set, last); // Remove following GO if any SqlServerGoDelimiter(true); } return exists; }