/* This is a proxy between a Sybase client and a Mysql server. The clients connect to it and it connects to Mysql and perform translation back and forth. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tdslib.h" #include "lasuite.h" #include using namespace std; extern DEBUG_KEY D_SRVMSG; extern DEBUG_KEY D_CLIMSG; static DEBUG_KEY K_REPL("replace","Replace SQL on the fly"); static DEBUG_KEY K_OTHER("other","Various debugs"); enum MYSQL_STATE{ STATE_NONE, STATE_CONNECTING, STATE_REQUESTING, STATE_REQUESTDONE, STATE_FETCHING, STATE_FETCHDONE }; static map renamed_db; // Back map from new name to old name static void tds_fakeresult(TDS_WRITER *w, TDS_LOGIN &login, SQL_OPTS &opts, const char *name, const char *val) { FIELD_DEF def; def.name = name; def.val1 = 10; def.type = MYSQL_TYPE_VARCHAR; def.decimals = 0; vector fdefs; fdefs.push_back(def); tdslib_rep_query_format(w,login,fdefs,opts); const char *vals[1]; vals[0] = val; tdslib_rep_query_row(w,login,fdefs,vals,opts); tdslib_rep_query_end(w,1,opts); } /* Execute a synchronous query (update,delete,insert) */ static int tds_mysql(MYSQL &mysql, const char *sql) { int ret = mysql_query(&mysql,sql); if (ret == -1){ tlmp_warning ("tds_mysql: %s: %s\n",sql,mysql_error(&mysql)); }else{ MYSQL_RES *res = mysql_store_result(&mysql); mysql_free_result(res); ret = mysql_affected_rows(&mysql); //printf ("Nombre de rangee %d: %s %s\n",ret,mysql_error(&mysql),sql); } debug_printf (K_OTHER,"tds_mysql ret=%d sql=%s\n",ret,sql); return ret; } static void tds_res_format (MYSQL_RES *res, vector &defs) { MYSQL_FIELD *f; while ((f=mysql_fetch_field(res))!=NULL){ FIELD_DEF def; // Remove the prefix for mysql keywords (see parser.tlcc spc_CF { const char *org_name = f->org_name; if (strncmp(org_name,"_CF_",4)==0) org_name += 4; def.org_name = org_name; } { const char *name = f->name; if (strncmp(name,"_CF_",4)==0) name += 4; def.name = name; } def.table = f->org_table; def.val1 = f->length; def.type = f->type; def.decimals = f->decimals; def.not_null = (f->flags & NOT_NULL_FLAG) != 0; def.binary = (f->flags & BINARY_FLAG) != 0; def.identity = (f->flags & PRI_KEY_FLAG) != 0; debug_printf (K_OTHER,"type %d len %lu decimals %u db_length %d def_length %d not_null %d identity %d binary %d flags %x name %s\n" ,f->type,f->length,f->decimals,f->db_length,f->def_length,def.not_null,def.identity,def.binary,f->flags,f->name); defs.push_back(def); } } static int tds_mysql(MYSQL &mysql, const char *sql, vector >&tb, vector &defs) { int ret = mysql_query(&mysql,sql); if (ret == -1){ tlmp_warning ("tds_mysql: %s: %s\n",sql,mysql_error(&mysql)); }else{ MYSQL_RES *res = mysql_store_result(&mysql); tds_res_format(res,defs); int num_fields = mysql_num_fields(res); MYSQL_ROW row; while ((row = mysql_fetch_row(res))){ vector r; for (int i=0; i defs; SQL_OPTS opts; int connection_id; // MySQL connection ID to cancel long query int spid; // Sybase only supports 16 bits connection_id, so we have to invent one to please application BULK_RECORDS *brecs; struct { // Control execution of a multiple statements request (not supported by MySql) long long start; // Start of query unsigned nosql; // Statement currently executed stack exstat; // Execution stack bool if_statement; // We are processing the condition part of the if or while bool one_result; // One result was sent to the client. unsigned long numrows; vector mysqls; // Sybase SQL transform into several MySQL request vector variables; // @variable, all set to null after the sequence execution string bulktable; // Table currently receiving bulk data } sqls; string busysql; // SQL request received while processing another string sybsql; // Original SQL query (sybase syntax) void resetsqls(){ sqls.nosql = 0; while (!sqls.exstat.empty()) sqls.exstat.pop(); sqls.exstat.push(EXEC_CONTROL()); // Put one for the first execution level, skip=false sqls.if_statement=false; sqls.one_result = false; sqls.numrows = 0; sqls.start = tdslib_getnow(); sqls.variables.clear(); sqls.bulktable.clear(); w = NULL; } void cleanup(FILE_LOG &flog, TDS_LOGIN &login){ // Remove stuff in the database related to this session // Update the pseudo table sysprocesses if (connection_id != -1){ string tmp = string_f ("delete from master.sysprocesses where spid=%d",spid); tds_mysql (mysql,tmp.c_str()); // Now we delete the temporary tables for (map::iterator it=opts.temp2id.begin(); it != opts.temp2id.end(); it++){ long long start = tdslib_getnow(); tmp = string_f ("drop table tempdb.`TMP_%d_%s`",spid,it->first.c_str()); tds_mysql (mysql,tmp.c_str()); debug_printf (K_OTHER,"cleanup: %s\n",tmp.c_str()); tdslib_logsql (flog.fsql,spid,login,start,0,tmp.c_str()); } } } TDS_INFO(){ start = last = time(NULL); status = 0; client = NULL; state = STATE_NONE; ret = NULL; res = NULL; row = NULL; w = NULL; nbquery=0; nberrmysql=0; nberrformat=0; nberrparse=0; connection_id=-1; spid = -1; resetsqls(); } ~TDS_INFO(){ // No need to delete client, this is a pointer to the TDS_INFO connection of the client } void allocwriter(){ if (w != NULL) printf ("******* allocwriter w != NULL\n"); w = tdslib_getwriter(client_fd); } void freewriter(){ //printf ("freewrite w=%p\n",w); if (w != NULL) tdslib_freewriter(w); w = NULL; } void fetch_start(const TDS_LOGIN &login){ state = STATE_REQUESTDONE; res = mysql_use_result(&mysql); sqls.numrows = 0; if (res == NULL){ tdslib_debug (D_SRVMSG,connection_id,login,"fetch_start: mysql_use_result error %d %s\n" ,mysql_errno(&mysql),mysql_error(&mysql)); } } void fetch_stop(bool internal){ if (!internal){ if (w != NULL){ if (sqls.bulktable.size() > 0){ tdslib_rep_bulk_end(w,sqls.numrows,opts); }else{ tdslib_rep_query_end(w,sqls.numrows,opts); } } sqls.one_result = true; } state = STATE_FETCHDONE; defs.clear(); mysql_free_result(res); res = NULL; } private: // Manage one more row, return 0 if it was the end int fetch_one_row(const TDS_LOGIN &login){ int ret = 1; tdslib_debug (D_SRVMSG,connection_id,login,"fetch_one_row: defs.size %lu\n",defs.size()); if (sqls.if_statement){ if (row){ //printf ("if result %s\n",row[0]); bool if_is_true = row[0] != NULL ? (atoi(row[0]) != 0) : false; sqls.exstat.top().skip = !if_is_true; }else{ sqls.if_statement = false; fetch_stop(true); ret = 0; } }else{ bool sendresult = w != NULL && !sqls.mysqls[sqls.nosql-1].is_assign; if (defs.size()==0){ // First row is in, now we have the column definition tds_res_format(res,defs); if (sqls.bulktable.size() > 0){ tdslib_debug (D_SRVMSG,connection_id,login,"fetch_one_row: bulk res_format defs.size %lu\n",defs.size()); tdslib_rep_bulk_format(w,login,defs,opts,brecs->fields); }else{ tdslib_debug (D_SRVMSG,connection_id,login,"fetch_one_row: res_format defs.size %lu\n",defs.size()); if (sendresult) tdslib_rep_query_format(w,login,defs,opts); } } if (row){ const char *vals[defs.size()]; for (unsigned i=0; i 0){ tdslib_debug (D_SRVMSG,connection_id,login,"nextresult: mysql_next_result error %d %s\n",next,mysql_error(&mysql)); if (w != NULL){ const char *errmsg = mysql_error(&mysql); tdslib_debug (D_CLIMSG,connection_id,login,"MYSQL nosql=%d error %d %s\n",sqls.nosql,opts.sqlerror,errmsg); tdslib_err_query(w,login,errmsg,opts.sqlerror); nberrmysql++; tdslib_logerr (flog.ferror,spid,login,errmsg,sqls.mysqls[sqls.nosql-1].sql.c_str(),sybsql.c_str()); freewriter(); } }else if (next != -1){ fetch_start(login); fetch(login); tdslib_debug (D_SRVMSG,connection_id,login,"nextresult: mysql_more_results true res=%p\n",res); if (res == NULL){ state = STATE_FETCHDONE; }else{ ret = true; break; } } }else{ tdslib_debug (D_SRVMSG,connection_id,login,"nextresult: mysql_more_results false\n"); break; } } return ret; } // Query completed, time to retrieve the result void querydone(FILE_LOG &flog, TDS_LOGIN &login, int err){ if (err){ state = STATE_NONE; opts.sqlerror = mysql_errno(&mysql); if (opts.sqlerror == 1213){ // This is a deadlock, so we translate this to sybase error code opts.sqlerror = 1205; } if (w != NULL){ const char *errmsg = mysql_error(&mysql); tdslib_debug (D_CLIMSG,connection_id,login,"MYSQL nosql=%d error %d %s\n",sqls.nosql,opts.sqlerror,errmsg); tdslib_err_query(w,login,errmsg,opts.sqlerror); nberrmysql++; tdslib_logerr (flog.ferror,spid,login,errmsg,sqls.mysqls[sqls.nosql-1].sql.c_str(),sybsql.c_str()); freewriter(); } }else{ opts.sqlerror = 0; const char *query = sqls.mysqls[sqls.nosql-1].sql.c_str(); query = str_skip(query); tdslib_debug (D_CLIMSG,connection_id,login,"MYSQL nosql=%d query ok %s\n",sqls.nosql,query); opts.last_was_select = false; if (strncasecmp(query,"use ",4)==0){ login.cur_db = query+4; // We try to put back the original name of the database // We know that the query was successful map::const_iterator it = renamed_db.find(login.cur_db); if (it != renamed_db.end()){ login.cur_db = it->second; } login.cur_db = tds_unquote (login.cur_db); tdslib_rep_use(w,login); sqls.one_result=true; state = STATE_FETCHDONE; }else if (strncasecmp(query,"create ",7)==0){ bool create_as = strstr(query,"as select"); if (create_as){ sqls.numrows = mysql_affected_rows(&mysql); tdslib_rep_action(w,login,sqls.numrows); }else{ tdslib_rep_other(w,login); } sqls.one_result=true; state = STATE_FETCHDONE; // We try to find out if a temp table was created const char *pt = strstr(query,"table tempdb.`TMP_"); if (pt != NULL){ pt += 18; while (isdigit(*pt)) pt++; if (*pt == '_') pt++; string word; while (isalpha(*pt)||isdigit(*pt)||*pt == '_') word += *pt++; debug_printf (K_OTHER,"select CF_object_create('#%s')\n",word.c_str()); opts.addtemptable(word); } }else if (strncasecmp(query,"drop ",5)==0){ //tdslib_rep_action(w,login,mysql_affected_rows(&mysql)); tdslib_rep_other(w,login); sqls.one_result=true; state = STATE_FETCHDONE; // We try to find out if a temp table was dropped const char *pt = strcasestr(query,"TABLE tempdb.`TMP_"); // printf ("drop query=%s pt=%p\n",query,pt); if (pt != NULL){ pt += 18; while (isdigit(*pt)) pt++; if (*pt == '_') pt++; string word; while (isalpha(*pt)||isdigit(*pt)||*pt == '_') word += *pt++; debug_printf (K_OTHER,"select CF_object_delete('#%s')\n",word.c_str()); opts.deltemptable(word); } }else if (strncmp(query,"truncate ",9)==0){ tdslib_rep_other(w,login); sqls.one_result=true; state = STATE_FETCHDONE; }else if (strncasecmp(query,"delete ",7)==0 || strncasecmp(query,"insert ",7)==0 || strncasecmp(query,"alter ",6)==0 || strncasecmp(query,"start ",6)==0 || strncasecmp(query,"commit",6)==0 || strncasecmp(query,"rollback",8)==0 || strncasecmp(query,"set ",4)==0 //|| strncasecmp(query,"call ",5)==0 || strncasecmp(query,"update ",7)==0){ tdslib_rep_action(w,login,mysql_affected_rows(&mysql)); sqls.one_result=true; state = STATE_FETCHDONE; }else{ opts.last_was_select = true; fetch_start(login); fetch(login); } if (state == STATE_FETCHDONE){ tdslib_logsql (flog.fsql,spid,login,sqls.start,sqls.numrows,sqls.mysqls[sqls.nosql-1].sql.c_str()); if (!nextresult(flog,login)){ nextsql(flog,login); } //printf ("nextsql state = %d\n",state); } } } void querystart (FILE_LOG &flog, TDS_LOGIN &login, const char *sql, const string &req){ int err; status = mysql_real_query_start(&err, &mysql,sql,strlen(sql)); if (!status){ querydone(flog,login,err); }else{ state = STATE_REQUESTING; tdslib_debug (D_CLIMSG,connection_id,login,"MYSQL err=%d status=%d %s\n",err,status,req.c_str()); } } void nextsql(FILE_LOG &flog, TDS_LOGIN &login){ state = STATE_NONE; while (sqls.nosql < sqls.mysqls.size()){ string &req = sqls.mysqls[sqls.nosql].sql; const char *sql = str_skip(req.c_str()); sqls.nosql++; tdslib_debug (D_CLIMSG,connection_id,login,"MYSQL[%d-%u] :%s:\n",sqls.nosql,sqls.mysqls.size(),sql); if (req.size() == 0){ state = STATE_NONE; if (sqls.nosql == sqls.mysqls.size()){ //tdslib_rep_other(w,login); tdslib_send_msg(w,login,NULL); sqls.one_result = true; break; } }else if (strstr(sql,"select `sysstat2`")!=NULL){ // Just fake a result state = STATE_NONE; tds_fakeresult (w,login,opts,"sysstat2","12345"); break; }else{ if (strncmp(sql,"IF ",3)==0){ // IF::reformat() putting this IF token so we know we have // to control the logic ourself since mysql do not support this sql += 3; bool parent_skip = sqls.exstat.top().skip; sqls.exstat.push(EXEC_CONTROL()); sqls.exstat.top().parent_skip = parent_skip; sqls.exstat.top().skip = parent_skip; sqls.if_statement = true; }else if (strncmp(sql,"WHILE ",6)==0){ // IF::reformat() putting this WHILE token so we know we have // to control the logic ourself since mysql do not support this sql += 6; bool parent_skip = sqls.exstat.top().skip; sqls.exstat.push(EXEC_CONTROL()); EXEC_CONTROL &ex = sqls.exstat.top(); ex.parent_skip = parent_skip; ex.skip = parent_skip; ex.while_start = sqls.nosql-1; ex.is_while = true; sqls.if_statement = true; } EXEC_CONTROL &ex = sqls.exstat.top(); if (strncasecmp(sql,"print ",6)==0){ if (!ex.skip){ //printf ("PRINT %s\n",sql+6); SSTRING tmp; str_copyquote (tmp,sql+6); tdslib_send_msg(w,login, tmp.c_str()); sqls.one_result = true; } }else if (strncmp(sql,"BULKNODESC ",11)==0){ // We have already sent the description to the client in the first BULK request const char *table = sql+11; sqls.bulktable = table; }else if (strncmp(sql,"BULK ",5)==0){ const char *table = sql+5; sqls.bulktable = table; // The client is about to send columns of data. // We must first send string tmp = string_f ("select * from %s where 1=0",table); sql = tmp.c_str(); tdslib_debug (D_CLIMSG,connection_id,login,"BULK extract schema: %s\n",sql); querystart (flog,login,sql,req); break; }else if (strncmp(sql,"declare ",8)==0){ // The reformat place the name of the variables followed by a select to initialise them // if provided const char *names = sql+8; string tmp; const char *sel = strstr(names," select "); if (sel != NULL){ tmp = string (names,sel-names); names = tmp.c_str(); } vector tb; int n = str_splitline (names,' ',tb); for (int i=0; i > tb; vector df; char tmp[200]; snprintf (tmp,sizeof(tmp),"SELECT DISTINCT TABLE_NAME, INDEX_NAME, 1 FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = '%s'",login.cur_db.c_str()); tds_mysql (mysql,tmp,tb,df); tdslib_rep_query_format(w,login,df,opts); for (unsigned i=0; i &v = tb[i]; const char *vals[df.size()]; for (unsigned j=0; j 0){ long long start = tdslib_getnow(); string s("select "); const char *sep = ""; for (vector::iterator it=sqls.variables.begin(); it != sqls.variables.end(); it++){ s += sep; s += *it; s += ":=null"; sep = ","; } int status = mysql_query(&mysql,s.c_str()); if (!status){ MYSQL_RES *res = mysql_store_result(&mysql); mysql_free_result(res); } tdslib_logsql (flog.fsql,spid,login,start,0,s.c_str()); } if (busysql.size() > 0){ tdslib_debug (D_CLIMSG,connection_id,login,"busysql now %s\n",busysql.c_str()); reqsql(busysql.c_str(),flog,login); busysql.clear(); } } } } } void reqsql (const char *sql, FILE_LOG &flog, TDS_LOGIN &login){ if (sql[0] == '\0'){ tdslib_rep_action(client_fd,login,0); state = STATE_NONE; }else{ resetsqls(); allocwriter(); nbquery++; sql = str_skip(sql); if (strncmp(sql,"mysql:",6)==0){ const char *sql6 = sql+6; sqls.mysqls.clear(); sqls.mysqls.push_back(ONESQL(sql6)); tdslib_debug (D_CLIMSG,connection_id,login,"MYSQL(mysql:) %s\n",sql6); nextsql(flog,login); }else if (opts.mysql_mode){ sqls.mysqls.clear(); if (strncasecmp(sql,"set mysql off",13)==0){ opts.mysql_mode = false; tdslib_send_msg(w,login,NULL); sqls.one_result=true; state = STATE_FETCHDONE; }else{ sqls.mysqls.push_back(ONESQL(sql)); tdslib_debug (D_CLIMSG,connection_id,login,"MYSQL(mysql_mode:) %s\n",sql); } nextsql(flog,login); }else if (strstr(sql,"sp_serverinfo server_csname")!=NULL){ state = STATE_NONE; tds_fakeresult (w,login,opts,"name","iso_1"); nextsql(flog,login); }else{ PARSE p (sql); EXPR expr; sqls.mysqls.clear(); opts.logthis = false; if (f_main(p,expr)==-1){ opts.sqlerror = ERROR_PARSER; string msg ("LASUITE: parsing error near '"); unsigned last = p.getlastpos(); string sqltmp(sql); strip_end (sqltmp); int len = sqltmp.size()-last; if (len > 30) len = 30; string tmp; if (len > 0){ tmp = string(sqltmp.c_str()+last,len); }else{ tmp = "EOL"; } msg += tmp; msg += "'"; tdslib_debug(D_CLIMSG,connection_id,login,msg.c_str()); tdslib_err_query(w,login,msg.c_str(),opts.sqlerror); tdslib_logerr (flog.ferror,spid,login,msg.c_str(),sql,NULL); nberrparse++; freewriter(); }else if (parser_expr_reformat (expr,sqls.mysqls,opts,spid) == -1){ opts.sqlerror = ERROR_REFORMAT; string msg = string("LASUITE: reformat error '") + opts.format_error + "'"; tdslib_debug(D_CLIMSG,connection_id,login,msg.c_str()); tdslib_err_query(w,login,msg.c_str(),opts.sqlerror); tdslib_logerr (flog.ferror,spid,login,msg.c_str(),sql,NULL); nberrformat++; freewriter(); }else{ if (opts.logthis && flog.fspc != NULL){ fprintf (flog.fspc,"***%d %s,%s,%s,%s,%s db=%s logthis\n%s\n",connection_id ,login.user.c_str(),login.program.c_str(),login.process.c_str() ,login.host.c_str() ,login.server.c_str(),login.cur_db.c_str() ,sql); } sybsql = sql; nextsql(flog,login); } } } } }; static void tds_set_renamedb(const vector &renamedb, map &backmap) { bool some_errors = false; for (unsigned i=0; itm_year+1900,tt->tm_mon+1,tt->tm_mday ,tt->tm_hour,tt->tm_min,tt->tm_sec); } // String compare without spaces (not perfect when there is a quote) struct string_nospace{ string s; void fill (const char *pt){ s.clear(); bool lastspace = true; // Was the last character a space ? bool paren = false; // Was the last non space char a parenthese while (*pt != '\0'){ if (*pt > ' '){ if (*pt == '(' || *pt == ')' || *pt == ',' || *pt == '='){ // We do not keep spaces around () lastspace = false; paren = true; }else{ paren = false; } if (lastspace) s += ' '; s += *pt; lastspace = false; }else{ if (!paren) lastspace = true; } pt++; } // Trim the spaces at the end size_t last = s.size()-1; while (last >= 0){ if (!isspace(s[last])) break; last--; } s.resize(last+1); } string_nospace(){} string_nospace(const char *_s){ fill (_s); } string_nospace(const string &_s){ fill (_s.c_str()); } bool operator < (const string_nospace &ns) const { return s < ns.s; } const char *c_str() const { return s.c_str(); } }; struct SPCSQL{ string name; int usage_count; string_nospace original; string replace; string regex_str; // Original transformed into valid regex vector parts; regex_t reg; int nbvar; SPCSQL(){ nbvar = 0; usage_count = 0; } SPCSQL(const SPCSQL &n){ nbvar = n.nbvar; usage_count = n.usage_count; name = n.name; original = n.original; replace = n.replace; regex_str = n.regex_str; parts = n.parts; if (nbvar > 0){ int ok = regcomp(®,regex_str.c_str(),REG_ICASE|REG_EXTENDED); if (ok != 0){ tlmp_error ("Can't compute the regular expression %s\n",regex_str.c_str()); } //printf ("Recomp nbvar=%d ok=%d\n",nbvar,ok); } } ~SPCSQL(){ if (nbvar>0) regfree (®); } }; static void tds_addregexp(vector &spcsql, const string &name, const string &origsql, const string &replsql) { SPCSQL spc; spc.name = name; spc.original = origsql; spc.replace = replsql; const char *sql = spc.original.c_str(); string tmp ("^"); while (*sql != '\0'){ if (*sql == '(' || *sql == ')' || *sql == '\\' || *sql == '|' || *sql == '*' || *sql == '+' || *sql == '?' || *sql == '[' || *sql == ']'){ tmp += string("\\") + *sql; sql++; }else if (strncmp(sql,"###",3)==0){ tmp += "(.*)"; sql += 3; spc.nbvar++; }else{ tmp += *sql++; } } tmp += "$"; // We make sure the regex will be a full match by enclosing it with ^ and $ // printf ("nbvar=%d %s\n",spc.nbvar,tmp.c_str()); if (spc.nbvar > 0){ spc.regex_str = tmp; int ok = regcomp(&spc.reg,spc.regex_str.c_str(),REG_ICASE|REG_EXTENDED); if (ok != 0){ tlmp_error ("Can't compile regex: %s\n",tmp.c_str()); }else{ sql = replsql.c_str(); const char *start = sql; while (*sql != '\0'){ if (strncmp(sql,"###",3)==0){ spc.parts.push_back(string(start,sql-start)); const char *pt = str_skipdig(sql + 3); spc.parts.push_back(string(sql,pt-sql)); start = pt; sql = pt; }else{ sql++; } } if (sql > start){ spc.parts.push_back(string(start,sql-start)); } } } spcsql.push_back(spc); } static bool tds_replacesql (vector &spcsql, const char *sql, string &res, bool debug) { bool ret = false; string_nospace ss(sql); const char *evalsql = ss.c_str(); //printf ("SQL = :%s:\n",evalsql); for (vector::iterator it = spcsql.begin(); it != spcsql.end(); it++){ if (debug) printf ("nbvar=%d eq=%d :%-40.40s:\n",it->nbvar ,strcmp(evalsql,it->original.c_str())==0,it->original.c_str()); if (it->nbvar == 0){ if (strcmp(it->original.c_str(),evalsql)==0){ ret = true; res = it->replace; it->usage_count++; break; } }else{ regmatch_t tbm[20]; int ok = regexec(&it->reg,evalsql,20,tbm,0); if (debug) printf ("regexec ok=%d\n",ok); if (ok == 0){ res = ""; unsigned size = it->parts.size(); for (unsigned i=0; iparts[i]; if (strncmp(part.c_str(),"###",3)==0){ int s = atoi(part.c_str()+3); regmatch_t &rm = tbm[s]; int len = rm.rm_eo-rm.rm_so; res += string(evalsql+rm.rm_so,len); }else{ res += part; } } it->usage_count++; ret = true; #if 0 s->sendf ("RES: %s\n",tmp.c_str()); for (int i=1; i<20; i++){ if (tbm[i].rm_so == -1) break; int len = tbm[i].rm_eo-tbm[i].rm_so; s->sendf ("%d %d %*.*s\n",tbm[i].rm_so,tbm[i].rm_eo ,len,len,evalsql+tbm[i].rm_so); } #endif break; }else{ if (debug) printf ("regstr %s\n---\n%s\n",it->regex_str.c_str(),evalsql); } } } return ret; } static void tds_readtranssql(const char *transsql, vector &spcsql) { if (transsql != NULL){ spcsql.clear(); glocal vector *spcsql = &spcsql; glocal string original,buf; glocal string name; (transsql,true); if (line[0] == '#'){ if (glocal.buf.size() > 0){ tds_addregexp (*glocal.spcsql,glocal.name,glocal.original,glocal.buf); } glocal.name = str_skip(line+1); glocal.buf.clear(); }else if (line[0] == '!'){ glocal.original = glocal.buf; glocal.buf.clear(); }else{ if (glocal.buf.size() > 0) glocal.buf += " "; glocal.buf += line; } return true; if (glocal.buf.size() > 0){ tds_addregexp (*glocal.spcsql,glocal.name,glocal.original,glocal.buf); } } } static void tds_load_connect_script (const char *connect_script, vector &sqls) { sqls.clear(); if (connect_script != NULL){ glocal vector *sqls = &sqls; (connect_script,true); line = str_skip(line); if (*line != '\0' && *line != '#'){ glocal.sqls->push_back (line); } return 0; } } int main (int argc, char *argv[]) { glocal const char *bind = NULL; glocal const char *port = "4101"; glocal const char *control = "/var/run/tds.sock"; glocal const char *user = "lasuite"; glocal const char *mysqlserver = NULL; glocal const char *defaultdb = "test"; glocal vector renamedb; glocal vector usertypes; glocal const char *fusertypes = NULL; glocal const char *logsession = NULL; glocal const char *logerror = NULL; glocal const char *logsql = NULL; glocal const char *logspc = NULL; glocal const char *transsql = NULL; glocal const char *nospace = NULL; glocal const char *superuser = "root"; glocal const char *superpass = ""; glocal const char *connect_script = NULL; glocal bool daemon = false; glocal const char *pidfile = "/var/run/tds.pid"; glocal bool synclog = false; glocal int spid_start = 1; glocal int spid_end = 32000; glocal int ret = -1; signal (SIGCHLD,SIG_IGN); glocal.ret = (argc,argv,"tlmpsql"); setproginfo ("tds",VERSION,"Sybase to Mysql translator\n"); setarg ('b',"bind","Bind on this IP",glocal.bind,false); setarg ('p',"sybaseport","Listen on this port for sybase client",glocal.port,false); setarg (' ',"control","Unix socket port to conrol and monitor this program",glocal.control,false); setarg ('S',"mysqlserver","MySQL server",glocal.mysqlserver,true); setgrouparg ("Process"); setarg ('d',"daemon","Run in background",glocal.daemon,false); setarg ('u',"user","Run as this user",glocal.user,false); setarg (' ',"pidfile","PID file",glocal.pidfile,false); setgrouparg ("Misc."); setarg (' ',"usertype","Define a usertype sybase=mysql",glocal.usertypes,false); setarg (' ',"usertypes","CSV file for user defined types(user -> sybase)",glocal.fusertypes,false); setarg (' ',"renamedb","Rename database old=new",glocal.renamedb,false); setarg (' ',"transsql","Translation/replacement for some SQL request (sybase to mysql)",glocal.transsql,false); setarg (' ',"connect_script","SQL to execute at each connection",glocal.connect_script,false); setarg (' ',"spid_start","First available SPID for connections",glocal.spid_start,false); setarg (' ',"spid_end","Last available SPID for connections",glocal.spid_end,false); setgrouparg ("Log files"); setarg (' ',"logsession","File holding sessions stats",glocal.logsession,false); setarg (' ',"logerror","File holding errors",glocal.logerror,false); setarg (' ',"logsql","File holding all SQL requests",glocal.logsql,false); setarg (' ',"logspc","File holding all special SQL requests",glocal.logspc,false); setarg (' ',"synclog","Force a fflush on all log file, often",glocal.synclog,false); setgrouparg ("Super user"); setarg (' ',"superuser","MySQL super user account (used to cancel query)",glocal.superuser,false); setarg (' ',"superpass","MySQL super user password",glocal.superpass,false); setgrouparg ("debug"); setarg (' ',"nospace","Remove extra spaces in SQL file",glocal.nospace,false); if (glocal.daemon){ syslog (LOG_ERR,"%s",msg); }else{ fprintf (stderr,"%s",msg); } if (glocal.daemon){ syslog (LOG_WARNING,"%s",msg); }else{ fprintf (stderr,"%s",msg); } glocal vector spcsql; glocal vector connect_sqls; // SQL to send at each connection (connect_script) glocal deque spids; // Alloc spid for each connection; glocal FILE_LOG flog; glocal.flog.fsession = glocal.flog.open("log session",glocal.logsession); glocal.flog.ferror = glocal.flog.open("log error",glocal.logerror); glocal.flog.fsql = glocal.flog.open("log sql",glocal.logsql); glocal.flog.fspc = glocal.flog.open("log sql special",glocal.logspc); for (short i=glocal.spid_start; i(glocal.nospace,false); glocal.sql += line; return 0; string_nospace ss(glocal.sql); printf ("%s\n",ss.c_str()); exit(0); } tds_readtranssql(glocal.transsql,glocal.spcsql); parser_initfunctions(); parser_inittokens(); tds_set_renamedb (glocal.renamedb,renamed_db); tdslib_setusertypes(glocal.usertypes); tdslib_loadusertypes(glocal.fusertypes); int ret = ("tds",glocal.bind,glocal.port,glocal.control,glocal.user,glocal.daemon,glocal.pidfile); int ret = 0; bool sessions = words[0] == "sessions"; bool debug = words[0] == "condebug"; bool pending = words[0] == "pending"; if (sessions || debug || pending){ unsigned id; TDS_LOGIN login; ARRAY_OBJ *o = iter_init(id,login); time_t now = time(NULL); while (o != NULL){ TDS_INFO *n = (TDS_INFO*)o; if (sessions){ char lastreq[20]; tds_formattime(n->last,lastreq); char logstr[100]; snprintf (logstr,sizeof(logstr)-1,"%s,%s,%s,%s,%s" ,login.user.c_str() ,login.program.c_str() ,login.process.c_str() ,login.host.c_str() ,login.server.c_str()); s->sendf ("id=%-3u,%-5d login=%-60s db=%-15s querys=%-4u errors=%u,%u,%u last=%s\n" ,id,n->connection_id ,logstr ,login.cur_db.c_str() ,n->nbquery ,n->nberrparse,n->nberrformat,n->nberrmysql ,lastreq); }else if (pending){ if (n->state == STATE_REQUESTING || n->state == STATE_FETCHING){ const char *lastsql = ""; if (n->sqls.nosql > 0 && n->sqls.nosql <= n->sqls.mysqls.size()){ lastsql = n->sqls.mysqls[n->sqls.nosql-1].sql.c_str(); } s->sendf ("id=%u,%d state=%d login=%s,%s,%s,%s db=%s duration=%d %s\n" ,id,n->connection_id,n->state ,login.user.c_str(),login.program.c_str() ,login.host.c_str() ,login.server.c_str(),login.cur_db.c_str() ,now-n->last,lastsql); } }else{ s->sendf("id=%u,%d state=%d nosql=%d/%lu exstat.size=%lu" " if_statement=%d skip=%d one_result=%d numrows=%ld\n" ,id,n->connection_id ,n->state ,n->sqls.nosql,n->sqls.mysqls.size() ,n->sqls.exstat.size() ,n->sqls.if_statement ,n->sqls.exstat.top().skip ,n->sqls.one_result ,n->sqls.numrows); } o = iter_next(id,login); } }else if (words[0] == "fflush"){ glocal.flog.fflush (); }else if (words[0] == "patchusage"){ s->sendf ("function_datediff %u\n",pusage.function_datediff); s->sendf ("function_convert %u\n",pusage.function_convert); s->sendf ("function_object_id %u\n",pusage.function_object_id); s->sendf ("function_object_name %u\n",pusage.function_object_name); s->sendf ("function_textptr %u\n",pusage.function_textptr); s->sendf ("function_db_id %u\n",pusage.function_db_id); s->sendf ("setuser %u\n",pusage.setuser); s->sendf ("error %u\n",pusage.error); s->sendf ("no_default_on_create %u\n",pusage.no_default_on_create); s->sendf ("grant %u\n",pusage.grant); s->sendf ("exec %u\n",pusage.exec); s->sendf ("alter %u\n",pusage.alter); s->sendf ("old_outer_join %u\n",pusage.old_outer_join); s->sendf ("skip_order_by %u\n",pusage.skip_order_by); for (unsigned i=0; isendf ("spcsql %s: %d\n",it.name.c_str(),it.usage_count); } }else if (words[0] == "readtrans"){ tds_readtranssql (glocal.transsql,glocal.spcsql); s->sendf ("%lu SPCSQL read\n",glocal.spcsql.size()); }else if (words[0] == "readscript"){ tds_load_connect_script (glocal.connect_script,glocal.connect_sqls); s->sendf ("%lu connect_script read\n",glocal.connect_sqls.size()); }else if (words[0] == "testtrans"){ // Test the patten matching of transactions string res; string_nospace nsp(line+10); #if 0 s->sendf ("No_space-X: %s\n",nsp.c_str()); for (unsigned i=0; isendf ("No_space-%u: %d %s\n",i ,strcmp(nsp.c_str(),ori)==0 ,ori); } #endif if (tds_replacesql(glocal.spcsql,line+10,res,true)){ s->sendf ("res=%s\n",res.c_str()); }else{ s->sendf ("No match\n"); } }else if (words.size() == 1 && words[0] == "closedatalog"){ if (glocal.flog.fdata != NULL){ fclose (glocal.flog.fdata); glocal.flog.fdata = NULL; } }else if (words.size() == 2 && words[0] == "opendatalog"){ if (glocal.flog.fdata != NULL) fclose (glocal.flog.fdata); glocal.flog.fdata = glocal.flog.open("log data",words[1].c_str()); }else if (words.size() == 1 && words[0] == "debugon"){ debug_seton(); }else if (words.size() == 1 && words[0] == "debugoff"){ debug_setoff(); }else if (words.size() == 2 && words[0] == "debugfile"){ debug_setfdebug (words[1].c_str()); }else if (words.size() == 1 && words[0] == "reopenlog"){ glocal.flog.closeall(); glocal.flog.fsession = glocal.flog.open("log session",glocal.logsession); glocal.flog.ferror = glocal.flog.open("log error",glocal.logerror); glocal.flog.fsql = glocal.flog.open("log sql",glocal.logsql); glocal.flog.fspc = glocal.flog.open("log sql special",glocal.logspc); }else{ ret = -1; } return ret; TDS_INFO *n = new TDS_INFO; n->ipaddr = info.ipaddr; mysql_init(&n->mysql); mysql_options(&n->mysql, MYSQL_OPT_NONBLOCK, 0); info.data = n; n->client_fd = client_fd; n->spid = glocal.spids[0]; glocal.spids.pop_front(); TDS_INFO *n = (TDS_INFO*)info.data; if (glocal.flog.fsession != NULL){ char start_str[20],end_str[20]; tds_formattime (n->start,start_str); time_t now = time(NULL); tds_formattime (now,end_str); fprintf (glocal.flog.fsession,"%s %s id=%05u login=%s,%s,%s,%s,%s db=%s querys=%u errors=%u,%u,%u\n" ,start_str,end_str ,n->connection_id ,login.user.c_str(),login.program.c_str(),login.process.c_str() ,login.host.c_str() ,login.server.c_str(),login.cur_db.c_str() ,n->nbquery ,n->nberrparse,n->nberrformat,n->nberrmysql); fflush (glocal.flog.fsession); } if (n->spid != -1){ glocal.spids.push_back(n->spid); n->cleanup(glocal.flog,login); } // The tds server is about to quit // Update the pseudo table sysprocesses unsigned id; TDS_LOGIN login; ARRAY_OBJ *o = iter_init(id,login); while (o != NULL){ TDS_INFO *n = (TDS_INFO*)o; n->cleanup(glocal.flog,login); o = iter_next(id,login); } TDS_INFO *n = (TDS_INFO*)info.data; tdslib_debug (D_CLIMSG,id,login,"LOGIN user=%s password=%s\n",login.user.c_str(),login.password.c_str()); n->status = mysql_real_connect_start(&n->ret, &n->mysql, glocal.mysqlserver, login.user.c_str(), login.password.c_str() , NULL, 0, NULL, CLIENT_FOUND_ROWS|CLIENT_MULTI_RESULTS); TDS_INFO *ns = new TDS_INFO; ns->client = n; n->client = ns; n->state = STATE_CONNECTING; link(mysql_get_socket(&n->mysql),ns); TDS_INFO *n = (TDS_INFO*)info.data; n->last = time(NULL); n->brecs = &brecs; tdslib_debug (D_CLIMSG,n->connection_id,login,"SQL :%s:\n",sql); string repl; if (tds_replacesql(glocal.spcsql,sql,repl,false)){ debug_printf (K_REPL,"replacesql %s -> %s\n",sql,repl.c_str()); sql = repl.c_str(); } if (n->w != NULL){ // We are already working on a request, we have to wait tdslib_debug (D_CLIMSG,n->connection_id,login,"reqsql busy: %s\n",sql); n->busysql = sql; }else{ n->reqsql(sql,glocal.flog,login); } if (glocal.synclog) glocal.flog.fflush (); TDS_INFO *n = (TDS_INFO*)info.data; if (n->sqls.bulktable.empty()){ // We are here because of a writetext, that we do not support n->allocwriter(); tdslib_rep_other(n->w,login); n->freewriter(); }else if (brecs.recs.size()==0){ // Oddly, it is possible to have an empty bulk request n->allocwriter(); tdslib_rep_other(n->w,login); n->freewriter(); }else{ string request = string_f ("mysql: insert into %s values ",n->sqls.bulktable.c_str()); const char *openpar = "("; for (vector >::iterator it = brecs.recs.begin(); it != brecs.recs.end(); it++){ //printf ("TDSBULK:"); request += openpar; unsigned nb = it->size(); const char *comma = ""; for (unsigned j=0; jreqsql(request.c_str(),glocal.flog,login); } // Do nothing for now, just reply TDS_INFO *n = (TDS_INFO*)info.data; debug_printf (K_OTHER,"Cancel state=%d connection_id=%d\n",n->state,n->connection_id); if (n->w == NULL){ // We are not doing anything, odd, but we reply anyway n->allocwriter(); tdslib_rep_cancel(n->w,login); n->freewriter(); }else{ tdslib_rep_cancel(n->w,login); n->freewriter(); // From now on, n->w is NULL, so results received from MySQL should be // discarded // Now we send a kill request in background char tmp[100]; snprintf (tmp,sizeof(tmp)-1,"kill query %d",n->connection_id); tds_super(glocal.superuser,glocal.superpass,glocal.mysqlserver,tmp); } printf ("Option %s\n",sql); tdslib_rep_action(client_fd,login,0); TDS_INFO *ns = (TDS_INFO*)info.data; TDS_INFO *n = ns->client; tdslib_debug (D_SRVMSG,n->connection_id,login,"SERVERRES %d\n",n->state); if (n->state == STATE_CONNECTING){ n->status = mysql_real_connect_cont(&n->ret,&n->mysql,n->status); if (!n->status){ if (n->ret != NULL){ // Send the connection script for (unsigned i=0; imysql,sql); } // login successfull tdslib_rep_login(client_fd,login,true); // Update the pseudo table sysprocesses vector > rows; vector fdefs; tds_mysql (n->mysql,"select connection_id()",rows,fdefs); if (rows.size()==1 && rows[0].size()==1){ n->connection_id = atoi(rows[0][0].c_str()); n->client->connection_id = n->connection_id; //printf ("Connection_id = %d\n",n->connection_id); }else{ tlmp_error ("Invalid result from connection_id()\n"); } char ip[20]; ipnum_ip2a (n->ipaddr,ip); char tmp[400]; snprintf (tmp,sizeof(tmp)-1,"insert into master.sysprocesses" " (spid,kpid,program_name,loggedindatetime,clienthostname,hostprocess,ipaddr)" " values (%d,%d,'%s',now(),'%s','%s','%s')" ,n->spid ,n->connection_id ,login.program.c_str(),login.host.c_str(),login.process.c_str() ,ip); tds_mysql (n->mysql,tmp); tdslib_debug (D_CLIMSG,n->connection_id,login,"LOGIN OK\n"); }else{ tdslib_debug (D_CLIMSG,id,login,"LOGIN FAIL\n"); tdslib_rep_login(client_fd,login,false); } n->state = STATE_NONE; } }else if (n->state == STATE_REQUESTING){ int err; n->status = mysql_real_query_cont(&err, &n->mysql, n->status); tdslib_debug (D_CLIMSG,n->connection_id,login,"MYSQL REQUESTING nosql=%d status=%d row=%p\n",n->sqls.nosql,n->status,n->row); if (!n->status){ n->querydone(glocal.flog,login,err); } }else if (n->state == STATE_FETCHING){ n->fetch(login); if (n->state == STATE_FETCHDONE){ tdslib_logsql (glocal.flog.fsql,n->spid,login,n->sqls.start,n->sqls.numrows,n->sqls.mysqls[n->sqls.nosql-1].sql.c_str()); n->nextsql(glocal.flog,login); } } if (glocal.synclog) glocal.flog.fflush (); return ret; return glocal.ret; }