diff --git a/cdb2api/cdb2api.c b/cdb2api/cdb2api.c index 16950657ed..db35e29dec 100644 --- a/cdb2api/cdb2api.c +++ b/cdb2api/cdb2api.c @@ -2936,6 +2936,8 @@ static int cdb2_convert_error_code(int rc) return CDB2ERR_DUPLICATE; case CDB2__ERROR_CODE__PREPARE_ERROR_OLD: return CDB2ERR_PREPARE_ERROR; + case CDB2__ERROR_CODE__INCOMPLETE: + return CDB2ERR_INCOMPLETE; default: return rc; } @@ -7279,20 +7281,18 @@ char *cdb2_string_escape(cdb2_hndl_tp *hndl, const char *src) } int cdb2_get_property(cdb2_hndl_tp *hndl, const char *key, char **value) { - if (hndl == NULL) + if (hndl == NULL) { + return CDB2ERR_NOSTATEMENT; + } else if (strcmp(key, "sql:tail") != 0) { + return CDB2ERR_UNKNOWN_PROPERTY; + } else if (hndl->firstresponse == NULL) { return CDB2ERR_NOSTATEMENT; - *value = NULL; - if (strcmp(key, "sql:tail") == 0) { - if (hndl->firstresponse == NULL) - return CDB2ERR_NOSTATEMENT; - if (!hndl->firstresponse->has_sql_tail_offset) { - return CDB2ERR_OLD_SERVER; - } - char *str = malloc(20); - sprintf(str, "%d", hndl->firstresponse->sql_tail_offset); - *value = str; + } else if (!hndl->firstresponse->has_sql_tail_offset) { + return CDB2ERR_OLD_SERVER; + } else { + *value = malloc(20); + if (!value) { return ENOMEM; } + sprintf(*value, "%d", hndl->firstresponse->sql_tail_offset); return 0; } - else - return CDB2ERR_UNKNOWN_PROPERTY; } diff --git a/cdb2api/cdb2api.h b/cdb2api/cdb2api.h index 7fa5de2180..e4ac413f70 100644 --- a/cdb2api/cdb2api.h +++ b/cdb2api/cdb2api.h @@ -64,7 +64,6 @@ enum cdb2_errors { CDB2ERR_BADSTATE = -8, CDB2ERR_ASYNCERR = -9, CDB2_OK_ASYNC = -10, - CDB2ERR_INCOMPLETE = -11, CDB2ERR_INVALID_ID = -12, CDB2ERR_RECORD_OUT_OF_RANGE = -13, @@ -110,6 +109,7 @@ enum cdb2_errors { CDB2ERR_CHECK_CONSTRAINT = 403, CDB2ERR_DIST_ABORT = 410, CDB2ERR_QUERY_REJECTED = 451, + CDB2ERR_INCOMPLETE = 452, CDB2ERR_UNKNOWN = 300 @@ -272,6 +272,8 @@ char *cdb2_string_escape(cdb2_hndl_tp *hndl, const char *str); int cdb2_send_2pc(cdb2_hndl_tp *hndl, char *dbname, char *pname, char *ptier, char *source, unsigned int op, char *dist_txnid, int rcode, int outrc, char *errmsg, int async); +// on success, `*value` points to dynamically allocated memory +// that must be freed by the caller. int cdb2_get_property(cdb2_hndl_tp *hndl, const char *key, char **value); typedef enum cdb2_event_ctrl { diff --git a/db/sql.h b/db/sql.h index 0a7cd05cda..de82a23cea 100644 --- a/db/sql.h +++ b/db/sql.h @@ -364,6 +364,7 @@ enum { XRESPONSE(RESPONSE_ERROR_PREPARE) \ XRESPONSE(RESPONSE_ERROR_PREPARE_RETRY) \ XRESPONSE(RESPONSE_ERROR_REJECT) \ + XRESPONSE(RESPONSE_ERROR_INCOMPLETE) \ XRESPONSE(RESPONSE_REDIRECT_FOREIGN) \ XRESPONSE(RESPONSE_FLUSH) \ XRESPONSE(RESPONSE_HEARTBEAT) \ diff --git a/db/sqlinterfaces.c b/db/sqlinterfaces.c index 3f987b88af..6311e55ed1 100644 --- a/db/sqlinterfaces.c +++ b/db/sqlinterfaces.c @@ -2911,7 +2911,11 @@ static void _prepare_error(struct sqlthdstate *thd, } reqlog_logf(thd->logger, REQL_TRACE, "sqlite3_prepare failed %d: %s\n", rc, errstr); - errstat_set_rcstrf(err, ERR_PREPARE, "%s", errstr); + + const int err_code = rc == SQLITE_MISSING_SEMI + ? RESPONSE_ERROR_INCOMPLETE : ERR_PREPARE; + + errstat_set_rcstrf(err, err_code, "%s", errstr); if (clnt->saved_errstr) { free(clnt->saved_errstr); } @@ -3137,6 +3141,10 @@ static int get_prepared_stmt_int(struct sqlthdstate *thd, if (prepareOnly || sqlite3_is_prepare_only(clnt)) sqlPrepFlags |= SQLITE_PREPARE_ONLY; + if (clnt->multiline) { + sqlPrepFlags |= SQLITE_PREPARE_REQUIRE_SEMI; + } + if (!gbl_allow_pragma) flags |= PREPARE_DENY_PRAGMA; @@ -4048,6 +4056,8 @@ int handle_sqlite_requests(struct sqlthdstate *thd, struct sqlclntstate *clnt) if (irc == ERR_PREPARE) { write_response(clnt, RESPONSE_ERROR_PREPARE, err.errstr, 0); handle_sqlite_error(thd, clnt, &rec, rc); + } else if (irc == RESPONSE_ERROR_INCOMPLETE) { + write_response(clnt, RESPONSE_ERROR_INCOMPLETE, err.errstr, 0); } else if (irc == ERR_PREPARE_RETRY) { write_response(clnt, RESPONSE_ERROR_PREPARE_RETRY, err.errstr, 0); rc = 0; @@ -6347,6 +6357,7 @@ int sql_check_errors(struct sqlclntstate *clnt, sqlite3 *sqldb, case SQLITE_NO_TEMPTABLES: case SQLITE_NO_TABLESCANS: case SQLITE_ANALYZE_ALREADY_RUNNING: + case SQLITE_PREPARE_REQUIRE_SEMI: *errstr = sqlite3_errmsg(sqldb); break; @@ -6520,6 +6531,8 @@ int sqlserver2sqlclient_error(int rc) return CDB2ERR_PREPARE_ERROR; case SQLITE_ANALYZE_ALREADY_RUNNING: return CDB2ERR_ANALYZE_ALREADY_RUNNING; + case SQLITE_MISSING_SEMI: + return RESPONSE_ERROR_INCOMPLETE; default: return CDB2ERR_UNKNOWN; } diff --git a/plugins/newsql/newsql.c b/plugins/newsql/newsql.c index 820868f971..2863a3a886 100644 --- a/plugins/newsql/newsql.c +++ b/plugins/newsql/newsql.c @@ -1071,6 +1071,7 @@ static int newsql_write_response(struct sqlclntstate *c, int t, void *a, int i) case RESPONSE_ERROR_BAD_STATE: return newsql_error(c, a, CDB2__ERROR_CODE__BADSTATE); case RESPONSE_ERROR_PREPARE: return newsql_error(c, a, CDB2__ERROR_CODE__PREPARE_ERROR); case RESPONSE_ERROR_REJECT: return newsql_error(c, a, CDB2__ERROR_CODE__REJECTED); + case RESPONSE_ERROR_INCOMPLETE: return newsql_error(c, a, CDB2__ERROR_CODE__INCOMPLETE); case RESPONSE_REDIRECT_FOREIGN: return newsql_redirect_foreign(c, a, i); case RESPONSE_FLUSH: return c->plugin.flush(c); case RESPONSE_HEARTBEAT: return newsql_heartbeat(c); @@ -2053,6 +2054,7 @@ int process_set_commands(struct sqlclntstate *clnt, CDB2SQLQUERY *sql_query) clnt->return_long_column_names = 0; } else { clnt->return_long_column_names = 1; + } } else if (strncasecmp(sqlstr, "multiline", 9) == 0) { sqlstr += 10; sqlstr = skipws(sqlstr); diff --git a/protobuf/sqlresponse.proto b/protobuf/sqlresponse.proto index 41aec6e6f4..479e9971db 100644 --- a/protobuf/sqlresponse.proto +++ b/protobuf/sqlresponse.proto @@ -1,18 +1,18 @@ -// package com.bloomberg.cdb2; +//package com.bloomberg.cdb2; syntax = "proto2"; option java_package = "com.bloomberg.comdb2.jdbc"; enum CDB2_ColumnType { - INTEGER = 1; - REAL = 2; - CSTRING = 3; - BLOB = 4; - DATETIME = 6; - INTERVALYM = 7; - INTERVALDS = 8; - DATETIMEUS = 9; - INTERVALDSUS = 10; + INTEGER = 1; + REAL = 2; + CSTRING = 3; + BLOB = 4; + DATETIME = 6; + INTERVALYM = 7; + INTERVALDS = 8; + DATETIMEUS = 9; + INTERVALDSUS = 10; } /* @@ -22,170 +22,168 @@ Ideally only sqlresponse.proto codes should be sent to client, and client should But if both codes are the same (as it is currently), then everything will still run correctly */ enum CDB2_ErrorCode { - OK = 0; - DUP_OLD = 1; - CONNECT_ERROR = -1; - NOTCONNECTED = -2; - - PREPARE_ERROR = -3; - PREPARE_ERROR_OLD = 1003; - - IO_ERROR = -4; - INTERNAL = -5; - NOSTATEMENT = -6; - BADCOLUMN = -7; - BADSTATE = -8; - ASYNCERR = -9; - OK_ASYNC = -10; - - INVALID_ID = -12; - RECORD_OUT_OF_RANGE = -13; - - REJECTED = -15; - STOPPED = -16; - BADREQ = -17; - DBCREATE_FAILED = -18; - - THREADPOOL_INTERNAL = -20; /* some error in threadpool code */ - READONLY = -21; - - NOMASTER = -101; - NOTSERIAL = 230; - SCHEMACHANGE = 240; - UNTAGGED_DATABASE = -102; - CONSTRAINTS = -103; - DEADLOCK = 203; - - TRAN_IO_ERROR = -105; - ACCESS = -106; + OK = 0; + DUP_OLD = 1; + CONNECT_ERROR = -1; + NOTCONNECTED = -2; + + PREPARE_ERROR = -3; + PREPARE_ERROR_OLD = 1003; + + IO_ERROR = -4; + INTERNAL = -5; + NOSTATEMENT = -6; + BADCOLUMN = -7; + BADSTATE = -8; + ASYNCERR = -9; + OK_ASYNC = -10; + + INVALID_ID = -12; + RECORD_OUT_OF_RANGE = -13; + + REJECTED = -15; + STOPPED = -16; + BADREQ = -17; + DBCREATE_FAILED = -18; + + THREADPOOL_INTERNAL = -20; /* some error in threadpool code */ + READONLY = -21; + APPSOCK_LIMIT = -23; + + NOMASTER = -101; + NOTSERIAL = 230; + SCHEMACHANGE = 240; + UNTAGGED_DATABASE = -102; + CONSTRAINTS = -103; + DEADLOCK = 203; + + TRAN_IO_ERROR = -105; + ACCESS = -106; QUERYLIMIT = -107; MASTER_TIMEOUT = -109; WRONG_DB = -111; - VERIFY_ERROR = 2; - FKEY_VIOLATION = 3; - NULL_CONSTRAINT = 4; - - CONV_FAIL = 113; - NONKLESS = 114; - MALLOC = 115; - NOTSUPPORTED = 116; - - TRAN_TOO_BIG = 202; - DUPLICATE = 299; - TZNAME_FAIL = 401; - CHANGENODE = 402; - UNKNOWN = 300; + VERIFY_ERROR = 2; + FKEY_VIOLATION = 3; + NULL_CONSTRAINT = 4; + + CONV_FAIL = 113; + NONKLESS = 114; + MALLOC = 115; + NOTSUPPORTED = 116; + + TRAN_TOO_BIG = 202; + DUPLICATE = 299; + TZNAME_FAIL = 401; + CHANGENODE = 402; + INCOMPLETE = 452; + UNKNOWN = 300; } enum ResponseHeader { - SQL_RESPONSE_HEARTBEAT = 205; // This is same as FSQL_HEARTBEAT - SQL_RESPONSE = 1002; - DBINFO_RESPONSE = 1005; - SQL_EFFECTS = 1006; - SQL_RESPONSE_PING = 1007; // SQL_RESPONSE + requesting ack - SQL_RESPONSE_PONG = 1008; - SQL_RESPONSE_TRACE = 1009; - SQL_RESPONSE_SSL = 1010; // SSL header + empty payload to trigger - // client SSL - DISTTXN_RESPONSE = 1011; + SQL_RESPONSE_HEARTBEAT = 205; // This is same as FSQL_HEARTBEAT + SQL_RESPONSE = 1002; + DBINFO_RESPONSE = 1005; + SQL_EFFECTS = 1006; + SQL_RESPONSE_PING = 1007; // SQL_RESPONSE + requesting ack + SQL_RESPONSE_PONG = 1008; + SQL_RESPONSE_TRACE = 1009; + SQL_RESPONSE_SSL = 1010; // SSL header + empty payload to trigger + // client SSL + DISTTXN_RESPONSE = 1011; } enum ResponseType { - COLUMN_NAMES = 1; + COLUMN_NAMES = 1; COLUMN_VALUES = 2; - LAST_ROW = 3; - COMDB2_INFO = 4; // For info about features, or snapshot file/offset etc - SP_TRACE = 5; - SP_DEBUG = 6; - SQL_ROW = 7; + LAST_ROW = 3; + COMDB2_INFO = 4; // For info about features, or snapshot file/offset etc + SP_TRACE = 5; + SP_DEBUG = 6; + SQL_ROW = 7; } enum CDB2SyncMode { - SYNC = 1; - ASYNC = 2; - SYNC_ROOM = 3; - SYNC_N = 4; - SYNC_SOURCE = 5; - SYNC_UNKNOWN = 6; + SYNC = 1; + ASYNC = 2; + SYNC_ROOM = 3; + SYNC_N = 4; + SYNC_SOURCE = 5; + SYNC_UNKNOWN = 6; } enum CDB2ServerFeatures { - SKIP_INTRANS_RESULTS = 1; + SKIP_INTRANS_RESULTS = 1; } message CDB2_DBINFORESPONSE { - message nodeinfo { - required string name = 1; - required int32 number = 2; - required int32 incoherent = 3; - optional int32 room = 4; - optional int32 port = 5; - } - required nodeinfo master = 1; - repeated nodeinfo nodes = - 2; // These are replicants, we need to parse through these only. - optional bool require_ssl = 3; - optional CDB2SyncMode sync_mode = 4; + message nodeinfo { + required string name = 1; + required int32 number = 2; + required int32 incoherent = 3; + optional int32 room = 4; + optional int32 port = 5; + } + required nodeinfo master = 1; + repeated nodeinfo nodes = 2; // These are replicants, we need to parse through these only. + optional bool require_ssl = 3; + optional CDB2SyncMode sync_mode = 4; } -message CDB2_DISTTXNRESPONSE { required int32 rcode = 1; } +message CDB2_DISTTXNRESPONSE { + required int32 rcode = 1; +} message CDB2_EFFECTS { - required int32 num_affected = 1; - required int32 num_selected = 2; - required int32 num_updated = 3; - required int32 num_deleted = 4; - required int32 num_inserted = 5; + required int32 num_affected = 1; + required int32 num_selected = 2; + required int32 num_updated = 3; + required int32 num_deleted = 4; + required int32 num_inserted = 5; } message CDB2_SQLRESPONSE { - required ResponseType response_type = 1; // enum ReponseType - message column { - optional CDB2_ColumnType type = 1; - required bytes value = 2; - optional bool isnull = 3 [ default = false ]; - } - repeated column value = 2; - optional CDB2_DBINFORESPONSE dbinforesponse = 3; - required CDB2_ErrorCode error_code = 4; - optional string error_string = 5; - optional CDB2_EFFECTS effects = 6; - message snapshotinfo { - required int32 file = 1; - required int32 offset = 2; - } - optional snapshotinfo snapshot_info = 7; - optional uint64 row_id = 8; // in case of retry, this will be used to identify - // the rows which need to be discarded - repeated CDB2ServerFeatures features = - 9; // This can tell client about features enabled in comdb2 - optional string info_string = 10; - - /* True if return row data directly under CDB2_SQLRESPONSE, instead of using a - nested `CDB2_SQLRESPONSE.column' message. A nested message is more - readable, but comes with an overhead: protobuf_c_message_pack_to_buffer() - must first pack a nested message into main memory to get its serialized - size, and encode the size in the message header. This means that for each - column value that is being serialized, we end up using memory 2x of its - size. We can avoid the overhead by collapsing the nested - CDB2_SQLRESPONSE.column message into its parent message. */ - optional bool flat_col_vals = 11; - repeated bytes values = 12; - repeated bool isnulls = 13; - - /* query fingerprint */ - optional bytes fp = 14; - - /* sqlite row */ - optional bytes sqlite_row = 15; - - /* query that should be run on foreign db */ - optional string foreign_db = 16; - optional string foreign_class = 17; - optional int32 foreign_policy_flag = 18; - - optional CDB2_DISTTXNRESPONSE disttxnresponse = 19; - optional int32 sql_tail_offset = 20; + required ResponseType response_type=1; // enum ReponseType + message column { + optional CDB2_ColumnType type = 1; + required bytes value = 2; + optional bool isnull = 3 [default = false]; + } + repeated column value=2; + optional CDB2_DBINFORESPONSE dbinforesponse =3; + required CDB2_ErrorCode error_code = 4; + optional string error_string = 5; + optional CDB2_EFFECTS effects = 6; + message snapshotinfo { + required int32 file = 1; + required int32 offset = 2; + } + optional snapshotinfo snapshot_info=7; + optional uint64 row_id = 8; // in case of retry, this will be used to identify the rows which need to be discarded + repeated CDB2ServerFeatures features = 9; // This can tell client about features enabled in comdb2 + optional string info_string = 10; + + /* True if return row data directly under CDB2_SQLRESPONSE, instead of using a nested `CDB2_SQLRESPONSE.column' + message. A nested message is more readable, but comes with an overhead: protobuf_c_message_pack_to_buffer() must + first pack a nested message into main memory to get its serialized size, and encode the size in the message header. + This means that for each column value that is being serialized, we end up using memory 2x of its size. + We can avoid the overhead by collapsing the nested CDB2_SQLRESPONSE.column message into its parent message. */ + optional bool flat_col_vals = 11; + repeated bytes values = 12; + repeated bool isnulls = 13; + + /* query fingerprint */ + optional bytes fp = 14; + + /* sqlite row */ + optional bytes sqlite_row = 15; + + /* query that should be run on foreign db */ + optional string foreign_db = 16; + optional string foreign_class = 17; + optional int32 foreign_policy_flag = 18; + + optional CDB2_DISTTXNRESPONSE disttxnresponse = 19; + optional int32 sql_tail_offset = 20; } diff --git a/sqlite/src/tokenize.c b/sqlite/src/tokenize.c index 55c37dd9f5..a27ae60e63 100644 --- a/sqlite/src/tokenize.c +++ b/sqlite/src/tokenize.c @@ -661,9 +661,10 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ } if( zSql[0]==0 ){ #if defined(SQLITE_BUILDING_FOR_COMDB2) - if ((pParse->prepFlags & SQLITE_PREPARE_REQUIRE_SEMI) && lastTokenParsed != TK_SEMI) { - pParse->rc = SQLITE_MISSING_SEMI; - break; + if ((pParse->prepFlags & SQLITE_PREPARE_REQUIRE_SEMI) + && lastTokenParsed != TK_SEMI) { + pParse->rc = SQLITE_MISSING_SEMI; + break; } #endif /* Upon reaching the end of input, call the parser two more times diff --git a/sqlite/src/vdbe.h b/sqlite/src/vdbe.h index b997492ca7..0a4db08313 100644 --- a/sqlite/src/vdbe.h +++ b/sqlite/src/vdbe.h @@ -209,7 +209,7 @@ typedef struct VdbeOpList VdbeOpList; */ #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ #if defined(SQLITE_BUILDING_FOR_COMDB2) -#define SQLITE_PREPARE_MASK 0x1f /* Mask of public flags */ +#define SQLITE_PREPARE_MASK 0x3f /* Mask of public flags */ #else /* defined(SQLITE_BUILDING_FOR_COMDB2) */ #define SQLITE_PREPARE_MASK 0x0f /* Mask of public flags */ #endif /* defined(SQLITE_BUILDING_FOR_COMDB2) */ diff --git a/tools/cdb2sql/CMakeLists.txt b/tools/cdb2sql/CMakeLists.txt index fde2257fb1..9efb82e7ac 100644 --- a/tools/cdb2sql/CMakeLists.txt +++ b/tools/cdb2sql/CMakeLists.txt @@ -12,7 +12,6 @@ endif() add_executable(cdb2sql cdb2sql.cpp ${PROJECT_SOURCE_DIR}/util/bb_getopt_long.c - completer_buf.c ) include_directories( diff --git a/tools/cdb2sql/cdb2sql.cpp b/tools/cdb2sql/cdb2sql.cpp index 79fcdad1a7..5d43dbc6d8 100644 --- a/tools/cdb2sql/cdb2sql.cpp +++ b/tools/cdb2sql/cdb2sql.cpp @@ -38,10 +38,10 @@ #include #include #include +#include // find_if_not #include "cdb2api.h" #include "cdb2_constants.h" -#include "completer_buf.h" static char *dbname = NULL; static char *dbtype = NULL; @@ -112,7 +112,6 @@ static char *prompt = main_prompt; static int connect_to_master = 0; static int cdb2_master = 0; int multiline = 0; -struct completer_buf *completer = NULL; static int now_ms(void) { @@ -182,6 +181,7 @@ static const char *usage_text = " -f, --file FL Read queries from the specified file FL\n" " -h, --help Help on usage \n" " -n, --host HOST Host to connect to and run query.\n" + " -l, --multiline Use ';'-delimited multiline statements\n" " -p, --precision # Set precision for floation point outputs\n" " -s, --script Script mode (less verbose output)\n" " --showeffects Show the effects of query at the end\n" @@ -1592,6 +1592,11 @@ static int run_statement(const char *sql, int ntypes, int *types, cdb2_clearbindings(cdb2h); } + if (rc == CDB2ERR_INCOMPLETE) { + assert(multiline); + return rc; + } + if (rc != CDB2_OK) { const char *err = cdb2_errstr(cdb2h); if (multiline) { @@ -1600,6 +1605,7 @@ static int run_statement(const char *sql, int ntypes, int *types, int prc = cdb2_get_property(cdb2h, "sql:tail", &tailstr); if (prc == 0) { tailoff = atoi(tailstr); + free(tailstr); fprintf(stderr, "[%.*s] failed with rc %d %s\n", tailoff, sql, rc, err ? err : ""); return rc; } @@ -1712,41 +1718,53 @@ static int run_statement(const char *sql, int ntypes, int *types, return 0; } -static int process_line(char *sql, int ntypes, int *types, - int *start_time, int *run_time) +static int is_comment_or_empty_line(const char * sql) { - char *sqlstr = sql; - int rc; + while (isspace(*sql)) { + sql++; + } - verbose_print("processing line sql '%.30s...'\n", sql); - /* Trim whitespace and then ignore comments and empty lines. */ - while (isspace(*sqlstr)) - sqlstr++; + return (sql[0] == '#' || sql[0] == '\0' || + (sql[0] == '-' && sql[1] == '-')); +} - if (sqlstr[0] == '#' || sqlstr[0] == '\0' || - (sqlstr[0] == '-' && sqlstr[1] == '-')) - return 0; +static void remove_trailing_spaces_and_semicolons(char * const sql) +{ + int len = strlen(sql); + while (len > 0 && isspace(sql[len - 1])) { + len--; + } + while (len > 0 && sql[len - 1] == ';') { + len--; + } - if (!multiline) { - int len; - len = strlen(sqlstr); - while (len > 0 && isspace(sqlstr[len - 1])) - len--; - while (len > 0 && sqlstr[len - 1] == ';') - len--; - sqlstr[len] = '\0'; + sql[len] = '\0'; +} + +static void remove_leading_spaces(char ** const p_sql) +{ + while (isspace(**p_sql)) { + (*p_sql)++; } +} + +static int run_raw_statement(const char * const sql, int ntypes, int *types, + int *start_time, int *run_time) +{ + if (is_comment_or_empty_line(sql)) { return 0; } + + verbose_print("processing line sql '%.30s...'\n", sql); int start_time_ms, run_time_ms; gbl_in_stmt = 1; - rc = run_statement(sqlstr, ntypes, types, &start_time_ms, &run_time_ms); + const int rc = run_statement(sql, ntypes, types, &start_time_ms, &run_time_ms); gbl_in_stmt = 0; gbl_sent_cancel_cnonce = 0; if (rc != 0 && rc != CDB2ERR_INCOMPLETE) { error++; } else if (!multiline && (!scriptmode) && !(printmode & DISP_NONE)) { - printf("[%s] rc %d\n", sqlstr, rc); + printf("[%s] rc %d\n", sql, rc); if (time_mode) { printf(" prep time %d ms\n", start_time_ms); printf(" run time %d ms\n", run_time_ms); @@ -1770,6 +1788,23 @@ static int process_line(char *sql, int ntypes, int *types, return rc; } +/* Trim whitespace at the start of the line and whitespace+semicolons at the end of the line. */ +static void pre_process_statement(char ** p_sql) +{ + remove_leading_spaces(p_sql); + remove_trailing_spaces_and_semicolons(*p_sql); +} + +static int preprocess_and_run_statement(char *sql, int ntypes, int *types, + int *start_time, int *run_time) +{ + if (is_comment_or_empty_line(sql)) { return 0; } + + char *pre_processed_sql = sql; + pre_process_statement(&pre_processed_sql); + return run_raw_statement(pre_processed_sql, ntypes, types, start_time, run_time); +} + struct winsize win_size; static int do_repeat(char *sql) @@ -1801,7 +1836,7 @@ static int do_repeat(char *sql) for (int i = 1; i <= repeat; i++) { start_time_ms = 0; run_time_ms = 0; - process_line(sql, 0, NULL, &start_time_ms, &run_time_ms); + preprocess_and_run_statement(sql, 0, NULL, &start_time_ms, &run_time_ms); start_time_ms_tot += start_time_ms; run_time_ms_tot += run_time_ms; @@ -1881,7 +1916,7 @@ static int *process_typed_statement_args(int ntypes, char **args) return types; } -static int is_multi_line(const char *sql) +static int is_multi_line_ddl(const char *sql) { if (sql == NULL) return 0; @@ -1905,7 +1940,7 @@ static int is_multi_line(const char *sql) return 0; } -static char *get_multi_line_statement(char *line) +static char *get_multi_line_ddl_statement(char *line) { char *stmt = NULL; int slen = 0; @@ -2013,6 +2048,170 @@ static void int_handler(int signum) send_cancel_cnonce(cdb2_cnonce(cdb2h)); } +class InputProcessor { +public: + InputProcessor() {} + virtual void process_input() = 0; + + InputProcessor(const InputProcessor&) = delete; + InputProcessor(InputProcessor&&) = delete; + InputProcessor& operator=(const InputProcessor&) = delete; + InputProcessor& operator=(InputProcessor&&) = delete; + +}; + +// Processes all statements as delimited blocks. +// TODO: Put in its own file +class BlockInputProcessor : public InputProcessor +{ +public: + BlockInputProcessor() : _sql(Sql()) + { + run_raw_statement("set multiline on", 0, NULL, 0, 0); + } + + void process_input() + { + for(const char * line; (line = read_line(main_prompt)) != NULL;) { + process_line(line); + } + handle_trailing_sql(); + } + +private: + class Sql { + public: + Sql() : _sql_str(std::string("")) {} + + void trim_lhs_whitespace() + { + const std::string::iterator first_non_whitespace_char = std::find_if_not( + _sql_str.begin(), _sql_str.end(), ::isspace); + + _sql_str.erase(_sql_str.begin(), first_non_whitespace_char); + } + + void trim_rhs_whitespace() + { + const std::string::reverse_iterator last_non_whitespace_char = std::find_if_not( + _sql_str.rbegin(), _sql_str.rend(), ::isspace); + + _sql_str.erase(last_non_whitespace_char.base()); + } + + int trim_lhs_stmt() + { + char *tailstr; + const int rc = cdb2_get_property(cdb2h, "sql:tail", &tailstr); + if (rc) { return 1; } + + const int tailoff = atoi(tailstr); + free(tailstr); + _sql_str.erase(0, tailoff); + + return 0; + } + + const char * c_str() + { + return _sql_str.c_str(); + } + + void append_line(const char * const line) + { + _sql_str.append(line); + _sql_str.append("\n"); + } + + bool is_delimited() + { + return _sql_str.find(_delim) != std::string::npos; + } + + private: + std::string _sql_str; + }; + + Sql _sql; + static const char _delim = ';'; + + void process_line(const char * const line) + { + if (is_comment_or_empty_line(_sql.c_str()) + && is_comment_or_empty_line(line)) { + return; + } + + verbose_print("Appending line %s\n", line); + _sql.append_line(line); + + while(_sql.is_delimited()) { + // cdb2_run_statement strips spaces; + // strip here to maintain offset + _sql.trim_lhs_whitespace(); + + verbose_print("Processing %s\n", _sql.c_str()); + int rc = run_raw_statement(_sql.c_str(), 0, NULL, 0, 0); + if (rc == CDB2ERR_INCOMPLETE) { return; } + + rc = _sql.trim_lhs_stmt(); + if (rc) { return; } + } + } + + void handle_trailing_sql() + { + if (!is_comment_or_empty_line(_sql.c_str())) { + _sql.trim_lhs_whitespace(); + _sql.trim_rhs_whitespace(); + fprintf(stderr, "[%s] failed: Not a complete statement\n", _sql.c_str()); + } + } +}; + +// Processes ddl statements as '$$' delimited blocks +// and all other statements as single lines. +class DdlBlockInputProcessor : public InputProcessor { +public: + DdlBlockInputProcessor() {} + + void process_input() + { + int should_quit = 0; + for(char * line; + !should_quit && (line = read_line(main_prompt)) != NULL;) { + should_quit = process_line(line); + } + } + +private: + // returns 1 if we should quit; 0 otherwise + int process_line(char * line) + { + if (strncmp(line, "quit", 4) == 0) { + return 1; + } + + // TODO: refactor these functions into class + int multi = 0; + if ((multi = is_multi_line_ddl(line)) != 0) { + line = get_multi_line_ddl_statement(line); + } + + if (repeat > 1) { + do_repeat(line); + } else { + preprocess_and_run_statement(line, 0, NULL, 0, 0); + } + + if (multi) { + free(line); + } + + return 0; + } +}; + int main(int argc, char *argv[]) { static char *filename = NULL; @@ -2058,6 +2257,7 @@ int main(int argc, char *argv[]) {"host", required_argument, NULL, 'n'}, {"minretries", required_argument, NULL, 'R'}, {"connect-to-master", no_argument, NULL, 'm'}, + {"multiline", no_argument, NULL, 'l'}, {0, 0, 0, 0}}; while ((c = bb_getopt_long(argc, argv, (char *)"hsvr:p:d:c:f:g:t:n:R:mMl", long_options, &opt_indx)) != -1) { @@ -2180,11 +2380,11 @@ int main(int argc, char *argv[]) types = process_typed_statement_args(ntypes, &argv[optind]); if (sql && *sql != '-') { scriptmode = 1; - process_line(sql, ntypes, types, 0, 0); + preprocess_and_run_statement(sql, ntypes, types, 0, 0); if (cdb2h) { cdb2_close(cdb2h); } - verbose_print("process_line error=%d\n", error); + verbose_print("preprocess_and_run_statement error=%d\n", error); if (report_costs != NULL) (*report_costs)(); @@ -2214,85 +2414,13 @@ int main(int argc, char *argv[]) sact.sa_handler = int_handler; sigaction(SIGINT, &sact, NULL); } - char *line; - int multi; - if (multiline) { - completer = completerbuf_new(); - char *set_multiline = strdup("set multiline on"); - process_line((char*) set_multiline, 0, NULL, 0, 0); - free(set_multiline); - } - const char *prompt = main_prompt; - while ((line = read_line(prompt)) != NULL) { - if (strncmp(line, "quit", 4) == 0) - break; - // TODO: move to its own routine? - if (multiline) { - completerbuf_append(completer, line); - line = completerbuf_get(completer); - char *s; - int have_semicolon = 0; - // An optimization. We don't want to send every line when - // we're sure it's not complete, which it won't be if the - // statement contains no semicolons. So look for a semicolon - // before we send this to the server and see if - // we have a complete statement. If not, we continue getting - // a line at a time and appending it until we see a semicolon. - // At no point do we attempt to parse anything. - if (memchr(line, ';', completerbuf_available(completer)) != NULL) { - have_semicolon = 1; - } - else - have_semicolon = 0; - while (have_semicolon) { - // We unfortunately strip space in cdb2_run_statement - strip it here as well so - // our offset remains valid. - char *start = line; - while (isspace(*line)) line++; - if (line != start) - completerbuf_advance(completer, line-start); - char *l = (char*) malloc(completerbuf_available(completer) + 1); - strncpy(l, line, completerbuf_available(completer)); - l[completerbuf_available(completer)] = 0; - line = l; - int rc = process_line(line, 0, NULL, 0, 0); - if (rc != CDB2ERR_INCOMPLETE) { - // Get the tail property and advance past the complete - // statement, if any. - char *tailstr; - int tailoff; - rc = cdb2_get_property(cdb2h, "sql:tail", &tailstr); - if (rc) { - completerbuf_clear(completer); - free(line); - have_semicolon = 0; - continue; - } - tailoff = atoi(tailstr); - free(line); - prompt = main_prompt; - completerbuf_advance(completer, tailoff); - line = completerbuf_get(completer); - if (memchr(line, ';', completerbuf_available(completer)) != NULL) { - have_semicolon = 1; - } - else - have_semicolon = 0; - } - } - continue; - } - else if ((multi = is_multi_line(line)) != 0) - line = get_multi_line_statement(line); - if (repeat > 1) { - do_repeat(line); - } else { - process_line(line, 0, NULL, 0, 0); - } - if (multi || multiline) - free(line); - } + InputProcessor * processor = multiline + ? (InputProcessor *) new BlockInputProcessor() + : (InputProcessor *) new DdlBlockInputProcessor(); + processor->process_input(); + delete processor; + if (istty) save_readline_history(); diff --git a/tools/cdb2sql/completer_buf.c b/tools/cdb2sql/completer_buf.c deleted file mode 100644 index e0dc96ac63..0000000000 --- a/tools/cdb2sql/completer_buf.c +++ /dev/null @@ -1,92 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "completer_buf.h" - -static const int minbuf = 10; -static const int initbuf = 1024; - -struct completer_buf { - char *buf; - int buflen; /* max read into buffer */ - int allocated; /* max allocated size */ - int offset; /* offset of first unused byte */ - void *usrptr; /* passed to feed when it needs to be called */ -}; - -struct completer_buf* completerbuf_new(void) { - struct completer_buf *b = malloc(sizeof(struct completer_buf)); - if (b == NULL) - return NULL; - b->buf = malloc(initbuf); - if (b->buf == NULL) - return NULL; - b->allocated = initbuf; - b->buflen = 0; - b->offset = 0; - return b; -} - -void completerbuf_append(struct completer_buf *b, char *line) { - int bytes = strlen(line); - // +2 for newline and nul - if (bytes > 0 && bytes+2 < (b->allocated - b->buflen)) { - memcpy(b->buf + b->buflen, line, bytes+1); - b->buflen += bytes; - b->buf[b->buflen++] = '\n'; - } - else { - int needmore = bytes - (b->allocated - b->buflen); - b->buf = realloc(b->buf, b->allocated + needmore + 2); - memcpy(b->buf + b->buflen, line, bytes); - b->allocated = b->allocated + needmore; - b->buflen = b->allocated; - b->buf[b->buflen++] = '\n'; - } -} - -char* completerbuf_get(struct completer_buf *b) { - int available = b->buflen - b->offset; - - // if we have something, return it - if (available) { - // reset if there's just a few bytes left - if ((b->buflen - b->offset) < minbuf) { - memmove(b->buf, b->buf + b->offset, b->buflen - b->offset); - b->buflen = b->buflen - b->offset; - b->offset = 0; - } - return b->buf + b->offset; - } - else - return 0; -} - -void completerbuf_advance(struct completer_buf *b, int bytes) { - assert(b->offset + bytes <= b->buflen); - b->offset += bytes; - // reset if we consumed everything - if (b->offset >= b->buflen) { - b->buflen = 0; - b->offset = 0; - } -} - -void completerbuf_clear(struct completer_buf *b) { - b->offset = 0; - b->buflen = 0; -} - - -void completerbuf_free(struct completer_buf *b) { - free(b->buf); - free(b); -} - -int completerbuf_available(struct completer_buf *b) { - return b->buflen - b->offset; -} diff --git a/tools/cdb2sql/completer_buf.h b/tools/cdb2sql/completer_buf.h deleted file mode 100644 index ccb0d0f07c..0000000000 --- a/tools/cdb2sql/completer_buf.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef BUF_H -#define BUF_H - -#ifdef __cplusplus -extern "C" { -#endif - -typedef int (*completer_buf_feedfunc)(void *usrptr, char **buf); -typedef void(*completer_buf_freefunc)(void *); - -struct completer_buf; -struct completer_buf* completerbuf_new(void); -char* completerbuf_get(struct completer_buf *b); -void completerbuf_append(struct completer_buf *b, char *s); -void completerbuf_advance(struct completer_buf *b, int bytes); -void completerbuf_clear(struct completer_buf *b); -int completerbuf_available(struct completer_buf *b); - -#ifdef __cplusplus -} -#endif - -#endif -