// 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; }
void SqlParser::AppendNoFormat(Token *token, TokenStr *str) { if(token == NULL || str == NULL) return; AppendNoFormat(token, str->str.c_str(), str->wstr.c_str(), str->len); }
// 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; }
// 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); }
void SqlParser::AppendSpaceCopy(Token *token, Token *first, Token *last, bool append_removed) { if(token == NULL) return; AppendNoFormat(token, " ", L" ", 1); AppendCopy(token, first, last, append_removed); }
// Append a space and specified token void SqlParser::AppendSpaceCopy(Token *token, Token *append) { if(token == NULL || append == NULL) return; AppendNoFormat(token, " ", L" ", 1); AppendCopy(token, append); }
// Change the token and add spaces around it if they do not exist void SqlParser::ChangeWithSpacesAround(Token *token, const char *new_str, const wchar_t *new_wstr, int len, Token *format) { if(token == NULL) return; if(token->prev != NULL && token->prev->IsBlank() == false) PrependNoFormat(token, " ", L" ", 1); if(token->next != NULL && token->next->IsBlank() == false) AppendNoFormat(token, " ", L" ", 1); Token::Change(token, new_str, new_wstr, len, format); }
// VALUES NEXTVAL seq INTO var pattern bool SqlParser::Db2ValuesNextValIntoPattern(Token *values) { if(values == NULL || _target != SQL_ORACLE) return false; // NEXTVAL keyword Token *nextval = GetNextWordToken("NEXTVAL", L"NEXTVAL", 7); if(nextval == NULL) return false; // FOR keyword Token *for_ = GetNextWordToken("FOR", L"FOR", 3); // Sequence name Token *seq_name = GetNextToken(for_); // INTO keyword Token *into = GetNextWordToken(seq_name, "INTO", L"INTO", 4); // Variable name Token *var = GetNextToken(into); if(var == NULL) { PushBack(nextval); return false; } // SELECT seq.NEXTVAL INTO var FROM dual in Oracle if(_target == SQL_ORACLE) { Token::Change(values, "SELECT", L"SELECT", 6); PrependCopy(nextval, seq_name); PrependNoFormat(nextval, ".", L".", 1); Token::Remove(for_, seq_name); Append(var, " FROM ", L" FROM ", 6, values); AppendNoFormat(var, "dual", L"dual", 4); } 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); }
// SQL Server requires an alias for subquery void SqlParser::SqlServerAppendSubqueryAlias(Token *append, int *appended_subquery_aliases) { if(append == NULL) return; int alias_num = 0; // Define the current counter within SELECT if(appended_subquery_aliases != NULL) alias_num = *appended_subquery_aliases; TokenStr alias(" s", L" s", 2); if(alias_num != 0) alias.Append(alias_num); AppendNoFormat(append, &alias); alias_num++; if(appended_subquery_aliases != NULL) *appended_subquery_aliases = alias_num; }
// 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 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; }
void SqlParser::Comment(const char *word, const wchar_t *w_word, int len, Token *first, Token *last) { PrependNoFormat(first, "/* ", L"/* ", 3); PrependNoFormat(first, word, w_word, len); AppendNoFormat(last, " */", L" */", 3); }
void SqlParser::CommentNoSpaces(Token *first, Token *last) { PrependNoFormat(first, "/*", L"/*", 2); AppendNoFormat(Nvl(last, first), "*/", L"*/", 2); }
// Convert row-level trigger using inserted and deleted system tables void SqlParser::SqlServerConvertRowLevelTrigger(Token *table, Token *when, Token *insert, Token *update, Token *delete_, Token *end) { if(table == NULL || when == NULL) return; TokenStr old_cols; TokenStr old_vars; Token *decl_end = _spl_last_declare; // Skip statement delimiter if(decl_end != NULL && Token::Compare(decl_end->next, ';', L';') == true) decl_end = decl_end->next; int num = 0; size_t old_prefix_len = 4; // Skip old. in column name references (old already prefixed with @ in target) if(_spl_old_correlation_name != NULL) old_prefix_len = _spl_old_correlation_name->len + 1 + 1; // Generate declaration for referenced OLD columns for(ListwItem *i = _spl_tr_old_columns.GetFirst(); i != NULL; i = i->next) { Token *var = (Token*)i->value; if(num == 0) Append(decl_end, "\n", L"\n", 1); else { old_cols.Append(", ", L", ", 2); old_vars.Append(", ", L", ", 2); } Append(decl_end, "\nDECLARE ", L"\nDECLARE ", 9, _declare_format); AppendCopy(decl_end, var); Append(decl_end, " ", L" ", 1); // Define the data type of the variable TokenStr type; GuessType(var, type); AppendNoFormat(decl_end, &type); Append(decl_end, ";", L";", 1); old_cols.Append(var, old_prefix_len, var->len - old_prefix_len + 1); old_vars.Append(var); num++; } // Declare deleted cursor if(delete_ != NULL) { Append(decl_end, "\nDECLARE ", L"\nDECLARE ", 9, _declare_format); AppendNoFormat(decl_end, "deleted_cur ", L"deleted_cur ", 12); Append(decl_end, "CURSOR FOR SELECT ", L"CURSOR FOR SELECT ", 18, _declare_format); AppendNoFormat(decl_end, &old_cols); Append(decl_end, " FROM ", L" FROM ", 6, _declare_format); AppendNoFormat(decl_end, "deleted;", L"deleted;", 8); } if(delete_ != NULL) Append(decl_end, "\n\nOPEN deleted_cur;", L"\n\nOPEN deleted_cur;", 19); Append(decl_end, "\nWHILE 1=0 BEGIN", L"\nWHILE 1=0 BEGIN", 16); if(delete_ != NULL) { Append(decl_end, "\nFETCH deleted_cur INTO ", L"\nFETCH deleted_cur INTO ", 24); AppendNoFormat(decl_end, &old_vars); Append(decl_end, ";", L";", 1); } Append(decl_end, "\nIF @@FETCH_STATUS <> 0 BREAK;", L"\nIF @@FETCH_STATUS <> 0 BREAK;", 30); // Terminate the cursor loop Token *end_loop = Prepend(end, "END\n", L"END\n", 4); if(delete_ != NULL) { Append(end_loop, "CLOSE deleted_cur;", L"CLOSE deleted_cur;", 18); Append(end_loop, "\nDEALLOCATE deleted_cur;", L"\nDEALLOCATE deleted_cur;", 24); } // Perform DELETE in INSTEAD OF trigger if(delete_ != NULL && insert == NULL && update == NULL) { Append(end_loop, "\nDELETE FROM ", L"\nDELETE FROM ", 13); AppendCopy(end_loop, table); Append(end_loop, " WHERE (", L" WHERE (", 8); AppendNoFormat(end_loop, &old_cols); Append(end_loop, ") IN (SELECT ", L") IN (SELECT ", 13); AppendNoFormat(end_loop, &old_cols); Append(end_loop, " FROM deleted);\n", L" FROM deleted);\n", 16); } }