// 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; }
// Append the token with the specified value Token* SqlParser::Append(Token *token, const char *str, const wchar_t *wstr, int len, Token *format) { if(token == NULL) return NULL; Token *append = new Token(); if(format == NULL) *append = *token; else *append = *format; append->prev = NULL; append->next = NULL; append->t_str = NULL; append->t_wstr = NULL; append->t_len = 0; // If token starts with newline, add the same number of spaces before appended token if(str != NULL && str[0] == '\n') { // Add newline token first Token newline; #ifdef WIN32 newline.type = TOKEN_SYMBOL; newline.chr = '\r'; AppendCopy(token, &newline); #endif newline.type = TOKEN_SYMBOL; newline.chr = '\n'; AppendCopy(token, &newline); Token *prev = (format != NULL) ? format->prev : token->prev; // Copy all blankes while(prev != NULL && (prev->Compare(' ', L' ') == true || prev->Compare('\t', L'\t') == true)) { AppendCopy(token, prev); prev = prev->prev; } // Skip new line in the token Token::Change(append, str + 1, wstr + 1, len - 1); } else Token::Change(append, str, wstr, len); Append(token, append); return append; }
// Append the range of tokens (inclusively) Token* SqlParser::AppendCopy(Token *token, Token *first, Token *last, bool append_removed) { if(token == NULL || first == NULL) return NULL; Token *cur = first; Token *new_first = NULL; while(cur != NULL) { bool removed = cur->IsRemoved(); // Check whether we need to skip already removed tokens if(removed == false || (removed == true && append_removed == true)) { Token *append = AppendCopy(token, cur); if(new_first == NULL) new_first = append; } // Append single token only if(first == last || last == NULL) break; // Tokens appended inclusively if(cur == last) break; cur = cur->next; } return new_first; }
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); }
// Prepend the token with the specified value Token* SqlParser::Prepend(Token *token, const char *str, const wchar_t *wstr, int len, Token *format) { if(token == NULL) return NULL; Token *prepend = new Token(); // Define how to format the token if(format == NULL) *prepend = *token; else *prepend = *format; prepend->prev = NULL; prepend->next = NULL; prepend->t_str = NULL; prepend->t_wstr = NULL; prepend->t_len = 0; prepend->flags = TOKEN_INSERTED; Token::Change(prepend, str, wstr, len); Prepend(token, prepend); // If token ends with newline, add the same number of spaces after prepended token if(str != NULL && str[len - 1] == '\n') { Token *prev = token->prev; // Skip previously appended tokens while(prev != NULL && prev->flags & TOKEN_INSERTED) prev = prev->prev; // Copy all blanks and tabs before appended token after new line while(prev != NULL && (prev->Compare(' ', L' ') == true || prev->Compare('\t', L'\t') == true)) { AppendCopy(prepend, prev); prev = prev->prev; } } return prepend; }
// 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; }
// 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 z/OS USING STOGROUP clause bool SqlParser::ParseDb2StogroupClause(Token *objname, Token *using_, Token *stogroup, int scope) { if(using_ == NULL || stogroup == NULL) return false; // STOGROUP name Token *name = GetNextToken(); if(name == NULL) return false; bool exists = false; // STOGROUP options while(true) { Token *next = GetNextToken(); if(next == NULL) break; // PRIQTY num if(next->Compare("PRIQTY", L"PRIQTY", 6) == true) { Token *num = GetNextToken(); if(_target != SQL_DB2) Token::Remove(next, num); exists = true; continue; } else // SECQTY num if(next->Compare("SECQTY", L"SECQTY", 6) == true) { Token *num = GetNextToken(); if(_target != SQL_DB2) Token::Remove(next, num); exists = true; continue; } else // ERASE NO | YES if(next->Compare("ERASE", L"ERASE", 5) == true) { Token *value = GetNextToken(); if(_target != SQL_DB2) Token::Remove(next, value); exists = true; continue; } PushBack(next); break; } // Set DATAFILE clause in CREATE TABLESPACE for Oracle if(_target == SQL_ORACLE && scope == SQL_SCOPE_TABLESPACE) { Token::Change(using_, "DATAFILE '", L"DATAFILE '", 10); // Use tablespace name as multiple tablespaces can be created in a single storage group AppendCopy(using_, objname); Append(using_, ".dbf'", L".dbf'", 5); Token::Remove(stogroup); Token::Remove(name); } else if(_target != SQL_DB2) Token::Remove(using_, name); 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; }
// Parse SQL Server, Sybase ASE UPDATE statememt bool SqlParser::ParseSqlServerUpdateStatement(Token *update) { Token *name = GetNextIdentToken(SQL_IDENT_OBJECT); if(name == NULL) return false; Token *set = TOKEN_GETNEXTW("SET"); if(set == NULL) { PushBack(name); return false; } // Parser list of assignments: c1 = exp1, ... while(true) { Token *col = GetNextIdentToken(); if(col == NULL) break; Token *equal = TOKEN_GETNEXT('='); if(equal == NULL) break; // Single value or (SELECT c1, c2, ...) can be specified Token *open = TOKEN_GETNEXT('('); Token *select = NULL; // Check for SELECT statement if(open != NULL) { select = GetNextSelectStartKeyword(); // A subquery used to specify assignment values if(select != NULL) ParseSelectStatement(select, 0, SQL_SEL_UPDATE_SET, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); TOKEN_GETNEXT(')'); } else { Token *exp = GetNextToken(); if(exp == NULL) break; ParseExpression(exp); } Token *comma = TOKEN_GETNEXT(','); if(comma == NULL) break; } Token *from = NULL; Token *from_end = NULL; // FROM clause can include inner/outer joins ParseSelectFromClause(NULL, false, &from, &from_end, NULL, true, NULL); Token *where_ = NULL; Token *where_end = NULL; // optional WHERE clause ParseWhereClause(SQL_STMT_UPDATE, &where_, &where_end, NULL); // UPDATE FROM syntax is used if(from != NULL) { // MySQL, MariaDB use syntax UPDATE t1, t2 SET ... WHERE if(Target(SQL_MYSQL, SQL_MARIADB)) { Token::Remove(from); AppendCopy(name, from, from_end, false); Token::Remove(name); Token::Remove(from, from_end); } } // Implement CONTINUE handler for NOT FOUND in Oracle if(_target == SQL_ORACLE) OracleContinueHandlerForUpdate(update); // Add statement delimiter if not set when source is SQL Server SqlServerAddStmtDelimiter(); return true; }
// 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); } }