/* * PROGRAM: Interactive SQL utility * MODULE: isql.epp * DESCRIPTION: Main line routine * * The contents of this file are subject to the Interbase Public * License Version 1.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy * of the License at http://www.Inprise.com/IPL.html * * Software distributed under the License is distributed on an * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express * or implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code was created by Inprise Corporation * and its predecessors. Portions created by Inprise Corporation are * Copyright (C) Inprise Corporation. * * All Rights Reserved. * Contributor(s): ______________________________________. * Revision 1.5 2000/11/18 16:49:24 fsg Increased PRINT_BUFFER_LENGTH to 2048 to show larger plans Fixed Bug #122563 in extract.e get_procedure_args Apparently this has to be done in show.e also, but that is for another day :-) 2001/05/20 Neil McCalden add planonly option 2001.09.09 Claudio Valderrama: put double quotes around identifiers in dialect 3 only when needed. Solve mischievous declaration/invocation of ISQL_copy_SQL_id that made no sense and caused pointer problems. 2001/10/03 Neil McCalden pick up Firebird version from database server and display it with client version when -z used. 2001.10.09 Claudio Valderrama: try to disconnect gracefully in batch mode. 2001.11.23 Claudio Valderrama: skip any number of -- comments but only at the beginning and ignore void statements like block comments followed by a semicolon. 2002-02-24 Sean Leyne - Code Cleanup of old Win 3.1 port (WINDOWS_ONLY) 2003-08-15 Fred Polizo, Jr. - Fixed print_item() to correctly print string types as their hex represention for CHARACTER SET OCTETS. 2004-11-16 Damyan Ivanov - bail out on error in non-interactive mode. */ #ifdef DARWIN #define _STLP_CCTYPE #endif #include "firebird.h" #include #include "../dsql/keywords.h" #include "../jrd/gds_proto.h" #include #include #include #include #include #include #include #include "../common/utils_proto.h" #include "../common/classes/ClumpletWriter.h" #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SIGNAL_H #include #endif #ifdef WIN_NT #include // mktemp #endif #ifdef HAVE_EDITLINE_H // This is a local file included in our distribution - but not always // compiled into the system #include "editline.h" #endif //#include enum literal_string_type { INIT_STR_FLAG = 0, SINGLE_QUOTED_STRING = 1, DOUBLE_QUOTED_STRING = 2, NO_MORE_STRING = 3, INCOMPLETE_STRING = 4 }; #include "../jrd/jrd_time.h" #include "../jrd/common.h" #if defined(WIN_NT) #include #endif #include "../jrd/ibase.h" #include "../isql/isql.h" #include "../jrd/perf.h" #include "../jrd/license.h" #include "../jrd/constants.h" #include "../jrd/ods.h" #include "../jrd/file_params.h" #include "../common/stuff.h" #include "../isql/extra_proto.h" #include "../isql/isql_proto.h" #include "../isql/show_proto.h" #include "../jrd/perf_proto.h" #include "../jrd/utl_proto.h" #include "../jrd/gdsassert.h" DATABASE DB = COMPILETIME "yachts.lnk"; IsqlGlobals isqlGlob; #define DIGIT(c) ((c) >= '0' && (c) <= '9') #define INT64_LIMIT ((((SINT64) 1) << 62) / 5) // same as in cvt.cpp // Print lengths of numeric values const size_t MAXCHARSET_SIZE = 32; // CHARSET names const int SHORT_LEN = 7; // NUMERIC (4,2) = -327.68 const int LONG_LEN = 12; // NUMERIC (9,2) = -21474836.48 const int INT64_LEN = 21; // NUMERIC(18,2) = -92233720368547758.08 const int QUAD_LEN = 19; const int FLOAT_LEN = 14; // -1.2345678E+38 const int DOUBLE_LEN = 23; // -1.234567890123456E+300 const int DATE_LEN = 11; // 11 for date only const int DATETIME_LEN = 25; // 25 for date-time const int TIME_ONLY_LEN = 13; // 13 for time only const int DATE_ONLY_LEN = 11; const int UNKNOWN_LEN = 20; // Unknown type: %d const int MAX_TERMS = 10; // max # of terms in an interactive cmd static inline bool commit_trans(isc_tr_handle* x) { if (isc_commit_transaction (isc_status, x)) { ISQL_errmsg (isc_status); isc_rollback_transaction (isc_status, x); return false; } return true; } static inline int fb_isspace(const char c) { return isspace((int)(UCHAR)c); } static inline int fb_isspace(const SSHORT c) { return isspace((int)(UCHAR)c); } struct indev { indev* indev_next; FILE* indev_fpointer; int indev_line; int indev_aux; char indev_fn[MAXPATHLEN]; void init(FILE* fp, const char* fn) { indev_fpointer = fp; indev_line = 0; indev_aux = 0; strncpy(indev_fn, fn, sizeof(indev_fn)); indev_fn[sizeof(indev_fn) - 1] = 0; } void copy_from(const indev* src) { indev_fpointer = src->indev_fpointer; indev_line = src->indev_line; indev_aux = src->indev_aux; strcpy(indev_fn, src->indev_fn); } private: indev& operator=(const void*); // prevent surprises. }; struct collist { collist* collist_next; TEXT col_name[WORDLENGTH]; SSHORT col_len; }; struct ri_actions { const SCHAR* ri_action_name; const SCHAR* ri_action_print_caps; const SCHAR* ri_action_print_mixed; }; static processing_state add_row(TEXT*); static processing_state blobedit(const TEXT*, const TEXT* const*); static bool check_date(const tm& times); static bool check_time(const tm& times); static bool check_timestamp(const tm& times, const int msec); static size_t chop_at(char target[], const size_t size); static void col_check(const TEXT*, SSHORT*); static void copy_str(TEXT**, const TEXT**, bool*, const TEXT* const, const TEXT* const, literal_string_type); static processing_state copy_table(TEXT*, TEXT*, TEXT*); static processing_state create_db(const TEXT*, TEXT*); static void do_isql(); static processing_state drop_db(); static processing_state edit(const TEXT* const*); static processing_state end_trans(); static processing_state escape(const TEXT*); static processing_state frontend(const TEXT*); static processing_state frontend_set(const char* cmd, const char* const* parms, const char* const* lparms, char* const bad_dialect_buf, bool& bad_dialect); static void frontend_free_parms(TEXT*[], TEXT*[]); static processing_state get_dialect(const char* const dialect_str, char* const bad_dialect_buf, bool& bad_dialect); static processing_state get_statement(TEXT* const, const size_t, const TEXT*); static bool get_numeric(const UCHAR*, USHORT, SSHORT*, SINT64*); static void get_str(const TEXT* const, const TEXT**, const TEXT**, literal_string_type*); static processing_state print_sets(); static processing_state help(const TEXT*); static processing_state input(const TEXT*); static bool isyesno(const TEXT*); static processing_state newdb(TEXT*, const TEXT*, const TEXT*, const TEXT*, const TEXT*, bool); static processing_state newoutput(const TEXT*); static processing_state newsize(const TEXT*, const TEXT*); static processing_state newtrans(const TEXT*); static processing_state parse_arg(int, SCHAR**, SCHAR*, FILE**); static SSHORT print_item(TEXT**, XSQLVAR*, SLONG); static processing_state print_item_blob(FILE*, const XSQLVAR*, FB_API_HANDLE); static void print_item_numeric(SINT64, SSHORT, SSHORT, TEXT*); static processing_state print_line(XSQLDA*, const SLONG*, TEXT line[]); static void print_performance(perf* perf_before); static processing_state print_sqlda_input(const bool is_selectable); static void print_sqlda_output(const XSQLDA& sqlda); static void process_header(const XSQLDA* sqlda, const SLONG pad[], TEXT header[], TEXT header2[]); static void process_plan(); static SLONG process_record_count(const SSHORT statement_type); static SSHORT process_request_type(); static SLONG* process_sqlda_buffer(XSQLDA* const sqlda, SSHORT nullind[]); static SLONG process_sqlda_display(XSQLDA* const sqlda, SLONG buffer[], SLONG pad[]); static int process_statement(const TEXT*, XSQLDA**); static void CLIB_ROUTINE query_abort(int); static void strip_quotes(const TEXT*, TEXT*); static processing_state do_set_command(const TEXT*, bool*); //#ifdef DEV_BUILD static const char* sqltype_to_string(USHORT); //#endif XSQLDA* global_sqlda; XSQLDA** global_sqldap; // The dialect spoken by the database, should be 0 when no database is connected. USHORT global_dialect_spoken = 0; USHORT requested_SQL_dialect = SQL_DIALECT_V6; bool connecting_to_pre_v6_server = false; bool Quiet; bool Version_info = false; #if defined (WIN95) bool fAnsiCP; #endif // Utility transaction handle static isc_tr_handle D__trans; static isc_tr_handle M__trans; static SCHAR global_Numbufs[16]; /* # of cache buffers on connect */ static isc_stmt_handle global_Stmt; static FILE* Ofp; //static FILE* Ifp; static indev Ifp; static SCHAR Password[128]; static SCHAR Charset[128]; static bool Merge_stderr; //static USHORT minor_ods; CVC: We do nothing useful with it! static collist* global_Cols = NULL; static indev* Filelist; static TEXT Tmpfile[MAXPATHLEN]; static bool Abort_flag = false; static bool Echo = false; static bool Time_display = false; static bool Sqlda_display = false; static int Exit_value; static bool Interactive = true; static bool Input_file = false; static SSHORT Pagelength = 20; static bool Stats = false; static bool Autocommit = true; // Commit ddl static bool Warnings = true; // Print warnings #ifdef SCROLLABLE_CURSORS static bool Autofetch = true; // automatically fetch all records static USHORT fetch_direction = 0; static SLONG fetch_offset = 1; #endif static SSHORT Doblob = 1; // Default to printing only text types static bool List = false; static bool Docount = false; static bool Plan = false; static bool Planonly = false; static bool Heading = true; static bool BailOnError = false; static int Termlen = 0; static SCHAR ISQL_charset[MAXCHARSET_SIZE] = { 0 }; static bool Merge_diagnostic = false; static FILE* Diag; static FILE* Help; static const TEXT* const sql_prompt = "SQL> "; #ifdef SCROLLABLE_CURSORS static const TEXT* const fetch_prompt = "FETCH> "; #endif static bool global_psw = false; static bool global_usr = false; static bool global_role = false; static bool has_global_numbufs = false; static bool have_trans = false; // translation of word "Yes" static TEXT yesword[BUFFER_LENGTH128]; static TEXT Print_buffer[PRINT_BUFFER_LENGTH]; // Didn't replace it by FB_SHORT_MONTHS because these are uppercased. static const SCHAR* alpha_months[] = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; #ifdef NOT_USED_OR_REPLACED // There's a local version of this, more complete, in ISQL_get_version() static const UCHAR db_version_info[] = { isc_info_ods_version, isc_info_ods_minor_version, isc_info_db_sql_dialect }; #endif #ifdef NOT_USED_OR_REPLACED static const UCHAR text_bpb[] = { isc_bpb_version1, isc_bpb_source_type, 1, isc_blob_text, isc_bpb_target_type, 1, isc_blob_text }; #endif static UCHAR predefined_blob_subtype_bpb[] = { isc_bpb_version1, isc_bpb_source_type, 1, 0, isc_bpb_target_type, 1, isc_blob_text }; // No check on input argument for now. inline void set_bpb_for_translation(const unsigned int blob_sub_type) { predefined_blob_subtype_bpb[3] = (UCHAR) blob_sub_type; } // Note that these transaction options aren't understood in Version 3.3 static const UCHAR default_tpb[] = { isc_tpb_version1, isc_tpb_write, isc_tpb_read_committed, isc_tpb_wait, isc_tpb_no_rec_version }; #ifdef NOT_USED_OR_REPLACED // CVC: Just in case we need it for R/O operations in the future. static const UCHAR batch_tpb[] = { isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed, isc_tpb_nowait, isc_tpb_rec_version }; #endif // If the action is restrict, do not print anything at all static const ri_actions ri_actions_all[] = { {RI_ACTION_CASCADE, RI_ACTION_CASCADE, "Cascade"}, {RI_ACTION_NULL, RI_ACTION_NULL, "Set Null"}, {RI_ACTION_DEFAULT, RI_ACTION_DEFAULT, "Set Default"}, {RI_ACTION_NONE, RI_ACTION_NONE, "No Action"}, {RI_RESTRICT, "", ""}, {"", "", ""}, {0, 0, 0} }; // We statically link ISQL against CLIB in MSVC6 and // hence we've got problems sharing file handles with FBCLIENT. // Since temp files are deleted by ISQL, let's create them here as well. #if defined(_MSC_VER) && (_MSC_VER <= 1200) static FILE* create_temp_file(const TEXT* string, TEXT* expanded_string) { gds__temp_dir(expanded_string); strcat(expanded_string, string); strcat(expanded_string, TEMP_PATTERN); mktemp(expanded_string); return fopen(expanded_string, "w+b"); } #define gds__temp_file(flag, string, expanded_string) create_temp_file(string, expanded_string) #endif int CLIB_ROUTINE main(int argc, char* argv[]) { /************************************** * * m a i n * ************************************** * * Functional description * This calls ISQL_main, and exists to * isolate main which does not exist under * MS Windows. * **************************************/ #if defined (WIN95) { const ULONG ulConsoleCP = GetConsoleCP(); if (ulConsoleCP == GetACP()) fAnsiCP = true; else { fAnsiCP = false; if (ulConsoleCP != GetOEMCP() && Warnings) { ISQL_printf(isqlGlob.Out, "WARNING: The current codepage is not supported. Any use of any\n" " extended characters may result in incorrect file names.\n"); } } } #endif // Initialize globals isqlGlob.major_ods = 0; //minor_ods = 0; return ISQL_main(argc, argv); } int ISQL_main(int argc, char* argv[]) { /************************************** * * I S Q L _ m a i n * ************************************** * * Functional description * Choose between reading and executing or generating SQL * ISQL_main isolates this from main for PC Clients. * **************************************/ #ifdef MU_ISQL /* ** This is code for QA Test bed Multiuser environment. ** ** Setup multi-user test environment. */ if (qa_mu_environment()) { /* ** Initialize multi-user test manager only if in that ** environment. */ if (!qa_mu_init(argc, argv, 1)) { /* ** Some problem in initializing multi-user test ** environment. */ qa_mu_cleanup(); exit(1); } else { /* ** When invoked by MU, additional command-line argument ** was added at tail. ISQL is not interested in it so ** remove it. */ argv[argc - 1] = NULL; argc--; } } #endif // MU_ISQL TEXT tabname[WORDLENGTH]; tabname[0] = '\0'; isqlGlob.db_SQL_dialect = 0; // Output goes to stdout by default isqlGlob.Out = stdout; isqlGlob.Errfp = stderr; processing_state ret = parse_arg(argc, argv, tabname, NULL); // Detect if stdin is redirected #ifdef WIN_NT HANDLE in = GetStdHandle(STD_INPUT_HANDLE); if (GetFileType(in) == FILE_TYPE_DISK) Interactive = false; #else if (!isatty(fileno(stdin))) Interactive = false; #endif // Init the diagnostics and help files if (Merge_diagnostic) Diag = isqlGlob.Out; else Diag = stdout; Help = stdout; if (Merge_stderr) isqlGlob.Errfp = isqlGlob.Out; ISQL_make_upper(tabname); switch (ret) { case EXTRACT: if (*isqlGlob.global_Db_name) { // Let's use user and password if provided. // This should solve bug #112263 FSG 28.Jan.2001 if (newdb(isqlGlob.global_Db_name, isqlGlob.User, Password, global_Numbufs, isqlGlob.Role, false) == SKIP) { Exit_value = EXTRACT_ddl(SQL_objects, tabname); ISQL_disconnect_database(true); // isc_detach_database(isc_status, &DB); } else Exit_value = FINI_ERROR; } break; case EXTRACTALL: if (*isqlGlob.global_Db_name) { Interactive = false; // Let's use user and password if provided. // This should solve bug #112263 FSG 28.Jan.2001 if (newdb(isqlGlob.global_Db_name, isqlGlob.User, Password, global_Numbufs, isqlGlob.Role, false) == SKIP) { Exit_value = EXTRACT_ddl(ALL_objects, tabname); ISQL_disconnect_database (true); //isc_detach_database(isc_status, &DB); } else Exit_value = FINI_ERROR; } break; case ERR: { TEXT helpstring[155]; ISQL_msg_get(USAGE1, sizeof(helpstring), helpstring, NULL); STDERROUT(helpstring); ISQL_msg_get(USAGE2, sizeof(helpstring), helpstring, NULL); STDERROUT(helpstring); ISQL_msg_get(USAGE3, sizeof(helpstring), helpstring, NULL); STDERROUT(helpstring); ISQL_msg_get(USAGE4, sizeof(helpstring), helpstring, NULL); STDERROUT(helpstring); Exit_value = FINI_ERROR; break; } default: do_isql(); // keep Exit_value to whatever it is set // by do_isql() // Exit_value = FINI_OK; break; } #ifdef MU_ISQL /* ** This is code for QA Test bed Multiuser environment. ** ** Do multi-user cleanup if running in multi-user domain. ** qa_mu_cleanup() is NOP in non-multi-user domain. */ qa_mu_cleanup(); #endif // MU_ISQL #ifdef DEBUG_GDS_ALLOC /* As ISQL can run under windows, all memory should be freed before * returning. In debug mode this call will report unfreed blocks. */ //gds_alloc_report(0, __FILE__, __LINE__); char fn[] = __FILE__; fn[strlen(fn) - 8] = 0; // all isql files in gen dir gds_alloc_report(0, fn, 0); #endif return Exit_value; } void ISQL_array_dimensions(const TEXT* fieldname) { /************************************** * * I S Q L _ a r r a y _ d i m e n s i o n s * ************************************** * * Functional description * Retrieves the dimensions of arrays and prints them. * * Parameters: fieldname -- the actual name of the array field * **************************************/ ISQL_printf(isqlGlob.Out, "["); // Transaction for all frontend commands if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return; } FOR FDIM IN RDB$FIELD_DIMENSIONS WITH FDIM.RDB$FIELD_NAME EQ fieldname SORTED BY FDIM.RDB$DIMENSION /* Format is [lower:upper, lower:upper,..] */ if (FDIM.RDB$DIMENSION > 0) { ISQL_printf (isqlGlob.Out, ", "); } sprintf (Print_buffer, "%ld:%ld", FDIM.RDB$LOWER_BOUND, FDIM.RDB$UPPER_BOUND); ISQL_printf (isqlGlob.Out, Print_buffer); END_FOR ON_ERROR ISQL_errmsg(isc_status); return; END_ERROR; ISQL_printf(isqlGlob.Out, "] "); } // Same than fb_utils::exact_name, but writes the output to the second argument. TEXT* ISQL_blankterm2(const TEXT* input, TEXT* output) { TEXT* q = output - 1; for (TEXT* p = output; (*p = *input) != 0; ++p, ++input) { if (*p != BLANK) q = p; } *(q + 1) = 0; return output; } bool ISQL_dbcheck() { /************************************** * * I S Q L _ d b c h e c k * ************************************** * * Functional description * Check to see if we are connected to a database. * Return true if connected, false otherwise * * Change from miroslavp on 25.04.2005 * Value assigned here on Exit_value is not used anywhere in the source * and cause isql.exe to return 1 in Quiet mode even there is no error. * If there is error Exit_value is set by callers - so it is removed from here. **************************************/ TEXT errbuf[MSG_LENGTH]; if (!isqlGlob.global_Db_name[0]) { if (!Quiet) { ISQL_msg_get(NO_DB, errbuf, NULL); STDERROUT(errbuf); } return false; } else return true; } //static std::string userPrompt; static char userPrompt2[MSG_LENGTH]; static const char* const userPrompt = userPrompt2; static char* lastInputLine = NULL; static int getColumn = -1; void ISQL_prompt(const TEXT* string) { /************************************** * * I S Q L _ p r o m p t * ************************************** * * Functional description * Print a prompt string for interactive user * Not for Windows, otherwise flush the string **************************************/ //userPrompt = (const char*) string; strncpy(userPrompt2, string, MSG_LENGTH - 1); userPrompt2[MSG_LENGTH - 1] = 0; //#ifndef HAVE_READLINE_READLINE_H // fprintf(stdout, userPrompt.c_str()); // fflush(stdout); //#endif } static void readNextInputLine(const char* prompt) { /************************************** * * r e a d N e x t I n p u t L i n e * ************************************** * * Functional description * Get next input line and put it in lastInputLine * If the first read is EOF set lineInputLine to NULL * Otherwise return the line up to EOF (excluded) or '\n' (included) * WARNING: If you bypass getNextInputChar() and call this directly, * remember to set getColumn = -1; immediately after calling this function * to avoid side effects or reading invalid memory locations. **************************************/ if (lastInputLine != NULL) { free(lastInputLine); lastInputLine = NULL; } getColumn = 0; #ifdef HAVE_EDITLINE_H if (Ifp.indev_fpointer == stdin) { // CVC: On 2005-04-02, use an empty prompt when not working in // interactive mode to behave like @@@ below at request by Pavel. const char* new_prompt = Interactive ? prompt : ""; lastInputLine = readline(new_prompt); if (lastInputLine != NULL && strlen(lastInputLine) != 0) { add_history(lastInputLine); } // Let's count lines if someone wants to enable line number error messages // for console reading for whatever reason. ++Ifp.indev_aux; return; } #endif // @@@ CVC: On 2005-04-02, take the "|| Echo" out at request by Pavel. if (Interactive && !Input_file)// || Echo) { // Write the prompt out. fprintf(stdout, prompt); fflush(stdout); } // Read the line char buffer[MAX_USHORT]; if (fgets(buffer, sizeof(buffer), Ifp.indev_fpointer) != NULL) { size_t lineSize = strlen(buffer); while (lineSize > 0 && (buffer[lineSize - 1] == '\n' || buffer[lineSize - 1] == '\r')) { buffer[--lineSize] = '\0'; ++Ifp.indev_aux; } lastInputLine = (char*) malloc(lineSize + 1); memcpy(lastInputLine, buffer, lineSize + 1); } } static void readNextInputLine() { readNextInputLine(userPrompt); //.c_str()); if (Echo && (lastInputLine != NULL)) { ISQL_printf(isqlGlob.Out, lastInputLine); ISQL_printf(isqlGlob.Out, NEWLINE); } } int getNextInputChar() { /************************************** * * g e t N e x t I n p u t C h a r * ************************************** * * Functional description * Read next char from input * * **************************************/ static int inputLen = 0; // At end of line try and read next line if (getColumn == -1) { readNextInputLine(); if (lastInputLine) inputLen = (int) strlen(lastInputLine); } // readline found EOF if (lastInputLine == NULL) { return EOF; } // If at end of line return \n if (getColumn == inputLen) //(int) strlen(lastInputLine)) { getColumn = -1; return '\n'; } // cast to unsigned char to prevent sign expansion // this way we can distinguish russian ya (0xFF) and EOF (usually (-1)) return (unsigned char)lastInputLine[getColumn++]; } void ISQL_copy_SQL_id(const TEXT* in_str, TEXT* output_str, TEXT escape_char) { /************************************** * * I S Q L _ c o p y _ S Q L _ i d * ************************************** * * Functional description * * Copy/rebuild the SQL identifier by adding escape double quote if * double quote is part of the SQL identifier and wraps around the * SQL identifier with delimited double quotes * **************************************/ /* CVC: Try to detect if we can get rid of double quotes as requested by Ann. Notice empty names need double quotes. Assume the caller invoked previously ISQL_blankterm() that's just another function like DYN_terminate, MET_exact_name, etc. ISQL_blankterm has been replaced by fb_utils::exact_name. */ if (escape_char == DBL_QUOTE) { // Cannot rely on ANSI functions that may be localized. bool need_quotes = *in_str < 'A' || *in_str > 'Z'; TEXT* q1 = output_str; for (const TEXT* p1 = in_str; *p1 && !need_quotes; ++p1, ++q1) { if ((*p1 < 'A' || *p1 > 'Z') && (*p1 < '0' || *p1 > '9') && *p1 != '_' && *p1 != '$') { need_quotes = true; } *q1 = *p1; } if (!need_quotes) { if (KEYWORD_stringIsAToken(in_str)) { need_quotes = true; } if (!need_quotes) { *q1 = '\0'; return; } } } TEXT* q1 = output_str; *q1++ = escape_char; for (const TEXT* p1 = in_str; *p1; p1++) { *q1++ = *p1; if (*p1 == escape_char) { *q1++ = escape_char; } } *q1++ = escape_char; *q1 = '\0'; } void ISQL_errmsg(const ISC_STATUS* status) { /************************************** * * I S Q L _ e r r m s g * ************************************** * * Functional description * Report error conditions * Simulate isc_print_status exactly, to control stderr **************************************/ TEXT errbuf[MSG_LENGTH]; #if defined (WIN95) #define TRANSLATE_CP if (!fAnsiCP) AnsiToOem(errbuf, errbuf) #else #define TRANSLATE_CP #endif if (Quiet) Exit_value = FINI_ERROR; else { const ISC_STATUS* vec = status; if (vec[0] != isc_arg_gds || (vec[0] == isc_arg_gds && vec[1] == 0 && vec[2] != isc_arg_warning) || (vec[0] == isc_arg_gds && vec[1] == 0 && vec[2] == isc_arg_warning && !Warnings)) { return; } ISQL_msg_get(0, errbuf, (TEXT*)(IPTR)isc_sqlcode(status)); TRANSLATE_CP; STDERROUT(errbuf); if (fb_interpret(errbuf, sizeof(errbuf), &vec)) { TRANSLATE_CP; STDERROUT(errbuf); // Continuation of error errbuf[0] = '-'; while (fb_interpret(errbuf + 1, sizeof(errbuf) - 1, &vec)) { TRANSLATE_CP; STDERROUT(errbuf); } } if (Input_file) { const char* s = 0; int linenum = -1; if (status[0] == isc_arg_gds && status[1] == isc_dsql_error && status[2] == isc_arg_gds && status[3] == isc_sqlerr && vec > &status[9]) { switch (status[7]) { case isc_dsql_field_err: case isc_dsql_procedure_err: case isc_dsql_relation_err: case isc_dsql_procedure_use_err: if (vec[-2] == isc_arg_string) { s = (char*) vec[-1]; //STDERROUT(s); } break; case isc_dsql_token_unk_err: if (status[8] == isc_arg_number) { linenum = status[9]; //s = errbuf; //STDERROUT(s); } break; } } if (s) { while (*s && !isdigit(*s)) ++s; if (isdigit(*s)) { linenum = 0; for (; *s && isdigit(*s); ++s) linenum = linenum * 10 + *s - '0'; } } if (linenum != -1) { linenum += Ifp.indev_line; sprintf(errbuf, "At line %d in file %s", linenum, Ifp.indev_fn); } else sprintf(errbuf, "After line %d in file %s", Ifp.indev_line, Ifp.indev_fn); STDERROUT(errbuf); } } } void ISQL_warning(ISC_STATUS* status) { /************************************** * * I S Q L _ w a r n i n g * ************************************** * * Functional desription * Report warning * Simulate isc_print_status exactly, to control stderr **************************************/ #if defined (WIN95) #define TRANSLATE_CP if (!fAnsiCP) AnsiToOem(buf, buf) #else #define TRANSLATE_CP #endif const ISC_STATUS* vec = status; fb_assert(vec[1] == 0); // shouldn't be any errors if (!Quiet) { if (vec[0] != isc_arg_gds || vec[2] != isc_arg_warning || (vec[2] == isc_arg_warning && !Warnings)) { return; } TEXT buf[MSG_LENGTH]; if (fb_interpret(buf, sizeof(buf), &vec)) { TRANSLATE_CP; STDERROUT(buf); // Continuation of warning buf[0] = '-'; while (fb_interpret(buf + 1, sizeof(buf) - 1, &vec)) { TRANSLATE_CP; STDERROUT(buf); } } } status[2] = isc_arg_end; } SSHORT ISQL_get_default_char_set_id() { /************************************* * * I S Q L _ g e t _ d e f a u l t _ c h a r _ s e t _ i d * ************************************** * * Functional description * Return the database default character set * id. * * -1 if the value can not be determined. * **************************************/ /* What is the default character set for this database? There are three states: 1. There is no entry available in RDB$DATABASE Then - NONE 2. The entry in RDB$DATABASE does not exist in RDB$CHARACTER_SETS Then - -1 to cause all character set defs to show 3. An entry in RDB$CHARACTER_SETS Then - RDB$CHARACTER_SET_ID */ SSHORT default_char_set_id = 0; FOR FIRST 1 EXT IN RDB$DATABASE WITH EXT.RDB$CHARACTER_SET_NAME NOT MISSING; default_char_set_id = -1; FOR FIRST 1 CHI IN RDB$CHARACTER_SETS WITH CHI.RDB$CHARACTER_SET_NAME = EXT.RDB$CHARACTER_SET_NAME default_char_set_id = CHI.RDB$CHARACTER_SET_ID; END_FOR; END_FOR; return (default_char_set_id); } SSHORT ISQL_get_field_length(const TEXT* field_name) { /************************************** * * I S Q L _ g e t _ f i e l d _ l e n g t h * ************************************** * * Retrieve character or field length of character types. * **************************************/ // Transaction for all frontend commands if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return 0; } SSHORT l = 0; FOR FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ field_name if (FLD.RDB$CHARACTER_LENGTH.NULL) l = FLD.RDB$FIELD_LENGTH; else l = FLD.RDB$CHARACTER_LENGTH; END_FOR ON_ERROR ISQL_errmsg(isc_status); return 0; END_ERROR; return l; } void ISQL_get_character_sets( SSHORT char_set_id, SSHORT collation, bool collate_only, TEXT* string) { /************************************** * * I S Q L _ g e t _ c h a r a c t e r _ s e t s * ************************************** * * Retrieve character set and collation order and format it * **************************************/ #ifdef DEV_BUILD bool found = false; #endif string[0] = 0; // Transaction for all frontend commands if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return; } //if (collation) { if (collation || collate_only) { FOR FIRST 1 COL IN RDB$COLLATIONS CROSS CST IN RDB$CHARACTER_SETS WITH COL.RDB$CHARACTER_SET_ID EQ CST.RDB$CHARACTER_SET_ID AND COL.RDB$COLLATION_ID EQ collation AND CST.RDB$CHARACTER_SET_ID EQ char_set_id SORTED BY COL.RDB$COLLATION_NAME, CST.RDB$CHARACTER_SET_NAME #ifdef DEV_BUILD found = true; #endif fb_utils::exact_name(CST.RDB$CHARACTER_SET_NAME); fb_utils::exact_name(COL.RDB$COLLATION_NAME); fb_utils::exact_name(CST.RDB$DEFAULT_COLLATE_NAME); /* Is specified collation the default collation for character set? */ if (strcmp (CST.RDB$DEFAULT_COLLATE_NAME, COL.RDB$COLLATION_NAME) == 0) { if (!collate_only) sprintf (string, " CHARACTER SET %s", CST.RDB$CHARACTER_SET_NAME); } else if (collate_only) sprintf (string, " COLLATE %s", COL.RDB$COLLATION_NAME); else sprintf (string, " CHARACTER SET %s COLLATE %s", CST.RDB$CHARACTER_SET_NAME, COL.RDB$COLLATION_NAME); END_FOR ON_ERROR ISQL_errmsg(isc_status); return; END_ERROR; #ifdef DEV_BUILD if (!found) { sprintf(Print_buffer, "ISQL_get_character_set: charset %d collation %d not found.\n", char_set_id, collation); STDERROUT(Print_buffer); } #endif } else { FOR FIRST 1 CST IN RDB$CHARACTER_SETS WITH CST.RDB$CHARACTER_SET_ID EQ char_set_id SORTED BY CST.RDB$CHARACTER_SET_NAME #ifdef DEV_BUILD found = true; #endif fb_utils::exact_name(CST.RDB$CHARACTER_SET_NAME); sprintf (string, " CHARACTER SET %s", CST.RDB$CHARACTER_SET_NAME); END_FOR ON_ERROR ISQL_errmsg(isc_status); return; END_ERROR; #ifdef DEV_BUILD if (!found) { sprintf(Print_buffer, "ISQL_get_character_set: charset %d not found.\n", char_set_id); STDERROUT(Print_buffer); } #endif } } void ISQL_get_default_source(const TEXT* rel_name, TEXT* field_name, ISC_QUAD* blob_id) { /************************************** * * I S Q L _ g e t _ d e f a u l t _ s o u r c e * ************************************** * * Retrieve the default source of a field. * Relation_fields takes precedence over fields if both * are present * * For a domain, a NULL is passed for rel_name **************************************/ fb_utils::exact_name(field_name); *blob_id = isc_blob_null; // Transaction for all frontend commands if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return; } if (rel_name) { // This is default for a column of a table FOR FLD IN RDB$FIELDS CROSS RFR IN RDB$RELATION_FIELDS WITH RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME AND RFR.RDB$RELATION_NAME EQ rel_name AND FLD.RDB$FIELD_NAME EQ field_name if (!RFR.RDB$DEFAULT_SOURCE.NULL) *blob_id = RFR.RDB$DEFAULT_SOURCE; else if (!FLD.RDB$DEFAULT_SOURCE.NULL) *blob_id = FLD.RDB$DEFAULT_SOURCE; END_FOR ON_ERROR ISQL_errmsg(isc_status); return; END_ERROR; } else { // Default for a domain FOR FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ field_name if (!FLD.RDB$DEFAULT_SOURCE.NULL) *blob_id = FLD.RDB$DEFAULT_SOURCE; END_FOR ON_ERROR ISQL_errmsg(isc_status); return; END_ERROR; } } SLONG ISQL_get_index_segments(TEXT* segs, const size_t buf_size, const TEXT* indexname, bool delimited_yes) { /************************************** * * I S Q L _ g e t _ i n d e x _ s e g m e n t s * ************************************** * * Functional description * returns the list of columns in an index. * **************************************/ TEXT SQL_identifier[BUFFER_LENGTH128]; *segs = '\0'; // Transaction for all frontend commands if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return 0; } TEXT* const segs_end = segs + buf_size - 1; // Query to get column names SLONG n = 0; bool count_only = false; FOR SEG IN RDB$INDEX_SEGMENTS WITH SEG.RDB$INDEX_NAME EQ indexname SORTED BY SEG.RDB$FIELD_POSITION ++n; if (count_only) continue; // Place a comma and a blank between each segment column name fb_utils::exact_name(SEG.RDB$FIELD_NAME); if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) { ISQL_copy_SQL_id (SEG.RDB$FIELD_NAME, SQL_identifier, DBL_QUOTE); } else { strcpy (SQL_identifier, SEG.RDB$FIELD_NAME); } const size_t len = strlen(SQL_identifier); if (n == 1) { // We assume the buffer is at least size(metadata name), so no initial check. strcpy(segs, SQL_identifier); segs += len; } else { if (segs + len + 2 >= segs_end) { strncpy(segs, ", ...", segs_end - segs); *segs_end = '\0'; count_only = true; } else { sprintf (segs, ", %s", SQL_identifier); segs += len + 2; } } END_FOR ON_ERROR ISQL_errmsg(isc_status); ROLLBACK; return 0; END_ERROR; return n; } bool ISQL_get_base_column_null_flag(const TEXT* view_name, const SSHORT view_context, const TEXT* base_field) { /************************************** * * I S Q L _ g e t _ b a s e _ c o l u m n _ n u l l _ f l a g * ************************************** * * Determine if a field on which view column is based * is nullable. We are passed the view_name * view_context and the base_field of the view column. * **************************************/ BASED_ON RDB$RELATION_FIELDS.RDB$RELATION_NAME save_view_name, save_base_field; strcpy(save_view_name, view_name); strcpy(save_base_field, base_field); SSHORT save_view_context = view_context; // Transaction for all frontend commands if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return false; } /* Using view_name and view_context get the relation name from RDB$VIEW_RELATIONS which contains the base_field for this view column. Get row corresponding to this base field and relation from rdb$field_relations. This will contain info on field's nullability unless it is a view column itself, in which case repeat this procedure till we get to a "real" column. */ bool null_flag = true; bool done = false; bool error = false; while (!done && !error) { fb_utils::exact_name(save_view_name); fb_utils::exact_name(save_base_field); FOR FIRST 1 VR IN RDB$VIEW_RELATIONS CROSS NEWRFR IN RDB$RELATION_FIELDS WITH VR.RDB$VIEW_NAME EQ save_view_name AND VR.RDB$VIEW_CONTEXT EQ save_view_context AND NEWRFR.RDB$RELATION_NAME = VR.RDB$RELATION_NAME AND NEWRFR.RDB$FIELD_NAME = save_base_field if (NEWRFR.RDB$BASE_FIELD.NULL) { if (NEWRFR.RDB$NULL_FLAG == 1) null_flag = false; done = true; } else { strcpy (save_view_name, NEWRFR.RDB$RELATION_NAME); save_view_context = NEWRFR.RDB$VIEW_CONTEXT; strcpy (save_base_field, NEWRFR.RDB$BASE_FIELD); } END_FOR ON_ERROR error = true; END_ERROR; } return null_flag; } bool ISQL_get_null_flag(const TEXT* rel_name, TEXT* field_name) { /************************************** * * I S Q L _ g e t _ n u l l _ f l a g * ************************************** * * Determine if a field has the null flag set. * Look for either rdb$relation_fields or rdb$fields to be * Set to 1 (NOT NULL), then this field cannot be null * We are passed the relation name and the relation_field name * For domains, the relation name is null. **************************************/ fb_utils::exact_name(field_name); bool null_flag = true; // Transaction for all frontend commands if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return false; } if (rel_name) { FOR FLD IN RDB$FIELDS CROSS RFR IN RDB$RELATION_FIELDS WITH RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME AND RFR.RDB$RELATION_NAME EQ rel_name AND RFR.RDB$FIELD_NAME EQ field_name if (FLD.RDB$NULL_FLAG == 1) null_flag = false; else { // If RDB$BASE_FIELD is not null then it is a view column if (RFR.RDB$BASE_FIELD.NULL) { /* Simple column. Did user define it not null? */ if (RFR.RDB$NULL_FLAG == 1) null_flag = false; } else null_flag = ISQL_get_base_column_null_flag (rel_name, RFR.RDB$VIEW_CONTEXT, RFR.RDB$BASE_FIELD); } END_FOR ON_ERROR ISQL_errmsg(isc_status); return null_flag; END_ERROR; } else { // Domains have only field entries to worry about FOR FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ field_name if (FLD.RDB$NULL_FLAG == 1) null_flag = false; END_FOR ON_ERROR ISQL_errmsg(isc_status); return null_flag; END_ERROR; } return null_flag; } void ISQL_disconnect_database(bool nQuietMode) { /************************************** * * I S Q L _ d i s c o n n e c t _ d a t a b a s e * ************************************** * * Functional description * Disconnect from the current database. First commit work and then * call isc_detach_database to detach from the database and then zero * out the DB handle. * **************************************/ // Ignore error msgs during disconnect Quiet = nQuietMode; // If we were in a database, commit before proceeding if (DB && (M__trans || D__trans)) end_trans(); /* * Commit transaction that was started on behalf of the request * Don't worry about the error if one occurs it will be network * related and caught later. */ if (DB && gds_trans) isc_rollback_transaction(isc_status, &gds_trans); /* If there is current user statement, free it I think option 2 is the right one (DSQL_drop), but who knows */ if (global_Stmt) isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_drop); // Detach from old database if (DB) { isc_detach_database(isc_status, &DB); } // Enable error msgs Quiet = false; // Zero database handle and transaction handles global_Stmt = 0; DB = 0; isqlGlob.global_Db_name[0] = '\0'; D__trans = 0; M__trans = 0; gds_trans = 0; // CVC: If we aren't connected to a db anymore, then the db's dialect is reset. // This should fix SF Bug #910430. isqlGlob.db_SQL_dialect = 0; // BRS this is also needed to fix #910430. global_dialect_spoken = 0; return; } #ifdef NOT_USED_OR_REPLACED bool ISQL_is_domain(const TEXT* field_name) { /************************************** * * I S Q L _ i s _ d o m a i n * ************************************** * * Determine if a field in rdb$fields is a domain, * **************************************/ bool is_domain = false; // Transaction for all frontend commands if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return false; } FOR FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ field_name if (!(strncmp (FLD.RDB$FIELD_NAME, "RDB$", 4) == 0 && isdigit (FLD.RDB$FIELD_NAME[4]) && FLD.RDB$SYSTEM_FLAG != 1)) { is_domain = true; } END_FOR ON_ERROR ISQL_errmsg(isc_status); return is_domain; END_ERROR; return is_domain; } #endif void ISQL_make_upper(TEXT* str) { /************************************** * * I S Q L _ m a k e _ u p p e r * ************************************** * * Force the name of a metadata object to * uppercase. * **************************************/ if (!str) return; for (UCHAR* p = reinterpret_cast(str); *p; p++) *p = UPPER7(*p); } void ISQL_msg_get(USHORT number, TEXT* msg, const TEXT* arg1, const TEXT* arg2, const TEXT* arg3, const TEXT* arg4, const TEXT* arg5) { /************************************** * * I S Q L _ m s g _ g e t * ************************************** * * Functional description * Retrieve a message from the error file * **************************************/ gds__msg_format(NULL, ISQL_MSG_FAC, number, MSG_LENGTH, msg, arg1, arg2, arg3, arg4, arg5); } void ISQL_msg_get(USHORT number, USHORT size, TEXT* msg, const TEXT* arg1, const TEXT* arg2, const TEXT* arg3, const TEXT* arg4, const TEXT* arg5) { /************************************** * * I S Q L _ m s g _ g e t * ************************************** * * Functional description * Retrieve a message from the error file * **************************************/ gds__msg_format(NULL, ISQL_MSG_FAC, number, size, msg, arg1, arg2, arg3, arg4, arg5); } void ISQL_print_validation(FILE* fp, ISC_QUAD* blobid, bool isComputedField, FB_API_HANDLE trans) { /************************************** * * I S Q L _ p r i n t _ v a l i d a t i o n * ************************************** * * Functional description * This does some minor syntax adjustmet for extracting * validation blobs and computed fields. * if it does not start with the word CHECK * if this is a computed field blob, look for () or insert them. * if flag == 0, this is a validation clause, * if flag == 1, this is a computed field * **************************************/ // Don't bother with null blobs if (blobid->gds_quad_high == 0) return; TEXT buffer[BUFFER_LENGTH512]; FB_API_HANDLE blob = 0; if (isc_open_blob(isc_status, &DB, &trans, &blob, blobid)) { ISQL_errmsg(isc_status); return; } bool issql = false; bool firsttime = true; USHORT length; while (!isc_get_segment(isc_status, &blob, &length, (USHORT) (BUFFER_LENGTH512 - 1), buffer) || isc_status[1] == isc_segment) { buffer[length] = 0; const TEXT* p = buffer; if (isComputedField) { // computed field SQL syntax correction while (fb_isspace(*p)) p++; if (*p == '(') issql = true; } else { // Validation SQL syntax correction while (fb_isspace(*p)) p++; if (!strncmp(p, "check", 5) || !strncmp(p, "CHECK", 5)) issql = true; } if (firsttime) { firsttime = false; if (!issql) { sprintf(Print_buffer, "%s ", (isComputedField ? "/* " : "(")); ISQL_printf(fp, Print_buffer); } } ISQL_printf(fp, buffer); } if (!issql) { sprintf(Print_buffer, "%s", (isComputedField ? " */" : ")")); ISQL_printf(fp, Print_buffer); } isc_close_blob(isc_status, &blob); if (isc_status[1]) ISQL_errmsg(isc_status); } void ISQL_printf(FILE* fp, const char* buffer) { /************************************** * * I S Q L _ p r i n t f * ************************************** * * Centralized printing facility * **************************************/ fprintf(fp, "%s", buffer); fflush(fp); // John's fix. } static processing_state add_row(TEXT* tabname) { /************************************** * * a d d _ r o w * ************************************** * * Functional description * Allows interactive insert of row, prompting for every column * * The technique is to describe a select query of select * from the table. * Then generate an insert with ? in every position, using the same sqlda. * It will allow edits of blobs, skip arrays and computed fields * * Parameters: * tabname -- Name of table to insert into * **************************************/ if (!*tabname) return (ERR); if (!Interactive) return (ERR); if (Input_file) return ERR; // There may have been a commit transaction before we got here if (!M__trans) if (isc_start_transaction(isc_status, &M__trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return FAIL; } // Start default transaction for prepare if (!D__trans) if (isc_start_transaction(isc_status, &D__trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return FAIL; } chop_at(tabname, WORDLENGTH * 2); if (tabname[0] != DBL_QUOTE) ISQL_make_upper(tabname); /* chop_at(tablename, WORDLENGTH); const bool delimited_yes = tablename[0] == DBL_QUOTE; TEXT tabname[WORDLENGTH * 2]; if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) { ISQL_copy_SQL_id(tablename, tabname, DBL_QUOTE); } else { strcpy(tabname, tablename); ISQL_make_upper(tabname); } */ // Query to obtain relation information SCHAR string[BUFFER_LENGTH120]; sprintf(string, "SELECT * FROM %s", tabname); XSQLDA* sqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(20))); sqlda->version = SQLDA_VERSION1; sqlda->sqln = 20; if (isc_dsql_prepare(isc_status, &D__trans, &global_Stmt, 0, string, isqlGlob.SQL_dialect, sqlda)) { ISQL_errmsg(isc_status); if (sqlda) ISQL_FREE(sqlda); return (SKIP); } const SSHORT n_cols = sqlda->sqld; // Reallocate to the proper size if necessary if (sqlda->sqld > sqlda->sqln) { ISQL_FREE(sqlda); sqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(n_cols))); sqlda->version = SQLDA_VERSION1; sqlda->sqld = sqlda->sqln = n_cols; // We must re-describe the sqlda, no need to re-prepare if (isc_dsql_describe(isc_status, &global_Stmt, isqlGlob.SQL_dialect, sqlda)) { ISQL_errmsg(isc_status); isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_close); if (sqlda) ISQL_FREE(sqlda); return (FAIL); } } isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_close); // Array for storing select/insert column mapping , colcheck sets it up SSHORT* colnumber = (SSHORT*) ISQL_ALLOC((SLONG) (n_cols * sizeof(SSHORT))); col_check(tabname, colnumber); // Create the new INSERT statement from the sqlda info SCHAR* insertstring = (SCHAR*) ISQL_ALLOC((SLONG) (40 + (WORDLENGTH * 2 + 4) * n_cols)); // There is a question marker for each column known updatable column sprintf(insertstring, "INSERT INTO %s (", tabname); int i; SSHORT i_cols = 0; i = 0; { // scope char fldname[WORDLENGTH]; char fldfixed[WORDLENGTH * 2]; for (const XSQLVAR* var = sqlda->sqlvar; i < n_cols; var++, i++) { // Skip columns that are computed if (colnumber[i] != -1) { strncpy(fldname, var->sqlname, var->sqlname_length); fldname[var->sqlname_length] = 0; const bool delimited_yes = fldname[0] == DBL_QUOTE; if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) { ISQL_copy_SQL_id(fldname, fldfixed, DBL_QUOTE); } else { strcpy(fldfixed, fldname); ISQL_make_upper(fldfixed); } sprintf(insertstring + strlen(insertstring), "%s,", fldfixed); i_cols++; } } } // scope for MSVC6 // Set i_cols to the number of insert columns insertstring[strlen(insertstring) - 1] = ')'; strcat(insertstring, " VALUES ("); for (i = 0; i < n_cols; i++) { if (colnumber[i] != -1) strcat(insertstring, " ?,"); } // Patch the last character in the string insertstring[strlen(insertstring) - 1] = ')'; // Allocate INSERT sqlda and null indicators XSQLDA* isqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(i_cols))); /* ISQL_ALLOC doesn't initialize the structure, so init everything * to avoid lots of problems. */ memset(isqlda, 0, XSQLDA_LENGTH(i_cols)); isqlda->version = SQLDA_VERSION1; isqlda->sqln = isqlda->sqld = i_cols; SSHORT* nullind = (SSHORT*) ISQL_ALLOC((SLONG) (i_cols * sizeof(SSHORT))); /* ** For each column, get the type, and length then prompt for a value ** and scanf the resulting string into a buffer of the right type. */ SCHAR infobuf[BUFFER_LENGTH180]; ISQL_msg_get(ADD_PROMPT, sizeof(infobuf), infobuf, NULL); STDERROUT(infobuf); TEXT msg[MSG_LENGTH]; SCHAR name[WORDLENGTH]; bool done = false; while (!done) { SSHORT* nullp = nullind; i = 0; for (const XSQLVAR* var = sqlda->sqlvar; i < n_cols && !done; var++, i++) { if (colnumber[i] == -1) continue; // SQLDA for INSERT statement might have fewer columns const SSHORT j = colnumber[i]; XSQLVAR* ivar = &isqlda->sqlvar[j]; *ivar = *var; *nullp = 0; ivar->sqlind = nullp++; ivar->sqldata = NULL; { // scope const SSHORT length = var->sqlname_length; strncpy(name, var->sqlname, length); name[length] = '\0'; } // scope const SSHORT type = var->sqltype & ~1; const SSHORT nullable = var->sqltype & 1; // Prompt with the name and read the line switch (type) { case SQL_BLOB: ISQL_msg_get(BLOB_PROMPT, msg, name); // Blob: %s, type 'edit' or filename to load> break; case SQL_TYPE_DATE: ISQL_msg_get(DATE_PROMPT, msg, name); // Enter %s as Y/M/D> break; case SQL_TYPE_TIME: ISQL_msg_get(TIME_PROMPT, msg, name); // Enter %s as H:M:S> break; case SQL_TIMESTAMP: ISQL_msg_get(TIMESTAMP_PROMPT, msg, name); // Enter %s as Y/MON/D H:MIN:S[.MSEC]> break; default: ISQL_msg_get(NAME_PROMPT, msg, name); /* Enter %s> */ break; } // On blank line or EOF, break the two loops without doing an insert. readNextInputLine(msg); getColumn = -1; // We are bypassing getNextInputChar(). if (lastInputLine == NULL || strlen(lastInputLine) == 0) { done = true; break; } const USHORT length = (USHORT) strlen(lastInputLine); if (length > MAX_USHORT - 2) { ISQL_msg_get(BUFFER_OVERFLOW, msg, NULL); STDERROUT(msg); done = true; break; } STDERROUT(""); // Convert first 4 chars to upper case for comparison SCHAR cmd[5]; strncpy(cmd, lastInputLine, 4); cmd[4] = '\0'; ISQL_make_upper(cmd); // If the user writes NULL, put a null in the column if (!strcmp(cmd, "NULL")) *ivar->sqlind = -1; else { *ivar->sqlind = 0; SLONG res; // Data types SSHORT* smallint; SLONG* integer; SINT64* pi64; SINT64 n; float* fvalue; double* dvalue; tm times; // Initialize the time structure memset(×, 0, sizeof(times)); char msec_str[5] = ""; int msec = 0; ISC_QUAD* blobid; switch (type) { case SQL_BLOB: blobid = (ISC_QUAD*) ISQL_ALLOC((SLONG) sizeof(ISC_QUAD)); if (!strcmp(cmd, "EDIT")) // If edit, we edit a temp file { // Documentation says: // If string is not NULL, it is assumed to point to an array of // at least L_tmpnam characters - or 2*L_tmpnam bytes. // The value of L_tmpnam is defined in STDIO.H. char bfile[L_tmpnam * 2]; tmpnam(bfile); gds__edit(bfile, 0); res = BLOB_text_load(blobid, DB, M__trans, bfile); unlink(bfile); } else // Otherwise just load the named file /* We can't be sure if it's a TEXT or BINARY file * being loaded. As we aren't sure, we'll * do binary operation - this is NEW data in * the database, not updating existing info */ { res = BLOB_load(blobid, DB, M__trans, lastInputLine); } if (!res) { STDERROUT("Unable to load file"); done = true; } ivar->sqldata = (SCHAR*) blobid; break; case SQL_FLOAT: fvalue = (float*) ISQL_ALLOC(sizeof(float)); if (sscanf(lastInputLine, "%g", fvalue) != 1) { STDERROUT("Input parsing problem"); done = true; } ivar->sqldata = (SCHAR*) fvalue; break; case SQL_DOUBLE: dvalue = (double*) ISQL_ALLOC(sizeof(double)); if (sscanf(lastInputLine, "%lg", dvalue) != 1) { STDERROUT("Input parsing problem"); done = true; } ivar->sqldata = (SCHAR*) dvalue; break; case SQL_TYPE_DATE: if (3 != sscanf(lastInputLine, "%d/%d/%d", ×.tm_year, ×.tm_mon, ×.tm_mday) || !check_date(times)) { ISQL_msg_get(DATE_ERR, msg, lastInputLine); STDERROUT(msg); // Bad date %s\n done = true; } else { --times.tm_mon; times.tm_year -= 1900; // tm_year is 1900-based. ISC_DATE* date = (ISC_DATE*) ISQL_ALLOC((SLONG) ivar->sqllen); isc_encode_sql_date(×, date); ivar->sqldata = (char*) date; } break; case SQL_TYPE_TIME: if (3 != sscanf(lastInputLine, "%d:%d:%d", ×.tm_hour, ×.tm_min, ×.tm_sec) || !check_time(times)) { ISQL_msg_get(TIME_ERR, msg, lastInputLine); STDERROUT(msg); // Bad time %s\n done = true; } else { ISC_TIME* vtime = (ISC_TIME*) ISQL_ALLOC((SLONG) ivar->sqllen); isc_encode_sql_time(×, vtime); ivar->sqldata = (char*) vtime; } break; case SQL_TIMESTAMP: if (6 <= sscanf(lastInputLine, "%d/%d/%d %d:%d:%d.%4s", ×.tm_year, ×.tm_mon, ×.tm_mday, ×.tm_hour, ×.tm_min, ×.tm_sec, msec_str)) { int count = 0; for (; count < 4; ++count) { if (isdigit(msec_str[count])) msec = msec * 10 + msec_str[count] - '0'; else break; } if (count != strlen(msec_str)) done = true; else { while (count++ < 4) msec *= 10; done = !check_timestamp(times, msec); } } else done = true; if (done) { ISQL_msg_get(TIMESTAMP_ERR, msg, lastInputLine); STDERROUT(msg); // Bad timestamp %s\n } else { --times.tm_mon; times.tm_year -= 1900; ISC_TIMESTAMP* datetime = (ISC_TIMESTAMP*) ISQL_ALLOC((SLONG) ivar->sqllen); isc_encode_timestamp(×, datetime); datetime->timestamp_time += msec; ivar->sqldata = (char*) datetime; } break; case SQL_TEXT: // Force all text to varying ivar->sqltype = SQL_VARYING + nullable; // Fall into: case SQL_VARYING: { vary* avary = (vary*) ISQL_ALLOC((SLONG) (length + sizeof(USHORT))); avary->vary_length = length; strncpy(avary->vary_string, lastInputLine, length); ivar->sqldata = (SCHAR*) avary; ivar->sqllen = length + sizeof(USHORT); break; } case SQL_SHORT: case SQL_LONG: if (type == SQL_SHORT) { smallint = (SSHORT*) ISQL_ALLOC(sizeof(SSHORT)); ivar->sqldata = (SCHAR*) smallint; } else if (type == SQL_LONG) { integer = (SLONG*) ISQL_ALLOC(sizeof(SLONG)); ivar->sqldata = (SCHAR*) integer; } // Fall into: // Fall through from SQL_SHORT and SQL_LONG ... case SQL_INT64: if (type == SQL_INT64) { pi64 = (SINT64*) ISQL_ALLOC(sizeof(SINT64)); ivar->sqldata = (SCHAR*) pi64; } if (var->sqlscale < 0) { SSHORT lscale = 0; if (get_numeric((UCHAR*) lastInputLine, length, &lscale, &n) != true) { STDERROUT("Input parsing problem"); done = true; } else { SSHORT dscale = var->sqlscale - lscale; if (dscale > 0) { TEXT err_buf[256]; sprintf(err_buf, "input error: input scale %d exceeds the field's scale %d", -lscale, -var->sqlscale); STDERROUT(err_buf); done = true; } while (dscale++ < 0) n *= 10; } } // sscanf assumes a 64-bit integer target else if (sscanf(lastInputLine, "%" QUADFORMAT "d", &n) != 1) { STDERROUT("Input parsing problem"); done = true; } if (type == SQL_INT64) *pi64 = n; else if (type == SQL_SHORT) { *smallint = n; if (SINT64(*smallint) != n) { STDERROUT("Value too big"); done = true; } } else if (type == SQL_LONG) { *integer = n; if (SINT64(*integer) != n) { STDERROUT("Value too big"); done = true; } } break; default: done = true; STDERROUT("Unexpected SQLTYPE in add_row()"); break; } } } if (!done) { // having completed all columns, try the insert statement with the isqlda if (isc_dsql_exec_immed2(isc_status, &DB, &M__trans, 0, insertstring, isqlGlob.SQL_dialect, isqlda, NULL)) { ISQL_errmsg(isc_status); break; } // Release each of the variables for next time XSQLVAR* ivar = isqlda->sqlvar; for (i = 0; i < i_cols; i++, ivar++) if (ivar->sqldata) ISQL_FREE(ivar->sqldata); } } ISQL_FREE(insertstring); ISQL_FREE(sqlda); if (isqlda) { XSQLVAR* ivar = isqlda->sqlvar; for (i = 0; i < i_cols; i++, ivar++) if (ivar->sqldata) ISQL_FREE(ivar->sqldata); ISQL_FREE(isqlda); } if (colnumber) ISQL_FREE(colnumber); if (nullind) ISQL_FREE(nullind); return (SKIP); } static processing_state blobedit(const TEXT* action, const TEXT* const* cmd) { /************************************** * * b l o b e d i t * ************************************** * * Functional description * Edit the text blob indicated by blobid * * Parameters: cmd -- Array of words interpreted as file name * **************************************/ if (!ISQL_dbcheck()) return FAIL; if (*cmd[1] == 0) return ERR; const TEXT* p = cmd[1]; // Find the high and low values of the blob id ISC_QUAD blobid; sscanf(p, "%"xLONGFORMAT":%"xLONGFORMAT, &blobid.gds_quad_high, &blobid.gds_quad_low); // If it isn't an explicit blobedit, then do a dump. Since this is a // user operation, put it on the M__trans handle. if (!strcmp(action, "BLOBVIEW")) BLOB_edit(&blobid, DB, M__trans, "blob"); else if ((!strcmp(action, "BLOBDUMP")) && (*cmd[2])) { // If this is a blobdump, make sure there is a file name /* We can't be sure if the BLOB is TEXT or BINARY data, * as we're not sure, we'll dump it in BINARY mode */ BLOB_dump(&blobid, DB, M__trans, cmd[2]); } else return ERR; return (SKIP); } // ******************* // c h e c k _ d a t e // ******************* // Check date as entered by the user, before adjustment (year - 1900, month - 1). static bool check_date(const tm& times) { const int y = times.tm_year; const int m = times.tm_mon; const int d = times.tm_mday; if (y < 1 || y > 4999) return false; if (m < 1 || m > 12) return false; const bool leap = y % 4 == 0 && y % 100 != 0 || y % 400 == 0; const int days[] = {0, 31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (d < 1 || d > days[m]) return false; return true; } // ******************* // c h e c k _ t i m e // ******************* // Check time is in range. static bool check_time(const tm& times) { if (times.tm_hour < 0 || times.tm_hour > 23) return false; if (times.tm_min < 0 || times.tm_min > 59) return false; if (times.tm_sec < 0 || times.tm_sec > 59) return false; return true; } // ****************************** // c h e c k _ t i m e s t a m p // ****************************** // Check both date and time according to the previous functions // and also check milliseconds range. static bool check_timestamp(const tm& times, const int msec) { return check_date(times) && check_time(times) && msec >= 0 && msec <= 9999; } // ************* // c h o p _ a t // ************* // Simply ensure a given string argument fits in a size, terminator included. static size_t chop_at(char target[], const size_t size) { size_t len = strlen(target); if (len >= size) { len = size - 1; target[len] = 0; } return len; } static void col_check(const TEXT* tabname, SSHORT* colnumber) { /************************************** * * c o l _ c h e c k * ************************************** * * Check for peculiarities not currently revealed by the SQLDA * colnumber array records the mapping of select columns to insert * columns which do not have an equivalent for array or computed cols. **************************************/ // Transaction for all frontend commands if (DB && !gds_trans) { if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); return; } } // Query to get array info and computed source not available in the sqlda int i = 0, j = 0; FOR F IN RDB$FIELDS CROSS R IN RDB$RELATION_FIELDS WITH F.RDB$FIELD_NAME = R.RDB$FIELD_SOURCE AND R.RDB$RELATION_NAME EQ tabname SORTED BY R.RDB$FIELD_POSITION, R.RDB$FIELD_NAME if ((!F.RDB$DIMENSIONS.NULL && F.RDB$DIMENSIONS) || (!F.RDB$COMPUTED_BLR.NULL)) colnumber[i] = -1; else colnumber[i] = j++; ++i; END_FOR ON_ERROR ISQL_errmsg(isc_status); END_ERROR; } static void copy_str(TEXT** output_str, const TEXT** input_str, bool* done, const TEXT* const str_begin, const TEXT* const str_end, literal_string_type str_flag) { /************************************** * * c o p y _ s t r * ************************************** * * Functional description * * Copy and/or modify the data in the input buffer and stores it * into output buffer. * * For SINGLE_QUOTED_STRING, copy everything from the begining of the * the input buffer to the end of the string constant * * For DOUBLE_QUOTED_STRING, copy everything from the begining of the * the input buffer to the begining of the string constant * * a. replace double quote string delimited character with single * quote * b. if '""' is found with in the string constant, removes one * extra double quote * c. if a single quote is found with in the string constant, * adds one more single quote * d. replace ending double quote string delimited character with * single quote * * For NO_MORE_STRING, copy everything from the begining of the * the input buffer to the end of input buffer * * Input Arguments: * * 1. address of the output buffer * 2. address of the input buffer * 3. address of the end of processing flag * * Output Arguments: * * 1. pointer to begining of the string constant * 2. pointer to ending of the string constant * 3. string type * **************************************/ int slen = 0; const TEXT* b1; TEXT* temp_str = *output_str; const TEXT* temp_local_stmt_str = *input_str; switch (str_flag) { case SINGLE_QUOTED_STRING: slen = str_end - temp_local_stmt_str; strncpy(temp_str, temp_local_stmt_str, slen); *output_str = temp_str + slen; *input_str = str_end; break; case DOUBLE_QUOTED_STRING: slen = str_begin - temp_local_stmt_str; strncpy(temp_str, temp_local_stmt_str, slen); temp_str += slen; *temp_str = SINGLE_QUOTE; temp_str++; b1 = str_begin + 1; while (b1 < str_end) { *temp_str = *b1; temp_str++; switch (*b1) { case SINGLE_QUOTE: *temp_str = SINGLE_QUOTE; temp_str++; break; case DBL_QUOTE: b1++; if (*b1 != DBL_QUOTE) { temp_str--; *temp_str = SINGLE_QUOTE; temp_str++; b1--; } break; default: break; } b1++; } *output_str = temp_str; *input_str = b1; break; case NO_MORE_STRING: slen = strlen(temp_local_stmt_str); strncpy(temp_str, temp_local_stmt_str, slen); temp_str += slen; *temp_str = '\0'; *done = true; *output_str = temp_str; *input_str = temp_local_stmt_str + slen; break; default: break; } } static processing_state copy_table(TEXT* source, TEXT* destination, TEXT* otherdb) { /************************************** * * c o p y _ t a b l e * ************************************** * * Functional description * Create a new table based on an existing one. * * Parameters: source -- name of source table * destination == name of newly created table * **************************************/ if (!source[0] || !destination[0]) { STDERROUT("Either source or destination tables are missing"); return SKIP; } TEXT errbuf[MSG_LENGTH]; // Call list_table with a temporary file, then hand that file to a // new version of isql FILE* const holdout = isqlGlob.Out; // If there is an alternate database, extract the domains const bool domain_flag = otherdb[0]; TEXT ftmp[MAXPATHLEN]; isqlGlob.Out = (FILE*) gds__temp_file(TRUE, SCRATCH, ftmp); if (isqlGlob.Out == (FILE*) - 1) { // If we can't open a temp file then bail ISQL_msg_get(FILE_OPEN_ERR, errbuf, ftmp); STDERROUT(errbuf); Exit_value = FINI_ERROR; isqlGlob.Out = holdout; return END; } chop_at(source, WORDLENGTH * 2); if (source[0] != DBL_QUOTE) ISQL_make_upper(source); /* chop_at(source_tbl, WORDLENGTH); TEXT source[WORDLENGTH * 2]; bool delimited_yes = source_tbl[0] == DBL_QUOTE; if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) { ISQL_copy_SQL_id(source_tbl, source, DBL_QUOTE); } else { strcpy(source, source_tbl); ISQL_make_upper(source); } */ chop_at(destination, WORDLENGTH * 2); if (destination[0] != DBL_QUOTE) ISQL_make_upper(destination); /* chop_at(destination_tbl, WORDLENGTH); TEXT destination[WORDLENGTH * 2]; delimited_yes = destination_tbl[0] == DBL_QUOTE; if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) { ISQL_copy_SQL_id(destination_tbl, destination, DBL_QUOTE); } else { strcpy(destination, destination_tbl); ISQL_make_upper(destination); } */ if (EXTRACT_list_table(source, destination, domain_flag, -1)) { ISQL_msg_get(NOT_FOUND, errbuf, source); STDERROUT(errbuf); fclose(isqlGlob.Out); } else { fclose(isqlGlob.Out); // easy to make a copy in another database const TEXT* altdb = isqlGlob.global_Db_name; if (*otherdb) altdb = otherdb; TEXT cmd[MAXPATHLEN * 2 + 20]; sprintf(cmd, "isql -q %s -i %s", altdb, ftmp); if (system(cmd)) { ISQL_msg_get(COPY_ERR, errbuf, destination, altdb); STDERROUT(errbuf); } } unlink(ftmp); isqlGlob.Out = holdout; return (SKIP); } static processing_state create_db(const TEXT* statement, TEXT* d_name) { /************************************** * * c r e a t e _ d b * ************************************** * * Functional description * Intercept create database commands to * adjust the DB and transaction handles * * Parameters: statement == the entire statement for processing. * * Note: SQL ROLE settings are ignored - the newly created database * will not have any roles defined in it. * **************************************/ processing_state ret = SKIP; // Disconnect from the database and cleanup ISQL_disconnect_database(false); SLONG arglength = strlen(statement) + strlen(isqlGlob.User) + strlen(Password) + 24; if (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET)) arglength += strlen(ISQL_charset) + strlen(" SET NAMES \'\' "); TEXT* local_statement = (TEXT*) ISQL_ALLOC(arglength + 1); if (!local_statement) return (FAIL); TEXT usr[USER_LENGTH]; TEXT psw[PASSWORD_LENGTH]; strcpy(local_statement, statement); TEXT quote = DBL_QUOTE; const TEXT* p = NULL; // If there is a user parameter, we will set it into the create stmt. #if defined (WIN95) if (!fAnsiCP || global_usr || global_psw || #else if (global_usr || global_psw || #endif (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET))) { strip_quotes(isqlGlob.User, usr); strip_quotes(Password, psw); // Look for the quotes on the database name and find the close quotes. // Use '"' first, if not successful try '''. // CVC: Again, this is wrong with embedded quotes. const TEXT* q = strchr(statement, quote); if (!q) { quote = SINGLE_QUOTE; q = strchr(statement, quote); } if (q) { // Set quote to match open quote quote = *q; q++; p = strchr(q, quote); if (p) { #if defined (WIN95) /* If we are not using the ANSI codepage then we need to * translate the database name to ANSI from OEM. * We will translate (p - q) characters from (q) into * the proper offset in local_statement. */ if (!fAnsiCP) OemToAnsiBuff(q, local_statement + (q - statement), p - q); #endif p++; const ptrdiff_t slen = p - statement; local_statement[slen] = '\0'; if (isqlGlob.SQL_dialect == 1) { if (global_usr) sprintf(local_statement + strlen(local_statement), " USER \'%s\' ", usr); if (global_psw) sprintf(local_statement + strlen(local_statement), " PASSWORD \'%s\' ", psw); if (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET)) sprintf(local_statement + strlen(local_statement), " SET NAMES \'%s\' ", ISQL_charset); sprintf(local_statement + strlen(local_statement), "%s", p); } } } } SLONG cnt = 0; if ((isqlGlob.SQL_dialect == 0) || (isqlGlob.SQL_dialect > 1)) { const TEXT* q = strchr(statement, SINGLE_QUOTE); while (q) { cnt++; const TEXT* l = q + 1; q = strchr(l, SINGLE_QUOTE); } TEXT* new_local_statement = NULL; if (cnt > 0) { arglength = strlen(statement) + strlen(isqlGlob.User) + strlen(Password) + 24 + 2 * cnt; if (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET)) arglength += strlen(ISQL_charset) + strlen(" SET NAMES \'\' "); new_local_statement = (TEXT*) ISQL_ALLOC(arglength + 1); if (!new_local_statement) { ISQL_FREE(local_statement); return (FAIL); } } TEXT errbuf[MSG_LENGTH]; TEXT* temp_str; if (new_local_statement) temp_str = new_local_statement; else temp_str = local_statement; const TEXT* temp_local_stmt_str = local_statement; bool done = false; while (!done) { const TEXT* str_begin = NULL; const TEXT* str_end = NULL; literal_string_type str_flag = INIT_STR_FLAG; get_str(temp_local_stmt_str, &str_begin, &str_end, &str_flag); if (str_flag == INCOMPLETE_STRING) { ISQL_msg_get(INCOMPLETE_STR, errbuf, "create database statement"); STDERROUT(errbuf); ISQL_FREE(local_statement); if (new_local_statement) ISQL_FREE(new_local_statement); return (FAIL); } copy_str(&temp_str, &temp_local_stmt_str, &done, str_begin, str_end, str_flag); } if (new_local_statement) temp_str = new_local_statement; else temp_str = local_statement; if (global_usr) sprintf(temp_str + strlen(temp_str), " USER \'%s\' ", usr); if (global_psw) sprintf(temp_str + strlen(temp_str), " PASSWORD \'%s\' ", psw); if (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET)) sprintf(temp_str + strlen(temp_str), " SET NAMES \'%s\' ", ISQL_charset); if (new_local_statement) temp_str = new_local_statement + strlen(new_local_statement); else temp_str = local_statement + strlen(local_statement); if (p && strlen(p) > 0) { temp_local_stmt_str = p; bool done2 = false; while (!done2) { const TEXT* str_begin = NULL; const TEXT* str_end = NULL; literal_string_type str_flag = INIT_STR_FLAG; get_str(temp_local_stmt_str, &str_begin, &str_end, &str_flag); if (str_flag == INCOMPLETE_STRING) { ISQL_msg_get(INCOMPLETE_STR, errbuf, "create database statement"); STDERROUT(errbuf); ISQL_FREE(local_statement); if (new_local_statement) ISQL_FREE(new_local_statement); return (FAIL); } copy_str(&temp_str, &temp_local_stmt_str, &done2, str_begin, str_end, str_flag); } } if (new_local_statement) { ISQL_FREE(local_statement); local_statement = new_local_statement; } } /* execute the create statement * If the isqlGlob.SQL_dialect is not set or set to 2, create the database * as a dialect 3 database. */ if (isqlGlob.SQL_dialect == 0 || isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION) { if (isc_dsql_execute_immediate(isc_status, &DB, &M__trans, 0, local_statement, requested_SQL_dialect, NULL)) { ISQL_errmsg(isc_status); if (!DB) ret = FAIL; } } else { if (isc_dsql_execute_immediate(isc_status, &DB, &M__trans, 0, local_statement, isqlGlob.SQL_dialect, NULL)) { ISQL_errmsg(isc_status); if (!DB) ret = FAIL; } } if (DB) { // Load isqlGlob.global_Db_name with some value to show a successful attach chop_at(d_name, MAXPATHLEN); strip_quotes(d_name, isqlGlob.global_Db_name); ISQL_get_version(true); // Start the user transaction if (!M__trans) { if (isc_start_transaction(isc_status, &M__trans, 1, &DB, 0, NULL)) ISQL_errmsg(isc_status); if (D__trans) commit_trans(&D__trans); if (isc_start_transaction(isc_status, &D__trans, 1, &DB, 5, default_tpb)) ISQL_errmsg(isc_status); } // Allocate a new user statement for the database if (isc_dsql_allocate_statement(isc_status, &DB, &global_Stmt)) { ISQL_errmsg(isc_status); ret = ERR; } } if (local_statement) ISQL_FREE(local_statement); return (ret); } static void do_isql() { /************************************** * * d o _ i s q l * ************************************** * * Functional description * Process incoming SQL statements, using the global sqlda * **************************************/ TEXT errbuf[MSG_LENGTH]; // Initialized user transactions M__trans = 0; // File used to edit sessions Ofp = (FILE*) gds__temp_file(TRUE, SCRATCH, Tmpfile); if (Ofp == (FILE*) - 1) { // If we can't open a temp file then bail ISQL_msg_get(FILE_OPEN_ERR, errbuf, Tmpfile); STDERROUT(errbuf); Exit_value = FINI_ERROR; return; } // Open database and start tansaction signal(SIGINT, query_abort); // // We will not execute this for now on WINDOWS. We are not prompting for // a database name, username and password. A connect statement has to be in // the file containing the script. // newdb(isqlGlob.global_Db_name, isqlGlob.User, Password, global_Numbufs, isqlGlob.Role, true); // If that failed or no Dbname was specified ISQL_dbcheck(); // Set up SQLDA for SELECTs, allow up to 20 fields to be selected. // If we subsequently encounter a query with more columns, we // will realloc the sqlda as needed global_sqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(20))); global_sqlda->version = SQLDA_VERSION1; global_sqlda->sqln = 20; // The sqldap is the handle containing the current sqlda pointer global_sqldap = &global_sqlda; // Read statements and process them from Ifp until the ret // code tells us we are done TEXT statement[MAX_USHORT]; processing_state ret; bool done = false; while (!done) { if (Abort_flag) { if (D__trans) isc_rollback_transaction(isc_status, &D__trans); if (M__trans) isc_rollback_transaction(isc_status, &M__trans); if (gds_trans) isc_rollback_transaction(isc_status, &gds_trans); /* * If there is current user statement, free it * option 2 does the drop */ if (global_Stmt) isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_drop); if (DB) isc_detach_database(isc_status, &DB); break; } ret = get_statement(statement, sizeof(statement), sql_prompt); // If there is no database yet, remind us of the need to connect // But don't execute the statement if (!isqlGlob.global_Db_name[0] && (ret == CONT)) { if (!Quiet) { ISQL_msg_get(NO_DB, errbuf, NULL); STDERROUT(errbuf); } if (!Interactive && BailOnError) ret = FAIL; else ret = SKIP; } switch (ret) { case CONT: if (process_statement(statement, global_sqldap) == ERR) { Exit_value = FINI_ERROR; if (!Interactive && BailOnError) Abort_flag = true; } break; case END: case EOF: case EXIT: if (Abort_flag) { if (D__trans) isc_rollback_transaction(isc_status, &D__trans); if (M__trans) isc_rollback_transaction(isc_status, &M__trans); if (gds_trans) isc_rollback_transaction(isc_status, &gds_trans); } else { if (D__trans) commit_trans(&D__trans); if (M__trans) commit_trans(&M__trans); if (gds_trans) commit_trans(&gds_trans); } /* * If there is current user statement, free it * I think option 2 is the right one (DSQL_drop), but who knows */ if (global_Stmt) isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_drop); if (DB) { isc_detach_database(isc_status, &DB); } done = true; break; case BACKOUT: if (D__trans) isc_rollback_transaction(isc_status, &D__trans); if (M__trans) isc_rollback_transaction(isc_status, &M__trans); if (gds_trans) isc_rollback_transaction(isc_status, &gds_trans); /* * If there is current user statement, free it * I think option 2 is the right one (DSQL_drop), but who knows */ if (global_Stmt) isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_drop); if (DB) { isc_detach_database(isc_status, &DB); } done = true; break; case EXTRACT: case EXTRACTALL: default: /* fb_assert (FALSE); -- removed as finds too many problems */ case ERR: case FAIL: Exit_value = FINI_ERROR; if (!Interactive && BailOnError) Abort_flag = true; break; case SKIP: break; } } global_Stmt = 0; DB = 0; isqlGlob.global_Db_name[0] = '\0'; D__trans = 0; M__trans = 0; gds_trans = 0; // Should have a valid Temp file pointer fb_assert(Ofp != (FILE*) - 1); fclose(Ofp); unlink(Tmpfile); if (global_sqlda) ISQL_FREE(global_sqlda); // CVC: If we were halt by an error and Bail, we have pending cleanup. while (Filelist) { indev* const flist = Filelist->indev_next; FILE* const p = Filelist->indev_fpointer; if (p != stdin && p != Ofp) fclose(p); ISQL_FREE(Filelist); Filelist = flist; } if (Ifp.indev_fpointer != stdin && Ifp.indev_fpointer != Ofp) fclose(Ifp.indev_fpointer); if (lastInputLine) free(lastInputLine); while (global_Cols) { collist* p = global_Cols; global_Cols = global_Cols->collist_next; ISQL_FREE(p); } } static processing_state drop_db() { /************************************** * * d r o p _ d b * ************************************** * * Functional description * Drop the current database * **************************************/ if (isqlGlob.global_Db_name[0]) { if (isc_drop_database(isc_status, &DB)) { ISQL_errmsg(isc_status); return (FAIL); } // If error occured and drop database got aborted if (DB) return (FAIL); } else return (FAIL); // The database got dropped with or without errors M__trans = 0; gds_trans = 0; global_Stmt = 0; D__trans = 0; // CVC: If we aren't connected to a db anymore, then the db's dialect is reset. // This should fix SF Bug #910430. isqlGlob.db_SQL_dialect = 0; // BRS this is also needed to fix #910430. global_dialect_spoken = 0; // Zero database name isqlGlob.global_Db_name[0] = '\0'; DB = 0; return (SKIP); } static processing_state edit(const TEXT* const* cmd) { /************************************** * * e d i t * ************************************** * * Functional description * Edit the current file or named file * * Parameters: cmd -- Array of words interpreted as file name * The result of calling this is to point the global input file * pointer, Ifp, to the named file after editing or the tmp file. * **************************************/ TEXT errbuf[MSG_LENGTH]; // Set up editing command for shell const TEXT* file = cmd[1]; // If there is a file name specified, try to open it if (*file) { TEXT path[MAXPATHLEN]; strip_quotes(file, path); FILE* fp = fopen(path, "r"); if (fp) { // Push the current ifp on the indev indev* current = (indev*) ISQL_ALLOC((SLONG) sizeof(indev)); current->copy_from(&Ifp); current->indev_next = Filelist; Filelist = current; Ifp.init(fp, path); gds__edit(file, 0); } else { ISQL_msg_get(FILE_OPEN_ERR, errbuf, file); STDERROUT(errbuf); } } else { // No file given, edit the temp file indev* current = (indev*) ISQL_ALLOC((SLONG) sizeof(indev)); current->copy_from(&Ifp); current->indev_next = Filelist; Filelist = current; // Close the file, edit it, then reopen and read from the top fclose(Ofp); gds__edit(Tmpfile, 0); Ofp = fopen(Tmpfile, "r+"); Ifp.init(Ofp, Tmpfile); } return (SKIP); } static processing_state end_trans() { /************************************** * * e n d _ t r a n s * ************************************** * * Functional description * Prompt the interactive user if there is an extant transaction and * either commit or rollback * * Called by newtrans, createdb, newdb; * Returns success or failure. * **************************************/ TEXT infobuf[BUFFER_LENGTH60]; processing_state ret = CONT; /* Give option of committing or rolling back before proceding unless ** the last command was a commit or rollback */ if (M__trans) { if (Interactive) { ISQL_msg_get(COMMIT_PROMPT, sizeof(infobuf), infobuf, NULL); readNextInputLine(infobuf); getColumn = -1; // We are bypassing getNextInputChar(). if (lastInputLine && isyesno(lastInputLine)) { // check for Yes answer ISQL_msg_get(COMMIT_MSG, sizeof(infobuf), infobuf, NULL); STDERROUT(infobuf); if (DB && M__trans) { if (isc_commit_transaction(isc_status, &M__trans)) { // Commit failed, so roll back anyway ISQL_errmsg(isc_status); ret = FAIL; } } } else { ISQL_msg_get(ROLLBACK_MSG, sizeof(infobuf), infobuf, NULL); STDERROUT(infobuf); if (DB && M__trans) { if (isc_rollback_transaction(isc_status, &M__trans)) { ISQL_errmsg(isc_status); ret = FAIL; } } } } else { // No answer, just roll back by default if (DB && M__trans) { // For WISQL, we keep track of whether a commit is needed by setting a flag in the ISQLPB // structure. This flag is set whenever a sql command is entered in the SQL input area or // if the user uses the create database dialog. Because of this, this should only show up if // the user connects and then disconnects or if the user enters a SET TRANSACTION stat without // ever doing anything that would cause changes to the dictionary. ISQL_msg_get(ROLLBACK_MSG, sizeof(infobuf), infobuf, NULL); STDERROUT(infobuf); if (isc_rollback_transaction(isc_status, &M__trans)) { ISQL_errmsg(isc_status); ret = FAIL; } } } } // Commit background transaction if (DB && D__trans) { if (isc_commit_transaction(isc_status, &D__trans)) { ISQL_errmsg(isc_status); ret = FAIL; } } return ret; } static processing_state escape(const TEXT* cmd) { /************************************** * * e s c a p e * ************************************** * * Functional description * Permit a shell escape to system call * * Parameters: cmd -- The command string with SHELL * **************************************/ // Advance past the shell const TEXT* shellcmd = cmd; // Search past the 'shell' keyword shellcmd += strlen("shell"); // Eat whitespace at beginning of command while (*shellcmd && fb_isspace(*shellcmd)) shellcmd++; #ifdef WIN_NT // MSDN says: You must explicitly flush (using fflush or _flushall) // or close any stream before calling system. _flushall(); #endif // If no command given just spawn a shell if (!*shellcmd) { #ifdef WIN_NT if (system("%ComSpec%")) return FAIL; #else if (system("$SHELL")) return FAIL; #endif } else { if (system(shellcmd)) return FAIL; } return (SKIP); } static processing_state frontend(const TEXT* statement) { /************************************** * * f r o n t e n d * ************************************** * * Functional description * Handle any frontend commands that start with * show or set converting the string into an * array of words parms, with MAX_TERMS words only. * * Parameters: statement is the string typed by the user * **************************************/ TEXT buffer[BUFFER_LENGTH256]; TEXT errbuf[MSG_LENGTH]; // Store the first NUM_TERMS words as they appear in parms, using blanks // to delimit. Each word beyond a real word gets a null char // Shift parms to upper case, leaving original case in lparms typedef TEXT* isql_params_t[MAX_TERMS]; isql_params_t parms, lparms; for (int iter = 0; iter < FB_NELEM(lparms); ++iter) { lparms[iter] = NULL; parms[iter] = NULL; } const TEXT* p = statement; // Any whitespace and comments at the beginning are already swallowed by get_statement() // Set beginning of statement past comment const TEXT* cmd = p; if (*cmd) { for (int i = 0; i < MAX_TERMS; i++) { bool role_found = false; TEXT* a = buffer; int j = 0; if (*p == DBL_QUOTE || *p == SINGLE_QUOTE) { if (i > 0 && (!strcmp(parms[i - 1], "ROLE"))) role_found = true; bool delimited_done = false; const TEXT end_quote = *p; j++; *a++ = *p++; // Allow a quoted string to have embedded spaces // Prevent overflow while (*p && !delimited_done && j < BUFFER_LENGTH256 - 1) { if (*p == end_quote) { j++; *a++ = *p++; if (*p && *p == end_quote && j < BUFFER_LENGTH256 - 1) { j++; // do not skip the escape quote here *a++ = *p++; } else delimited_done = true; } else { j++; *a++ = *p++; } } } else { // Prevent overflow while (*p && !fb_isspace(*p) && j < BUFFER_LENGTH256 - 1) { j++; *a++ = *p++; } } *a = '\0'; const size_t length = strlen(buffer); parms[i] = (TEXT*) ISQL_ALLOC((SLONG) (length + 1)); lparms[i] = (TEXT*) ISQL_ALLOC((SLONG) (length + 1)); strncpy(parms[i], buffer, length); parms[i][length] = '\0'; while (*p && fb_isspace(*p)) p++; strcpy(lparms[i], parms[i]); if (!role_found) ISQL_make_upper(parms[i]); } } char bad_dialect_buf[512]; bool bad_dialect = false; // Look to see if the words (parms) match any known verbs. If nothing // matches then just hand the statement to process_statement processing_state ret = SKIP; if (!*cmd) { ret = SKIP; // redundant } else if (!strcmp(parms[0], "SHOW")) { // Transaction for all frontend commands if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); // Free the frontend command frontend_free_parms(parms, lparms); return FAIL; } ret = SHOW_metadata(parms, lparms); if (gds_trans) commit_trans(&gds_trans); } else if (!strcmp(parms[0], "ADD")) { if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); // Free the frontend command frontend_free_parms(parms, lparms); return FAIL; } ret = add_row(lparms[1]); if (gds_trans) commit_trans(&gds_trans); } else if (!strcmp(parms[0], "COPY")) { if (DB && !gds_trans) if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) { ISQL_errmsg(isc_status); // Free the frontend command frontend_free_parms(parms, lparms); return FAIL; } ret = copy_table(lparms[1], lparms[2], lparms[3]); if (gds_trans) commit_trans(&gds_trans); } #ifdef MU_ISQL // This is code for QA Test bed Multiuser environment. else if (!strcmp(parms[0], "PAUSE")) { if (qa_mu_environment()) { qa_mu_pause(); ret = SKIP; } else ret = CONT; } #endif // MU_ISQL else if ((!strcmp(parms[0], "BLOBVIEW")) || (!strcmp(parms[0], "BLOBDUMP"))) ret = blobedit(parms[0], lparms); else if ((!strcmp(parms[0], "OUTPUT")) || (!strcmp(parms[0], "OUT"))) ret = newoutput(lparms[1]); else if (!strcmp(parms[0], "SHELL")) ret = escape(cmd); else if (!strcmp(parms[0], "SET")) { ret = frontend_set(cmd, parms, lparms, bad_dialect_buf, bad_dialect); } else if (!strcmp(parms[0], "CREATE")) { if (!strcmp(parms[1], "DATABASE") || !strcmp(parms[1], "SCHEMA")) ret = create_db(cmd, lparms[2]); else ret = CONT; } else if (!strcmp(parms[0], "DROP")) { if (!strcmp(parms[1], "DATABASE") || !strcmp(parms[1], "SCHEMA")) if (*parms[2]) ret = ERR; else ret = drop_db(); else ret = CONT; } else if (!strcmp(parms[0], "CONNECT")) { const TEXT* psw = NULL; const TEXT* usr = NULL; const TEXT* sql_role_nm = NULL; const TEXT* numbufs = NULL; /* if a parameter is given in the command more than once, the last one will be used. The parameters can appear each any order, but each must provide a value. */ ret = SKIP; for (int i = 2; i < (MAX_TERMS - 1);) { if (!strcmp(parms[i], "CACHE") && *lparms[i + 1]) { numbufs = lparms[i + 1]; i += 2; } else if (!strcmp(parms[i], "USER") && *lparms[i + 1]) { usr = lparms[i + 1]; i += 2; } else if (!strcmp(parms[i], "PASSWORD") && *lparms[i + 1]) { psw = lparms[i + 1]; i += 2; } else if (!strcmp(parms[i], "ROLE") && *lparms[i + 1]) { sql_role_nm = lparms[i + 1]; i += 2; } else if (*parms[i]) { // Unrecognized option to CONNECT ret = ERR; break; } else i++; } if (ret != ERR) ret = newdb(lparms[1], usr, psw, numbufs, sql_role_nm, true); } #ifdef SCROLLABLE_CURSORS // Perform scrolling within the currently active cursor else if (!strcmp(parms[0], "NEXT")) { fetch_offset = 1; fetch_direction = 0; ret = FETCH; } else if (!strcmp(parms[0], "PRIOR")) { fetch_offset = 1; fetch_direction = 1; ret = FETCH; } else if (!strcmp(parms[0], "FIRST")) { fetch_offset = 1; fetch_direction = 2; ret = FETCH; } else if (!strcmp(parms[0], "LAST")) { fetch_offset = 1; fetch_direction = 3; ret = FETCH; } else if (!strcmp(parms[0], "ABSOLUTE")) { fetch_direction = 4; fetch_offset = atoi(parms[1]); ret = FETCH; } else if (!strcmp(parms[0], "RELATIVE")) { fetch_direction = 5; fetch_offset = atoi(parms[1]); ret = FETCH; } #endif else if (!strcmp(parms[0], "EDIT")) ret = edit(lparms); else if ((!strcmp(parms[0], "INPUT")) || (!strcmp(parms[0], "IN"))) { // CVC: Set by input() below only if successful. //Input_file = true; ret = input(lparms[1]); } else if (!strcmp(parms[0], "QUIT")) ret = BACKOUT; else if (!strcmp(parms[0], "EXIT")) ret = EXIT; else if (!strcmp(parms[0], "?") || (!strcmp(parms[0], "HELP"))) ret = help(parms[1]); else // Didn't match, it must be SQL ret = CONT; // In case any default transaction was started - commit it here if (gds_trans) commit_trans(&gds_trans); // Free the frontend command frontend_free_parms(parms, lparms); if (ret == ERR) { if (bad_dialect) ISQL_msg_get(CMD_ERR, errbuf, bad_dialect_buf); else ISQL_msg_get(CMD_ERR, errbuf, cmd); STDERROUT(errbuf); } return ret; } static void frontend_free_parms(TEXT* parms[], TEXT* lparms[]) { for (int j = 0; j < MAX_TERMS; j++) { if (parms[j]) { ISQL_FREE(parms[j]); ISQL_FREE(lparms[j]); } } } // *********************** // f r o n t e n d _ s e t // *********************** // Validates and executes the SET {option {params}} command. static processing_state frontend_set(const char* cmd, const char* const* parms, const char* const* lparms, char* const bad_dialect_buf, bool& bad_dialect) { processing_state ret = SKIP; // Display current set options if (!*parms[1]) ret = print_sets(); else if ((!strcmp(parms[1], "STATS")) || (!strcmp(parms[1], "STAT"))) ret = do_set_command(parms[2], &Stats); else if (!strcmp(parms[1], "COUNT")) ret = do_set_command(parms[2], &Docount); else if (!strcmp(parms[1], "LIST")) ret = do_set_command(parms[2], &List); else if (!strcmp(parms[1], "PLAN")) { ret = do_set_command(parms[2], &Plan); if (Planonly && !Plan) ret = do_set_command("OFF", &Planonly); } else if (!strcmp (parms[1], "PLANONLY")) { ret = do_set_command (parms[2], &Planonly); if ( Planonly && !Plan ) { // turn on plan ret = do_set_command ("ON", &Plan); } } else if ((!strcmp(parms[1], "BLOBDISPLAY")) || (!strcmp(parms[1], "BLOB"))) { // No arg means turn off blob display if (!*parms[2] || !strcmp(parms[2], "OFF")) Doblob = NO_BLOBS; else if (!strcmp(parms[2], "ALL")) Doblob = ALL_BLOBS; else Doblob = atoi(parms[2]); } else if (!strcmp(parms[1], "ECHO")) { ret = do_set_command(parms[2], &Echo); if (!Echo) ISQL_prompt(""); } else if ((!strcmp(parms[1], "AUTODDL")) || (!strcmp(parms[1], "AUTO"))) { ret = do_set_command(parms[2], &Autocommit); } #ifdef SCROLLABLE_CURSORS else if (!strcmp(parms[1], "AUTOFETCH")) ret = do_set_command(parms[2], &Autofetch); #endif else if (!strcmp(parms[1], "WIDTH")) ret = newsize(parms[2], parms[3]); else if ((!strcmp(parms[1], "TRANSACTION")) || (!strcmp(parms[1], "TRANS"))) { ret = newtrans(cmd); } else if ((!strcmp(parms[1], "TERM")) || (!strcmp(parms[1], "TERMINATOR"))) { const TEXT* a = (*lparms[2]) ? lparms[2] : DEFTERM; Termlen = strlen(a); if (Termlen < MAXTERM_SIZE) { strcpy(isqlGlob.global_Term, a); } else { Termlen = MAXTERM_SIZE - 1; strncpy(isqlGlob.global_Term, a, Termlen); isqlGlob.global_Term[Termlen] = 0; } } else if (!strcmp(parms[1], "NAMES")) { if (!*parms[2]) { const size_t lgth = strlen(DEFCHARSET); if (lgth < MAXCHARSET_SIZE) strcpy(ISQL_charset, DEFCHARSET); else { strncpy(ISQL_charset, DEFCHARSET, MAXCHARSET_SIZE - 1); ISQL_charset[MAXCHARSET_SIZE - 1] = 0; } } else { const size_t lgth = strlen(parms[2]); if (lgth < MAXCHARSET_SIZE) strcpy(ISQL_charset, parms[2]); else { strncpy(ISQL_charset, parms[2], MAXCHARSET_SIZE - 1); ISQL_charset[MAXCHARSET_SIZE - 1] = 0; } } } else if (!strcmp(parms[1], "TIME")) { ret = do_set_command(parms[2], &Time_display); } //#ifdef DEV_BUILD else if (!strcmp(parms[1], "SQLDA_DISPLAY")) { ret = do_set_command(parms[2], &Sqlda_display); } //#endif // DEV_BUILD else if (!strcmp(parms[1], "SQL")) { if (!strcmp(parms[2], "DIALECT")) ret = get_dialect(parms[3], bad_dialect_buf, bad_dialect); else ret = ERR; } else if (!strcmp (parms[1], "WARNINGS") || !strcmp (parms[1], "WNG")) { ret = do_set_command (parms[2], &Warnings); } else if (!strcmp(parms[1], "GENERATOR")) ret = CONT; else if (!strcmp(parms[1], "STATISTICS")) ret = CONT; else if (!strcmp(parms[1], "HEADING")) ret = do_set_command(parms[2], &Heading); else if (!strcmp(parms[1], "BAIL")) ret = do_set_command(parms[2], &BailOnError); else ret = ERR; return ret; } static processing_state do_set_command(const TEXT* parm, bool* global_flag) { /************************************** * * d o _ s e t _ c o m m a n d * ************************************** * * Functional description * set the flag pointed to by global_flag * to true or false. * if parm is missing, toggle it * if parm is "ON", set it to true * if parm is "OFF", set it to false * **************************************/ processing_state ret = SKIP; if (!*parm) *global_flag = !*global_flag; else if (!strcmp(parm, "ON")) *global_flag = true; else if (!strcmp(parm, "OFF")) *global_flag = false; else ret = ERR; return (ret); } // ********************* // g e t _ d i a l e c t // ********************* // Validates SET SQL DIALECT command according to the target db. static processing_state get_dialect(const char* const dialect_str, char* const bad_dialect_buf, bool& bad_dialect) { processing_state ret = SKIP; bool print_warning = false; const USHORT old_SQL_dialect = isqlGlob.SQL_dialect; // save the old SQL dialect if (dialect_str && (isqlGlob.SQL_dialect = atoi(dialect_str))) { if (isqlGlob.SQL_dialect < SQL_DIALECT_V5 || isqlGlob.SQL_dialect > SQL_DIALECT_V6) { bad_dialect = true; sprintf(bad_dialect_buf, "%s%s", "invalid SQL dialect ", dialect_str); isqlGlob.SQL_dialect = old_SQL_dialect; // restore SQL dialect ret = ERR; } else { if (isqlGlob.major_ods) { if (isqlGlob.major_ods < ODS_VERSION10) { if (isqlGlob.SQL_dialect > SQL_DIALECT_V5) { if (global_dialect_spoken) { sprintf(bad_dialect_buf, "%s%d%s%s%s%d%s", "ERROR: Database SQL dialect ", global_dialect_spoken, " database does not accept Client SQL dialect ", dialect_str, " setting. Client SQL dialect still remains ", old_SQL_dialect, NEWLINE); } else { sprintf(bad_dialect_buf, "%s%s%s%s%s%s", "ERROR: Pre IB V6 database only speaks ", "Database SQL dialect 1 and ", "does not accept Client SQL dialect ", dialect_str, " setting. Client SQL dialect still remains 1.", NEWLINE); } isqlGlob.SQL_dialect = old_SQL_dialect; // restore SQL dialect ISQL_printf(isqlGlob.Out, bad_dialect_buf); } } else { // ODS 10 databases switch (global_dialect_spoken) { case SQL_DIALECT_V5: if (isqlGlob.SQL_dialect > SQL_DIALECT_V5) { if (SQL_DIALECT_V6_TRANSITION) Merge_stderr = true; print_warning = true; } break; case SQL_DIALECT_V6: if (isqlGlob.SQL_dialect == SQL_DIALECT_V5 || isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION) { if (SQL_DIALECT_V6_TRANSITION) Merge_stderr = true; print_warning = true; } break; default: break; } if (print_warning && Warnings) { //print_warning = false; sprintf(bad_dialect_buf, "%s%d%s%d%s%s", "WARNING: Client SQL dialect has been set to ", isqlGlob.SQL_dialect, " when connecting to Database SQL dialect ", global_dialect_spoken, " database. ", NEWLINE); ISQL_printf(isqlGlob.Out, bad_dialect_buf); } } } } } else { // handle non numeric invalid "set sql dialect" case isqlGlob.SQL_dialect = old_SQL_dialect; // restore SQL dialect bad_dialect = true; sprintf(bad_dialect_buf, "%s%s", "invalid SQL dialect ", dialect_str); ret = ERR; } return ret; } static processing_state get_statement(TEXT* const statement, const size_t bufsize, const TEXT* statement_prompt) { /************************************** * * g e t _ s t a t e m e n t * ************************************** * * Functional description * Get an SQL statement, or QUIT/EXIT command to process * * Arguments: Pointer to statement, size of statement_buffer and prompt msg. * **************************************/ processing_state ret = CONT; indev* flist = 0; // Lookup the continuation prompt once TEXT con_prompt[MSG_LENGTH]; ISQL_msg_get(CON_PROMPT, con_prompt, NULL); if (Interactive && !Input_file || Echo) { ISQL_prompt(statement_prompt); } // Clear out statement buffer TEXT* p = statement; *p = '\0'; // Set count of characters to zero int count = 0; int valuable_count = 0; // counter of valuable (non-space) chars const int term_length = Termlen - 1; // additional variable for decreasing calculation Ifp.indev_line = Ifp.indev_aux; bool done = false; enum {normal, in_single_line_comment, in_comment, in_single_quoted_string, in_double_quoted_string} state = normal; while (!done) { SSHORT c = getNextInputChar(); switch (c) { case EOF: // Go back to getc if we get interrupted by a signal. if (SYSCALL_INTERRUPTED(errno)) { errno = 0; break; } // If there was something valuable before EOF - error if (valuable_count > 0) { TEXT errbuf[MSG_LENGTH]; ISQL_msg_get(UNEXPECTED_EOF, errbuf, NULL); STDERROUT(errbuf); Exit_value = FINI_ERROR; } // If we hit EOF at the top of the flist, exit time // Save the last flist so it can be deleted flist = Filelist; if (!flist->indev_next) return FOUND_EOF; // If this is not tmpfile, close it if (Ifp.indev_fpointer != Ofp) fclose(Ifp.indev_fpointer); Filelist = flist->indev_next; // Reset to previous after other input Ifp.copy_from(flist); ISQL_FREE(flist); if (Interactive && !Input_file || Echo) ISQL_prompt(statement_prompt); // CVC: Let's detect if we went back to the first level. if (Ifp.indev_fpointer == stdin) { Interactive = true; Input_file = false; } // Try to convince the new routines to go back to previous file(s) // This should fix the INPUT bug introduced with editline. getColumn = -1; break; case '\n': // case '\0': // In particular with readline the \n is removed if (state == in_single_line_comment) { state = normal; } /* Catch the help ? without a terminator */ if (*statement == '?' && count == 1) { c = 0; done = true; break; } // If in a comment, keep reading if (Interactive && !Input_file || Echo) { if (state == in_comment) { // Comment prompt ISQL_prompt("--> "); } else if (valuable_count == 0) { // Ignore a series of nothing at the beginning ISQL_prompt(statement_prompt); } else { ISQL_prompt(con_prompt); } } break; case '-': // Could this the be start of a single-line comment. if (state == normal && count > 0 && (*(p - 1) == '-')) { state = in_single_line_comment; if (valuable_count == 1) valuable_count = 0; } break; case '*': // Could this the be start of a comment. We can only look back, // not forward. // Ignore possibilities of a comment beginning inside // quoted strings. if (state == normal && count > 0 && (*(p - 1) == '/')) { state = in_comment; if (valuable_count == 1) valuable_count = 0; } break; case '/': // Perhaps this is the end of a comment. // Ignore possibilities of a comment ending inside // quoted strings. if (state == in_comment && (*(p - 1) == '*')) { state = normal; valuable_count--; // This char is not valuable } break; case SINGLE_QUOTE: switch (state) { case normal: state = in_single_quoted_string; break; case in_single_quoted_string: state = normal; break; } break; case DBL_QUOTE: switch (state) { case normal: state = in_double_quoted_string; break; case in_double_quoted_string: state = normal; break; } break; default: if (state == normal && c == isqlGlob.global_Term[term_length] && // one-char terminator or the beginning also match (Termlen == 1 || (valuable_count >= term_length && strncmp(p - term_length, isqlGlob.global_Term, term_length) == 0))) { c = 0; done = true; p -= term_length; count -= term_length; } } // Any non-space character is significant if not in comment if (state != in_comment && state != in_single_line_comment && !fb_isspace(c) && c != EOF) { valuable_count++; if (valuable_count == 1) // this is the first valuable char in stream { // ignore all previous crap p = statement; count = 0; } } *p++ = c; count++; if (count > bufsize && !done) { TEXT errbuf[MSG_LENGTH]; ISQL_msg_get(BUFFER_OVERFLOW, errbuf, NULL); STDERROUT(errbuf); ret = SKIP; // move some content to start of buffer just in case if // the overflow has splitted terminator count = MAX(Termlen, 2); memcpy(statement, p - count, count); p = statement + count; *p = '\0'; } } // If this was a null statement, skip it if (!*statement) ret = SKIP; if (ret == CONT) ret = frontend(statement); if (ret == CONT) { // Place each non frontend statement in the temp file if we are reading // from stdin. Add newline to make the file more readable. if (Ifp.indev_fpointer == stdin) { fputs(statement, Ofp); fputs(isqlGlob.global_Term, Ofp); fputc('\n', Ofp); } } return ret; } static void get_str(const TEXT* const input_str, const TEXT** str_begin, const TEXT** str_end, literal_string_type* str_flag) { /************************************** * * g e t _ s t r * ************************************** * * Functional description * * Scanning a string constant from the input buffer. If it found, then * passes back the starting address and ending address of the string * constant. It also marks the type of string constant and passes back. * * string type: * * 1. SINGLE_QUOTED_STRING --- string constant delimited by * single quotes * 2. DOUBLE_QUOTED_STRING --- string constant delimited by * double quotes * 3. NO_MORE_STRING --- no string constant was found * * 4. INCOMPLETE_STRING --- no matching ending quote * * Input Arguments: * * 1. input buffer * * Output Arguments: * * 1. pointer to the input buffer * 2. address to the begining of a string constant * 3. address to the ending of a string constant * 4. address to the string flag * **************************************/ const TEXT* b1 = strchr(input_str, SINGLE_QUOTE); const TEXT* b2 = strchr(input_str, DBL_QUOTE); if (!b1 && !b2) *str_flag = NO_MORE_STRING; else { if (b1 && !b2) *str_flag = SINGLE_QUOTED_STRING; else if (!b1 && b2) { *str_flag = DOUBLE_QUOTED_STRING; b1 = b2; } else if (b1 > b2) { *str_flag = DOUBLE_QUOTED_STRING; b1 = b2; } else *str_flag = SINGLE_QUOTED_STRING; *str_begin = b1; TEXT delimited_char = *b1; b1++; bool done = false; while (!done) { if (*b1 == delimited_char) { b1++; if (*b1 == delimited_char) b1++; else { done = true; *str_end = b1; } } else b1 = strchr(b1, delimited_char); /* In case there is no matching ending quote (single or double) either because of mismatched quotes or simply because user didn't complete the quotes, we cannot find a valid string at all. So we return a special value INCOMPLETE_STRING. In this case str_end is not populated and hence copy_str should not be called after get_str, but instead the caller must process this error & return to it's calling routine - Shailesh */ if (b1 == NULL) { *str_flag = INCOMPLETE_STRING; done = true; } } } } void ISQL_get_version(bool call_by_create_db) { /************************************** * * I S Q L _ g e t _ v e r s i o n * ************************************** * * Functional description * finds out if the database we just attached to is * V4 or newer as well as other info. * **************************************/ static const UCHAR db_version_info[] = { isc_info_ods_version, //isc_info_ods_minor_version, isc_info_db_sql_dialect, isc_info_firebird_version, isc_info_end }; /* ** Each info item requested will return ** ** 1 byte for the info item tag ** 2 bytes for the length of the information that follows ** 1 to 4 bytes of integer information ** ** isc_info_end will not have a 2-byte length - which gives us ** some padding in the buffer. */ // UCHAR buffer[sizeof(db_version_info) * (1 + 2 + 4)]; // Now we are also getting the Firebird server version which is a // string the above calculation does not apply. NM 03-Oct-2001 UCHAR buffer[BUFFER_LENGTH512]; char bad_dialect_buf[BUFFER_LENGTH512]; bool print_warning = false; global_dialect_spoken = 0; if (isc_database_info(isc_status, &DB, sizeof(db_version_info), reinterpret_cast(db_version_info), sizeof(buffer), (SCHAR*) buffer)) { ISQL_errmsg(isc_status); return; } // We assume the version information provided by the server is less than 200 bytes. char server_version[256]; const UCHAR* p = buffer; while (*p != isc_info_end && *p != isc_info_truncated) { const UCHAR item = (UCHAR) *p++; const USHORT length = gds__vax_integer(p, sizeof(USHORT)); p += sizeof(USHORT); switch (item) { case isc_info_ods_version: isqlGlob.major_ods = gds__vax_integer(p, length); break; //case isc_info_ods_minor_version: // minor_ods = gds__vax_integer(p, length); // break; case isc_info_db_sql_dialect: global_dialect_spoken = gds__vax_integer(p, length); if (isqlGlob.major_ods < ODS_VERSION10) { if (isqlGlob.SQL_dialect > SQL_DIALECT_V5 && Warnings) { ISQL_printf(isqlGlob.Out, NEWLINE); sprintf(bad_dialect_buf, "%s%s%s%d%s%s", "WARNING: Pre IB V6 database only speaks", " SQL dialect 1 and ", "does not accept Client SQL dialect ", isqlGlob.SQL_dialect, " . Client SQL dialect is reset to 1.", NEWLINE); ISQL_printf(isqlGlob.Out, bad_dialect_buf); } } else { // ODS 10 databases switch (global_dialect_spoken) { case SQL_DIALECT_V5: if (isqlGlob.SQL_dialect > SQL_DIALECT_V5) print_warning = true; break; case SQL_DIALECT_V6: if (isqlGlob.SQL_dialect != 0 && isqlGlob.SQL_dialect < SQL_DIALECT_V6) print_warning = true; break; default: break; } if (print_warning && Warnings) { print_warning = false; ISQL_printf(isqlGlob.Out, NEWLINE); sprintf(bad_dialect_buf, "%s%d%s%d%s%s", "WARNING: This database speaks SQL dialect ", global_dialect_spoken, " but Client SQL dialect was set to ", isqlGlob.SQL_dialect, " .", NEWLINE); ISQL_printf(isqlGlob.Out, bad_dialect_buf); } } break; case isc_info_error: // Error indicates an option was not understood by the // remote server. if (*p == isc_info_firebird_version) { // must be an old or non Firebird server break; } if (isqlGlob.SQL_dialect && isqlGlob.SQL_dialect != SQL_DIALECT_V5 && Warnings) { ISQL_printf(isqlGlob.Out, NEWLINE); if (call_by_create_db) sprintf(bad_dialect_buf, "%s%s%d%s%s", "WARNING: Pre IB V6 server only speaks SQL dialect 1", " and does not accept Client SQL dialect ", isqlGlob.SQL_dialect, " . Client SQL dialect is reset to 1.", NEWLINE); else { connecting_to_pre_v6_server = true; sprintf(bad_dialect_buf, "%s%s%d%s%s", "ERROR: Pre IB V6 server only speaks SQL dialect 1", " and does not accept Client SQL dialect ", isqlGlob.SQL_dialect, " . Client SQL dialect is reset to 1.", NEWLINE); } ISQL_printf(isqlGlob.Out, bad_dialect_buf); } else { if (isqlGlob.SQL_dialect == 0) { connecting_to_pre_v6_server = true; sprintf(bad_dialect_buf, "%s%s%d%s%s", "ERROR: Pre IB V6 server only speaks SQL dialect 1", " and does not accept Client SQL dialect ", isqlGlob.SQL_dialect, " . Client SQL dialect is reset to 1.", NEWLINE); ISQL_printf(isqlGlob.Out, bad_dialect_buf); } } break; case isc_info_firebird_version: strcpy(server_version, "Server: "); strncat(server_version, (char*) &p[2], (length - 2)); if (Version_info) { strcat(server_version, NEWLINE); ISQL_printf (isqlGlob.Out, server_version); } break; default: { TEXT message[100]; sprintf(message, "Internal error: Unexpected isc_info_value %d\n", item); ISQL_printf(isqlGlob.Out, message); } break; } p += length; } if (isqlGlob.major_ods < ODS_VERSION8) { TEXT errbuf[MSG_LENGTH]; ISQL_msg_get(SERVER_TOO_OLD, errbuf, NULL); STDERROUT(errbuf); return; } /* If the remote server did not respond to our request for "dialects spoken", then we can assume it can only speak the V5 dialect. We automatically change the connection dialect to that spoken by the server. Otherwise the current dialect is set to whatever the user requested. */ if (global_dialect_spoken == 0) isqlGlob.SQL_dialect = SQL_DIALECT_V5; else if (isqlGlob.major_ods < ODS_VERSION10) isqlGlob.SQL_dialect = global_dialect_spoken; else if (isqlGlob.SQL_dialect == 0) // client SQL dialect has not been set isqlGlob.SQL_dialect = global_dialect_spoken; if (global_dialect_spoken > 0) isqlGlob.db_SQL_dialect = global_dialect_spoken; else isqlGlob.db_SQL_dialect = SQL_DIALECT_V5; } void ISQL_remove_and_unescape_quotes(TEXT* string, const char quote) { /************************************** * * I S Q L _ r e m o v e _ a n d _ u n e s c a p e _ q u o t e s * ************************************** * * Functional description * Remove the delimited quotes. Blanks could be part of * delimited SQL identifier. It has to deal with embedded quotes, too. * **************************************/ const size_t cmd_len = strlen(string); TEXT* q = string; const TEXT* p = q; const TEXT* const end_of_str = p + cmd_len; for (size_t cnt = 1; cnt < cmd_len && p < end_of_str; cnt++) { p++; if (cnt < cmd_len - 1) { *q = *p; if (p + 1 < end_of_str) { if (*(p + 1) == quote) // skip the escape double quote p++; } else { p++; *q = '\0'; } } else { *q = '\0'; } q++; } *q = '\0'; } void ISQL_truncate_term(TEXT* str, USHORT len) { /************************************** * * I S Q L _ t r u n c a t e _ t e r m * ************************************** * * Functional description * Truncates the rightmost contiguous spaces on a string. * CVC: Notice isspace may be influenced by locales. **************************************/ int i; for (i = len - 1; i >= 0 && ((fb_isspace(str[i])) || (str[i] == 0)); i--); str[i + 1] = 0; } void ISQL_ri_action_print(const TEXT* ri_action_str, const TEXT* ri_action_prefix_str, bool all_caps) { /************************************** * * I S Q L _ r i _ a c t i o n _ p r i n t * ************************************** * * Functional description * prints the description of ref. integrity actions. * The actions must be one of the cascading actions or RESTRICT. * RESTRICT is used to indicate that the user did not specify any * actions, so do not print it out. * **************************************/ for (const ri_actions* ref_int = ri_actions_all; ref_int->ri_action_name; ++ref_int) { if (!strcmp(ref_int->ri_action_name, ri_action_str)) { if (*ref_int->ri_action_print_caps) { // we have something to print if (all_caps) sprintf(Print_buffer, "%s %s", ri_action_prefix_str, ref_int->ri_action_print_caps); else if (*ref_int->ri_action_print_mixed) sprintf(Print_buffer, "%s %s", ri_action_prefix_str, ref_int->ri_action_print_mixed); ISQL_printf(isqlGlob.Out, Print_buffer); } return; } } fb_assert(FALSE); } static bool get_numeric(const UCHAR* string, USHORT length, SSHORT* scale, SINT64* ptr) { /************************************** * * g e t _ n u m e r i c * ************************************** * * Functional description * Convert a numeric literal (string) to its binary value. * * The binary value (int64) is stored at the * address given by ptr. * **************************************/ SINT64 value = 0; SSHORT local_scale = 0, sign = 0; bool digit_seen = false, fraction = false; const UCHAR* const end = string + length; for (const UCHAR* p = string; p < end; p++) { if (DIGIT(*p)) { digit_seen = true; /* Before computing the next value, make sure there will be no overflow. Trying to detect overflow after the fact is tricky: the value doesn't always become negative after an overflow! */ if (value >= INT64_LIMIT) { // possibility of an overflow if (value > INT64_LIMIT) break; else if (((*p > '8') && (sign == -1)) || ((*p > '7') && (sign != -1))) { break; } } /* Force the subtraction to be performed before the addition, thus preventing a possible signed arithmetic overflow. */ value = value * 10 + (*p - '0'); if (fraction) --local_scale; } else if (*p == '.') { if (fraction) return false; else fraction = true; } else if (*p == '-' && !digit_seen && !sign && !fraction) sign = -1; else if (*p == '+' && !digit_seen && !sign && !fraction) sign = 1; else if (*p != BLANK) return false; } if (!digit_seen) return false; *scale = local_scale; *(SINT64*) ptr = ((sign == -1) ? -value : value); return true; } static processing_state print_sets() { /************************************** * * p r i n t _ s e t s * ************************************** * * Functional description * Print the current set values * **************************************/ ISQL_printf(isqlGlob.Out, "Print statistics: "); ISQL_printf(isqlGlob.Out, (Stats ? "ON" : "OFF")); ISQL_printf(isqlGlob.Out, NEWLINE); ISQL_printf(isqlGlob.Out, "Echo commands: "); ISQL_printf(isqlGlob.Out, (Echo ? "ON" : "OFF")); ISQL_printf(isqlGlob.Out, NEWLINE); ISQL_printf(isqlGlob.Out, "List format: "); ISQL_printf(isqlGlob.Out, (List ? "ON" : "OFF")); ISQL_printf(isqlGlob.Out, NEWLINE); ISQL_printf(isqlGlob.Out, "Row Count: "); ISQL_printf(isqlGlob.Out, (Docount ? "ON" : "OFF")); ISQL_printf(isqlGlob.Out, NEWLINE); ISQL_printf(isqlGlob.Out, "Autocommit DDL: "); ISQL_printf(isqlGlob.Out, (Autocommit ? "ON" : "OFF")); ISQL_printf(isqlGlob.Out, NEWLINE); #ifdef SCROLLABLE_CURSORS ISQL_printf(isqlGlob.Out, "Autofetch records: "); ISQL_printf(isqlGlob.Out, (Autofetch ? "ON" : "OFF")); ISQL_printf(isqlGlob.Out, NEWLINE); #endif ISQL_printf(isqlGlob.Out, "Access Plan: "); ISQL_printf(isqlGlob.Out, (Plan ? "ON" : "OFF")); ISQL_printf(isqlGlob.Out, NEWLINE); ISQL_printf(isqlGlob.Out, "Access Plan only: "); ISQL_printf(isqlGlob.Out, (Planonly ? "ON" : "OFF")); ISQL_printf(isqlGlob.Out, NEWLINE); ISQL_printf(isqlGlob.Out, "Display BLOB type: "); if (Doblob == ALL_BLOBS) ISQL_printf(isqlGlob.Out, "ALL"); else if (Doblob == NO_BLOBS) ISQL_printf(isqlGlob.Out, "NONE"); else { sprintf(Print_buffer, "%d", Doblob); ISQL_printf(isqlGlob.Out, Print_buffer); } ISQL_printf(isqlGlob.Out, NEWLINE); if (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET)) { sprintf(Print_buffer, "Set names: %s%s", ISQL_charset, NEWLINE); ISQL_printf(isqlGlob.Out, Print_buffer); } ISQL_printf(isqlGlob.Out, "Column headings: "); ISQL_printf(isqlGlob.Out, Heading ? "ON" : "OFF"); ISQL_printf(isqlGlob.Out, NEWLINE); if (global_Cols) { sprintf(Print_buffer, "Column print widths:%s", NEWLINE); ISQL_printf(isqlGlob.Out, Print_buffer); const collist* p = global_Cols; while (p) { sprintf(Print_buffer, "%s%s width: %d%s", TAB_AS_SPACES, p->col_name, p->col_len, NEWLINE); ISQL_printf(isqlGlob.Out, Print_buffer); p = p->collist_next; } } ISQL_printf(isqlGlob.Out, "Terminator: "); ISQL_printf(isqlGlob.Out, isqlGlob.global_Term); ISQL_printf(isqlGlob.Out, NEWLINE); sprintf(Print_buffer, "Time: %s%s", (Time_display) ? "ON" : "OFF", NEWLINE); ISQL_printf(isqlGlob.Out, Print_buffer); sprintf(Print_buffer, "Warnings: %s%s", (Warnings) ? "ON" : "OFF", NEWLINE); ISQL_printf(isqlGlob.Out, Print_buffer); sprintf(Print_buffer, "Bail on error: %s%s", (BailOnError) ? "ON" : "OFF", NEWLINE); ISQL_printf(isqlGlob.Out, Print_buffer); return SKIP; } static processing_state help(const TEXT* what) { /************************************** * * h e l p * ************************************** * * Functional description * List the known commands. * **************************************/ // Ordered list of help messages to display. Use -1 to terminate list, // and 0 for an empty blank line static const SSHORT help_ids[] = { HLP_FRONTEND, // Frontend commands: HLP_BLOBDMP, /*BLOBDUMP -- dump BLOB to a file */ HLP_BLOBVIEW, /*BLOBVIEW -- view BLOB in text editor */ HLP_EDIT, /*EDIT [] -- edit SQL script file and execute */ HLP_EDIT2, /*EDIT -- edit current command buffer and execute */ HLP_HELP, /*HELP -- display this menu */ HLP_INPUT, /*INput -- take input from the named SQL file */ HLP_OUTPUT, /*OUTput [] -- write output to named file */ HLP_OUTPUT2, /*OUTput -- return output to stdout */ HLP_SET_ROOT, /*SET