#include #include #include #include #include #include "lasuite.h" #include #include #include #include #include #include PATCH_USAGE pusage; using namespace std; static map tokens; static bool str_nocaseeq(const string &a, const string &b) { return strcasecmp(a.c_str(),b.c_str())==0; } static string toupper(const std::string &table) { string ret(table); for (string::iterator it=ret.begin(); it != ret.end(); it++) *it = toupper(*it); return ret; } void SQL_OPTS::addtemptable(const std::string &table) { temp2id[toupper(table)] = temp_alloc_id++; } void SQL_OPTS::deltemptable(const std::string &table) { std::map::iterator it = temp2id.find(toupper(table)); if (it != temp2id.end()) temp2id.erase(it); } int SQL_OPTS::gettempid(const std::string &table) { int ret = -1; std::map::iterator it = temp2id.find(toupper(table)); if (it != temp2id.end()) ret = it->second; //printf ("gettempid ret=%d table=:%s:\n",ret,toupper(table).c_str()); return ret; } void parser_inittokens() { struct TOKNAMES{ TOKTYPE type; const char *val; }tb[]={ {TOK_EOF,"EOF"}, {TOK_OPNBRACE, "{"}, {TOK_CLSBRACE, "}"}, {TOK_OPNSQUARE,"["}, {TOK_CLSSQUARE,"]"}, {TOK_OPNPAR, "("}, {TOK_CLSPAR, ")"}, {TOK_STAR, "*"}, {TOK_PLUS,"+"}, {TOK_MINUS, "-"}, {TOK_DIVIDE, "/"}, {TOK_MULTIPLY, "*"}, {TOK_MODULO, "%"}, {TOK_NAME, "--name--"}, {TOK_STRING, "--string--"}, {TOK_NUM, "--num--"}, {TOK_COMMA, ","}, {TOK_COLON, ":"}, {TOK_DIFFER, "<>"}, {TOK_NOT, "!"}, {TOK_EQUAL, "="}, {TOK_LEFTJOIN, "*="}, {TOK_RIGHTJOIN, "=*"}, {TOK_SMALLER, "<"}, {TOK_SMALLEREQ, "<="}, {TOK_GREATER, ">"}, {TOK_GREATEREQ, ">="}, {TOK_EOF,NULL} }; for (int i=0; tb[i].val != NULL; i++) tokens[tb[i].type] = tb[i].val; } static map toks; static map fieldcnvs; // Convert from sybase to mysql static set udefs; // User defined type. See parser_setusertype() void parser_initfunctions() { // For now, we put function and field types together static const char *tbfunc[]={ "trim","ltrim","rtrim","convert","count","coalesce","abs","max","min","isnull","sum","round", "object_id","object_name","datepart","current_date","dateadd","datediff", "getdate","syb_quit","datalength","char_length","textptr","object_name","db_id","substring", "right","left","len","charindex","identity", "month","year","day","db_name","str_replace","ascii","nullif", NULL }; for (int i=0; tbfunc[i] != NULL; i++){ toks[tbfunc[i]] = TOK_FUNCTION; } static const char *tbtype[]={ "decimal","numeric","varchar","nvarchar","char","int","tinyint","smallint","image","text","blob", "datetime","smalldatetime","bit","binary","float", NULL }; for (int i=0; tbtype[i] != NULL; i++){ string type = tbtype[i]; toks[type] = TOK_FIELDTYPE; fieldcnvs[type] = type; } fieldcnvs["tinyint"] = "tinyint unsigned"; fieldcnvs["blob"] = "mediumblob"; fieldcnvs["image"] = "longblob"; fieldcnvs["smalldatetime"] = "datetime"; fieldcnvs["bit"] = "bit(1)"; fieldcnvs["text"] = "longtext"; static const char *tbwopt[]={ "exp_row_size","dml_logging","identity_gap","reservepagegap","fillfactor", "sorted_data", NULL }; for (int i=0; tbwopt[i] != NULL; i++){ toks[tbwopt[i]] = TOK_WITHOPTION; } static struct { const char *s; TOKTYPE type; } tb[]={ {"in",TOK_IN}, {"from",TOK_FROM}, {"into",TOK_INTO}, {"select",TOK_SELECT}, {"update",TOK_UPDATE}, {"table",TOK_TABLE}, {"where",TOK_WHERE}, {"top",TOK_TOP}, {"to",TOK_TO}, {"having",TOK_HAVING}, {"delete",TOK_DELETE}, {"truncate",TOK_TRUNCATE}, {"like",TOK_LIKE}, {"not",TOK_NOT}, {"as",TOK_AS}, {"order",TOK_ORDER}, {"by",TOK_BY}, {"asc",TOK_ASC}, {"desc",TOK_DESC}, {"group",TOK_GROUP}, {"distinct",TOK_DISTINCT}, {"create",TOK_CREATE}, {"or",TOK_OR}, {"and",TOK_AND}, {"set",TOK_SET}, {"join",TOK_JOIN}, {"left",TOK_LEFT}, {"inner",TOK_INNER}, {"right",TOK_RIGHT}, {"outer",TOK_OUTER}, {"on",TOK_ON}, {"off",TOK_OFF}, {"case",TOK_CASE}, {"when",TOK_WHEN}, {"then",TOK_THEN}, {"else",TOK_ELSE}, {"end",TOK_END}, {"index",TOK_INDEX}, {"is",TOK_IS}, {"drop",TOK_DROP}, {"add",TOK_ADD}, {"modify",TOK_MODIFY}, {"if",TOK_IF}, {"while",TOK_WHILE}, {"begin",TOK_BEGIN}, {"values",TOK_VALUES}, {"insert",TOK_INSERT}, {"exists",TOK_EXISTS}, {"unique",TOK_UNIQUE}, {"clustered",TOK_CLUSTERED}, {"nonclustered",TOK_NONCLUSTERED}, {"union",TOK_UNION}, {"all",TOK_ALL}, {"null",TOK_NULL}, {"between",TOK_BETWEEN}, {"use",TOK_USE}, {"identity",TOK_IDENTITY}, {"default",TOK_DEFAULT}, {"check",TOK_CHECK}, {"constraint",TOK_CONSTRAINT}, {"lock",TOK_LOCK}, {"grant",TOK_GRANT}, {"setuser",TOK_SETUSER}, {"with",TOK_WITH}, {"partition",TOK_PARTITION}, {"roundrobin",TOK_ROUNDROBIN}, {"print",TOK_PRINT}, {"references",TOK_REFERENCES}, {"trigger",TOK_TRIGGER}, {"for",TOK_FOR}, {"primary",TOK_PRIMARY}, {"key",TOK_KEY}, {"exec",TOK_EXEC}, {"alter",TOK_ALTER}, {"foreign",TOK_FOREIGN}, {"sp_helpsegment",TOK_SP_HELPSEGMENT}, {"sp_configure",TOK_SP_CONFIGURE}, {"sp_dboption",TOK_SP_DBOPTION}, {"checkpoint",TOK_CHECKPOINT}, {"readtext",TOK_READTEXT}, {"writetext",TOK_WRITETEXT}, {"waitfor",TOK_WAITFOR}, {"delay",TOK_DELAY}, {"tran",TOK_TRANSACTION}, {"transaction",TOK_TRANSACTION}, {"commit",TOK_COMMIT}, {"rollback",TOK_ROLLBACK}, {"dbcc",TOK_DBCC}, {"traceon",TOK_TRACEON}, {"declare",TOK_DECLARE}, {"statistics",TOK_STATISTICS}, {"bulk",TOK_BULK}, {"nodescribe",TOK_NODESCRIBE}, {"output",TOK_OUTPUT}, {NULL,TOK_UNKNOWN} }; for (int i=0; tb[i].s != NULL; i++) toks[tb[i].s] = tb[i].type; } static string parser_cnvbasetype(const char *sybtype) { string ret = sybtype; for (unsigned i=0; i::iterator it = fieldcnvs.find(ret); if (it == fieldcnvs.end()){ tlmp_error ("Can't convert sybase type %s\n",sybtype); }else{ return it->second; } return ret; } string parser_cnvsybtype(const char *sybtype) { string ret = sybtype; const char *pt = strchr(sybtype,'('); if (pt == NULL){ ret = parser_cnvbasetype(sybtype); }else{ string basetype=string(sybtype,pt-sybtype); string type = parser_cnvbasetype(basetype.c_str()); ret = type+pt; } return ret; } /* Record sybase user defined types User defined type are assigned to TOK_NAME instead of TOK_FIELDTYPE because sybase supports using a type as a field name. So we define those as TOK_NAME but insert them into udefs. See f_ctable(). */ void parser_setusertype (const char *sybase_type, const char *mysql_type) { string tmp = sybase_type; for (unsigned i=0; i start){ int off = (int)(last-start); //if (off > 100) off = 100; printf ("ERROR %*.*s\n",off,off,last-off); for (int i=0; i0){ ret = saved.top(); saved.pop(); return ret; } while (1){ while (isspace(*pt)) pt++; if (*pt == '/' && pt[1] == '*'){ pt+=2; while (*pt != '\0'){ if (*pt == '*' && pt[1] == '/'){ pt+=2; break; } pt++; } continue; }else if (*pt == '-' && pt[1] == '-'){ pt+=2; while (*pt != '\0'){ if (*pt == '\n'){ pt++; break; } pt++; } continue; } last = pt; if (*pt == '\0'){ ret.type = TOK_EOF; break; }else if (*pt == '{'){ ret.type = TOK_OPNBRACE; pt++; break; }else if (*pt == '}'){ ret.type = TOK_CLSBRACE; pt++; break; }else if (*pt == '['){ //ret.type = TOK_OPNSQUARE; ret.type = TOK_NAME; const char *start = pt+1; while (*pt != '\0' && *pt != ']') pt++; // We do the conversion for MySQL immediatly ret.val = string(start,pt-start); if (*pt == ']') pt++; break; }else if (*pt == ']'){ // This never happens ret.type = TOK_CLSSQUARE; break; }else if (*pt == '('){ ret.type = TOK_OPNPAR; pt++; break; }else if (*pt == ')'){ ret.type = TOK_CLSPAR; pt++; break; }else if (*pt == '+'){ ret.type = TOK_PLUS; pt++; break; }else if ((*pt == '-' && (isdigit(pt[1]) || pt[1] == '.')) ||(*pt == '.' && isdigit(pt[1])) || isdigit(*pt)){ ret.type = TOK_NUM; const char *start = pt; if (*pt == '0' && (pt[1] == 'x' || pt[1] == 'X')){ pt += 2; while (isxdigit(*pt)) pt++; }else{ if (*pt == '-') pt++; while (isdigit(*pt))pt++; if (*pt == '.'){ pt++; while (isdigit(*pt))pt++; } if (*pt == 'e' || *pt == 'E'){ pt++; if (*pt == '-') pt++; while (isdigit(*pt))pt++; } } ret.val = string(start,pt-start); break; }else if (*pt == '-'){ ret.type = TOK_MINUS; pt++; break; }else if (*pt == '/'){ ret.type = TOK_DIVIDE; pt++; break; }else if (*pt == '%'){ ret.type = TOK_MODULO; pt++; break; }else if (*pt == '*'){ if (pt[1] == '='){ pt+=2; ret.type = TOK_LEFTJOIN; }else{ ret.type = TOK_MULTIPLY; pt++; } break; }else if (*pt == '='){ if (pt[1] == '*'){ pt+=2; ret.type = TOK_RIGHTJOIN; }else{ ret.type = TOK_EQUAL; pt++; } break; }else if (*pt == '!'){ pt++; if (*pt == '='){ ret.type = TOK_DIFFER; pt++; }else{ ret.type = TOK_NOT; } break; }else if (*pt == '<'){ pt++; if (*pt == '='){ ret.type = TOK_SMALLEREQ; pt++; }else if (*pt == '>'){ ret.type = TOK_DIFFER; pt++; }else{ ret.type = TOK_SMALLER; } break; }else if (*pt == '>'){ pt++; if (*pt == '='){ ret.type = TOK_GREATEREQ; pt++; }else{ ret.type = TOK_GREATER; } break; }else if (*pt == '"'){ ret.type = TOK_STRING; const char *start = pt; pt++; while (*pt != '\0'){ if (*pt == '"'){ if (pt[1] == '"'){ pt+= 2; }else{ pt++; break; } }else{ pt++; } } ret.val = string(start,pt-start); break; }else if (*pt == '\''){ ret.type = TOK_STRING; const char *start = pt; pt++; while (*pt != '\0'){ if (*pt == '\''){ if (pt[1] == '\''){ pt+= 2; }else{ pt++; break; } }else{ pt++; } } ret.val = string(start,pt-start); break; }else if (*pt == ','){ ret.type = TOK_COMMA; pt++; break; }else if (*pt == ':'){ ret.type = TOK_COLON; pt++; break; }else if (isalpha(*pt) || *pt == '#' || *pt == '@'){ // An id is either a name or a name.name (like table.field) or name.* ret.type = TOK_NAME; const char *start = pt++; if (*pt == '@') pt++; while (isalpha(*pt) || isdigit(*pt) || *pt == '_' || *pt == '.' || *pt == '#'){ if (*pt == '.'){ // name.* is valid, name.xxx* is name.xxx followed by the * operator if (pt[1] == '*') pt++; } pt++; } ret.val = string(start,pt-start); string lowerval = parser_tolower(ret.val); map::iterator it = toks.find(lowerval); if (it != toks.end()){ ret.type = it->second; if (ret.type == TOK_FUNCTION){ // Functions may be also keywords. The syntax is ambiguous // If a function is not followed by a (, then we switch to TOK_NAME const char *ptpar = str_skip(pt); if (*ptpar != '(') ret.type = TOK_NAME; } } break; }else{ // fprintf (stderr,"Ne sait pas quoi faire: %s\n",pt); ret.type = TOK_UNKNOWN; pt+=strlen(pt); break; } } //fprintf (stderr,"tok %d %s\n",ret.type,ret.val.c_str()); return ret; } static FILE *dumpfout = stdout; static int dumplevel = 1; EXPR::EXPR() { } EXPR::EXPR(const EXPR &e) { items = e.items; } EXPR *EXPR::dup() const { return new EXPR(*this); } void EXPR::add (TOKEN &t) { items.push_back(ITEM(t)); } unsigned EXPR::size() const { return items.size(); } void EXPR::add (ITEMS &i) { items.push_back(ITEM(i)); } void EXPR::add (USE &i) { items.push_back(ITEM(i)); } void EXPR::add (GRANT &i) { items.push_back(ITEM(i)); } void EXPR::add (EXEC &i) { items.push_back(ITEM(i)); } void EXPR::add (ALTER &i) { items.push_back(ITEM(i)); } void EXPR::add (EXPR &e) { size_t s = e.items.size(); // This optimisation is important as some code in the reformat section expect things // to be organised in a single flat list. The code that check of "val = null" and // converts it to "val is null" is one example. // The parser naturally builds sub-sub-expr if (s > 1){ items.push_back(ITEM(e)); }else if (s == 1){ if (e.items[0].t.type == TOK_FUNCTION){ items.push_back(ITEM(e)); }else{ items.push_back(e.items[0]); } } } void EXPR::add (PAREXPR &e) { items.push_back(ITEM(e)); } void EXPR::add (UNION &e) { items.push_back(ITEM(e)); } void EXPR::add (SELECT &s) { items.push_back(ITEM(s)); } void EXPR::add (UPDATE &s) { items.push_back(ITEM(s)); } void EXPR::add (INSERT &s) { items.push_back(ITEM(s)); } void EXPR::add (DROP &s) { items.push_back(ITEM(s)); } void EXPR::add (SET &s) { items.push_back(ITEM(s)); } void EXPR::add (IF &s) { items.push_back(ITEM(s)); } void EXPR::add (CREATE_TABLE &s) { items.push_back(ITEM(s)); } void EXPR::add (CREATE_INDEX &s) { items.push_back(ITEM(s)); } void EXPR::add (CREATE_TRIGGER &s) { items.push_back(ITEM(s)); } void EXPR::add (DELETE &s) { items.push_back(ITEM(s)); } void EXPR::add (TRUNCATE &s) { items.push_back(ITEM(s)); } void EXPR::add (DECLARE &s) { items.push_back(ITEM(s)); } void EXPR::dump(bool top) const { if (items.size() > 0){ const ITEM &item = items[0]; if (item.type == ITEM::IS_TOKEN && item.t.type == TOK_FUNCTION){ item.dump(); fprintf (dumpfout,"("); for (unsigned i=1; i 0 && top) fprintf (dumpfout,"\t\t"); items[i].dump(); if (top) fprintf (dumpfout,"\n"); } } }else if (top){ fprintf (dumpfout,"\n"); } } void EXPR::dump() const { dump(false); } void EXPR::dumptop() const { dumplevel++; dump(true); dumplevel--; } bool EXPR::cmp(const ITEMS *it) const { const EXPR *e = (const EXPR*)it; bool ret = false; unsigned size = items.size(); if (size == e->items.size()){ ret = true; for (unsigned i=0; iitems[i])){ ret = false; break; } } } return ret; } bool EXPR::cmpstruct(const ITEMS *it) const { const EXPR *e = (const EXPR*)it; bool ret = false; unsigned size = items.size(); if (size == e->items.size()){ ret = true; for (unsigned i=0; iitems[i])){ ret = false; break; } } } return ret; } string EXPR::getckey() const { static string space(" "); static string s123("1 2 3 "); static string sabc("'a' 'b' 'c'"); unsigned size = items.size(); string ret; if (size > 0){ // If the EXPR is just a list of numbers // or a list of string, we report a constant list // so queries that looks like this // ... where x in (1,2,3) // ... where x in (3,4) // will be equivalent unsigned numbers = 0; unsigned strings = 0; for (unsigned i=0; i 1){ if (size == numbers){ ret = s123; }else if (size == strings){ ret = sabc; } } } return ret; } /* This holds a sub-expression in parentheses */ struct PAREXPR: public EXPR{ PAREXPR(){} PAREXPR(const PAREXPR &e): EXPR(e){ } PAREXPR *dup() const{ return new PAREXPR(*this); } void reformat (string &s, SQL_OPTS &opts, bool include_null, bool &null_seen, bool &other_seen) const; void reformat (string &s, SQL_OPTS &opts) const; }; struct ORDERBY{ EXPR expr; bool asc; bool operator == (const ORDERBY &t) const { return expr.cmp(&t.expr) && asc == t.asc; } string getckey() const{ static string sasc(" asc "); static string sdesc(" desc "); return expr.getckey() + (asc ? sasc : sdesc); } }; static string orderbys_getckey(const vector &t) { string ret; unsigned size = t.size(); for (unsigned i=0; i orderby; UNION(){ all = false; } UNION(const UNION &e): EXPR(e){ all = e.all; orderby = e.orderby; } UNION *dup() const{ return new UNION(*this); } void reformat (string &s, SQL_OPTS &opts) const; }; struct NAMEALIAS{ EXPR expr; string alias; bool distinct; NAMEALIAS(){ distinct = false; } bool operator == (const NAMEALIAS &n) const { return expr.cmp(&n.expr) && alias == n.alias && distinct == n.distinct; } string getckey() const { string ret = distinct ? "distinct " : ""; ret += expr.getckey(); if (alias.size() > 0){ ret += " as " + alias; } return ret; } void reformat (string &s, SQL_OPTS &opts) const; }; enum JOINTYPE { JOIN_NORMAL, JOIN_LEFT, JOIN_RIGHT, JOIN_LEFT_OUTER, JOIN_RIGHT_OUTER }; struct TABLEALIAS{ string name; EXPR selectexpr; // This is a sub-select instead of a table name string alias; string hint; EXPR expr; JOINTYPE jtype; TABLEALIAS(){ jtype = JOIN_NORMAL; } bool operator == (const TABLEALIAS &t) const { return name == t.name && alias == t.alias && hint == t.hint && expr.cmp(&t.expr) && jtype == t.jtype; } string getckey() const { static string space(" "); static string opnpar(" ("); static string clspar(") "); static string empty(""); static string tmpname("#tmp"); static string tb_outer[]={"","left ","right ","left outer ","right outer "}; if (name[0] == '#'){ return tmpname + space + alias + opnpar + hint + clspar + tb_outer[jtype] + expr.getckey(); }else{ return name + space + alias + opnpar + hint + clspar + tb_outer[jtype] + expr.getckey(); } } void reformat (string &s, SQL_OPTS &opts) const; }; static string namealias_getckey (const vector &names) { string ret; unsigned size = names.size(); for (unsigned i=0; i &tables) { string ret; unsigned size = tables.size(); for (unsigned i=0; i &names) { TOKEN t; while (1){ // fprintf (stderr,"fields %d %s\n",t.type,t.val.c_str()); // We must support two ways for the "as" syntax // One is alias = expression // and the other is expression as alias bool as_syntax = true; NAMEALIAS one; t = p.get(); if (t.type == TOK_DISTINCT){ one.distinct = true; t = p.get(); } if (t.type == TOK_MULTIPLY){ one.expr.add (t); t = p.get(); as_syntax = false; }else if (t.type == TOK_NAME){ TOKEN tn = p.get(); if (tn.type == TOK_EQUAL){ one.alias = t.val; t = f_math (p,one.expr); as_syntax = false; }else{ p.unget(tn); p.unget(t); } }else{ p.unget(t); } if (as_syntax){ t = f_math (p,one.expr); if (t.type == TOK_AS){ t = p.get(); if (t.type != TOK_NAME){ break; } one.alias = t.val; t = p.get(); }else if (t.type == TOK_NAME){ one.alias = t.val; t = p.get(); } } names.push_back(one); if (t.type != TOK_COMMA){ break; } } return t; } static TOKEN f_logical (PARSE &p, EXPR &expr); static TOKEN f_select (PARSE &p, EXPR &expr); static TOKEN f_tables (PARSE &p, vector &tables) { // We are parsing a sequence of table1, table2, table_joins , table3 // where table_joins are table1 join table2 on expression join ... TOKEN t = p.get(); bool need_table = true; while (1){ if (need_table){ if (t.type == TOK_NAME){ TABLEALIAS table; table.name = t.val; t = p.get(); if (t.type == TOK_AS){ // "as" is optional t = p.get(); } if (t.type == TOK_NAME){ table.alias = t.val; t = p.get(); } if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type == TOK_INDEX){ t = p.get(); if (t.type == TOK_NAME){ table.hint = t.val; t = p.get(); if (t.type == TOK_CLSPAR){ t = p.get(); } } } } tables.push_back(table); }else if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type != TOK_SELECT){ break; }else{ TABLEALIAS table; t = f_select(p,table.selectexpr); if (t.type != TOK_CLSPAR){ break; } t = p.get(); if (t.type == TOK_AS){ // "as" is optional t = p.get(); } if (t.type == TOK_NAME){ table.alias = t.val; tables.push_back(table); t = p.get(); }else{ break; } } }else{ break; } need_table = false; }else{ JOINTYPE jtype = JOIN_NORMAL; if (t.type == TOK_INNER) t = p.get(); if (t.type == TOK_LEFT || t.type == TOK_RIGHT){ jtype = t.type == TOK_LEFT ? JOIN_LEFT : JOIN_RIGHT; t = p.get(); if (t.type == TOK_OUTER){ jtype = jtype == JOIN_LEFT ? JOIN_LEFT_OUTER : JOIN_RIGHT_OUTER; t = p.get(); } } if (t.type != TOK_JOIN){ break; } t = p.get(); TABLEALIAS table; if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type != TOK_SELECT){ break; }else{ t = f_select(p,table.selectexpr); if (t.type != TOK_CLSPAR){ break; } } }else if (t.type != TOK_NAME){ break; } table.jtype = jtype; table.name = t.val; t = p.get(); if (t.type == TOK_AS){ // "as" is optional t = p.get(); } if (t.type == TOK_NAME){ table.alias = t.val; t = p.get(); } if (t.type != TOK_ON){ break; } t = f_logical (p,table.expr); tables.push_back(table); } if (t.type == TOK_COMMA){ need_table = true; t = p.get(); } } return t; } static TOKEN f_where (PARSE &p, EXPR &expr); static TOKEN f_item (PARSE &p, EXPR &expr); static TOKEN f_function (PARSE &p, EXPR &expr) { TOKEN t = p.get(); if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type == TOK_CLSPAR){ t = p.get(); }else if (t.type == TOK_MULTIPLY){ // Special case for count(*) expr.add (t); t = p.get(); if (t.type == TOK_CLSPAR){ t = p.get(); } }else{ p.unget(t); while (1){ t = f_math(p,expr); //fprintf (stderr,"f_function %d %d %s\n",t.type,TOK_MULTIPLY,t.val.c_str()); if (t.type == TOK_CLSPAR){ t = p.get(); break; }else if (t.type != TOK_COMMA){ break; } } } }else{ p.setinval (t); } return t; } /* Special case for sybase function convert. The first argument is a type definition, not an expression type is type [ (n [, m ] )] The second argument is the value to convert The third argument is a optional. It is a number used for conversion style (mostly for dates) */ static TOKEN f_convert (PARSE &p, EXPR &expr) { bool ok = false; TOKEN t = p.get(); if (t.type == TOK_OPNPAR){ // First this is a type t = p.get(); if (t.type == TOK_FIELDTYPE){ t.type = TOK_STRING; //t.val = p_dquote + t.val + p_dquote; const char *defsize = strcasecmp(t.val.c_str(),"char")==0 || strcasecmp(t.val.c_str(),"varchar")==0 ? "30" : "0"; expr.add (t); t = p.get(); // PATCH, we provide to the CF_convert function a fixed amount of parameters // type size1 size2 value style // When the argument are missing from the original statements, fakes are added // so convert(char,N) becomes CF_convert char 0 0 N 0 // For char and varchar, the default is 30 if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type == TOK_NUM){ expr.add (t); t = p.get(); if (t.type == TOK_COMMA){ t = p.get(); if (t.type == TOK_NUM){ expr.add (t); t = p.get(); if (t.type == TOK_CLSPAR){ t = p.get(); ok = true; } } }else if (t.type == TOK_CLSPAR){ TOKEN fake; fake.type = TOK_NUM; fake.val = "0"; expr.add (fake); t = p.get(); ok = true; } } }else{ TOKEN fake; fake.type = TOK_NUM; fake.val = defsize; expr.add (fake); fake.val = "0"; expr.add (fake); ok = true; } if (ok){ ok = false; if (t.type == TOK_COMMA){ t = f_math(p,expr); if (t.type == TOK_COMMA){ t = f_math(p,expr); if (t.type == TOK_CLSPAR){ t = p.get(); ok = true; } }else if (t.type == TOK_CLSPAR){ TOKEN fake; fake.type = TOK_NUM; fake.val = "0"; expr.add (fake); t = p.get(); ok = true; } } } } } if (!ok){ p.setinval (t); } return t; } static TOKEN f_when (PARSE &p, EXPR &expr) { TOKEN t; EXPR condexpr; t = f_where (p,condexpr); expr.add (condexpr); if (t.type == TOK_THEN){ EXPR trueexpr; t = f_math(p,trueexpr); expr.add (trueexpr); if (t.type == TOK_WHEN){ EXPR subexpr; subexpr.add (t); t = f_when (p,subexpr); expr.add(subexpr); }else if (t.type == TOK_ELSE){ EXPR elseexpr; elseexpr.add (t); t = f_math(p,elseexpr); expr.add (elseexpr); if (t.type == TOK_END){ t = p.get(); }else{ p.setinval(t); } }else if (t.type == TOK_END){ t = p.get(); }else{ p.setinval(t); } } return t; } static TOKEN f_exists (PARSE &p, EXPR &expr) { bool ok = false; PAREXPR subexpr; TOKEN t = p.get(); if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type == TOK_SELECT){ t = f_select (p,subexpr); expr.add (subexpr); if (t.type == TOK_CLSPAR){ t = p.get(); ok = true; } } } if (!ok) p.setinval(t); return t; } static TOKEN f_item (PARSE &p, EXPR &expr) { TOKEN t; t = p.get(); if (t.type == TOK_NAME || t.type == TOK_NUM || t.type == TOK_STRING || t.type == TOK_NULL){ expr.add (t); t = p.get(); }else if (t.type == TOK_OPNPAR){ PAREXPR subexpr; t = p.get(); if (t.type == TOK_SELECT){ t = f_select (p,subexpr); }else{ p.unget(t); t = f_where (p,subexpr); } expr.add (subexpr); if (t.type == TOK_CLSPAR){ t = p.get(); } }else if (t.type == TOK_NOT){ // syntax is ambiguous here. It may be followed by TOK_EXISTS or TOK_LIKE or TOK_IN. // Only TOK_EXISTS is seen as an item while the other are part of f_compare // TO solve this, we lookup further ahead TOKEN tn = p.get(); if (tn.type != TOK_EXISTS){ p.unget(tn); }else{ expr.add (t); expr.add (tn); t = f_exists (p,expr); } }else if (t.type == TOK_EXISTS){ expr.add (t); t = f_exists (p,expr); }else if (t.type == TOK_DISTINCT){ expr.add (t); t = f_item (p,expr); }else if (t.type == TOK_FUNCTION || t.type == TOK_LEFT || t.type == TOK_RIGHT || t.type == TOK_IDENTITY){ // left and right are keyword and functions. The parser get them as keywords EXPR subexpr; t.type = TOK_FUNCTION; subexpr.add (t); if (str_nocaseeq(t.val,p_convert)){ t = f_convert (p,subexpr); }else{ t = f_function (p,subexpr); } expr.add (subexpr); }else if (t.type == TOK_CASE){ TOKEN tt = p.get(); EXPR caseexpr; if (tt.type == TOK_WHEN){ // This is "case when expr ..." caseexpr.add (t); t = f_when (p,caseexpr); }else{ // This is "case expr when expr ..." t.type = TOK_CASEval; // PATCH we change the token type. In reformat // we know how to format this properly caseexpr.add (t); p.unget(tt); t = f_math(p,caseexpr); if (t.type == TOK_WHEN){ t = f_when (p,caseexpr); } } expr.add (caseexpr); }else{ t.type = TOK_UNKNOWN; } return t; } static TOKEN f_math (PARSE &p, EXPR &expr) { TOKEN t; EXPR subexpr; while (1){ t = p.get(); if (t.type == TOK_MINUS){ subexpr.add (t); }else{ p.unget(t); } t = f_item(p,subexpr); if (t.type == TOK_NUM && t.val[0] == '-'){ // Patch because the lexical parser believe that -xxx is a number // so 1-2 becomes [1] [-2] instead of [1] [-] [2] t.val = t.val.substr(1); p.unget(t); t.type = TOK_MINUS; t.val.clear(); } if (t.type != TOK_PLUS && t.type != TOK_MINUS && t.type != TOK_DIVIDE && t.type != TOK_MODULO && t.type != TOK_MULTIPLY){ break; } subexpr.add (t); } expr.add (subexpr); return t; } static TOKEN f_compare (PARSE &p, EXPR &expr) { EXPR subexpr; // Special case for TOK_NOT. It is processed in f_item, for the syntax "not exists" // And it is processed here for the syntax not compare_expression. So we have to look ahead TOKEN t = p.get(); if (t.type == TOK_NOT){ TOKEN tn = p.get(); if (tn.type != TOK_EXISTS){ subexpr.add (t); p.unget(tn); t = f_compare (p,subexpr); expr.add (subexpr); return t; }else{ p.unget(tn); p.unget(t); } }else{ p.unget(t); } t = f_math(p,subexpr); if (t.type == TOK_EQUAL || t.type == TOK_LEFTJOIN || t.type == TOK_RIGHTJOIN || t.type == TOK_DIFFER || t.type == TOK_GREATER || t.type == TOK_GREATEREQ || t.type == TOK_LIKE || t.type == TOK_SMALLER || t.type == TOK_SMALLEREQ){ subexpr.add (t); t = f_math(p,subexpr); }else if (t.type == TOK_IS){ subexpr.add (t); t = p.get(); if (t.type == TOK_NOT){ subexpr.add (t); t = p.get(); } if (t.type == TOK_NULL){ subexpr.add (t); t = p.get(); } }else if (t.type == TOK_BETWEEN){ subexpr.add (t); t = f_math(p,subexpr); if (t.type == TOK_AND){ subexpr.add (t); t = f_math(p,subexpr); } }else if (t.type != TOK_UNKNOWN && t.type != TOK_EOF){ bool not_was_seen = false; if (t.type == TOK_NOT){ not_was_seen = true; subexpr.add (t); t = p.get(); } if (t.type == TOK_LIKE){ subexpr.add (t); t = f_math(p,subexpr); }else if (t.type == TOK_IN){ subexpr.add (t); t = p.get(); if (t.type == TOK_OPNPAR){ t = p.get(); PAREXPR subsubexpr; if (t.type == TOK_SELECT){ t = f_select (p,subsubexpr); }else{ while (1){ if (t.type != TOK_NUM && t.type != TOK_STRING && t.type != TOK_NULL){ break; } subsubexpr.add (t); t = p.get(); if (t.type != TOK_COMMA) break; t = p.get(); } } if (t.type == TOK_CLSPAR){ t = p.get(); } subexpr.add (subsubexpr); } }else if (not_was_seen){ p.setinval (t); } } expr.add (subexpr); return t; } static TOKEN f_logical (PARSE &p, EXPR &expr) { TOKEN t; while (1){ t = f_compare(p,expr); if (t.type != TOK_OR && t.type != TOK_AND){ break; } expr.add (t); } return t; } static TOKEN f_where (PARSE &p, EXPR &expr) { TOKEN t = f_logical (p,expr); return t; } static void debug_indent () { for (int i=0; i &words) { debug_indent(); fprintf (dumpfout,"%s:",title); for (unsigned i=0; i &v) { debug_indent(); fprintf (dumpfout,"%s:",title); for (unsigned i=0; i &tables) { debug_indent(); fprintf (dumpfout,"%s:",title); for (unsigned i=0; i 0){ fprintf (dumpfout," as %s,",f.alias.c_str()); }else{ fprintf (dumpfout,","); } } fprintf (dumpfout,"\n"); } static string tb_jointypes[]={"join","left join","right join","left outer join","right outer join"}; static void debug_dump (const char *title, const vector &tables) { int len = strlen(title); debug_indent(); fprintf (dumpfout,"%s:",title); if (tables.size() > 0){ for (unsigned i=0; i 0){ debug_indent(); fprintf (dumpfout,"%*s ",len,""); } fprintf (dumpfout," %s:%s",table.name.c_str(),table.alias.c_str()); if (table.hint.size()>0){ fprintf (dumpfout," (index %s)",table.hint.c_str()); } if (table.expr.size() > 0){ fprintf (dumpfout," %s ",tb_jointypes[table.jtype].c_str()); table.expr.dumptop(); }else{ fprintf (dumpfout,"\n"); } } }else{ fprintf (dumpfout,"\n"); } } static void debug_dump (const char *title, const EXPR &expr) { debug_indent(); fprintf (dumpfout,"%s:",title); expr.dumptop(); } static TOKEN f_groupby (PARSE &p, EXPR &expr) { TOKEN t; while (1){ t = f_math(p,expr); if (t.type != TOK_COMMA){ break; } } return t; } static TOKEN f_orderby (PARSE &p, vector &v) { TOKEN t; while (1){ EXPR expr; t = f_math(p,expr); ORDERBY order; order.expr = expr; order.asc = true; if (t.type == TOK_DESC){ order.asc = false; t = p.get(); }else if (t.type == TOK_ASC){ order.asc = true; t = p.get(); } v.push_back(order); if (t.type != TOK_COMMA){ break; } } return t; } struct SELECT{ vector tables; vector fields; string top,into; EXPR where; EXPR groupby; EXPR having; vector orderby; SELECT(){} SELECT(const SELECT &s){ tables = s.tables; fields = s.fields; top = s.top; into = s.into; where = s.where; having = s.having; groupby = s.groupby; orderby = s.orderby; } void dump() const{ fprintf (dumpfout,"\n"); debug_indent(); fprintf (dumpfout,"top %s\n",top.c_str()); debug_indent(); fprintf (dumpfout,"into %s\n",into.c_str()); debug_dump ("select_field",fields); debug_dump ("select_table",tables); debug_dump ("select_where",where); debug_dump ("select_groupby",groupby); debug_dump ("select_having",having); debug_dump ("select_orderby",orderby); } bool cmp(const SELECT *s2) const{ return tables == s2->tables && fields == s2->fields && top == s2->top && into == s2->into && where.cmp(&s2->where) && groupby.cmp(&s2->groupby) && having.cmp(&s2->having) && orderby == s2->orderby; } bool cmpstruct(const SELECT *s2) const{ return tables == s2->tables && fields == s2->fields && into == s2->into && where.cmpstruct(&s2->where) && groupby.cmpstruct(&s2->groupby) && having.cmpstruct(&s2->having) && orderby == s2->orderby; } string getckey() const { static string select("select "); static string from("from "); static string sorderby("orderby"); static string sgroupby("groupby"); static string space(" "); static string swhere(" where "); static string shaving (" having "); string ret = select + namealias_getckey(fields) + from + tablesalias_getckey(tables) + swhere + where.getckey(); if (orderby.size()>0){ ret += sorderby + orderbys_getckey(orderby); } if (groupby.size() > 0){ ret += groupby.getckey(); } if (having.size() > 0){ ret += shaving + having.getckey(); } return ret; } void reformat (string &s, SQL_OPTS &opts) const; }; struct ASSIGN{ string field; EXPR valexpr; ASSIGN(){} ASSIGN(const ASSIGN &u){ field = u.field; valexpr = u.valexpr; } void dump(){ debug_indent(); fprintf (dumpfout,"%s = ",field.c_str()); valexpr.dumptop(); } bool cmp(const ASSIGN *s2) const { return valexpr.cmp(&s2->valexpr) && field == s2->field; } bool cmpstruct(const ASSIGN *s2) const { return valexpr.cmpstruct(&s2->valexpr) && field == s2->field; } string getckey() const { static string space(" "); return field + space + valexpr.getckey(); } }; struct UPDATE{ bool statistics; vector sets; EXPR whereexpr; vector tables; string table,alias; UPDATE(){ statistics = false; // The same object describe normal updates and update statistics, which are ignored in reformat } UPDATE(const UPDATE &u){ statistics = u.statistics; sets = u.sets; whereexpr = u.whereexpr; tables = u.tables; table = u.table; alias = u.alias; } void dump(){ debug_indent(); fprintf (dumpfout,"update: %s\n",table.c_str()); debug_dump ("from",tables); debug_indent(); fprintf (dumpfout,"set : "); for (unsigned i=0; isets.size()){ for (unsigned i=0; isets[i])){ ret = false; break; } } if (ret){ ret = whereexpr.cmp(&s2->whereexpr) && tables == s2->tables && table == s2->table && alias == s2->alias; } }else{ ret = false; } return ret; } bool cmpstruct(const UPDATE *s2) const { bool ret = true; if (sets.size() == s2->sets.size()){ for (unsigned i=0; isets[i])){ ret = false; break; } } if (ret){ ret = whereexpr.cmpstruct(&s2->whereexpr) && tables == s2->tables && table == s2->table && alias == s2->alias; } }else{ ret = false; } return ret; } string getckey() const { static string update("update "); static string where(" where "); static string space(" "); string setckey; for (unsigned i=0; i vars; bool bulk; bool nodescribe; // Used when doing multiple inserts to the same table INSERT(){ bulk = false; nodescribe = false; } INSERT(const INSERT &u){ table = u.table; values = u.values; select = u.select; vars = u.vars; bulk = u.bulk; nodescribe = u.nodescribe; } void dump(){ debug_indent(); fprintf (dumpfout,"insert: table %s\n",table.c_str()); debug_dump("vars",vars); debug_indent(); fprintf (dumpfout,"values: "); values.dumptop(); fprintf (dumpfout,"select: "); select.dumptop(); } bool cmp(const INSERT *s2) const { return table==s2->table && values.cmp(&s2->values) && select.cmp(&s2->select) && vars == s2->vars; } bool cmpstruct(const INSERT *s2) const { return table==s2->table && values.cmpstruct(&s2->values) && select.cmpstruct(&s2->select) && vars == s2->vars; } string getckey() const { static string insert ("insert "); static string space (" "); string ret; if (bulk){ ret = insert + "bulk " + table; if (nodescribe) ret += " nodescribe"; }else{ ret = insert + table; unsigned size = vars.size(); for (unsigned i=0; itype && what == s2->what; } string getckey() const { static string drop("drop "); static string tmpwhat("#tmp"); static string space(" "); if (what[0] == '#'){ return drop + type + space + tmpwhat; }else{ return drop + type + space + what; } } void reformat (string &s, SQL_OPTS &opts) const; }; struct SET{ string name; string value; SET(){} SET(const SET &d){ name = d.name; value = d.value; } void dump(){ debug_indent(); fprintf (dumpfout,"set %s %s\n",name.c_str(),value.c_str()); } bool cmp(const SET *s2) const { return name == s2->name && value == s2->value; } string getckey() const { static string sset("set "); static string space(" "); return sset + name + space + value; } void reformat (string &s, SQL_OPTS &opts) const; }; // Supports both if and while struct IF{ bool is_while; EXPR ifexpr,beginexpr,elseexpr; IF(bool _is_while){ is_while = _is_while; } IF(const IF &d){ is_while = d.is_while; ifexpr = d.ifexpr; beginexpr = d.beginexpr; elseexpr = d.elseexpr; } void dump(){ ifexpr.dumptop(); beginexpr.dumptop(); if (!is_while) elseexpr.dumptop(); } bool cmp(const IF *s2) const { return ifexpr.cmp(&s2->ifexpr) && beginexpr.cmp(&s2->beginexpr) && elseexpr.cmp(&s2->elseexpr); } bool cmpstruct(const IF *s2) const { return ifexpr.cmpstruct(&s2->ifexpr) && beginexpr.cmpstruct(&s2->beginexpr) && elseexpr.cmpstruct(&s2->elseexpr); } string getckey() const { static string sif("if "); static string swhile("while "); static string sthen(" then "); static string selse(" else "); static string send (" end "); if (is_while){ return swhile + ifexpr.getckey() + sthen + beginexpr.getckey() + send; }else{ return sif + ifexpr.getckey() + sthen + beginexpr.getckey() + selse + elseexpr.getckey() + send; } } void reformat (vector &tbs, SQL_OPTS &opts) const; }; struct WITHOPT{ string feature; string value; WITHOPT(){} WITHOPT(const WITHOPT &d){ feature = d.feature; value = d.value; } }; enum INDEX_FIELD_ORDER{ none, asc, desc }; struct INDEX_FIELD{ string name; INDEX_FIELD_ORDER order; INDEX_FIELD(const string &_name, INDEX_FIELD_ORDER _order){ name = _name; order = _order; } }; struct CREATE_INDEX{ string index,table; vector fields; bool unique; bool clustered; vector withopts; CREATE_INDEX(){ unique = false; clustered = false; } CREATE_INDEX(const CREATE_INDEX &d){ index = d.index; table = d.table; fields = d.fields; unique = d.unique; clustered = d.clustered; } void dump(){ debug_indent(); fprintf (dumpfout,"index : %s %s %s on %s\n" ,unique ? "unique" : "" ,clustered ? "clustered" : "" ,index.c_str(),table.c_str()); debug_indent(); fprintf (dumpfout,"fields:"); for (unsigned i=0; i typeopt; bool identity; bool null; bool not_null; EXPR defexpr; EXPR checkexpr; FIELD(){ identity = false; not_null = false; null = false; } FIELD(const FIELD &f){ name = f.name; type = f.type; typeopt = f.typeopt; identity = f.identity; not_null = f.not_null; null = f.null; defexpr = f.defexpr; checkexpr = f.checkexpr; } }; struct CONSTRAINT{ string name; // 3 type of constraint // 1: expression with a check EXPR expr; // 2: foreign key struct { vector field; string table; vector field_ref; } foreign; // 3: primary key bool is_primary; bool is_clustered; vector key_names; string key_on; CONSTRAINT(){ is_primary = false; is_clustered = false; } CONSTRAINT(const CONSTRAINT &c){ name = c.name; expr = c.expr; is_primary = c.is_primary; is_clustered = c.is_clustered; key_names = c.key_names; key_on = c.key_on; foreign = c.foreign; } }; static void table_dump ( const vector &fields, const vector &constraints, const vector &checks) { debug_indent(); fprintf (dumpfout,"fields:"); for (unsigned i=0; i 0){ fprintf (dumpfout,"("); for (unsigned j=0; j 0) fprintf (dumpfout,","); fprintf (dumpfout,"%d",f.typeopt[j]); } fprintf (dumpfout,")"); } if (f.null) fprintf (dumpfout," null"); if (f.not_null) fprintf (dumpfout," not null"); if (f.identity) fprintf (dumpfout," identity"); if (f.defexpr.size() > 0){ fprintf (dumpfout," default "); f.defexpr.dump(); } if (f.checkexpr.size() > 0){ fprintf (dumpfout," check( "); f.checkexpr.dump(); fprintf (dumpfout,") "); } fprintf (dumpfout,","); } fprintf (dumpfout,"\n"); for (unsigned i=0; i0 ? "," : "",c.key_names[i].c_str()); } fprintf (dumpfout,") on %s",c.key_on.c_str()); }else if (c.foreign.field.size() > 0){ fprintf (dumpfout,"constraint %s foreign key (",c.name.c_str()); const char *sep = ""; for (unsigned j=0; j fields; vector sets; DECLARE(){} DECLARE(const DECLARE &d){ fields = d.fields; sets = d.sets; } void dump(){ debug_indent(); fprintf (dumpfout,"declare :\n"); vector constraints; vector checks; table_dump(fields,constraints,checks); debug_indent(); fprintf (dumpfout,"\n"); } bool cmp(const DECLARE*s2) const{ return false; } string getckey() const { static string declare("declare"); string ret = declare; unsigned size = fields.size(); for (unsigned i=0; i fields; vector constraints; vector checks; string lock; string onvolume; vector withopts; CREATE_TABLE(){} CREATE_TABLE(const CREATE_TABLE &d){ name = d.name; fields = d.fields; lock = d.lock; constraints = d.constraints; checks = d.checks; onvolume = d.onvolume; withopts = d.withopts; } void dump(){ debug_indent(); fprintf (dumpfout,"table : %s\n",name.c_str()); table_dump(fields,constraints,checks); debug_indent(); fprintf (dumpfout,"Options:"); if (lock.size() > 0) fprintf (dumpfout," lock %s",lock.c_str()); if (onvolume.size() > 0) fprintf (dumpfout," on %s",onvolume.c_str()); for (unsigned i=0; i 0){ ret += "("; for (unsigned j=0; j 0) ret += comma; char tmp[20]; snprintf (tmp,sizeof(tmp),"%d",f.typeopt[j]); ret += tmp; } ret += ")"; } if (f.null) ret += space + null; if (f.not_null) ret += space + not_null; if (f.identity) ret += space + identity; if (f.defexpr.size() > 0) ret += space + s_default + space + f.defexpr.getckey(); if (f.checkexpr.size() > 0) ret += space + check + space + f.checkexpr.getckey(); ret += comma; } for (unsigned i=0; i 0) ret += space + k_lock + space + lock; return ret; } void reformat (string &s, SQL_OPTS &opts) const; }; struct DELETE{ string table; vector tables; EXPR whereexpr; DELETE(){} DELETE(const DELETE &d){ table = d.table; tables = d.tables; whereexpr = d.whereexpr; } void dump(){ debug_indent(); fprintf (dumpfout,"delete: %s\n",table.c_str()); debug_dump ("from",tables); debug_indent(); fprintf (dumpfout,"where : "); whereexpr.dumptop(); } bool cmp (const DELETE *s2) const { return table == s2->table && tables == s2->tables && whereexpr.cmp(&s2->whereexpr); } bool cmpstruct (const DELETE *s2) const { return table == s2->table && tables == s2->tables && whereexpr.cmpstruct(&s2->whereexpr); } string getckey() const { static string sdelete ("delete "); static string space (" "); static string tmptable ("#tmp"); if (table[0] == '#'){ return sdelete + tmptable + space + tablesalias_getckey(tables) + space + whereexpr.getckey(); }else{ return sdelete + table + space + tablesalias_getckey(tables) + space + whereexpr.getckey(); } } void reformat (string &s, SQL_OPTS &opts) const; }; struct TRUNCATE{ string table; TRUNCATE(){} TRUNCATE(const TRUNCATE &d){ table = d.table; } void dump(){ debug_indent(); fprintf (dumpfout,"truncate table %s\n",table.c_str()); } bool cmp (const TRUNCATE *s2) const { return table == s2->table; } string getckey() const { static string truncate("truncate "); static string tmptable ("#tmp"); if (table[0] == '#'){ return truncate + table; }else{ return truncate + table; } } void reformat (string &s, SQL_OPTS &opts) const; }; /* Select accepts () around the statement. This is something we just found. In this parser, we are already processing parentheses for sub-selects. This is potentially redundant. is_opnpar is true if we know (or expect) a select, but we have not seen the TOK_SELECT yet */ static TOKEN f_select (PARSE &p, EXPR &expr, bool is_opnpar) { bool error = false; UNION uunion; TOKEN t = p.get(); while (1){ if (is_opnpar){ if (t.type != TOK_SELECT){ p.setinval(t); break; } t = p.get(); } SELECT sel; if (t.type == TOK_TOP){ t = p.get(); if (t.type != TOK_NUM){ error = true; }else{ sel.top = t.val; } }else{ p.unget(t); } if (!error){ t = f_fields(p,sel.fields); if (t.type == TOK_INTO){ t = p.get(); if (t.type == TOK_NAME){ sel.into = t.val; t = p.get(); } } // Sybase accepts a "where" even if there is no "from" if (t.type == TOK_FROM){ t = f_tables(p,sel.tables); } if (t.type == TOK_WHERE){ t = f_where(p,sel.where); } if (t.type == TOK_GROUP){ t = p.get(); if (t.type == TOK_BY){ t = f_groupby(p,sel.groupby); } } if (t.type == TOK_HAVING){ t = f_where(p,sel.having); } if (t.type == TOK_ORDER){ t = p.get(); if (t.type == TOK_BY){ t = f_orderby(p,sel.orderby); } } } if (is_opnpar){ if (t.type != TOK_CLSPAR){ p.setinval(t); break; } t = p.get(); } if (t.type == TOK_UNION){ t = p.get(); if (t.type == TOK_ALL){ uunion.all = true; t = p.get(); } if (is_opnpar){ PAREXPR pexpr; pexpr.add (sel); uunion.add (pexpr); }else{ uunion.add (sel); } is_opnpar = false; if (t.type == TOK_OPNPAR){ is_opnpar = true; }else if (t.type != TOK_SELECT){ p.setinval(t); break; } t = p.get(); }else{ if (uunion.size() > 0){ if (is_opnpar){ PAREXPR pexpr; pexpr.add (sel); uunion.add (pexpr); }else{ uunion.add (sel); } if (t.type == TOK_ORDER){ t = p.get(); if (t.type == TOK_BY){ t = f_orderby(p,uunion.orderby); } } }else{ expr.add (sel); } break; } } if (uunion.size() > 0) expr.add (uunion); return t; } static TOKEN f_select(PARSE &p, EXPR &expr) { return f_select (p,expr,false); } static TOKEN f_update(PARSE &p, EXPR &expr) { UPDATE upd; TOKEN t = p.get(); if (t.type == TOK_NAME){ upd.table = t.val; t = p.get(); if (t.type == TOK_NAME){ upd.alias = t.val; t = p.get(); } if (t.type == TOK_SET){ while (1){ t = p.get(); if (t.type == TOK_NAME){ ASSIGN a; a.field = t.val; t = p.get(); if (t.type == TOK_EQUAL){ t = f_math(p,a.valexpr); upd.sets.push_back(a); if (t.type != TOK_COMMA){ break; } }else{ p.setinval(t); break; } }else{ break; } } if (t.type == TOK_FROM){ t = f_tables(p,upd.tables); } if (t.type == TOK_WHERE){ t = f_where (p,upd.whereexpr); } } }else if (t.type == TOK_STATISTICS){ t = p.get(); upd.statistics = true; if (t.type == TOK_NAME){ upd.table = t.val; t = p.get(); if (t.type == TOK_NAME){ // PATCH: We place the index name here upd.alias = t.val; t = p.get(); } } } expr.add(upd); return t; } static TOKEN f_delete(PARSE &p, EXPR &expr) { DELETE del; TOKEN t = p.get(); // Sybase supports "delete from table" and "delete table" if (t.type == TOK_FROM){ t = p.get(); } if (t.type == TOK_NAME){ del.table = t.val; t = p.get(); if (t.type == TOK_FROM){ t = f_tables(p,del.tables); } if (t.type == TOK_WHERE){ t = f_where (p,del.whereexpr); } } expr.add (del); return t; } static TOKEN f_set(PARSE &p, EXPR &expr) { SET sset; TOKEN t = p.get(); bool is_statistics = t.type == TOK_STATISTICS; if (t.type == TOK_NAME || is_statistics){ if (is_statistics){ sset.name = p_statistics; }else{ sset.name = parser_tolower(t.val); } t = p.get(); if (t.type == TOK_NAME || t.type == TOK_NUM || t.type == TOK_ON || t.type == TOK_OFF){ sset.value = parser_tolower(t.val); t = p.get(); // ok we don't really check what it is since we are throwing this stuff away // in the reformat if (t.type == TOK_ON || t.type == TOK_OFF){ t = p.get(); } } }else if (t.type == TOK_TRANSACTION){ // SET TRANSACTION ISOLATION LEVEL READ COMMITTED static const char *tb[]={"ISOLATION","LEVEL","READ","COMMITTED"}; sset.name = parser_tolower(t.val); t = p.get(); for (unsigned i=0; i<4; i++){ if (t.type != TOK_NAME || strcasecmp(t.val.c_str(),tb[i])!=0){ p.setinval(t); break; }else{ sset.value += p_space + parser_tolower(t.val); t = p.get(); } } } expr.add (sset); return t; } static TOKEN f_subs (PARSE &p, EXPR &expr); static TOKEN f_committrans(PARSE &p, EXPR &expr) { TOKEN t = p.get(); // You can do commit transaction or just commit if (t.type == TOK_TRANSACTION){ t = p.get(); } EXPR texpr; TOKEN tt; tt.type = TOK_COMMIT; texpr.add (tt); // We just put a single token COMMIT, and it will be easy to reformat expr.add (texpr); return t; } static TOKEN f_rollbacktrans(PARSE &p, EXPR &expr) { TOKEN t = p.get(); // You can do rollback transaction or just rollback if (t.type == TOK_TRANSACTION){ t = p.get(); } EXPR texpr; TOKEN tt; tt.type = TOK_ROLLBACK; texpr.add (tt); // We just put a single token ROLLBACK, and it will be easy to reformat expr.add (texpr); return t; } static TOKEN f_begintrans(PARSE &p, EXPR &expr) { TOKEN t = p.get(); if (t.type == TOK_TRANSACTION){ EXPR texpr; texpr.add (t); // We just put a single token TRANSACTION, and it will be easy to reformat expr.add (texpr); t = p.get(); #if 0 // commit and rollback may appear inside a sub-statement, so not just here t = f_subs (p,expr); if (t.type == TOK_COMMIT){ t = f_committrans(p,expr); }else if (t.type == TOK_ROLLBACK){ t = f_rollbacktrans(p,expr); } #endif }else{ p.unget(t); t = f_subs(p,expr); if (t.type == TOK_END){ t = p.get(); } } return t; } static TOKEN f_dbcc(PARSE &p, EXPR &expr) { bool ok = false; TOKEN t = p.get(); if (t.type == TOK_TRACEON){ EXPR texpr; texpr.add (t); // We just put a single token TRACEON, and it will be easy to reformat expr.add (texpr); t = p.get(); if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type == TOK_NUM){ texpr.add (t); t = p.get(); if (t.type == TOK_CLSPAR){ ok = true; expr.add (texpr); t = p.get(); } } } } if (!ok){ p.setinval(t); } return t; } static TOKEN f_index (PARSE &p, bool unique, bool clustered, EXPR &expr) { bool ok = false; CREATE_INDEX ind; ind.unique = unique; ind.clustered = clustered; TOKEN t = p.get(); if (t.type == TOK_NAME){ ind.index = t.val; t = p.get(); if (t.type == TOK_ON){ t = p.get(); if (t.type == TOK_NAME){ ind.table = t.val; t = p.get(); if (t.type == TOK_OPNPAR){ while (1){ t = p.get(); if (t.type != TOK_NAME){ break; } INDEX_FIELD f(t.val,none); t = p.get(); if (t.type == TOK_ASC){ t = p.get(); f.order = asc; }else if (t.type == TOK_DESC){ t = p.get(); f.order = desc; } ind.fields.push_back(f); if (t.type != TOK_COMMA){ break; } } if (t.type == TOK_CLSPAR){ ok = true; t = p.get(); if (t.type == TOK_WITH){ bool with_ok = false; t = p.get(); while (1){ with_ok = false; if (t.type == TOK_WITHOPTION){ WITHOPT opt; opt.feature = t.val; t = p.get(); if (t.type == TOK_EQUAL){ t = p.get(); if (t.type == TOK_NAME || t.type == TOK_NUM){ opt.value = t.val; ind.withopts.push_back(opt); t = p.get(); with_ok = true; } }else{ ind.withopts.push_back(opt); with_ok = true; } } if (with_ok && t.type == TOK_COMMA){ t = p.get(); }else{ break; } } if (!with_ok) p.setinval(t); } } } } } } if (!ok){ p.setinval(t); } expr.add (ind); return t; } static TOKEN parser_name_list (PARSE &p, vector &list) { TOKEN t; while (1){ t = p.get(); if (t.type != TOK_NAME){ break; } list.push_back(t.val); t = p.get(); if (t.type != TOK_COMMA){ break; } } return t; } /* Extract the value in a comma separated list */ static TOKEN f_parse_vals (PARSE &p, bool &error, vector &vals) { error = false; TOKEN t; while (1){ t = p.get(); if (t.type == TOK_NAME){ vals.push_back(t.val); t = p.get(); if (t.type != TOK_COMMA){ break; } }else{ error = true; break; } } return t; } #if 0 /* Match a token sequence */ static TOKEN f_parse_seq (PARSE &p, bool &error, int nbtok, TOKTYPE tb[] , TOKEN vals[]) { error = false; TOKEN t; for (int i=0; i &fields, vector &constraints, vector &checks) { TOKEN t = p.get(); while (1){ if (t.type == TOK_NAME){ FIELD f; f.name = t.val; t = p.get(); if (t.type == TOK_FIELDTYPE || (t.type == TOK_NAME && udefs.count(t.val)>0)){ f.type = t.val; t = p.get(); if (t.type == TOK_OPNPAR){ t = p.get(); while (1){ if (t.type == TOK_NUM){ f.typeopt.push_back(atoi(t.val.c_str())); t = p.get(); if (t.type == TOK_COMMA){ t = p.get(); }else{ break; } }else{ break; } } if (t.type == TOK_CLSPAR){ t = p.get(); }else{ p.setinval(t); } } while (1){ if (t.type == TOK_NOT){ t = p.get(); if (t.type == TOK_NULL){ f.not_null = true; t = p.get(); }else{ p.setinval (t); } }else if (t.type == TOK_NULL){ f.null = true; t = p.get(); }else if (t.type == TOK_IDENTITY){ f.identity = true; t = p.get(); }else if (t.type == TOK_DEFAULT){ t = f_math(p,f.defexpr); }else if (t.type == TOK_CHECK){ t = p.get(); if (t.type == TOK_OPNPAR){ t = f_logical(p,f.checkexpr); if (t.type == TOK_CLSPAR){ t = p.get(); }else{ p.setinval(t); } }else{ p.setinval(t); } }else{ break; } } fields.push_back(f); if (t.type == TOK_COMMA){ t = p.get(); }else{ break; } }else{ p.setinval(t); break; } }else if (t.type == TOK_CONSTRAINT){ bool c_ok = false; t = p.get(); if (t.type == TOK_NAME){ CONSTRAINT c; c.name = t.val; t = p.get(); if (t.type == TOK_CHECK){ t = p.get(); if (t.type == TOK_OPNPAR){ t = f_logical(p,c.expr); if (t.type == TOK_CLSPAR){ c_ok = true; constraints.push_back(c); t = p.get(); } } }else if (t.type == TOK_PRIMARY){ t = p.get(); if (t.type == TOK_KEY){ t = p.get(); c.is_primary = true; c.is_clustered = t.type == TOK_CLUSTERED; if (c.is_clustered || t.type == TOK_NONCLUSTERED){ t = p.get(); if (t.type == TOK_OPNPAR){ t = parser_name_list (p,c.key_names); if (t.type == TOK_CLSPAR){ t = p.get(); if (t.type == TOK_ON){ t = p.get(); if (t.type == TOK_STRING){ c.key_on = t.val; c_ok = true; constraints.push_back(c); t = p.get(); } }else{ c_ok = true; constraints.push_back(c); } } } } } }else if (t.type == TOK_FOREIGN){ t = p.get(); if (t.type == TOK_KEY){ t = p.get(); if (t.type == TOK_OPNPAR){ bool error; t = f_parse_vals (p,error,c.foreign.field); if (!error && t.type == TOK_CLSPAR){ t = p.get(); if (t.type == TOK_REFERENCES){ t = p.get(); if (t.type == TOK_NAME){ t = p.get(); if (t.type == TOK_OPNPAR){ t = f_parse_vals(p,error,c.foreign.field_ref); if (!error && t.type == TOK_CLSPAR){ t = p.get(); c_ok = true; constraints.push_back(c); } } } } } } } } } if (!c_ok) p.setinval(t); if (t.type == TOK_COMMA){ t = p.get(); }else{ break; } }else if (t.type == TOK_CHECK){ bool c_ok = false; t = p.get(); if (t.type == TOK_OPNPAR){ EXPR chkexpr; t = f_logical(p,chkexpr); if (t.type == TOK_CLSPAR){ c_ok = true; checks.push_back(chkexpr); t = p.get(); } } if (!c_ok) p.setinval(t); if (t.type == TOK_COMMA){ t = p.get(); }else{ break; } }else{ break; } } return t; } static TOKEN f_ctable (PARSE &p, EXPR &expr) { TOKEN t = p.get(); bool ok = false; if (t.type == TOK_NAME){ CREATE_TABLE ctable; ctable.name = t.val; t = p.get(); if (t.type == TOK_OPNPAR){ t = f_ctable_fields (p,expr,ctable.fields,ctable.constraints,ctable.checks); if (t.type == TOK_CLSPAR){ t = p.get(); while (1){ if (t.type == TOK_LOCK){ t = p.get(); if (t.type == TOK_NAME){ ctable.lock = t.val; t = p.get(); }else{ p.setinval(t); } }else if (t.type == TOK_WITH){ // PATCH create table() with dml_logging = full // This stuff is skipped/ignored bool with_ok = false; t = p.get(); while (1){ with_ok = false; if (t.type == TOK_WITHOPTION){ WITHOPT opt; opt.feature = t.val; t = p.get(); if (t.type == TOK_EQUAL){ t = p.get(); if (t.type == TOK_NAME || t.type == TOK_NUM){ opt.value = t.val; ctable.withopts.push_back(opt); t = p.get(); with_ok = true; } } } if (with_ok && t.type == TOK_COMMA){ t = p.get(); }else{ break; } } if (!with_ok) p.setinval(t); }else if (t.type == TOK_ON){ // PATCH, do nothing with on 'default' t = p.get(); if (t.type == TOK_STRING){ ctable.onvolume = t.val; t = p.get(); }else{ p.setinval(t); } }else if (t.type == TOK_PARTITION){ // PATCH do nothing with partition bool partition_ok = false; t = p.get(); if (t.type == TOK_BY){ t = p.get(); if (t.type == TOK_ROUNDROBIN){ t = p.get(); if (t.type == TOK_NUM){ t = p.get(); partition_ok = true; } } } if (!partition_ok){ p.setinval(t); } }else{ break; } } expr.add (ctable); ok = true; } } } if (!ok) p.setinval(t); return t; } static TOKEN f_subs (PARSE &p,EXPR &expr); static TOKEN f_trigger_if (PARSE &p, string &fieldname) { bool ok = false; TOKEN t = p.get(); if (t.type == TOK_IF){ t = p.get(); if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type == TOK_UPDATE){ t = p.get(); if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type == TOK_NAME){ fieldname = t.val; t = p.get(); if (t.type == TOK_CLSPAR){ t = p.get(); if (t.type == TOK_CLSPAR){ t = p.get(); ok = true; } } } } } } } if (!ok) p.setinval (t); return t; } static TOKEN f_trigger (PARSE &p, EXPR &expr) { TOKEN t = p.get(); bool ok = false; if (t.type == TOK_NAME){ CREATE_TRIGGER ctrig; ctrig.name = t.val; t = p.get(); if (t.type == TOK_ON){ t = p.get(); if (t.type == TOK_NAME){ ctrig.table = t.val; t = p.get(); if (t.type == TOK_FOR){ while (1){ t = p.get(); if (t.type == TOK_UPDATE){ ctrig.update = true; t = p.get(); if (t.type != TOK_COMMA) break; }else if (t.type == TOK_INSERT){ ctrig.insert = true; t = p.get(); if (t.type != TOK_COMMA) break; }else{ break; } } if (t.type == TOK_AS){ t = f_trigger_if (p,ctrig.fieldname); if (t.type == TOK_BEGIN){ t = f_subs(p,ctrig.expr); if (t.type == TOK_END){ ok = true; expr.add (ctrig); t = p.get(); } } } } } } } if (!ok) p.setinval(t); return t; } static TOKEN f_create(PARSE &p, EXPR &expr) { TOKEN t = p.get(); bool unique = false; bool clustered = false; bool index_opts_seen = false; while (1){ if (t.type == TOK_UNIQUE){ t = p.get(); unique = true; index_opts_seen = true; }else if (t.type == TOK_CLUSTERED){ t = p.get(); clustered = true; index_opts_seen = true; }else if (t.type == TOK_NONCLUSTERED){ t = p.get(); clustered = false; index_opts_seen = true; }else{ break; } } if (t.type == TOK_INDEX){ t = f_index (p,unique,clustered,expr); }else if (!index_opts_seen && t.type == TOK_TABLE){ t = f_ctable (p,expr); }else if (!index_opts_seen && t.type == TOK_TRIGGER){ t = f_trigger (p,expr); }else{ p.setinval(t); } return t; } static TOKEN f_truncate(PARSE &p, EXPR &expr) { TRUNCATE trunc; TOKEN t = p.get(); if (t.type == TOK_TABLE){ t = p.get(); if (t.type == TOK_NAME){ trunc.table = t.val; t = p.get(); }else{ p.setinval(t); } } expr.add (trunc); return t; } static TOKEN f_drop(PARSE &p, EXPR &expr) { TOKEN t = p.get(); DROP drop; if (t.type == TOK_TABLE || t.type == TOK_INDEX){ drop.type = t.val; t = p.get(); if (t.type == TOK_NAME){ drop.what = t.val; t = p.get(); }else{ p.setinval(t); } } expr.add (drop); return t; } static TOKEN f_insert (PARSE &p, EXPR &expr) { TOKEN t = p.get(); INSERT ins; if (t.type == TOK_INTO){ // into is optional t = p.get(); } if (t.type == TOK_BULK){ t = p.get(); if (t.type == TOK_NAME){ ins.table = t.val; ins.bulk = true; t = p.get(); if (t.type == TOK_WITH){ t = p.get(); if (t.type == TOK_NODESCRIBE){ ins.nodescribe = true; t = p.get(); }else{ p.setinval(t); } } }else{ p.setinval(t); } }else if (t.type == TOK_NAME){ ins.table = t.val; t = p.get(); if (t.type == TOK_OPNPAR){ while (1){ t = p.get(); if (t.type != TOK_NAME){ break; } ins.vars.push_back(t.val); t = p.get(); if (t.type != TOK_COMMA){ break; } } if (t.type == TOK_CLSPAR){ t = p.get(); }else{ p.setinval(t); } } if (t.type == TOK_VALUES){ t = p.get(); if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type != TOK_CLSPAR){ p.unget(t); while (1){ t = f_math(p,ins.values); if (t.type != TOK_COMMA){ break; } } } if (t.type == TOK_CLSPAR){ t = p.get(); } } }else if (t.type == TOK_OPNPAR){ t = p.get(); if (t.type == TOK_SELECT){ t = f_select(p,ins.select); if (t.type == TOK_CLSPAR){ t = p.get(); } } }else if (t.type == TOK_SELECT){ t = f_select (p,ins.select); }else{ p.setinval(t); } } expr.add (ins); return t; } struct USE: public ITEMS{ string table; USE(){} USE(const USE &u){ table = u.table; } USE *dup()const { return new USE(*this); } void dump() const { debug_indent(); fprintf (dumpfout,"use %s\n",table.c_str()); } bool cmp (const ITEMS *it) const { USE *uit = (USE*) it; return table == uit->table; } string getckey() const { static string use ("use "); return use + table; } void reformat (string &s, SQL_OPTS &opts) const; }; struct GRANT: public ITEMS{ TOKTYPE operation; string object; string name; GRANT(){} GRANT(const GRANT &u){ object = u.object; name = u.name; operation = u.operation; } GRANT *dup()const { return new GRANT(*this); } void dump() const { debug_indent(); // PATCH we only support select const char *oper = "select"; fprintf (dumpfout,"grant %s %s %s\n",oper,object.c_str(),name.c_str()); } bool cmp (const ITEMS *it) const { GRANT *uit = (GRANT*) it; return operation == uit->operation && object == uit->object && name == uit->name; } string getckey() const { static string grant ("grant "); static string space(" "); static string select("select "); return grant + select + object + space + name; } void reformat (string &s, SQL_OPTS &opts) const; }; struct ALTER: public ITEMS{ string table; vector drops; struct { vector fields; vector constraints; vector checks; }add; struct { vector fields; vector constraints; vector checks; }mod; ALTER(){} ALTER(const ALTER &u){ table = u.table; drops = u.drops; add = u.add; mod = u.mod; } ALTER *dup()const { return new ALTER(*this); } void dump() const { debug_indent(); fprintf (dumpfout,"alter %s ",table.c_str()); if (drops.size() > 0){ for (unsigned i=0; itable){ ret = true; } return ret; } string getckey() const { static string alter ("alter "); static string space(" "); string ret = alter + space + table + space; return ret; } void reformat (string &s, SQL_OPTS &opts) const; }; struct EXEC: public ITEMS{ string name; vector parms; EXEC(){} EXEC(const EXEC &u){ name = u.name; parms = u.parms; } EXEC *dup()const { return new EXEC(*this); } void dump() const { debug_indent(); fprintf (dumpfout,"exec %s ",name.c_str()); for (unsigned i=0; i0) fprintf (dumpfout,","); fprintf (dumpfout,"%s",parms[i].c_str()); } fprintf (dumpfout,"\n"); } bool cmp (const ITEMS *it) const { EXEC *uit = (EXEC*) it; bool ret = false; if (name == uit->name && parms.size() == uit->parms.size()){ ret = true; for (unsigned i=0; iparms[i]){ ret = false; break; } } } return ret; } string getckey() const { static string exec ("exec "); static string space(" "); string ret = exec + space + name + space; for (unsigned i=0; i0) ret += ","; ret += parms[i]; } return ret; } void reformat (string &s, SQL_OPTS &opts) const; }; static TOKEN f_use (PARSE &p, EXPR &expr) { USE use; TOKEN t = p.get(); if (t.type == TOK_NAME){ use.table = t.val; t = p.get(); }else{ p.setinval(t); } expr.add (use); return t; } static TOKEN f_grant (PARSE &p, EXPR &expr) { GRANT grant; TOKEN t = p.get(); bool ok = false; // PATCH only select supported in GRANT if (t.type == TOK_SELECT || t.type == TOK_INSERT || t.type == TOK_DELETE || t.type == TOK_UPDATE || t.type == TOK_REFERENCES){ grant.operation = t.type; t = p.get(); if (t.type == TOK_ON){ t = p.get(); if (t.type == TOK_NAME){ grant.object = t.val; t = p.get(); if (t.type == TOK_TO){ t = p.get(); if (t.type == TOK_NAME){ grant.name = t.val; t = p.get(); ok = true; expr.add (grant); } } } } } if (!ok){ p.setinval(t); } return t; } static TOKEN f_exec (PARSE &p, EXPR &expr) { EXEC ex; TOKEN t = p.get(); if (t.type == TOK_NAME){ ex.name = t.val; while (1){ t = p.get(); if (t.type == TOK_NAME || t.type == TOK_STRING || t.type == TOK_NUM || t.type == TOK_NULL){ ex.parms.push_back(t.val); t = p.get(); // PATCH for storeproc, we simply skip the OUTPUT token if (t.type == TOK_OUTPUT) t = p.get(); if (t.type != TOK_COMMA){ break; } }else{ break; } } expr.add (ex); } return t; } static TOKEN f_alter (PARSE &p, EXPR &expr) { ALTER alt; bool c_ok = false; TOKEN t = p.get(); if (t.type == TOK_TABLE){ t = p.get(); if (t.type == TOK_NAME){ alt.table = t.val; t = p.get(); while (1){ if (t.type == TOK_ADD){ t = f_ctable_fields(p,expr,alt.add.fields,alt.add.constraints,alt.add.checks); c_ok = true; }else if (t.type == TOK_MODIFY){ t = f_ctable_fields(p,expr,alt.mod.fields,alt.mod.constraints,alt.mod.checks); c_ok = true; }else if (t.type == TOK_DROP){ t = p.get(); if (t.type == TOK_NAME){ alt.drops.push_back(t.val); t = p.get(); c_ok = true; }else{ c_ok = false; break; } }else{ break; } } expr.add (alt); } } if (!c_ok) p.setinval(t); return t; } static TOKEN f_declare (PARSE &p, EXPR &expr) { vector constraints; vector checks; DECLARE decl; TOKEN t = f_ctable_fields (p,expr,decl.fields,constraints,checks); if (t.type == TOK_SET){ t = p.get(); while (1){ if (t.type != TOK_NAME){ break; }else{ ASSIGN a; a.field = t.val; t = p.get(); if (t.type == TOK_EQUAL){ t = f_math(p,a.valexpr); decl.sets.push_back(a); if (t.type != TOK_COMMA){ break; } t = p.get(); }else{ break; } } } } expr.add (decl); return t; } static TOKEN f_if(bool is_while, PARSE &p, EXPR &expr); static TOKEN f_sub (PARSE &p,EXPR &expr, bool &one) { one = true; TOKEN t = p.get(); { if (t.type == TOK_SELECT){ t = f_select (p,expr); }else if (t.type == TOK_OPNPAR){ t = f_select (p,expr,true); }else if (t.type == TOK_UPDATE){ t = f_update(p,expr); }else if (t.type == TOK_INSERT){ t = f_insert(p,expr); }else if (t.type == TOK_DELETE){ t = f_delete(p,expr); }else if (t.type == TOK_TRUNCATE){ t = f_truncate(p,expr); }else if (t.type == TOK_CREATE){ t = f_create(p,expr); }else if (t.type == TOK_DROP){ t = f_drop(p,expr); }else if (t.type == TOK_SET){ t = f_set(p,expr); }else if (t.type == TOK_IF){ t = f_if(false,p,expr); }else if (t.type == TOK_WHILE){ t = f_if(true,p,expr); }else if (t.type == TOK_USE){ t = f_use(p,expr); }else if (t.type == TOK_GRANT){ t = f_grant(p,expr); }else if (t.type == TOK_SETUSER){ // PATCH, setuser are skipped pusage.setuser++; t = p.get(); if (t.type == TOK_STRING){ t = p.get(); } }else if (t.type == TOK_EXEC){ t = f_exec(p,expr); }else if (t.type == TOK_ALTER){ t = f_alter(p,expr); }else if (t.type == TOK_PRINT){ EXPR pexpr; pexpr.add (t); t = p.get(); if (t.type == TOK_STRING){ pexpr.add (t); expr.add (pexpr); t = p.get(); }else{ p.setinval(t); } }else if (t.type == TOK_SP_HELPSEGMENT){ EXPR pexpr; pexpr.add (t); t = p.get(); if (t.type == TOK_STRING){ pexpr.add (t); expr.add (pexpr); t = p.get(); }else{ p.setinval(t); } }else if (t.type == TOK_SP_DBOPTION){ EXPR pexpr; pexpr.add (t); t = p.get(); bool ok = false; if (t.type == TOK_STRING){ pexpr.add (t); t = p.get(); if (t.type == TOK_COMMA){ t = p.get(); if (t.type == TOK_STRING){ pexpr.add (t); t = p.get(); if (t.type == TOK_COMMA){ t = p.get(); if (t.type == TOK_NAME){ pexpr.add (t); expr.add (pexpr); t = p.get(); ok = true; } } } } } if (!ok) p.setinval(t); }else if (t.type == TOK_SP_CONFIGURE){ EXPR pexpr; pexpr.add (t); t = p.get(); bool ok = false; if (t.type == TOK_STRING){ pexpr.add (t); t = p.get(); if (t.type == TOK_COMMA){ t = p.get(); if (t.type == TOK_NUM){ pexpr.add (t); expr.add (pexpr); t = p.get(); ok = true; } } } if (!ok) p.setinval(t); }else if (t.type == TOK_CHECKPOINT){ EXPR pexpr; pexpr.add (t); t = p.get(); expr.add (pexpr); }else if (t.type == TOK_READTEXT){ bool ok = false; EXPR pexpr; pexpr.add (t); t = p.get(); if (t.type == TOK_NAME){ pexpr.add (t); t = p.get(); if (t.type == TOK_NUM){ pexpr.add (t); t = p.get(); if (t.type == TOK_NUM){ pexpr.add (t); t = p.get(); if (t.type == TOK_NUM){ pexpr.add (t); t = p.get(); expr.add (pexpr); ok = true; } } } } if (!ok) p.setinval(t); }else if (t.type == TOK_WRITETEXT){ bool ok = false; EXPR pexpr; pexpr.add (t); t = p.get(); if (t.type == TOK_BULK){ pexpr.add (t); t = p.get(); if (t.type == TOK_NAME){ pexpr.add (t); t = p.get(); if (t.type == TOK_NUM){ pexpr.add (t); t = p.get(); if (t.type == TOK_WITH){ pexpr.add(t); t = p.get(); if (t.type == TOK_NAME){ pexpr.add(t); t = p.get(); expr.add (pexpr); ok = true; } } } } } if (!ok) p.setinval(t); }else if (t.type == TOK_WAITFOR){ bool ok = false; EXPR pexpr; pexpr.add (t); t = p.get(); if (t.type == TOK_DELAY){ pexpr.add (t); t = p.get(); if (t.type == TOK_STRING){ pexpr.add (t); expr.add (pexpr); t = p.get(); ok = true; } } if (!ok){ p.setinval(t); } }else if (t.type == TOK_DBCC){ t = f_dbcc(p,expr); }else if (t.type == TOK_BEGIN){ t = f_begintrans(p,expr); }else if (t.type == TOK_COMMIT){ t = f_committrans(p,expr); }else if (t.type == TOK_ROLLBACK){ t = f_rollbacktrans(p,expr); }else if (t.type == TOK_DECLARE){ t = f_declare(p,expr); }else{ one = false; } } return t; } static TOKEN f_subs (PARSE &p,EXPR &expr) { TOKEN t; while (1){ bool one = false; t = f_sub(p,expr,one); if (!one) break; p.unget(t); } return t; } static TOKEN f_if (bool is_while,PARSE &p, EXPR &expr) { TOKEN t; IF iff(is_while); t = f_logical(p,iff.ifexpr); if (t.type == TOK_BEGIN){ t = f_subs(p,iff.beginexpr); if (t.type == TOK_END){ t = p.get(); if (!is_while && t.type == TOK_ELSE){ t = p.get(); if (t.type == TOK_BEGIN){ t = f_subs(p,iff.elseexpr); if (t.type == TOK_END){ t = p.get(); }else{ p.setinval(t); } } } }else{ p.setinval(t); } }else{ p.unget(t); bool one = false; t = f_sub(p,iff.beginexpr,one); if (t.type == TOK_ELSE){ t = f_sub(p,iff.elseexpr,one); } } expr.add (iff); return t; } ITEM::ITEM(TOKEN &_t) { init(); type = IS_TOKEN; t = _t; } ITEM::ITEM(ITEMS &_i) { init(); type = IS_ITEMS; u.items = _i.dup(); } ITEM::ITEM(EXPR &_i) { init(); type = IS_EXPR; u.expr = new EXPR(_i); } ITEM::ITEM(PAREXPR &_i) { init(); type = IS_PAREXPR; u.parexpr = new PAREXPR(_i); } ITEM::ITEM(UNION &_i) { init(); type = IS_UNION; u.uunion = new UNION(_i); } ITEM::ITEM(USE &_i) { init(); type = IS_USE; u.use = new USE (_i); } ITEM::ITEM(GRANT &_i) { init(); type = IS_GRANT; u.grant = new GRANT (_i); } ITEM::ITEM(EXEC &_i) { init(); type = IS_EXEC; u.exec = new EXEC (_i); } ITEM::ITEM(ALTER &_i) { init(); type = IS_ALTER; u.alter = new ALTER (_i); } ITEM::ITEM(SELECT &_s) { init(); type = IS_SELECT; u.select = new SELECT(_s); } ITEM::ITEM(INSERT &_i) { init(); type = IS_INSERT; u.insert = new INSERT(_i); } ITEM::ITEM(UPDATE &_u) { init(); type = IS_UPDATE; u.update = new UPDATE (_u); } ITEM::ITEM(DROP &_d) { init(); type = IS_DROP; u.drop = new DROP (_d); } ITEM::ITEM(SET &_s) { init(); type = IS_SET; u.sset = new SET (_s); } ITEM::ITEM(IF &_i) { init(); type = IS_IF; u.iif = new IF (_i); } ITEM::ITEM(CREATE_TABLE &_c) { init(); type = IS_CREATE_TABLE; u.ctable = new CREATE_TABLE (_c); } ITEM::ITEM(CREATE_INDEX &_c) { init(); type = IS_CREATE_INDEX; u.cindex = new CREATE_INDEX (_c); } ITEM::ITEM(CREATE_TRIGGER &_c) { init(); type = IS_CREATE_TRIGGER; u.ctrig = new CREATE_TRIGGER (_c); } ITEM::ITEM(DELETE &_t) { init(); type = IS_DELETE; u.del = new DELETE (_t); } ITEM::ITEM(TRUNCATE &_t) { init(); type = IS_TRUNCATE; u.truncate = new TRUNCATE (_t); } ITEM::ITEM(DECLARE &_t) { init(); type = IS_DECLARE; u.declare = new DECLARE (_t); } ITEM::ITEM(const ITEM &_i) { init(); type = _i.type; switch(type){ case IS_UNKNOWN: break; case IS_TOKEN: t = _i.t; break; case IS_ITEMS: u.items = _i.u.items->dup(); break; case IS_EXPR: u.expr = new EXPR(*_i.u.expr); break; case IS_PAREXPR: u.parexpr = new PAREXPR(*_i.u.parexpr); break; case IS_UNION: u.uunion = new UNION(*_i.u.uunion); break; case IS_USE: u.use = new USE(*_i.u.use); break; case IS_GRANT: u.grant = new GRANT(*_i.u.grant); break; case IS_EXEC: u.exec = new EXEC(*_i.u.exec); break; case IS_ALTER: u.alter = new ALTER(*_i.u.alter); break; case IS_SELECT: u.select = new SELECT(*_i.u.select); break; case IS_INSERT: u.insert = new INSERT(*_i.u.insert); break; case IS_UPDATE: u.update = new UPDATE(*_i.u.update); break; case IS_DROP: u.drop = new DROP(*_i.u.drop); break; case IS_SET: u.sset = new SET (*_i.u.sset); break; case IS_IF: u.iif = new IF (*_i.u.iif); break; case IS_CREATE_TABLE: u.ctable = new CREATE_TABLE(*_i.u.ctable); break; case IS_CREATE_INDEX: u.cindex = new CREATE_INDEX (*_i.u.cindex); break; case IS_CREATE_TRIGGER: u.ctrig = new CREATE_TRIGGER (*_i.u.ctrig); break; case IS_DELETE: u.del = new DELETE(*_i.u.del); break; case IS_TRUNCATE: u.truncate = new TRUNCATE (*_i.u.truncate); break; case IS_DECLARE: u.declare = new DECLARE (*_i.u.declare); break; } } ITEM::~ITEM() { switch(type){ case IS_UNKNOWN: case IS_TOKEN: break; case IS_ITEMS: delete u.items; break; case IS_EXPR: delete u.expr; break; case IS_PAREXPR: delete u.parexpr; break; case IS_UNION: delete u.uunion; break; case IS_USE: delete u.use; break; case IS_GRANT: delete u.grant; break; case IS_EXEC: delete u.exec; break; case IS_ALTER: delete u.alter; break; case IS_SELECT: delete u.select; break; case IS_INSERT: delete u.insert; break; case IS_UPDATE: delete u.update; break; case IS_DROP: delete u.drop; break; case IS_SET: delete u.sset; break; case IS_IF: delete u.iif; break; case IS_CREATE_TABLE: delete u.ctable; break; case IS_CREATE_INDEX: delete u.cindex; break; case IS_CREATE_TRIGGER: delete u.ctrig; break; case IS_DELETE: delete u.del; break; case IS_TRUNCATE: delete u.truncate; break; case IS_DECLARE: delete u.declare; break; } } void ITEM::dump() const { switch(type){ case IS_ITEMS: case IS_EXPR: case IS_USE: fprintf (dumpfout," ["); u.items->dump(); fprintf (dumpfout," ]"); break; case IS_PAREXPR: fprintf (dumpfout," ("); u.items->dump(); fprintf (dumpfout," )"); break; case IS_UNION: fprintf (dumpfout," union["); u.items->dump(); fprintf (dumpfout," ]"); break; case IS_SELECT: fprintf (dumpfout," ["); u.select->dump (); fprintf (dumpfout," ]"); break; case IS_INSERT: fprintf (dumpfout," ["); u.insert->dump(); fprintf (dumpfout," ]"); break; case IS_UPDATE: fprintf (dumpfout," ["); u.update->dump(); fprintf (dumpfout," ]"); break; case IS_DROP: fprintf (dumpfout," ["); u.drop->dump(); fprintf (dumpfout," ]"); break; case IS_SET: fprintf (dumpfout," ["); u.sset->dump(); fprintf (dumpfout," ]"); break; case IS_IF: fprintf (dumpfout," ["); u.iif->dump(); fprintf (dumpfout," ]"); break; case IS_CREATE_TABLE: fprintf (dumpfout," ["); u.ctable->dump(); fprintf (dumpfout," ]"); break; case IS_CREATE_INDEX: fprintf (dumpfout," ["); u.cindex->dump(); fprintf (dumpfout," ]"); break; case IS_CREATE_TRIGGER: fprintf (dumpfout," ["); u.ctrig->dump(); fprintf (dumpfout," ]"); break; case IS_DELETE: fprintf (dumpfout," ["); u.del->dump(); fprintf (dumpfout," ]"); break; case IS_TRUNCATE: fprintf (dumpfout," ["); u.truncate->dump(); fprintf (dumpfout," ]"); break; case IS_DECLARE: fprintf (dumpfout," ["); u.declare->dump(); fprintf (dumpfout," ]"); break; case IS_GRANT: fprintf (dumpfout," ["); u.grant->dump(); fprintf (dumpfout," ]"); break; case IS_ALTER: fprintf (dumpfout," ["); u.alter->dump(); fprintf (dumpfout," ]"); break; case IS_EXEC: fprintf (dumpfout," ["); u.exec->dump(); fprintf (dumpfout," ]"); break; case IS_TOKEN: if (t.val.size() > 0){ fprintf (dumpfout," %s",t.val.c_str()); }else{ fprintf (dumpfout," %s",tokens[t.type]); } break; case IS_UNKNOWN: fprintf (dumpfout," IS_UNKNOWN???"); } } bool ITEM::cmp(const ITEM &it) const { bool ret = false; if (type == it.type){ switch(type){ case IS_UNKNOWN: break; case IS_TOKEN: ret = t.type == it.t.type && t.val == it.t.val; break; case IS_ITEMS: ret = u.items->cmp(it.u.items); break; case IS_EXPR: ret = u.expr->cmp(it.u.expr); break; case IS_PAREXPR: ret = u.parexpr->cmp(it.u.parexpr); break; case IS_UNION: ret = u.uunion->cmp(it.u.uunion); break; case IS_USE: ret = u.use->cmp(it.u.use); break; case IS_GRANT: ret = u.grant->cmp(it.u.grant); break; case IS_EXEC: ret = u.exec->cmp(it.u.exec); break; case IS_ALTER: ret = u.alter->cmp(it.u.alter); break; case IS_SELECT: ret = u.select->cmp(it.u.select); break; case IS_INSERT: ret = u.insert->cmp(it.u.insert); break; case IS_UPDATE: ret = u.update->cmp(it.u.update); break; case IS_DROP: ret = u.drop->cmp(it.u.drop); break; case IS_SET: ret = u.sset->cmp(it.u.sset); break; case IS_IF: ret = u.iif->cmp(it.u.iif); break; case IS_CREATE_TABLE: ret = u.ctable->cmp(it.u.ctable); break; case IS_CREATE_INDEX: ret = u.cindex->cmp(it.u.cindex); break; case IS_CREATE_TRIGGER: ret = u.ctrig->cmp(it.u.ctrig); break; case IS_DELETE: ret = u.del->cmp(it.u.del); break; case IS_TRUNCATE: ret = u.truncate->cmp(it.u.truncate); break; case IS_DECLARE: ret = u.declare->cmp(it.u.declare); break; } } return ret; } bool ITEM::cmpstruct(const ITEM &it) const { bool ret = false; if (type == it.type){ switch(type){ case IS_UNKNOWN: break; case IS_TOKEN: ret = t.type == it.t.type; if (ret){ if (t.type != TOK_NUM && t.type != TOK_STRING){ ret = t.val == it.t.val; } } break; case IS_ITEMS: ret = u.items->cmp(it.u.items); break; case IS_EXPR: ret = u.expr->cmpstruct(it.u.expr); break; case IS_PAREXPR: ret = u.parexpr->cmpstruct(it.u.parexpr); break; case IS_UNION: ret = u.uunion->cmpstruct(it.u.uunion); break; case IS_USE: ret = u.use->cmp(it.u.use); break; case IS_GRANT: ret = u.grant->cmp(it.u.grant); break; case IS_ALTER: ret = u.alter->cmp(it.u.alter); break; case IS_EXEC: ret = u.exec->cmp(it.u.exec); break; case IS_SELECT: ret = u.select->cmpstruct(it.u.select); break; case IS_INSERT: ret = u.insert->cmpstruct(it.u.insert); break; case IS_UPDATE: ret = u.update->cmpstruct(it.u.update); break; case IS_DROP: ret = u.drop->cmp(it.u.drop); break; case IS_SET: ret = u.sset->cmp(it.u.sset); break; case IS_IF: ret = u.iif->cmpstruct (it.u.iif); break; case IS_CREATE_TABLE: ret = u.ctable->cmp(it.u.ctable); break; case IS_CREATE_INDEX: ret = u.cindex->cmp(it.u.cindex); break; case IS_CREATE_TRIGGER: ret = u.ctrig->cmp(it.u.ctrig); break; case IS_DELETE: ret = u.del->cmpstruct(it.u.del); break; case IS_TRUNCATE: ret = u.truncate->cmp(it.u.truncate); break; case IS_DECLARE: ret = u.declare->cmp(it.u.declare); break; } } return ret; } string ITEM::getckey() const { string ret; switch(type){ case IS_UNKNOWN: break; case IS_TOKEN: { static string zero("0"); static string empty("\"\""); if (t.type == TOK_NUM){ ret = zero; }else if (t.type == TOK_STRING){ ret = empty; }else{ char car = t.val[0]; if (car != '\0'){ ret = t.val; }else{ ret = tokens[t.type]; } } } break; case IS_ITEMS: ret = "i"; break; case IS_EXPR: ret = u.expr->getckey(); break; case IS_PAREXPR: ret = u.parexpr->getckey(); break; case IS_UNION: ret = u.uunion->getckey(); break; case IS_USE: ret = u.use->getckey(); break; case IS_GRANT: ret = u.grant->getckey(); break; case IS_EXEC: ret = u.exec->getckey(); break; case IS_ALTER: ret = u.alter->getckey(); break; case IS_SELECT: ret = u.select->getckey(); break; case IS_INSERT: ret = u.insert->getckey(); break; case IS_UPDATE: ret = u.update->getckey(); break; case IS_DROP: ret = u.drop->getckey(); break; case IS_SET: ret = u.sset->getckey(); break; case IS_IF: ret = u.iif->getckey(); break; case IS_CREATE_TABLE: ret = u.ctable->getckey(); break; case IS_CREATE_INDEX: ret = u.cindex->getckey(); break; case IS_CREATE_TRIGGER: ret = u.ctrig->getckey(); break; case IS_DELETE: ret = u.del->getckey(); break; case IS_TRUNCATE: ret = u.truncate->getckey(); break; case IS_DECLARE: ret = u.declare->getckey(); break; } return ret; } int f_main (PARSE &p, EXPR &expr) { TOKEN t = f_subs(p,expr); return t.type == TOK_EOF ? 0 : -1; } bool EXPR::not_empty() const { return items.size() > 0; } /* =================== Reformat to MySQL ==================== */ static string p_rowcount("rowcount"); static string p_quoted_identifier("quoted_identifier"); static string p_textsize("textsize"); static string p_nocount("nocount"); static string p_mysql("mysql"); static string p_dateformat("dateformat"); static string p_datediff ("datediff"); static string p_zero("0"); static string p_index("index"); static string p_isnull("isnull"); static string p_ifnull("ifnull"); static string p_chained("chained"); static string p_ansinull("ansinull"); static string p_arithignore("arithignore"); static string p_getdate("getdate"); static string p_replace("replace"); static string p_str_replace("str_replace"); static string p_now("now"); static string p_current_timestamp("current_timestamp"); static string p_bit("bit"); static string p_on("on"); static string p_datalength("datalength"); static string p_char_length("char_length"); static string p_length("length"); static string p_textptr("textptr"); static string p_object_name("object_name"); static string p_object_id("object_id"); static string p_CF_ ("CF_"); static string p_date_format ("date_format"); static string p_dd ("dd"); static string p_day ("day"); static string p_mm("mm"); static string p_month("month"); static string p_year("year"); static string p_yy("yy"); static string p_wk ("wk"); static string p_week ("week"); static string p_dy ("dy"); static string p_dayofyear ("dayofyear"); static string p_dw ("dw"); static string p_weekday ("weekday"); static string p_hour ("hour"); static string p_hh ("hh"); static string p_minute ("minute"); static string p_mi ("mi"); static string p_second ("second"); static string p_ss ("ss"); static string p_opnpar ("("); static string p_clspar (")"); static string p_values ("values"); static string p_delete ("delete"); static string p_start_transaction ("start transaction"); static string p_commit ("commit"); static string p_rollback ("rollback"); static string p_transaction ("transaction"); static string p_showplan ("showplan"); static string p_arithabort ("arithabort"); struct OLDNEW{ string old; string newname; OLDNEW(){}; OLDNEW(const char *_old, const char *_new){ old = _old; newname = _new; } }; static vector renamedb; void parser_set_renamedb (const char *oldname, const char *newname) { renamedb.push_back(OLDNEW(oldname,newname)); // PATCH. To support the [name] syntax, we have to duplicate the entries here string tmp = string("`") + oldname + string("`"); renamedb.push_back(OLDNEW(tmp.c_str(),newname)); } static bool nocreateindex = false; void parser_set_createindex (bool _nocreateindex) { nocreateindex = _nocreateindex; } static set createonly; void parser_set_createtb (const vector &tb) { for (unsigned i=0; i 0){ s += " as "; static string s_quote("\""); s += s_quote + alias + s_quote; } } } void TABLEALIAS::reformat (string &s, SQL_OPTS &opts) const { s += p_space; if (expr.size() > 0){ s += tb_jointypes[jtype]; s += p_space; } if (selectexpr.size() > 0){ s += "("; selectexpr.reformat(s,opts); s += ") "; }else{ table_reformat (s,name); } if (alias.size() > 0){ s += p_space; const char *pt = alias.c_str(); s += '`'; if (*pt == '#'){ s += string_f ("tempdb.TMP_%u_",curr_id); pt++; } while (*pt != '\0'){ s += *pt; pt++; } s += '`'; } if (hint.size() > 0){ if (hint[0] == '#'){ s += string(" use index(") + hint.substr(1) + p_clspar; }else{ s += string(" use index(") + hint + p_clspar; } } if (expr.size() > 0){ s += p_space + p_on + p_space; expr.reformat(s,opts); } } struct TEXTPTR_CONTEXT{ string table; string field; string where; bool operator < (const TEXTPTR_CONTEXT &c) const { bool ret = false; int cmp = strcmp(table.c_str(),c.table.c_str()); if (cmp < 0){ ret = true; }else if (cmp == 0){ cmp = strcmp(field.c_str(),c.field.c_str()); if (cmp < 0){ ret = true; }else if (cmp == 0){ ret = where < c.where; } } return ret; } }; static map textptrs_s; static vector textptrs; static void orderby_reformat (string &s, SQL_OPTS &opts, const vector &orderby) { if (orderby.size() > 0){ if (opts.no_order_by){ pusage.skip_order_by++; }else{ s += " order by "; for (unsigned i=0; i 0) s += ", "; const ORDERBY &o = orderby[i]; o.expr.reformat(s,opts); s += o.asc ? " asc": " desc"; } } } } void SELECT::reformat (string &s, SQL_OPTS &opts) const { string limit = opts.limit; // The limit (set rowcount) only applies to the main select // not sub-select opts.limit.clear(); // PATCH select into is converted into create ... as select: string into; if (into.size() > 0){ // We must tell if the table exists int id = -1; string temporary; if (into[0] == '#'){ id = opts.gettempid(into.substr(1)); //temporary = "temporary"; }else if (strncasecmp(into.c_str(),"tempdb..#",9)==0){ id = opts.gettempid(into.substr(9)); //temporary = "temporary"; }else{ id = opts.gettempid(into); } // This code permits to do a "select into" when the table already exist // but Sybase does not support that. So we disable the code here. if (true || id == -1){ s += string("create ") + temporary + string(" table "); table_reformat (s,into); s += " as "; }else{ s += "insert into "; table_reformat (s,into); } } s += " select"; for (unsigned i=0; i0) s += ", "; fields[i].reformat (s,opts); } if (tables.size() > 0){ s += " from"; for (unsigned i=0; i0 && tables[i].expr.size()==0) s += ", "; tables[i].reformat (s,opts); } } string where_str; if (where.not_empty()){ // MySQL does not accept a where section unless there is a from section. // Some program do this (weird I know) // select convert(char(10),NULL) as fieldname into #table where 1=2 // This is valid in Sybase. This is a create. The where 1=2 prevent any // rows to be written. // Se we make sure a "from" is inserted. if (tables.size()==0) s += " from sysobjects"; s += " where"; where.reformat(where_str,opts); s += where_str; } if (groupby.size() > 0){ s += " group by "; for (unsigned i=0; i 0) s += ", "; groupby.items[i].reformat(s,opts); } } if (having.not_empty()){ s += " having"; having.reformat(s,opts); } orderby_reformat (s,opts,orderby); if (top.size() > 0){ s += " limit " + top; }else if (limit.size() > 0){ s += " limit " + limit; } opts.limit = limit; // PATCH to support textptr size_t pos = s.find("###TEXTPTR"); if (pos != string::npos){ // Ok, this is a patch. We assume // the query is simply "select textptr(field) from table where condition" // So we only have to remember the field, the table and the condition to // convert later the readtext request into a simple select TEXTPTR_CONTEXT c; size_t posf = pos+10; while (s[posf] != '#') c.field += s[posf++]; posf++; tables[0].reformat (c.table,opts); c.where = where_str; unsigned id = 0; map::iterator it = textptrs_s.find(c); if (it == textptrs_s.end()){ static unsigned alloc = 0; id = alloc; textptrs_s[c] = alloc++; textptrs.push_back(c); }else{ id = it->second; } char tmp[100]; snprintf (tmp,sizeof(tmp),"convert(%u,binary)",id); s = s.substr(0,pos) + tmp + s.substr(posf); fprintf (stderr,"TEXTPTR :%s: :%s: :%s: %u\n",c.field.c_str(),c.table.c_str(),c.where.c_str(),id); } } static void reformat_error (SQL_OPTS &opts, const char *ctl, ...) { if (opts.format_error.size()==0){ va_list list; va_start (list,ctl); char buf[1000]; vsnprintf (buf,sizeof(buf)-1,ctl,list); opts.format_error = buf; va_end (list); } } static void parser_expr_reformat (const EXPR &expr, vector &tbs, SQL_OPTS &opts); void DROP::reformat (string &s, SQL_OPTS &opts) const { s += "drop " + type + " "; if (type == p_index){ size_t n = what.find ('.'); if (n == string::npos){ // PATCH we don't know on which table this index is used reformat_error (opts,"Don't know on which table drop this index, no dot: %s",what.c_str()); }else{ int posindex = n+1; if (what[posindex] == '#') posindex++; s += what.substr(posindex) + p_space + p_on + p_space; table_reformat(s,what.substr(0,n)); } }else{ table_reformat (s,what); } } void INSERT::reformat_values (string &s, SQL_OPTS &opts) const { s += "("; for (unsigned i=0; i0) s += ", "; values.items[i].reformat(s,opts); } s += ")"; } void INSERT::reformat (string &s, SQL_OPTS &opts) const { if (bulk){ // We create a dummy request that will be processed by tds if (nodescribe){ s += " BULKNODESC "; }else{ s += " BULK "; } table_reformat (s,table); }else{ s += " insert into "; table_reformat (s,table); unsigned vars_size = vars.size(); if (vars_size > 0){ s += " ("; for (unsigned i=0; i0) s += p_comma; table_reformat (s,vars[i]); } s += p_clspar; } if (select.not_empty()){ select.reformat (s,opts); }else{ s += p_space + p_values; reformat_values (s,opts); } } } bool INSERT::same_prefix (const INSERT &in) const { bool ret = false; unsigned size = vars.size(); unsigned sizev = values.size(); if (table == in.table && size > 0 && sizev > 0 && size == in.vars.size() && sizev == in.values.size()){ ret = true; for (unsigned i=0; i 0){ // We have to check if the UPDATE::table in part of the tables vector (first for, set found to true) // If this is not true, we insert the definition for table // if this is true, we forget about UPDATE::table and simply process tables const char *sep = ""; bool found = false; for (unsigned i=0; i0){ s += p_space; t0_alias = alias; table_reformat (s,alias); } sep = ", "; } for (unsigned i=0; i0){ s += p_space; t0_alias = alias; table_reformat (s,alias); } } s += " set "; for (unsigned i=0; i0) s += ", "; const ASSIGN &a = sets[i]; if (strchr(a.field.c_str(),'.')==NULL && tables.size() > 0){ if (t0_alias.size()>0){ table_reformat(s,t0_alias); }else{ table_reformat(s,table); } s += "." + a.field; }else{ table_reformat(s,a.field); } s += "="; a.valexpr.reformat(s,opts); } if (whereexpr.not_empty()){ s += " where"; whereexpr.reformat (s,opts); } } } void SET::reformat(string&, SQL_OPTS &opts) const { if (name == p_rowcount){ opts.limit = value; if (opts.limit == p_zero) opts.limit.clear(); }else if (name == p_textsize){ opts.textsize = atol(value.c_str()); }else if (name == p_quoted_identifier){ // Do nothing for now }else if (name == p_nocount){ opts.nocount = value == p_on; }else if (name == p_mysql){ opts.mysql_mode = value == p_on; }else if (name == p_dateformat){ // Do nothing for now }else if (name == p_chained){ // Do nothing for now }else if (name == p_ansinull){ // Do nothing for now }else if (name == p_arithignore){ // Do nothing for now }else if (name == p_transaction){ // Do nothing for now }else if (name == p_showplan){ // Do nothing for now }else if (name == p_arithabort){ // Do nothing for now }else if (name == p_statistics){ // Do nothing for now }else{ reformat_error (opts,"reformat SET %s %s",name.c_str(),value.c_str()); } } void IF::reformat(vector &tbs, SQL_OPTS &opts) const { // MySQL does not support that construct (only in storeproc) // We format the first condition statement using a select // Then we put all the SQL statement in case this is true // Then we enter a fake ELSE line // followed by the SQL statements to execute when false // Ended with END // No ELSE section for while statement tbs.push_back(ONESQL()); string &s = tbs[tbs.size()-1].sql; if (is_while){ s += "WHILE select"; }else{ s += "IF select"; } ifexpr.reformat(s,opts); if (beginexpr.not_empty()){ parser_expr_reformat (beginexpr,tbs,opts); } if (!is_while){ tbs.push_back("ELSE"); if (elseexpr.not_empty()){ parser_expr_reformat (elseexpr,tbs,opts); } } tbs.push_back("END"); } // Field creation, work for create and alter (add and modify) static void create_fields_reformat ( const vector &fields, const vector &constraints, const vector &checks, const string &prefix, string &s, SQL_OPTS &opts) { for (unsigned i=0; i 0) s += p_comma; if (prefix.size() > 0) s += prefix + p_space; const FIELD &f = fields[i]; if (f.identity){ char type[20]; if (f.typeopt.size() > 0){ if (f.typeopt[0] > 9){ snprintf (type,sizeof(type)," bigint(%d)",f.typeopt[0]); }else{ snprintf (type,sizeof(type)," int(%d)",f.typeopt[0]); } }else{ strcpy(type," int"); } s += f.name + type + " auto_increment primary key"; }else{ s += f.name + " " + parser_cnvbasetype(f.type.c_str()); if (f.typeopt.size() > 0){ s += "("; for (unsigned j=0; j 0) s += ","; char tmp[20]; snprintf (tmp,sizeof(tmp),"%d",f.typeopt[j]); s += tmp; } s += ")"; } if (f.null) s += " null"; if (f.not_null) s += " not null"; if (f.defexpr.size() > 0){ // PATCH mysql support only constant in default statement, not function // the getdate() case can be translated into a special mysql constant // So some sybase code can't work. const TOKEN &t = f.defexpr.items[0].t; if (str_nocaseeq(t.val,p_getdate)){ s += " default "; s += p_current_timestamp; }else if (t.type == TOK_NUM || t.type == TOK_STRING){ s += " default "; // PATCH, not sure this is even valid in sybase. if (t.val == "'N'" && f.type == p_bit){ s += "0"; }else{ f.defexpr.reformat(s,opts); } }else{ // PATCH no default produced here because MySQL do not support it pusage.no_default_on_create++; } } if (f.checkexpr.size() > 0){ s += " check("; f.checkexpr.reformat(s,opts); s += ") "; } } } for (unsigned i=0; i0){ if (name[0] == '#'){ s += " create temporary table "; }else{ s += " create table "; } table_reformat (s,name); s += p_opnpar; static string prefix_empty; create_fields_reformat (fields,constraints,checks,prefix_empty,s,opts); s += ") engine " + default_engine; } } void CREATE_INDEX::reformat(string&s, SQL_OPTS &opts) const { const char *pt = strrchr(table.c_str(),'.'); if (pt == NULL){ pt = table.c_str(); }else{ pt++; } if (!nocreateindex && (createonly.size()==0 || createonly.count(pt)==1)){ // PATCH do nothing with clustered for create index //bool clustered; s += " create"; if (unique) s += " unique"; s += " index "; if (index[0] == '#'){ table_reformat (s,index.substr(1)); }else{ table_reformat (s,index); } s += " on "; table_reformat (s,table); s += "("; for (unsigned i=0; i0) s += ", "; const INDEX_FIELD &f = fields[i]; table_reformat(s,f.name); if (f.order == asc){ s += p_space + p_asc; }else if (f.order == desc){ s += p_space + p_desc; } } s += ")"; } } void CREATE_TRIGGER::reformat(string&s, SQL_OPTS &opts) const { s += " create trigger " + name + " after update on " + table + " for each row begin "; expr.reformat(s,opts); s += " end"; } void DELETE::reformat(string &s, SQL_OPTS &opts) const { if (tables.size() > 0){ // PATCH // delete tablename from tablename aliasname where ... // does not work in MySQL. It has to be // select aliasname from tablename as alias where ... // So we have to find the alias for the table and put it in its place // Another issue // delete table1 from table2 where ... // does not work // It has to be // delete table1 from table1,table2 where s += p_delete + p_space; bool found = false; for (vector::const_iterator it = tables.begin(); it != tables.end(); it++){ if (table == it->name){ found = true; if (it->alias.size()==0){ // We keep the name, no alias table_reformat(s,table); }else{ table_reformat (s,it->alias); } break; } } bool second = false; if (!found){ table_reformat (s,table); s += " from"; table_reformat (s,table); second = true; }else{ s += " from"; } for (unsigned i=0; i 0) s += p_comma; s += parms[i]; } s += p_clspar; } void ALTER::reformat(string &s, SQL_OPTS &opts) const { pusage.alter++; s += p_space + p_alter + p_space + p_table + p_space; table_reformat (s,table); bool comma_needed = false; if (drops.size() > 0){ for (unsigned i=0; i 0){ if (comma_needed) s += p_comma; comma_needed = true; s += p_space; create_fields_reformat (add.fields,add.constraints,add.checks,p_add,s,opts); } if (mod.fields.size() > 0){ if (comma_needed) s += p_comma; comma_needed = true; s += p_space; create_fields_reformat (mod.fields,mod.constraints,mod.checks,p_modify,s,opts); } } void DECLARE::reformat(string &s, SQL_OPTS &opts) const { // We simply pass the information about the name of variable so the tds server knows // which one to delete after the query s += " declare"; for (vector::const_iterator it=fields.begin(); it != fields.end(); it++){ s += p_space; if (it->name[0] == '@' && it->name[1] == '@'){ s += it->name.substr(1); }else{ s += it->name; } } if (sets.size() > 0){ // We put the select statement to initialise the variable right after the "declare name" // tds will learn the name of the variable and execute the statement if there. s += " select "; for (unsigned i=0; i 0) s += p_comma; s += a.field; s += " := "; a.valexpr.reformat (s,opts); } } opts.is_assign = true; // This statement should not produce output to the client } static const char *parser_cnvhour(const char *pt, int &hour, int &minute, int &second, bool &valid) { valid = false; hour = atoi(pt); minute = 0; second = 0; pt = str_skipdig(pt); if (pt[0] == ':' && isdigit(pt[1])){ pt++; minute = atoi(pt); pt = str_skipdig(pt); if (pt[0] == ':' && isdigit(pt[1])){ pt++; second = atoi(pt); pt = str_skipdig(pt); valid = true; }else{ valid = true; } } pt = str_skip(pt); if (hour == 12) hour = 0; if (strncasecmp(pt,"am",2)==0){ pt = str_skip(pt+2); }else if (strncasecmp(pt,"pm",2)==0){ hour += 12; pt = str_skip(pt+2); } return pt; } /* Convert a string into a ISO date if it was a sybase date. Return true if there was a conversion (and something was appended to res) */ static bool parser_cnvsybdate(const string &s, string &res) { static map mapmonth; static const char *tbmonth[]={ "jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec" }; if (mapmonth.size() == 0){ for (int i=0; i<12; i++){ mapmonth[tbmonth[i]] = i+1; } } // Sybase example: "Aug 12 2016 12:00:00 AM" // Another case: "yyyymmdd hh:mm:ss AM" // Another case: "yyyymmdd" // Another case: "yyyy.mm.dd hh:mmAM" bool converted = false; if (s.size() >= 8){ const char *pt = s.c_str(); const char quote = *pt; pt = str_skip(pt+1); // Skip the " and the spaces int yyyymmdd = atoi(pt); int lenpt = strlen(pt); if (yyyymmdd >= 19000101 && yyyymmdd < 21000000){ pt += 8; bool onespace = isspace(*pt); pt = str_skip(pt); if (*pt == quote || *pt == '\0'){ char date[100]; snprintf (date,sizeof(date)-1,"\"%04d/%02d/%02d\"" ,yyyymmdd/10000,(yyyymmdd/100)%100,yyyymmdd%100); converted = true; res += date; }else if (onespace){ bool valid; int hour,minute,second; pt = parser_cnvhour(pt,hour,minute,second,valid); if (valid && pt[0] == quote && pt[1] == '\0'){ char date[100]; snprintf (date,sizeof(date)-1,"\"%04d/%02d/%02d %02d:%02d:%02d\"" ,yyyymmdd/10000,(yyyymmdd/100)%100,yyyymmdd%100 ,hour,minute,second); converted = true; res += date; } } }else if (yyyymmdd >= 1900 && yyyymmdd < 2100 && lenpt >= 10 && pt[4] == '.' && isdigit(pt[5]) && isdigit(pt[6]) && pt[7] == '.' && isdigit(pt[8]) && isdigit(pt[9])){ int year = atoi(pt); int month = atoi(pt+5); int day = atoi(pt+8); char date[100]; pt += 10; if (*pt == '\0' || *pt == quote){ // Just a date without time snprintf (date,sizeof(date),"\"%04d/%02d/%02d\"",year,month,day); converted = true; }else if (isspace(*pt)){ pt = str_skip(pt); if (*pt == '\0' || *pt == quote){ // Just a date without time snprintf (date,sizeof(date),"\"%04d/%02d/%02d\"",year,month,day); converted = true; }else{ lenpt = strlen(pt); if (lenpt >= 7 && isdigit(pt[0]) && isdigit(pt[1]) && pt[2] == ':' && isdigit(pt[3]) && isdigit(pt[4]) && isalpha(pt[5]) && (pt[6] == 'm' || pt[6] == 'M')){ const char *extra = str_skip(pt+7); if (*extra == '\0' || *extra == quote){ const char ampm = pt[5]; int hour = atoi(pt); int minute = atoi(pt+3); if (ampm == 'A' || ampm == 'a'){ if (hour == 12) hour = 0; converted = true; }else if (ampm == 'P' || ampm == 'p'){ if (hour == 12){ hour = 0; }else{ hour += 12; } converted = true; } snprintf (date,sizeof(date),"\"%04d/%02d/%02d %02d:%02d\"",year,month,day,hour,minute); } } } } if (converted) res += date; }else if (yyyymmdd > 00 && yyyymmdd < 13){ int month = yyyymmdd; pt = str_skipdig(pt); if (*pt == '/'){ pt++; int day = atoi(pt); if (day > 0 && day < 32){ pt = str_skipdig(pt); if (*pt == '/'){ pt++; int year = atoi(pt); if (year >= 1900 && year < 2100){ pt += 4; pt = str_skip(pt); char date[100]; if (*pt == quote || *pt == '\0'){ snprintf (date,sizeof(date),"\"%04d/%02d/%02d\"",year,month,day); converted = true; }else{ bool valid; int hour,minute,second; pt = parser_cnvhour(pt,hour,minute,second,valid); if ((*pt == quote || *pt == '\0') && valid){ converted = true; snprintf (date,sizeof(date),"\"%04d/%02d/%02d %02d:%02d:%02d\"",year,month,day,hour,minute,second); } } if (converted) res += date; } } } } }else if (isalpha(pt[0]) && isalpha(pt[1]) && isalpha(pt[2]) && pt[3] == ' '){ char month[4]={(char)tolower(pt[0]),(char)tolower(pt[1]),(char)tolower(pt[2]),'\0'}; map::iterator itm = mapmonth.find(month); pt = str_skip(pt+4); if (itm != mapmonth.end() && isdigit(pt[0])){ const char *day = pt; if (isdigit(pt[1])) pt++; // One or two digits if (pt[1] == ' '){ pt = str_skip(pt+1); if (isdigit(pt[0]) && isdigit(pt[1]) && isdigit(pt[2]) && isdigit(pt[3])){ const char *year = pt; pt += 4; bool onespace = pt[0] == ' '; pt = str_skip(pt); if (pt[0] == quote && pt[1] == '\0'){ // We have just a date and not time, pt[0] is a quote char date[23]; snprintf (date,22,"\"%04d/%02d/%02d\"" ,atoi(year),itm->second,atoi(day)); converted = true; res += date; }else if (onespace && isdigit(pt[0])){ bool valid; int hour,minute,second; pt = parser_cnvhour(pt,hour,minute,second,valid); if (valid && pt[0] == quote && pt[1] == '\0'){ char date[100]; snprintf (date,22,"\"%04d/%02d/%02d %02d:%02d:%02d\"" ,atoi(year),itm->second,atoi(day),hour,minute,second); converted = true; res += date; } } } } } } } return converted; } void ITEM::reformat (string &s, SQL_OPTS &opts) const { // Attention: In this function, because of the optimisation in EXPR::add (EXPR &), we // end up with some duplication. Check the code in EXPR::reformat below // printf ("ITEM::reformat %d %d\n",type,ITEM::IS_EXPR); if (type == ITEM::IS_EXPR){ u.expr->reformat(s,opts); }else if (type == ITEM::IS_PAREXPR){ u.parexpr->reformat(s,opts); }else if (type == ITEM::IS_UNION){ u.uunion->reformat(s,opts); }else if (type == ITEM::IS_SELECT){ u.select->reformat(s,opts); }else if (type == ITEM::IS_UPDATE){ u.update->reformat(s,opts); }else if (type == ITEM::IS_INSERT){ u.insert->reformat(s,opts); }else if (type == ITEM::IS_DROP){ u.drop->reformat(s,opts); }else if (type == ITEM::IS_SET){ u.sset->reformat(s,opts); }else if (type == ITEM::IS_IF){ // IF are only supported top level, see parser_expr_reformat() at the end of this file //u.iif->reformat(s,opts); reformat_error (opts,"IF reformat in item, ???"); }else if (type == ITEM::IS_CREATE_TABLE){ u.ctable->reformat(s,opts); }else if (type == ITEM::IS_CREATE_INDEX){ u.cindex->reformat(s,opts); }else if (type == ITEM::IS_CREATE_TRIGGER){ u.ctrig->reformat(s,opts); }else if (type == ITEM::IS_ALTER){ u.alter->reformat(s,opts); }else if (type == ITEM::IS_DELETE){ u.del->reformat(s,opts); }else if (type == ITEM::IS_TRUNCATE){ u.truncate->reformat(s,opts); }else if (type == ITEM::IS_EXEC){ u.exec->reformat(s,opts); }else if (type == ITEM::IS_DECLARE){ u.declare->reformat(s,opts); }else if (type == ITEM::IS_ITEMS){ tlmp_error ("IS_ITEMS ????\n"); }else if (type == ITEM::IS_TOKEN){ // printf ("ITEM::IS_TOKEN type %d %d %s\n",t.type,TOK_FUNCTION,t.val.c_str()); if (t.type == TOK_STRING){ // PATCH, we convert sybase date format without checking if this field is indeed a date. s += " "; if (!parser_cnvsybdate(t.val,s)){ // PATCH We must escapce backslash const char *pt = t.val.c_str(); while (*pt != '\0'){ if (*pt == '\\'){ s += '\\'; } s += *pt++; } } }else if (t.type == TOK_NAME){ s += " "; table_reformat (s,t.val); }else if (t.type == TOK_FUNCTION){ // We end here because of the optimisation in EXPR::add (EXPR &) if (str_nocaseeq(t.val,p_db_id)){ // PATCH pusage.function_db_id++; s += " 1"; // Just to try }else{ if (str_nocaseeq(t.val,p_getdate)){ s += p_now; }else{ s += t.val; } s += p_opnpar + p_clspar; } }else if (t.type == TOK_LEFTJOIN || t.type == TOK_RIGHTJOIN){ opts.logthis = true; pusage.old_outer_join++; s += "="; // PATCH for testing }else if (t.type == TOK_TRANSACTION){ s += p_space + p_start_transaction; }else if (t.type == TOK_COMMIT){ s += p_space + p_commit; }else if (t.type == TOK_ROLLBACK){ s += p_space + p_rollback; }else if (t.type == TOK_TRACEON){ s += p_space + "select 1"; }else if (t.val.size() > 0){ s += " "; s += t.val; }else{ s += " "; s += tokens[t.type]; } }else if (type == ITEM::IS_USE){ u.use->reformat(s,opts); } } static unsigned xdig (char car) { unsigned ret = (unsigned)-1; if (isdigit(car)){ ret = car - '0'; }else if (isalpha(car)){ if (car < 'a'){ ret = car - 'A' + 10; }else{ ret = car - 'a' + 10; } } return ret; } static unsigned xtoi (const char *val) { unsigned ret = (unsigned)-1; if (val[0] == '0' && val[1] == 'x'){ val += 2; ret = 0; while (isxdigit(val[0]) && isxdigit(val[1])){ unsigned v1 = xdig(val[0]); unsigned v2 = xdig(val[1]); unsigned charnum = (v1<<4)+v2; printf ("charnum=%c%c v1=%u v2=%u %u\n",val[0],val[1],v1,v2,charnum); ret = (ret * 10) + xdig(charnum); val += 2; } printf ("xtoi ret = %u\n",ret); } return ret; } /* PATCH: Force coalesce when using concat The concat mysql function does not like NULL. When we patching string + something + -> concat (string,something) we place a coalesce(something,"") to be sure everything is different from NULL */ static void parser_coalesce (const ITEM &item, string &s, SQL_OPTS &opts) { if (item.istokentype(TOK_STRING)){ item.reformat(s,opts); }else{ s += " coalesce ("; item.reformat (s,opts); s += ",'')"; } } /* Reformat the expression tree in SQL , compatible with MySQL */ void EXPR::reformat (string &s, SQL_OPTS &opts) const { unsigned size = items.size(); bool null_compare = false; if (size >= 3){ if (items[1].istokentype(TOK_PLUS) && (items[0].istokentype(TOK_STRING) || items[2].istokentype(TOK_STRING))){ // PATCH Sybase concatenate strings using operator + // MySQL does not support that. It uses the concat() function // and Sybase does not have a compatible function, so we need this code // We have to make sure that we have a + sequence and an odd number of items if ((size & 1)==0){ reformat_error (opts,"Can't use concat on this expression, even argument number"); }else{ // We make sure we have only + for (unsigned i=3; ireformat (s2,opts,false,null_seen,other_seen); //printf ("s0=%s in s2=%s null_seen=%d\n",s0.c_str(),s2.c_str(),null_seen); if (null_seen){ if (other_seen){ s += p_opnpar + s0 + string(" in ") + s2 + string(" or ") + s0 + string(" is null)"); }else{ // This is just something in (NULL) s += s0 + string(" is null"); } }else{ s += s0 + " in " + s2; } return; } for (unsigned i=0; ireformat(s,opts); }else if (it.type == ITEM::IS_PAREXPR){ it.u.parexpr->reformat(s,opts); }else if (it.type == ITEM::IS_UNION){ it.u.uunion->reformat(s,opts); }else if (it.type == ITEM::IS_SELECT){ it.u.select->reformat(s,opts); }else if (it.type == ITEM::IS_UPDATE){ it.u.update->reformat(s,opts); }else if (it.type == ITEM::IS_INSERT){ it.u.insert->reformat(s,opts); }else if (it.type == ITEM::IS_DROP){ it.u.drop->reformat(s,opts); }else if (it.type == ITEM::IS_SET){ it.u.sset->reformat(s,opts); }else if (it.type == ITEM::IS_IF){ // IF are only supported top level, see parser_expr_reformat() at the end of this file //it.u.iif->reformat(s,opts); reformat_error (opts,"EXPR IF reformat ???"); }else if (it.type == ITEM::IS_CREATE_TABLE){ it.u.ctable->reformat(s,opts); }else if (it.type == ITEM::IS_CREATE_INDEX){ it.u.cindex->reformat(s,opts); }else if (it.type == ITEM::IS_DELETE){ it.u.del->reformat(s,opts); }else if (it.type == ITEM::IS_TRUNCATE){ it.u.truncate->reformat(s,opts); }else if (it.type == ITEM::IS_DECLARE){ it.u.declare->reformat(s,opts); }else if (it.type == ITEM::IS_ITEMS){ tlmp_error ("ITEMS ????\n"); }else if (it.type == ITEM::IS_USE){ it.u.use->reformat(s,opts); }else if (it.type == ITEM::IS_GRANT){ it.u.grant->reformat(s,opts); }else if (it.type == ITEM::IS_EXEC){ it.u.exec->reformat(s,opts); }else if (it.type == ITEM::IS_ALTER){ it.u.alter->reformat(s,opts); }else if (it.type == ITEM::IS_TOKEN){ if (it.t.type == TOK_FUNCTION){ // printf ("function %s\n",it.t.val.c_str()); if (str_nocaseeq(it.t.val,p_datediff)){ // PATCH sybase first parameter is replaced by a number. We do not support all sybase case. See below pusage.function_datediff++; if (size == 4){ s += p_space; s += p_CF_ + it.t.val; s += p_opnpar; const char *datepart = items[1].t.val.c_str(); if (strcasecmp(datepart,"dd")==0 || strcasecmp(datepart,"day")==0){ s += "0"; }else if (strcasecmp(datepart,"wk")==0 || strcasecmp(datepart,"week")==0){ s += "1"; }else if (strcasecmp(datepart,"mm")==0 || strcasecmp(datepart,"month")==0){ s += "2"; }else if (strcasecmp(datepart,"yy")==0 || strcasecmp(datepart,"year")==0){ s += "3"; }else{ reformat_error (opts,"datediff date-parts %s not supported",datepart); } s += p_comma; items[2].reformat(s,opts); s += p_comma; items[3].reformat(s,opts); s += p_clspar; }else{ reformat_error(opts,"wrong argument count for function datediff"); } }else if (str_nocaseeq(it.t.val,p_datepart)){ s += p_space; s += p_date_format; s += p_opnpar; items[2].reformat(s,opts); s += p_comma; string suffix; if (str_nocaseeq(items[1].t.val,p_dd) || str_nocaseeq(items[1].t.val,p_day)){ s += "\"%e\""; }else if (str_nocaseeq(items[1].t.val,p_month) || str_nocaseeq(items[1].t.val,p_mm)){ s += "\"%c\""; }else if (str_nocaseeq(items[1].t.val,p_year) || str_nocaseeq(items[1].t.val,p_yy)){ s += "\"%Y\""; }else if (str_nocaseeq(items[1].t.val,p_dayofyear) || str_nocaseeq(items[1].t.val,p_dy)){ s += "\"%j\""; }else if (str_nocaseeq(items[1].t.val,p_weekday) || str_nocaseeq(items[1].t.val,p_dw)){ s += "\"%w\""; suffix = "+1"; // Sybase expects 1-7, MySQL produces 0-6 }else if (str_nocaseeq(items[1].t.val,p_hour) || str_nocaseeq(items[1].t.val,p_hh)){ s += "\"%H\""; }else if (str_nocaseeq(items[1].t.val,p_minute) || str_nocaseeq(items[1].t.val,p_mi)){ s += "\"%i\""; }else if (str_nocaseeq(items[1].t.val,p_second) || str_nocaseeq(items[1].t.val,p_ss)){ s += "\"%S\""; }else{ s += items[1].t.val; } s += p_clspar+suffix; }else if (str_nocaseeq(it.t.val,p_dateadd)){ s += p_space; s += p_date_add; s += p_opnpar; if (size != 4){ reformat_error (opts,"wrong argument count for function dateadd"); }else{ items[3].reformat(s,opts); s += p_comma; s += p_interval; s += p_space; items[2].reformat(s,opts); s += p_space; if (str_nocaseeq(items[1].t.val,p_dd) || str_nocaseeq(items[1].t.val,p_day)){ s += p_day; }else if (str_nocaseeq(items[1].t.val,p_month) || str_nocaseeq(items[1].t.val,p_mm)){ s += p_month; }else if (str_nocaseeq(items[1].t.val,p_year) || str_nocaseeq(items[1].t.val,p_yy)){ s += p_year; }else{ s += items[1].t.val; } } s += p_clspar; }else if (str_nocaseeq(it.t.val,p_db_id)){ // PATCH pusage.function_db_id++; s += " 1"; // Just to try }else if (str_nocaseeq(it.t.val,p_charindex)){ s += p_space; s += p_instr; s += p_opnpar; if (size == 3){ // The arguments are reversed items[2].reformat(s,opts); s += p_comma; items[1].reformat(s,opts); }else{ reformat_error (opts,"wrong argument count for function charindex"); } s += p_clspar; }else if (str_nocaseeq(it.t.val,p_object_id)){ pusage.function_object_id++; if (size != 2){ reformat_error (opts,"wrong argument count for function object_id"); }else{ string ss; items[1].reformat(ss,opts); s += p_space; const char *pt = ss.c_str(); if (strncmp(pt," '#",3)==0 || strncmp(pt," \"#",3)==0 || strncmp(pt," 'tempdb..#",3)==0 || strncmp(pt," \"tempdb..#",3)==0){ string table; pt += pt[2] == '#' ? 3 : 11; while (*pt != '\'' && *pt != '"') table += *pt++; int id = opts.gettempid(table); if (id == -1){ s += "NULL"; }else{ char tmp[20]; snprintf (tmp,sizeof(tmp)-1,"%d",id); s += tmp; } }else{ s += p_CF_ + it.t.val; s += p_opnpar; s += ss; s += p_clspar; } } }else if (str_nocaseeq(it.t.val,p_convert)){ // PATCH Sybase convert accepts more types than MySQL pusage.function_convert++; s += p_space; string t1,t2,t3,t5; items[1].reformat(t1,opts); items[2].reformat(t2,opts); items[3].reformat(t3,opts); items[5].reformat(t5,opts); t1 = t1.substr(1); s += it.t.val + p_opnpar; if (strcmp(t5.c_str()," 0")==0){ // This is just a normal type conversion items[4].reformat(s,opts); }else{ // This is probably a date conversion, we turn the type into a string with dquotes // For some reason, the data is always returns as binary (the flag in FIELD_TYPES, see tds.tlcc) // This is why we enclose the CF_convert into another convert(). s += p_CF_ + it.t.val; s += p_opnpar + p_dquote + t1 + p_dquote; for (i=2; i1) s += p_comma; items[i].reformat(s,opts); } s += p_clspar; } break; }else if (it.t.type == TOK_CASE){ s += " case when "; items[1].reformat(s,opts); s += " then "; items[2].reformat(s,opts); if (size == 4){ items[3].reformat(s,opts); } s += " end"; break; }else if (it.t.type == TOK_WHEN){ s += " when "; items[1].reformat(s,opts); s += " then "; items[2].reformat(s,opts); if (size == 4){ items[3].reformat(s,opts); } break; }else if (it.t.type == TOK_ELSE){ s += " else "; items[1].reformat(s,opts); break; }else if (it.t.type == TOK_CASEval){ // PATCH case val when null // This was "case expr when expr ..." // MySql does not support "case expr when null ..." // So we rewrite the thing as "case when expr = value ..." // or in case of null "case when expr is null ..." s += " case when "; items[1].reformat(s,opts); string ss; items[2].reformat(ss,opts); if (strcmp(ss.c_str()," null")==0){ s += " is " + ss; }else{ s += "=" + ss; } s += " then "; items[3].reformat(s,opts); if (size == 5){ items[4].reformat(s,opts); } s += " end"; break; }else if (it.t.type == TOK_WAITFOR){ char tmp[100]; string ss; items[2].reformat(ss,opts); const char *ptss = str_skip(ss.c_str()); if (*ptss == '\'' || *ptss == '"') ptss++; int hour = atoi(ptss); int minutes = 0; int seconds = 0; ptss = str_skipdig(ptss); if (*ptss == ':'){ ptss++; minutes = atoi(ptss); ptss = str_skipdig(ptss); if (*ptss == ':'){ ptss++; seconds = atoi(ptss); } } seconds += hour*60*60+minutes*60; snprintf (tmp,sizeof(tmp)-1," select sleep(%d)",seconds); s += tmp; break; }else{ it.reformat(s,opts); } } } } void PAREXPR::reformat (string &s,SQL_OPTS &opts, bool include_null, bool &null_seen, bool &other_seen) const { null_seen = false; other_seen = false; s += "("; // Is this a list unsigned nb=0; unsigned size = items.size(); for (unsigned i=0; i0){ s += " union "; if (all) s+= "all "; } items[i].reformat(s,opts); } orderby_reformat (s,opts,orderby); //opts.no_order_by = no_order_by; } static void parser_expr_reformat_push (const EXPR &expr, vector &tbs, SQL_OPTS &opts) { unsigned last = tbs.size(); tbs.push_back(ONESQL()); opts.is_assign = false; expr.reformat (tbs[last].sql,opts); tbs[last].is_assign = opts.is_assign; } static void parser_expr_reformat (const EXPR &expr, vector &tbs, SQL_OPTS &opts) { unsigned size = expr.items.size(); // In sybase, you can send "insert into table (a,b) values (1,2) insert into table (a,b) ..." // in one query. MySQL support multiple insert, so if the prefix of the insert is the same // we can rewrite the syntax if (size > 1){ bool done = false; if (expr.items[0].type == ITEM::IS_INSERT){ // Check if we have many inserts with the same prefix // We convert this to a single insert statement, multi-valued // insert into table (a,b) values (1,2) // insert into table (a,b) values (3,4) // becomes // insert into table (a,b) values (1,2),(3,4) unsigned nb = 1; const ITEM &i0 = expr.items[0]; for (unsigned i=1; isame_prefix(*ii.u.insert)){ nb++; } } if (nb == size){ unsigned last = tbs.size(); tbs.push_back(ONESQL()); opts.is_assign = false; string &s = tbs[last].sql; expr.items[0].reformat(s,opts); for (unsigned i=1; ireformat_values(s,opts); } tbs[last].is_assign = opts.is_assign; done = true; } } if (!done){ for (unsigned i=0; ireformat(tbs,opts); }else{ unsigned last = tbs.size(); tbs.push_back(ONESQL()); opts.is_assign = false; expr.items[i].reformat(tbs[last].sql,opts); tbs[last].is_assign = opts.is_assign; } } } }else if (size > 0){ if (expr.items[0].type == ITEM::IS_IF){ expr.items[0].u.iif->reformat(tbs,opts); }else{ parser_expr_reformat_push (expr,tbs,opts); } } } int parser_expr_reformat (const EXPR &expr, vector &tbs, SQL_OPTS &opts, unsigned id) { opts.format_error.clear(); curr_id = id; sqlerror = opts.sqlerror; last_was_select = opts.last_was_select; parser_expr_reformat(expr,tbs,opts); return opts.format_error.size() > 0 ? -1 : 0; }