8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-25 00:03:03 +01:00
firebird-mirror/src/isql/isql.epp

8673 lines
217 KiB
Plaintext
Raw Normal View History

2001-05-23 15:26:42 +02:00
/*
* PROGRAM: Interactive SQL utility
* MODULE: isql.epp
2001-05-23 15:26:42 +02:00
* 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): ______________________________________.
*
2001-05-23 15:26:42 +02:00
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
2002-06-29 15:39:11 +02:00
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.
2001-05-23 15:26:42 +02:00
*/
2001-12-24 03:51:06 +01:00
#ifdef DARWIN
#define _STLP_CCTYPE
#endif
#include "firebird.h"
2004-04-29 00:36:29 +02:00
#include <stdio.h>
#include "../dsql/keywords.h"
#include "../jrd/gds_proto.h"
2001-05-23 15:26:42 +02:00
#include <stdlib.h>
#include <stdarg.h>
2001-05-23 15:26:42 +02:00
#include <string.h>
2006-06-06 17:42:50 +02:00
#include <limits.h>
2001-05-23 15:26:42 +02:00
#include <math.h>
#include <ctype.h>
#include <errno.h>
2003-12-31 06:36:12 +01:00
#include "../common/utils_proto.h"
2006-06-01 07:12:38 +02:00
#include "../common/classes/array.h"
2008-02-13 12:53:41 +01:00
#include "../common/classes/init.h"
#include "../common/classes/ClumpletWriter.h"
#include "../common/classes/TempFile.h"
2001-05-23 15:26:42 +02:00
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#ifdef WIN_NT
#include <io.h> // 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
2003-09-09 13:03:37 +02:00
enum literal_string_type
{
INIT_STR_FLAG = 0,
SINGLE_QUOTED_STRING = 1,
DOUBLE_QUOTED_STRING = 2,
NO_MORE_STRING = 3,
INCOMPLETE_STRING = 4
2003-09-09 13:03:37 +02:00
};
#include "../common/classes/timestamp.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/common.h"
2003-02-11 21:17:56 +01:00
#if defined(WIN_NT)
2001-05-23 15:26:42 +02:00
#include <windows.h>
#endif
#include "../jrd/ibase.h"
2001-05-23 15:26:42 +02:00
#include "../isql/isql.h"
#include "../jrd/perf.h"
#include "../jrd/license.h"
#include "../jrd/constants.h"
#include "../jrd/ods.h"
2003-07-06 10:14:44 +02:00
#include "../jrd/file_params.h"
//#include "../common/stuff.h"
2001-05-23 15:26:42 +02:00
#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"
2008-01-16 08:23:29 +01:00
#ifdef SCROLLABLE_CURSORS
#include "../jrd/scroll_cursors.h"
#endif
#include "../isql/Extender.h"
#include "../isql/PtrSentry.h"
#include "../common/classes/UserBlob.h"
#include "../common/classes/MsgPrint.h"
using MsgFormat::SafeArg;
2008-01-16 08:23:29 +01:00
#include "../isql/ColList.h"
#include "../isql/InputDevices.h"
#include "../isql/OptionsBase.h"
2001-05-23 15:26:42 +02:00
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
2001-05-23 15:26:42 +02:00
// Print lengths of numeric values
2001-05-23 15:26:42 +02:00
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
enum switch_argtype {
SWARG_NONE,
SWARG_STRING,
SWARG_INTEGER
};
enum switch_id {
SWITCH_EXTRACTALL,
SWITCH_BAIL,
SWITCH_CACHE,
SWITCH_CHARSET,
SWITCH_DATABASE,
SWITCH_ECHO,
SWITCH_EXTRACT,
SWITCH_INPUT,
SWITCH_MERGE,
SWITCH_MERGE2,
SWITCH_NOAUTOCOMMIT,
SWITCH_NODBTRIGGERS,
SWITCH_NOWARN,
SWITCH_OUTPUT,
SWITCH_PAGE,
SWITCH_PASSWORD,
SWITCH_QUIET,
SWITCH_ROLE,
SWITCH_ROLE2,
SWITCH_SQLDIALECT,
SWITCH_TERM,
2006-12-08 19:38:15 +01:00
#ifdef TRUSTED_AUTH
SWITCH_TRUSTED,
#endif
SWITCH_USER,
SWITCH_VERSION,
#ifdef DEV_BUILD
SWITCH_EXTRACTTBL,
#endif
// this id has to be the last
SWITCH_UNKNOWN
};
struct switch_info {
switch_id id; // switch id
const char* text; // canonical switch name
size_t abbrlen; // minimum abbreviation length
switch_argtype argtype; // argument type (SWARG_NONE if none)
int usage_id; // message id for usage text
//bool allow_dup; // switch can be used more then once
};
const switch_info switches[] =
{
#ifdef DEV_BUILD
{ SWITCH_EXTRACTTBL, "xt", 2, SWARG_NONE, -1 },
#endif
{ SWITCH_EXTRACTALL, "all", 1, SWARG_NONE, 11 },
{ SWITCH_BAIL, "bail", 1, SWARG_NONE, 104 },
{ SWITCH_CACHE, "cache", 1, SWARG_INTEGER, 111 },
{ SWITCH_CHARSET, "charset", 2, SWARG_STRING, 122 },
{ SWITCH_DATABASE, "database", 1, SWARG_STRING, 123 },
{ SWITCH_ECHO, "echo", 1, SWARG_NONE, 124 },
{ SWITCH_EXTRACT, "extract", 2, SWARG_NONE, 125 },
{ SWITCH_INPUT, "input", 1, SWARG_STRING, 126 },
{ SWITCH_MERGE, "merge", 1, SWARG_NONE, 127 },
{ SWITCH_MERGE2, "m2", 2, SWARG_NONE, 128 },
{ SWITCH_NOAUTOCOMMIT, "noautocommit", 1, SWARG_NONE, 129 },
{ SWITCH_NODBTRIGGERS, "nodbtriggers", 3, SWARG_NONE, 154 },
{ SWITCH_NOWARN, "nowarnings", 3, SWARG_NONE, 130 },
{ SWITCH_OUTPUT, "output", 1, SWARG_STRING, 131 },
{ SWITCH_PAGE, "pagelength", 3, SWARG_INTEGER, 132 },
{ SWITCH_PASSWORD, "password", 1, SWARG_STRING, 133 },
{ SWITCH_QUIET, "quiet", 1, SWARG_NONE, 134 },
{ SWITCH_ROLE, "role", 1, SWARG_STRING, 135 },
{ SWITCH_ROLE2, "r2", 2, SWARG_STRING, 136 },
{ SWITCH_SQLDIALECT, "sqldialect", 1, SWARG_INTEGER, 137 },
{ SWITCH_SQLDIALECT, "sql_dialect", 1, SWARG_INTEGER, -1 },
{ SWITCH_TERM, "terminator", 1, SWARG_STRING, 138 },
2006-12-08 19:38:15 +01:00
#ifdef TRUSTED_AUTH
{ SWITCH_TRUSTED, "trusted", 2, SWARG_NONE, 155 },
#endif
{ SWITCH_USER, "user", 1, SWARG_STRING, 139 },
{ SWITCH_EXTRACT, "x", 1, SWARG_NONE, 140 },
{ SWITCH_VERSION, "z", 1, SWARG_NONE, 141 }
};
static inline bool commit_trans(isc_tr_handle* x)
2004-05-09 07:48:33 +02:00
{
if (isc_commit_transaction (isc_status, x)) {
ISQL_errmsg (isc_status);
isc_rollback_transaction (isc_status, x);
return false;
}
return true;
}
2001-05-23 15:26:42 +02:00
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);
}
static inline int fb_isdigit(const char c)
{
2006-06-16 00:34:49 +02:00
return isdigit((int)(UCHAR)c);
}
2004-05-09 07:48:33 +02:00
// I s q l G l o b a l s : : p r i n t f
// Output to the Out stream.
2005-05-24 06:42:01 +02:00
void IsqlGlobals::printf(const char* buffer, ...)
{
va_list args;
va_start(args, buffer);
vfprintf(Out, buffer, args);
va_end(args);
fflush(Out); // John's fix.
}
// I s q l G l o b a l s : : p r i n t s
// Output to the Out stream a literal string. No escape characters recognized.
void IsqlGlobals::prints(const char* buffer)
{
fprintf(Out, "%s", buffer);
fflush(Out); // John's fix.
}
2005-05-24 06:42:01 +02:00
2003-09-17 12:49:29 +02:00
struct ri_actions {
const SCHAR* ri_action_name;
const SCHAR* ri_action_print_caps;
const SCHAR* ri_action_print_mixed;
2003-09-17 12:49:29 +02:00
};
2001-05-23 15:26:42 +02:00
static processing_state add_row(TEXT*);
static processing_state blobedit(const TEXT*, const TEXT* const*);
static processing_state bulk_insert_hack(const char* command, XSQLDA** sqldap);
static bool bulk_insert_retriever(const char* prompt);
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);
2008-01-16 08:23:29 +01:00
static void frontend_free_parms(TEXT*[], TEXT*[], TEXT parm_defaults[][1]);
static void frontend_load_parms(const TEXT* p, TEXT* parms[], TEXT* lparms[],
TEXT parm_defaults[][1]);
static processing_state do_set_command(const TEXT*, bool*);
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 void print_set(const char* str, bool v);
static processing_state print_sets();
static processing_state help(const TEXT*);
static bool isyesno(const TEXT*);
static processing_state newdb(TEXT*, const TEXT*, const TEXT*, int, const TEXT*, bool);
static processing_state newinput(const TEXT*);
static processing_state newoutput(const TEXT*);
static processing_state newsize(const TEXT*, const TEXT*);
static processing_state newtrans(const TEXT*);
2004-04-29 00:36:29 +02:00
static processing_state parse_arg(int, SCHAR**, SCHAR*, FILE**);
#ifdef DEV_BUILD
static processing_state passthrough(const char* cmd);
#endif
2008-01-16 08:23:29 +01:00
static SSHORT print_item(TEXT**, XSQLVAR*, const int);
static void print_item_numeric(SINT64, int, int, TEXT*);
static processing_state print_line(XSQLDA*, const int pad[], 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);
2008-01-16 08:23:29 +01:00
static void process_header(const XSQLDA* sqlda, const int pad[], TEXT header[], TEXT header2[]);
static void process_plan();
2008-01-16 08:23:29 +01:00
static SLONG process_record_count(const int statement_type);
static SSHORT process_request_type();
static SLONG* process_sqlda_buffer(XSQLDA* const sqlda, SSHORT nullind[]);
2008-01-16 08:23:29 +01:00
static SLONG process_sqlda_display(XSQLDA* const sqlda, SLONG buffer[], int pad[]);
static int process_statement(const TEXT*, XSQLDA**);
#ifdef WIN_NT
static BOOL CALLBACK query_abort(DWORD);
#else
static void CLIB_ROUTINE query_abort(int);
#endif
2008-01-16 08:23:29 +01:00
static bool stdin_redirected();
2003-09-09 13:03:37 +02:00
static void strip_quotes(const TEXT*, TEXT*);
//#ifdef DEV_BUILD
static const char* sqltype_to_string(USHORT);
//#endif
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
USHORT requested_SQL_dialect = SQL_DIALECT_V6;
2008-01-16 08:23:29 +01:00
//bool connecting_to_pre_v6_server = false; Not used now.
2006-12-08 19:38:15 +01:00
bool Quiet = false;
#ifdef TRUSTED_AUTH
bool Trusted_auth = false;
#endif
bool Version_info = false;
2001-05-23 15:26:42 +02:00
// Utility transaction handle
static isc_tr_handle D__trans;
static isc_tr_handle M__trans;
2006-09-07 09:56:25 +02:00
static int global_numbufs; // # of cache buffers on connect
static isc_stmt_handle global_Stmt;
2008-01-16 08:23:29 +01:00
//static FILE* Ofp;
//static FILE* Ifp;
static SCHAR Password[128];
static SCHAR Charset[128];
static bool Merge_stderr;
2008-01-16 08:23:29 +01:00
static ColList global_Cols;
static int global_Col_default = 0; // Need to write code for it in the future.
2008-02-13 12:53:41 +01:00
static Firebird::GlobalPtr<InputDevices> Filelist;
2008-01-16 08:23:29 +01:00
//static TEXT Tmpfile[MAXPATHLEN];
2004-12-09 03:50:47 +01:00
static bool Abort_flag = false;
static bool Interrupt_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;
2008-01-16 08:23:29 +01:00
static int Pagelength = 20;
static bool Stats = false;
static bool Autocommit = true; // Commit ddl
static bool Nodbtriggers = false; // No database triggers
static bool Warnings = true; // Print warnings
#ifdef SCROLLABLE_CURSORS
static bool Autofetch = true; // automatically fetch all records
2008-01-16 08:23:29 +01:00
static USHORT fetch_direction = isc_fetch_next;
2001-05-23 15:26:42 +02:00
static SLONG fetch_offset = 1;
#endif
static int 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;
2004-04-29 00:36:29 +02:00
static FILE* Diag;
static FILE* Help;
static const TEXT* const sql_prompt = "SQL> ";
#ifdef SCROLLABLE_CURSORS
static const TEXT* const fetch_prompt = "FETCH> ";
#endif
2001-05-23 15:26:42 +02:00
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];
2001-05-23 15:26:42 +02:00
static TEXT Print_buffer[PRINT_BUFFER_LENGTH];
2003-12-31 06:36:12 +01:00
// 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,
2001-05-23 15:26:42 +02:00
isc_info_ods_minor_version,
isc_info_db_sql_dialect
};
#endif
#ifdef NOT_USED_OR_REPLACED
static const UCHAR text_bpb[] =
{
2001-05-23 15:26:42 +02:00
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[] =
{
2001-05-23 15:26:42 +02:00
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;
}
2001-05-23 15:26:42 +02:00
// Note that these transaction options aren't understood in Version 3.3
static const UCHAR default_tpb[] =
{
isc_tpb_version1, isc_tpb_write,
2001-05-23 15:26:42 +02:00
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
2002-06-29 15:39:11 +02:00
};
#endif
2002-06-29 15:39:11 +02:00
2005-04-25 07:23:57 +02:00
// If the action is restrict, do not print anything at all
2003-10-16 10:51:06 +02:00
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, "", ""},
{"", "", ""},
2003-10-16 10:51:06 +02:00
{0, 0, 0}
2001-05-23 15:26:42 +02:00
};
int CLIB_ROUTINE main(int argc,
char* argv[])
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* m a i n
*
**************************************
*
* Functional description
* This calls ISQL_main, and exists to
* isolate main which does not exist under
* MS Windows.
*
**************************************/
// Initialize globals
isqlGlob.major_ods = 0;
2006-07-18 12:18:41 +02:00
isqlGlob.minor_ods = 0;
2001-05-23 15:26:42 +02:00
return ISQL_main(argc, argv);
2001-05-23 15:26:42 +02:00
}
2003-12-03 09:19:24 +01:00
int ISQL_main(int argc,
char* argv[])
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2006-09-07 09:56:25 +02:00
// This is code for QA Test bed Multiuser environment.
// Setup multi-user test environment.
2001-05-23 15:26:42 +02:00
if (qa_mu_environment()) {
2006-09-07 09:56:25 +02:00
// Initialize multi-user test manager only if in that
// environment.
2001-05-23 15:26:42 +02:00
if (!qa_mu_init(argc, argv, 1)) {
2006-09-07 09:56:25 +02:00
// Some problem in initializing multi-user test
// environment.
2001-05-23 15:26:42 +02:00
qa_mu_cleanup();
exit(1);
}
else {
2006-09-07 09:56:25 +02:00
// When invoked by MU, additional command-line argument
// was added at tail. ISQL is not interested in it so
// remove it.
2001-05-23 15:26:42 +02:00
argv[argc - 1] = NULL;
argc--;
}
}
#endif // MU_ISQL
2001-05-23 15:26:42 +02:00
TEXT tabname[WORDLENGTH];
2001-05-23 15:26:42 +02:00
tabname[0] = '\0';
isqlGlob.db_SQL_dialect = 0;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Output goes to stdout by default
isqlGlob.Out = stdout;
isqlGlob.Errfp = stderr;
2001-05-23 15:26:42 +02:00
const processing_state ret = parse_arg(argc, argv, tabname, NULL);
2001-05-23 15:26:42 +02:00
2008-01-16 08:23:29 +01:00
// Can't do a simple assignment because parse_arg may set Interactive to false.
if (stdin_redirected())
Interactive = false;
2005-04-25 07:23:57 +02:00
// Init the diagnostics and help files
if (Merge_diagnostic)
Diag = isqlGlob.Out;
else
2004-04-29 00:36:29 +02:00
Diag = stdout;
2004-04-29 00:36:29 +02:00
Help = stdout;
2001-05-23 15:26:42 +02:00
if (Merge_stderr)
isqlGlob.Errfp = isqlGlob.Out;
2001-05-23 15:26:42 +02:00
ISQL_make_upper(tabname);
2001-05-23 15:26:42 +02:00
switch (ret) {
case EXTRACT:
case EXTRACTALL:
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)
{
LegacyTables flag = ret == EXTRACT ? SQL_objects : ALL_objects;
Exit_value = EXTRACT_ddl(flag, tabname);
ISQL_disconnect_database(true);
// isc_detach_database(isc_status, &DB);
}
else
Exit_value = FINI_ERROR;
2001-05-23 15:26:42 +02:00
}
break;
case ps_ERR:
{
TEXT helpstring[155];
ISQL_msg_get(USAGE, sizeof(helpstring), helpstring);
STDERROUT(helpstring);
for (int i = 0; i < FB_NELEM(switches); i++) {
if (switches[i].usage_id >= 0) {
ISQL_msg_get(switches[i].usage_id, sizeof(helpstring), helpstring);
STDERROUT(helpstring);
}
}
Exit_value = FINI_ERROR;
break;
}
2001-05-23 15:26:42 +02:00
default:
do_isql();
// keep Exit_value to whatever it is set
// by do_isql()
// Exit_value = FINI_OK;
2001-05-23 15:26:42 +02:00
break;
}
#ifdef MU_ISQL
2006-09-07 09:56:25 +02:00
// 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.
2001-05-23 15:26:42 +02:00
qa_mu_cleanup();
#endif // MU_ISQL
2001-05-23 15:26:42 +02:00
#ifdef DEBUG_GDS_ALLOC
2006-09-07 09:56:25 +02:00
// 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);
2001-05-23 15:26:42 +02:00
#endif
return Exit_value;
}
void ISQL_array_dimensions(const TEXT* fieldname)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
*
**************************************/
isqlGlob.printf("[");
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Transaction for all frontend commands
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return;
}
2001-05-23 15:26:42 +02:00
FOR FDIM IN RDB$FIELD_DIMENSIONS WITH
FDIM.RDB$FIELD_NAME EQ fieldname
SORTED BY FDIM.RDB$DIMENSION
// Format is [lower:upper, lower:upper,..]
// When lower == 1 no need to print a range. Done.
// When upper == 1 no need to print a range either, but it's confusing. Not done.
if (FDIM.RDB$DIMENSION > 0) {
isqlGlob.printf(", ");
}
if (FDIM.RDB$LOWER_BOUND == 1)
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%ld", FDIM.RDB$UPPER_BOUND);
else
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%ld:%ld", FDIM.RDB$LOWER_BOUND, FDIM.RDB$UPPER_BOUND);
2001-05-23 15:26:42 +02:00
END_FOR
ON_ERROR
ISQL_errmsg(isc_status);
return;
2001-05-23 15:26:42 +02:00
END_ERROR;
isqlGlob.printf("]");
2001-05-23 15:26:42 +02:00
}
// 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;
}
2001-05-23 15:26:42 +02:00
bool ISQL_dbcheck()
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2001-05-23 15:26:42 +02:00
*
* Change from miroslavp on 2005.04.25
* 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.
2001-05-23 15:26:42 +02:00
**************************************/
TEXT errbuf[MSG_LENGTH];
if (!isqlGlob.global_Db_name[0]) {
if (!Quiet)
{
ISQL_msg_get(NO_DB, errbuf);
STDERROUT(errbuf);
2001-05-23 15:26:42 +02:00
}
return false;
2001-05-23 15:26:42 +02:00
}
else
return true;
2001-05-23 15:26:42 +02:00
}
static char userPrompt2[MSG_LENGTH];
static const char* const userPrompt = userPrompt2;
static char* lastInputLine = NULL;
static int getColumn = -1;
2001-05-23 15:26:42 +02:00
void ISQL_prompt(const TEXT* string)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* I S Q L _ p r o m p t
2001-05-23 15:26:42 +02:00
*
**************************************
*
* Functional description
* Print a prompt string for interactive user
* Not for Windows, otherwise flush the string
**************************************/
//userPrompt = (const char*) string;
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(userPrompt2, string, MSG_LENGTH);
2002-06-29 15:39:11 +02:00
//#ifndef HAVE_READLINE_READLINE_H
2004-04-29 00:36:29 +02:00
// fprintf(stdout, userPrompt.c_str());
// fflush(stdout);
//#endif
2002-06-29 15:39:11 +02:00
}
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;
}
2002-06-29 15:39:11 +02:00
getColumn = 0;
#ifdef HAVE_EDITLINE_H
2008-02-13 12:53:41 +01:00
if (Filelist->readingStdin())
{
// 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);
}
2005-04-03 08:58:40 +02:00
// Let's count lines if someone wants to enable line number error messages
// for console reading for whatever reason.
2008-02-13 12:53:41 +01:00
++Filelist->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.
2004-04-29 00:36:29 +02:00
fprintf(stdout, prompt);
fflush(stdout);
}
// Read the line
char buffer[MAX_USHORT];
2008-02-13 12:53:41 +01:00
if (fgets(buffer, sizeof(buffer), Filelist->Ifp().indev_fpointer) != NULL)
{
size_t lineSize = strlen(buffer);
2008-01-16 08:23:29 +01:00
// If the last non empty line doesn't end in '\n', indev_aux won't be
// updated, but then there're no more commands, so it's irrelevant.
while (lineSize > 0 &&
2004-12-24 09:52:39 +01:00
(buffer[lineSize - 1] == '\n' || buffer[lineSize - 1] == '\r'))
2004-12-16 13:45:30 +01:00
{
buffer[--lineSize] = '\0';
2008-02-13 12:53:41 +01:00
++Filelist->Ifp().indev_aux;
}
lastInputLine = (char*) malloc(lineSize + 1);
memcpy(lastInputLine, buffer, lineSize + 1);
2004-12-09 02:53:31 +01:00
}
}
static void readNextInputLine()
{
2007-03-01 12:40:26 +01:00
readNextInputLine(userPrompt);
if (Echo && (lastInputLine != NULL)) {
isqlGlob.printf("%s%s", lastInputLine, 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';
}
2005-03-23 11:10:38 +01:00
// cast to unsigned char to prevent sign expansion
// this way we can distinguish russian ya (0xFF) and EOF (usually (-1))
2005-03-23 11:10:38 +01:00
return (unsigned char)lastInputLine[getColumn++];
2001-05-23 15:26:42 +02:00
}
void ISQL_copy_SQL_id(const TEXT* in_str,
TEXT* output_str,
TEXT escape_char)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2001-05-23 15:26:42 +02:00
*
**************************************/
2002-06-29 15:39:11 +02:00
/* 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
2003-12-31 06:36:12 +01:00
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;
break;
}
*q1 = *p1;
}
if (!need_quotes && !KEYWORD_stringIsAToken(in_str))
{
*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';
2001-05-23 15:26:42 +02:00
}
void ISQL_errmsg(const ISC_STATUS* status)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* I S Q L _ e r r m s g
*
**************************************
*
* Functional description
* Report error conditions
2004-04-29 00:36:29 +02:00
* Simulate isc_print_status exactly, to control stderr
2001-05-23 15:26:42 +02:00
**************************************/
TEXT errbuf[MSG_LENGTH];
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, SafeArg() << isc_sqlcode(status));
STDERROUT(errbuf);
if (fb_interpret(errbuf, sizeof(errbuf), &vec))
{
STDERROUT(errbuf);
// Continuation of error
errbuf[0] = '-';
while (fb_interpret(errbuf + 1, sizeof(errbuf) - 1, &vec)) {
STDERROUT(errbuf);
}
2001-05-23 15:26:42 +02:00
}
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:
case isc_dsql_no_dup_name:
vec = &status[8];
while (*vec++ != isc_arg_end)
if (vec[0] == isc_dsql_line_col_error && vec[1] == isc_arg_number)
{
linenum = vec[2];
//STDERROUT(s);
break;
}
break;
case isc_dsql_token_unk_err:
if (status[8] == isc_arg_number)
{
linenum = status[9];
//s = errbuf;
//STDERROUT(s);
}
break;
}
}
/* CVC: Obsolete on 2005.10.06 because now line & column are numeric arguments.
if (s)
{
while (*s && !fb_isdigit(*s))
++s;
if (isdigit(*s))
{
linenum = 0;
for (; *s && isdigit(*s); ++s)
linenum = linenum * 10 + *s - '0';
}
}
*/
2008-02-13 12:53:41 +01:00
const InputDevices::indev& Ifp = Filelist->Ifp();
if (linenum != -1)
{
linenum += Ifp.indev_line;
2008-01-16 08:23:29 +01:00
ISQL_msg_get(EXACTLINE, errbuf, SafeArg() << linenum << Ifp.fileName());
}
else
2008-01-16 08:23:29 +01:00
ISQL_msg_get(AFTERLINE, errbuf, SafeArg() << Ifp.indev_line << Ifp.fileName());
STDERROUT(errbuf);
}
2001-05-23 15:26:42 +02:00
}
}
void ISQL_warning(ISC_STATUS* status)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* I S Q L _ w a r n i n g
*
**************************************
*
* Functional desription
* Report warning
2004-04-29 00:36:29 +02:00
* Simulate isc_print_status exactly, to control stderr
2001-05-23 15:26:42 +02:00
**************************************/
const ISC_STATUS* vec = status;
fb_assert(vec[1] == 0); // shouldn't be any errors
2001-05-23 15:26:42 +02:00
//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))
{
STDERROUT(buf);
// Continuation of warning
buf[0] = '-';
while (fb_interpret(buf + 1, sizeof(buf) - 1, &vec)) {
STDERROUT(buf);
}
2001-05-23 15:26:42 +02:00
}
}
status[2] = isc_arg_end;
2001-05-23 15:26:42 +02:00
}
SSHORT ISQL_get_default_char_set_id()
{
/*************************************
2001-05-23 15:26:42 +02:00
*
* 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
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
END_FOR;
return (default_char_set_id);
}
SSHORT ISQL_get_field_length(const TEXT* field_name)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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.
2001-05-23 15:26:42 +02:00
*
**************************************/
2005-04-25 07:23:57 +02:00
// Transaction for all frontend commands
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return 0;
}
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
return l;
}
void ISQL_get_character_sets(
SSHORT char_set_id,
SSHORT collation,
bool collate_only,
TEXT* string)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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;
2001-05-23 15:26:42 +02:00
#endif
string[0] = 0;
2005-04-25 07:23:57 +02:00
// Transaction for all frontend commands
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return;
}
2001-05-23 15:26:42 +02:00
//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
2001-05-23 15:26:42 +02:00
#ifdef DEV_BUILD
found = true;
2001-05-23 15:26:42 +02:00
#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);
2006-09-07 09:56:25 +02:00
// 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;
2001-05-23 15:26:42 +02:00
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);
2001-05-23 15:26:42 +02:00
}
#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
2001-05-23 15:26:42 +02:00
#ifdef DEV_BUILD
found = true;
2001-05-23 15:26:42 +02:00
#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;
2001-05-23 15:26:42 +02:00
#ifdef DEV_BUILD
if (!found) {
sprintf(Print_buffer, "ISQL_get_character_set: charset %d not found.\n",
2001-05-23 15:26:42 +02:00
char_set_id);
STDERROUT(Print_buffer);
2001-05-23 15:26:42 +02:00
}
#endif
}
}
void ISQL_get_default_source(const TEXT* rel_name,
TEXT* field_name,
ISC_QUAD* blob_id)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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);
2001-05-23 15:26:42 +02:00
*blob_id = isc_blob_null;
2005-04-25 07:23:57 +02:00
// Transaction for all frontend commands
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return;
}
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
END_ERROR;
}
2005-04-25 07:23:57 +02:00
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;
2001-05-23 15:26:42 +02:00
END_ERROR;
}
}
SLONG ISQL_get_index_segments(TEXT* segs,
const size_t buf_size,
const TEXT* indexname,
bool delimited_yes)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return 0;
}
2001-05-23 15:26:42 +02:00
TEXT* const segs_end = segs + buf_size - 1;
// Query to get column names
SLONG n = 0;
bool count_only = false;
2001-05-23 15:26:42 +02:00
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;
}
}
2001-05-23 15:26:42 +02:00
END_FOR
ON_ERROR
ISQL_errmsg(isc_status);
ROLLBACK;
return 0;
2001-05-23 15:26:42 +02:00
END_ERROR;
return n;
2001-05-23 15:26:42 +02:00
}
bool ISQL_get_base_column_null_flag(const TEXT* view_name,
const SSHORT view_context,
const TEXT* base_field)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Transaction for all frontend commands
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return false;
}
2001-05-23 15:26:42 +02:00
/*
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
2001-05-23 15:26:42 +02:00
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.
2001-05-23 15:26:42 +02:00
*/
bool null_flag = true;
bool done = false;
bool error = false;
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
END_ERROR;
}
return null_flag;
}
bool ISQL_get_null_flag(const TEXT* rel_name,
TEXT* field_name)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2001-05-23 15:26:42 +02:00
* 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);
2001-05-23 15:26:42 +02:00
bool null_flag = true;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Transaction for all frontend commands
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return false;
}
2001-05-23 15:26:42 +02:00
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 {
2004-03-07 08:58:55 +01:00
// If RDB$BASE_FIELD is not null then it is a view column
if (RFR.RDB$BASE_FIELD.NULL) {
2006-09-07 09:56:25 +02:00
// 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;
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
END_ERROR;
}
return null_flag;
}
void ISQL_disconnect_database(bool nQuietMode)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* I S Q L _ d i s c o n n e c t _ d a t a b a s e
2001-05-23 15:26:42 +02:00
*
**************************************
*
* Functional description
* Disconnect from the current database. First commit work and then
2005-04-25 07:23:57 +02:00
* call isc_detach_database to detach from the database and then zero
2001-05-23 15:26:42 +02:00
* out the DB handle.
*
* Change from miroslavp on 2005.05.06
* Quiet global variable is preserved in local variable and restored at the end.
2001-05-23 15:26:42 +02:00
**************************************/
const bool OriginalQuiet = Quiet;
// Ignore error msgs during disconnect
2001-05-23 15:26:42 +02:00
Quiet = nQuietMode;
2005-04-25 07:23:57 +02:00
// If we were in a database, commit before proceeding
2001-05-23 15:26:42 +02:00
if (DB && (M__trans || D__trans))
end_trans();
2006-09-07 09:56:25 +02:00
// 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);
2001-05-23 15:26:42 +02:00
2006-09-07 09:56:25 +02:00
// 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);
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Detach from old database
if (DB) {
isc_detach_database(isc_status, &DB);
}
2001-05-23 15:26:42 +02:00
// Restore original value of the flag
Quiet = OriginalQuiet;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Zero database handle and transaction handles
2004-05-03 01:06:37 +02:00
global_Stmt = 0;
DB = 0;
isqlGlob.global_Db_name[0] = '\0';
2004-05-03 01:06:37 +02:00
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;
2001-05-23 15:26:42 +02:00
return;
}
#ifdef NOT_USED_OR_REPLACED
bool ISQL_is_domain(const TEXT* field_name)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Transaction for all frontend commands
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return false;
}
2001-05-23 15:26:42 +02:00
FOR FLD IN RDB$FIELDS WITH
FLD.RDB$FIELD_NAME EQ field_name
if (!(implicit_domain(FLD.RDB$FIELD_NAME) && FLD.RDB$SYSTEM_FLAG != 1))
{
is_domain = true;
}
2001-05-23 15:26:42 +02:00
END_FOR
ON_ERROR
ISQL_errmsg(isc_status);
return is_domain;
2001-05-23 15:26:42 +02:00
END_ERROR;
return is_domain;
}
#endif
2001-05-23 15:26:42 +02:00
void ISQL_make_upper(TEXT* str)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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<UCHAR*>(str); *p; p++)
2001-05-23 15:26:42 +02:00
*p = UPPER7(*p);
}
void ISQL_msg_get(USHORT number,
TEXT* msg,
const SafeArg& args)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* I S Q L _ m s g _ g e t
*
**************************************
*
* Functional description
* Retrieve a message from the error file
*
**************************************/
fb_msg_format(NULL, ISQL_MSG_FAC, number, MSG_LENGTH, msg, args);
2001-05-23 15:26:42 +02:00
}
void ISQL_msg_get(USHORT number,
USHORT size,
TEXT* msg,
const SafeArg& args)
{
/**************************************
*
* I S Q L _ m s g _ g e t
*
**************************************
*
* Functional description
* Retrieve a message from the error file
*
**************************************/
fb_msg_format(NULL, ISQL_MSG_FAC, number, size, msg, args);
}
2004-04-29 00:36:29 +02:00
void ISQL_print_validation(FILE* fp,
ISC_QUAD* blobid,
2003-09-09 13:03:37 +02:00
bool isComputedField,
2004-05-03 01:06:37 +02:00
FB_API_HANDLE trans)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2001-05-23 15:26:42 +02:00
* 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 == false, this is a validation clause,
* if flag == true, this is a computed field
2001-05-23 15:26:42 +02:00
*
**************************************/
2005-04-25 07:23:57 +02:00
// Don't bother with null blobs
2001-05-23 15:26:42 +02:00
if (blobid->gds_quad_high == 0)
2001-05-23 15:26:42 +02:00
return;
UserBlob blob(isc_status);
if (!blob.open(DB, trans, *blobid))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return;
}
bool issql = false;
bool firsttime = true;
TEXT buffer[BUFFER_LENGTH512];
size_t length;
while (blob.getSegment(sizeof(buffer) - 1, buffer, length))
{
2001-05-23 15:26:42 +02:00
buffer[length] = 0;
const TEXT* p = buffer;
2003-09-09 13:03:37 +02:00
if (isComputedField) {
// computed field SQL syntax correction
2001-05-23 15:26:42 +02:00
while (fb_isspace(*p))
2001-05-23 15:26:42 +02:00
p++;
if (*p == '(')
issql = true;
2001-05-23 15:26:42 +02:00
}
else {
// Validation SQL syntax correction
2001-05-23 15:26:42 +02:00
while (fb_isspace(*p))
2001-05-23 15:26:42 +02:00
p++;
if (!strncmp(p, "check", 5) || !strncmp(p, "CHECK", 5))
issql = true;
2001-05-23 15:26:42 +02:00
}
if (firsttime) {
firsttime = false;
2001-05-23 15:26:42 +02:00
if (!issql) {
2005-12-03 08:01:16 +01:00
ISQL_printf2(fp, "%s ", (isComputedField ? "/* " : "("));
2001-05-23 15:26:42 +02:00
}
}
ISQL_printf(fp, buffer);
}
if (!issql) {
2005-12-03 08:01:16 +01:00
ISQL_printf2(fp, "%s", (isComputedField ? " */" : ")"));
2001-05-23 15:26:42 +02:00
}
if (isc_status[1] && isc_status[1] != isc_segstr_eof)
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
}
2004-04-29 00:36:29 +02:00
void ISQL_printf(FILE* fp,
const char* buffer)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* I S Q L _ p r i n t f
*
**************************************
*
* Centralized printing facility
*
**************************************/
2004-04-29 00:36:29 +02:00
fprintf(fp, "%s", buffer);
fflush(fp); // John's fix.
2001-05-23 15:26:42 +02:00
}
void ISQL_printf2(FILE* fp,
const char* buffer, ...)
{
/**************************************
*
* I S Q L _ p r i n t f 2
*
**************************************
*
* Centralized printing facility, more flexible
*
**************************************/
va_list args;
va_start(args, buffer);
vfprintf(fp, buffer, args);
va_end(args);
fflush(fp); // John's fix.
}
static processing_state add_row(TEXT* tabname)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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 (ps_ERR);
2001-05-23 15:26:42 +02:00
if (!Interactive)
return (ps_ERR);
if (Input_file)
return ps_ERR;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// There may have been a commit transaction before we got here
2001-05-23 15:26:42 +02:00
if (!M__trans)
if (isc_start_transaction(isc_status, &M__trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return FAIL;
}
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Start default transaction for prepare
2001-05-23 15:26:42 +02:00
if (!D__trans)
if (isc_start_transaction(isc_status, &D__trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return FAIL;
}
2001-05-23 15:26:42 +02:00
chop_at(tabname, QUOTEDLENGTH);
if (tabname[0] != DBL_QUOTE)
ISQL_make_upper(tabname);
/*
chop_at(tablename, WORDLENGTH);
const bool delimited_yes = tablename[0] == DBL_QUOTE;
TEXT tabname[QUOTEDLENGTH];
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];
2001-05-23 15:26:42 +02:00
sprintf(string, "SELECT * FROM %s", tabname);
XSQLDA* sqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(20)));
memset(sqlda, 0, XSQLDA_LENGTH(20));
2001-05-23 15:26:42 +02:00
sqlda->version = SQLDA_VERSION1;
sqlda->sqln = 20;
if (isc_dsql_prepare(isc_status, &D__trans, &global_Stmt, 0, string,
isqlGlob.SQL_dialect, sqlda))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
if (sqlda)
ISQL_FREE(sqlda);
2001-05-23 15:26:42 +02:00
return (SKIP);
}
const SSHORT n_cols = sqlda->sqld;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Reallocate to the proper size if necessary
2001-05-23 15:26:42 +02:00
if (sqlda->sqld > sqlda->sqln) {
ISQL_FREE(sqlda);
sqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(n_cols)));
memset(sqlda, 0, XSQLDA_LENGTH(n_cols));
sqlda->version = SQLDA_VERSION1;
2001-05-23 15:26:42 +02:00
sqlda->sqld = sqlda->sqln = n_cols;
// We must re-describe the sqlda, no need to re-prepare
2001-05-23 15:26:42 +02:00
if (isc_dsql_describe(isc_status, &global_Stmt, isqlGlob.SQL_dialect, sqlda)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_close);
2001-05-23 15:26:42 +02:00
if (sqlda)
ISQL_FREE(sqlda);
return (FAIL);
}
2001-05-23 15:26:42 +02:00
}
isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_close);
2001-05-23 15:26:42 +02:00
// Array for storing select/insert column mapping, colcheck sets it up
SSHORT* colnumber = (SSHORT*) ISQL_ALLOC((SLONG) (n_cols * sizeof(SSHORT)));
2001-05-23 15:26:42 +02:00
col_check(tabname, colnumber);
2005-04-25 07:23:57 +02:00
// Create the new INSERT statement from the sqlda info
2001-05-23 15:26:42 +02:00
SCHAR* insertstring = (SCHAR*) ISQL_ALLOC((SLONG) (40 + (QUOTEDLENGTH + 4) * n_cols));
2001-05-23 15:26:42 +02:00
// There is a question mark for each column that's known to be updatable.
2001-05-23 15:26:42 +02:00
sprintf(insertstring, "INSERT INTO %s (", tabname);
int i;
SSHORT i_cols = 0;
i = 0;
{ // scope
char fldname[WORDLENGTH];
char fldfixed[QUOTEDLENGTH];
for (const XSQLVAR* var = sqlda->sqlvar; i < n_cols; var++, i++) {
// Skip columns that are computed
if (colnumber[i] != -1)
{
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(fldname, var->sqlname, var->sqlname_length + 1);
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++;
}
2001-05-23 15:26:42 +02:00
}
} // scope for MSVC6
// Set i_cols to the number of insert columns
2001-05-23 15:26:42 +02:00
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
2001-05-23 15:26:42 +02:00
insertstring[strlen(insertstring) - 1] = ')';
// Allocate INSERT sqlda and null indicators
2001-05-23 15:26:42 +02:00
XSQLDA* isqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(i_cols)));
2001-05-23 15:26:42 +02:00
2006-09-07 09:56:25 +02:00
// ISQL_ALLOC doesn't initialize the structure, so init everything
// to avoid lots of problems.
memset(isqlda, 0, XSQLDA_LENGTH(i_cols));
2001-05-23 15:26:42 +02:00
isqlda->version = SQLDA_VERSION1;
isqlda->sqln = isqlda->sqld = i_cols;
SSHORT* nullind = (SSHORT*) ISQL_ALLOC((SLONG) (i_cols * sizeof(SSHORT)));
2001-05-23 15:26:42 +02:00
2006-09-07 09:56:25 +02:00
// 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.
2001-05-23 15:26:42 +02:00
SCHAR infobuf[BUFFER_LENGTH180];
ISQL_msg_get(ADD_PROMPT, sizeof(infobuf), infobuf);
STDERROUT(infobuf);
TEXT msg[MSG_LENGTH];
SCHAR name[WORDLENGTH];
bool done = false;
2001-05-23 15:26:42 +02:00
while (!done) {
SSHORT* nullp = nullind;
i = 0;
for (const XSQLVAR* var = sqlda->sqlvar; i < n_cols && !done; var++, i++)
{
2001-05-23 15:26:42 +02:00
if (colnumber[i] == -1)
continue;
// SQLDA for INSERT statement might have fewer columns
const SSHORT j = colnumber[i];
XSQLVAR* ivar = &isqlda->sqlvar[j];
2001-05-23 15:26:42 +02:00
*ivar = *var;
*nullp = 0;
ivar->sqlind = nullp++;
ivar->sqldata = NULL;
{ // scope
const SSHORT length = var->sqlname_length;
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(name, var->sqlname, length + 1);
} // scope
const SSHORT type = var->sqltype & ~1;
const SSHORT nullable = var->sqltype & 1;
2001-05-23 15:26:42 +02:00
// Prompt with the name and read the line
2001-05-23 15:26:42 +02:00
switch (type)
{
case SQL_BLOB:
ISQL_msg_get(BLOB_PROMPT, msg, SafeArg() << name);
// Blob: %s, type 'edit' or filename to load>
break;
case SQL_TYPE_DATE:
ISQL_msg_get(DATE_PROMPT, msg, SafeArg() << name);
// Enter %s as Y/M/D>
break;
case SQL_TYPE_TIME:
ISQL_msg_get(TIME_PROMPT, msg, SafeArg() << name);
// Enter %s as H:M:S>
break;
case SQL_TIMESTAMP:
ISQL_msg_get(TIMESTAMP_PROMPT, msg, SafeArg() << name);
// Enter %s as Y/MON/D H:MIN:S[.MSEC]>
break;
default:
ISQL_msg_get(NAME_PROMPT, msg, SafeArg() << name);
2006-09-07 09:56:25 +02:00
// Enter %s>
break;
2001-05-23 15:26:42 +02:00
}
// On blank line or EOF, break the two loops without doing an insert.
2001-05-23 15:26:42 +02:00
readNextInputLine(msg);
getColumn = -1; // We are bypassing getNextInputChar().
if (lastInputLine == NULL || strlen(lastInputLine) == 0) {
done = true;
break;
}
2001-05-23 15:26:42 +02:00
const size_t length = strlen(lastInputLine);
if (length > MAX_USHORT - 2)
{
ISQL_msg_get(BUFFER_OVERFLOW, msg);
STDERROUT(msg);
done = true;
break;
}
STDERROUT("");
2001-05-23 15:26:42 +02:00
// Convert first 4 chars to upper case for comparison
SCHAR cmd[5];
strncpy(cmd, lastInputLine, 4);
2001-05-23 15:26:42 +02:00
cmd[4] = '\0';
ISQL_make_upper(cmd);
2001-05-23 15:26:42 +02:00
// If the user writes NULL, put a null in the column
2001-05-23 15:26:42 +02:00
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(&times, 0, sizeof(times));
char msec_str[5] = "";
int msec = 0;
ISC_QUAD* blobid;
2001-05-23 15:26:42 +02:00
switch (type) {
case SQL_BLOB:
blobid = (ISC_QUAD*) ISQL_ALLOC((SLONG) sizeof(ISC_QUAD));
2001-05-23 15:26:42 +02:00
if (!strcmp(cmd, "EDIT")) // If edit, we edit a temp file.
2001-05-23 15:26:42 +02:00
{
2006-06-30 21:09:24 +02:00
Firebird::PathName ftmp = TempFile::create(SCRATCH);
2006-07-01 04:21:22 +02:00
if (ftmp.empty())
2006-06-30 21:09:24 +02:00
res = 0;
2006-07-01 04:21:22 +02:00
else
{
2006-06-30 21:09:24 +02:00
gds__edit(ftmp.c_str(), 0);
res = BLOB_text_load(blobid, DB, M__trans, ftmp.c_str());
unlink(ftmp.c_str());
}
2001-05-23 15:26:42 +02:00
}
2006-07-01 04:21:22 +02:00
else // Otherwise just load the named file
2001-05-23 15:26:42 +02:00
{
2006-07-01 04:21:22 +02:00
// 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);
2001-05-23 15:26:42 +02:00
}
if (!res) {
STDERROUT("Unable to load file");
done = true;
2001-05-23 15:26:42 +02:00
}
ivar->sqldata = (SCHAR*) blobid;
2001-05-23 15:26:42 +02:00
break;
case SQL_FLOAT:
fvalue = (float*) ISQL_ALLOC(sizeof(float));
if (sscanf(lastInputLine, "%g", fvalue) != 1) {
STDERROUT("Input parsing problem");
done = true;
2001-05-23 15:26:42 +02:00
}
ivar->sqldata = (SCHAR*) fvalue;
2001-05-23 15:26:42 +02:00
break;
case SQL_DOUBLE:
dvalue = (double*) ISQL_ALLOC(sizeof(double));
if (sscanf(lastInputLine, "%lg", dvalue) != 1) {
STDERROUT("Input parsing problem");
done = true;
2001-05-23 15:26:42 +02:00
}
ivar->sqldata = (SCHAR*) dvalue;
2001-05-23 15:26:42 +02:00
break;
case SQL_TYPE_DATE:
if (3 != sscanf(lastInputLine, "%d/%d/%d", &times.tm_year,
2007-02-25 22:25:33 +01:00
&times.tm_mon, &times.tm_mday) ||
!check_date(times))
{
ISQL_msg_get(DATE_ERR, msg, SafeArg() << 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(&times, date);
ivar->sqldata = (char*) date;
}
break;
2001-05-23 15:26:42 +02:00
case SQL_TYPE_TIME:
if (3 != sscanf(lastInputLine, "%d:%d:%d", &times.tm_hour,
2007-02-25 22:25:33 +01:00
&times.tm_min, &times.tm_sec) ||
!check_time(times))
{
ISQL_msg_get(TIME_ERR, msg, SafeArg() << lastInputLine);
STDERROUT(msg); // Bad time %s\n
done = true;
2001-05-23 15:26:42 +02:00
}
else {
ISC_TIME* vtime = (ISC_TIME*) ISQL_ALLOC((SLONG) ivar->sqllen);
isc_encode_sql_time(&times, vtime);
ivar->sqldata = (char*) vtime;
}
2001-05-23 15:26:42 +02:00
break;
case SQL_TIMESTAMP:
if (6 <= sscanf(lastInputLine, "%d/%d/%d %d:%d:%d.%4s",
&times.tm_year, &times.tm_mon, &times.tm_mday,
&times.tm_hour, &times.tm_min, &times.tm_sec,
msec_str))
{
2007-04-11 18:05:40 +02:00
unsigned int count = 0;
for (; count < 4; ++count)
{
if (fb_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, SafeArg() << lastInputLine);
STDERROUT(msg); // Bad timestamp %s\n
2001-05-23 15:26:42 +02:00
}
else
{
--times.tm_mon;
times.tm_year -= 1900;
ISC_TIMESTAMP* datetime =
(ISC_TIMESTAMP*) ISQL_ALLOC((SLONG) ivar->sqllen);
isc_encode_timestamp(&times, datetime);
datetime->timestamp_time += msec;
ivar->sqldata = (char*) datetime;
2001-05-23 15:26:42 +02:00
}
break;
case SQL_TEXT:
// Force all text to varying
2001-05-23 15:26:42 +02:00
ivar->sqltype = SQL_VARYING + nullable;
// Fall into:
2001-05-23 15:26:42 +02:00
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;
break;
}
2001-05-23 15:26:42 +02:00
case SQL_SHORT:
case SQL_LONG:
if (type == SQL_SHORT) {
smallint = (SSHORT*) ISQL_ALLOC(sizeof(SSHORT));
ivar->sqldata = (SCHAR*) smallint;
2001-05-23 15:26:42 +02:00
}
else if (type == SQL_LONG) {
integer = (SLONG*) ISQL_ALLOC(sizeof(SLONG));
ivar->sqldata = (SCHAR*) integer;
2001-05-23 15:26:42 +02:00
}
// Fall into:
2001-05-23 15:26:42 +02:00
// Fall through from SQL_SHORT and SQL_LONG ...
2001-05-23 15:26:42 +02:00
case SQL_INT64:
if (type == SQL_INT64) {
pi64 = (SINT64*) ISQL_ALLOC(sizeof(SINT64));
ivar->sqldata = (SCHAR*) pi64;
2001-05-23 15:26:42 +02:00
}
if (ivar->sqlscale < 0)
{
2001-05-23 15:26:42 +02:00
SSHORT lscale = 0;
if (get_numeric((UCHAR*) lastInputLine, length, &lscale, &n) != true) {
STDERROUT("Input parsing problem");
done = true;
2001-05-23 15:26:42 +02:00
}
else {
int dscale = ivar->sqlscale - lscale;
2001-05-23 15:26:42 +02:00
if (dscale > 0) {
2003-04-08 12:38:59 +02:00
TEXT err_buf[256];
sprintf(err_buf,
2001-05-23 15:26:42 +02:00
"input error: input scale %d exceeds the field's scale %d",
-lscale, -ivar->sqlscale);
STDERROUT(err_buf);
done = true;
2001-05-23 15:26:42 +02:00
}
while (dscale++ < 0)
n *= 10;
}
}
// sscanf assumes a 64-bit integer target
2006-05-04 02:02:58 +02:00
else if (sscanf(lastInputLine, "%" SQUADFORMAT, &n) != 1)
{
STDERROUT("Input parsing problem");
done = true;
2001-05-23 15:26:42 +02:00
}
2007-03-01 12:40:26 +01:00
if (done)
break; // Nothing else, we found an error.
2001-05-23 15:26:42 +02:00
if (type == SQL_INT64)
*pi64 = n;
else if (type == SQL_SHORT)
{
2001-05-23 15:26:42 +02:00
*smallint = n;
if (SINT64(*smallint) != n)
{
STDERROUT("Value too big");
done = true;
}
}
2001-05-23 15:26:42 +02:00
else if (type == SQL_LONG)
{
2001-05-23 15:26:42 +02:00
*integer = n;
if (SINT64(*integer) != n)
{
STDERROUT("Value too big");
done = true;
}
}
2001-05-23 15:26:42 +02:00
break;
default:
done = true;
STDERROUT("Unexpected SQLTYPE in add_row()");
2001-05-23 15:26:42 +02:00
break;
}
}
2001-05-23 15:26:42 +02:00
}
if (!done) {
// having completed all columns, try the insert statement with the isqlda
2001-05-23 15:26:42 +02:00
if (isc_dsql_exec_immed2(isc_status, &DB, &M__trans, 0,
insertstring, isqlGlob.SQL_dialect, isqlda, NULL))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
break;
}
2007-02-25 22:25:33 +01:00
// Release each of the variables for next time
XSQLVAR* ivar = isqlda->sqlvar;
for (i = 0; i < i_cols; i++, ivar++)
2007-02-25 22:25:33 +01:00
{
2001-05-23 15:26:42 +02:00
if (ivar->sqldata)
ISQL_FREE(ivar->sqldata);
2007-02-25 22:25:33 +01:00
}
2001-05-23 15:26:42 +02:00
}
}
ISQL_FREE(insertstring);
ISQL_FREE(sqlda);
2001-05-23 15:26:42 +02:00
if (isqlda)
{
XSQLVAR* ivar = isqlda->sqlvar;
for (i = 0; i < i_cols; i++, ivar++)
if (ivar->sqldata)
ISQL_FREE(ivar->sqldata);
2001-05-23 15:26:42 +02:00
ISQL_FREE(isqlda);
}
2001-05-23 15:26:42 +02:00
if (colnumber)
ISQL_FREE(colnumber);
if (nullind)
ISQL_FREE(nullind);
return (SKIP);
}
2002-06-29 15:39:11 +02:00
2001-05-23 15:26:42 +02:00
static processing_state blobedit(const TEXT* action,
const TEXT* const* cmd)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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 ps_ERR;
2001-05-23 15:26:42 +02:00
const TEXT* p = cmd[1];
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Find the high and low values of the blob id
ISC_QUAD blobid;
2003-04-08 12:38:59 +02:00
sscanf(p, "%"xLONGFORMAT":%"xLONGFORMAT, &blobid.gds_quad_high, &blobid.gds_quad_low);
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// If it isn't an explicit blobedit, then do a dump. Since this is a
// user operation, put it on the M__trans handle.
2001-05-23 15:26:42 +02:00
processing_state rc = SKIP;
2001-05-23 15:26:42 +02:00
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.
TEXT path[MAXPATHLEN];
2008-01-16 08:23:29 +01:00
strip_quotes(cmd[2], path);
if (!BLOB_dump(&blobid, DB, M__trans, path))
{
TEXT errbuf[MSG_LENGTH];
ISQL_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << path);
STDERROUT(errbuf);
rc = ps_ERR;
}
2001-05-23 15:26:42 +02:00
}
else
rc = ps_ERR;
2001-05-23 15:26:42 +02:00
return rc;
2001-05-23 15:26:42 +02:00
}
// *******************************
// b u l k _ i n s e r t _ h a c k
// *******************************
// Primitive processing for prepared insertions. Invocation is
// SET BULK_INSERT <insert_statement>
// (val1, ..., valN)
// (val1, ..., valN)
// Finish with an empty line or anything different than an opening parenthesis.
// For example, STOP may be explicit, but any word without '(' will do the trick.
// Tuples must go in a single line. Only quoted strings can span more than one line.
// Added a very visible +++ to put other parameters of the same row in another line.
// Use +++ after a comma and continue in the next line. No comments in the middle.
// Only single quote accepted. Strings without special characters can go unquoted.
// Use the default question mark (?) to designate parameters.
// Single line comments are recognized only in the first column on a separate line
// and they can only appear before or after full tuples (not between multi-line tuples).
// The command COMMIT or COMMIT WORK can appear only in the first column in a
// single line and it will be recognized. We do not check if there's more text
// in the same line, thus it can be the terminator or random garbage.
// Two commas are invalid as empty/NULL value, use NULL or the appropriate "empty" value
// for the data type instead (zero for numbers, two single quotes for string, etc.)
// The code needs review, cleanup and moving some messages to the msg db.
// Since the code is forgiving, it will check for double ')' in the row but you can
// write (val1, ..., valN); with the terminator at the end and garbage following it.
// Indeed, the terminator can be replaced by unfamiliar characters to the parser
// like #, $, %, etc., and anything can follow them, including random text.
// It's invalid to put +++ (signaling row continuation) after a tuple is complete.
// If you came here looking for robust parsing code, you're at the wrong place.
static processing_state bulk_insert_hack(const char* command, XSQLDA** sqldap)
{
// Skip "SET BULK_INSERT" part.
for (int j = 0; j < 2; ++j)
{
while (*command && *command != 0x20)
++command;
while (*command && *command == 0x20)
++command;
}
if (!*command)
return ps_ERR;
processing_state ret = SKIP;
// We received the address of the sqlda in case we realloc it
XSQLDA* sqlda = *sqldap;
// If somebody did a commit or rollback, we are out of a transaction
if (!M__trans)
2007-02-25 22:25:33 +01:00
{
if (isc_start_transaction(isc_status, &M__trans, 1, &DB, 0, NULL))
ISQL_errmsg(isc_status);
2007-02-25 22:25:33 +01:00
}
// No need to start a default transaction unless there is no current one
2007-03-01 12:40:26 +01:00
if (Autocommit && !D__trans)
2007-02-25 22:25:33 +01:00
{
if (isc_start_transaction(isc_status, &D__trans, 1, &DB, 5, default_tpb))
{
ISQL_errmsg(isc_status);
}
2007-02-25 22:25:33 +01:00
}
// If statistics are requested, then reserve them here
perf perf_before;
if (Stats)
perf_get_info(&DB, &perf_before);
// Prepare the dynamic query stored in string.
// But put this on the DDL transaction to get maximum visibility of
// metadata.
FB_API_HANDLE prepare_trans;
if (Autocommit)
prepare_trans = D__trans;
else
prepare_trans = M__trans;
if (isc_dsql_prepare(isc_status, &prepare_trans, &global_Stmt, 0, command,
isqlGlob.SQL_dialect, sqlda))
{
if (isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION && Input_file) {
isqlGlob.printf("%s%s%s%s%s%s",
NEWLINE,
"**** Error preparing statement:",
NEWLINE,
NEWLINE,
command,
NEWLINE);
}
ISQL_errmsg(isc_status);
return ps_ERR;
}
// check for warnings
if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
// Find out what kind of statement this is
2008-01-16 08:23:29 +01:00
const int statement_type = process_request_type();
if (!statement_type)
return ps_ERR;
if (statement_type != isc_info_sql_stmt_insert)
{
isqlGlob.printf("Only INSERT commands are accepted in bulk mode.%s", NEWLINE);
return ps_ERR;
}
//#ifdef DEV_BUILD
if (Sqlda_display)
{
const bool can_have_input_parameters =
statement_type == isc_info_sql_stmt_select ||
statement_type == isc_info_sql_stmt_insert ||
statement_type == isc_info_sql_stmt_update ||
statement_type == isc_info_sql_stmt_delete ||
statement_type == isc_info_sql_stmt_exec_procedure ||
statement_type == isc_info_sql_stmt_select_for_upd;
if (print_sqlda_input(can_have_input_parameters) == ps_ERR)
return ps_ERR;
}
//#endif // DEV_BUILD
if (isc_dsql_describe_bind(isc_status, &global_Stmt, isqlGlob.SQL_dialect, sqlda)) {
ISQL_errmsg(isc_status);
return ps_ERR;
}
// check for warnings
if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
if (!sqlda->sqld) // No input parameters, doesn't make sense for bulk insertions.
{
isqlGlob.printf("There must be at least one parameter in the statement.%s", NEWLINE);
return ps_ERR;
}
if (sqlda->sqld > sqlda->sqln) {
const USHORT n_columns = sqlda->sqld;
ISQL_FREE(sqlda);
*sqldap = sqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(n_columns)));
memset(sqlda, 0, XSQLDA_LENGTH(n_columns));
sqlda->version = SQLDA_VERSION1;
sqlda->sqld = sqlda->sqln = n_columns;
if (isc_dsql_describe_bind(isc_status, &global_Stmt, isqlGlob.SQL_dialect,
sqlda))
{
ISQL_errmsg(isc_status);
return ps_ERR;
}
else if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
}
else
{
// Our dear process_statement doesn't clean the global sqlda after using it,
// although it deallocates the sqldata in a single continuous memory section.
const int n_cols = sqlda->sqln;
XSQLVAR* ivar = sqlda->sqlvar;
for (int i = 0; i < n_cols; ++i, ++ivar)
2007-02-25 22:25:33 +01:00
{
if (ivar->sqldata)
{
//ISQL_FREE(ivar->sqldata); Assume everything was deallocated.
ivar->sqldata = 0;
}
2007-02-25 22:25:33 +01:00
}
}
// If PLAN is set, print out the plan now.
if (Plan) {
process_plan();
if (Planonly)
return ret; // Do not execute.
}
const int n_cols = sqlda->sqld;
SSHORT* nullind = new SSHORT[n_cols];
PtrSentry<SSHORT> nullindSentry(nullind, true);
int textFieldCounter = 0;
{ // scope
for (int i = 0; i < n_cols; ++i)
{
switch (sqlda->sqlvar[i].sqltype & ~1)
{
case SQL_TEXT:
case SQL_VARYING:
++textFieldCounter;
break;
}
}
} // scope
// Used to optimize allocations to text fields.
Extender* const stringBuffers = textFieldCounter ? new Extender[textFieldCounter] : 0;
PtrSentry<Extender> stringbufSentry(stringBuffers, true);
char bulk_prompt[BUFFER_LENGTH180] = "";
ISQL_msg_get(BULK_PROMPT, sizeof(bulk_prompt), bulk_prompt);
// Lookup the continuation prompt once
TEXT con_prompt[MSG_LENGTH];
ISQL_msg_get(CON_PROMPT, con_prompt);
TEXT msg[MSG_LENGTH];
bool commit_failureM = false;
bool commit_failureD = false;
bool done = false; // This is mostly "done with errors".
while (!done)
{
if (bulk_insert_retriever(bulk_prompt)) // We finished normally, found EOF.
break;
2007-03-01 12:40:26 +01:00
// We only support single line comments and they should be at the first column,
// no spaces before, etc. Go to read the next line.
2007-03-01 12:40:26 +01:00
if (lastInputLine[0] == '-' && lastInputLine[1] == '-')
continue;
const char* insert = lastInputLine;
while (*insert && *insert != '(')
++insert;
if (!*insert || *insert != '(')
2007-03-01 12:40:26 +01:00
{
// Again, we are strict, we need COMMIT or COMMIT WORK exactly at the beginning
// and it will be the only command in the line (we don't care about the rest).
2007-03-01 12:40:26 +01:00
if (fb_utils::strnicmp(lastInputLine, "COMMIT", 6) == 0 ||
fb_utils::strnicmp(lastInputLine, "COMMIT WORK", 11) == 0)
{
if (isc_commit_transaction(isc_status, &M__trans) ||
isc_start_transaction(isc_status, &M__trans, 1, &DB, 0, NULL))
{
ISQL_errmsg(isc_status);
done = true;
commit_failureM = M__trans ? true : false;
2007-03-01 12:40:26 +01:00
break; // We failed to commit, quit the bulk insertion.
}
// CVC: Commit may fail with AUTO-DDL off and DDL changes rejected by DFW.
if (D__trans && commit_trans(&D__trans) &&
isc_start_transaction(isc_status, &D__trans, 1, &DB, 5, default_tpb))
{
ISQL_errmsg(isc_status);
done = true;
commit_failureD = D__trans ? true : false;
2007-03-01 12:40:26 +01:00
break; // We failed to commit, quit the bulk insertion.
}
continue; // Go to read another line.
}
break; // For example, STOP or blank line not inside quoted string.
2007-03-01 12:40:26 +01:00
}
++insert;
2007-03-01 12:40:26 +01:00
while (*insert == 0x20 || *insert == '\r' || *insert == '\t')
++insert;
if (!*insert) // Did we finish gracefully? The last line may be spaces or tab.
break;
const char* lastPos = insert;
Extender extender; // Used only for multi-line tuples.
SSHORT* nullp = nullind;
for (int i = 0, textFieldIter = 0; i < n_cols && !done; ++i)
{
XSQLVAR* const ivar = &sqlda->sqlvar[i];
*nullp = 0;
ivar->sqlind = nullp++;
//ivar->sqldata = NULL; Not here because we are reusing fixed size elements.
const SSHORT type = ivar->sqltype & ~1;
const SSHORT nullable = ivar->sqltype & 1;
const char* finder = 0;
int subtract = 0;
const bool quote = *insert == SINGLE_QUOTE;
if (quote)
{
subtract = 2; // Ignore the quotes at the beginning and end.
bool finished = false; // Did the string close?
++insert;
while (*insert)
{
if (*insert == SINGLE_QUOTE)
{
++insert;
if (*insert == SINGLE_QUOTE)
++subtract;
else
{
finished = true;
break;
}
}
++insert;
}
if (!finished)
{
extender.alloc(MAX_USHORT);
extender.append(lastPos, insert - lastPos);
while (!finished && !bulk_insert_retriever(con_prompt))
{
finder = lastInputLine;
while (*finder)
{
if (*finder == SINGLE_QUOTE)
{
++finder;
if (*finder == SINGLE_QUOTE)
++subtract;
else
{
finished = true;
break;
}
}
++finder;
}
size_t how_many = finder - lastInputLine;
if (extender.append(lastInputLine, how_many) <= how_many)
{
done = true;
ret = ps_ERR; // Do not delete, see if() below.
STDERROUT("Failed to concatenate string");
break;
}
}
if (ret != ps_ERR && !finished)
{
done = true;
STDERROUT("Failed to find end of quoted string");
}
if (done)
break;
lastPos = extender.getBuffer();
insert = lastPos + extender.getUsed();
}
if (subtract > 2)
{
// Get rid of pairs of single quotes; they are a syntax artifact.
const char* view = lastPos + 1;
// We are working over lastInputLine or Extender's buffer, so "unconst" is safe.
char* mover = const_cast<char*>(lastPos + 1);
for (int counter = subtract; view < insert; ++view, ++mover)
{
*mover = *view;
if (*view == SINGLE_QUOTE && view[1] == SINGLE_QUOTE && counter > 2)
{
++view;
--counter;
}
}
}
}
else
{
//if ((type == SQL_TEXT || type == SQL_VARYING || type == SQL_BLOB)
// && fb_utils::strnicmp(insert, "NULL", 4))
//{
// STDERROUT("Looking for unquoted string in:");
// STDERROUT(insert);
//}
for (bool go = true; go && *insert; ++insert)
{
switch (*insert)
{
case 0x20:
2007-03-01 12:40:26 +01:00
//case '\n':
case '\r':
case '\t':
case ',':
case ')':
go = false;
--insert;
break;
}
}
}
SCHAR cmd[5] = "";
const USHORT length = insert - lastPos - subtract;
if (!length && !quote)
{
// Go ahead with an aux var and see if we hit end of string.
const char* ipeek = insert;
//char s[3] = {ipeek[-1], ipeek[0] ? ipeek[0] : 'Z', 0};
//STDERROUT(s);
while (*ipeek == 0x20 || *ipeek == '\r' || *ipeek == '\t')
++ipeek;
//s[0] = ipeek[-1];
//s[1] = ipeek[0] ? ipeek[0] : 'Z';
//STDERROUT(s);
switch (*ipeek)
{
case ',':
STDERROUT("Fields with zero length only allowed in quoted strings");
break;
case ')':
STDERROUT("Found ')' before reading all fields in a row");
break;
default:
STDERROUT("Unterminated row, use +++ to put more parameters in another line");
}
done = true;
continue;
}
if (quote)
++lastPos;
else
{
// Convert first 4 chars to upper case for comparison.
strncpy(cmd, lastPos, 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;
// Data types
SSHORT* smallint;
SLONG* integer;
SINT64* pi64;
SINT64 n;
float* fvalue;
double* dvalue;
tm times;
// Initialize the time structure.
memset(&times, 0, sizeof(times));
char msec_str[5] = "";
int msec = 0;
ISC_QUAD* blobid;
switch (type)
{
case SQL_BLOB:
if (ivar->sqldata)
blobid = (ISC_QUAD*) ivar->sqldata;
else
blobid = (ISC_QUAD*) ISQL_ALLOC(sizeof(ISC_QUAD));
{ // scope
UserBlob bs(isc_status);
if (!bs.create(DB, M__trans, *blobid))
{
STDERROUT("Unable to create blob");
ISQL_errmsg(isc_status);
done = true;
break; // Quit the switch()
}
size_t real_len;
if (!bs.putData(length, lastPos, real_len) || real_len != length)
{
STDERROUT("Unable to write to blob");
ISQL_errmsg(isc_status);
done = true;
}
if (!bs.close())
{
STDERROUT("Unable to close blob");
ISQL_errmsg(isc_status);
done = true;
}
} // scope
ivar->sqldata = (SCHAR*) blobid;
break;
case SQL_FLOAT:
if (ivar->sqldata)
fvalue = (float*) ivar->sqldata;
else
fvalue = (float*) ISQL_ALLOC(sizeof(float));
if (sscanf(lastPos, "%g", fvalue) != 1)
{
STDERROUT("Input parsing problem in 'float' value");
done = true;
}
ivar->sqldata = (SCHAR*) fvalue;
break;
case SQL_DOUBLE:
if (ivar->sqldata)
dvalue = (double*) ivar->sqldata;
else
dvalue = (double*) ISQL_ALLOC(sizeof(double));
if (sscanf(lastPos, "%lg", dvalue) != 1)
{
STDERROUT("Input parsing problem in 'double' value");
done = true;
}
ivar->sqldata = (SCHAR*) dvalue;
break;
case SQL_TYPE_DATE:
if (3 != sscanf(lastPos, "%d-%d-%d", &times.tm_year,
&times.tm_mon, &times.tm_mday)
|| !check_date(times))
{
ISQL_msg_get(DATE_ERR, msg, SafeArg() << lastPos);
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*) ivar->sqldata;
fb_assert(ivar->sqllen == sizeof(ISC_DATE));
if (!date)
date = (ISC_DATE*) ISQL_ALLOC((SLONG) ivar->sqllen);
isc_encode_sql_date(&times, date);
ivar->sqldata = (char*) date;
}
break;
case SQL_TYPE_TIME:
if (3 != sscanf(lastPos, "%d:%d:%d", &times.tm_hour,
&times.tm_min, &times.tm_sec)
|| !check_time(times))
{
ISQL_msg_get(TIME_ERR, msg, SafeArg() << lastPos);
STDERROUT(msg); // Bad time %s\n
done = true;
}
else {
ISC_TIME* vtime = (ISC_TIME*) ivar->sqldata;
fb_assert(ivar->sqllen == sizeof(ISC_TIME));
if (!vtime)
vtime = (ISC_TIME*) ISQL_ALLOC((SLONG) ivar->sqllen);
isc_encode_sql_time(&times, vtime);
ivar->sqldata = (char*) vtime;
}
break;
case SQL_TIMESTAMP:
2007-03-01 12:40:26 +01:00
if (6 <= sscanf(lastPos, "%d-%d-%d %d:%d:%d.%4s",
&times.tm_year, &times.tm_mon, &times.tm_mday,
&times.tm_hour, &times.tm_min, &times.tm_sec,
msec_str))
{
2007-04-11 18:05:40 +02:00
unsigned int count = 0;
for (; count < 4; ++count)
{
if (fb_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, SafeArg() << lastPos);
STDERROUT(msg); // Bad timestamp %s\n
}
else
{
--times.tm_mon;
times.tm_year -= 1900;
ISC_TIMESTAMP* datetime = (ISC_TIMESTAMP*) ivar->sqldata;
fb_assert(ivar->sqllen == sizeof(ISC_TIMESTAMP));
if (!datetime)
datetime = (ISC_TIMESTAMP*) ISQL_ALLOC((SLONG) ivar->sqllen);
isc_encode_timestamp(&times, 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:
{
fb_assert(textFieldIter < textFieldCounter);
Extender& field = stringBuffers[textFieldIter++];
field.alloc(length + sizeof(USHORT));
vary* avary = reinterpret_cast<vary*>(field.getBuffer());
avary->vary_length = length;
strncpy(avary->vary_string, lastPos, length);
ivar->sqldata = (SCHAR*) avary;
ivar->sqllen = length;
break;
}
case SQL_SHORT:
case SQL_LONG:
if (type == SQL_SHORT)
{
if (ivar->sqldata)
smallint = (SSHORT*) ivar->sqldata;
else
smallint = (SSHORT*) ISQL_ALLOC(sizeof(SSHORT));
ivar->sqldata = (SCHAR*) smallint;
}
else if (type == SQL_LONG)
{
if (ivar->sqldata)
integer = (SLONG*) ivar->sqldata;
else
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) {
if (ivar->sqldata)
pi64 = (SINT64*) ivar->sqldata;
else
pi64 = (SINT64*) ISQL_ALLOC(sizeof(SINT64));
ivar->sqldata = (SCHAR*) pi64;
}
if (ivar->sqlscale < 0)
{
SSHORT lscale = 0;
if (get_numeric((UCHAR*) lastPos, length, &lscale, &n) != true)
{
STDERROUT("Input parsing problem in 'numeric' or 'decimal' value");
done = true;
}
else
{
int dscale = ivar->sqlscale - lscale;
if (dscale > 0)
{
TEXT err_buf[256];
sprintf(err_buf,
"Input error: input scale %d exceeds the field's scale %d",
-lscale, -ivar->sqlscale);
STDERROUT(err_buf);
done = true;
}
while (dscale++ < 0)
n *= 10;
}
}
// sscanf assumes a 64-bit integer target
else if (sscanf(lastPos, "%" SQUADFORMAT, &n) != 1)
{
STDERROUT("Input parsing problem in 'integer' value");
done = true;
}
2007-03-01 12:40:26 +01:00
if (done)
continue; // Nothing else, we found an error.
if (type == SQL_INT64)
*pi64 = n;
else if (type == SQL_SHORT)
{
*smallint = n;
if (SINT64(*smallint) != n)
{
STDERROUT("Integer value too big");
done = true;
}
}
else if (type == SQL_LONG)
{
*integer = n;
if (SINT64(*integer) != n)
{
STDERROUT("Integer value too big");
done = true;
}
}
break;
default:
done = true;
STDERROUT("Unexpected SQLTYPE in bulk_insert_hack()");
break;
}
}
if (done)
break;
// Restore "insert" pointer if we needed multi-line hack.
if (finder)
insert = finder;
//while (*insert && *insert != ',' && *insert != ')')
// ++insert;
int comma_count = 0;
int parenthesis_count = 0;
for (bool go = true; go && *insert; ++insert)
{
switch (*insert)
{
case 0x20:
2007-03-01 12:40:26 +01:00
//case '\n':
case '\r':
case '\t':
break;
case ',':
if (++comma_count > 1)
{
go = false;
--insert;
}
else if (i + 1 == n_cols) // We read all the row, no comma allowed!
{
go = false;
done = true;
STDERROUT("All fields were read but a comma was found");
}
break;
case ')':
if (++parenthesis_count > 1)
{
go = false;
done = true;
STDERROUT("Found more than one ')' in a row");
}
else if (i + 1 < n_cols) // We didn't read all fields but found closing parenthesis!
{
go = false;
done = true;
STDERROUT("Found ')' before reading all fields in a row");
}
break;
default:
go = false;
--insert;
break;
}
}
// Allow line continuation after comma.
if (!done && strncmp(insert, "+++", 3) == 0)
{
if (i + 1 == n_cols) // We read all the row, no continuation allowed!
{
done = true;
STDERROUT("All fields were read but the continuation mark +++ was found");
}
else
{
if (bulk_insert_retriever(con_prompt))
{
done = true;
STDERROUT("The continuation mark +++ was found but EOF was reached");
}
insert = lastInputLine;
}
}
lastPos = insert;
} // for (int i = 0;
if (!done)
{
// Having completed all columns, try the insert statement with the sqlda.
// This is a non-select DML statement or trans.
if (isc_dsql_execute(isc_status, &M__trans, &global_Stmt, isqlGlob.SQL_dialect, sqlda))
{
ISQL_errmsg(isc_status);
done = true;
}
// Check for warnings.
if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
}
// No need to release each of the variables for next time.
2007-02-25 22:25:33 +01:00
// Text is handled by an array of Extender and the others are fixed size
// and therefore allocated only once.
} // while (!done)
if (done)
{
// Save whatever we were able to pump, except when the failure was the commit itself.
if (M__trans)
{
if (commit_failureM)
isc_rollback_transaction(isc_status, &M__trans);
else
commit_trans(&M__trans);
}
if (D__trans)
{
if (commit_failureD)
isc_rollback_transaction(isc_status, &D__trans);
else
commit_trans(&D__trans);
}
TEXT errbuf[MSG_LENGTH];
2008-01-16 08:23:29 +01:00
sprintf(errbuf, "Stopped prematurely due to error in line %d with text:",
2008-02-13 12:53:41 +01:00
Filelist->Ifp().indev_aux);
STDERROUT(errbuf);
STDERROUT(lastInputLine);
STDERROUT("Going to EOF");
// Avoid thousands of errors. Assume the file is full of bulk insertion data.
2008-02-13 12:53:41 +01:00
Filelist->gotoEof();
ret = ps_ERR;
}
// Release each of the variables for next usage.
// The text variables are deallocated separately.
XSQLVAR* ivar = sqlda->sqlvar;
for (int i = 0; i < n_cols; ++i, ++ivar)
{
if ((ivar->sqltype & ~1) != SQL_VARYING && ivar->sqldata)
{
ISQL_FREE(ivar->sqldata);
ivar->sqldata = 0;
}
}
// Explicit just to test.
stringbufSentry.clean();
nullindSentry.clean();
// Get rid of the statement handle.
isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_close);
// Statistics printed here upon request
if (Stats)
print_performance(&perf_before);
return ret;
}
// *****************************************
// b u l k _ i n s e r t _ r e t r i e v e r
// *****************************************
// Helper to the previous bulk_insert_hack to get more lines of input.
// It returns true when it finds end of file or too long string (almost 64K).
static bool bulk_insert_retriever(const char* prompt)
{
readNextInputLine(prompt);
getColumn = -1; // We are bypassing getNextInputChar().
// Stop at end of line only. Empty lines are valid in this mode if inside quoted strings
// but this function cannot know whether we are in a string because it doesn't parse
// but only retrieves another line. Therefore, assumes and empty line is valid.
bool rc = false;
if (lastInputLine == NULL)
rc = true;
else
{
size_t length = strlen(lastInputLine);
/*
if (length == 0)
rc = true;
*/
if (length > MAX_USHORT - 2)
{
TEXT msg[MSG_LENGTH];
ISQL_msg_get(BUFFER_OVERFLOW, msg);
STDERROUT(msg);
rc = true;
}
}
return rc;
}
// *******************
// 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)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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)) {
2001-05-23 15:26:42 +02:00
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);
2001-05-23 15:26:42 +02:00
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)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2001-05-23 15:26:42 +02:00
* quote
* b. if '""' is found with in the string constant, removes one
2001-05-23 15:26:42 +02:00
* extra double quote
* c. if a single quote is found with in the string constant,
2001-05-23 15:26:42 +02:00
* adds one more single quote
* d. replace ending double quote string delimited character with
2001-05-23 15:26:42 +02:00
* 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;
2001-05-23 15:26:42 +02:00
TEXT* temp_str = *output_str;
const TEXT* temp_local_stmt_str = *input_str;
2001-05-23 15:26:42 +02:00
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;
2002-06-29 15:39:11 +02:00
while (b1 < str_end) {
2001-05-23 15:26:42 +02:00
*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;
2001-05-23 15:26:42 +02:00
*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)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* c o p y _ t a b l e
*
**************************************
*
* Functional description
* Create a new table based on an existing one.
*
2001-05-23 15:26:42 +02:00
* Parameters: source -- name of source table
* destination == name of newly created table
*
2001-05-23 15:26:42 +02:00
**************************************/
if (!source[0] || !destination[0])
{
STDERROUT("Either source or destination tables are missing");
return SKIP;
}
2001-05-23 15:26:42 +02:00
TEXT errbuf[MSG_LENGTH];
// Call list_table with a temporary file, then hand that file to a
// new version of isql
2001-05-23 15:26:42 +02:00
FILE* const holdout = isqlGlob.Out;
2001-05-23 15:26:42 +02:00
// If there is an alternate database, extract the domains
const bool domain_flag = otherdb[0];
2001-05-23 15:26:42 +02:00
Firebird::PathName ftmp = TempFile::create(SCRATCH);
isqlGlob.Out = fopen(ftmp.c_str(), "w+b");
2006-06-30 17:45:03 +02:00
if (!isqlGlob.Out) {
// If we can't open a temp file then bail
2001-05-23 15:26:42 +02:00
ISQL_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << ftmp.c_str());
STDERROUT(errbuf);
2001-05-23 15:26:42 +02:00
Exit_value = FINI_ERROR;
2004-12-12 03:01:52 +01:00
isqlGlob.Out = holdout;
2001-05-23 15:26:42 +02:00
return END;
}
chop_at(source, QUOTEDLENGTH);
if (source[0] != DBL_QUOTE)
ISQL_make_upper(source);
/*
chop_at(source_tbl, WORDLENGTH);
TEXT source[QUOTEDLENGTH];
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, QUOTEDLENGTH);
if (destination[0] != DBL_QUOTE)
ISQL_make_upper(destination);
/*
chop_at(destination_tbl, WORDLENGTH);
TEXT destination[QUOTEDLENGTH];
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);
}
*/
2001-05-23 15:26:42 +02:00
if (EXTRACT_list_table(source, destination, domain_flag, -1)) {
ISQL_msg_get(NOT_FOUND_MSG, errbuf, SafeArg() << source);
STDERROUT(errbuf);
fclose(isqlGlob.Out);
2001-05-23 15:26:42 +02:00
}
else {
fclose(isqlGlob.Out);
2001-05-23 15:26:42 +02:00
// easy to make a copy in another database
const TEXT* altdb = isqlGlob.global_Db_name;
2001-05-23 15:26:42 +02:00
if (*otherdb)
altdb = otherdb;
TEXT cmd[MAXPATHLEN * 2 + 20];
sprintf(cmd, "isql -q %s -i %s", altdb, ftmp.c_str());
2001-05-23 15:26:42 +02:00
if (system(cmd)) {
ISQL_msg_get(COPY_ERR, errbuf, SafeArg() << destination << altdb);
STDERROUT(errbuf);
2001-05-23 15:26:42 +02:00
}
}
unlink(ftmp.c_str());
isqlGlob.Out = holdout;
2001-05-23 15:26:42 +02:00
return (SKIP);
}
static processing_state create_db(const TEXT* statement,
TEXT* d_name)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* c r e a t e _ d b
*
**************************************
*
* Functional description
* Intercept create database commands to
* adjust the DB and transaction handles
*
2001-05-23 15:26:42 +02:00
* 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.
*
2001-05-23 15:26:42 +02:00
**************************************/
processing_state ret = SKIP;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Disconnect from the database and cleanup
ISQL_disconnect_database(false);
2001-05-23 15:26:42 +02:00
SLONG arglength = strlen(statement) + strlen(isqlGlob.User) + strlen(Password) + 24;
2001-05-23 15:26:42 +02:00
if (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET))
arglength += strlen(ISQL_charset) + strlen(" SET NAMES \'\' ");
TEXT* local_statement = (TEXT*) ISQL_ALLOC(arglength + 1);
2001-05-23 15:26:42 +02:00
if (!local_statement)
return (FAIL);
TEXT usr[USER_LENGTH];
TEXT psw[PASSWORD_LENGTH];
2001-05-23 15:26:42 +02:00
strcpy(local_statement, statement);
TEXT quote = DBL_QUOTE;
const TEXT* p = NULL;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// If there is a user parameter, we will set it into the create stmt.
2001-05-23 15:26:42 +02:00
if (global_usr || global_psw ||
(*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET)))
{
strip_quotes(isqlGlob.User, usr);
2001-05-23 15:26:42 +02:00
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.
// Maybe ISQL_remove_and_unescape_quotes coupled with ISQL_copy_SQL_id could work.
const TEXT* q = strchr(statement, quote);
2001-05-23 15:26:42 +02:00
if (!q) {
quote = SINGLE_QUOTE;
q = strchr(statement, quote);
}
if (q) {
// Set quote to match open quote
2001-05-23 15:26:42 +02:00
quote = *q;
q++;
p = strchr(q, quote);
if (p) {
p++;
const ptrdiff_t slen = p - statement;
2001-05-23 15:26:42 +02:00
local_statement[slen] = '\0';
if (isqlGlob.SQL_dialect == 1) {
2001-05-23 15:26:42 +02:00
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);
2001-05-23 15:26:42 +02:00
while (q) {
cnt++;
const TEXT* l = q + 1;
2001-05-23 15:26:42 +02:00
q = strchr(l, SINGLE_QUOTE);
}
TEXT* new_local_statement = NULL;
2001-05-23 15:26:42 +02:00
if (cnt > 0) {
arglength = strlen(statement) +
strlen(isqlGlob.User) + strlen(Password) + 24 + 2 * cnt;
2001-05-23 15:26:42 +02:00
if (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET))
arglength +=
strlen(ISQL_charset) + strlen(" SET NAMES \'\' ");
new_local_statement =
(TEXT*) ISQL_ALLOC(arglength + 1);
2001-05-23 15:26:42 +02:00
if (!new_local_statement)
{
ISQL_FREE(local_statement);
2001-05-23 15:26:42 +02:00
return (FAIL);
}
2001-05-23 15:26:42 +02:00
}
TEXT errbuf[MSG_LENGTH];
2001-05-23 15:26:42 +02:00
TEXT* temp_str;
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
while (!done) {
const TEXT* str_begin = NULL;
const TEXT* str_end = NULL;
literal_string_type str_flag = INIT_STR_FLAG;
2001-05-23 15:26:42 +02:00
get_str(temp_local_stmt_str, &str_begin, &str_end, &str_flag);
if (str_flag == INCOMPLETE_STRING) {
ISQL_msg_get(INCOMPLETE_STR, errbuf, SafeArg() << "create database statement");
STDERROUT(errbuf);
ISQL_FREE(local_statement);
if (new_local_statement)
ISQL_FREE(new_local_statement);
2001-05-23 15:26:42 +02:00
return (FAIL);
}
copy_str(&temp_str, &temp_local_stmt_str, &done,
str_begin, str_end, str_flag);
2001-05-23 15:26:42 +02:00
}
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;
2004-05-12 21:39:17 +02:00
bool done2 = false;
while (!done2) {
const TEXT* str_begin = NULL;
const TEXT* str_end = NULL;
literal_string_type str_flag = INIT_STR_FLAG;
2001-05-23 15:26:42 +02:00
get_str(temp_local_stmt_str, &str_begin, &str_end, &str_flag);
if (str_flag == INCOMPLETE_STRING) {
ISQL_msg_get(INCOMPLETE_STR, errbuf, SafeArg() << "create database statement");
STDERROUT(errbuf);
ISQL_FREE(local_statement);
if (new_local_statement)
ISQL_FREE(new_local_statement);
2001-05-23 15:26:42 +02:00
return (FAIL);
}
2004-05-12 21:39:17 +02:00
copy_str(&temp_str, &temp_local_stmt_str, &done2,
str_begin, str_end, str_flag);
2001-05-23 15:26:42 +02:00
}
}
if (new_local_statement)
{
ISQL_FREE(local_statement);
2001-05-23 15:26:42 +02:00
local_statement = new_local_statement;
}
2001-05-23 15:26:42 +02:00
}
2006-09-07 09:56:25 +02:00
// 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) {
2001-05-23 15:26:42 +02:00
if (isc_dsql_execute_immediate(isc_status, &DB, &M__trans, 0,
local_statement, requested_SQL_dialect,
NULL))
{
2001-05-23 15:26:42 +02:00
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))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
if (!DB)
ret = FAIL;
}
}
if (DB) {
// Load isqlGlob.global_Db_name with some value to show a successful attach
2001-05-23 15:26:42 +02:00
// CVC: Someone may decide to play strange games with undocumented ability
// to write crap between CREATE DATABASE and the db name, as described by
// Helen on CORE-932. Let's see if we can discover the real db name.
size_t db_name_len = MAXPATHLEN - 1;
char temp_name[MAXPATHLEN];
if (!strncmp(d_name, "/*", 2) || !strcmp(d_name, "--"))
{
const char* target = d_name[0] == '/' ? "/*" : "--";
const char* comment = strstr(statement, target);
while (true)
{
if (target[0] == '/')
{
// We don't accept recursive comment blocks, so looking for the next *./ is safe.
comment = strstr(comment + 2, "*/");
// What happens if the comment is not closed or quotes are spoiling the party?
// Then the statement is ill-formed and will be rejected by the server anyway.
if (comment)
{
comment += 2;
while (*comment && fb_isspace(*comment))
++comment;
}
else
break; // no end of block comment, error, do nothing.
}
else
{
while (!strncmp(comment, "--", 2))
{
while (*comment && *comment != '\n')
++comment;
if (*comment == '\n')
++comment; // skip the newline
while (*comment && fb_isspace(*comment))
++comment;
}
}
if (!strncmp(comment, "/*", 2))
{
target = "/*";
continue;
}
if (!strncmp(comment, "--", 2))
{
target = "--";
continue;
}
// Does the advanced user want a charset specification before the name, too?
// AFAIK, supported by IB and FB but dsql/preparse.cpp doesn't understand it.
/*
if (*comment == '_')
{
while (*comment && !fb_isspace(*comment) && *comment != '"' && *comment != '\'')
++comment;
while (*comment && fb_isspace(*comment))
++comment;
// Include the "continue" loops here for this to work in all cases.
// Check that this happens only once, etc.
}
*/
if (*comment == '"' || *comment == '\'')
{
for (const char& newquote = *comment++; *comment; ++comment)
{
if (*comment != newquote)
continue;
// If we find 'hello ''world''' this is valid.
if (comment[1] == newquote)
{
comment += 2;
if (!*comment)
break;
}
if (*comment == newquote && comment[1] != newquote)
{
db_name_len = comment - &newquote + 1;
if (db_name_len >= MAXPATHLEN)
db_name_len = MAXPATHLEN - 1;
d_name = temp_name;
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(d_name, &newquote, db_name_len + 1);
break;
}
}
}
break;
} // while (true)
}
chop_at(d_name, db_name_len + 1);
strip_quotes(d_name, isqlGlob.global_Db_name);
2001-05-23 15:26:42 +02:00
ISQL_get_version(true);
2001-05-23 15:26:42 +02:00
// Start the user transaction
2001-05-23 15:26:42 +02:00
if (!M__trans) {
if (isc_start_transaction(isc_status, &M__trans, 1, &DB, 0, NULL))
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
if (D__trans)
commit_trans(&D__trans);
if (Autocommit && isc_start_transaction(isc_status, &D__trans, 1, &DB, 5, default_tpb))
2004-05-09 07:48:33 +02:00
ISQL_errmsg(isc_status);
2001-05-23 15:26:42 +02:00
}
// Allocate a new user statement for the database
2001-05-23 15:26:42 +02:00
if (isc_dsql_allocate_statement(isc_status, &DB, &global_Stmt)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
ret = ps_ERR;
2001-05-23 15:26:42 +02:00
}
}
if (local_statement)
ISQL_FREE(local_statement);
return (ret);
}
static void do_isql()
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* d o _ i s q l
2001-05-23 15:26:42 +02:00
*
**************************************
*
* Functional description
* Process incoming SQL statements, using the global sqlda
2001-05-23 15:26:42 +02:00
*
**************************************/
TEXT errbuf[MSG_LENGTH];
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Initialized user transactions
2001-05-23 15:26:42 +02:00
2004-05-03 01:06:37 +02:00
M__trans = 0;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// File used to edit sessions
2008-01-16 08:23:29 +01:00
{ // scope
const Firebird::PathName filename = TempFile::create(SCRATCH);
const char* Tmpfile = filename.c_str();
FILE* f = fopen(Tmpfile, "w+"); // It was w+b
if (f)
2008-02-13 12:53:41 +01:00
Filelist->Ofp().init(f, Tmpfile);
2008-01-16 08:23:29 +01:00
else
{
// If we can't open a temp file then bail
2001-05-23 15:26:42 +02:00
2008-01-16 08:23:29 +01:00
ISQL_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << Tmpfile);
STDERROUT(errbuf);
Exit_value = FINI_ERROR;
return;
}
2001-05-23 15:26:42 +02:00
}
#ifdef WIN_NT
SetConsoleCtrlHandler(query_abort, TRUE);
#elif defined(HAVE_SIGACTION)
struct sigaction sig_action;
if (sigaction(SIGINT, NULL, &sig_action) == 0) {
sig_action.sa_handler = query_abort;
sig_action.sa_flags |= SA_RESTART;
sigaction(SIGINT, &sig_action, NULL);
}
#else
signal(SIGINT, query_abort);
#endif
// Open database and start tansaction
2001-05-23 15:26:42 +02:00
//
// 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);
2001-05-23 15:26:42 +02:00
// If that failed or no Dbname was specified
2001-05-23 15:26:42 +02:00
ISQL_dbcheck();
2001-05-23 15:26:42 +02:00
// 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
2001-05-23 15:26:42 +02:00
global_sqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(20)));
memset(global_sqlda, 0, XSQLDA_LENGTH(20));
global_sqlda->version = SQLDA_VERSION1;
global_sqlda->sqln = 20;
2001-05-23 15:26:42 +02:00
// The sqldap is the handle containing the current sqlda pointer
2001-05-23 15:26:42 +02:00
global_sqldap = &global_sqlda;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Read statements and process them from Ifp until the ret
// code tells us we are done
2006-06-01 07:12:38 +02:00
Firebird::Array<TEXT> stmt;
2006-06-01 07:56:55 +02:00
const size_t stmtLength = MAX_USHORT;
TEXT* statement = stmt.getBuffer(stmtLength);
processing_state ret;
bool done = false;
2001-05-23 15:26:42 +02:00
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);
2006-09-07 09:56:25 +02:00
// If there is current user statement, free it
if (global_Stmt)
isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_drop);
2001-05-23 15:26:42 +02:00
if (DB)
isc_detach_database(isc_status, &DB);
break;
}
if (Interrupt_flag)
{
// SIGINT caught in interactive mode
Interrupt_flag = false;
2006-07-02 00:48:42 +02:00
if (Input_file)
{
2006-07-02 00:48:42 +02:00
// close input files going back to stdin
2008-02-13 12:53:41 +01:00
Filelist->clear(stdin);
2006-07-02 00:48:42 +02:00
// should have two stdin in Filelist
2008-02-13 12:53:41 +01:00
fb_assert(Filelist->count() == 2);
2008-02-13 12:53:41 +01:00
Filelist->removeIntoIfp();
2006-07-02 00:48:42 +02:00
Input_file = false;
}
}
2006-06-01 07:56:55 +02:00
ret = get_statement(statement, stmtLength, sql_prompt);
2001-05-23 15:26:42 +02:00
// If there is no database yet, remind us of the need to connect
2001-05-23 15:26:42 +02:00
// But don't execute the statement
2001-05-23 15:26:42 +02:00
if (!isqlGlob.global_Db_name[0] && (ret == CONT)) {
if (!Quiet)
{
ISQL_msg_get(NO_DB, errbuf);
STDERROUT(errbuf);
2001-05-23 15:26:42 +02:00
}
if (!Interactive && BailOnError)
ret = FAIL;
else
ret = SKIP;
2001-05-23 15:26:42 +02:00
}
switch (ret) {
case CONT:
if (process_statement(statement, global_sqldap) == ps_ERR)
{
Exit_value = FINI_ERROR;
if (!Interactive && BailOnError)
Abort_flag = true;
}
2001-05-23 15:26:42 +02:00
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);
2001-05-23 15:26:42 +02:00
}
else {
if (D__trans)
commit_trans(&D__trans);
2001-05-23 15:26:42 +02:00
if (M__trans)
commit_trans(&M__trans);
if (gds_trans)
commit_trans(&gds_trans);
2001-05-23 15:26:42 +02:00
}
2006-09-07 09:56:25 +02:00
// If there is current user statement, free it
// I think DSQL_drop is the right one, but who knows
if (global_Stmt)
isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_drop);
2004-02-05 07:58:04 +01:00
if (DB) {
isc_detach_database(isc_status, &DB);
}
done = true;
2001-05-23 15:26:42 +02:00
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);
2006-09-07 09:56:25 +02:00
// If there is current user statement, free it
// I think DSQL_drop is the right one, but who knows
if (global_Stmt)
isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_drop);
2004-02-05 07:58:04 +01:00
if (DB) {
isc_detach_database(isc_status, &DB);
}
done = true;
2001-05-23 15:26:42 +02:00
break;
case ERR_BUFFER_OVERFLOW:
ISQL_msg_get(BUFFER_OVERFLOW, errbuf);
STDERROUT(errbuf);
2001-05-23 15:26:42 +02:00
case EXTRACT:
case EXTRACTALL:
default:
2006-09-07 09:56:25 +02:00
// fb_assert (FALSE); -- removed as finds too many problems
case ps_ERR:
2001-05-23 15:26:42 +02:00
case FAIL:
Exit_value = FINI_ERROR;
if (!Interactive && BailOnError)
Abort_flag = true;
break;
2001-05-23 15:26:42 +02:00
case SKIP:
break;
}
}
2004-05-03 01:06:37 +02:00
global_Stmt = 0;
DB = 0;
isqlGlob.global_Db_name[0] = '\0';
2004-05-03 01:06:37 +02:00
D__trans = 0;
M__trans = 0;
gds_trans = 0;
2001-05-23 15:26:42 +02:00
2008-02-13 12:53:41 +01:00
InputDevices::indev& Ofp = Filelist->Ofp();
2005-04-25 07:23:57 +02:00
// Should have a valid Temp file pointer
2008-01-16 08:23:29 +01:00
fb_assert(Ofp.indev_fpointer);
Ofp.drop();
2001-05-23 15:26:42 +02:00
if (global_sqlda)
ISQL_FREE(global_sqlda);
// CVC: If we were halt by an error and Bail, we have pending cleanup.
2008-02-13 12:53:41 +01:00
Filelist->clear();
if (lastInputLine)
free(lastInputLine);
2008-01-16 08:23:29 +01:00
global_Cols.clear(); // The destructor would do anyway if we don't reach this point.
2001-05-23 15:26:42 +02:00
}
static processing_state drop_db()
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* d r o p _ d b
*
**************************************
*
* Functional description
* Drop the current database
*
2001-05-23 15:26:42 +02:00
**************************************/
if (isqlGlob.global_Db_name[0]) {
2001-05-23 15:26:42 +02:00
if (isc_drop_database(isc_status, &DB)) {
ISQL_errmsg(isc_status);
return (FAIL);
}
2006-09-07 09:56:25 +02:00
// If error occurred and drop database got aborted
2001-05-23 15:26:42 +02:00
if (DB)
return (FAIL);
}
else
return (FAIL);
2005-04-25 07:23:57 +02:00
// The database got dropped with or without errors
2001-05-23 15:26:42 +02:00
2004-05-03 01:06:37 +02:00
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;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Zero database name
2001-05-23 15:26:42 +02:00
isqlGlob.global_Db_name[0] = '\0';
2004-05-03 01:06:37 +02:00
DB = 0;
2001-05-23 15:26:42 +02:00
return (SKIP);
}
static processing_state edit(const TEXT* const* cmd)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2001-05-23 15:26:42 +02:00
* pointer, Ifp, to the named file after editing or the tmp file.
*
**************************************/
2005-04-25 07:23:57 +02:00
// Set up editing command for shell
2001-05-23 15:26:42 +02:00
const TEXT* file = cmd[1];
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// If there is a file name specified, try to open it
2001-05-23 15:26:42 +02:00
processing_state rc = SKIP;
2001-05-23 15:26:42 +02:00
if (*file) {
TEXT path[MAXPATHLEN];
2001-05-23 15:26:42 +02:00
strip_quotes(file, path);
2004-04-29 00:36:29 +02:00
FILE* fp = fopen(path, "r");
if (fp) {
2008-01-16 08:23:29 +01:00
// Push the current ifp on the indev
2001-05-23 15:26:42 +02:00
2008-02-13 12:53:41 +01:00
Filelist->insertIfp();
Filelist->Ifp().init(fp, path);
gds__edit(path, 0);
2008-01-16 08:23:29 +01:00
Input_file = true;
getColumn = -1;
2001-05-23 15:26:42 +02:00
}
else {
TEXT errbuf[MSG_LENGTH];
ISQL_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << path);
STDERROUT(errbuf);
rc = ps_ERR;
2001-05-23 15:26:42 +02:00
}
}
else {
// No file given, edit the temp file
2001-05-23 15:26:42 +02:00
2008-02-13 12:53:41 +01:00
Filelist->insertIfp();
// Close the file, edit it, then reopen and read from the top
2008-02-13 12:53:41 +01:00
InputDevices::indev& Ofp = Filelist->Ofp();
2008-01-16 08:23:29 +01:00
Ofp.close();
const char* Tmpfile = Ofp.fileName();
2001-05-23 15:26:42 +02:00
gds__edit(Tmpfile, 0);
2008-01-16 08:23:29 +01:00
Ofp.init(fopen(Tmpfile, "r+"), Tmpfile); // We don't check for failure.
2008-02-13 12:53:41 +01:00
Filelist->Ifp().init(Ofp);
2008-01-16 08:23:29 +01:00
Input_file = true;
getColumn = -1;
2001-05-23 15:26:42 +02:00
}
return rc;
2001-05-23 15:26:42 +02:00
}
static processing_state end_trans()
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2001-05-23 15:26:42 +02:00
*
* Called by newtrans, createdb, newdb;
* Returns success or failure.
*
**************************************/
2001-12-24 03:51:06 +01:00
TEXT infobuf[BUFFER_LENGTH60];
2001-05-23 15:26:42 +02:00
processing_state ret = CONT;
2006-09-07 09:56:25 +02:00
// Give option of committing or rolling back before proceding unless
// the last command was a commit or rollback
2001-05-23 15:26:42 +02:00
if (M__trans) {
if (Interactive) {
ISQL_msg_get(COMMIT_PROMPT, sizeof(infobuf), infobuf);
readNextInputLine(infobuf);
getColumn = -1; // We are bypassing getNextInputChar().
2005-04-25 07:23:57 +02:00
if (lastInputLine && isyesno(lastInputLine)) {
// check for Yes answer
ISQL_msg_get(COMMIT_MSG, sizeof(infobuf), infobuf);
STDERROUT(infobuf);
2001-05-23 15:26:42 +02:00
if (DB && M__trans) {
if (isc_commit_transaction(isc_status, &M__trans)) {
// Commit failed, so roll back anyway
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
ret = FAIL;
}
}
}
else {
ISQL_msg_get(ROLLBACK_MSG, sizeof(infobuf), infobuf);
STDERROUT(infobuf);
2001-05-23 15:26:42 +02:00
if (DB && M__trans) {
if (isc_rollback_transaction(isc_status, &M__trans)) {
ISQL_errmsg(isc_status);
ret = FAIL;
}
}
}
}
2005-04-25 07:23:57 +02:00
else {
// No answer, just roll back by default
2001-05-23 15:26:42 +02:00
if (DB && M__trans) {
2005-04-25 07:23:57 +02:00
// 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);
STDERROUT(infobuf);
2001-05-23 15:26:42 +02:00
if (isc_rollback_transaction(isc_status, &M__trans)) {
ISQL_errmsg(isc_status);
ret = FAIL;
}
}
}
}
2005-04-25 07:23:57 +02:00
// Commit background transaction
2001-05-23 15:26:42 +02:00
if (DB && D__trans) {
if (isc_commit_transaction(isc_status, &D__trans)) {
ISQL_errmsg(isc_status);
ret = FAIL;
}
}
return ret;
2001-05-23 15:26:42 +02:00
}
static processing_state escape(const TEXT* cmd)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* e s c a p e
*
**************************************
*
* Functional description
* Permit a shell escape to system call
*
* Parameters: cmd -- The command string with SHELL
2001-05-23 15:26:42 +02:00
*
**************************************/
2005-04-25 07:23:57 +02:00
// Advance past the shell
2001-05-23 15:26:42 +02:00
const TEXT* shellcmd = cmd;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Search past the 'shell' keyword
2001-05-23 15:26:42 +02:00
shellcmd += strlen("shell");
2005-04-25 07:23:57 +02:00
// Eat whitespace at beginning of command
while (*shellcmd && fb_isspace(*shellcmd))
2001-05-23 15:26:42 +02:00
shellcmd++;
#ifdef WIN_NT
// MSDN says: You must explicitly flush (using fflush or _flushall)
// or close any stream before calling system.
// CVC: But that function defeats our possible several input streams opened.
//_flushall();
// Save Ofp position in case it's being used as input. See EDIT command.
fpos_t OfpPos = 0;
2008-02-13 12:53:41 +01:00
Filelist->Ofp().getPos(&OfpPos);
fflush(NULL); // Flush only output buffers.
const char* emptyCmd = "%ComSpec%";
#else
const char* emptyCmd = "$SHELL";
#endif
2005-04-25 07:23:57 +02:00
// If no command given just spawn a shell
if (!*shellcmd)
shellcmd = emptyCmd;
int rc = system(shellcmd);
2001-05-23 15:26:42 +02:00
#ifdef WIN_NT
// If we are reading from the temp file, restore the read position because
// it's opened in r+ mode in this case, that's R/W.
2008-02-13 12:53:41 +01:00
if (Filelist->sameInputAndOutput())
Filelist->Ofp().setPos(&OfpPos);
2001-05-23 15:26:42 +02:00
#endif
return rc ? FAIL : SKIP;
2001-05-23 15:26:42 +02:00
}
static processing_state frontend(const TEXT* statement)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* f r o n t e n d
2001-05-23 15:26:42 +02:00
*
**************************************
*
* 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
2001-05-23 15:26:42 +02:00
*
**************************************/
2008-01-16 08:23:29 +01:00
class FrontOptions : public OptionsBase
{
public:
enum front_commands
{
show, add, copy,
#ifdef MU_ISQL
pause,
#endif
blobview, output, shell, set, create, drop, connect,
#ifdef SCROLLABLE_CURSORS
next, prior, first, last, absolute, relative,
#endif
edit, input, quit, exit, help,
#ifdef DEV_BUILD
passthrough,
#endif
wrong
};
FrontOptions(const optionsMap* inmap, size_t insize, int wrongval)
: OptionsBase(inmap, insize, wrongval)
{}
};
static const FrontOptions::optionsMap options[] =
{
{FrontOptions::show, "SHOW", 0},
{FrontOptions::add, "ADD", 0},
{FrontOptions::copy, "COPY", 0},
#ifdef MU_ISQL
{FrontOptions::pause, "PAUSE", 0},
#endif
{FrontOptions::blobview, "BLOBVIEW", 0},
{FrontOptions::blobview, "BLOBDUMP", 0},
//{FrontOptions::output, "OUT", },
{FrontOptions::output, "OUTPUT", 3},
{FrontOptions::shell, "SHELL", 0},
{FrontOptions::set, "SET", 0},
{FrontOptions::create, "CREATE", 0},
{FrontOptions::drop, "DROP", 0},
{FrontOptions::connect, "CONNECT", 0},
#ifdef SCROLLABLE_CURSORS
{FrontOptions::next, "NEXT", 0},
{FrontOptions::prior, "PRIOR", 0},
{FrontOptions::first, "FIRST", 0},
{FrontOptions::last, "LAST", 0},
{FrontOptions::absolute, "ABSOLUTE", 0},
{FrontOptions::relative, "RELATIVE", 0},
#endif
{FrontOptions::edit, "EDIT", 0},
//{FrontOptions::input, "IN", },
{FrontOptions::input, "INPUT", 2},
{FrontOptions::quit, "QUIT", 0},
{FrontOptions::exit, "EXIT", 0},
{FrontOptions::help, "?", 0},
{FrontOptions::help, "HELP", 0}
#ifdef DEV_BUILD
,
{FrontOptions::passthrough, "PASSTHROUGH", 0}
#endif
};
TEXT errbuf[MSG_LENGTH];
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// 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;
}
2008-01-16 08:23:29 +01:00
TEXT parm_defaults[MAX_TERMS][1];
2005-04-25 07:23:57 +02:00
// Any whitespace and comments at the beginning are already swallowed by get_statement()
2001-05-23 15:26:42 +02:00
// Set beginning of statement past comment
2008-01-16 08:23:29 +01:00
const TEXT* const cmd = statement;
if (!*cmd)
{
// In case any default transaction was started - commit it here
if (gds_trans)
commit_trans(&gds_trans);
return SKIP;
}
2008-01-16 08:23:29 +01:00
frontend_load_parms(statement, parms, lparms, parm_defaults);
char bad_dialect_buf[512];
bool bad_dialect = false;
2005-04-25 07:23:57 +02:00
// 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;
2008-01-16 08:23:29 +01:00
const FrontOptions frontoptions(options, FB_NELEM(options), FrontOptions::wrong);
switch (frontoptions.getCommand(parms[0]))
{
case FrontOptions::show:
// Transaction for all frontend commands
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
// Free the frontend command
2008-01-16 08:23:29 +01:00
frontend_free_parms(parms, lparms, parm_defaults);
2001-05-23 15:26:42 +02:00
return FAIL;
}
ret = SHOW_metadata(parms, lparms);
if (gds_trans)
commit_trans(&gds_trans);
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::add:
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
// Free the frontend command
2008-01-16 08:23:29 +01:00
frontend_free_parms(parms, lparms, parm_defaults);
2001-05-23 15:26:42 +02:00
return FAIL;
}
ret = add_row(lparms[1]);
if (gds_trans)
commit_trans(&gds_trans);
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::copy:
if (DB && !gds_trans)
if (isc_start_transaction(isc_status, &gds_trans, 1, &DB, 0, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
// Free the frontend command
2008-01-16 08:23:29 +01:00
frontend_free_parms(parms, lparms, parm_defaults);
2001-05-23 15:26:42 +02:00
return FAIL;
}
ret = copy_table(lparms[1], lparms[2], lparms[3]);
if (gds_trans)
commit_trans(&gds_trans);
2008-01-16 08:23:29 +01:00
break;
2001-05-23 15:26:42 +02:00
#ifdef MU_ISQL
2005-04-25 07:23:57 +02:00
// This is code for QA Test bed Multiuser environment.
2008-01-16 08:23:29 +01:00
case FrontOptions::pause:
2001-05-23 15:26:42 +02:00
if (qa_mu_environment()) {
qa_mu_pause();
ret = SKIP;
}
else
ret = CONT;
2008-01-16 08:23:29 +01:00
break;
#endif // MU_ISQL
case FrontOptions::blobview:
ret = blobedit(parms[0], lparms);
break;
case FrontOptions::output:
2001-05-23 15:26:42 +02:00
ret = newoutput(lparms[1]);
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::shell:
2001-05-23 15:26:42 +02:00
ret = escape(cmd);
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::set:
ret = frontend_set(cmd, parms, lparms, bad_dialect_buf, bad_dialect);
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::create:
2001-05-23 15:26:42 +02:00
if (!strcmp(parms[1], "DATABASE") || !strcmp(parms[1], "SCHEMA"))
ret = create_db(cmd, lparms[2]);
else
ret = CONT;
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::drop:
2001-05-23 15:26:42 +02:00
if (!strcmp(parms[1], "DATABASE") || !strcmp(parms[1], "SCHEMA"))
if (*parms[2])
ret = ps_ERR;
2001-05-23 15:26:42 +02:00
else
ret = drop_db();
else
ret = CONT;
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::connect:
{
const TEXT* psw = NULL;
const TEXT* usr = NULL;
const TEXT* sql_role_nm = NULL;
int numbufs = 0;
2001-05-23 15:26:42 +02:00
2008-01-16 08:23:29 +01:00
// 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.
2001-05-23 15:26:42 +02:00
2008-01-16 08:23:29 +01:00
ret = SKIP;
for (int i = 2; i < (MAX_TERMS - 1);)
2001-05-23 15:26:42 +02:00
{
2008-01-16 08:23:29 +01:00
if (!strcmp(parms[i], "CACHE") && *lparms[i + 1])
{
char* err;
long value = strtol(lparms[i + 1], &err, 10);
if (*err || (value <= 0) || (value >= INT_MAX)) {
ret = ps_ERR;
break;
}
numbufs = (int) value;
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 = ps_ERR;
break;
}
2008-01-16 08:23:29 +01:00
else
i++;
2001-05-23 15:26:42 +02:00
}
2008-01-16 08:23:29 +01:00
if (ret != ps_ERR)
ret = newdb(lparms[1], usr, psw, numbufs, sql_role_nm, true);
2001-05-23 15:26:42 +02:00
}
2008-01-16 08:23:29 +01:00
break;
2001-05-23 15:26:42 +02:00
#ifdef SCROLLABLE_CURSORS
2005-04-25 07:23:57 +02:00
// Perform scrolling within the currently active cursor
2001-05-23 15:26:42 +02:00
2008-01-16 08:23:29 +01:00
case FrontOptions::next:
2001-05-23 15:26:42 +02:00
fetch_offset = 1;
2008-01-16 08:23:29 +01:00
fetch_direction = isc_fetch_next;
2001-05-23 15:26:42 +02:00
ret = FETCH;
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::prior:
2001-05-23 15:26:42 +02:00
fetch_offset = 1;
2008-01-16 08:23:29 +01:00
fetch_direction = isc_fetch_prior;
2001-05-23 15:26:42 +02:00
ret = FETCH;
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::first:
2001-05-23 15:26:42 +02:00
fetch_offset = 1;
2008-01-16 08:23:29 +01:00
fetch_direction = isc_fetch_first;
2001-05-23 15:26:42 +02:00
ret = FETCH;
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::last:
2001-05-23 15:26:42 +02:00
fetch_offset = 1;
2008-01-16 08:23:29 +01:00
fetch_direction = isc_fetch_last;
2001-05-23 15:26:42 +02:00
ret = FETCH;
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::absolute:
fetch_direction = isc_fetch_absolute;
2001-05-23 15:26:42 +02:00
fetch_offset = atoi(parms[1]);
ret = FETCH;
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::relative:
fetch_direction = isc_fetch_relative;
2001-05-23 15:26:42 +02:00
fetch_offset = atoi(parms[1]);
ret = FETCH;
2008-01-16 08:23:29 +01:00
break;
2001-05-23 15:26:42 +02:00
#endif
2008-01-16 08:23:29 +01:00
case FrontOptions::edit:
2001-05-23 15:26:42 +02:00
ret = edit(lparms);
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::input:
// CVC: Set by newinput() below only if successful.
//Input_file = true;
ret = newinput(lparms[1]);
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::quit:
2001-05-23 15:26:42 +02:00
ret = BACKOUT;
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::exit:
2001-05-23 15:26:42 +02:00
ret = EXIT;
2008-01-16 08:23:29 +01:00
break;
case FrontOptions::help:
2001-05-23 15:26:42 +02:00
ret = help(parms[1]);
2008-01-16 08:23:29 +01:00
break;
#ifdef DEV_BUILD
2008-01-16 08:23:29 +01:00
case FrontOptions::passthrough:
ret = passthrough(cmd + 11);
2008-01-16 08:23:29 +01:00
break;
#endif
2008-01-16 08:23:29 +01:00
default: // Didn't match, it must be SQL
2001-05-23 15:26:42 +02:00
ret = CONT;
2008-01-16 08:23:29 +01:00
break;
}
2001-05-23 15:26:42 +02:00
// In case any default transaction was started - commit it here
if (gds_trans)
commit_trans(&gds_trans);
2001-05-23 15:26:42 +02:00
// Free the frontend command
2008-01-16 08:23:29 +01:00
frontend_free_parms(parms, lparms, parm_defaults);
2001-05-23 15:26:42 +02:00
if (ret == ps_ERR) {
2003-09-09 13:03:37 +02:00
if (bad_dialect)
ISQL_msg_get(CMD_ERR, errbuf, SafeArg() << bad_dialect_buf);
2001-05-23 15:26:42 +02:00
else
ISQL_msg_get(CMD_ERR, errbuf, SafeArg() << cmd);
STDERROUT(errbuf);
2001-05-23 15:26:42 +02:00
}
return ret;
2001-05-23 15:26:42 +02:00
}
2008-01-16 08:23:29 +01:00
static void frontend_free_parms(TEXT* parms[], TEXT* lparms[], TEXT parm_defaults[][1])
{
for (int j = 0; j < MAX_TERMS; j++) {
2008-01-16 08:23:29 +01:00
if (parms[j] && parms[j] != parm_defaults[j]) {
ISQL_FREE(parms[j]);
ISQL_FREE(lparms[j]);
}
}
}
2008-01-16 08:23:29 +01:00
static void frontend_load_parms(const TEXT* p, TEXT* parms[], TEXT* lparms[],
TEXT parm_defaults[][1])
{
TEXT buffer[BUFFER_LENGTH256];
for (int i = 0; i < MAX_TERMS; ++i)
{
if (!*p)
{
parms[i] = lparms[i] = parm_defaults[i];
parm_defaults[i][0] = '\0';
continue;
}
bool role_found = false;
TEXT* a = buffer;
int j = 0;
const bool quoted = *p == DBL_QUOTE || *p == SINGLE_QUOTE;
if (quoted) {
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++;
}
}
*a = '\0';
}
else {
// Prevent overflow. Do not copy the string (redundant).
while (*p && !fb_isspace(*p) && j < BUFFER_LENGTH256 - 1) {
j++;
++p;
}
}
fb_assert(!quoted || strlen(buffer) == j);
const size_t length = quoted ? strlen(buffer) : j;
parms[i] = (TEXT*) ISQL_ALLOC((SLONG) (length + 1));
lparms[i] = (TEXT*) ISQL_ALLOC((SLONG) (length + 1));
memcpy(parms[i], quoted ? buffer : p - j, length);
parms[i][length] = 0;
while (*p && fb_isspace(*p))
p++;
strcpy(lparms[i], parms[i]);
if (!role_found)
ISQL_make_upper(parms[i]);
}
}
// ***********************
// 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)
{
2008-01-16 08:23:29 +01:00
class SetOptions : public OptionsBase
{
public:
enum set_commands
{
stat, count, list, plan, planonly, blobdisplay, echo, autoddl,
#ifdef SCROLLABLE_CURSORS
autofetch,
#endif
width, transaction, terminator, names, time,
//#ifdef DEV_BUILD
sqlda_display,
//#endif
sql, warning, generator, statistics, heading, bail,
bulk_insert, wrong
};
SetOptions(const optionsMap* inmap, size_t insize, int wrongval)
: OptionsBase(inmap, insize, wrongval)
{}
};
static const SetOptions::optionsMap options[] =
{
{SetOptions::stat, "STATS", 4},
{SetOptions::count, "COUNT", 0},
{SetOptions::list, "LIST", 0},
{SetOptions::plan, "PLAN", 0},
{SetOptions::planonly, "PLANONLY", 0},
{SetOptions::blobdisplay, "BLOBDISPLAY", 4},
{SetOptions::echo, "ECHO", 0},
{SetOptions::autoddl, "AUTODDL", 4},
#ifdef SCROLLABLE_CURSORS
{SetOptions::autofetch, "AUTOFETCH", 0},
#endif
{SetOptions::width, "WIDTH", 0},
{SetOptions::transaction, "TRANSACTION", 5},
{SetOptions::terminator, "TERMINATOR", 4},
{SetOptions::names, "NAMES", 0},
{SetOptions::time, "TIME", 0},
//#ifdef DEV_BUILD
{SetOptions::sqlda_display, "SQLDA_DISPLAY", 0},
//#endif
{SetOptions::sql, "SQL", 0},
{SetOptions::warning, "WARNINGS", 7},
{SetOptions::warning, "WNG", 0},
{SetOptions::generator, "GENERATOR", 0},
{SetOptions::statistics, "STATISTICS", 0},
{SetOptions::heading, "HEADING", 0},
{SetOptions::bail, "BAIL", 0},
{SetOptions::bulk_insert, "BULK_INSERT", 0},
};
// Display current set options
if (!*parms[1])
2008-01-16 08:23:29 +01:00
return print_sets();
processing_state ret = SKIP;
const SetOptions setoptions(options, FB_NELEM(options), SetOptions::wrong);
switch (setoptions.getCommand(parms[1]))
{
case SetOptions::stat:
ret = do_set_command(parms[2], &Stats);
2008-01-16 08:23:29 +01:00
break;
case SetOptions::count:
ret = do_set_command(parms[2], &Docount);
2008-01-16 08:23:29 +01:00
break;
case SetOptions::list:
ret = do_set_command(parms[2], &List);
2008-01-16 08:23:29 +01:00
break;
case SetOptions::plan:
ret = do_set_command(parms[2], &Plan);
if (Planonly && !Plan)
ret = do_set_command("OFF", &Planonly);
2008-01-16 08:23:29 +01:00
break;
case SetOptions::planonly:
ret = do_set_command (parms[2], &Planonly);
if ( Planonly && !Plan ) {
// turn on plan
ret = do_set_command ("ON", &Plan);
}
2008-01-16 08:23:29 +01:00
break;
2008-01-16 08:23:29 +01:00
case SetOptions::blobdisplay:
// 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]);
2008-01-16 08:23:29 +01:00
break;
case SetOptions::echo:
ret = do_set_command(parms[2], &Echo);
if (!Echo)
ISQL_prompt("");
2008-01-16 08:23:29 +01:00
break;
case SetOptions::autoddl:
ret = do_set_command(parms[2], &Autocommit);
break;
#ifdef SCROLLABLE_CURSORS
2008-01-16 08:23:29 +01:00
case SetOptions::autofetch:
ret = do_set_command(parms[2], &Autofetch);
2008-01-16 08:23:29 +01:00
break;
#endif
2008-01-16 08:23:29 +01:00
case SetOptions::width:
ret = newsize(parms[2][0] == '"' ? lparms[2] : parms[2], parms[3]);
break;
case SetOptions::transaction:
ret = newtrans(cmd);
2008-01-16 08:23:29 +01:00
break;
case SetOptions::terminator:
{
2008-01-16 08:23:29 +01:00
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;
fb_utils::copy_terminate(isqlGlob.global_Term, a, Termlen + 1);
}
}
2008-01-16 08:23:29 +01:00
break;
case SetOptions::names:
if (!*parms[2])
{
const size_t lgth = strlen(DEFCHARSET);
if (lgth < MAXCHARSET_SIZE)
strcpy(ISQL_charset, DEFCHARSET);
else
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(ISQL_charset, DEFCHARSET, MAXCHARSET_SIZE);
}
else
{
const size_t lgth = strlen(parms[2]);
if (lgth < MAXCHARSET_SIZE)
strcpy(ISQL_charset, parms[2]);
else
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(ISQL_charset, parms[2], MAXCHARSET_SIZE);
}
2008-01-16 08:23:29 +01:00
break;
case SetOptions::time:
ret = do_set_command(parms[2], &Time_display);
2008-01-16 08:23:29 +01:00
break;
//#ifdef DEV_BUILD
2008-01-16 08:23:29 +01:00
case SetOptions::sqlda_display:
ret = do_set_command(parms[2], &Sqlda_display);
2008-01-16 08:23:29 +01:00
break;
//#endif // DEV_BUILD
2008-01-16 08:23:29 +01:00
case SetOptions::sql:
if (!strcmp(parms[2], "DIALECT"))
ret = get_dialect(parms[3], bad_dialect_buf, bad_dialect);
else
ret = ps_ERR;
2008-01-16 08:23:29 +01:00
break;
case SetOptions::warning:
ret = do_set_command (parms[2], &Warnings);
2008-01-16 08:23:29 +01:00
break;
case SetOptions::generator:
case SetOptions::statistics:
ret = CONT;
2008-01-16 08:23:29 +01:00
break;
case SetOptions::heading:
ret = do_set_command(parms[2], &Heading);
2008-01-16 08:23:29 +01:00
break;
case SetOptions::bail:
ret = do_set_command(parms[2], &BailOnError);
2008-01-16 08:23:29 +01:00
break;
case SetOptions::bulk_insert:
if (*parms[2])
ret = bulk_insert_hack(cmd, global_sqldap);
else
ret = ps_ERR;
2008-01-16 08:23:29 +01:00
break;
default:
{
TEXT msg_string[MSG_LENGTH];
ISQL_msg_get(VALID_OPTIONS, msg_string);
isqlGlob.printf("%s\n", msg_string);
}
setoptions.showCommands(isqlGlob.Out);
ret = ps_ERR;
2008-01-16 08:23:29 +01:00
break;
}
return ret;
}
static processing_state do_set_command(const TEXT* parm,
bool* global_flag)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2001-05-23 15:26:42 +02:00
*
**************************************/
processing_state ret = SKIP;
2001-05-23 15:26:42 +02:00
if (!*parm)
*global_flag = !*global_flag;
2001-05-23 15:26:42 +02:00
else if (!strcmp(parm, "ON"))
*global_flag = true;
2001-05-23 15:26:42 +02:00
else if (!strcmp(parm, "OFF"))
*global_flag = false;
2001-05-23 15:26:42 +02:00
else
ret = ps_ERR;
2001-05-23 15:26:42 +02:00
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 = ps_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
isqlGlob.prints(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);
isqlGlob.prints(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 = ps_ERR;
}
return ret;
}
static processing_state get_statement(TEXT* const statement,
const size_t bufsize,
const TEXT* statement_prompt)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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.
2001-05-23 15:26:42 +02:00
*
**************************************/
processing_state ret = CONT;
2005-04-25 07:23:57 +02:00
// Lookup the continuation prompt once
TEXT con_prompt[MSG_LENGTH];
ISQL_msg_get(CON_PROMPT, con_prompt);
2001-05-23 15:26:42 +02:00
if (Interactive && !Input_file || Echo) {
ISQL_prompt(statement_prompt);
2001-05-23 15:26:42 +02:00
}
2005-04-25 07:23:57 +02:00
// Clear out statement buffer
2001-05-23 15:26:42 +02:00
TEXT* p = statement;
2001-05-23 15:26:42 +02:00
*p = '\0';
2005-04-25 07:23:57 +02:00
// Set count of characters to zero
2001-05-23 15:26:42 +02:00
2007-04-11 18:05:40 +02:00
size_t count = 0;
size_t valuable_count = 0; // counter of valuable (non-space) chars
const size_t term_length = Termlen - 1; // additional variable for decreasing calculation
2008-02-13 12:53:41 +01:00
Filelist->Ifp().indev_line = Filelist->Ifp().indev_aux;
bool done = false;
2001-05-23 15:26:42 +02:00
enum
{
normal,
in_single_line_comment,
in_block_comment,
in_single_quoted_string,
in_double_quoted_string
} state = normal;
2001-05-23 15:26:42 +02:00
while (!done) {
SSHORT c = getNextInputChar();
switch (c) {
2001-05-23 15:26:42 +02:00
case EOF:
2004-04-29 00:36:29 +02:00
// Go back to getc if we get interrupted by a signal.
2001-05-23 15:26:42 +02:00
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);
STDERROUT(errbuf);
Exit_value = FINI_ERROR;
ret = FAIL;
}
// If we hit EOF at the top of the flist, exit time
2008-02-13 12:53:41 +01:00
if (Filelist->count() == 1)
return FOUND_EOF;
2001-05-23 15:26:42 +02:00
2004-04-29 00:36:29 +02:00
// If this is not tmpfile, close it
2001-05-23 15:26:42 +02:00
2008-02-13 12:53:41 +01:00
if (!Filelist->sameInputAndOutput())
Filelist->Ifp().close();
2001-05-23 15:26:42 +02:00
// Reset to previous after other input
2001-05-23 15:26:42 +02:00
2008-02-13 12:53:41 +01:00
Filelist->removeIntoIfp();
2001-05-23 15:26:42 +02:00
if (Interactive && !Input_file || Echo)
2001-05-23 15:26:42 +02:00
ISQL_prompt(statement_prompt);
// CVC: Let's detect if we went back to the first level.
2008-02-13 12:53:41 +01:00
if (Filelist->readingStdin())
{
2008-01-16 08:23:29 +01:00
Interactive = !stdin_redirected();
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;
2001-05-23 15:26:42 +02:00
break;
case '\n':
// case '\0': // In particular with readline the \n is removed
if (state == in_single_line_comment)
{
state = normal;
2001-05-23 15:26:42 +02:00
}
2006-09-07 09:56:25 +02:00
// Catch the help ? without a terminator
if (*statement == '?' && count == 1) {
c = 0;
done = true;
2001-05-23 15:26:42 +02:00
break;
}
// If in a comment, keep reading
if (Interactive && !Input_file || Echo)
{
if (state == in_block_comment)
{ // Block comment prompt.
ISQL_prompt("--> ");
}
else if (valuable_count == 0)
{ // Ignore a series of nothing at the beginning
ISQL_prompt(statement_prompt);
}
else
{
2001-05-23 15:26:42 +02:00
ISQL_prompt(con_prompt);
}
2001-05-23 15:26:42 +02:00
}
break;
2001-05-23 15:26:42 +02:00
case '-':
// Could this the be start of a single-line comment.
2006-12-28 07:11:28 +01:00
if (state == normal && count > 0 && p[-1] == '-')
{
state = in_single_line_comment;
if (valuable_count == 1)
valuable_count = 0;
2001-05-23 15:26:42 +02:00
}
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.
2006-12-28 07:11:28 +01:00
if (state == normal && count > 0 && p[-1] == '/')
{
state = in_block_comment;
if (valuable_count == 1)
valuable_count = 0;
}
2001-05-23 15:26:42 +02:00
break;
case '/':
// Perhaps this is the end of a comment.
// Ignore possibilities of a comment ending inside
// quoted strings.
2006-12-28 07:11:28 +01:00
// Ignore things like /*/ since it isn't a block comment; only the start of it.
if (state == in_block_comment && count > 2 && p[-1] == '*')
{
state = normal;
valuable_count--; // This char is not valuable
}
2001-05-23 15:26:42 +02:00
break;
case SINGLE_QUOTE:
switch (state)
{
2004-08-27 07:13:47 +02:00
case normal:
state = in_single_quoted_string;
break;
case in_single_quoted_string:
state = normal;
break;
}
2001-05-23 15:26:42 +02:00
break;
case DBL_QUOTE:
switch (state)
{
2004-08-27 07:13:47 +02:00
case normal:
state = in_double_quoted_string;
break;
case in_double_quoted_string:
state = normal;
break;
}
2001-05-23 15:26:42 +02:00
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 &&
2004-08-27 07:00:31 +02:00
strncmp(p - term_length, isqlGlob.global_Term, term_length) == 0)))
{
c = 0;
done = true;
p -= term_length;
count -= term_length;
}
}
2001-05-23 15:26:42 +02:00
// Any non-space character is significant if not in comment
if (state != in_block_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;
}
2001-05-23 15:26:42 +02:00
}
*p++ = c;
count++;
2001-05-23 15:26:42 +02:00
if (count > bufsize && !done)
{
ret = ERR_BUFFER_OVERFLOW;
2001-05-23 15:26:42 +02:00
// move some content to start of buffer just in case if
2004-12-16 11:13:51 +01:00
// the overflow has splitted terminator
count = MAX(Termlen, 2);
2004-08-27 07:00:31 +02:00
memcpy(statement, p - count, count);
p = statement + count;
2001-05-23 15:26:42 +02:00
*p = '\0';
}
}
// If this was a null statement, skip it
if (ret == CONT && !*statement)
2001-05-23 15:26:42 +02:00
ret = SKIP;
if (ret == CONT)
ret = frontend(statement);
2001-05-23 15:26:42 +02:00
2008-01-16 08:23:29 +01:00
if (ret == CONT)
{
// Place each non frontend statement in the temp file if we are reading
2008-01-16 08:23:29 +01:00
// from stdin.
2001-05-23 15:26:42 +02:00
2008-02-13 12:53:41 +01:00
Filelist->saveCommand(statement, isqlGlob.global_Term);
2001-05-23 15:26:42 +02:00
}
return ret;
}
static void get_str(const TEXT* const input_str,
const TEXT** str_begin,
const TEXT** str_end,
2003-09-09 13:03:37 +02:00
literal_string_type* str_flag)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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
2001-05-23 15:26:42 +02:00
* constant. It also marks the type of string constant and passes back.
*
2001-05-23 15:26:42 +02:00
* 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
2001-05-23 15:26:42 +02:00
*
* 4. INCOMPLETE_STRING --- no matching ending quote
2003-09-09 13:03:37 +02:00
*
2001-05-23 15:26:42 +02:00
* 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);
2001-05-23 15:26:42 +02:00
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;
2003-09-09 13:03:37 +02:00
TEXT delimited_char = *b1;
2001-05-23 15:26:42 +02:00
b1++;
2003-09-09 13:03:37 +02:00
bool done = false;
while (!done)
{
2001-05-23 15:26:42 +02:00
if (*b1 == delimited_char) {
b1++;
if (*b1 == delimited_char)
b1++;
else {
done = true;
2001-05-23 15:26:42 +02:00
*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
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
}
}
2001-05-23 15:26:42 +02:00
}
}
void ISQL_get_version(bool call_by_create_db)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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.
2001-05-23 15:26:42 +02:00
*
**************************************/
const UCHAR db_version_info[] = {
isc_info_ods_version,
2006-07-18 12:18:41 +02:00
isc_info_ods_minor_version,
isc_info_db_sql_dialect,
Version_info ? isc_info_firebird_version: isc_info_end,
isc_info_end
2001-05-23 15:26:42 +02:00
};
/*
** Each info item requested will return
2001-05-23 15:26:42 +02:00
**
** 1 byte for the info item tag
** 2 bytes for the length of the information that follows
** 1 to 4 bytes of integer information
2001-05-23 15:26:42 +02:00
**
** isc_info_end will not have a 2-byte length - which gives us
** some padding in the buffer.
*/
2002-06-29 15:39:11 +02:00
// 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[PRINT_BUFFER_LENGTH];
2004-09-05 17:00:51 +02:00
char bad_dialect_buf[BUFFER_LENGTH512];
bool print_warning = false;
2001-05-23 15:26:42 +02:00
global_dialect_spoken = 0;
2001-05-23 15:26:42 +02:00
if (isc_database_info(isc_status, &DB, sizeof(db_version_info),
reinterpret_cast<const char*>(db_version_info),
sizeof(buffer), (SCHAR*) buffer))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return;
}
const UCHAR* p = buffer;
while (*p != isc_info_end && *p != isc_info_truncated && p < buffer + sizeof(buffer))
{
const UCHAR item = (UCHAR) *p++;
const USHORT length = gds__vax_integer(p, sizeof(USHORT));
2001-05-23 15:26:42 +02:00
p += sizeof(USHORT);
switch (item) {
case isc_info_ods_version:
isqlGlob.major_ods = gds__vax_integer(p, length);
2001-05-23 15:26:42 +02:00
break;
2006-07-18 12:18:41 +02:00
case isc_info_ods_minor_version:
isqlGlob.minor_ods = gds__vax_integer(p, length);
break;
2001-05-23 15:26:42 +02:00
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) {
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
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,
2001-05-23 15:26:42 +02:00
" . Client SQL dialect is reset to 1.", NEWLINE);
isqlGlob.prints(bad_dialect_buf);
2001-05-23 15:26:42 +02:00
}
}
else { // ODS 10 databases
2001-05-23 15:26:42 +02:00
switch (global_dialect_spoken) {
2001-05-23 15:26:42 +02:00
case SQL_DIALECT_V5:
if (isqlGlob.SQL_dialect > SQL_DIALECT_V5)
print_warning = true;
2001-05-23 15:26:42 +02:00
break;
case SQL_DIALECT_V6:
if (isqlGlob.SQL_dialect != 0 && isqlGlob.SQL_dialect < SQL_DIALECT_V6)
print_warning = true;
2001-05-23 15:26:42 +02:00
break;
default:
break;
}
if (print_warning && Warnings) {
print_warning = false;
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
sprintf(bad_dialect_buf, "%s%d%s%d%s%s",
"WARNING: This database speaks SQL dialect ",
global_dialect_spoken,
2001-05-23 15:26:42 +02:00
" but Client SQL dialect was set to ",
isqlGlob.SQL_dialect, " .", NEWLINE);
isqlGlob.prints(bad_dialect_buf);
2001-05-23 15:26:42 +02:00
}
}
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)
{
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
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,
2001-05-23 15:26:42 +02:00
" . Client SQL dialect is reset to 1.", NEWLINE);
else {
2008-01-16 08:23:29 +01:00
//connecting_to_pre_v6_server = true; Not used anywhere.
2001-05-23 15:26:42 +02:00
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,
2001-05-23 15:26:42 +02:00
" . Client SQL dialect is reset to 1.", NEWLINE);
}
isqlGlob.prints(bad_dialect_buf);
2001-05-23 15:26:42 +02:00
}
else {
if (isqlGlob.SQL_dialect == 0) {
2008-01-16 08:23:29 +01:00
//connecting_to_pre_v6_server = true; Not used anywhere.
2001-05-23 15:26:42 +02:00
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,
2001-05-23 15:26:42 +02:00
" . Client SQL dialect is reset to 1.", NEWLINE);
isqlGlob.prints(bad_dialect_buf);
2001-05-23 15:26:42 +02:00
}
}
break;
case isc_info_firebird_version:
if (Version_info)
{
// This information will be skipped if the server isn't given enough buffer
// to put it all. It's a FULL or NOTHING answer. It grows with redirection.
// The command SHOW version that calls isc_version() will return more info.
isqlGlob.printf("Server version:%s", NEWLINE);
const UCHAR* q = p; // We don't want to spoil p with a wrong calculation.
const UCHAR* limit = q + length;
for (int times = *q++; times && q < limit; --times)
{
int l = *q++;
if (l > limit - q)
l = limit - q;
isqlGlob.printf("%.*s%s", l, q, NEWLINE);
q += l;
2005-12-29 09:06:11 +01:00
}
}
break;
2002-06-29 15:39:11 +02:00
2001-05-23 15:26:42 +02:00
default:
isqlGlob.printf("Internal error: Unexpected isc_info_value %d%s",
item, NEWLINE);
2001-05-23 15:26:42 +02:00
break;
}
p += length;
}
if (isqlGlob.major_ods < ODS_VERSION8) {
TEXT errbuf[MSG_LENGTH];
ISQL_msg_get(SERVER_TOO_OLD, errbuf);
STDERROUT(errbuf);
return;
2001-05-23 15:26:42 +02:00
}
2005-04-25 07:23:57 +02:00
/* If the remote server did not respond to our request for
2001-05-23 15:26:42 +02:00
"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
2001-05-23 15:26:42 +02:00
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;
2001-05-23 15:26:42 +02:00
if (global_dialect_spoken > 0)
isqlGlob.db_SQL_dialect = global_dialect_spoken;
2001-05-23 15:26:42 +02:00
else
isqlGlob.db_SQL_dialect = SQL_DIALECT_V5;
2001-05-23 15:26:42 +02:00
}
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)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* 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.
2001-05-23 15:26:42 +02:00
**************************************/
int i;
for (i = len - 1; i >= 0 && ((fb_isspace(str[i])) || (str[i] == 0)); i--);
2001-05-23 15:26:42 +02:00
str[i + 1] = 0;
}
2003-10-16 10:51:06 +02:00
void ISQL_ri_action_print(const TEXT* ri_action_str,
const TEXT* ri_action_prefix_str,
bool all_caps)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* I S Q L _ r i _ a c t i o n _ p r i n t
2001-05-23 15:26:42 +02:00
*
**************************************
*
* 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.
2001-05-23 15:26:42 +02:00
*
**************************************/
2003-10-16 10:51:06 +02:00
for (const ri_actions* ref_int = ri_actions_all; ref_int->ri_action_name;
++ref_int)
{
2001-05-23 15:26:42 +02:00
if (!strcmp(ref_int->ri_action_name, ri_action_str)) {
if (*ref_int->ri_action_print_caps)
{
// we have something to print
2001-05-23 15:26:42 +02:00
if (all_caps)
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%s %s", ri_action_prefix_str,
2001-05-23 15:26:42 +02:00
ref_int->ri_action_print_caps);
else if (*ref_int->ri_action_print_mixed)
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%s %s", ri_action_prefix_str,
2001-05-23 15:26:42 +02:00
ref_int->ri_action_print_mixed);
}
return;
}
}
fb_assert(FALSE);
2001-05-23 15:26:42 +02:00
}
static bool get_numeric(const UCHAR* string,
USHORT length,
SSHORT* scale,
SINT64* ptr)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* g e t _ n u m e r i c
2001-05-23 15:26:42 +02:00
*
**************************************
*
* Functional description
* Convert a numeric literal (string) to its binary value.
2001-05-23 15:26:42 +02:00
*
* The binary value (int64) is stored at the
* address given by ptr.
2001-05-23 15:26:42 +02:00
*
**************************************/
SINT64 value = 0;
SSHORT local_scale = 0, sign = 0;
bool digit_seen = false, fraction = false;
2001-05-23 15:26:42 +02:00
const UCHAR* const end = string + length;
for (const UCHAR* p = string; p < end; p++) {
2001-05-23 15:26:42 +02:00
if (DIGIT(*p)) {
digit_seen = true;
2001-05-23 15:26:42 +02:00
2006-09-07 09:56:25 +02:00
// 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!
2001-05-23 15:26:42 +02:00
2007-02-06 10:51:38 +01:00
if (value > INT64_LIMIT)
return false;
if (value == INT64_LIMIT)
{
// possibility of an overflow
2007-02-06 10:51:38 +01:00
if ((*p > '8' && sign == -1) || (*p > '7' && sign != -1))
return false;
2001-05-23 15:26:42 +02:00
}
2006-09-07 09:56:25 +02:00
// Force the subtraction to be performed before the addition,
// thus preventing a possible signed arithmetic overflow.
2001-05-23 15:26:42 +02:00
value = value * 10 + (*p - '0');
if (fraction)
--local_scale;
}
2007-02-06 10:51:38 +01:00
else if (*p == '.')
{
2001-05-23 15:26:42 +02:00
if (fraction)
return false;
2007-02-06 10:51:38 +01:00
fraction = true;
2001-05-23 15:26:42 +02:00
}
else if (*p == '-' && !digit_seen && !sign && !fraction)
sign = -1;
else if (*p == '+' && !digit_seen && !sign && !fraction)
sign = 1;
else if (*p != BLANK)
return false;
2001-05-23 15:26:42 +02:00
}
if (!digit_seen)
return false;
2001-05-23 15:26:42 +02:00
*scale = local_scale;
*(SINT64*) ptr = ((sign == -1) ? -value : value);
return true;
2001-05-23 15:26:42 +02:00
}
// Helper to print boolean values in the SET options.
static void print_set(const char* str, bool v)
{
isqlGlob.printf("%-25s%s%s", str, v ? "ON" : "OFF", NEWLINE);
}
static processing_state print_sets()
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* p r i n t _ s e t s
2001-05-23 15:26:42 +02:00
*
**************************************
*
* Functional description
* Print the current set values
*
**************************************/
print_set("Print statistics:", Stats);
print_set("Echo commands:", Echo);
print_set("List format:", List);
print_set("Row Count:", Docount);
print_set("Autocommit DDL:", Autocommit);
#ifdef SCROLLABLE_CURSORS
print_set("Autofetch records:", Autofetch);
2001-05-23 15:26:42 +02:00
#endif
print_set("Access Plan:", Plan);
print_set("Access Plan only:", Planonly);
isqlGlob.printf("%-25s", "Display BLOB type:");
2001-05-23 15:26:42 +02:00
if (Doblob == ALL_BLOBS)
isqlGlob.printf("ALL");
2001-05-23 15:26:42 +02:00
else if (Doblob == NO_BLOBS)
isqlGlob.printf("NONE");
2001-05-23 15:26:42 +02:00
else {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%d", Doblob);
2001-05-23 15:26:42 +02:00
}
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
if (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET)) {
isqlGlob.printf("%-25s%s%s", "Set names:", ISQL_charset, NEWLINE);
2001-05-23 15:26:42 +02:00
}
print_set("Column headings:", Heading);
2008-01-16 08:23:29 +01:00
if (global_Cols.count())
{
2005-05-24 06:42:01 +02:00
isqlGlob.printf("Column print widths:%s", NEWLINE);
2008-01-16 08:23:29 +01:00
const ColList::item* p = global_Cols.getHead();
2001-05-23 15:26:42 +02:00
while (p) {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%s%s width: %d%s",
TAB_AS_SPACES, p->col_name, p->col_len, NEWLINE);
2008-01-16 08:23:29 +01:00
p = p->next;
2001-05-23 15:26:42 +02:00
}
}
isqlGlob.printf("%-25s%s%s", "Terminator:", isqlGlob.global_Term, NEWLINE);
print_set("Time:", Time_display);
print_set("Warnings:", Warnings);
print_set("Bail on error:", BailOnError);
2005-04-03 08:58:40 +02:00
return SKIP;
2001-05-23 15:26:42 +02:00
}
static processing_state help(const TEXT* what)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* h e l p
*
**************************************
*
* Functional description
* List the known commands.
*
**************************************/
2005-04-25 07:23:57 +02:00
// 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:
2006-09-08 23:13:30 +02:00
HLP_BLOBDMP, // BLOBDUMP <blobid> <file> -- dump BLOB to a file
HLP_BLOBVIEW, // BLOBVIEW <blobid> -- view BLOB in text editor
HLP_EDIT, // EDIT [<filename>] -- edit SQL script file and execute
HLP_EDIT2, // EDIT -- edit current command buffer and execute
HLP_HELP, // HELP -- display this menu
2006-09-07 09:56:25 +02:00
HLP_INPUT, // INput <filename> -- take input from the named SQL file
2006-09-08 23:13:30 +02:00
HLP_OUTPUT, // OUTput [<filename>] -- write output to named file
HLP_OUTPUT2, // OUTput -- return output to stdout
HLP_SET_ROOT, // SET <option> -- (use HELP SET for list)
HLP_SHELL, // SHELL <command> -- execute Operating System command in sub-shell
HLP_SHOW, // SHOW <object> [<name>] -- display system information
2006-09-07 09:56:25 +02:00
HLP_OBJTYPE, // <object> = CHECK, DATABASE, DOMAIN, EXCEPTION, FILTER, FUNCTION, GENERATOR,
HLP_OBJTYPE2, // GRANT, INDEX, PROCEDURE, ROLE, SQL DIALECT, SYSTEM, TABLE,
HLP_OBJTYPE3, // TRIGGER, VERSION, VIEW
HLP_EXIT, // EXIT -- exit and commit changes
HLP_QUIT, // QUIT -- exit and roll back changes
2001-05-23 15:26:42 +02:00
0,
HLP_ALL, // All commands may be abbreviated to letters in CAPitals
-1 // end of list
2001-05-23 15:26:42 +02:00
};
static const SSHORT help_set_ids[] = {
HLP_SETCOM, //Set commands:
2006-09-08 23:13:30 +02:00
HLP_SET, // SET -- display current SET options
HLP_SETAUTO, // SET AUTOddl -- toggle autocommit of DDL statements
2001-05-23 15:26:42 +02:00
#ifdef SCROLLABLE_CURSORS
2006-09-08 23:13:30 +02:00
HLP_SETFETCH, // SET AUTOfetch -- toggle autofetch of records
2001-05-23 15:26:42 +02:00
#endif
2006-09-08 23:13:30 +02:00
HLP_SETBAIL, // SET BAIL -- toggle bailing out on errors in non-interactive mode
HLP_SETBLOB, // SET BLOB [ALL|<n>] -- display BLOBS of subtype <n> or ALL
HLP_SETBLOB2, // SET BLOB -- turn off BLOB display
HLP_SETCOUNT, // SET COUNT -- toggle count of selected rows on/off
HLP_SETECHO, // SET ECHO -- toggle command echo on/off
HLP_SETHEADING, // SET HEADING -- toggle column titles display on/off
HLP_SETLIST, // SET LIST -- toggle column or table display format
HLP_SETNAMES, // SET NAMES <csname> -- set name of runtime character set
HLP_SETPLAN, // SET PLAN -- toggle display of query access plan
HLP_SETPLANONLY, // SET PLANONLY -- toggle display of query plan without executing
HLP_SETSQLDIALECT, // SET SQL DIALECT <n> -- set sql dialect to <n>
HLP_SETSTAT, // SET STATs -- toggle display of performance statistics
HLP_SETTIME, // SET TIME -- toggle display of timestamp with DATE values
HLP_SETTERM, // SET TERM <string> -- change statement terminator string
HLP_SETWIDTH, // SET WIDTH <col> [<n>] -- set/unset print width to <n> for column <col>
2001-05-23 15:26:42 +02:00
0,
HLP_ALL, // All commands may be abbreviated to letters in CAPitals
-1 // end of list
2001-05-23 15:26:42 +02:00
};
TEXT msg[MSG_LENGTH];
const SSHORT* msgid;
if (!strcmp(what, "SET")) {
2001-05-23 15:26:42 +02:00
msgid = help_set_ids;
}
else {
2001-05-23 15:26:42 +02:00
msgid = help_ids;
}
2001-05-23 15:26:42 +02:00
for (; *msgid != -1; msgid++) {
if (*msgid != 0) {
ISQL_msg_get(*msgid, msg);
2001-05-23 15:26:42 +02:00
ISQL_printf(Help, msg);
}
ISQL_printf(Help, NEWLINE);
2001-05-23 15:26:42 +02:00
}
return (SKIP);
}
static bool isyesno(const TEXT* buffer)
2001-05-23 15:26:42 +02:00
{
/**********************************************
*
* i s y e s n o
*
**********************************************
*
* Functional description
* check if the first letter of the user's response
* corresponds to the first letter of Yes
* (in whatever language they are using)
*
* returns true for Yes, otherwise false.
2001-05-23 15:26:42 +02:00
*
**********************************************/
if (!have_trans) { // get the translation if we don't have it already
ISQL_msg_get(YES_ANS, sizeof(yesword), yesword);
have_trans = true;
2001-05-23 15:26:42 +02:00
}
2005-04-25 07:23:57 +02:00
// Just check first byte of yes response -- could be multibyte problem
2001-05-23 15:26:42 +02:00
if (UPPER7(buffer[0]) == UPPER7(yesword[0]))
return true;
2001-05-23 15:26:42 +02:00
else
return false;
2001-05-23 15:26:42 +02:00
}
static processing_state newdb(TEXT* dbname,
const TEXT* usr,
const TEXT* psw,
int numbufs,
const TEXT* sql_role_nm,
bool start_user_trans)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* n e w d b
*
**************************************
*
* Functional description
* Change the current database from what it was.
* This procedure is called when we first enter this program.
*
* Parameters: dbname -- Name of database to open
* usr -- user name, if given
* psw -- password, if given
* numbufs -- # of connection cache buffers, if given, 0 if not
2001-05-23 15:26:42 +02:00
* sql_role_nm -- sql role name
*
2001-05-23 15:26:42 +02:00
**************************************/
2005-04-25 07:23:57 +02:00
// No name of a database, just return an error
2001-05-23 15:26:42 +02:00
if (!dbname || !*dbname)
return ps_ERR;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
/*
* Since the dbname is set already, in the case where a database is specified
* on the command line, we need to save it so ISQL_disconnect does not NULL it
* out. We will restore it after the disconnect. The save_database buffer
* will also be used to translate dbname to the proper character set.
*/
const size_t len = chop_at(dbname, MAXPATHLEN);
SCHAR* save_database = (SCHAR*) ISQL_ALLOC(len + 1);
2001-05-23 15:26:42 +02:00
if (!save_database)
return ps_ERR;
2001-05-23 15:26:42 +02:00
strcpy(save_database, dbname);
ISQL_disconnect_database(false);
2001-05-23 15:26:42 +02:00
strcpy(dbname, save_database);
TEXT local_psw[BUFFER_LENGTH128];
TEXT local_usr[BUFFER_LENGTH128];
TEXT local_sql_role[BUFFER_LENGTH256];
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// global user and passwords are set only if they are not originally set
2001-05-23 15:26:42 +02:00
local_psw[0] = 0;
local_usr[0] = 0;
local_sql_role[0] = 0;
2005-04-25 07:23:57 +02:00
// Strip quotes if well-intentioned
2001-05-23 15:26:42 +02:00
strip_quotes(dbname, isqlGlob.global_Db_name);
2001-05-23 15:26:42 +02:00
strip_quotes(usr, local_usr);
strip_quotes(psw, local_psw);
2005-04-25 07:23:57 +02:00
/* if local user is not specified, see if global options are
specified - don't let a global role setting carry forward if a
specific local user was specified
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
Special handling for SQL Roles:
* V5 client -- strip the quotes
* V6 client -- pass the role name as it is
*/
2001-05-23 15:26:42 +02:00
if (sql_role_nm) {
if (isqlGlob.SQL_dialect == SQL_DIALECT_V5)
2001-05-23 15:26:42 +02:00
strip_quotes(sql_role_nm, local_sql_role);
else
strcpy(local_sql_role, sql_role_nm);
}
if (!(strlen(local_sql_role)) && global_role) {
if (isqlGlob.SQL_dialect == SQL_DIALECT_V5)
strip_quotes(isqlGlob.Role, local_sql_role);
2001-05-23 15:26:42 +02:00
else
strcpy(local_sql_role, isqlGlob.Role);
2001-05-23 15:26:42 +02:00
}
if (!(strlen(local_usr)) && global_usr)
strcpy(local_usr, isqlGlob.User);
2001-05-23 15:26:42 +02:00
if (!(strlen(local_psw)) && global_psw)
strcpy(local_psw, Password);
int local_numbufs = numbufs;
if ((local_numbufs == 0) && has_global_numbufs)
local_numbufs = global_numbufs;
2001-05-23 15:26:42 +02:00
// Build up a dpb
Firebird::ClumpletWriter dpb(Firebird::ClumpletReader::Tagged, MAX_DPB_SIZE, isc_dpb_version1);
2001-05-23 15:26:42 +02:00
if (*ISQL_charset && strcmp(ISQL_charset, DEFCHARSET)) {
dpb.insertString(isc_dpb_lc_ctype, ISQL_charset, strlen(ISQL_charset));
2001-05-23 15:26:42 +02:00
}
SSHORT l;
2001-05-23 15:26:42 +02:00
if (l = strlen(local_usr)) {
dpb.insertString(isc_dpb_user_name, local_usr, l);
2001-05-23 15:26:42 +02:00
}
if (l = strlen(local_psw)) {
dpb.insertString(isc_dpb_password, local_psw, l);
2001-05-23 15:26:42 +02:00
}
if (l = strlen(local_sql_role)) {
dpb.insertInt(isc_dpb_sql_dialect, isqlGlob.SQL_dialect);
dpb.insertString(isc_dpb_sql_role_name, local_sql_role, l);
2001-05-23 15:26:42 +02:00
}
if (local_numbufs > 0) {
dpb.insertInt(isc_dpb_num_buffers, local_numbufs);
2001-05-23 15:26:42 +02:00
}
if (Nodbtriggers)
dpb.insertInt(isc_dpb_no_db_triggers, 1);
2006-12-08 19:38:15 +01:00
#ifdef TRUSTED_AUTH
if (Trusted_auth) {
dpb.insertTag(isc_dpb_trusted_auth);
}
#endif
{ // scope
const TEXT* local_name = isqlGlob.global_Db_name;
2001-05-23 15:26:42 +02:00
if (isc_attach_database
(isc_status, 0, local_name, &DB, dpb.getBufferLength(),
reinterpret_cast<const char*>(dpb.getBuffer())))
{
ISQL_errmsg(isc_status);
isqlGlob.global_Db_name[0] = '\0';
if (save_database)
ISQL_FREE(save_database);
return FAIL;
}
} // scope
2001-05-23 15:26:42 +02:00
if (save_database)
ISQL_FREE(save_database);
ISQL_get_version(false);
2001-05-23 15:26:42 +02:00
if (*local_sql_role) {
switch (isqlGlob.SQL_dialect) {
2001-05-23 15:26:42 +02:00
case SQL_DIALECT_V5:
// Uppercase the Sql isqlGlob.Role name
ISQL_make_upper(local_sql_role);
2001-05-23 15:26:42 +02:00
break;
case SQL_DIALECT_V6_TRANSITION:
case SQL_DIALECT_V6:
if (*local_sql_role == DBL_QUOTE
|| *local_sql_role == SINGLE_QUOTE)
{
// Remove the delimited quotes and escape quote from ROLE name.
const TEXT end_quote = *local_sql_role;
ISQL_remove_and_unescape_quotes(local_sql_role, end_quote);
2001-05-23 15:26:42 +02:00
}
else {
ISQL_make_upper(local_sql_role);
2001-05-23 15:26:42 +02:00
}
break;
default:
break;
}
}
// CVC: Do not put the user and pw used to extract metadata in a script!
// Only acknowledge user connection parameters in interactive logins.
if (Interactive) {
if (local_usr[0] != '\0') {
if (local_sql_role[0] != '\0') {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("Database: %s, User: %s, Role: %s%s",
dbname, local_usr, local_sql_role, NEWLINE);
}
else {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("Database: %s, User: %s%s",
dbname, local_usr, NEWLINE);
}
}
else {
if (local_sql_role[0] != '\0') {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("Database: %s, Role: %s%s",
dbname, local_sql_role, NEWLINE);
}
else {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("Database: %s%s", dbname, NEWLINE);
}
}
}
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// CVC: We do not require those pesky transactions for -x or -a options.
// Metadata extraction works exclusively with default transaction gds__trans.
2001-05-23 15:26:42 +02:00
if (start_user_trans) {
2001-05-23 15:26:42 +02:00
// Start the user transaction and default transaction
2001-05-23 15:26:42 +02:00
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 (Autocommit && isc_start_transaction(isc_status, &D__trans, 1, &DB, 5, default_tpb))
ISQL_errmsg(isc_status);
}
2001-05-23 15:26:42 +02:00
2002-06-29 15:39:11 +02:00
// Allocate a new user statement for this database
global_Stmt = 0;
if (isc_dsql_allocate_statement(isc_status, &DB, &global_Stmt)) {
ISQL_errmsg(isc_status);
if (M__trans)
end_trans();
if (D__trans)
commit_trans(&D__trans);
isc_detach_database(isc_status, &DB);
DB = 0;
isqlGlob.global_Db_name[0] = '\0';
2004-05-03 01:06:37 +02:00
M__trans = 0;
D__trans = 0;
return FAIL;
}
}
else {
global_Stmt = 0;
}
2001-05-23 15:26:42 +02:00
return SKIP;
2001-05-23 15:26:42 +02:00
}
static processing_state newinput(const TEXT* infile)
{
/**************************************
*
* n e w i n p u t
*
**************************************
*
* Functional description
* Read commands from the named input file
*
* Parameters: infile -- Second word of the command line
* filelist is a stack of file pointers to
* return from nested inputs
*
* The result of calling this is to point the
* global input file pointer, Ifp, to the named file.
*
**************************************/
TEXT errbuf[MSG_LENGTH];
// If there is no file name specified, return error
if (!infile || !*infile) {
return ps_ERR;
}
TEXT path[MAXPATHLEN];
strip_quotes(infile, path);
const TEXT* file = path;
// filelist is a linked list of file pointers. We must add a node to
// the linked list before discarding the current Ifp.
// Filelist is a global pointing to base of list.
FILE* fp = fopen(file, "r");
if (fp) {
2008-02-13 12:53:41 +01:00
Filelist->insertIfp();
Filelist->Ifp().init(fp, file);
}
else {
ISQL_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << file);
STDERROUT(errbuf);
return FAIL;
}
Input_file = true;
return SKIP;
}
static processing_state newoutput(const TEXT* outfile)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* n e w o u t p u t
*
**************************************
*
* Functional description
* Change the current output file
2001-05-23 15:26:42 +02:00
*
* Parameters: outfile : Name of file to receive query output
*
**************************************/
processing_state ret = SKIP;
2005-04-25 07:23:57 +02:00
// If there is a file name, attempt to open it for append
2001-05-23 15:26:42 +02:00
if (*outfile) {
TEXT path[MAXPATHLEN];
2001-05-23 15:26:42 +02:00
strip_quotes(outfile, path);
outfile = path;
2004-04-29 00:36:29 +02:00
FILE* fp = fopen(outfile, "a");
if (fp)
2001-05-23 15:26:42 +02:00
{
if (isqlGlob.Out && isqlGlob.Out != stdout)
fclose(isqlGlob.Out);
isqlGlob.Out = fp;
2001-05-23 15:26:42 +02:00
if (Merge_stderr)
isqlGlob.Errfp = isqlGlob.Out;
if (Merge_diagnostic)
Diag = isqlGlob.Out;
2001-05-23 15:26:42 +02:00
}
else {
TEXT errbuf[MSG_LENGTH];
ISQL_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << outfile);
STDERROUT(errbuf);
2001-05-23 15:26:42 +02:00
ret = FAIL;
}
}
else {
2004-04-29 00:36:29 +02:00
// Revert to stdout
if (isqlGlob.Out != stdout) {
fclose(isqlGlob.Out);
isqlGlob.Out = stdout;
2001-05-23 15:26:42 +02:00
if (Merge_stderr)
isqlGlob.Errfp = isqlGlob.Out;
if (Merge_diagnostic)
Diag = isqlGlob.Out;
2001-05-23 15:26:42 +02:00
}
}
return (ret);
}
static processing_state newsize(const TEXT* colname,
const TEXT* sizestr)
{
/**************************************
*
* n e w s i z e
*
**************************************
*
* Functional description
* Add a column name and print width to collist
*
**************************************/
2008-01-16 08:23:29 +01:00
if (!*colname || (strlen(colname) >= QUOTEDLENGTH))
return ps_ERR;
2008-01-16 08:23:29 +01:00
char buf[QUOTEDLENGTH];
if (colname[0] == DBL_QUOTE)
{
strcpy(buf, colname);
ISQL_remove_and_unescape_quotes(buf, DBL_QUOTE);
colname = buf;
}
2008-01-16 08:23:29 +01:00
if (strlen(colname) > MAX_SQL_IDENTIFIER_LEN)
return ps_ERR;
// If no size is given, remove the entry
if (!*sizestr)
{
// We don't signal error if the item to be removed doesn't exist.
global_Cols.remove(colname);
return SKIP;
}
2008-01-16 08:23:29 +01:00
const int size = atoi(sizestr);
if (size <= 0)
return ps_ERR;
2008-01-16 08:23:29 +01:00
// We don't care if it was insertion or replacement.
global_Cols.put(colname, size);
return SKIP;
}
static processing_state newtrans(const TEXT* statement)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* n e w t r a n s
*
**************************************
*
* Functional description
* Intercept and handle a set transaction statement by zeroing M__trans
*
* Leave the default transaction, gds_trans, alone
2001-05-23 15:26:42 +02:00
* Parameters: The statement in its entirety
*
**************************************/
if (!ISQL_dbcheck())
return FAIL;
if (end_trans() == FAIL)
2001-05-23 15:26:42 +02:00
return FAIL;
2004-05-03 01:06:37 +02:00
M__trans = 0;
2001-05-23 15:26:42 +02:00
// M__trans = 0 after the commit or rollback. Ready for a new transaction
2001-05-23 15:26:42 +02:00
if (isc_dsql_execute_immediate(isc_status, &DB, &M__trans, 0, statement,
isqlGlob.SQL_dialect, NULL))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return FAIL;
}
return SKIP;
}
static processing_state parse_arg(int argc,
SCHAR** argv,
SCHAR* tabname,
2004-04-29 00:36:29 +02:00
FILE** sess)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* p a r s e _ a r g
*
**************************************
*
* Functional description
* Parse command line flags
2001-05-23 15:26:42 +02:00
* All flags except database are -X. Look for
* the - and make it so.
*
**************************************/
processing_state ret = SKIP;
TEXT errbuf[MSG_LENGTH];
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Initialize database name
2001-05-23 15:26:42 +02:00
isqlGlob.global_Db_name[0] = '\0';
isqlGlob.global_Target_db[0] = '\0';
2001-05-23 15:26:42 +02:00
Password[0] = '\0';
isqlGlob.User[0] = '\0';
isqlGlob.Role[0] = '\0';
global_numbufs = 0;
Quiet = false;
2001-05-23 15:26:42 +02:00
Exit_value = FINI_OK;
2005-04-25 07:23:57 +02:00
// default behavior in V6.0 is SQL_DIALECT_V6
2001-05-23 15:26:42 +02:00
requested_SQL_dialect = SQL_DIALECT_V6;
Merge_stderr = false;
Merge_diagnostic = false;
2001-05-23 15:26:42 +02:00
{ // scope
const size_t lgth = strlen(DEFCHARSET);
if (lgth < MAXCHARSET_SIZE)
strcpy(ISQL_charset, DEFCHARSET);
else
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(ISQL_charset, DEFCHARSET, MAXCHARSET_SIZE);
} // scope
2001-05-23 15:26:42 +02:00
// redirected stdin means act like -i was set
2001-05-23 15:26:42 +02:00
2008-02-13 12:53:41 +01:00
Filelist->Ifp().init(stdin, "stdin");
2001-05-23 15:26:42 +02:00
// Terminators are initialized
2001-05-23 15:26:42 +02:00
Termlen = strlen(DEFTERM);
if (Termlen < MAXTERM_SIZE)
strcpy(isqlGlob.global_Term, DEFTERM);
2001-05-23 15:26:42 +02:00
else {
Termlen = MAXTERM_SIZE - 1;
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(isqlGlob.global_Term, DEFTERM, Termlen + 1);
2001-05-23 15:26:42 +02:00
}
2005-04-25 07:23:57 +02:00
// Initialize list of input file pointers
2001-05-23 15:26:42 +02:00
2008-02-13 12:53:41 +01:00
//Filelist->clear();
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Interpret each command line argument
const SCHAR switchchar = '-';
#ifdef DEV_BUILD
bool istable = false;
#endif
bool switch_used[SWITCH_UNKNOWN];
for (int su = 0; su < SWITCH_UNKNOWN; ++su) {
switch_used[su] = false;
}
2004-12-09 03:50:47 +01:00
int i = 1;
const char* s = argv[i];
2001-05-23 15:26:42 +02:00
while (i < argc) {
// Look at flags to find unique match. If the flag has an arg,
// advance the pointer (and i). Only change the return value
// for extract switch or error.
if (*s == switchchar)
{
switch_id swid = SWITCH_UNKNOWN;
int swarg_int = 0;
char* swarg_str = NULL;
size_t swlen = strlen(s + 1);
2006-03-26 05:34:14 +02:00
for (int j = 0; j < FB_NELEM(switches); j++) {
if (swlen >= switches[j].abbrlen && !fb_utils::strnicmp(s + 1, switches[j].text, swlen))
{
swid = switches[j].id;
if (switch_used[swid]) // && !switches[j].allow_dup
{
ISQL_msg_get(USAGE_DUPSW, errbuf, SafeArg() << (s + 1));
STDERROUT(errbuf);
ret = ps_ERR;
break;
}
switch_used[swid] = true;
if (switches[j].argtype != SWARG_NONE)
{
// make sure swarg_str is really a pointer to argv, not something else !!
swarg_str = argv[++i];
if (!swarg_str || !*swarg_str) {
ISQL_msg_get(USAGE_NOARG, errbuf, SafeArg() << (s + 1));
STDERROUT(errbuf);
ret = ps_ERR;
}
else if (switches[j].argtype == SWARG_INTEGER) {
char* err;
long value = strtol(swarg_str, &err, 10);
if (*err) {
// conversion error
ISQL_msg_get(USAGE_NOTINT, errbuf, SafeArg() << swarg_str << (s + 1));
STDERROUT(errbuf);
ret = ps_ERR;
}
else if ((value < INT_MIN) || (value > INT_MAX)) {
ISQL_msg_get(USAGE_RANGE, errbuf, SafeArg() << swarg_str << (s + 1));
STDERROUT(errbuf);
ret = ps_ERR;
}
else {
swarg_int = (int) value;
}
}
}
break; // switch found, exit the loop
}
}
// Quit the loop of interpreting if we got an error
if (ret == ps_ERR)
break;
2006-04-06 10:18:53 +02:00
switch (swid)
{
2001-05-23 15:26:42 +02:00
#ifdef DEV_BUILD
case SWITCH_EXTRACTTBL:
istable = true;
ret = EXTRACT;
break;
2001-05-23 15:26:42 +02:00
#endif
case SWITCH_EXTRACT:
ret = EXTRACT;
break;
2001-05-23 15:26:42 +02:00
case SWITCH_EXTRACTALL:
ret = EXTRACTALL;
break;
2001-05-23 15:26:42 +02:00
case SWITCH_BAIL:
BailOnError = true;
break;
2001-05-23 15:26:42 +02:00
case SWITCH_ECHO:
Echo = true;
break;
case SWITCH_MERGE:
Merge_stderr = true;
break;
2001-05-23 15:26:42 +02:00
case SWITCH_MERGE2:
Merge_diagnostic = true;
break;
2001-05-23 15:26:42 +02:00
case SWITCH_NOAUTOCOMMIT:
Autocommit = false;
break;
case SWITCH_NODBTRIGGERS:
Nodbtriggers = true;
break;
case SWITCH_NOWARN:
Warnings = false;
break;
case SWITCH_OUTPUT:
if (newoutput(swarg_str) == FAIL) {
ret = ps_ERR;
}
break;
case SWITCH_INPUT:
if (newinput(swarg_str) == SKIP) {
Interactive = false;
}
else {
ret = ps_ERR;
}
// CVC: Set by newinput() above only if successful.
// Input_file = true;
break;
case SWITCH_TERM:
Termlen = strlen(swarg_str);
if (Termlen >= MAXTERM_SIZE) {
Termlen = MAXTERM_SIZE - 1;
}
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(isqlGlob.global_Term, swarg_str, Termlen + 1);
break;
case SWITCH_DATABASE:
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(isqlGlob.global_Target_db, swarg_str, sizeof(isqlGlob.global_Target_db));
break;
case SWITCH_PAGE:
Pagelength = swarg_int;
break;
case SWITCH_PASSWORD:
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(Password, fb_utils::get_passwd(swarg_str), sizeof(Password));
// make sure swarg_str is really a pointer to argv, not something else !!
global_psw = true;
break;
case SWITCH_USER:
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(isqlGlob.User, swarg_str, sizeof(isqlGlob.User));
global_usr = true;
break;
case SWITCH_ROLE:
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(isqlGlob.Role, swarg_str, sizeof(isqlGlob.Role));
global_role = true;
break;
case SWITCH_ROLE2:
strcpy(isqlGlob.Role, "\"");
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(isqlGlob.Role + 1, swarg_str, sizeof(isqlGlob.Role) - 2);
strcat(isqlGlob.Role, "\"");
global_role = true;
break;
case SWITCH_CACHE:
if (swarg_int <= 0) {
ISQL_msg_get(USAGE_RANGE, errbuf, SafeArg() << swarg_str << (s + 1));
STDERROUT(errbuf);
ret = ps_ERR;
}
global_numbufs = swarg_int;
has_global_numbufs = true;
break;
case SWITCH_CHARSET:
{
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(Charset, swarg_str, sizeof(Charset));
const size_t lgth = strlen(Charset);
if (lgth < MAXCHARSET_SIZE) {
strcpy(ISQL_charset, Charset);
}
2008-01-16 08:23:29 +01:00
else
fb_utils::copy_terminate(ISQL_charset, Charset, MAXCHARSET_SIZE);
break;
}
case SWITCH_QUIET:
Quiet = true;
break;
2006-12-08 19:38:15 +01:00
#ifdef TRUSTED_AUTH
case SWITCH_TRUSTED:
Trusted_auth = true;
break;
#endif
case SWITCH_VERSION:
Version_info = true;
ISQL_msg_get(VERSION, errbuf, SafeArg() << FB_VERSION);
isqlGlob.printf("%s%s", errbuf, NEWLINE);
break;
case SWITCH_SQLDIALECT:
requested_SQL_dialect = swarg_int;
if (requested_SQL_dialect < SQL_DIALECT_V5 ||
requested_SQL_dialect > SQL_DIALECT_CURRENT)
{
ret = ps_ERR;
}
else {
// requested_SQL_dialect is used to specify the database dialect
// if SQL dialect was not specified. Since it is possible to
// have a client dialect of 2, force the database dialect to 3, but
// leave the client dialect as 2
isqlGlob.SQL_dialect = requested_SQL_dialect;
if (requested_SQL_dialect == SQL_DIALECT_V6_TRANSITION)
{
Merge_stderr = true;
requested_SQL_dialect = SQL_DIALECT_V6;
}
}
break;
2001-05-23 15:26:42 +02:00
default: // unknown switch
ISQL_msg_get(SWITCH, errbuf, SafeArg() << (s + 1));
STDERROUT(errbuf);
ret = ps_ERR;
2001-05-23 15:26:42 +02:00
}
}
else
{
// This is not a switch, it is a db_name
if (isqlGlob.global_Db_name[0]) {
// We already have a database name
ISQL_msg_get(USAGE_DUPDB, errbuf, SafeArg() << isqlGlob.global_Db_name << s);
STDERROUT(errbuf);
ret = ps_ERR;
}
else {
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(isqlGlob.global_Db_name, s, sizeof(isqlGlob.global_Db_name));
2001-05-23 15:26:42 +02:00
#ifdef DEV_BUILD
// If there is a table name, it follows
if (istable && (s = argv[++i]) && *s)
2008-01-16 08:23:29 +01:00
fb_utils::copy_terminate(tabname, s, WORDLENGTH);
2001-05-23 15:26:42 +02:00
#endif
}
2001-05-23 15:26:42 +02:00
}
// Quit the loop of interpreting if we got an error
if (ret == ps_ERR)
2001-05-23 15:26:42 +02:00
break;
2001-05-23 15:26:42 +02:00
s = argv[++i];
}
2005-04-25 07:23:57 +02:00
// If not input, then set up first filelist
2001-05-23 15:26:42 +02:00
2008-02-13 12:53:41 +01:00
if (Filelist->readingStdin()) {
Filelist->insert(stdin, "stdin");
fb_assert(Filelist->count() == 1);
2001-05-23 15:26:42 +02:00
}
2008-01-16 08:23:29 +01:00
return ret;
2001-05-23 15:26:42 +02:00
}
// *********************
// p a s s t h r o u g h
// *********************
// Execute a command directly on the server. No interpretation done in isql.
// Use with care. Only for debug builds.
#ifdef DEV_BUILD
static processing_state passthrough(const char* cmd)
{
processing_state ret = SKIP;
if (isc_dsql_execute_immediate(isc_status, &DB, &M__trans, 0,
cmd, isqlGlob.SQL_dialect, NULL))
{
ISQL_errmsg(isc_status);
if (!DB)
ret = FAIL;
else
ret = ps_ERR;
}
return ret;
}
#endif
static SSHORT print_item(TEXT** s,
XSQLVAR* var,
2008-01-16 08:23:29 +01:00
const int length)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* p r i n t _ i t e m
*
**************************************
*
* Functional description
* Print an SQL data item as described.
*
**************************************/
2004-06-06 07:25:32 +02:00
TEXT d[32];
2001-05-23 15:26:42 +02:00
TEXT* p = *s;
2001-05-23 15:26:42 +02:00
*p = '\0';
SSHORT dtype = var->sqltype & ~1; // may be modified by print_item_blob()
2008-01-16 08:23:29 +01:00
const int dscale = var->sqlscale;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// The printlength should have in it the right length
2008-01-16 08:23:29 +01:00
//const int length = printlength;
2001-05-23 15:26:42 +02:00
if (List) {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%-31s ", fb_utils::exact_name(var->aliasname));
2001-05-23 15:26:42 +02:00
}
if ((var->sqltype & 1) && (*var->sqlind < 0)) {
2005-04-25 07:23:57 +02:00
// If field was missing print <null>
2001-05-23 15:26:42 +02:00
if (List) {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("<null>%s", NEWLINE);
2001-05-23 15:26:42 +02:00
}
if ((dtype == SQL_TEXT) || (dtype == SQL_VARYING))
2008-01-16 08:23:29 +01:00
sprintf(p, "%-*s ", length, "<null>");
2001-05-23 15:26:42 +02:00
else
2008-01-16 08:23:29 +01:00
sprintf(p, "%*s ", length, "<null>");
2001-05-23 15:26:42 +02:00
}
else if (!strncmp(var->sqlname, "DB_KEY", 6)) {
2004-06-06 07:25:32 +02:00
// Special handling for db_keys printed in hex
2001-05-23 15:26:42 +02:00
// Keep a temp buf, d for building the binary string
2001-05-23 15:26:42 +02:00
for (const TEXT* t = var->sqldata; t < var->sqldata + var->sqllen; t++)
2004-06-06 07:25:32 +02:00
{
2001-05-23 15:26:42 +02:00
if (List) {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%02X", (unsigned int) (UCHAR) *t);
2001-05-23 15:26:42 +02:00
}
else {
sprintf(d, "%02X", (unsigned int) (UCHAR) *t);
2001-05-23 15:26:42 +02:00
strcat(p, d);
}
}
if (List)
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
else
strcat(p, " ");
}
else {
2004-06-06 07:25:32 +02:00
const ISC_QUAD* blobid;
TEXT blobbuf[30];
TEXT* string;
2008-01-16 08:23:29 +01:00
tm times;
2004-06-06 07:25:32 +02:00
2001-05-23 15:26:42 +02:00
switch (dtype) {
case SQL_ARRAY:
// Print blob-ids only here
2001-05-23 15:26:42 +02:00
2004-06-06 07:25:32 +02:00
blobid = (ISC_QUAD*) var->sqldata;
sprintf(blobbuf, "%"xLONGFORMAT":%"xLONGFORMAT, blobid->gds_quad_high,
blobid->gds_quad_low);
2001-05-23 15:26:42 +02:00
sprintf(p, "%17s ", blobbuf);
break;
2008-01-16 08:23:29 +01:00
2001-05-23 15:26:42 +02:00
case SQL_BLOB:
// Print blob-ids only here
2001-05-23 15:26:42 +02:00
2004-06-06 07:25:32 +02:00
blobid = (ISC_QUAD*) var->sqldata;
sprintf(blobbuf, "%"xLONGFORMAT":%"xLONGFORMAT, blobid->gds_quad_high,
blobid->gds_quad_low);
2001-05-23 15:26:42 +02:00
sprintf(p, "%17s ", blobbuf);
if (List) {
isqlGlob.printf("%s%s", blobbuf, NEWLINE);
dtype = ISQL_print_item_blob(isqlGlob.Out, var, M__trans, Doblob);
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
}
break;
case SQL_SHORT:
case SQL_LONG:
case SQL_INT64:
{
TEXT str_buf[30];
SINT64 value;
if (dtype == SQL_SHORT)
2008-01-16 08:23:29 +01:00
value = (SINT64) (*(SSHORT*) var->sqldata);
2001-05-23 15:26:42 +02:00
else if (dtype == SQL_LONG)
2008-01-16 08:23:29 +01:00
value = (SINT64) (*(SLONG*) var->sqldata);
2001-05-23 15:26:42 +02:00
else if (dtype == SQL_INT64)
2008-01-16 08:23:29 +01:00
value = *(SINT64*) var->sqldata;
2001-05-23 15:26:42 +02:00
print_item_numeric(value, length, dscale, str_buf);
sprintf(p, "%s ", str_buf);
if (List) {
// Added 1999-03-23 to left-justify in LIST ON mode
2001-05-23 15:26:42 +02:00
{
TEXT* src = str_buf;
TEXT* dest = str_buf;
2001-05-23 15:26:42 +02:00
while (' ' == *src)
src++;
2006-09-07 09:56:25 +02:00
// This is pretty much the same thing as a strcpy,
// but strcpy is not guaranteed to do the right thing
// when the source and destination overlap. :-(
2001-05-23 15:26:42 +02:00
if (src != dest)
2008-01-16 08:23:29 +01:00
while (0 != (*dest++ = *src++)); // empty
2001-05-23 15:26:42 +02:00
}
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%s%s", str_buf, NEWLINE);
2001-05-23 15:26:42 +02:00
}
}
2008-01-16 08:23:29 +01:00
break;
2001-05-23 15:26:42 +02:00
#ifdef NATIVE_QUAD
case SQL_QUAD:
if (dscale) {
// Handle floating scale and precision
2001-05-23 15:26:42 +02:00
2008-01-16 08:23:29 +01:00
double numeric = *(SQUAD*) var->sqldata;
2004-06-06 07:25:32 +02:00
const double exponent = -dscale;
2001-05-23 15:26:42 +02:00
numeric = numeric / pow(10.0, exponent);
2008-01-16 08:23:29 +01:00
sprintf(p, "%*.*f ", length, -dscale, numeric);
2001-05-23 15:26:42 +02:00
if (List) {
2008-01-16 08:23:29 +01:00
isqlGlob.printf("%.*f%s", -dscale, numeric, NEWLINE);
2001-05-23 15:26:42 +02:00
}
}
else {
2008-01-16 08:23:29 +01:00
sprintf(p, "%*ld ", length, *(SQUAD*) var->sqldata);
2001-05-23 15:26:42 +02:00
if (List) {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%ld%s",
*(SQUAD*) var->sqldata, NEWLINE);
2001-05-23 15:26:42 +02:00
}
}
break;
#endif
case SQL_FLOAT:
2008-01-16 08:23:29 +01:00
{
//
// BRS 08 Aug 2003
// MSVC6 has a bug in the g format when used with # and display
// one digit more than the specified precision when the value is 0
// The bug appears in TCS DSQL_DOMAIN_12 and 13
//
const double value = (double) *(float*) var->sqldata;
#if (defined(_MSC_VER) && (_MSC_VER <= 1200)) || defined(MINGW)
2008-01-16 08:23:29 +01:00
if (value == 0) {
sprintf(p, "% #*.*g ", length,
(int) MIN(8, (length - 6)) - 1,
value);
if (List) {
isqlGlob.printf("%.*g%s", FLOAT_LEN - 6 -1,
value, NEWLINE);
}
}
2008-01-16 08:23:29 +01:00
else {
sprintf(p, "% #*.*g ", length,
(int) MIN(8, (length - 6)),
value);
if (List) {
isqlGlob.printf("%.*g%s", FLOAT_LEN - 6,
value, NEWLINE);
}
}
#else
sprintf(p, "% #*.*g ", length, (int) MIN(8, (length - 6)),
value);
if (List) {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%.*g%s", FLOAT_LEN - 6,
2008-01-16 08:23:29 +01:00
value, NEWLINE);
}
#endif
2008-01-16 08:23:29 +01:00
}
2001-05-23 15:26:42 +02:00
break;
2008-01-16 08:23:29 +01:00
2001-05-23 15:26:42 +02:00
case SQL_DOUBLE:
{
2008-01-16 08:23:29 +01:00
const double value = *(double*) var->sqldata;
// Don't let numeric/decimal doubles overflow print length
// Special handling for 0 -- don't test log for length
int rounded = 0;
if (dscale && (!value || (rounded = ceil(fabs(log10(fabs(value))))) < length - 10))
{
int precision = 0;
2008-01-25 12:28:50 +01:00
// CVC: Test values are
// select -2.488355210669293e+01 from rdb$database;
// select +2.488355210669293e+01 from rdb$database;
// select +2.488355210669293e-01 from rdb$database;
// select -2.488355210669293e-01 from rdb$database;
// and sprintf should return length + 1.
// See http://tracker.firebirdsql.org/browse/CORE-1363
// The only way to enter this code is with a literal value (see above).
// Taking SQL_DOUBLE from table fields or expressions will
// yield dscale being zero.
// This means that dscale is not what Borland expected; it's simply
// the length of the incoming literal.
if (value > 1)
precision = length - rounded - 1; // nnn.nnn
else if (value >= 0)
precision = length - 2; // 0.nnn
else if (value >= -1)
precision = length - 3; // -0.nnn
else // -nnn.nnn
precision = length - rounded - 2;
2008-01-25 12:28:50 +01:00
// Take into account old database containing negative scales for double
if (dscale < 0 && precision > -dscale)
2008-01-25 12:28:50 +01:00
{
precision = -dscale;
}
2008-01-16 08:23:29 +01:00
sprintf(p, "%*.*f ", length, precision, value);
if (List) {
2008-01-16 08:23:29 +01:00
isqlGlob.printf("%.*f%s",
precision, //dscale,
value, NEWLINE);
}
}
else {
2008-01-16 08:23:29 +01:00
#if (defined(_MSC_VER) && (_MSC_VER <= 1200)) || defined(MINGW)
if (value == 0) {
sprintf(p, "% #*.*g ", length,
(int) MIN(16, (length - 7)) - 1,
value);
if (List) {
isqlGlob.printf("%#.*g%s",
DOUBLE_LEN - 7 - 1,
value, NEWLINE);
}
}
else {
sprintf(p, "% #*.*g ", length,
(int) MIN(16, (length - 7)),
value);
if (List) {
isqlGlob.printf("%#.*g%s",
DOUBLE_LEN - 7,
value, NEWLINE);
}
}
#else
sprintf(p, "% #*.*g ", length,
(int) MIN(16, (length - 7)),
value);
if (List) {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%#.*g%s",
DOUBLE_LEN - 7,
2008-01-16 08:23:29 +01:00
value, NEWLINE);
}
#endif
2008-01-16 08:23:29 +01:00
}
2001-05-23 15:26:42 +02:00
}
break;
case SQL_TEXT:
string = var->sqldata;
string[var->sqllen] = '\0';
// See if it is character set OCTETS
2001-05-23 15:26:42 +02:00
if (var->sqlsubtype == 1) {
const SLONG hex_len = 2 * var->sqllen;
TEXT* buff2 = (TEXT*) ISQL_ALLOC(hex_len + 1);
// Convert the string to hex digits
for (USHORT i = 0; i < var->sqllen; i++) {
sprintf(&buff2[i * 2], "%02X", (UCHAR)string[i]);
2001-05-23 15:26:42 +02:00
}
buff2[hex_len] = 0;
2001-05-23 15:26:42 +02:00
if (List) {
isqlGlob.printf("%s%s", buff2, NEWLINE);
2001-05-23 15:26:42 +02:00
}
else
2008-01-16 08:23:29 +01:00
sprintf(p, "%-*s ", length, buff2);
2001-05-23 15:26:42 +02:00
ISQL_FREE(buff2);
}
else if (List) {
isqlGlob.printf("%s%s", string, NEWLINE);
2001-05-23 15:26:42 +02:00
}
else {
// Truncate if necessary
2001-05-23 15:26:42 +02:00
if (length < var->sqllen)
string[length] = '\0';
2008-01-16 08:23:29 +01:00
sprintf(p, "%-*s ", length, var->sqldata);
2001-05-23 15:26:42 +02:00
}
break;
case SQL_TIMESTAMP:
2008-01-16 08:23:29 +01:00
isc_decode_timestamp((ISC_TIMESTAMP*) var->sqldata, &times);
2001-05-23 15:26:42 +02:00
if (isqlGlob.SQL_dialect > SQL_DIALECT_V5) {
2003-04-08 12:38:59 +02:00
sprintf(d, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d.%4.4"ULONGFORMAT,
2001-05-23 15:26:42 +02:00
times.tm_year + 1900, (times.tm_mon + 1),
times.tm_mday, times.tm_hour, times.tm_min,
times.tm_sec,
((ULONG*) var->sqldata)[1] % ISC_TIME_SECONDS_PRECISION);
2001-05-23 15:26:42 +02:00
}
else {
if (Time_display)
2003-04-08 12:38:59 +02:00
sprintf(d, "%2d-%s-%4d %2.2d:%2.2d:%2.2d.%4.4"ULONGFORMAT,
2001-05-23 15:26:42 +02:00
times.tm_mday, alpha_months[times.tm_mon],
times.tm_year + 1900, times.tm_hour, times.tm_min,
times.tm_sec,
((ULONG*) var->sqldata)[1] % ISC_TIME_SECONDS_PRECISION);
2001-05-23 15:26:42 +02:00
else
sprintf(d, "%2d-%s-%4d", times.tm_mday,
alpha_months[times.tm_mon], times.tm_year + 1900);
}
2008-01-16 08:23:29 +01:00
sprintf(p, "%-*s ", length, d);
2001-05-23 15:26:42 +02:00
if (List) {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%s%s", d, NEWLINE);
2001-05-23 15:26:42 +02:00
}
break;
case SQL_TYPE_TIME:
2008-01-16 08:23:29 +01:00
isc_decode_sql_time((ISC_TIME*) var->sqldata, &times);
sprintf(d, "%2.2d:%2.2d:%2.2d.%4.4"ULONGFORMAT,
times.tm_hour, times.tm_min, times.tm_sec,
(*(ISC_TIME*) var->sqldata) % ISC_TIME_SECONDS_PRECISION);
sprintf(p, "%-*s ", length, d);
if (List) {
isqlGlob.printf("%s%s", d, NEWLINE);
2001-05-23 15:26:42 +02:00
}
2008-01-16 08:23:29 +01:00
break;
2001-05-23 15:26:42 +02:00
case SQL_TYPE_DATE:
2008-01-16 08:23:29 +01:00
isc_decode_sql_date((ISC_DATE*) var->sqldata, &times);
sprintf(d, "%4.4d-%2.2d-%2.2d", times.tm_year + 1900,
(times.tm_mon + 1), times.tm_mday);
2001-05-23 15:26:42 +02:00
2008-01-16 08:23:29 +01:00
sprintf(p, "%-*s ", length, d);
if (List) {
isqlGlob.printf("%s%s", d, NEWLINE);
2001-05-23 15:26:42 +02:00
}
break;
case SQL_VARYING:
{
vary* avary = (vary*) var->sqldata;
2001-05-23 15:26:42 +02:00
// Note: we allocated the dataspace 1 byte larger already
avary->vary_string[avary->vary_length] = 0;
2001-05-23 15:26:42 +02:00
// If CHARACTER SET OCTETS, print two hex digits per octet
if (var->sqlsubtype == 1)
{
const SLONG hex_len = 2 * avary->vary_length;
char* buff2 = static_cast<char*>(ISQL_ALLOC(hex_len + 1));
char* bp = buff2;
for (USHORT i = 0; i < avary->vary_length; i++, bp += 2)
{
sprintf(bp, "%02X",
static_cast<UCHAR>(avary->vary_string[i]));
}
buff2[hex_len] = '\0'; // there is an extra byte for terminator
if (List)
{
isqlGlob.printf("%s%s", buff2, NEWLINE);
}
else
{
// Truncate if necessary
2008-01-16 08:23:29 +01:00
sprintf(p, "%-*.*s ", length,
static_cast<int>(MIN(length, hex_len)), buff2);
}
ISQL_FREE(buff2);
}
else if (List) {
isqlGlob.printf("%s%s", avary->vary_string, NEWLINE);
}
else {
// Truncate if necessary
if (length < avary->vary_length)
avary->vary_length = length;
2008-01-16 08:23:29 +01:00
sprintf(p, "%-*.*s ", length, (int) avary->vary_length,
avary->vary_string);
}
break;
}
2001-05-23 15:26:42 +02:00
default:
sprintf(d, "Unknown type: %d", dtype);
2008-01-16 08:23:29 +01:00
sprintf(p, "%-*s ", length, d);
2001-05-23 15:26:42 +02:00
if (List) {
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%s%s", d, NEWLINE);
2001-05-23 15:26:42 +02:00
}
break;
}
}
while (*p)
++p;
*s = p;
return dtype;
2001-05-23 15:26:42 +02:00
}
processing_state ISQL_print_item_blob(FILE* fp,
const XSQLVAR* var,
FB_API_HANDLE trans,
int subtype)
2001-05-23 15:26:42 +02:00
{
/******************************************
2001-05-23 15:26:42 +02:00
*
* I S Q L _ p r i n t _ i t e m _ b l o b
2001-05-23 15:26:42 +02:00
*
******************************************
2001-05-23 15:26:42 +02:00
*
* Functional description
* Print a User Selected BLOB field (as oppposed to
2001-05-23 15:26:42 +02:00
* any BLOB selected in a show or extract command)
*
**************************************/
TEXT msg[MSG_LENGTH];
2001-05-23 15:26:42 +02:00
ISC_QUAD* blobid = (ISC_QUAD*) var->sqldata;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Don't bother with null blobs
2001-05-23 15:26:42 +02:00
if (UserBlob::blobIsNull(*blobid))
2001-05-23 15:26:42 +02:00
return CONT;
if ((var->sqlsubtype != subtype) && (subtype != ALL_BLOBS)) {
ISQL_msg_get(BLOB_SUBTYPE, msg, SafeArg() << subtype << var->sqlsubtype);
2004-03-07 08:58:55 +01:00
// Blob display set to subtype %d. This blob: subtype = %d\n
2001-05-23 15:26:42 +02:00
ISQL_printf(fp, msg);
return CONT;
}
USHORT bpb_length = 0;
const UCHAR* bpb = NULL;
UCHAR bpb_buffer[64];
ISC_BLOB_DESC from_desc;
const int blob_subtype = var->sqlsubtype;
if (blob_subtype == isc_blob_text) {
2007-07-01 04:40:15 +02:00
// ASF: Since ODS11.1, BLOBs are automatically transliterated to the client charset.
2007-06-28 17:30:10 +02:00
if (isqlGlob.major_ods < ODS_VERSION11 ||
(isqlGlob.major_ods == ODS_VERSION11 && isqlGlob.minor_ods == 0))
{
// Lookup the remaining descriptor information for the BLOB field,
// most specifically we're interested in the Character Set so
// we can set up a BPB that requests character set transliteration
// CVC: Adriano changed trans to D__trans with the following comment:
// Fix a bug that occur when a BLOB column is added after the start
// of the DML transaction.
// ASF: But use M__trans if D__trans is not started;
if (!var->relname[0] ||
isc_blob_lookup_desc(isc_status, &DB, (D__trans ? &D__trans : &M__trans),
(const UCHAR*) var->relname, (const UCHAR*) var->sqlname,
&from_desc, NULL) == 0)
{
if (!var->relname[0])
{
from_desc.blob_desc_subtype = var->sqlsubtype;
from_desc.blob_desc_charset = var->sqlscale;
}
2006-08-08 13:23:47 +02:00
ISC_BLOB_DESC to_desc;
isc_blob_default_desc(&to_desc, (const UCHAR*) var->relname, (const UCHAR*) var->sqlname);
if (!isc_blob_gen_bpb
(isc_status, &to_desc, &from_desc, sizeof(bpb_buffer),
bpb_buffer, &bpb_length))
{
bpb = bpb_buffer;
}
}
2001-05-23 15:26:42 +02:00
}
}
else if (blob_subtype > isc_blob_text && blob_subtype < isc_blob_max_predefined_subtype)
{
bpb = predefined_blob_subtype_bpb;
bpb_length = sizeof(predefined_blob_subtype_bpb);
set_bpb_for_translation(blob_subtype);
2001-05-23 15:26:42 +02:00
}
UserBlob blob(isc_status);
if (!blob.open(DB, trans, *blobid, bpb_length, bpb))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return ps_ERR;
2001-05-23 15:26:42 +02:00
}
TEXT buffer[BUFFER_LENGTH512];
size_t length;
while (blob.getSegment(sizeof(buffer) - 1, buffer, length))
{
// Special displays for blr or acl subtypes
2001-05-23 15:26:42 +02:00
if (blob_subtype > isc_blob_text && blob_subtype < isc_blob_max_predefined_subtype)
{
2001-05-23 15:26:42 +02:00
buffer[length--] = 0;
for (char* b = buffer + length; b >= buffer;) {
2001-05-23 15:26:42 +02:00
if (*b == '\n' || *b == '\t' || *b == BLANK)
*b-- = 0;
else
break;
}
2005-12-03 08:01:16 +01:00
ISQL_printf2(fp, "%s %s%s", TAB_AS_SPACES, buffer, NEWLINE);
2001-05-23 15:26:42 +02:00
}
else {
buffer[length] = 0;
ISQL_printf(fp, buffer);
2001-05-23 15:26:42 +02:00
}
}
if (isc_status[1] && isc_status[1] != isc_segstr_eof)
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return ps_ERR;
2001-05-23 15:26:42 +02:00
}
return CONT;
}
static void print_item_numeric(SINT64 value,
2008-01-16 08:23:29 +01:00
int length,
int scale,
TEXT* buf)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* p r i n t _ i t e m _ n u m e r i c
*
**************************************
*
* Functional description
* Print a INT64 value into a buffer by accomodating
* decimal point '.' for scale notation
*
**************************************/
bool all_digits_used = false;
2003-09-08 03:45:09 +02:00
bool neg = (value < 0);
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Handle special case of no negative scale, no '.' required!
2001-05-23 15:26:42 +02:00
if (scale >= 0) {
if (scale > 0)
value *= (SINT64) pow(10.0, (double) scale);
2006-05-04 02:02:58 +02:00
sprintf(buf, "%*" SQUADFORMAT, length, value);
2001-05-23 15:26:42 +02:00
return;
}
2005-04-25 07:23:57 +02:00
// Use one less space than allowed, to leave room for '.'
2001-05-23 15:26:42 +02:00
length--;
2006-05-04 02:02:58 +02:00
sprintf(buf, "%*" SQUADFORMAT, length, value);
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// start from LSByte towards MSByte
SSHORT from = length - 1;
SSHORT to = length;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// make space for decimal '.' point in buffer
2001-05-23 15:26:42 +02:00
buf[to + 1] = '\0';
for (; from >= 0 && DIGIT(buf[from]) && scale; from--, to--, ++scale)
buf[to] = buf[from];
2005-04-25 07:23:57 +02:00
// Check whether we need a '0' (zero) in front of the '.' point
2001-05-23 15:26:42 +02:00
if (!DIGIT(buf[from]))
all_digits_used = true;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Insert new '0's to satisfy larger scale than input digits
// For e.g: 12345 with scale -7 would be .0012345
2001-05-23 15:26:42 +02:00
if (from > 0 && scale)
do {
buf[to--] = '0';
} while (++scale != 0);
2005-04-25 07:23:57 +02:00
// Insert '.' decimal point, and if required, '-' and '0'
2001-05-23 15:26:42 +02:00
buf[to--] = '.';
2003-09-09 13:03:37 +02:00
if (all_digits_used) {
2001-05-23 15:26:42 +02:00
buf[to--] = '0';
2003-09-09 13:03:37 +02:00
if (neg)
2001-05-23 15:26:42 +02:00
buf[to--] = '-';
}
}
static processing_state print_line(XSQLDA* sqlda,
2008-01-16 08:23:29 +01:00
const int pad[],
TEXT line[])
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* p r i n t _ l i n e
*
**************************************
*
* Functional description
* Print a line of SQL variables.
*
* Args: sqlda, an XSQLDA
* pad = an array of the print lengths of all the columns
* line = pointer to the line buffer.
2001-05-23 15:26:42 +02:00
*
**************************************/
const int maxblob = 20;
XSQLVAR* varlist[maxblob]; // No more than 20 blobs per line
int varnum = 0;
{ // scope
TEXT* p = line;
int i = 0;
XSQLVAR* var = sqlda->sqlvar;
for (const XSQLVAR* const end = var + sqlda->sqld; var < end;
var++, i++)
{
if (!Interrupt_flag && !Abort_flag) {
// Save all the blob vars and print them at the end
// CVC: If varnum reaches 20, we must print an error instead of crashing.
const int rc = print_item(&p, var, pad[i]);
if (rc == SQL_BLOB && varnum < maxblob)
varlist[varnum++] = var;
2003-12-03 09:19:24 +01:00
}
}
*p = 0;
} // scope
2001-05-23 15:26:42 +02:00
if (List) {
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
return (CONT);
}
isqlGlob.printf("%s%s", line, NEWLINE);
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// If blobdisplay is not wanted, set varnum back to -1
2001-05-23 15:26:42 +02:00
if (Doblob == NO_BLOBS)
varnum = 0;
else if (varnum >= maxblob)
{
TEXT msg[MSG_LENGTH];
ISQL_msg_get(ONLY_FIRST_BLOBS, msg, SafeArg() << maxblob);
isqlGlob.printf("%s%s", msg, NEWLINE);
}
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// If there were Blobs to print, print them passing the blobid
2001-05-23 15:26:42 +02:00
for (int i = 0; i < varnum; i++) {
const XSQLVAR* var = varlist[i];
if (!(var->sqltype & 1) || *var->sqlind == 0) {
// Print blob title
2001-05-23 15:26:42 +02:00
2005-05-24 06:42:01 +02:00
isqlGlob.printf(
2001-05-23 15:26:42 +02:00
"==============================================================================%s",
NEWLINE);
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%s: %s", var->aliasname, NEWLINE);
if (ISQL_print_item_blob(isqlGlob.Out, var, M__trans, Doblob) != CONT)
return (ps_ERR);
2005-05-24 06:42:01 +02:00
isqlGlob.printf(
2001-05-23 15:26:42 +02:00
"%s==============================================================================%s",
NEWLINE, NEWLINE);
}
}
return CONT;
}
// *********************************
// p r i n t _ p e r f o r m a n c e
// *********************************
// As the name implies, show performance as extracted from the API routines.
static void print_performance(perf* perf_before)
{
// Translation of report strings. Do not remove "static" modifier.
static bool have_report = false;
static TEXT report_1[MSG_LENGTH + MSG_LENGTH];
perf perf_after;
perf_get_info(&DB, &perf_after);
if (!have_report) {
ISQL_msg_get(REPORT1, report_1);
// Current memory = !c\nDelta memory = !d\nMax memory = !x\nElapsed time= !e sec\n
ISQL_msg_get(REPORT2, &report_1[strlen(report_1)]);
2006-09-07 09:56:25 +02:00
// For GUI_TOOLS: Buffers = !b\nReads = !r\nWrites = !w\nFetches
// = !f\n
// Else: Cpu = !u sec\nBuffers = !b\nReads = !r\nWrites = !w\nFetch
// es = !f\n
have_report = true;
}
SCHAR statistics[256];
perf_format(perf_before, &perf_after, report_1, statistics, NULL);
2005-12-03 08:01:16 +01:00
ISQL_printf2(Diag, "%s%s", statistics, NEWLINE);
2001-05-23 15:26:42 +02:00
}
// *********************************
// p r i n t _ s q l d a _ i n p u t
// *********************************
// Show the contents of the input sqlda, used typically for parameters.
static processing_state print_sqlda_input(const bool can_have_input_parameters)
{
// Describe the input
XSQLDA* input_sqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(10)));
memset(input_sqlda, 0, XSQLDA_LENGTH(10));
input_sqlda->version = SQLDA_VERSION1;
input_sqlda->sqld = input_sqlda->sqln = 10;
if (isc_dsql_describe_bind(isc_status, &global_Stmt, isqlGlob.SQL_dialect, input_sqlda)) {
ISQL_FREE(input_sqlda);
ISQL_errmsg(isc_status);
return ps_ERR;
}
else if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
// Reallocate if there's LOTS of input
if (input_sqlda->sqld > input_sqlda->sqln) {
USHORT n_columns = input_sqlda->sqld;
ISQL_FREE(input_sqlda);
input_sqlda =
(XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(n_columns)));
memset(input_sqlda, 0, XSQLDA_LENGTH(n_columns));
input_sqlda->version = SQLDA_VERSION1;
input_sqlda->sqld = input_sqlda->sqln = n_columns;
if (isc_dsql_describe_bind(isc_status, &global_Stmt, isqlGlob.SQL_dialect,
input_sqlda))
{
ISQL_FREE(input_sqlda);
ISQL_errmsg(isc_status);
return ps_ERR;
}
else if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
}
if (can_have_input_parameters)
{
isqlGlob.printf(
//"\nINPUT SQLDA version: %d sqldaid: %s sqldabc: %ld sqln: %d sqld: %d\n",
"\nINPUT SQLDA version: %d sqln: %d sqld: %d\n",
//input_sqlda->version, input_sqlda->sqldaid,
//input_sqlda->sqldabc, input_sqlda->sqln,
input_sqlda->version, input_sqlda->sqln,
input_sqlda->sqld);
int i = 0;
const XSQLVAR* var = input_sqlda->sqlvar;
for (const XSQLVAR* const end = var + input_sqlda->sqld; var < end;
var++, i++)
{
isqlGlob.printf(
"%02d: sqltype: %d %s %s sqlscale: %d sqlsubtype: %d sqllen: %d\n",
i + 1, var->sqltype, sqltype_to_string(var->sqltype),
(var->sqltype & 1) ? "Nullable" : " ",
var->sqlscale, var->sqlsubtype, var->sqllen);
isqlGlob.printf(" : name: (%d)%*s alias: (%d)%*s\n",
var->sqlname_length, var->sqlname_length,
var->sqlname, var->aliasname_length,
var->aliasname_length, var->aliasname);
isqlGlob.printf(" : table: (%d)%*s owner: (%d)%*s\n",
var->relname_length, var->relname_length,
var->relname, var->ownname_length,
var->ownname_length, var->ownname);
}
}
ISQL_FREE(input_sqlda);
return CONT;
}
// ***********************************
// p r i n t _ s q l d a _ o u t p u t
// ***********************************
// Show the contents of the output sqlda, that's the result of SELECT from
// both tables and procedures and execution of procedures that return output.
static void print_sqlda_output(const XSQLDA& sqlda)
{
isqlGlob.printf(
//"\nOUTPUT SQLDA version: %d sqldaid: %s sqldabc: %ld sqln: %d sqld: %d\n",
"\nOUTPUT SQLDA version: %d sqln: %d sqld: %d\n",
//sqlda.version, sqlda.sqldaid, sqlda.sqldabc, sqlda.sqln,
sqlda.version, sqlda.sqln,
sqlda.sqld);
int i = 0;
const XSQLVAR* var = sqlda.sqlvar;
for (const XSQLVAR* const end = var + sqlda.sqld; var < end;
var++, i++)
{
isqlGlob.printf(
"%02d: sqltype: %d %s %s sqlscale: %d sqlsubtype: %d sqllen: %d\n",
i + 1, var->sqltype, sqltype_to_string(var->sqltype),
(var->sqltype & 1) ? "Nullable" : " ",
var->sqlscale, var->sqlsubtype, var->sqllen);
isqlGlob.printf(" : name: (%d)%*s alias: (%d)%*s\n",
var->sqlname_length, var->sqlname_length, var->sqlname,
var->aliasname_length, var->aliasname_length,
var->aliasname);
isqlGlob.printf(" : table: (%d)%*s owner: (%d)%*s\n",
var->relname_length, var->relname_length, var->relname,
var->ownname_length, var->ownname_length, var->ownname);
}
}
// ***************************
// p r o c e s s _ h e a d e r
// ***************************
// Write into buffers the information that will be printed as page header for
// statements that return data.
2008-01-16 08:23:29 +01:00
static void process_header(const XSQLDA* sqlda, const int pad[], TEXT header[], TEXT header2[])
{
// Create the column header : Left justify strings
TEXT* p = header;
TEXT* p2 = header2;
int i = 0;
const XSQLVAR* var = sqlda->sqlvar;
for (const XSQLVAR* const end = var + sqlda->sqld; var < end; var++, i++)
{
const SSHORT type = var->sqltype & ~1;
if (type == SQL_TEXT || type == SQL_VARYING)
2008-01-16 08:23:29 +01:00
sprintf(p, "%-*.*s ", pad[i], pad[i], var->aliasname);
else
2008-01-16 08:23:29 +01:00
sprintf(p, "%*s ", pad[i], var->aliasname);
// Separators need not go on forever no more than a line
size_t limit = strlen(p);
for (size_t j = 1; j < limit && j < 80; j++)
*p2++ = '=';
*p2++ = BLANK;
p += limit;
}
*p2 = '\0';
}
// ***********************
// p r o c e s s _ p l a n
// ***********************
// Retrieve and show the server's execution plan.
// We don't consider critical a failure to get the plan, so we don't return
// any result to the caller.
static void process_plan()
{
// Bug 7565: A plan larger than plan_buffer will not be displayed
// Bug 7565: Note also that the plan must fit into Print_Buffer
const SCHAR plan_info[] = { isc_info_sql_get_plan };
TEXT plan_buffer[PRINT_BUFFER_LENGTH];
memset(plan_buffer, 0, sizeof(plan_buffer));
if (isc_dsql_sql_info(isc_status, &global_Stmt, sizeof(plan_info), plan_info,
sizeof(plan_buffer), plan_buffer))
{
ISQL_errmsg(isc_status);
}
2005-12-03 08:01:16 +01:00
else if (plan_buffer[0] == isc_info_sql_get_plan)
{
const int l =
gds__vax_integer(reinterpret_cast<const UCHAR*>(plan_buffer) + 1, 2);
2005-12-03 08:01:16 +01:00
ISQL_printf2(Diag, "%.*s%s", l, plan_buffer + 3, NEWLINE);
}
}
// ***************************************
// p r o c e s s _ r e c o r d _ c o u n t
// ***************************************
// Get number of records affected for updates, deletions and insertions.
// Return -1 if the statement is not one of those or if there's an error
// retrieving the information from the server.
2008-01-16 08:23:29 +01:00
static SLONG process_record_count(const int statement_type)
{
UCHAR count_type = 0;
// Skip selects, better to count records incoming later
switch (statement_type)
{
case isc_info_sql_stmt_update:
count_type = isc_info_req_update_count;
break;
case isc_info_sql_stmt_delete:
count_type = isc_info_req_delete_count;
break;
case isc_info_sql_stmt_insert:
count_type = isc_info_req_insert_count;
break;
}
if (count_type) {
const SCHAR count_info[] = { isc_info_sql_records };
SCHAR count_buffer[33];
if (isc_dsql_sql_info(isc_status, &global_Stmt, sizeof(count_info),
count_info, sizeof(count_buffer),
count_buffer))
{
ISQL_errmsg(isc_status);
return -1;
}
const UCHAR* p = reinterpret_cast<const UCHAR*>(count_buffer + 3);
while (*p != isc_info_end)
{
const UCHAR count_is = *p++;
const SSHORT l = gds__vax_integer(p, 2);
p += 2;
const SLONG count = gds__vax_integer(p, l);
p += l;
if (count_is == count_type)
return count;
}
}
return -1;
}
// ***************************************
// p r o c e s s _ r e q u e s t _ t y p e
// ***************************************
// Retrieve the statement type according to the DSQL layer.
// A failure is indicated by returning zero.
static SSHORT process_request_type()
{
const SCHAR sqlda_info[] = { isc_info_sql_stmt_type };
SCHAR info_buffer[16];
if (isc_dsql_sql_info (isc_status, &global_Stmt, sizeof (sqlda_info), sqlda_info,
sizeof (info_buffer), info_buffer))
{
ISQL_errmsg (isc_status);
}
else {
if (info_buffer[0] == isc_info_sql_stmt_type) {
const UCHAR* b = reinterpret_cast<const UCHAR*>(info_buffer);
const SSHORT l = gds__vax_integer(b + 1, 2);
return (SSHORT) gds__vax_integer(b + 3, l);
}
//ISQL_msg_get(UNKNOWN_STATEMENT_TYPE, errmsg);
STDERROUT("Cannot determine statement type");
}
return 0;
}
// ***************************************
// p r o c e s s _ s q l d a _ b u f f e r
// ***************************************
// Generate the buffer for sqlda output and assign the null indicators already created.
// The caller should deallocate the returned buffer after using it.
static SLONG* process_sqlda_buffer(XSQLDA* const sqlda, SSHORT nullind[])
{
SLONG bufsize = 0;
SSHORT* nullp = nullind;
XSQLVAR* var = sqlda->sqlvar;
for (const XSQLVAR* const end = var + sqlda->sqld; var < end; var++) {
// Allocate enough space for all the sqldata and null pointers
const SSHORT type = var->sqltype & ~1;
USHORT alignment, data_length;
alignment = data_length = var->sqllen;
// special case db_key alignment which arrives as an SQL_TEXT
2005-04-25 07:23:57 +02:00
// Bug 8562: since DB_KEY arrives as SQL_TEXT, we need to account
// for the null character at the end of the string
if (!strncmp(var->sqlname, "DB_KEY", 6)) {
alignment = 8;
// Room for null string terminator
data_length++;
}
else
2005-04-25 07:23:57 +02:00
{
// Special alignment cases
2005-04-25 07:23:57 +02:00
if (type == SQL_TEXT) {
alignment = 1;
// Room for null string terminator
data_length++;
}
else if (type == SQL_VARYING) {
alignment = sizeof(SSHORT);
// Room for a null string terminator for display
data_length += sizeof(SSHORT) + 1;
}
}
bufsize = FB_ALIGN(bufsize, alignment) + data_length;
var->sqlind = nullp++;
}
// Buffer for reading data from the fetch
if (bufsize) {
SLONG* buffer = (SLONG*) ISQL_ALLOC ((SLONG) bufsize);
memset(buffer, 0, (size_t) bufsize);
return buffer;
}
return 0;
}
// *****************************************
// p r o c e s s _ s q l d a _ d i s p l a y
// *****************************************
// Calculate individual column widths and return total line width.
// Assign previously allocated buffer in pieces to every XSQLVAR.
2008-01-16 08:23:29 +01:00
static SLONG process_sqlda_display(XSQLDA* const sqlda, SLONG buffer[], int pad[])
{
2008-01-16 08:23:29 +01:00
int linelength = 0;
SLONG offset = 0;
int i = 0;
XSQLVAR* var = sqlda->sqlvar;
2008-01-16 08:23:29 +01:00
for (const XSQLVAR* const end = var + sqlda->sqld; var < end; ++var, ++i)
{
2005-04-25 07:23:57 +02:00
// Record the length of name and var, then convert to print
// length, later to be stored in pad array
USHORT data_length, disp_length, alignment;
data_length = disp_length = alignment = var->sqllen;
SSHORT namelength = var->aliasname_length;
2005-04-25 07:23:57 +02:00
// Minimum display length should not be less than that needed
// for displaying null
if (namelength < NULL_DISP_LEN)
namelength = NULL_DISP_LEN;
const SSHORT type = var->sqltype & ~1;
switch (type) {
case SQL_BLOB:
case SQL_ARRAY:
// enough room for the blob id to print
disp_length = 17;
break;
case SQL_TIMESTAMP:
if (Time_display || isqlGlob.SQL_dialect > SQL_DIALECT_V5)
disp_length = DATETIME_LEN;
else
disp_length = DATE_ONLY_LEN;
break;
case SQL_TYPE_TIME:
disp_length = TIME_ONLY_LEN;
break;
case SQL_TYPE_DATE:
disp_length = DATE_ONLY_LEN;
break;
case SQL_FLOAT:
disp_length = FLOAT_LEN;
break;
case SQL_DOUBLE:
disp_length = DOUBLE_LEN;
break;
case SQL_TEXT:
alignment = 1;
data_length++;
// OCTETS data is displayed in hex
if (var->sqlsubtype == 1)
disp_length = 2 * var->sqllen;
break;
case SQL_VARYING:
data_length += sizeof(USHORT) + 1;
alignment = sizeof(USHORT);
// OCTETS data is displayed in hex
if (var->sqlsubtype == 1)
disp_length = 2 * var->sqllen;
break;
case SQL_SHORT:
disp_length = SHORT_LEN;
break;
case SQL_LONG:
disp_length = LONG_LEN;
break;
case SQL_INT64:
disp_length = INT64_LEN;
break;
#ifdef NATIVE_QUAD
case SQL_QUAD:
disp_length = QUAD_LEN;
break;
#endif
default:
disp_length = UNKNOWN_LEN;
break;
}
// special case db_key alignment which arrives as an SQL_TEXT
if (!strncmp(var->sqlname, "DB_KEY", 6)) {
alignment = 8;
disp_length = 2 * var->sqllen;
}
// This is the print width of each column
pad[i] = (disp_length > namelength ? disp_length : namelength);
// Is there a collist entry, then use that width, but only for text
if (type == SQL_TEXT || type == SQL_VARYING)
{
2008-01-16 08:23:29 +01:00
if (!global_Cols.find(var->aliasname, &pad[i]) && global_Col_default)
pad[i] = global_Col_default;
}
2008-01-16 08:23:29 +01:00
// The total line length
linelength += pad[i] + 1;
// Allocate space in buffer for data
offset = FB_ALIGN(offset, alignment);
var->sqldata = (SCHAR*) buffer + offset;
offset += data_length;
}
return linelength;
}
static int process_statement(const TEXT* string,
XSQLDA** sqldap)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* p r o c e s s _ s t a t e m e n t
*
**************************************
*
* Functional description
* Prepare and execute a dynamic SQL statement.
* This function uses the isc_dsql user functions rather
* than embedded dynamic statements. The user request
* is placed on transaction M__trans, while all
* background work is on the default gds_trans.
* This function now returns CONT (success) or ps_ERR.
2001-05-23 15:26:42 +02:00
**************************************/
processing_state ret = CONT;
2005-04-25 07:23:57 +02:00
// We received the address of the sqlda in case we realloc it
XSQLDA* sqlda = *sqldap;
2005-04-25 07:23:57 +02:00
// If somebody did a commit or rollback, we are out of a transaction
2001-05-23 15:26:42 +02:00
if (!M__trans)
2007-03-01 12:40:26 +01:00
{
if (isc_start_transaction(isc_status, &M__trans, 1, &DB, 0, NULL))
ISQL_errmsg(isc_status);
2007-03-01 12:40:26 +01:00
}
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// No need to start a default transaction unless there is no current one
2001-05-23 15:26:42 +02:00
if (Autocommit && !D__trans)
{
if (isc_start_transaction(isc_status, &D__trans, 1, &DB, 5, default_tpb))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
}
}
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// If statistics are requested, then reserve them here
2001-05-23 15:26:42 +02:00
perf perf_before;
2001-05-23 15:26:42 +02:00
if (Stats)
perf_get_info(&DB, &perf_before);
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Prepare the dynamic query stored in string.
// But put this on the DDL transaction to get maximum visibility of
// metadata.
2001-05-23 15:26:42 +02:00
2004-05-03 01:06:37 +02:00
FB_API_HANDLE prepare_trans;
2001-05-23 15:26:42 +02:00
if (Autocommit)
prepare_trans = D__trans;
else
prepare_trans = M__trans;
if (isc_dsql_prepare(isc_status, &prepare_trans, &global_Stmt, 0, string,
isqlGlob.SQL_dialect, sqlda))
{
if (isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION && Input_file) {
isqlGlob.printf("%s%s%s%s%s%s",
NEWLINE,
"**** Error preparing statement:",
NEWLINE,
NEWLINE,
string,
NEWLINE);
2001-05-23 15:26:42 +02:00
}
ISQL_errmsg(isc_status);
return ps_ERR;
2001-05-23 15:26:42 +02:00
}
// check for warnings
2001-05-23 15:26:42 +02:00
if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
// Find out what kind of statement this is
2008-01-16 08:23:29 +01:00
const int statement_type = process_request_type();
if (!statement_type)
return ps_ERR;
//#ifdef DEV_BUILD
if (Sqlda_display)
{
const bool can_have_input_parameters =
statement_type == isc_info_sql_stmt_select ||
statement_type == isc_info_sql_stmt_insert ||
statement_type == isc_info_sql_stmt_update ||
statement_type == isc_info_sql_stmt_delete ||
statement_type == isc_info_sql_stmt_exec_procedure ||
statement_type == isc_info_sql_stmt_select_for_upd;
if (print_sqlda_input(can_have_input_parameters) == ps_ERR)
return ps_ERR;
2001-05-23 15:26:42 +02:00
}
//#endif // DEV_BUILD
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// check for warnings
2001-05-23 15:26:42 +02:00
if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
const bool is_selectable =
statement_type == isc_info_sql_stmt_select ||
statement_type == isc_info_sql_stmt_select_for_upd ||
statement_type == isc_info_sql_stmt_exec_procedure ||
statement_type == isc_info_sql_stmt_get_segment;
// if PLAN is set, print out the plan now
2002-06-29 15:39:11 +02:00
2001-05-23 15:26:42 +02:00
if (Plan) {
process_plan();
if (Planonly && !is_selectable)
2001-05-23 15:26:42 +02:00
{
return ret; // do not execute
2001-05-23 15:26:42 +02:00
}
}
// If the statement isn't a select, execute it and be done
2001-05-23 15:26:42 +02:00
if (!is_selectable && !Planonly)
{
2002-06-29 15:39:11 +02:00
// If this is an autocommit, put the DDL stmt on a special trans
2001-05-23 15:26:42 +02:00
2007-03-01 12:40:26 +01:00
if (Autocommit && (statement_type == isc_info_sql_stmt_ddl))
{
if (isc_dsql_execute_immediate(isc_status, &DB, &D__trans, 0,
string, isqlGlob.SQL_dialect, NULL))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
ret = ps_ERR;
2001-05-23 15:26:42 +02:00
}
else if (isc_status[2] == isc_arg_warning)
{
// check for warnings
ISQL_warning(isc_status);
}
// CVC: DDL statements that aren't syntax errors are caught by DFW
// only at commit time, so we need to check here.
// AP: isc_status will be cleaned in commit_trans()
if (!commit_trans(&D__trans))
ret = ps_ERR;
2001-05-23 15:26:42 +02:00
// check for warnings from COMMIT
2001-05-23 15:26:42 +02:00
if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
return ret;
}
// Check to see if this is a SET TRANSACTION statement
2001-05-23 15:26:42 +02:00
if (statement_type == isc_info_sql_stmt_start_trans) {
// CVC: Starting a txn can fail, too. Let's check it, although I
// suspect isql will catch it in frontend() through get_statement(),
// so this place has little chance to be reached.
if (newtrans(string) == FAIL)
return ps_ERR;
2001-05-23 15:26:42 +02:00
return ret;
}
// This is a non-select DML statement or trans
2001-05-23 15:26:42 +02:00
if (isc_dsql_execute(isc_status, &M__trans, &global_Stmt, isqlGlob.SQL_dialect, NULL))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
// CVC: Make this conditional if it causes problems. For example
// if (BailOnError)
ret = ps_ERR;
}
2001-05-23 15:26:42 +02:00
// check for warnings
2001-05-23 15:26:42 +02:00
if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
// We are executing a commit or rollback, commit default trans
2001-05-23 15:26:42 +02:00
if ((statement_type == isc_info_sql_stmt_commit) ||
(statement_type == isc_info_sql_stmt_rollback))
{
// CVC: Commit may fail with AUTO-DDL off and DDL changes rejected by DFW.
if (D__trans && !commit_trans(&D__trans))
ret = ps_ERR;
}
2001-05-23 15:26:42 +02:00
// Print statistics report
2001-05-23 15:26:42 +02:00
if (Docount) {
const SLONG count = process_record_count(statement_type);
if (count >= 0)
{
TEXT rec_count_msg[MSG_LENGTH];
ISQL_msg_get(REC_COUNT, rec_count_msg, SafeArg() << count);
// Records affected: %ld
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%s%s", rec_count_msg, NEWLINE);
2001-05-23 15:26:42 +02:00
}
}
if (Stats)
print_performance(&perf_before);
return ret;
2001-05-23 15:26:42 +02:00
}
const SSHORT n_cols = sqlda->sqld;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// The query is bigger than the curent sqlda, so make more room
2001-05-23 15:26:42 +02:00
if (n_cols > sqlda->sqln) {
ISQL_FREE(sqlda);
sqlda = (XSQLDA*) ISQL_ALLOC((SLONG) (XSQLDA_LENGTH(n_cols)));
memset(sqlda, 0, XSQLDA_LENGTH(n_cols));
2001-05-23 15:26:42 +02:00
*sqldap = sqlda;
sqlda->version = SQLDA_VERSION1;
sqlda->sqld = sqlda->sqln = n_cols;
// We must re-describe the sqlda, no need to re-prepare
2001-05-23 15:26:42 +02:00
if (isc_dsql_describe(isc_status, &global_Stmt, isqlGlob.SQL_dialect, sqlda))
{
ISQL_errmsg(isc_status);
return ps_ERR;
}
2001-05-23 15:26:42 +02:00
}
//#ifdef DEV_BUILD
2006-09-07 09:56:25 +02:00
// To facilitate debugging, the option
// SET SQLDA_DISPLAY ON
// will activate code to display the SQLDA after each statement.
if (Sqlda_display)
print_sqlda_output(*sqlda);
//#endif // DEV_BUILD
if (Planonly)
return ret;
2001-05-23 15:26:42 +02:00
if (statement_type != isc_info_sql_stmt_exec_procedure) {
// Otherwise, open the cursor to start things up
2001-05-23 15:26:42 +02:00
#ifdef SCROLLABLE_CURSORS
// Declare the C cursor for what it is worth
// CVC: isql doesn't do positioned updates, so I assumed declaring a cursor
// may be needed if scrollable cursors are enabled in the engine.
2001-05-23 15:26:42 +02:00
if (isc_dsql_set_cursor_name(isc_status, &global_Stmt, "C ", 0)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return (ps_ERR);
2001-05-23 15:26:42 +02:00
}
// check for warnings
2001-05-23 15:26:42 +02:00
if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
#endif
2001-05-23 15:26:42 +02:00
if (isc_dsql_execute(isc_status, &M__trans, &global_Stmt, isqlGlob.SQL_dialect, NULL)) {
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
return (ps_ERR);
2001-05-23 15:26:42 +02:00
}
// check for warnings
2001-05-23 15:26:42 +02:00
if (isc_status[2] == isc_arg_warning)
ISQL_warning(isc_status);
}
SSHORT* nullind = NULL;
if (n_cols) {
nullind = (SSHORT*) ISQL_ALLOC((SLONG) (n_cols * sizeof(SSHORT)));
}
2002-06-29 15:39:11 +02:00
SLONG* buffer = process_sqlda_buffer(sqlda, nullind);
2001-05-23 15:26:42 +02:00
// Pad is an array of lengths to be passed to the print_item
2008-01-16 08:23:29 +01:00
int* pad = NULL;
if (sqlda->sqld) {
2008-01-16 08:23:29 +01:00
pad = (int*) ISQL_ALLOC ((SLONG) (sqlda->sqld * sizeof(int)));
}
2001-05-23 15:26:42 +02:00
// Calculate display width and add a few for line termination, et al
const SLONG linelength = process_sqlda_display(sqlda, buffer, pad) + 10;
2001-05-23 15:26:42 +02:00
// Allocate the print line, the header line and the separator
2001-05-23 15:26:42 +02:00
2008-01-16 08:23:29 +01:00
TEXT* line = (TEXT*) ISQL_ALLOC(linelength);
TEXT* header = 0;
TEXT* header2 = 0;
if (Heading)
{
2008-01-16 08:23:29 +01:00
header = (TEXT*) ISQL_ALLOC(linelength);
header2 = (TEXT*) ISQL_ALLOC(linelength);
*header = '\0';
*header2 = '\0';
process_header(sqlda, pad, header, header2);
2001-05-23 15:26:42 +02:00
}
// If this is an exec procedure, execute into the sqlda with one fetch only
2001-05-23 15:26:42 +02:00
if (statement_type == isc_info_sql_stmt_exec_procedure)
{
if (isc_dsql_execute2(isc_status, &M__trans, &global_Stmt,
isqlGlob.SQL_dialect, NULL, sqlda))
{
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
ret = ps_ERR;
2001-05-23 15:26:42 +02:00
}
else {
if (sqlda->sqld) { // do not output unnecessary white text
isqlGlob.printf(NEWLINE);
if (!List && Heading) {
isqlGlob.printf("%s%s%s%s",
header,
NEWLINE,
header2,
NEWLINE);
}
print_line(sqlda, pad, line);
isqlGlob.printf(NEWLINE);
}
}
}
2001-05-23 15:26:42 +02:00
else {
// Otherwise fetch and print records until EOF
#ifdef HAVE_TERMIOS_H
int out_fd = fileno(isqlGlob.Out);
if (isatty(out_fd)) {
struct termios tflags;
if (tcgetattr(out_fd, &tflags) == 0) {
tflags.c_lflag |= NOFLSH;
tcsetattr(out_fd, TCSANOW, &tflags);
}
}
#endif
const bool printHead = !List && Heading;
2008-01-16 08:23:29 +01:00
unsigned int lines;
for (lines = 0; !Interrupt_flag && !Abort_flag; ++lines) {
2001-05-23 15:26:42 +02:00
// Fetch the current cursor
2001-05-23 15:26:42 +02:00
#ifdef SCROLLABLE_CURSORS
if (Autofetch) {
#endif
2008-01-16 08:23:29 +01:00
if (isc_dsql_fetch(isc_status, &global_Stmt, isqlGlob.SQL_dialect, sqlda) == 100)
break;
2001-05-23 15:26:42 +02:00
#ifdef SCROLLABLE_CURSORS
}
else {
TEXT fetch_statement[BUFFER_LENGTH80];
2001-05-23 15:26:42 +02:00
2006-09-07 09:56:25 +02:00
// if not autofetching, sit and wait for user to input
// NEXT, PRIOR, FIRST, LAST, ABSOLUTE <n>, or RELATIVE <n>
2001-05-23 15:26:42 +02:00
const SSHORT fetch_ret =
get_statement(fetch_statement, sizeof(fetch_statement),
2001-05-23 15:26:42 +02:00
fetch_prompt);
if (fetch_ret == FETCH) {
if (isc_dsql_fetch2(isc_status, &global_Stmt, isqlGlob.SQL_dialect, sqlda,
fetch_direction, fetch_offset) == 100)
{
2001-05-23 15:26:42 +02:00
break;
}
}
else if (fetch_ret == BACKOUT || fetch_ret == EXIT) {
lines = 0;
break;
}
else {
TEXT errbuf[MSG_LENGTH];
ISQL_msg_get(CMD_ERR, errbuf, SafeArg() <<
"Expected NEXT, PRIOR, FIRST, LAST, ABSOLUTE <n>, RELATIVE <n>, or QUIT");
STDERROUT(errbuf);
2001-05-23 15:26:42 +02:00
}
}
#endif
2006-09-07 09:56:25 +02:00
// Print the header every Pagelength number of lines for
// command-line ISQL only. For WISQL, print the column
// headings only once.
2001-05-23 15:26:42 +02:00
if (printHead && (lines % Pagelength == 0)) {
isqlGlob.printf("%s%s%s%s%s",
NEWLINE,
header,
NEWLINE,
header2,
NEWLINE);
2001-05-23 15:26:42 +02:00
}
if (isc_status[1]) {
// CVC: Add a \n so the error message is not at the right of
// header but in its own line, if no row was output yet.
if (!lines && !printHead)
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
ISQL_errmsg(isc_status);
// CVC: It appears we can have an implicit cursor opened anyways.
isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_close);
2001-05-23 15:26:42 +02:00
if (nullind)
ISQL_FREE(nullind);
if (pad)
ISQL_FREE(pad);
if (buffer)
ISQL_FREE(buffer);
if (header)
ISQL_FREE(header);
if (header2)
ISQL_FREE(header2);
if (line)
ISQL_FREE(line);
return (ps_ERR);
2001-05-23 15:26:42 +02:00
}
if (!lines && !printHead)
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
ret = print_line(sqlda, pad, line);
}
if (lines)
isqlGlob.printf(NEWLINE);
2001-05-23 15:26:42 +02:00
// Record count printed here upon request
2001-05-23 15:26:42 +02:00
if (Docount) {
TEXT rec_count_msg[MSG_LENGTH];
ISQL_msg_get(REC_COUNT, rec_count_msg, SafeArg() << lines);
// Records affected: %ld
2005-05-24 06:42:01 +02:00
isqlGlob.printf("%s%s", rec_count_msg, NEWLINE);
2001-05-23 15:26:42 +02:00
}
}
// CVC: It appears we can have an implicit cursor opened anyways.
if (statement_type != isc_info_sql_stmt_exec_procedure) {
isc_dsql_free_statement(isc_status, &global_Stmt, DSQL_close);
}
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Statistics printed here upon request
2001-05-23 15:26:42 +02:00
if (Stats)
print_performance(&perf_before);
2001-05-23 15:26:42 +02:00
if (pad)
ISQL_FREE(pad);
if (buffer)
ISQL_FREE(buffer);
if (line)
ISQL_FREE(line);
if (header)
ISQL_FREE(header);
if (header2)
ISQL_FREE(header2);
if (nullind)
ISQL_FREE(nullind);
return (ret);
}
#ifdef WIN_NT
static BOOL CALLBACK query_abort(DWORD dwCtrlType)
#else
static void CLIB_ROUTINE query_abort(int)
#endif
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* q u e r y _ a b o r t
*
**************************************
*
* Functional description
* Signal handler for interrupting output of a query.
*
**************************************/
#ifdef WIN_NT
if (dwCtrlType != CTRL_C_EVENT)
return FALSE;
#endif
if (Interactive)
Interrupt_flag = true;
else
Abort_flag = true;
#ifdef WIN_NT
return TRUE;
#endif
2001-05-23 15:26:42 +02:00
}
2008-01-16 08:23:29 +01:00
// Detect if stdin is redirected, IE we aren't reading from the console.
static bool stdin_redirected()
{
#ifdef WIN_NT
HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
if (GetFileType(in) == FILE_TYPE_CHAR) //FILE_TYPE_DISK)
return false;
#else
if (isatty(fileno(stdin)))
return false;
#endif
return true;
}
// CVC: There's something either wrong or on purpose in this routine:
// it doesn't unescape the embedded quotes that may exist.
2008-01-16 08:23:29 +01:00
// But it's useful for paths as it's now.
2003-09-09 13:03:37 +02:00
static void strip_quotes(const TEXT* in,
TEXT* out)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* s t r i p _ q u o t e s
*
**************************************
*
* Functional description
* Get rid of quotes around strings
*
**************************************/
if (!in || !*in) {
*out = 0;
return;
}
2003-09-09 13:03:37 +02:00
TEXT quote = 0;
2005-04-25 07:23:57 +02:00
// Skip any initial quote
2001-05-23 15:26:42 +02:00
if ((*in == DBL_QUOTE) || (*in == SINGLE_QUOTE))
quote = *in++;
2003-09-09 13:03:37 +02:00
const TEXT* p = in;
2001-05-23 15:26:42 +02:00
2005-04-25 07:23:57 +02:00
// Now copy characters until we see the same quote or EOS
2001-05-23 15:26:42 +02:00
while (*p && (*p != quote)) {
*out++ = *p++;
}
*out = 0;
}
//#ifdef DEV_BUILD
static const char* sqltype_to_string(USHORT sqltype)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* s q l t y p e _ t o _ s t r i n g
*
**************************************
*
* Functional description
* Return a more readable version of SQLDA.sqltype
*
**************************************/
switch (sqltype & ~1) {
case SQL_TEXT:
return "TEXT ";
2001-05-23 15:26:42 +02:00
case SQL_VARYING:
return "VARYING ";
2001-05-23 15:26:42 +02:00
case SQL_SHORT:
return "SHORT ";
2001-05-23 15:26:42 +02:00
case SQL_LONG:
return "LONG ";
2001-05-23 15:26:42 +02:00
case SQL_INT64:
return "INT64 ";
2001-05-23 15:26:42 +02:00
case SQL_FLOAT:
return "FLOAT ";
2001-05-23 15:26:42 +02:00
case SQL_DOUBLE:
return "DOUBLE ";
2001-05-23 15:26:42 +02:00
case SQL_D_FLOAT:
return "D_FLOAT ";
2001-05-23 15:26:42 +02:00
case SQL_TIMESTAMP:
return "TIMESTAMP";
2001-05-23 15:26:42 +02:00
case SQL_TYPE_DATE:
return "SQL DATE ";
2001-05-23 15:26:42 +02:00
case SQL_TYPE_TIME:
return "TIME ";
2001-05-23 15:26:42 +02:00
case SQL_BLOB:
return "BLOB ";
2001-05-23 15:26:42 +02:00
case SQL_ARRAY:
return "ARRAY ";
2001-05-23 15:26:42 +02:00
case SQL_QUAD:
return "QUAD ";
2001-05-23 15:26:42 +02:00
default:
return "unknown ";
2001-05-23 15:26:42 +02:00
}
}
//#endif
2003-09-09 13:03:37 +02:00