mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-27 20:43:03 +01:00
8674 lines
209 KiB
Plaintext
8674 lines
209 KiB
Plaintext
/*
|
|
* PROGRAM: Interactive SQL utility
|
|
* MODULE: isql.epp
|
|
* DESCRIPTION: Main line routine
|
|
*
|
|
* The contents of this file are subject to the Interbase Public
|
|
* License Version 1.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.Inprise.com/IPL.html
|
|
*
|
|
* Software distributed under the License is distributed on an
|
|
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
|
|
* or implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code was created by Inprise Corporation
|
|
* and its predecessors. Portions created by Inprise Corporation are
|
|
* Copyright (C) Inprise Corporation.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
*
|
|
Revision 1.5 2000/11/18 16:49:24 fsg
|
|
Increased PRINT_BUFFER_LENGTH to 2048 to show larger plans
|
|
Fixed Bug #122563 in extract.e get_procedure_args
|
|
Apparently this has to be done in show.e also,
|
|
but that is for another day :-)
|
|
|
|
2001/05/20 Neil McCalden add planonly option
|
|
2001.09.09 Claudio Valderrama: put double quotes around identifiers
|
|
in dialect 3 only when needed. Solve mischievous declaration/invocation
|
|
of IUTILS_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.
|
|
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include <stdio.h>
|
|
#include "../yvalve/keywords.h"
|
|
#include "../jrd/intl.h"
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include "../common/utils_proto.h"
|
|
#include "../common/classes/array.h"
|
|
#include "../common/classes/init.h"
|
|
#include "../common/classes/ClumpletWriter.h"
|
|
#include "../common/classes/TempFile.h"
|
|
#include "../common/classes/FpeControl.h"
|
|
#include "../common/classes/GenericMap.h"
|
|
#include "../common/os/path_utils.h"
|
|
#include "../common/StatusHolder.h"
|
|
#include "../common/Tokens.h"
|
|
|
|
#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 HAVE_LOCALE_H
|
|
#include <locale.h>
|
|
#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
|
|
|
|
enum literal_string_type
|
|
{
|
|
INIT_STR_FLAG = 0,
|
|
SINGLE_QUOTED_STRING = 1,
|
|
DOUBLE_QUOTED_STRING = 2,
|
|
NO_MORE_STRING = 3,
|
|
INCOMPLETE_STRING = 4
|
|
};
|
|
|
|
#include "../common/classes/timestamp.h"
|
|
|
|
#if defined(WIN_NT)
|
|
#include <windows.h>
|
|
#endif
|
|
#include "../jrd/ibase.h"
|
|
#include "../isql/isql.h"
|
|
#include "../yvalve/perf.h"
|
|
#include "../jrd/license.h"
|
|
#include "../jrd/constants.h"
|
|
#include "../jrd/ods.h"
|
|
#include "../common/file_params.h"
|
|
#include "../common/classes/ImplementHelper.h"
|
|
#include "../isql/extra_proto.h"
|
|
#include "../isql/isql_proto.h"
|
|
#include "../isql/show_proto.h"
|
|
#include "../isql/iutils_proto.h"
|
|
#include "../yvalve/perf_proto.h"
|
|
#include "../yvalve/utl_proto.h"
|
|
#include "../yvalve/why_proto.h"
|
|
#include "../common/gdsassert.h"
|
|
|
|
#include "../isql/Extender.h"
|
|
#include "../isql/PtrSentry.h"
|
|
#include "../common/classes/UserBlob.h"
|
|
#include "../common/classes/MsgPrint.h"
|
|
|
|
using Firebird::PathName;
|
|
using Firebird::TempFile;
|
|
using MsgFormat::SafeArg;
|
|
|
|
#include "../isql/ColList.h"
|
|
#include "../isql/InputDevices.h"
|
|
#include "../isql/OptionsBase.h"
|
|
|
|
#include "../common/classes/Switches.h"
|
|
#include "../isql/isqlswi.h"
|
|
|
|
#include "../intl/charsets.h"
|
|
#include <unicode/utf8.h>
|
|
|
|
|
|
//DATABASE DB = COMPILETIME "yachts.lnk";
|
|
DATABASE DB = COMPILETIME "yachts.lnk" RUNTIME isqlGlob.global_Db_name;
|
|
|
|
IsqlGlobals isqlGlob;
|
|
|
|
#define DIGIT(c) ((c) >= '0' && (c) <= '9')
|
|
#define INT64_LIMIT ((((SINT64) 1) << 62) / 5) // same as in cvt.cpp
|
|
|
|
// Print lengths of numeric values
|
|
|
|
const size_t MAXCHARSET_SIZE = 32; // CHARSET names
|
|
const int SHORT_LEN = 7; // NUMERIC (4,2) = -327.68
|
|
const int LONG_LEN = 12; // NUMERIC (9,2) = -21474836.48
|
|
const int INT64_LEN = 21; // NUMERIC(18,2) = -92233720368547758.08
|
|
//const int QUAD_LEN = 19;
|
|
const int FLOAT_LEN = 14; // -1.2345678E+38
|
|
const int DOUBLE_LEN = 23; // -1.234567890123456E+300
|
|
const int DATE_LEN = 11; // 11 for date only
|
|
const int DATETIME_LEN = 25; // 25 for date-time
|
|
const int TIME_ONLY_LEN = 13; // 13 for time only
|
|
const int DATE_ONLY_LEN = 11;
|
|
const int BOOLEAN_LEN = 7; // <false>
|
|
const int UNKNOWN_LEN = 20; // Unknown type: %d
|
|
|
|
const int MAX_TERMS = 10; // max # of terms in an interactive cmd
|
|
|
|
const char* ISQL_COUNTERS_SET = "CurrentMemory, MaxMemory, RealTime, UserTime, Buffers, Reads, Writes, Fetches";
|
|
const int ISQL_COUNTERS = 8;
|
|
|
|
const char* UNKNOWN = "*unknown*";
|
|
|
|
namespace IcuUtil
|
|
{
|
|
// Duplicate from ICU to not need to link ISQL with it. It's used by U8_NEXT_UNSAFE.
|
|
static const uint8_t utf8_countTrailBytes[256] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
|
3, 3, 3, 3, 3,
|
|
3, 3, 3, /* illegal in Unicode */
|
|
4, 4, 4, 4, /* illegal in Unicode */
|
|
5, 5, /* illegal in Unicode */
|
|
0, 0 /* illegal bytes 0xfe and 0xff */
|
|
};
|
|
|
|
// Return the number of characters of a string.
|
|
static unsigned charLength(SSHORT charset, unsigned len, const char* str)
|
|
{
|
|
charset = TTYPE_TO_CHARSET(charset);
|
|
|
|
if (charset != CS_UNICODE_FSS && charset != CS_UTF8)
|
|
return len;
|
|
|
|
unsigned charLen = 0;
|
|
unsigned i = 0;
|
|
|
|
while (i < len)
|
|
{
|
|
UChar32 c;
|
|
U8_NEXT_UNSAFE(str, i, c);
|
|
++charLen;
|
|
}
|
|
|
|
return charLen;
|
|
}
|
|
|
|
// Pads a string to a specified column width.
|
|
static void pad(char* buffer, SSHORT charset, unsigned len, const char* str, unsigned width,
|
|
bool right)
|
|
{
|
|
charset = TTYPE_TO_CHARSET(charset);
|
|
|
|
if (charset != CS_UNICODE_FSS && charset != CS_UTF8)
|
|
{
|
|
// Truncate if necessary.
|
|
if (len > width)
|
|
len = width;
|
|
|
|
if (right)
|
|
{
|
|
memcpy(buffer + width - len, str, len);
|
|
if (width > len)
|
|
memset(buffer, ' ', width - len);
|
|
}
|
|
else
|
|
{
|
|
memcpy(buffer, str, len);
|
|
if (width > len)
|
|
memset(buffer + len, ' ', width - len);
|
|
}
|
|
buffer[width] = '\0';
|
|
return;
|
|
}
|
|
|
|
unsigned i = 0;
|
|
|
|
while (i < len && width > 0)
|
|
{
|
|
UChar32 c;
|
|
U8_NEXT_UNSAFE(str, i, c);
|
|
--width;
|
|
}
|
|
|
|
if (right)
|
|
{
|
|
while (width-- > 0)
|
|
*buffer++ = ' ';
|
|
}
|
|
|
|
memcpy(buffer, str, i);
|
|
buffer += i;
|
|
|
|
if (!right)
|
|
{
|
|
while (width-- > 0)
|
|
*buffer++ = ' ';
|
|
}
|
|
|
|
*buffer = '\0';
|
|
}
|
|
}
|
|
|
|
|
|
static inline bool commit_trans(Firebird::ITransaction** x)
|
|
{
|
|
(*x)->commit(fbStatus);
|
|
if (ISQL_errmsg (fbStatus))
|
|
{
|
|
(*x)->rollback(fbStatus);
|
|
if (!(fbStatus->getState() & Firebird::IStatus::STATE_ERRORS))
|
|
{
|
|
*x = NULL;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
*x = NULL;
|
|
return true;
|
|
}
|
|
|
|
static inline int fb_isspace(const char c)
|
|
{
|
|
return isspace((int)(UCHAR)c);
|
|
}
|
|
|
|
static inline int fb_isspace(const SSHORT c)
|
|
{
|
|
return isspace((int)(UCHAR)c);
|
|
}
|
|
|
|
static inline int fb_isdigit(const char c)
|
|
{
|
|
return isdigit((int)(UCHAR)c);
|
|
}
|
|
|
|
|
|
// I s q l G l o b a l s : : p r i n t f
|
|
// Output to the Out stream.
|
|
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.
|
|
}
|
|
|
|
|
|
|
|
struct ri_actions
|
|
{
|
|
const SCHAR* ri_action_name;
|
|
const SCHAR* ri_action_print_caps;
|
|
const SCHAR* ri_action_print_mixed;
|
|
};
|
|
|
|
static processing_state add_row(TEXT*);
|
|
static processing_state blobedit(const TEXT*, const TEXT* const*);
|
|
static processing_state bulk_insert_hack(const char* command);
|
|
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*, unsigned*);
|
|
static processing_state copy_table(TEXT*, TEXT*, TEXT*);
|
|
static processing_state create_db(const TEXT*, TEXT*);
|
|
static void do_isql();
|
|
static processing_state drop_db();
|
|
static processing_state edit(const TEXT* const*);
|
|
static processing_state end_trans();
|
|
static processing_state escape(const TEXT*);
|
|
static processing_state frontend(const TEXT*);
|
|
static processing_state frontend_set(const char* cmd, const char* const* parms,
|
|
const char* const* lparms, char* const bad_dialect_buf, bool& bad_dialect);
|
|
static void frontend_free_parms(TEXT*[], TEXT*[], 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(Firebird::string&, const TEXT*);
|
|
static bool get_numeric(const UCHAR*, USHORT, SSHORT*, SINT64*);
|
|
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 newMaxRows(const TEXT* newMaxRowsStr);
|
|
static processing_state newtrans(const TEXT*);
|
|
static processing_state parse_arg(int, SCHAR**, SCHAR*); //, FILE**);
|
|
#ifdef DEV_BUILD
|
|
static processing_state passthrough(const char* cmd);
|
|
#endif
|
|
static unsigned print_item(TEXT**, const IsqlVar*, const unsigned);
|
|
static void print_item_numeric(SINT64, int, int, TEXT*);
|
|
static processing_state print_line(Firebird::IMessageMetadata*, UCHAR*, const unsigned pad[], TEXT line[]);
|
|
static processing_state print_performance(const SINT64* perf_before);
|
|
static void print_message(Firebird::IMessageMetadata* msg, const char* dir);
|
|
static void process_header(Firebird::IMessageMetadata*, const unsigned pad[], TEXT header[], TEXT header2[]);
|
|
static void process_plan();
|
|
static SINT64 process_record_count(const int statement_type);
|
|
static int process_request_type();
|
|
static unsigned process_message_display(Firebird::IMessageMetadata* msg, unsigned pad[]);
|
|
static processing_state process_statement(const TEXT*);
|
|
#ifdef WIN_NT
|
|
static BOOL CALLBACK query_abort(DWORD);
|
|
#else
|
|
static int query_abort(const int, const int, void*);
|
|
#endif
|
|
static bool stdin_redirected();
|
|
static void strip_quotes(const TEXT*, TEXT*);
|
|
static const char* sqltype_to_string(unsigned);
|
|
static const char* charset_to_string(unsigned);
|
|
|
|
// The dialect spoken by the database, should be 0 when no database is connected.
|
|
USHORT global_dialect_spoken = 0;
|
|
USHORT requested_SQL_dialect = SQL_DIALECT_V6;
|
|
//bool connecting_to_pre_v6_server = false; Not used now.
|
|
bool Quiet = false;
|
|
#ifdef TRUSTED_AUTH
|
|
bool Trusted_auth = false;
|
|
#endif
|
|
bool Version_info = false;
|
|
|
|
// Utility transaction handle
|
|
static Firebird::ITransaction* D__trans = NULL;
|
|
static Firebird::ITransaction* M__trans = NULL;
|
|
static int global_numbufs; // # of cache buffers on connect
|
|
static Firebird::IStatement* global_Stmt = NULL;
|
|
static SCHAR Password[128];
|
|
static SCHAR Charset[128];
|
|
static bool Merge_stderr;
|
|
|
|
static Firebird::GlobalPtr<Firebird::UCharBuffer> global_Buffer;
|
|
static bool Abort_flag = false;
|
|
static bool Interrupt_flag = false;
|
|
static Firebird::GlobalPtr<InputDevices> Filelist;
|
|
static int Pagelength = 20;
|
|
static bool Nodbtriggers = false; // No database triggers
|
|
static int Exit_value = 0;
|
|
static bool Interactive = true;
|
|
static bool Input_file = false;
|
|
|
|
|
|
// Values used in the SET command.
|
|
// Initial options set exclusively from the command line are not included here.
|
|
class SetValues
|
|
{
|
|
public:
|
|
SetValues()
|
|
{
|
|
//ColList global_Cols;
|
|
global_Col_default = 0; // Need to write code for it in the future.
|
|
Echo = false;
|
|
Time_display = false;
|
|
Sqlda_display = false;
|
|
Stats = false;
|
|
Autocommit = true; // Commit ddl
|
|
Warnings = true; // Print warnings
|
|
Doblob = 1; // Default to printing only text types
|
|
List = false;
|
|
Docount = false;
|
|
maxRows = 0;
|
|
Plan = false;
|
|
Planonly = false;
|
|
ExplainPlan = false;
|
|
Heading = true;
|
|
BailOnError = false;
|
|
ISQL_charset[0] = 0;
|
|
}
|
|
|
|
ColList global_Cols;
|
|
int global_Col_default; // Need to write code for it in the future.
|
|
bool Echo;
|
|
bool Time_display;
|
|
bool Sqlda_display;
|
|
bool Stats;
|
|
bool Autocommit; // Commit ddl
|
|
bool Warnings; // Print warnings
|
|
int Doblob; // Default to printing only text types
|
|
bool List;
|
|
bool Docount;
|
|
size_t maxRows;
|
|
bool Plan;
|
|
bool Planonly;
|
|
bool ExplainPlan;
|
|
bool Heading;
|
|
bool BailOnError;
|
|
SCHAR ISQL_charset[MAXCHARSET_SIZE];
|
|
};
|
|
|
|
static SetValues setValues;
|
|
|
|
|
|
static bool Merge_diagnostic = false;
|
|
static FILE* Diag;
|
|
static FILE* Help;
|
|
|
|
static const TEXT* const sql_prompt = "SQL> ";
|
|
|
|
// Keep in sync with the chars that have their own "case" in get_statement(...).
|
|
static const char FORBIDDEN_TERM_CHARS[] = { '\n', '-', '*', '/', SINGLE_QUOTE, DBL_QUOTE };
|
|
static const char FORBIDDEN_TERM_CHARS_DISPLAY[] = "<ENTER>, -, *, /, SINGLE_QUOTE, DOUBLE_QUOTE";
|
|
|
|
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];
|
|
|
|
// 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"
|
|
};
|
|
|
|
static UCHAR predefined_blob_subtype_bpb[] =
|
|
{
|
|
isc_bpb_version1,
|
|
isc_bpb_source_type, 1, 0,
|
|
isc_bpb_target_type, 1, isc_blob_text
|
|
};
|
|
|
|
// No check on input argument for now.
|
|
inline void set_bpb_for_translation(const unsigned int blob_sub_type)
|
|
{
|
|
predefined_blob_subtype_bpb[3] = (UCHAR) blob_sub_type;
|
|
}
|
|
|
|
// Note that these transaction options aren't understood in Version 3.3
|
|
static const UCHAR default_tpb[] =
|
|
{
|
|
isc_tpb_version1, isc_tpb_write,
|
|
isc_tpb_read_committed, isc_tpb_wait,
|
|
isc_tpb_no_rec_version
|
|
};
|
|
|
|
#ifdef NOT_USED_OR_REPLACED
|
|
// CVC: Just in case we need it for R/O operations in the future.
|
|
static const UCHAR batch_tpb[] =
|
|
{
|
|
isc_tpb_version3, isc_tpb_read,
|
|
isc_tpb_read_committed, isc_tpb_nowait,
|
|
isc_tpb_rec_version
|
|
};
|
|
#endif
|
|
|
|
// If the action is restrict, do not print anything at all
|
|
static const ri_actions ri_actions_all[] =
|
|
{
|
|
{RI_ACTION_CASCADE, RI_ACTION_CASCADE, "Cascade"},
|
|
{RI_ACTION_NULL, RI_ACTION_NULL, "Set Null"},
|
|
{RI_ACTION_DEFAULT, RI_ACTION_DEFAULT, "Set Default"},
|
|
{RI_ACTION_NONE, RI_ACTION_NONE, "No Action"},
|
|
{RI_RESTRICT, "", ""},
|
|
{"", "", ""},
|
|
{0, 0, 0}
|
|
};
|
|
|
|
|
|
static bool startTransaction(Firebird::ITransaction** t, unsigned len = 0, const UCHAR* tpb = NULL)
|
|
{
|
|
if (DB && !*t)
|
|
{
|
|
*t = DB->startTransaction(fbStatus, len, tpb);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return false;
|
|
}
|
|
|
|
return DB != NULL;
|
|
}
|
|
|
|
static bool M_Transaction()
|
|
{
|
|
return startTransaction(&M__trans);
|
|
}
|
|
|
|
static bool D_Transaction()
|
|
{
|
|
return startTransaction(&D__trans, sizeof(default_tpb), default_tpb);
|
|
}
|
|
|
|
// Transaction for all frontend commands
|
|
static bool frontendTransaction()
|
|
{
|
|
return startTransaction(&fbTrans, sizeof(default_tpb), default_tpb);
|
|
}
|
|
|
|
static void atexit_fb_shutdown()
|
|
{
|
|
fb_shutdown(0, fb_shutrsn_app_stopped);
|
|
}
|
|
|
|
int CLIB_ROUTINE main(int argc, char* argv[])
|
|
{
|
|
/**************************************
|
|
*
|
|
* m a i n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* This calls ISQL_main, and exists to
|
|
* isolate main which does not exist under
|
|
* MS Windows.
|
|
*
|
|
**************************************/
|
|
|
|
return ISQL_main(argc, argv);
|
|
}
|
|
|
|
|
|
int ISQL_main(int argc, char* argv[])
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ m a i n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Choose between reading and executing or generating SQL
|
|
* ISQL_main isolates this from main for PC Clients.
|
|
*
|
|
**************************************/
|
|
|
|
#if defined(HAVE_EDITLINE_H) && defined(HAVE_LOCALE_H)
|
|
setlocale(LC_CTYPE, "");
|
|
#endif
|
|
atexit(&atexit_fb_shutdown);
|
|
|
|
TEXT tabname[WORDLENGTH];
|
|
tabname[0] = '\0';
|
|
|
|
// Initialize globals
|
|
isqlGlob.major_ods = 0;
|
|
isqlGlob.minor_ods = 0;
|
|
isqlGlob.db_SQL_dialect = 0;
|
|
isqlGlob.att_charset = 0;
|
|
|
|
// Output goes to stdout by default
|
|
isqlGlob.Out = stdout;
|
|
isqlGlob.Errfp = stderr;
|
|
|
|
const processing_state ret = parse_arg(argc, argv, tabname);
|
|
|
|
// Can't do a simple assignment because parse_arg may set Interactive to false.
|
|
if (stdin_redirected())
|
|
Interactive = false;
|
|
|
|
// Init the diagnostics and help files
|
|
if (Merge_diagnostic)
|
|
Diag = isqlGlob.Out;
|
|
else
|
|
Diag = stdout;
|
|
|
|
Help = stdout;
|
|
|
|
if (Merge_stderr)
|
|
isqlGlob.Errfp = isqlGlob.Out;
|
|
|
|
IUTILS_make_upper(tabname);
|
|
switch (ret)
|
|
{
|
|
case EXTRACT:
|
|
case EXTRACTALL:
|
|
if (*isqlGlob.global_Db_name)
|
|
{
|
|
Interactive = false; // "extract" option only can be called from command-line
|
|
|
|
// 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);
|
|
}
|
|
else
|
|
Exit_value = FINI_ERROR;
|
|
}
|
|
break;
|
|
|
|
case ps_ERR:
|
|
{
|
|
TEXT helpstring[158];
|
|
IUTILS_msg_get(USAGE, sizeof(helpstring), helpstring);
|
|
STDERROUT(helpstring);
|
|
for (int i = 0; i < FB_NELEM(isql_in_sw_table); i++)
|
|
{
|
|
if (isql_in_sw_table[i].in_sw_msg > 0)
|
|
{
|
|
IUTILS_msg_get(isql_in_sw_table[i].in_sw_msg, sizeof(helpstring), helpstring);
|
|
STDERROUT(helpstring);
|
|
}
|
|
}
|
|
Exit_value = FINI_ERROR;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
do_isql();
|
|
// keep Exit_value to whatever it is set
|
|
// by do_isql()
|
|
// Exit_value = FINI_OK;
|
|
break;
|
|
}
|
|
#ifdef DEBUG_GDS_ALLOC
|
|
// As ISQL can run under windows, all memory should be freed before
|
|
// returning. In debug mode this call will report unfreed blocks.
|
|
|
|
//gds_alloc_report(0 ALLOC_ARGS);
|
|
char fn[] = __FILE__;
|
|
fn[strlen(fn) - 8] = 0; // all isql files in gen dir
|
|
gds_alloc_report(0, fn, 0);
|
|
#endif
|
|
|
|
return Exit_value;
|
|
}
|
|
|
|
|
|
void ISQL_array_dimensions(const TEXT* fieldname)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ a r r a y _ d i m e n s i o n s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Retrieves the dimensions of arrays and prints them.
|
|
*
|
|
* Parameters: fieldname -- the actual name of the array field
|
|
*
|
|
**************************************/
|
|
|
|
isqlGlob.printf("[");
|
|
|
|
if (!frontendTransaction())
|
|
return;
|
|
|
|
FOR FDIM IN RDB$FIELD_DIMENSIONS WITH
|
|
FDIM.RDB$FIELD_NAME EQ fieldname
|
|
SORTED BY FDIM.RDB$DIMENSION
|
|
|
|
// Format is [lower:upper, lower:upper,..]
|
|
// 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)
|
|
isqlGlob.printf("%ld", FDIM.RDB$UPPER_BOUND);
|
|
else
|
|
isqlGlob.printf("%ld:%ld", FDIM.RDB$LOWER_BOUND, FDIM.RDB$UPPER_BOUND);
|
|
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
return;
|
|
END_ERROR;
|
|
|
|
isqlGlob.printf("]");
|
|
}
|
|
|
|
|
|
|
|
bool ISQL_dbcheck()
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ d b c h e c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Check to see if we are connected to a database.
|
|
* Return true if connected, false otherwise
|
|
*
|
|
* Change from miroslavp on 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.
|
|
**************************************/
|
|
TEXT errbuf[MSG_LENGTH];
|
|
|
|
if (!(DB && isqlGlob.global_Db_name[0]))
|
|
{
|
|
if (!Quiet)
|
|
{
|
|
IUTILS_msg_get(NO_DB, errbuf);
|
|
STDERROUT(errbuf);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static char userPrompt2[MSG_LENGTH];
|
|
static const char* const userPrompt = userPrompt2;
|
|
static char* lastInputLine = NULL;
|
|
static int getColumn = -1;
|
|
|
|
|
|
|
|
void ISQL_prompt(const TEXT* string)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ p r o m p t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Print a prompt string for interactive user
|
|
* Not for Windows, otherwise flush the string
|
|
**************************************/
|
|
//userPrompt = (const char*) string;
|
|
fb_utils::copy_terminate(userPrompt2, string, MSG_LENGTH);
|
|
|
|
//#ifndef HAVE_READLINE_READLINE_H
|
|
// fprintf(stdout, userPrompt.c_str());
|
|
// fflush(stdout);
|
|
//#endif
|
|
|
|
}
|
|
|
|
static void readNextInputLine(const char* prompt)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e a d N e x t I n p u t L i n e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get next input line and put it in lastInputLine
|
|
* If the first read is EOF set lineInputLine to NULL
|
|
* Otherwise return the line up to EOF (excluded) or '\n' (included)
|
|
* WARNING: If you bypass getNextInputChar() and call this directly,
|
|
* remember to set getColumn = -1; immediately after calling this function
|
|
* to avoid side effects or reading invalid memory locations.
|
|
**************************************/
|
|
|
|
if (lastInputLine != NULL)
|
|
{
|
|
free(lastInputLine);
|
|
lastInputLine = NULL;
|
|
}
|
|
|
|
getColumn = 0;
|
|
|
|
#ifdef HAVE_EDITLINE_H
|
|
if (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);
|
|
}
|
|
// Let's count lines if someone wants to enable line number error messages
|
|
// for console reading for whatever reason.
|
|
++Filelist->Ifp().indev_aux;
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
// @@@ CVC: On 2005-04-02, take the "|| Echo" out at request by Pavel.
|
|
if (Interactive && !Input_file)// || setValues.Echo)
|
|
{
|
|
// Write the prompt out.
|
|
fprintf(stdout, "%s", prompt);
|
|
fflush(stdout);
|
|
}
|
|
|
|
Firebird::string line;
|
|
bool end = false;
|
|
|
|
do
|
|
{
|
|
// Read the line
|
|
char buffer[MAX_USHORT];
|
|
|
|
if (fgets(buffer, sizeof(buffer), Filelist->Ifp().indev_fpointer) != NULL)
|
|
{
|
|
size_t lineSize = strlen(buffer);
|
|
|
|
// 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 &&
|
|
(buffer[lineSize - 1] == '\n' || buffer[lineSize - 1] == '\r'))
|
|
{
|
|
buffer[--lineSize] = '\0';
|
|
++Filelist->Ifp().indev_aux;
|
|
end = true;
|
|
}
|
|
|
|
line.append(buffer, lineSize);
|
|
}
|
|
else if (line.isEmpty())
|
|
return;
|
|
else
|
|
end = true;
|
|
} while (!end);
|
|
|
|
lastInputLine = (char*) malloc(line.length() + 1);
|
|
memcpy(lastInputLine, line.c_str(), line.length() + 1);
|
|
}
|
|
|
|
static void readNextInputLine()
|
|
{
|
|
readNextInputLine(userPrompt);
|
|
if (setValues.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';
|
|
}
|
|
// cast to unsigned char to prevent sign expansion
|
|
// this way we can distinguish russian ya (0xFF) and EOF (usually (-1))
|
|
return (unsigned char)lastInputLine[getColumn++];
|
|
}
|
|
|
|
|
|
bool ISQL_errmsg(Firebird::IStatus* st)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ e r r m s g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Report error conditions
|
|
* Simulate isc_print_status exactly, to control stderr
|
|
**************************************/
|
|
TEXT errbuf[MSG_LENGTH];
|
|
const ISC_STATUS* const status = st->getErrors();
|
|
|
|
const unsigned state = st->getState();
|
|
if (Quiet && (state & Firebird::IStatus::STATE_ERRORS))
|
|
Exit_value = FINI_ERROR;
|
|
//else
|
|
{
|
|
const ISC_STATUS* vec = status;
|
|
if (vec[0] != isc_arg_gds)
|
|
return false;
|
|
|
|
if (!(state & Firebird::IStatus::STATE_ERRORS) &&
|
|
(!(state & Firebird::IStatus::STATE_WARNINGS) || !setValues.Warnings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (state & Firebird::IStatus::STATE_ERRORS)
|
|
{
|
|
FB_SQLSTATE_STRING sqlstate;
|
|
fb_sqlstate(sqlstate, status);
|
|
IUTILS_msg_get(GEN_ERR, errbuf, SafeArg() << sqlstate);
|
|
STDERROUT(errbuf);
|
|
TEXT* err = errbuf;
|
|
unsigned es = sizeof(errbuf);
|
|
if (fb_interpret(err, es, &vec))
|
|
{
|
|
STDERROUT(errbuf);
|
|
|
|
// Continuation of error
|
|
*err++ = '-';
|
|
--es;
|
|
while (fb_interpret(err, es, &vec))
|
|
STDERROUT(errbuf);
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (state & Firebird::IStatus::STATE_WARNINGS)
|
|
{
|
|
const ISC_STATUS* w = st->getWarnings();
|
|
while (fb_interpret(err, es, &w)) {
|
|
STDERROUT(errbuf);
|
|
if (err == errbuf)
|
|
{
|
|
*err++ = '-';
|
|
--es;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (Input_file)
|
|
{
|
|
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';
|
|
}
|
|
}
|
|
*/
|
|
const InputDevices::indev& Ifp = Filelist->Ifp();
|
|
if (linenum != -1)
|
|
{
|
|
linenum += Ifp.indev_line;
|
|
IUTILS_msg_get(EXACTLINE, errbuf, SafeArg() << linenum << Ifp.fileName(true).c_str());
|
|
}
|
|
else
|
|
IUTILS_msg_get(AFTERLINE, errbuf, SafeArg() << Ifp.indev_line << Ifp.fileName(true).c_str());
|
|
|
|
STDERROUT(errbuf);
|
|
}
|
|
}
|
|
|
|
return (state & Firebird::IStatus::STATE_ERRORS);
|
|
}
|
|
|
|
|
|
void ISQL_warning(Firebird::CheckStatusWrapper* st)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ w a r n i n g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional desription
|
|
* Report warning
|
|
* Simulate isc_print_status exactly, to control stderr
|
|
**************************************/
|
|
|
|
if ((st->getState() & Firebird::IStatus::STATE_WARNINGS) && setValues.Warnings)
|
|
{
|
|
const ISC_STATUS* vec = st->getWarnings();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
st->init();
|
|
}
|
|
|
|
|
|
SSHORT ISQL_get_default_char_set_id()
|
|
{
|
|
/*************************************
|
|
*
|
|
* I S Q L _ g e t _ d e f a u l t _ c h a r _ s e t _ i d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Return the database default character set
|
|
* id.
|
|
*
|
|
* -1 if the value can not be determined.
|
|
*
|
|
**************************************/
|
|
|
|
/* What is the default character set for this database?
|
|
There are three states:
|
|
1. There is no entry available in RDB$DATABASE
|
|
Then - NONE
|
|
2. The entry in RDB$DATABASE does not exist in
|
|
RDB$CHARACTER_SETS
|
|
Then - -1 to cause all character set defs to show
|
|
3. An entry in RDB$CHARACTER_SETS
|
|
Then - RDB$CHARACTER_SET_ID
|
|
*/
|
|
SSHORT default_char_set_id = 0;
|
|
FOR FIRST 1 EXT IN RDB$DATABASE
|
|
WITH EXT.RDB$CHARACTER_SET_NAME NOT MISSING;
|
|
|
|
default_char_set_id = -1;
|
|
|
|
FOR FIRST 1 CHI IN RDB$CHARACTER_SETS
|
|
WITH CHI.RDB$CHARACTER_SET_NAME = EXT.RDB$CHARACTER_SET_NAME
|
|
|
|
default_char_set_id = CHI.RDB$CHARACTER_SET_ID;
|
|
|
|
END_FOR;
|
|
END_FOR;
|
|
|
|
return (default_char_set_id);
|
|
}
|
|
|
|
|
|
SSHORT ISQL_get_field_length(const TEXT* field_name)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ g e t _ f i e l d _ l e n g t h
|
|
*
|
|
**************************************
|
|
*
|
|
* Retrieve character or field length of character types.
|
|
*
|
|
**************************************/
|
|
|
|
if (!frontendTransaction())
|
|
return 0;
|
|
|
|
SSHORT l = 0;
|
|
FOR FLD IN RDB$FIELDS WITH
|
|
FLD.RDB$FIELD_NAME EQ field_name
|
|
|
|
if (FLD.RDB$CHARACTER_LENGTH.NULL)
|
|
l = FLD.RDB$FIELD_LENGTH;
|
|
else
|
|
l = FLD.RDB$CHARACTER_LENGTH;
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
return 0;
|
|
END_ERROR;
|
|
|
|
return l;
|
|
}
|
|
|
|
|
|
void ISQL_get_character_sets(SSHORT char_set_id, SSHORT collation, bool collate_only,
|
|
bool not_null, bool quote, TEXT* string)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ g e t _ c h a r a c t e r _ s e t s
|
|
*
|
|
**************************************
|
|
*
|
|
* Retrieve character set and collation order and format it
|
|
*
|
|
**************************************/
|
|
#ifdef DEV_BUILD
|
|
bool found = false;
|
|
#endif
|
|
|
|
const char* notNullStr = not_null ? " NOT NULL" : "";
|
|
string[0] = 0;
|
|
|
|
if (!frontendTransaction())
|
|
return;
|
|
|
|
//if (collation) {
|
|
if (collation || collate_only)
|
|
{
|
|
FOR FIRST 1 COL IN RDB$COLLATIONS CROSS
|
|
CST IN RDB$CHARACTER_SETS WITH
|
|
COL.RDB$CHARACTER_SET_ID EQ CST.RDB$CHARACTER_SET_ID AND
|
|
COL.RDB$COLLATION_ID EQ collation AND
|
|
CST.RDB$CHARACTER_SET_ID EQ char_set_id
|
|
SORTED BY COL.RDB$COLLATION_NAME, CST.RDB$CHARACTER_SET_NAME
|
|
|
|
#ifdef DEV_BUILD
|
|
found = true;
|
|
#endif
|
|
fb_utils::exact_name(CST.RDB$CHARACTER_SET_NAME);
|
|
fb_utils::exact_name(COL.RDB$COLLATION_NAME);
|
|
fb_utils::exact_name(CST.RDB$DEFAULT_COLLATE_NAME);
|
|
|
|
char charSetName[QUOTEDLENGTH];
|
|
char collateName[QUOTEDLENGTH];
|
|
|
|
if (quote && isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION)
|
|
{
|
|
IUTILS_copy_SQL_id(CST.RDB$CHARACTER_SET_NAME, charSetName, DBL_QUOTE);
|
|
IUTILS_copy_SQL_id(COL.RDB$COLLATION_NAME, collateName, DBL_QUOTE);
|
|
}
|
|
else
|
|
{
|
|
strcpy(charSetName, CST.RDB$CHARACTER_SET_NAME);
|
|
strcpy(collateName, COL.RDB$COLLATION_NAME);
|
|
}
|
|
|
|
// Is specified collation the default collation for character set?
|
|
if (strcmp (CST.RDB$DEFAULT_COLLATE_NAME, COL.RDB$COLLATION_NAME) == 0)
|
|
{
|
|
if (!collate_only)
|
|
sprintf (string, " CHARACTER SET %s%s", charSetName, notNullStr);
|
|
}
|
|
else if (collate_only)
|
|
sprintf (string, "%s COLLATE %s", notNullStr, collateName);
|
|
else
|
|
sprintf (string, " CHARACTER SET %s%s COLLATE %s",
|
|
charSetName, notNullStr, collateName);
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
return;
|
|
END_ERROR;
|
|
#ifdef DEV_BUILD
|
|
if (!found)
|
|
{
|
|
TEXT Print_buffer[PRINT_BUFFER_LENGTH];
|
|
sprintf(Print_buffer,
|
|
"ISQL_get_character_set: charset %d collation %d not found.\n",
|
|
char_set_id, collation);
|
|
STDERROUT(Print_buffer);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
FOR FIRST 1 CST IN RDB$CHARACTER_SETS WITH
|
|
CST.RDB$CHARACTER_SET_ID EQ char_set_id
|
|
SORTED BY CST.RDB$CHARACTER_SET_NAME
|
|
|
|
#ifdef DEV_BUILD
|
|
found = true;
|
|
#endif
|
|
fb_utils::exact_name(CST.RDB$CHARACTER_SET_NAME);
|
|
|
|
sprintf (string, " CHARACTER SET %s%s", CST.RDB$CHARACTER_SET_NAME, notNullStr);
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
return;
|
|
END_ERROR;
|
|
#ifdef DEV_BUILD
|
|
if (!found)
|
|
{
|
|
TEXT Print_buffer[PRINT_BUFFER_LENGTH];
|
|
sprintf(Print_buffer, "ISQL_get_character_set: charset %d not found.\n",
|
|
char_set_id);
|
|
STDERROUT(Print_buffer);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
inline static bool failed()
|
|
{
|
|
return (fbStatus->getState() & Firebird::IStatus::STATE_ERRORS);
|
|
}
|
|
|
|
|
|
inline static bool succeeded()
|
|
{
|
|
return !failed();
|
|
}
|
|
|
|
|
|
processing_state ISQL_fill_var(IsqlVar* var,
|
|
Firebird::IMessageMetadata* msg,
|
|
unsigned index,
|
|
UCHAR* buf)
|
|
{
|
|
var->field = msg->getField(fbStatus, index); if (failed()) return ps_ERR;
|
|
var->relation = msg->getRelation(fbStatus, index); if (failed()) return ps_ERR;
|
|
var->owner = msg->getOwner(fbStatus, index); if (failed()) return ps_ERR;
|
|
var->alias = msg->getAlias(fbStatus, index); if (failed()) return ps_ERR;
|
|
var->subType = msg->getSubType(fbStatus, index); if (failed()) return ps_ERR;
|
|
var->scale = msg->getScale(fbStatus, index); if (failed()) return ps_ERR;
|
|
var->type = msg->getType(fbStatus, index); if (failed()) return ps_ERR;
|
|
var->length = msg->getLength(fbStatus, index); if (failed()) return ps_ERR;
|
|
var->charSet = msg->getCharSet(fbStatus, index); if (failed()) return ps_ERR;
|
|
var->nullable = msg->isNullable(fbStatus, index); if (failed()) return ps_ERR;
|
|
|
|
if (buf)
|
|
{
|
|
var->nullInd = (short*) &buf[msg->getNullOffset(fbStatus, index)]; if (failed()) return ps_ERR;
|
|
var->value.setPtr = &buf[msg->getOffset(fbStatus, index)]; if (failed()) return ps_ERR;
|
|
}
|
|
else
|
|
{
|
|
var->nullInd = 0;
|
|
var->value.setPtr = 0;
|
|
}
|
|
|
|
return CONT;
|
|
}
|
|
|
|
|
|
void ISQL_get_default_source(const TEXT* rel_name,
|
|
TEXT* field_name,
|
|
ISC_QUAD* blob_id)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ g e t _ d e f a u l t _ s o u r c e
|
|
*
|
|
**************************************
|
|
*
|
|
* Retrieve the default source of a field.
|
|
* Relation_fields takes precedence over fields if both
|
|
* are present
|
|
*
|
|
* For a domain, a NULL is passed for rel_name
|
|
**************************************/
|
|
|
|
fb_utils::exact_name(field_name);
|
|
|
|
*blob_id = fbBlobNull;
|
|
|
|
if (!frontendTransaction())
|
|
return;
|
|
|
|
if (rel_name)
|
|
{
|
|
// This is default for a column of a table
|
|
FOR FLD IN RDB$FIELDS CROSS
|
|
RFR IN RDB$RELATION_FIELDS WITH
|
|
RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME AND
|
|
RFR.RDB$RELATION_NAME EQ rel_name AND
|
|
FLD.RDB$FIELD_NAME EQ field_name
|
|
|
|
if (!RFR.RDB$DEFAULT_SOURCE.NULL)
|
|
*blob_id = RFR.RDB$DEFAULT_SOURCE;
|
|
else if (!FLD.RDB$DEFAULT_SOURCE.NULL)
|
|
*blob_id = FLD.RDB$DEFAULT_SOURCE;
|
|
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
return;
|
|
END_ERROR;
|
|
}
|
|
else
|
|
{
|
|
// Default for a domain
|
|
FOR FLD IN RDB$FIELDS WITH
|
|
FLD.RDB$FIELD_NAME EQ field_name
|
|
|
|
if (!FLD.RDB$DEFAULT_SOURCE.NULL)
|
|
*blob_id = FLD.RDB$DEFAULT_SOURCE;
|
|
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
return;
|
|
END_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
SLONG ISQL_get_index_segments(TEXT* segs,
|
|
const size_t buf_size,
|
|
const TEXT* indexname,
|
|
bool delimited_yes)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ g e t _ i n d e x _ s e g m e n t s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* returns the list of columns in an index.
|
|
*
|
|
**************************************/
|
|
TEXT SQL_identifier[BUFFER_LENGTH128];
|
|
|
|
*segs = '\0';
|
|
|
|
if (!frontendTransaction())
|
|
return 0;
|
|
|
|
TEXT* const segs_end = segs + buf_size - 1;
|
|
// Query to get column names
|
|
SLONG n = 0;
|
|
bool count_only = false;
|
|
|
|
FOR SEG IN RDB$INDEX_SEGMENTS WITH
|
|
SEG.RDB$INDEX_NAME EQ indexname
|
|
SORTED BY SEG.RDB$FIELD_POSITION
|
|
|
|
++n;
|
|
if (count_only)
|
|
continue;
|
|
|
|
// Place a comma and a blank between each segment column name
|
|
|
|
fb_utils::exact_name(SEG.RDB$FIELD_NAME);
|
|
if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) {
|
|
IUTILS_copy_SQL_id (SEG.RDB$FIELD_NAME, SQL_identifier, DBL_QUOTE);
|
|
}
|
|
else {
|
|
strcpy (SQL_identifier, SEG.RDB$FIELD_NAME);
|
|
}
|
|
|
|
const size_t len = strlen(SQL_identifier);
|
|
|
|
if (n == 1)
|
|
{
|
|
// We assume the buffer is at least size(metadata name), so no initial check.
|
|
strcpy(segs, SQL_identifier);
|
|
segs += len;
|
|
}
|
|
else
|
|
{
|
|
if (segs + len + 2 >= segs_end)
|
|
{
|
|
strncpy(segs, ", ...", segs_end - segs);
|
|
*segs_end = '\0';
|
|
count_only = true;
|
|
}
|
|
else
|
|
{
|
|
sprintf (segs, ", %s", SQL_identifier);
|
|
segs += len + 2;
|
|
}
|
|
}
|
|
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
ROLLBACK;
|
|
return 0;
|
|
END_ERROR;
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
bool ISQL_get_base_column_null_flag(const TEXT* view_name,
|
|
const SSHORT view_context,
|
|
const TEXT* base_field)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ g e t _ b a s e _ c o l u m n _ n u l l _ f l a g
|
|
*
|
|
**************************************
|
|
*
|
|
* Determine if a field on which view column is based
|
|
* is nullable. We are passed the view_name
|
|
* view_context and the base_field of the view column.
|
|
*
|
|
**************************************/
|
|
BASED_ON RDB$RELATION_FIELDS.RDB$RELATION_NAME save_view_name,
|
|
save_base_field;
|
|
|
|
strcpy(save_view_name, view_name);
|
|
strcpy(save_base_field, base_field);
|
|
SSHORT save_view_context = view_context;
|
|
|
|
|
|
if (!frontendTransaction())
|
|
return false;
|
|
|
|
/*
|
|
Using view_name and view_context get the relation name from
|
|
RDB$VIEW_RELATIONS which contains the base_field for this view column.
|
|
Get row corresponding to this base field and relation from
|
|
rdb$field_relations. This will contain info on field's nullability unless
|
|
it is a view column itself, in which case repeat this procedure till
|
|
we get to a "real" column.
|
|
*/
|
|
bool null_flag = true;
|
|
bool done = false;
|
|
bool error = false;
|
|
while (!done && !error)
|
|
{
|
|
fb_utils::exact_name(save_view_name);
|
|
fb_utils::exact_name(save_base_field);
|
|
bool found = false;
|
|
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
|
|
|
|
found = true;
|
|
if (NEWRFR.RDB$BASE_FIELD.NULL)
|
|
{
|
|
if (!NEWRFR.RDB$NULL_FLAG.NULL && NEWRFR.RDB$NULL_FLAG == 1)
|
|
null_flag = false;
|
|
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
strcpy (save_view_name, NEWRFR.RDB$RELATION_NAME);
|
|
save_view_context = NEWRFR.RDB$VIEW_CONTEXT;
|
|
strcpy (save_base_field, NEWRFR.RDB$BASE_FIELD);
|
|
}
|
|
END_FOR
|
|
ON_ERROR
|
|
error = true;
|
|
END_ERROR;
|
|
|
|
if (!found)
|
|
{
|
|
if (isqlGlob.major_ods >= ODS_VERSION12)
|
|
{
|
|
// Copy/paste from DYN_UTIL_find_field_source
|
|
FOR FIRST 1
|
|
VRL IN RDB$VIEW_RELATIONS CROSS
|
|
PPR IN RDB$PROCEDURE_PARAMETERS
|
|
WITH VRL.RDB$RELATION_NAME EQ PPR.RDB$PROCEDURE_NAME AND
|
|
VRL.RDB$VIEW_NAME EQ save_view_name AND
|
|
VRL.RDB$VIEW_CONTEXT EQ save_view_context AND
|
|
VRL.RDB$CONTEXT_TYPE EQ VCT_PROCEDURE AND
|
|
PPR.RDB$PACKAGE_NAME EQUIV VRL.RDB$PACKAGE_NAME AND
|
|
PPR.RDB$PARAMETER_NAME EQ save_base_field AND
|
|
PPR.RDB$PARAMETER_TYPE = 1 // output
|
|
|
|
found = true;
|
|
if (!PPR.RDB$NULL_FLAG.NULL && PPR.RDB$NULL_FLAG == 1)
|
|
null_flag = false;
|
|
|
|
done = true;
|
|
END_FOR
|
|
ON_ERROR
|
|
error = true;
|
|
END_ERROR;
|
|
}
|
|
else if (ENCODE_ODS(isqlGlob.major_ods, isqlGlob.minor_ods) >= ODS_11_2)
|
|
{
|
|
FOR FIRST 1
|
|
VR IN RDB$VIEW_RELATIONS
|
|
CROSS NEWPP IN RDB$PROCEDURE_PARAMETERS WITH
|
|
VR.RDB$VIEW_NAME EQ save_view_name AND
|
|
VR.RDB$VIEW_CONTEXT EQ save_view_context AND
|
|
NEWPP.RDB$PROCEDURE_NAME = VR.RDB$RELATION_NAME AND
|
|
NEWPP.RDB$PARAMETER_NAME = save_base_field AND
|
|
NEWPP.RDB$PARAMETER_TYPE = 1 // output param
|
|
|
|
found = true;
|
|
if (!NEWPP.RDB$NULL_FLAG.NULL && NEWPP.RDB$NULL_FLAG == 1)
|
|
null_flag = false;
|
|
|
|
done = true;
|
|
END_FOR
|
|
ON_ERROR
|
|
error = true;
|
|
END_ERROR;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
error = true;
|
|
}
|
|
|
|
// The error shouldn't be masked here. It should be propagated.
|
|
return null_flag;
|
|
}
|
|
|
|
bool ISQL_get_null_flag(const TEXT* rel_name,
|
|
TEXT* field_name)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ g e t _ n u l l _ f l a g
|
|
*
|
|
**************************************
|
|
*
|
|
* Determine if a field has the null flag set.
|
|
* Look for either rdb$relation_fields or rdb$fields to be
|
|
* Set to 1 (NOT NULL), then this field cannot be null
|
|
* We are passed the relation name and the relation_field name
|
|
* For domains, the relation name is null.
|
|
**************************************/
|
|
fb_utils::exact_name(field_name);
|
|
|
|
bool null_flag = true;
|
|
|
|
if (!frontendTransaction())
|
|
return false;
|
|
|
|
if (rel_name)
|
|
{
|
|
FOR FLD IN RDB$FIELDS CROSS
|
|
RFR IN RDB$RELATION_FIELDS WITH
|
|
RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME AND
|
|
RFR.RDB$RELATION_NAME EQ rel_name AND
|
|
RFR.RDB$FIELD_NAME EQ field_name
|
|
|
|
if (!FLD.RDB$NULL_FLAG.NULL && FLD.RDB$NULL_FLAG == 1)
|
|
null_flag = false;
|
|
else
|
|
{
|
|
|
|
// If RDB$BASE_FIELD is not null then it is a view column
|
|
|
|
if (RFR.RDB$BASE_FIELD.NULL)
|
|
{
|
|
|
|
// Simple column. Did user define it not null?
|
|
|
|
if (!RFR.RDB$NULL_FLAG.NULL && 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(fbStatus);
|
|
return null_flag;
|
|
END_ERROR;
|
|
}
|
|
else
|
|
{
|
|
// Domains have only field entries to worry about
|
|
FOR FLD IN RDB$FIELDS WITH
|
|
FLD.RDB$FIELD_NAME EQ field_name
|
|
|
|
if (FLD.RDB$NULL_FLAG == 1)
|
|
null_flag = false;
|
|
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
return null_flag;
|
|
END_ERROR;
|
|
}
|
|
|
|
return null_flag;
|
|
}
|
|
|
|
|
|
void ISQL_disconnect_database(bool nQuietMode)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ d i s c o n n e c t _ d a t a b a s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Disconnect from the current database. First commit work and then
|
|
* call detach to detach from the database and then zero
|
|
* out the DB handle.
|
|
*
|
|
* Change from miroslavp on 2005.05.06
|
|
* Quiet global variable is preserved in local variable and restored at the end.
|
|
**************************************/
|
|
|
|
const bool OriginalQuiet = Quiet;
|
|
// Ignore error msgs during disconnect
|
|
Quiet = nQuietMode;
|
|
|
|
// If we were in a database, commit before proceeding
|
|
if (DB && (M__trans || D__trans))
|
|
end_trans();
|
|
|
|
// Commit transaction that was started on behalf of the request
|
|
// Don't worry about the error if one occurs it will be network
|
|
// related and caught later.
|
|
if (DB && fbTrans)
|
|
{
|
|
fbTrans->rollback(fbStatus);
|
|
if (succeeded())
|
|
fbTrans = NULL;
|
|
}
|
|
|
|
// If there is current user statement, free it
|
|
if (global_Stmt)
|
|
{
|
|
global_Stmt->free(fbStatus);
|
|
if (succeeded())
|
|
global_Stmt = NULL;
|
|
}
|
|
|
|
// Detach from old database
|
|
if (DB)
|
|
{
|
|
DB->detach(fbStatus);
|
|
if (succeeded())
|
|
DB = NULL;
|
|
}
|
|
|
|
// Restore original value of the flag
|
|
Quiet = OriginalQuiet;
|
|
|
|
// Zero database handle and transaction handles
|
|
global_Stmt = NULL;
|
|
DB = NULL;
|
|
isqlGlob.global_Db_name[0] = '\0';
|
|
D__trans = NULL;
|
|
M__trans = NULL;
|
|
fbTrans = NULL;
|
|
|
|
// CVC: If we aren't connected to a db anymore, then the db's dialect is reset.
|
|
// This should fix SF Bug #910430.
|
|
isqlGlob.db_SQL_dialect = 0;
|
|
// BRS this is also needed to fix #910430.
|
|
global_dialect_spoken = 0;
|
|
return;
|
|
}
|
|
|
|
|
|
#ifdef NOT_USED_OR_REPLACED
|
|
bool ISQL_is_domain(const TEXT* field_name)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ i s _ d o m a i n
|
|
*
|
|
**************************************
|
|
*
|
|
* Determine if a field in rdb$fields is a domain,
|
|
*
|
|
**************************************/
|
|
bool is_domain = false;
|
|
|
|
if (!frontendTransaction())
|
|
return false;
|
|
|
|
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;
|
|
}
|
|
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
return is_domain;
|
|
END_ERROR;
|
|
|
|
return is_domain;
|
|
}
|
|
#endif
|
|
|
|
|
|
// Print the numeric type as accurately as possible, depending on the ODS.
|
|
// If it isn't numeric/decimal or is another non-numeric data type, print only the type.
|
|
bool ISQL_printNumericType(const char* fieldName, const int fieldType, const int fieldScale)
|
|
{
|
|
// Look through types array
|
|
int i = 0;
|
|
while (Column_types[i].type && fieldType != Column_types[i].type)
|
|
++i;
|
|
|
|
fb_assert(Column_types[i].type != 0);
|
|
if (Column_types[i].type == 0)
|
|
{
|
|
isqlGlob.printf("Unknown data type %d for field name %s%s", fieldType, fieldName, NEWLINE);
|
|
return false;
|
|
}
|
|
|
|
bool precision_known = false;
|
|
|
|
if (isqlGlob.major_ods >= ODS_VERSION10 &&
|
|
(fieldType == SMALLINT || fieldType == INTEGER || fieldType == BIGINT))
|
|
{
|
|
// Handle Integral subtypes NUMERIC and DECIMAL
|
|
// We are ODS >= 10 and could be any Dialect
|
|
FOR FLD1 IN RDB$FIELDS WITH
|
|
FLD1.RDB$FIELD_NAME EQ fieldName
|
|
AND NOT FLD1.RDB$FIELD_PRECISION EQ 0
|
|
|
|
// We are Dialect >=3 since FIELD_PRECISION is non-NULL
|
|
if (FLD1.RDB$FIELD_SUB_TYPE > 0 && FLD1.RDB$FIELD_SUB_TYPE <= MAX_INTSUBTYPES)
|
|
{
|
|
isqlGlob.printf("%s(%d, %d)",
|
|
Integral_subtypes[FLD1.RDB$FIELD_SUB_TYPE],
|
|
FLD1.RDB$FIELD_PRECISION,
|
|
-FLD1.RDB$FIELD_SCALE);
|
|
precision_known = true;
|
|
}
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg (fbStatus);
|
|
return false;
|
|
END_ERROR;
|
|
}
|
|
|
|
if (!precision_known)
|
|
{
|
|
if (fieldScale < 0)
|
|
{
|
|
// Take a stab at numerics and decimals
|
|
switch (fieldType)
|
|
{
|
|
case SMALLINT:
|
|
isqlGlob.printf("NUMERIC(4, %d)", -fieldScale);
|
|
break;
|
|
case INTEGER:
|
|
isqlGlob.printf("NUMERIC(9, %d)", -fieldScale);
|
|
break;
|
|
case BIGINT:
|
|
isqlGlob.printf("NUMERIC(18, %d)", -fieldScale);
|
|
break;
|
|
case DOUBLE_PRECISION:
|
|
isqlGlob.printf("NUMERIC(15, %d)", -fieldScale);
|
|
break;
|
|
default:
|
|
isqlGlob.printf("%s", Column_types[i].type_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is a non-numeric data type
|
|
isqlGlob.printf("%s", Column_types[i].type_name);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void ISQL_print_validation(FILE* fp,
|
|
ISC_QUAD* blobid,
|
|
bool isComputedField,
|
|
Firebird::ITransaction* trans)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ p r i n t _ v a l i d a t i o n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* This does some minor syntax adjustmet for extracting
|
|
* validation blobs and computed fields.
|
|
* if it does not start with the word CHECK
|
|
* if this is a computed field blob, look for () or insert them.
|
|
* if flag == false, this is a validation clause,
|
|
* if flag == true, this is a computed field
|
|
*
|
|
**************************************/
|
|
// Don't bother with null blobs
|
|
|
|
if (blobid->gds_quad_high == 0 || !DB)
|
|
return;
|
|
|
|
Firebird::IBlob* blob = DB->openBlob(fbStatus, trans, blobid, 0, NULL);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return;
|
|
|
|
bool issql = false;
|
|
bool firsttime = true;
|
|
TEXT buffer[BUFFER_LENGTH512];
|
|
|
|
do
|
|
{
|
|
unsigned int length;
|
|
int cc = blob->getSegment(fbStatus, sizeof(buffer) - 1, buffer, &length);
|
|
if (cc == Firebird::IStatus::RESULT_NO_DATA || cc == Firebird::IStatus::RESULT_ERROR)
|
|
break;
|
|
|
|
buffer[length] = 0;
|
|
const TEXT* p = buffer;
|
|
if (isComputedField)
|
|
{
|
|
// computed field SQL syntax correction
|
|
|
|
while (fb_isspace(*p))
|
|
p++;
|
|
if (*p == '(')
|
|
issql = true;
|
|
}
|
|
else
|
|
{
|
|
// Validation SQL syntax correction
|
|
|
|
while (fb_isspace(*p))
|
|
p++;
|
|
if (!fb_utils::strnicmp(p, "CHECK", 5))
|
|
issql = true;
|
|
}
|
|
if (firsttime)
|
|
{
|
|
firsttime = false;
|
|
if (!issql) {
|
|
IUTILS_printf2(fp, "%s ", (isComputedField ? "/* " : "("));
|
|
}
|
|
}
|
|
|
|
IUTILS_printf(fp, buffer);
|
|
} while (true);
|
|
|
|
// CVC: If firsttime == true, then it didn't write the "/*" or the "(".
|
|
if (!issql && !firsttime)
|
|
IUTILS_printf2(fp, "%s", (isComputedField ? " */" : ")"));
|
|
|
|
if (failed())
|
|
ISQL_errmsg(fbStatus);
|
|
|
|
blob->close(fbStatus);
|
|
}
|
|
|
|
|
|
static processing_state add_row(TEXT* tabname)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a d d _ r o w
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allows interactive insert of row, prompting for every column
|
|
*
|
|
* The technique is to describe a select query of select * from the table.
|
|
* Then generate an insert with ? in every position, using the same sqlda.
|
|
* It will allow edits of blobs, skip arrays and computed fields
|
|
*
|
|
* Parameters:
|
|
* tabname -- Name of table to insert into
|
|
*
|
|
**************************************/
|
|
if (!*tabname)
|
|
return (ps_ERR);
|
|
|
|
if (!Interactive)
|
|
return (ps_ERR);
|
|
|
|
if (Input_file || !DB)
|
|
return ps_ERR;
|
|
|
|
// There may have been a commit transaction before we got here
|
|
if (!M_Transaction())
|
|
{
|
|
return FAIL;
|
|
}
|
|
|
|
// Start default transaction for prepare
|
|
if (!D_Transaction())
|
|
{
|
|
return FAIL;
|
|
}
|
|
|
|
chop_at(tabname, QUOTEDLENGTH);
|
|
if (tabname[0] != DBL_QUOTE)
|
|
IUTILS_make_upper(tabname);
|
|
|
|
// Query to obtain relation information
|
|
Firebird::string str2;
|
|
str2.printf("SELECT * FROM %s", tabname);
|
|
|
|
if (global_Stmt)
|
|
{
|
|
global_Stmt->free(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
}
|
|
|
|
global_Stmt = DB->prepare(fbStatus, D__trans, 0, str2.c_str(), isqlGlob.SQL_dialect,
|
|
Firebird::IStatement::PREPARE_PREFETCH_METADATA);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
|
|
Firebird::RefPtr<Firebird::IMessageMetadata> msg(Firebird::REF_NO_INCR, global_Stmt->getOutputMetadata(fbStatus));
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
|
|
const unsigned n_cols = msg->getCount(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
|
|
global_Stmt->free(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
global_Stmt = NULL;
|
|
|
|
// Array for storing select/insert column mapping, colcheck sets it up
|
|
Firebird::Array<unsigned> coln;
|
|
unsigned* colnumber = coln.getBuffer(n_cols);
|
|
col_check(tabname, colnumber);
|
|
|
|
// Create the new INSERT statement from the sqlda info
|
|
|
|
Firebird::string insertstring;
|
|
|
|
// There is a question mark for each column that's known to be updatable.
|
|
|
|
insertstring.printf("INSERT INTO %s (", tabname);
|
|
|
|
unsigned i_cols = 0;
|
|
{ // scope
|
|
char fldfixed[QUOTEDLENGTH];
|
|
int i = 0;
|
|
for (unsigned i = 0; i < n_cols; ++i)
|
|
{
|
|
// Skip columns that are computed
|
|
if (colnumber[i] != ~0u)
|
|
{
|
|
const char* fldname = msg->getField(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
const bool delimited_yes = fldname[0] == DBL_QUOTE;
|
|
if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes)
|
|
{
|
|
IUTILS_copy_SQL_id(fldname, fldfixed, DBL_QUOTE);
|
|
}
|
|
else
|
|
{
|
|
strcpy(fldfixed, fldname);
|
|
IUTILS_make_upper(fldfixed);
|
|
}
|
|
|
|
// Set i_cols to the number of insert columns
|
|
if (i_cols++)
|
|
insertstring += ',';
|
|
insertstring += fldfixed;
|
|
}
|
|
}
|
|
} // scope
|
|
|
|
insertstring += ") VALUES (";
|
|
|
|
for (unsigned i = 0; i < i_cols; i++)
|
|
{
|
|
if (i)
|
|
insertstring += ',';
|
|
insertstring += '?';
|
|
}
|
|
|
|
insertstring += ')';
|
|
|
|
// Build metadata for insert message
|
|
if (i_cols != n_cols)
|
|
{
|
|
Firebird::RefPtr<Firebird::IMetadataBuilder> bldr(Firebird::REF_NO_INCR, msg->getBuilder(fbStatus));
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
|
|
unsigned i = n_cols;
|
|
while (i-- > 0)
|
|
{
|
|
if (colnumber[i] == ~0u)
|
|
{
|
|
bldr->remove(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
}
|
|
}
|
|
msg.assignRefNoIncr(bldr->getMetadata(fbStatus));
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
}
|
|
|
|
// Allocate INSERT buffer
|
|
unsigned bSize = msg->getMessageLength(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
UCHAR* msgBuf = global_Buffer->getBuffer(bSize);
|
|
|
|
// For each column, get the type, and length then prompt for a value
|
|
// and scanf the resulting string into a buffer of the right type.
|
|
|
|
SCHAR infobuf[BUFFER_LENGTH180];
|
|
IUTILS_msg_get(ADD_PROMPT, sizeof(infobuf), infobuf);
|
|
STDERROUT(infobuf);
|
|
|
|
TEXT txt[MSG_LENGTH];
|
|
bool done = false;
|
|
while (!done)
|
|
{
|
|
for (unsigned i = 0; i < i_cols && !done; ++i)
|
|
{
|
|
short* nullp = (short*)&msgBuf[msg->getNullOffset(fbStatus, i)];
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
UCHAR* datap = &msgBuf[msg->getOffset(fbStatus, i)];
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
|
|
const char* name = msg->getField(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
unsigned type = msg->getType(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
const bool nullable = msg->isNullable(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
unsigned varLength;
|
|
int scale;
|
|
|
|
// Prompt with the name and read the line
|
|
switch (type)
|
|
{
|
|
case SQL_BLOB:
|
|
IUTILS_msg_get(BLOB_PROMPT, txt, SafeArg() << name);
|
|
// Blob: %s, type 'edit' or filename to load>
|
|
break;
|
|
case SQL_TYPE_DATE:
|
|
IUTILS_msg_get(DATE_PROMPT, txt, SafeArg() << name);
|
|
// Enter %s as Y/M/D>
|
|
break;
|
|
case SQL_TYPE_TIME:
|
|
IUTILS_msg_get(TIME_PROMPT, txt, SafeArg() << name);
|
|
// Enter %s as H:M:S>
|
|
break;
|
|
case SQL_TIMESTAMP:
|
|
IUTILS_msg_get(TIMESTAMP_PROMPT, txt, SafeArg() << name);
|
|
// Enter %s as Y/MON/D H:MIN:S[.MSEC]>
|
|
break;
|
|
default:
|
|
IUTILS_msg_get(NAME_PROMPT, txt, SafeArg() << name);
|
|
// Enter %s>
|
|
break;
|
|
}
|
|
|
|
// On blank line or EOF, break the two loops without doing an insert.
|
|
|
|
readNextInputLine(txt);
|
|
getColumn = -1; // We are bypassing getNextInputChar().
|
|
if (lastInputLine == NULL || strlen(lastInputLine) == 0)
|
|
{
|
|
done = true;
|
|
break;
|
|
}
|
|
|
|
unsigned length = static_cast<unsigned>(strlen(lastInputLine));
|
|
if (length > MAX_SQL_LENGTH)
|
|
{
|
|
IUTILS_msg_get(BUFFER_OVERFLOW, txt);
|
|
STDERROUT(txt);
|
|
done = true;
|
|
break;
|
|
}
|
|
STDERROUT("");
|
|
|
|
// Convert first 4 chars to upper case for comparison
|
|
SCHAR cmd[5];
|
|
strncpy(cmd, lastInputLine, 4);
|
|
cmd[4] = '\0';
|
|
IUTILS_make_upper(cmd);
|
|
|
|
// If the user writes NULL, put a null in the column
|
|
|
|
if (!strcmp(cmd, "NULL"))
|
|
*nullp = -1;
|
|
else
|
|
{
|
|
*nullp = 0;
|
|
SLONG res;
|
|
|
|
// Data types
|
|
SSHORT* smallint;
|
|
SLONG* integer;
|
|
SINT64* pi64;
|
|
SINT64 n;
|
|
float* fvalue;
|
|
double* dvalue;
|
|
UCHAR* boolean;
|
|
ISC_QUAD* blobid;
|
|
vary* avary;
|
|
char* achar;
|
|
tm times;
|
|
// Initialize the time structure
|
|
memset(×, 0, sizeof(times));
|
|
char msec_str[5] = "";
|
|
int msec = 0;
|
|
|
|
switch (type)
|
|
{
|
|
case SQL_BLOB:
|
|
|
|
blobid = (ISC_QUAD*) datap;
|
|
|
|
if (!strcmp(cmd, "EDIT")) // If edit, we edit a temp file.
|
|
{
|
|
const Firebird::PathName ftmp = TempFile::create(SCRATCH);
|
|
if (ftmp.empty())
|
|
res = 0;
|
|
else
|
|
{
|
|
gds__edit(ftmp.c_str(), 0);
|
|
Firebird::UtilInterfacePtr()->loadBlob(fbStatus, blobid, DB, M__trans,
|
|
ftmp.c_str(), FB_TRUE);
|
|
unlink(ftmp.c_str());
|
|
res = succeeded();
|
|
}
|
|
}
|
|
else // Otherwise just load the named file
|
|
{
|
|
// We can't be sure if it's a TEXT or BINARY file
|
|
// being loaded. As we aren't sure, we'll
|
|
// do binary operation - this is NEW data in
|
|
// the database, not updating existing info
|
|
Firebird::UtilInterfacePtr()->loadBlob(fbStatus, blobid, DB, M__trans,
|
|
lastInputLine, FB_FALSE);
|
|
res = succeeded();
|
|
}
|
|
|
|
if (!res)
|
|
{
|
|
STDERROUT("Unable to load file");
|
|
done = true;
|
|
}
|
|
break;
|
|
|
|
case SQL_FLOAT:
|
|
fvalue = (float*) datap;
|
|
if (sscanf(lastInputLine, "%g", fvalue) != 1)
|
|
{
|
|
STDERROUT("Input parsing problem");
|
|
done = true;
|
|
}
|
|
break;
|
|
|
|
case SQL_DOUBLE:
|
|
dvalue = (double*) datap;
|
|
if (sscanf(lastInputLine, "%lg", dvalue) != 1)
|
|
{
|
|
STDERROUT("Input parsing problem");
|
|
done = true;
|
|
}
|
|
break;
|
|
|
|
case SQL_TYPE_DATE:
|
|
if (3 != sscanf(lastInputLine, "%d/%d/%d", ×.tm_year,
|
|
×.tm_mon, ×.tm_mday) ||
|
|
!check_date(times))
|
|
{
|
|
IUTILS_msg_get(DATE_ERR, txt, SafeArg() << lastInputLine);
|
|
STDERROUT(txt); // Bad date %s\n
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
--times.tm_mon;
|
|
times.tm_year -= 1900; // tm_year is 1900-based.
|
|
ISC_DATE* date = (ISC_DATE*) datap;
|
|
isc_encode_sql_date(×, date);
|
|
}
|
|
break;
|
|
|
|
case SQL_TYPE_TIME:
|
|
if (3 != sscanf(lastInputLine, "%d:%d:%d", ×.tm_hour,
|
|
×.tm_min, ×.tm_sec) ||
|
|
!check_time(times))
|
|
{
|
|
IUTILS_msg_get(TIME_ERR, txt, SafeArg() << lastInputLine);
|
|
STDERROUT(txt); // Bad time %s\n
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
ISC_TIME* vtime = (ISC_TIME*) datap;
|
|
isc_encode_sql_time(×, vtime);
|
|
}
|
|
break;
|
|
|
|
case SQL_TIMESTAMP:
|
|
if (6 <= sscanf(lastInputLine, "%d/%d/%d %d:%d:%d.%4s",
|
|
×.tm_year, ×.tm_mon, ×.tm_mday,
|
|
×.tm_hour, ×.tm_min, ×.tm_sec,
|
|
msec_str))
|
|
{
|
|
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)
|
|
{
|
|
IUTILS_msg_get(TIMESTAMP_ERR, txt, SafeArg() << lastInputLine);
|
|
STDERROUT(txt); // Bad timestamp %s\n
|
|
}
|
|
else
|
|
{
|
|
--times.tm_mon;
|
|
times.tm_year -= 1900;
|
|
ISC_TIMESTAMP* datetime = (ISC_TIMESTAMP*) datap;
|
|
isc_encode_timestamp(×, datetime);
|
|
datetime->timestamp_time += msec;
|
|
}
|
|
break;
|
|
|
|
case SQL_TEXT:
|
|
case SQL_VARYING:
|
|
varLength = msg->getLength(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
if (length > varLength)
|
|
{
|
|
STDERROUT("String too long"); // msg!
|
|
length = varLength;
|
|
}
|
|
|
|
if (type == SQL_VARYING)
|
|
{
|
|
avary = (vary*) datap;
|
|
avary->vary_length = length;
|
|
memcpy(avary->vary_string, lastInputLine, length);
|
|
}
|
|
else
|
|
{
|
|
achar = (char*) datap;
|
|
memcpy(achar, lastInputLine, length);
|
|
memset(&achar[length], ' ', varLength - length);
|
|
}
|
|
break;
|
|
|
|
case SQL_SHORT:
|
|
case SQL_LONG:
|
|
case SQL_INT64:
|
|
smallint = (SSHORT*) datap;
|
|
integer = (SLONG*) datap;
|
|
pi64 = (SINT64*) datap;
|
|
|
|
scale = msg->getScale(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
|
|
if (scale < 0)
|
|
{
|
|
SSHORT lscale = 0;
|
|
if (!get_numeric((UCHAR*) lastInputLine, length, &lscale, &n))
|
|
{
|
|
STDERROUT("Input parsing problem");
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
int dscale = scale - lscale;
|
|
if (dscale > 0)
|
|
{
|
|
TEXT err_buf[256];
|
|
sprintf(err_buf,
|
|
"input error: input scale %d exceeds the field's scale %d",
|
|
-lscale, -scale);
|
|
STDERROUT(err_buf);
|
|
done = true;
|
|
}
|
|
while (dscale++ < 0)
|
|
n *= 10;
|
|
}
|
|
}
|
|
// sscanf assumes a 64-bit integer target
|
|
else if (sscanf(lastInputLine, "%" SQUADFORMAT, &n) != 1)
|
|
{
|
|
STDERROUT("Input parsing problem");
|
|
done = true;
|
|
}
|
|
if (done)
|
|
break; // Nothing else, we found an error.
|
|
|
|
switch (type)
|
|
{
|
|
case SQL_INT64:
|
|
*pi64 = n;
|
|
break;
|
|
case SQL_SHORT:
|
|
*smallint = n;
|
|
if (SINT64(*smallint) != n)
|
|
{
|
|
STDERROUT("Value too big");
|
|
done = true;
|
|
}
|
|
break;
|
|
case SQL_LONG:
|
|
*integer = n;
|
|
if (SINT64(*integer) != n)
|
|
{
|
|
STDERROUT("Value too big");
|
|
done = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SQL_BOOLEAN:
|
|
boolean = (FB_BOOLEAN*) datap;
|
|
|
|
if (fb_utils::stricmp(lastInputLine, "TRUE") == 0)
|
|
*boolean = 1;
|
|
else if (fb_utils::stricmp(lastInputLine, "FALSE") == 0)
|
|
*boolean = 0;
|
|
else
|
|
{
|
|
STDERROUT("Invalid boolean value");
|
|
done = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
done = true;
|
|
STDERROUT("Unexpected SQLTYPE in add_row()");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!done)
|
|
{
|
|
// having completed all columns, try the insert statement with the msg
|
|
|
|
DB->execute(fbStatus, M__trans, insertstring.length(), insertstring.c_str(),
|
|
isqlGlob.SQL_dialect, msg, msgBuf, NULL, NULL);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (SKIP);
|
|
}
|
|
|
|
|
|
static processing_state blobedit(const TEXT* action, const TEXT* const* cmd)
|
|
{
|
|
/**************************************
|
|
*
|
|
* b l o b e d i t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Edit the text blob indicated by blobid
|
|
*
|
|
* Parameters: cmd -- Array of words interpreted as file name
|
|
*
|
|
**************************************/
|
|
if (!ISQL_dbcheck())
|
|
return FAIL;
|
|
|
|
if (*cmd[1] == 0)
|
|
return ps_ERR;
|
|
|
|
const TEXT* p = cmd[1];
|
|
|
|
// Find the high and low values of the blob id
|
|
ISC_QUAD blobid;
|
|
sscanf(p, "%" xLONGFORMAT":%" xLONGFORMAT, &blobid.gds_quad_high, &blobid.gds_quad_low);
|
|
|
|
// If it isn't an explicit blobedit, then do a dump. Since this is a
|
|
// user operation, put it on the M__trans handle.
|
|
|
|
processing_state rc = SKIP;
|
|
if (!strcmp(action, "BLOBVIEW"))
|
|
{
|
|
Firebird::UtilInterfacePtr utl;
|
|
PathName tmpf = TempFile::create(fbStatus, "blob");
|
|
if (ISQL_errmsg(fbStatus))
|
|
return ps_ERR;
|
|
const char* filename = tmpf.c_str();
|
|
|
|
utl->dumpBlob(fbStatus, &blobid, DB, M__trans, filename, FB_TRUE);
|
|
if (ISQL_errmsg(fbStatus))
|
|
rc = ps_ERR;
|
|
else
|
|
gds__edit(filename, 0);
|
|
|
|
unlink(filename);
|
|
}
|
|
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];
|
|
strip_quotes(cmd[2], path);
|
|
Firebird::UtilInterfacePtr()->dumpBlob(fbStatus, &blobid, DB, M__trans, path, FB_FALSE);
|
|
}
|
|
else
|
|
rc = ps_ERR;
|
|
|
|
if (rc == SKIP && ISQL_errmsg(fbStatus))
|
|
{
|
|
rc = ps_ERR;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
// *******************************
|
|
// 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)
|
|
{
|
|
// 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;
|
|
|
|
// If somebody did a commit or rollback, we are out of a transaction
|
|
|
|
if (!M_Transaction())
|
|
return ps_ERR;
|
|
|
|
// No need to start a default transaction unless there is no current one
|
|
|
|
if (setValues.Autocommit)
|
|
{
|
|
if (!D_Transaction())
|
|
return ps_ERR;
|
|
}
|
|
|
|
// If statistics are requested, then reserve them here
|
|
|
|
SINT64 perf_before[ISQL_COUNTERS];
|
|
if (setValues.Stats)
|
|
{
|
|
Firebird::UtilInterfacePtr()->getPerfCounters(fbStatus,
|
|
DB, ISQL_COUNTERS_SET, perf_before);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
}
|
|
|
|
// Prepare the dynamic query stored in string.
|
|
// But put this on the DDL transaction to get maximum visibility of
|
|
// metadata.
|
|
|
|
if (global_Stmt)
|
|
{
|
|
global_Stmt->free(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
}
|
|
|
|
unsigned prepFlags = Firebird::IStatement::PREPARE_PREFETCH_METADATA |
|
|
(setValues.Plan ? Firebird::IStatement::PREPARE_PREFETCH_DETAILED_PLAN : 0);
|
|
|
|
global_Stmt = DB->prepare(fbStatus, setValues.Autocommit ? D__trans : M__trans, 0, command,
|
|
isqlGlob.SQL_dialect, prepFlags);
|
|
|
|
if (failed())
|
|
{
|
|
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(fbStatus);
|
|
return ps_ERR;
|
|
}
|
|
|
|
// check for warnings
|
|
ISQL_warning(fbStatus);
|
|
|
|
// Find out what kind of statement this is
|
|
const int statement_type = process_request_type();
|
|
if (!statement_type)
|
|
return ps_ERR;
|
|
|
|
/*** ASF: This is util to test others commands too.
|
|
if (statement_type != isc_info_sql_stmt_insert)
|
|
{
|
|
isqlGlob.printf("Only INSERT commands are accepted in bulk mode.%s", NEWLINE);
|
|
return ps_ERR;
|
|
}
|
|
***/
|
|
|
|
Firebird::RefPtr<Firebird::IMessageMetadata> message(global_Stmt->getInputMetadata(fbStatus));
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
|
|
if (setValues.Sqlda_display)
|
|
{
|
|
print_message(message, "IN");
|
|
}
|
|
|
|
const unsigned n_cols = message->getCount(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
if (!n_cols) // 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 PLAN is set, print out the plan now.
|
|
|
|
if (setValues.Plan)
|
|
{
|
|
process_plan();
|
|
if (setValues.Planonly)
|
|
return ret; // Do not execute.
|
|
}
|
|
|
|
unsigned msgSize = message->getMessageLength(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return ps_ERR;
|
|
UCHAR* msgBuf = global_Buffer->getBuffer(msgSize);
|
|
|
|
char bulk_prompt[BUFFER_LENGTH180] = "";
|
|
IUTILS_msg_get(BULK_PROMPT, sizeof(bulk_prompt), bulk_prompt);
|
|
|
|
// Lookup the continuation prompt once
|
|
TEXT con_prompt[MSG_LENGTH];
|
|
IUTILS_msg_get(CON_PROMPT, con_prompt);
|
|
|
|
TEXT msg[MSG_LENGTH];
|
|
bool commit_failedM = false;
|
|
bool commit_failedD = false;
|
|
bool done = false; // This is mostly "done with errors".
|
|
while (!done)
|
|
{
|
|
if (bulk_insert_retriever(bulk_prompt)) // We finished normally, found EOF.
|
|
break;
|
|
|
|
// We only support single line comments and they should be at the first column,
|
|
// no spaces before, etc. Go to read the next line.
|
|
if (lastInputLine[0] == '-' && lastInputLine[1] == '-')
|
|
continue;
|
|
|
|
const char* insert = lastInputLine;
|
|
while (*insert && *insert != '(')
|
|
++insert;
|
|
|
|
if (!*insert || *insert != '(')
|
|
{
|
|
// 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).
|
|
if (fb_utils::strnicmp(lastInputLine, "COMMIT", 6) == 0 ||
|
|
fb_utils::strnicmp(lastInputLine, "COMMIT WORK", 11) == 0)
|
|
{
|
|
done = true;
|
|
|
|
if (!commit_trans(&M__trans))
|
|
{
|
|
commit_failedM = true;
|
|
break; // We failed to commit, quit the bulk insertion.
|
|
}
|
|
|
|
if (!M_Transaction())
|
|
{
|
|
commit_failedM = false;
|
|
break; // We failed to start transaction, quit the bulk insertion.
|
|
}
|
|
|
|
// CVC: Commit may fail with AUTO-DDL off and DDL changes rejected by DFW.
|
|
if (D__trans)
|
|
{
|
|
if (!commit_trans(&D__trans))
|
|
{
|
|
commit_failedD = true;
|
|
break; // We failed to commit, quit the bulk insertion.
|
|
}
|
|
|
|
if (!D_Transaction())
|
|
{
|
|
commit_failedD = false;
|
|
break; // We failed to commit, quit the bulk insertion.
|
|
}
|
|
}
|
|
|
|
done = false;
|
|
continue; // Go to read another line.
|
|
}
|
|
break; // For example, STOP or blank line not inside quoted string.
|
|
}
|
|
|
|
++insert;
|
|
|
|
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.
|
|
|
|
for (unsigned i = 0, textFieldIter = 0; i < n_cols && !done; ++i)
|
|
{
|
|
unsigned offset = message->getNullOffset(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return ps_ERR;
|
|
short* nullp = (short*) &msgBuf[offset];
|
|
|
|
offset = message->getOffset(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return ps_ERR;
|
|
void* datap = &msgBuf[offset];
|
|
|
|
unsigned type = message->getType(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
const bool nullable = message->isNullable(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
|
|
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:
|
|
//case '\n':
|
|
case '\r':
|
|
case '\t':
|
|
case ',':
|
|
case ')':
|
|
go = false;
|
|
--insert;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SCHAR cmd[5] = "";
|
|
unsigned 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';
|
|
IUTILS_make_upper(cmd);
|
|
}
|
|
|
|
// If the user writes NULL, put a null in the column.
|
|
|
|
if (!strcmp(cmd, "NULL"))
|
|
*nullp = -1;
|
|
else
|
|
{
|
|
*nullp = 0;
|
|
// Data types
|
|
SSHORT* smallint;
|
|
SLONG* integer;
|
|
SINT64* pi64;
|
|
SINT64 n;
|
|
float* fvalue;
|
|
double* dvalue;
|
|
UCHAR* boolean;
|
|
vary* avary;
|
|
char* achar;
|
|
tm times;
|
|
// Initialize the time structure.
|
|
memset(×, 0, sizeof(times));
|
|
char msec_str[5] = "";
|
|
int msec = 0;
|
|
ISC_QUAD* blobid;
|
|
unsigned varLength;
|
|
int scale;
|
|
|
|
switch (type)
|
|
{
|
|
case SQL_BLOB:
|
|
blobid = (ISC_QUAD*) datap;
|
|
|
|
{ // scope
|
|
Firebird::IBlob* bs = DB->createBlob(fbStatus, M__trans, blobid, 0, NULL);
|
|
if (failed())
|
|
{
|
|
STDERROUT("Unable to create blob");
|
|
ISQL_errmsg(fbStatus);
|
|
done = true;
|
|
break; // Quit the switch()
|
|
}
|
|
|
|
bs->putSegment(fbStatus, length, lastPos);
|
|
if (failed())
|
|
{
|
|
STDERROUT("Unable to write to blob");
|
|
ISQL_errmsg(fbStatus);
|
|
done = true;
|
|
}
|
|
bs->close(fbStatus);
|
|
if (failed())
|
|
{
|
|
STDERROUT("Unable to close blob");
|
|
ISQL_errmsg(fbStatus);
|
|
done = true;
|
|
}
|
|
} // scope
|
|
|
|
break;
|
|
|
|
case SQL_FLOAT:
|
|
fvalue = (float*) datap;
|
|
if (sscanf(lastPos, "%g", fvalue) != 1)
|
|
{
|
|
STDERROUT("Input parsing problem in 'float' value");
|
|
done = true;
|
|
}
|
|
break;
|
|
|
|
case SQL_DOUBLE:
|
|
dvalue = (double*) datap;
|
|
if (sscanf(lastPos, "%lg", dvalue) != 1)
|
|
{
|
|
STDERROUT("Input parsing problem in 'double' value");
|
|
done = true;
|
|
}
|
|
break;
|
|
|
|
case SQL_TYPE_DATE:
|
|
if (3 != sscanf(lastPos, "%d-%d-%d", ×.tm_year,
|
|
×.tm_mon, ×.tm_mday) ||
|
|
!check_date(times))
|
|
{
|
|
IUTILS_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*) datap;
|
|
isc_encode_sql_date(×, date);
|
|
}
|
|
break;
|
|
|
|
case SQL_TYPE_TIME:
|
|
if (3 != sscanf(lastPos, "%d:%d:%d", ×.tm_hour,
|
|
×.tm_min, ×.tm_sec) ||
|
|
!check_time(times))
|
|
{
|
|
IUTILS_msg_get(TIME_ERR, msg, SafeArg() << lastPos);
|
|
STDERROUT(msg); // Bad time %s\n
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
ISC_TIME* vtime = (ISC_TIME*) datap;
|
|
isc_encode_sql_time(×, vtime);
|
|
}
|
|
break;
|
|
|
|
case SQL_TIMESTAMP:
|
|
if (6 <= sscanf(lastPos, "%d-%d-%d %d:%d:%d.%4s",
|
|
×.tm_year, ×.tm_mon, ×.tm_mday,
|
|
×.tm_hour, ×.tm_min, ×.tm_sec,
|
|
msec_str))
|
|
{
|
|
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)
|
|
{
|
|
IUTILS_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*) datap;
|
|
isc_encode_timestamp(×, datetime);
|
|
datetime->timestamp_time += msec;
|
|
}
|
|
break;
|
|
|
|
case SQL_TEXT:
|
|
case SQL_VARYING:
|
|
varLength = message->getLength(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
if (length > varLength)
|
|
{
|
|
STDERROUT("String too long"); // msg!
|
|
length = varLength;
|
|
}
|
|
|
|
if (type == SQL_VARYING)
|
|
{
|
|
avary = (vary*) datap;
|
|
avary->vary_length = length;
|
|
memcpy(avary->vary_string, lastPos, length);
|
|
}
|
|
else
|
|
{
|
|
achar = (char*) datap;
|
|
memcpy(achar, lastPos, length);
|
|
memset(&achar[length], ' ', varLength - length);
|
|
}
|
|
break;
|
|
|
|
case SQL_SHORT:
|
|
case SQL_LONG:
|
|
case SQL_INT64:
|
|
smallint = (SSHORT*) datap;
|
|
integer = (SLONG*) datap;
|
|
pi64 = (SINT64*) datap;
|
|
|
|
scale = message->getScale(fbStatus, i);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
|
|
if (scale < 0)
|
|
{
|
|
SSHORT lscale = 0;
|
|
if (!get_numeric((UCHAR*) lastPos, length, &lscale, &n))
|
|
{
|
|
STDERROUT("Input parsing problem in 'numeric' or 'decimal' value");
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
int dscale = scale - lscale;
|
|
if (dscale > 0)
|
|
{
|
|
TEXT err_buf[256];
|
|
sprintf(err_buf,
|
|
"Input error: input scale %d exceeds the field's scale %d",
|
|
-lscale, -scale);
|
|
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;
|
|
}
|
|
if (done)
|
|
continue; // Nothing else, we found an error.
|
|
|
|
switch (type)
|
|
{
|
|
case SQL_INT64:
|
|
*pi64 = n;
|
|
break;
|
|
case SQL_SHORT:
|
|
*smallint = n;
|
|
if (SINT64(*smallint) != n)
|
|
{
|
|
STDERROUT("Integer value too big");
|
|
done = true;
|
|
}
|
|
break;
|
|
case SQL_LONG:
|
|
*integer = n;
|
|
if (SINT64(*integer) != n)
|
|
{
|
|
STDERROUT("Integer value too big");
|
|
done = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SQL_BOOLEAN:
|
|
boolean = (FB_BOOLEAN*) datap;
|
|
if (fb_utils::stricmp(lastInputLine, "TRUE") == 0)
|
|
*boolean = 1;
|
|
else if (fb_utils::stricmp(lastInputLine, "FALSE") == 0)
|
|
*boolean = 0;
|
|
else
|
|
{
|
|
STDERROUT("Invalid boolean value");
|
|
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:
|
|
//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 message.
|
|
// This is a non-select DML statement or trans.
|
|
|
|
global_Stmt->execute(fbStatus, M__trans, message, msgBuf, NULL, NULL);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Check for warnings.
|
|
ISQL_warning(fbStatus);
|
|
}
|
|
} // while (!done)
|
|
|
|
if (done)
|
|
{
|
|
// Save whatever we were able to pump, except when the failure was the commit itself.
|
|
if (M__trans)
|
|
{
|
|
if (commit_failedM)
|
|
{
|
|
M__trans->rollback(fbStatus);
|
|
if (succeeded())
|
|
M__trans = NULL;
|
|
}
|
|
else
|
|
commit_trans(&M__trans);
|
|
}
|
|
|
|
if (D__trans)
|
|
{
|
|
if (commit_failedD)
|
|
{
|
|
D__trans->rollback(fbStatus);
|
|
if (succeeded())
|
|
D__trans = NULL;
|
|
}
|
|
else
|
|
commit_trans(&D__trans);
|
|
}
|
|
|
|
TEXT errbuf[MSG_LENGTH];
|
|
sprintf(errbuf, "Stopped prematurely due to error in line %d with text:",
|
|
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.
|
|
Filelist->gotoEof();
|
|
|
|
ret = ps_ERR;
|
|
}
|
|
|
|
// Get rid of the statement handle.
|
|
global_Stmt->free(fbStatus);
|
|
if (succeeded())
|
|
global_Stmt = NULL;
|
|
|
|
// Statistics printed here upon request
|
|
if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
|
|
ret = ps_ERR;
|
|
|
|
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_SQL_LENGTH)
|
|
{
|
|
TEXT msg[MSG_LENGTH];
|
|
IUTILS_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, unsigned* colnumber)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c o l _ c h e c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Check for peculiarities not currently revealed by the SQLDA
|
|
* colnumber array records the mapping of select columns to insert
|
|
* columns which do not have an equivalent for array or computed cols.
|
|
**************************************/
|
|
|
|
if (!frontendTransaction())
|
|
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] = ~0u;
|
|
else
|
|
colnumber[i] = j++;
|
|
++i;
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
END_ERROR;
|
|
}
|
|
|
|
|
|
static processing_state copy_table(TEXT* source,
|
|
TEXT* destination,
|
|
TEXT* otherdb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c o p y _ t a b l e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Create a new table based on an existing one.
|
|
*
|
|
* Parameters: source -- name of source table
|
|
* destination == name of newly created table
|
|
*
|
|
**************************************/
|
|
if (!source[0] || !destination[0])
|
|
{
|
|
STDERROUT("Either source or destination tables are missing");
|
|
return SKIP;
|
|
}
|
|
|
|
TEXT errbuf[MSG_LENGTH];
|
|
|
|
// Call list_table with a temporary file, then hand that file to a
|
|
// new version of isql
|
|
|
|
FILE* const holdout = isqlGlob.Out;
|
|
|
|
// If there is an alternate database, extract the domains
|
|
const bool domain_flag = otherdb[0];
|
|
|
|
const Firebird::PathName ftmp = TempFile::create(SCRATCH);
|
|
isqlGlob.Out = fopen(ftmp.c_str(), "w+b");
|
|
if (!isqlGlob.Out)
|
|
{
|
|
// If we can't open a temp file then bail
|
|
|
|
IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << ftmp.c_str());
|
|
STDERROUT(errbuf);
|
|
Exit_value = FINI_ERROR;
|
|
isqlGlob.Out = holdout;
|
|
return END;
|
|
}
|
|
|
|
chop_at(source, QUOTEDLENGTH);
|
|
if (source[0] != DBL_QUOTE)
|
|
IUTILS_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) {
|
|
IUTILS_copy_SQL_id(source_tbl, source, DBL_QUOTE);
|
|
}
|
|
else
|
|
{
|
|
strcpy(source, source_tbl);
|
|
IUTILS_make_upper(source);
|
|
}
|
|
*/
|
|
|
|
chop_at(destination, QUOTEDLENGTH);
|
|
if (destination[0] != DBL_QUOTE)
|
|
IUTILS_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) {
|
|
IUTILS_copy_SQL_id(destination_tbl, destination, DBL_QUOTE);
|
|
}
|
|
else
|
|
{
|
|
strcpy(destination, destination_tbl);
|
|
IUTILS_make_upper(destination);
|
|
}
|
|
*/
|
|
|
|
if (EXTRACT_list_table(source, destination, domain_flag, -1))
|
|
{
|
|
IUTILS_msg_get(NOT_FOUND_MSG, errbuf, SafeArg() << source);
|
|
STDERROUT(errbuf);
|
|
fclose(isqlGlob.Out);
|
|
}
|
|
else
|
|
{
|
|
fclose(isqlGlob.Out);
|
|
|
|
// easy to make a copy in another database
|
|
const TEXT* altdb = isqlGlob.global_Db_name;
|
|
if (*otherdb)
|
|
altdb = otherdb;
|
|
TEXT cmd[MAXPATHLEN * 2 + 20];
|
|
sprintf(cmd, "isql -q %s -i %s", altdb, ftmp.c_str());
|
|
if (system(cmd))
|
|
{
|
|
IUTILS_msg_get(COPY_ERR, errbuf, SafeArg() << destination << altdb);
|
|
STDERROUT(errbuf);
|
|
}
|
|
}
|
|
|
|
unlink(ftmp.c_str());
|
|
isqlGlob.Out = holdout;
|
|
|
|
return (SKIP);
|
|
}
|
|
|
|
|
|
static void appendClause(Firebird::string& to, const char* label, const TEXT* value, char quote = 0)
|
|
{
|
|
to += ' ';
|
|
to += label;
|
|
to += ' ';
|
|
if (quote)
|
|
to += quote;
|
|
to += value;
|
|
if (quote)
|
|
to += quote;
|
|
to += ' ';
|
|
}
|
|
|
|
|
|
static processing_state create_db(const TEXT* statement, TEXT* d_name)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c r e a t e _ d b
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Intercept create database commands to
|
|
* adjust the DB and transaction handles
|
|
*
|
|
* Parameters: statement == the entire statement for processing.
|
|
*
|
|
* Note: SQL ROLE setting must be taken into an account no matter
|
|
* that the newly created database will not have any roles defined
|
|
* in it. Role may affect right to create new database.
|
|
*
|
|
**************************************/
|
|
processing_state ret = SKIP;
|
|
|
|
// Disconnect from the database and cleanup
|
|
ISQL_disconnect_database(false);
|
|
|
|
// Parse statement to tokens
|
|
const char* quotes = "\"'";
|
|
Firebird::string nlStatement(statement);
|
|
nlStatement += '\n';
|
|
Firebird::Tokens toks;
|
|
toks.quotes(quotes);
|
|
toks.parse(0, nlStatement.c_str());
|
|
|
|
const unsigned KEY_USER = 0;
|
|
const unsigned KEY_PASS = 1;
|
|
const unsigned KEY_ROLE = 2;
|
|
const unsigned KEY_NAMES = 3;
|
|
const unsigned KEY_SET = 4;
|
|
|
|
struct Key
|
|
{
|
|
const char* text;
|
|
bool has;
|
|
};
|
|
|
|
Key keys[5] = {
|
|
{ "USER", false },
|
|
{ "PASSWORD", false },
|
|
{ "ROLE", false },
|
|
{ "NAMES", false },
|
|
{ "SET", false }
|
|
};
|
|
|
|
for (unsigned t = 0; t < toks.getCount(); ++t)
|
|
{
|
|
Firebird::NoCaseString token(toks[t].text, toks[t].length);
|
|
unsigned k;
|
|
|
|
for (k = 0; k < FB_NELEM(keys); ++k)
|
|
{
|
|
if (token == keys[k].text)
|
|
{
|
|
if (k != KEY_NAMES || keys[KEY_SET].has)
|
|
keys[k].has = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (k != KEY_SET)
|
|
keys[KEY_SET].has = false;
|
|
}
|
|
|
|
for (int createWithoutRole = 0; createWithoutRole < 2; ++createWithoutRole)
|
|
{
|
|
ret = SKIP;
|
|
TEXT usr[USER_LENGTH];
|
|
TEXT psw[PASSWORD_LENGTH];
|
|
TEXT role[ROLE_LENGTH];
|
|
|
|
Firebird::string modifiedCreateStatement(statement);
|
|
|
|
TEXT quote = DBL_QUOTE;
|
|
const TEXT* p = NULL;
|
|
|
|
// If there is a global parameter, we will set it into the create stmt.
|
|
if (global_usr || global_role || global_psw ||
|
|
(*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET)))
|
|
{
|
|
strcpy(usr, isqlGlob.User);
|
|
strip_quotes(Password, psw);
|
|
strcpy(role, isqlGlob.Role);
|
|
|
|
Firebird::string clauses;
|
|
|
|
if (global_usr && !keys[KEY_USER].has)
|
|
appendClause(clauses, keys[KEY_USER].text, usr);
|
|
|
|
if (global_psw && !keys[KEY_PASS].has)
|
|
appendClause(clauses, keys[KEY_PASS].text, psw, SINGLE_QUOTE);
|
|
|
|
if (global_role && (!keys[KEY_ROLE].has) && createWithoutRole == 0)
|
|
appendClause(clauses, keys[KEY_ROLE].text, role);
|
|
|
|
if (*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET)&& !keys[KEY_NAMES].has)
|
|
{
|
|
Firebird::string setNames = keys[KEY_SET].text;
|
|
setNames += ' ';
|
|
setNames += keys[KEY_NAMES].text;
|
|
appendClause(clauses, setNames.c_str(), setValues.ISQL_charset, SINGLE_QUOTE);
|
|
}
|
|
|
|
if (toks.getCount() > 3)
|
|
modifiedCreateStatement.insert(toks[3].origin, clauses);
|
|
else
|
|
modifiedCreateStatement += clauses;
|
|
}
|
|
|
|
// execute the create statement
|
|
// If the isqlGlob.SQL_dialect is not set or set to 2, create the database
|
|
// as a dialect 3 database.
|
|
unsigned dialect =
|
|
(isqlGlob.SQL_dialect == 0 || isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION) ?
|
|
requested_SQL_dialect : isqlGlob.SQL_dialect;
|
|
|
|
DB = Firebird::UtilInterfacePtr()->executeCreateDatabase(fbStatus, modifiedCreateStatement.length(),
|
|
modifiedCreateStatement.c_str(), dialect, NULL);
|
|
|
|
if ((!DB) && (createWithoutRole == 0) && (fbStatus->getErrors()[1] == isc_dsql_error))
|
|
{
|
|
// OLd server failed to parse ROLE clause
|
|
continue;
|
|
}
|
|
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
ret = FAIL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (DB)
|
|
{
|
|
// Make it read owner name to display grantor correctly
|
|
SHOW_read_owner();
|
|
|
|
// No use in cancel when running non-end-user operators
|
|
DB->cancelOperation(fbStatus, fb_cancel_disable);
|
|
|
|
// Load isqlGlob.global_Db_name with some value to show a successful attach
|
|
|
|
// 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.
|
|
Firebird::string s;
|
|
if (toks.getCount() > 2)
|
|
s.assign(toks[2].text, toks[2].length);
|
|
else
|
|
s = d_name;
|
|
strip_quotes(s.c_str(), isqlGlob.global_Db_name);
|
|
|
|
ISQL_get_version(true);
|
|
|
|
// Start the user transaction
|
|
|
|
if (!M__trans)
|
|
{
|
|
M_Transaction();
|
|
if (D__trans)
|
|
commit_trans(&D__trans);
|
|
if (setValues.Autocommit)
|
|
D_Transaction();
|
|
}
|
|
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
static void do_isql()
|
|
{
|
|
/**************************************
|
|
*
|
|
* d o _ i s q l
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Process incoming SQL statements, using the global message metadata
|
|
*
|
|
**************************************/
|
|
TEXT errbuf[MSG_LENGTH];
|
|
|
|
// Initialized user transactions
|
|
|
|
M__trans = 0;
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1400 && _MSC_VER < 1900
|
|
_set_output_format(_TWO_DIGIT_EXPONENT);
|
|
#endif
|
|
|
|
#ifdef WIN_NT
|
|
SetConsoleCtrlHandler(query_abort, TRUE);
|
|
#else
|
|
fb_shutdown_callback(0, query_abort, fb_shut_confirmation, 0);
|
|
#endif
|
|
|
|
// Open database and start tansaction
|
|
|
|
//
|
|
// We will not execute this for now on WINDOWS. We are not prompting for
|
|
// a database name, username and password. A connect statement has to be in
|
|
// the file containing the script.
|
|
|
|
newdb(isqlGlob.global_Db_name, isqlGlob.User, Password, global_numbufs, isqlGlob.Role, true);
|
|
|
|
// If that failed or no Dbname was specified
|
|
|
|
ISQL_dbcheck();
|
|
|
|
// Read statements and process them from Ifp until the ret
|
|
// code tells us we are done
|
|
|
|
Firebird::string stmt;
|
|
processing_state ret;
|
|
|
|
bool done = false;
|
|
while (!done)
|
|
{
|
|
|
|
if (Abort_flag)
|
|
{
|
|
if (D__trans)
|
|
{
|
|
D__trans->rollback(fbStatus);
|
|
if (succeeded())
|
|
D__trans = NULL;
|
|
}
|
|
|
|
if (M__trans)
|
|
{
|
|
M__trans->rollback(fbStatus);
|
|
if (succeeded())
|
|
M__trans = NULL;
|
|
}
|
|
|
|
if (fbTrans)
|
|
{
|
|
fbTrans->rollback(fbStatus);
|
|
if (succeeded())
|
|
fbTrans = NULL;
|
|
}
|
|
|
|
// If there is current user statement, free it
|
|
if (global_Stmt)
|
|
{
|
|
global_Stmt->free(fbStatus);
|
|
if (succeeded())
|
|
global_Stmt = NULL;
|
|
}
|
|
|
|
if (DB)
|
|
{
|
|
DB->detach(fbStatus);
|
|
if (succeeded())
|
|
DB = NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (Interrupt_flag)
|
|
{
|
|
// SIGINT caught in interactive mode
|
|
Interrupt_flag = false;
|
|
|
|
if (Input_file)
|
|
{
|
|
// close input files going back to stdin
|
|
Filelist->clear(stdin);
|
|
|
|
// should have two stdin in Filelist
|
|
fb_assert(Filelist->count() == 2);
|
|
|
|
Filelist->removeIntoIfp();
|
|
Input_file = false;
|
|
}
|
|
}
|
|
|
|
ret = get_statement(stmt, sql_prompt);
|
|
|
|
// If there is no database yet, remind us of the need to connect
|
|
|
|
// But don't execute the statement
|
|
|
|
if (!isqlGlob.global_Db_name[0] && (ret == CONT))
|
|
{
|
|
if (!Quiet)
|
|
{
|
|
IUTILS_msg_get(NO_DB, errbuf);
|
|
STDERROUT(errbuf);
|
|
}
|
|
if (!Interactive && setValues.BailOnError)
|
|
ret = FAIL;
|
|
else
|
|
ret = SKIP;
|
|
}
|
|
|
|
switch (ret)
|
|
{
|
|
case CONT:
|
|
if (process_statement(stmt.c_str()) == ps_ERR)
|
|
{
|
|
Exit_value = FINI_ERROR;
|
|
if (!Interactive && setValues.BailOnError)
|
|
Abort_flag = true;
|
|
}
|
|
break;
|
|
|
|
case END:
|
|
case EOF:
|
|
case EXIT:
|
|
if (Abort_flag)
|
|
{
|
|
if (D__trans)
|
|
{
|
|
D__trans->rollback(fbStatus);
|
|
if (succeeded())
|
|
D__trans = NULL;
|
|
}
|
|
|
|
if (M__trans)
|
|
{
|
|
M__trans->rollback(fbStatus);
|
|
if (succeeded())
|
|
M__trans = NULL;
|
|
}
|
|
|
|
if (fbTrans)
|
|
{
|
|
fbTrans->rollback(fbStatus);
|
|
if (succeeded())
|
|
fbTrans = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (D__trans)
|
|
commit_trans(&D__trans);
|
|
if (M__trans)
|
|
commit_trans(&M__trans);
|
|
if (fbTrans)
|
|
commit_trans(&fbTrans);
|
|
}
|
|
|
|
// If there is current user statement, free it
|
|
// I think DSQL_drop is the right one, but who knows
|
|
if (global_Stmt)
|
|
{
|
|
global_Stmt->free(fbStatus);
|
|
if (succeeded())
|
|
global_Stmt = NULL;
|
|
}
|
|
|
|
if (DB)
|
|
{
|
|
DB->detach(fbStatus);
|
|
if (succeeded())
|
|
DB = NULL;
|
|
}
|
|
|
|
done = true;
|
|
break;
|
|
|
|
case BACKOUT:
|
|
if (D__trans)
|
|
{
|
|
D__trans->rollback(fbStatus);
|
|
if (succeeded())
|
|
D__trans = NULL;
|
|
}
|
|
|
|
if (M__trans)
|
|
{
|
|
M__trans->rollback(fbStatus);
|
|
if (succeeded())
|
|
M__trans = NULL;
|
|
}
|
|
|
|
if (fbTrans)
|
|
{
|
|
fbTrans->rollback(fbStatus);
|
|
if (succeeded())
|
|
fbTrans = NULL;
|
|
}
|
|
|
|
// If there is current user statement, free it
|
|
if (global_Stmt)
|
|
{
|
|
global_Stmt->free(fbStatus);
|
|
if (succeeded())
|
|
global_Stmt = NULL;
|
|
}
|
|
|
|
if (DB)
|
|
{
|
|
DB->detach(fbStatus);
|
|
if (succeeded())
|
|
DB = NULL;
|
|
}
|
|
|
|
done = true;
|
|
break;
|
|
|
|
case ERR_BUFFER_OVERFLOW:
|
|
IUTILS_msg_get(BUFFER_OVERFLOW, errbuf);
|
|
STDERROUT(errbuf);
|
|
|
|
case EXTRACT:
|
|
case EXTRACTALL:
|
|
default:
|
|
// fb_assert (FALSE); -- removed as finds too many problems
|
|
case ps_ERR:
|
|
case FAIL:
|
|
Exit_value = FINI_ERROR;
|
|
if (!Interactive && setValues.BailOnError)
|
|
Abort_flag = true;
|
|
break;
|
|
|
|
case SKIP:
|
|
break;
|
|
}
|
|
}
|
|
|
|
global_Stmt = NULL;
|
|
DB = NULL;
|
|
isqlGlob.global_Db_name[0] = '\0';
|
|
D__trans = NULL;
|
|
M__trans = NULL;
|
|
fbTrans = NULL;
|
|
|
|
InputDevices::indev& Ofp = Filelist->Ofp();
|
|
// Does it have a valid Temp file pointer?
|
|
if (Ofp.indev_fpointer)
|
|
Ofp.drop();
|
|
|
|
// CVC: If we were halt by an error and Bail, we have pending cleanup.
|
|
Filelist->clear();
|
|
if (lastInputLine)
|
|
free(lastInputLine);
|
|
|
|
setValues.global_Cols.clear(); // The destructor would do anyway if we don't reach this point.
|
|
}
|
|
|
|
|
|
static processing_state drop_db()
|
|
{
|
|
/**************************************
|
|
*
|
|
* d r o p _ d b
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Drop the current database
|
|
*
|
|
**************************************/
|
|
if (DB && isqlGlob.global_Db_name[0])
|
|
{
|
|
RELEASE_REQUESTS FOR DB;
|
|
|
|
DB->dropDatabase(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return (FAIL);
|
|
}
|
|
}
|
|
else
|
|
return (FAIL);
|
|
|
|
// The database got dropped with or without errors
|
|
M__trans = NULL;
|
|
fbTrans = NULL;
|
|
global_Stmt = NULL;
|
|
D__trans = NULL;
|
|
|
|
// CVC: If we aren't connected to a db anymore, then the db's dialect is reset.
|
|
// This should fix SF Bug #910430.
|
|
isqlGlob.db_SQL_dialect = 0;
|
|
// BRS this is also needed to fix #910430.
|
|
global_dialect_spoken = 0;
|
|
|
|
// Zero database name
|
|
|
|
isqlGlob.global_Db_name[0] = '\0';
|
|
DB = NULL;
|
|
|
|
return (SKIP);
|
|
}
|
|
|
|
|
|
static processing_state edit(const TEXT* const* cmd)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e d i t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Edit the current file or named file
|
|
*
|
|
* Parameters: cmd -- Array of words interpreted as file name
|
|
* The result of calling this is to point the global input file
|
|
* pointer, Ifp, to the named file after editing or the tmp file.
|
|
*
|
|
**************************************/
|
|
|
|
// Set up editing command for shell
|
|
|
|
const TEXT* file = cmd[1];
|
|
|
|
// If there is a file name specified, try to open it
|
|
|
|
processing_state rc = SKIP;
|
|
if (*file)
|
|
{
|
|
TEXT path[MAXPATHLEN];
|
|
strip_quotes(file, path);
|
|
FILE* fp = fopen(path, "r");
|
|
if (fp)
|
|
{
|
|
// Push the current ifp on the indev
|
|
|
|
Filelist->insertIfp();
|
|
Filelist->Ifp().init(fp, path, path);
|
|
gds__edit(path, 0);
|
|
Input_file = true;
|
|
getColumn = -1;
|
|
}
|
|
else
|
|
{
|
|
TEXT errbuf[MSG_LENGTH];
|
|
IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << path);
|
|
STDERROUT(errbuf);
|
|
rc = ps_ERR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No file given, edit the temp file
|
|
|
|
Filelist->insertIfp();
|
|
// Close the file, edit it, then reopen and read from the top
|
|
InputDevices::indev& Ofp = Filelist->Ofp();
|
|
|
|
if (!Ofp.indev_fpointer)
|
|
{
|
|
// File used to edit sessions
|
|
const Firebird::PathName filename = TempFile::create(SCRATCH);
|
|
const char* Tmpfile = filename.c_str();
|
|
FILE* f = fopen(Tmpfile, "w+"); // It was w+b
|
|
if (f)
|
|
{
|
|
Ofp.init(f, Tmpfile, Tmpfile);
|
|
Filelist->commandsToFile(f);
|
|
}
|
|
else
|
|
{
|
|
// If we can't open a temp file then bail
|
|
TEXT errbuf[MSG_LENGTH];
|
|
IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << Tmpfile);
|
|
STDERROUT(errbuf);
|
|
return ps_ERR;
|
|
}
|
|
}
|
|
|
|
Ofp.close();
|
|
PathName tmpfile = Ofp.fileName(false);
|
|
gds__edit(tmpfile.c_str(), 0);
|
|
Ofp.init(fopen(tmpfile.c_str(), "r+"), tmpfile.c_str(), tmpfile.c_str()); // We don't check for failure.
|
|
Filelist->Ifp().init(Ofp);
|
|
Input_file = true;
|
|
getColumn = -1;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static processing_state end_trans()
|
|
{
|
|
/**************************************
|
|
*
|
|
* e n d _ t r a n s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Prompt the interactive user if there is an extant transaction and
|
|
* either commit or rollback
|
|
*
|
|
* Called by newtrans, createdb, newdb;
|
|
* Returns success or failure.
|
|
*
|
|
**************************************/
|
|
|
|
TEXT infobuf[BUFFER_LENGTH60];
|
|
|
|
processing_state ret = CONT;
|
|
// Give option of committing or rolling back before proceding unless
|
|
// the last command was a commit or rollback
|
|
|
|
if (M__trans)
|
|
{
|
|
if (Interactive)
|
|
{
|
|
IUTILS_msg_get(COMMIT_PROMPT, sizeof(infobuf), infobuf);
|
|
readNextInputLine(infobuf);
|
|
getColumn = -1; // We are bypassing getNextInputChar().
|
|
if (lastInputLine && isyesno(lastInputLine))
|
|
{
|
|
// check for Yes answer
|
|
IUTILS_msg_get(COMMIT_MSG, sizeof(infobuf), infobuf);
|
|
STDERROUT(infobuf);
|
|
if (DB && M__trans)
|
|
{
|
|
M__trans->commit(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
// Commit failed, so roll back anyway
|
|
ret = FAIL;
|
|
}
|
|
else
|
|
M__trans = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IUTILS_msg_get(ROLLBACK_MSG, sizeof(infobuf), infobuf);
|
|
STDERROUT(infobuf);
|
|
if (DB && M__trans)
|
|
{
|
|
M__trans->rollback(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
ret = FAIL;
|
|
else
|
|
M__trans = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No answer, just roll back by default
|
|
|
|
if (DB && M__trans)
|
|
{
|
|
// For WISQL, we keep track of whether a commit is needed by setting a flag in the ISQLPB
|
|
// structure. This flag is set whenever a sql command is entered in the SQL input area or
|
|
// if the user uses the create database dialog. Because of this, this should only show up if
|
|
// the user connects and then disconnects or if the user enters a SET TRANSACTION stat without
|
|
// ever doing anything that would cause changes to the dictionary.
|
|
IUTILS_msg_get(ROLLBACK_MSG, sizeof(infobuf), infobuf);
|
|
STDERROUT(infobuf);
|
|
M__trans->rollback(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
ret = FAIL;
|
|
else
|
|
M__trans = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Commit background transaction
|
|
|
|
if (DB && D__trans)
|
|
{
|
|
D__trans->commit(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
ret = FAIL;
|
|
else
|
|
D__trans = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static processing_state escape(const TEXT* cmd)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e s c a p e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Permit a shell escape to system call
|
|
*
|
|
* Parameters: cmd -- The command string with SHELL
|
|
*
|
|
**************************************/
|
|
|
|
// Advance past the shell
|
|
|
|
const TEXT* shellcmd = cmd;
|
|
|
|
// Search past the 'shell' keyword
|
|
shellcmd += strlen("shell");
|
|
|
|
// Eat whitespace at beginning of command
|
|
while (*shellcmd && fb_isspace(*shellcmd))
|
|
shellcmd++;
|
|
|
|
#ifdef WIN_NT
|
|
// MSDN says: You must explicitly flush (using fflush or _flushall)
|
|
// or close any stream before calling system.
|
|
// 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;
|
|
if (Filelist->Ofp().indev_fpointer)
|
|
Filelist->Ofp().getPos(&OfpPos);
|
|
fflush(NULL); // Flush only output buffers.
|
|
const char* emptyCmd = "%ComSpec%";
|
|
#else
|
|
const char* emptyCmd = "$SHELL";
|
|
#endif
|
|
|
|
// If no command given just spawn a shell
|
|
if (!*shellcmd)
|
|
shellcmd = emptyCmd;
|
|
|
|
const int rc = system(shellcmd);
|
|
|
|
#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.
|
|
if (Filelist->sameInputAndOutput())
|
|
Filelist->Ofp().setPos(&OfpPos);
|
|
#endif
|
|
|
|
return rc ? FAIL : SKIP;
|
|
}
|
|
|
|
|
|
static processing_state frontend(const TEXT* statement)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f r o n t e n d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Handle any frontend commands that start with
|
|
* show or set converting the string into an
|
|
* array of words parms, with MAX_TERMS words only.
|
|
*
|
|
* Parameters: statement is the string typed by the user
|
|
*
|
|
**************************************/
|
|
|
|
class FrontOptions : public OptionsBase
|
|
{
|
|
public:
|
|
enum front_commands
|
|
{
|
|
show, add, copy,
|
|
blobview, output, shell, set, create, drop, connect,
|
|
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},
|
|
{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},
|
|
{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];
|
|
|
|
// 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;
|
|
}
|
|
TEXT parm_defaults[MAX_TERMS][1];
|
|
|
|
// Any whitespace and comments at the beginning are already swallowed by get_statement()
|
|
|
|
// Set beginning of statement past comment
|
|
const TEXT* const cmd = statement;
|
|
if (!*cmd)
|
|
{
|
|
// In case any default transaction was started - commit it here
|
|
if (fbTrans)
|
|
commit_trans(&fbTrans);
|
|
|
|
return SKIP;
|
|
}
|
|
|
|
frontend_load_parms(statement, parms, lparms, parm_defaults);
|
|
|
|
char bad_dialect_buf[512];
|
|
bool bad_dialect = false;
|
|
|
|
// Look to see if the words (parms) match any known verbs. If nothing
|
|
// matches then just hand the statement to process_statement
|
|
processing_state ret = SKIP;
|
|
const FrontOptions frontoptions(options, FB_NELEM(options), FrontOptions::wrong);
|
|
switch (frontoptions.getCommand(parms[0]))
|
|
{
|
|
case FrontOptions::show:
|
|
if (DB && !frontendTransaction())
|
|
{
|
|
// Free the frontend command
|
|
frontend_free_parms(parms, lparms, parm_defaults);
|
|
return FAIL;
|
|
}
|
|
|
|
ret = SHOW_metadata(parms, lparms);
|
|
if (fbTrans)
|
|
commit_trans(&fbTrans);
|
|
break;
|
|
|
|
case FrontOptions::add:
|
|
if (!frontendTransaction())
|
|
{
|
|
// Free the frontend command
|
|
frontend_free_parms(parms, lparms, parm_defaults);
|
|
return FAIL;
|
|
}
|
|
|
|
ret = add_row(lparms[1]);
|
|
if (fbTrans)
|
|
commit_trans(&fbTrans);
|
|
break;
|
|
|
|
case FrontOptions::copy:
|
|
if (!frontendTransaction())
|
|
{
|
|
// Free the frontend command
|
|
frontend_free_parms(parms, lparms, parm_defaults);
|
|
return FAIL;
|
|
}
|
|
|
|
ret = copy_table(lparms[1], lparms[2], lparms[3]);
|
|
if (fbTrans)
|
|
commit_trans(&fbTrans);
|
|
break;
|
|
|
|
case FrontOptions::blobview:
|
|
ret = blobedit(parms[0], lparms);
|
|
break;
|
|
|
|
case FrontOptions::output:
|
|
ret = newoutput(lparms[1]);
|
|
break;
|
|
|
|
case FrontOptions::shell:
|
|
ret = escape(cmd);
|
|
break;
|
|
|
|
case FrontOptions::set:
|
|
ret = frontend_set(cmd, parms, lparms, bad_dialect_buf, bad_dialect);
|
|
break;
|
|
|
|
case FrontOptions::create:
|
|
if (!strcmp(parms[1], "DATABASE") ||
|
|
(!strcmp(parms[1], "SCHEMA") && isqlGlob.major_ods > 0 && isqlGlob.major_ods < ODS_VERSION12))
|
|
{
|
|
ret = create_db(cmd, lparms[2]);
|
|
}
|
|
else
|
|
ret = CONT;
|
|
break;
|
|
|
|
case FrontOptions::drop:
|
|
if (!strcmp(parms[1], "DATABASE") ||
|
|
(!strcmp(parms[1], "SCHEMA") && isqlGlob.major_ods > 0 && isqlGlob.major_ods < ODS_VERSION12))
|
|
{
|
|
if (*parms[2])
|
|
ret = ps_ERR;
|
|
else
|
|
ret = drop_db();
|
|
}
|
|
else
|
|
ret = CONT;
|
|
break;
|
|
|
|
case FrontOptions::connect:
|
|
{
|
|
const TEXT* psw = NULL;
|
|
const TEXT* usr = NULL;
|
|
const TEXT* sql_role_nm = NULL;
|
|
int numbufs = 0;
|
|
|
|
// if a parameter is given in the command more than once, the
|
|
// last one will be used. The parameters can appear each any
|
|
// order, but each must provide a value.
|
|
|
|
ret = SKIP;
|
|
for (int i = 2; i < (MAX_TERMS - 1);)
|
|
{
|
|
if (!strcmp(parms[i], "CACHE") && *lparms[i + 1])
|
|
{
|
|
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;
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
if (ret != ps_ERR)
|
|
ret = newdb(lparms[1], usr, psw, numbufs, sql_role_nm, true);
|
|
}
|
|
break;
|
|
|
|
case FrontOptions::edit:
|
|
ret = edit(lparms);
|
|
break;
|
|
|
|
case FrontOptions::input:
|
|
// CVC: Set by newinput() below only if successful.
|
|
//Input_file = true;
|
|
ret = newinput(lparms[1]);
|
|
break;
|
|
|
|
case FrontOptions::quit:
|
|
ret = BACKOUT;
|
|
break;
|
|
|
|
case FrontOptions::exit:
|
|
ret = EXIT;
|
|
break;
|
|
|
|
case FrontOptions::help:
|
|
ret = help(parms[1]);
|
|
break;
|
|
|
|
#ifdef DEV_BUILD
|
|
case FrontOptions::passthrough:
|
|
ret = passthrough(cmd + 11);
|
|
break;
|
|
#endif
|
|
|
|
default: // Didn't match, it must be SQL
|
|
ret = CONT;
|
|
break;
|
|
}
|
|
|
|
// In case any default transaction was started - commit it here
|
|
if (fbTrans)
|
|
commit_trans(&fbTrans);
|
|
|
|
// Free the frontend command
|
|
frontend_free_parms(parms, lparms, parm_defaults);
|
|
|
|
if (ret == ps_ERR)
|
|
{
|
|
if (bad_dialect)
|
|
IUTILS_msg_get(CMD_ERR, errbuf, SafeArg() << bad_dialect_buf);
|
|
else
|
|
IUTILS_msg_get(CMD_ERR, errbuf, SafeArg() << cmd);
|
|
STDERROUT(errbuf);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void frontend_free_parms(TEXT* parms[], TEXT* lparms[], TEXT parm_defaults[][1])
|
|
{
|
|
for (int j = 0; j < MAX_TERMS; j++)
|
|
{
|
|
if (parms[j] && parms[j] != parm_defaults[j])
|
|
{
|
|
ISQL_FREE(parms[j]);
|
|
ISQL_FREE(lparms[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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) == size_t(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)
|
|
IUTILS_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)
|
|
{
|
|
|
|
class SetOptions : public OptionsBase
|
|
{
|
|
public:
|
|
enum set_commands
|
|
{
|
|
stat, count, list, plan, planonly, explain, blobdisplay, echo, autoddl,
|
|
width, transaction, terminator, names, time,
|
|
//#ifdef DEV_BUILD
|
|
sqlda_display,
|
|
//#endif
|
|
sql, warning, sqlCont, heading, bail,
|
|
bulk_insert, maxrows, 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::explain, "EXPLAIN", 0},
|
|
{SetOptions::blobdisplay, "BLOBDISPLAY", 4},
|
|
{SetOptions::echo, "ECHO", 0},
|
|
{SetOptions::autoddl, "AUTODDL", 4},
|
|
{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::sqlCont, "GENERATOR", 0},
|
|
{SetOptions::sqlCont, "STATISTICS", 0},
|
|
{SetOptions::heading, "HEADING", 0},
|
|
{SetOptions::bail, "BAIL", 0},
|
|
{SetOptions::bulk_insert, "BULK_INSERT", 0},
|
|
{SetOptions::maxrows, "ROWCOUNT", 0}, // legacy, compatible with FB2.5
|
|
{SetOptions::maxrows, "MAXROWS", 0},
|
|
{SetOptions::sqlCont, "ROLE", 0},
|
|
{SetOptions::sqlCont, "TRUSTED", 0}, // TRUSTED ROLE, will get DSQL error other case
|
|
};
|
|
|
|
// Display current set options
|
|
if (!*parms[1])
|
|
return print_sets();
|
|
|
|
processing_state ret = SKIP;
|
|
const SetOptions setoptions(options, FB_NELEM(options), SetOptions::wrong);
|
|
switch (setoptions.getCommand(parms[1]))
|
|
{
|
|
case SetOptions::sqlCont:
|
|
ret = CONT;
|
|
break;
|
|
|
|
case SetOptions::stat:
|
|
ret = do_set_command(parms[2], &setValues.Stats);
|
|
break;
|
|
|
|
case SetOptions::count:
|
|
ret = do_set_command(parms[2], &setValues.Docount);
|
|
break;
|
|
|
|
case SetOptions::list:
|
|
ret = do_set_command(parms[2], &setValues.List);
|
|
break;
|
|
|
|
case SetOptions::plan:
|
|
ret = do_set_command(parms[2], &setValues.Plan);
|
|
if (setValues.Planonly && !setValues.Plan)
|
|
ret = do_set_command("OFF", &setValues.Planonly);
|
|
break;
|
|
|
|
case SetOptions::planonly:
|
|
ret = do_set_command (parms[2], &setValues.Planonly);
|
|
if (setValues.Planonly && !setValues.Plan)
|
|
{
|
|
// turn on plan
|
|
ret = do_set_command ("ON", &setValues.Plan);
|
|
}
|
|
break;
|
|
|
|
case SetOptions::explain:
|
|
ret = do_set_command(parms[2], &setValues.ExplainPlan);
|
|
if (setValues.ExplainPlan)
|
|
ret = do_set_command("ON", &setValues.Plan);
|
|
break;
|
|
|
|
case SetOptions::blobdisplay:
|
|
// No arg means turn off blob display
|
|
if (!*parms[2] || !strcmp(parms[2], "OFF"))
|
|
setValues.Doblob = NO_BLOBS;
|
|
else if (!strcmp(parms[2], "ALL"))
|
|
setValues.Doblob = ALL_BLOBS;
|
|
else
|
|
setValues.Doblob = atoi(parms[2]);
|
|
break;
|
|
|
|
case SetOptions::echo:
|
|
ret = do_set_command(parms[2], &setValues.Echo);
|
|
if (!setValues.Echo)
|
|
ISQL_prompt("");
|
|
break;
|
|
|
|
case SetOptions::autoddl:
|
|
ret = do_set_command(parms[2], &setValues.Autocommit);
|
|
break;
|
|
|
|
case SetOptions::width:
|
|
ret = newsize(parms[2][0] == '"' ? lparms[2] : parms[2], parms[3]);
|
|
break;
|
|
|
|
case SetOptions::transaction:
|
|
ret = newtrans(cmd);
|
|
break;
|
|
|
|
case SetOptions::terminator:
|
|
{
|
|
const TEXT* a = (*lparms[2]) ? lparms[2] : DEFTERM;
|
|
for (size_t iter = 0; iter < sizeof(FORBIDDEN_TERM_CHARS); ++iter)
|
|
{
|
|
if (strchr(a, FORBIDDEN_TERM_CHARS[iter]))
|
|
{
|
|
TEXT msg_string[MSG_LENGTH];
|
|
IUTILS_msg_get(INVALID_TERM_CHARS, msg_string, SafeArg() << FORBIDDEN_TERM_CHARS_DISPLAY);
|
|
isqlGlob.printf("%s\n", msg_string);
|
|
return ps_ERR;
|
|
}
|
|
}
|
|
|
|
isqlGlob.Termlen = strlen(a);
|
|
if (isqlGlob.Termlen < MAXTERM_SIZE)
|
|
{
|
|
strcpy(isqlGlob.global_Term, a);
|
|
}
|
|
else
|
|
{
|
|
isqlGlob.Termlen = MAXTERM_SIZE - 1;
|
|
fb_utils::copy_terminate(isqlGlob.global_Term, a, isqlGlob.Termlen + 1);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SetOptions::names:
|
|
if (!*parms[2])
|
|
{
|
|
const size_t lgth = strlen(DEFCHARSET);
|
|
if (lgth < MAXCHARSET_SIZE)
|
|
strcpy(setValues.ISQL_charset, DEFCHARSET);
|
|
else
|
|
fb_utils::copy_terminate(setValues.ISQL_charset, DEFCHARSET, MAXCHARSET_SIZE);
|
|
}
|
|
else
|
|
{
|
|
const size_t lgth = strlen(parms[2]);
|
|
if (lgth < MAXCHARSET_SIZE)
|
|
strcpy(setValues.ISQL_charset, parms[2]);
|
|
else
|
|
fb_utils::copy_terminate(setValues.ISQL_charset, parms[2], MAXCHARSET_SIZE);
|
|
}
|
|
break;
|
|
|
|
case SetOptions::time:
|
|
ret = do_set_command(parms[2], &setValues.Time_display);
|
|
break;
|
|
|
|
//#ifdef DEV_BUILD
|
|
case SetOptions::sqlda_display:
|
|
ret = do_set_command(parms[2], &setValues.Sqlda_display);
|
|
break;
|
|
//#endif // DEV_BUILD
|
|
|
|
case SetOptions::sql:
|
|
if (!strcmp(parms[2], "DIALECT"))
|
|
ret = get_dialect(parms[3], bad_dialect_buf, bad_dialect);
|
|
else
|
|
ret = ps_ERR;
|
|
break;
|
|
|
|
case SetOptions::warning:
|
|
ret = do_set_command (parms[2], &setValues.Warnings);
|
|
break;
|
|
|
|
case SetOptions::heading:
|
|
ret = do_set_command(parms[2], &setValues.Heading);
|
|
break;
|
|
|
|
case SetOptions::bail:
|
|
ret = do_set_command(parms[2], &setValues.BailOnError);
|
|
break;
|
|
|
|
case SetOptions::bulk_insert:
|
|
if (*parms[2])
|
|
ret = bulk_insert_hack(cmd);
|
|
else
|
|
ret = ps_ERR;
|
|
break;
|
|
|
|
case SetOptions::maxrows:
|
|
ret = newMaxRows((*lparms[2]) ? lparms[2] : "0");
|
|
break;
|
|
|
|
default:
|
|
{
|
|
TEXT msg_string[MSG_LENGTH];
|
|
IUTILS_msg_get(VALID_OPTIONS, msg_string);
|
|
isqlGlob.printf("%s\n", msg_string);
|
|
}
|
|
setoptions.showCommands(isqlGlob.Out);
|
|
ret = ps_ERR;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static processing_state do_set_command(const TEXT* parm, bool* global_flag)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d o _ s e t _ c o m m a n d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* set the flag pointed to by global_flag
|
|
* to true or false.
|
|
* if parm is missing, toggle it
|
|
* if parm is "ON", set it to true
|
|
* if parm is "OFF", set it to false
|
|
*
|
|
**************************************/
|
|
processing_state ret = SKIP;
|
|
|
|
if (!*parm)
|
|
*global_flag = !*global_flag;
|
|
else if (!strcmp(parm, "ON"))
|
|
*global_flag = true;
|
|
else if (!strcmp(parm, "OFF"))
|
|
*global_flag = false;
|
|
else
|
|
ret = ps_ERR;
|
|
return (ret);
|
|
}
|
|
|
|
|
|
// *********************
|
|
// g e t _ d i a l e c t
|
|
// *********************
|
|
// Validates SET SQL DIALECT command according to the target db.
|
|
static processing_state get_dialect(const char* const dialect_str,
|
|
char* const bad_dialect_buf, bool& bad_dialect)
|
|
{
|
|
processing_state ret = SKIP;
|
|
bool print_warning = false;
|
|
const USHORT old_SQL_dialect = isqlGlob.SQL_dialect; // save the old SQL dialect
|
|
if (dialect_str && (isqlGlob.SQL_dialect = atoi(dialect_str)))
|
|
{
|
|
if (isqlGlob.SQL_dialect < SQL_DIALECT_V5 ||
|
|
isqlGlob.SQL_dialect > SQL_DIALECT_V6)
|
|
{
|
|
bad_dialect = true;
|
|
sprintf(bad_dialect_buf, "%s%s",
|
|
"invalid SQL dialect ", dialect_str);
|
|
isqlGlob.SQL_dialect = old_SQL_dialect; // restore SQL dialect
|
|
ret = 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 && setValues.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(Firebird::string& statement,
|
|
const TEXT* statement_prompt)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ s t a t e m e n t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get an SQL statement, or QUIT/EXIT command to process
|
|
*
|
|
* Arguments: Pointer to statement, size of statement_buffer and prompt msg.
|
|
*
|
|
**************************************/
|
|
processing_state ret = CONT;
|
|
|
|
// Lookup the continuation prompt once
|
|
TEXT con_prompt[MSG_LENGTH];
|
|
IUTILS_msg_get(CON_PROMPT, con_prompt);
|
|
|
|
if (Interactive && !Input_file || setValues.Echo) {
|
|
ISQL_prompt(statement_prompt);
|
|
}
|
|
|
|
// Clear out statement buffer
|
|
statement.resize(0);
|
|
|
|
// Set count of characters to zero
|
|
|
|
size_t valuable_count = 0; // counter of valuable (non-space) chars
|
|
size_t comment_pos = 0; // position of block comment start
|
|
size_t non_comment_pos = 0; // position of char after block comment
|
|
const size_t term_length = isqlGlob.Termlen - 1; // additional variable for decreasing calculation
|
|
|
|
Filelist->Ifp().indev_line = Filelist->Ifp().indev_aux;
|
|
bool done = false;
|
|
|
|
enum
|
|
{
|
|
normal,
|
|
in_single_line_comment,
|
|
in_block_comment,
|
|
in_single_quoted_string,
|
|
in_double_quoted_string
|
|
} state = normal;
|
|
|
|
char lastChar = '\0';
|
|
char altQuoteChar = '\0';
|
|
|
|
while (!done)
|
|
{
|
|
SSHORT c = getNextInputChar();
|
|
switch (c)
|
|
{
|
|
case EOF:
|
|
// Go back to getc if we get interrupted by a signal.
|
|
|
|
if (SYSCALL_INTERRUPTED(errno))
|
|
{
|
|
errno = 0;
|
|
break;
|
|
}
|
|
|
|
// If there was something valuable before EOF - error
|
|
if (valuable_count > 0)
|
|
{
|
|
TEXT errbuf[MSG_LENGTH];
|
|
IUTILS_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
|
|
|
|
if (Filelist->count() == 1)
|
|
return FOUND_EOF;
|
|
|
|
// If this is not tmpfile, close it
|
|
|
|
if (!Filelist->sameInputAndOutput())
|
|
Filelist->Ifp().close();
|
|
|
|
// Reset to previous after other input
|
|
|
|
Filelist->removeIntoIfp();
|
|
|
|
if (Interactive && !Input_file || setValues.Echo)
|
|
ISQL_prompt(statement_prompt);
|
|
|
|
// CVC: Let's detect if we went back to the first level.
|
|
if (Filelist->readingStdin())
|
|
{
|
|
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;
|
|
break;
|
|
|
|
case '\n':
|
|
// case '\0': // In particular with readline the \n is removed
|
|
if (state == in_single_line_comment)
|
|
{
|
|
state = normal;
|
|
}
|
|
|
|
// Catch the help ? without a terminator
|
|
if (statement.length() == 1 && statement[0] == '?')
|
|
{
|
|
c = 0;
|
|
done = true;
|
|
break;
|
|
}
|
|
|
|
// If in a comment, keep reading
|
|
if (Interactive && !Input_file || setValues.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
|
|
{
|
|
ISQL_prompt(con_prompt);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case '-':
|
|
// Could this the be start of a single-line comment.
|
|
if (state == normal && statement.length() > 0 &&
|
|
statement[statement.length() - 1] == '-')
|
|
{
|
|
state = in_single_line_comment;
|
|
if (valuable_count == 1)
|
|
valuable_count = 0;
|
|
}
|
|
break;
|
|
|
|
case '*':
|
|
// Could this the be start of a comment. We can only look back,
|
|
// not forward.
|
|
// Ignore possibilities of a comment beginning inside
|
|
// quoted strings.
|
|
if (state == normal && statement.length() > 0 &&
|
|
statement[statement.length() - 1] == '/' && statement.length() != non_comment_pos)
|
|
{
|
|
state = in_block_comment;
|
|
comment_pos = statement.length() - 1;
|
|
if (valuable_count == 1)
|
|
valuable_count = 0;
|
|
}
|
|
break;
|
|
|
|
case '/':
|
|
// Perhaps this is the end of a comment.
|
|
// Ignore possibilities of a comment ending inside
|
|
// quoted strings.
|
|
// Ignore things like /*/ since it isn't a block comment; only the start of it. Or end.
|
|
if (state == in_block_comment && statement.length() > 2 &&
|
|
statement[statement.length() - 1] == '*' && statement.length() > comment_pos + 2)
|
|
{
|
|
state = normal;
|
|
non_comment_pos = statement.length() + 1; // mark start of non-comment to track this: /**/*
|
|
valuable_count--; // This char is not valuable
|
|
}
|
|
break;
|
|
|
|
case SINGLE_QUOTE:
|
|
switch (state)
|
|
{
|
|
case normal:
|
|
if (lastChar == 'q' || lastChar == 'Q')
|
|
{
|
|
statement += (lastChar = c);
|
|
altQuoteChar = c = getNextInputChar();
|
|
|
|
switch (altQuoteChar)
|
|
{
|
|
case '{':
|
|
altQuoteChar = '}';
|
|
break;
|
|
case '(':
|
|
altQuoteChar = ')';
|
|
break;
|
|
case '[':
|
|
altQuoteChar = ']';
|
|
break;
|
|
case '<':
|
|
altQuoteChar = '>';
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
altQuoteChar = '\0';
|
|
|
|
state = in_single_quoted_string;
|
|
break;
|
|
case in_single_quoted_string:
|
|
if (!altQuoteChar || lastChar == altQuoteChar)
|
|
state = normal;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case DBL_QUOTE:
|
|
switch (state)
|
|
{
|
|
case normal:
|
|
state = in_double_quoted_string;
|
|
break;
|
|
case in_double_quoted_string:
|
|
state = normal;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
|
|
default:
|
|
if (state == normal && c == isqlGlob.global_Term[term_length] &&
|
|
// one-char terminator or the beginning also match
|
|
(isqlGlob.Termlen == 1u ||
|
|
(valuable_count >= term_length &&
|
|
strncmp(&statement[statement.length() - term_length],
|
|
isqlGlob.global_Term, term_length) == 0)))
|
|
{
|
|
c = 0;
|
|
done = true;
|
|
statement.resize(statement.length() - term_length);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
statement.resize(0);
|
|
non_comment_pos = 0;
|
|
}
|
|
}
|
|
|
|
statement += (lastChar = c);
|
|
}
|
|
|
|
// If this was a null statement, skip it
|
|
if (ret == CONT && statement.isEmpty())
|
|
ret = SKIP;
|
|
|
|
if (ret == CONT)
|
|
ret = frontend(statement.c_str());
|
|
|
|
if (ret == CONT)
|
|
{
|
|
// Place each non frontend statement in the temp file if we are reading
|
|
// from stdin.
|
|
|
|
Filelist->saveCommand(statement.c_str(), isqlGlob.global_Term);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void ISQL_get_version(bool call_by_create_db)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ g e t _ v e r s i o n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* finds out if the database we just attached to is
|
|
* V4 or newer as well as other info.
|
|
*
|
|
**************************************/
|
|
const UCHAR db_version_info[] =
|
|
{
|
|
isc_info_ods_version,
|
|
isc_info_ods_minor_version,
|
|
isc_info_db_sql_dialect,
|
|
frb_info_att_charset,
|
|
Version_info ? isc_info_firebird_version: isc_info_end,
|
|
isc_info_end
|
|
};
|
|
/*
|
|
** Each info item requested will return
|
|
**
|
|
** 1 byte for the info item tag
|
|
** 2 bytes for the length of the information that follows
|
|
** 1 to 4 bytes of integer information
|
|
**
|
|
** isc_info_end will not have a 2-byte length - which gives us
|
|
** some padding in the buffer.
|
|
*/
|
|
|
|
// UCHAR buffer[sizeof(db_version_info) * (1 + 2 + 4)];
|
|
|
|
// Now we are also getting the Firebird server version which is a
|
|
// string the above calculation does not apply. NM 03-Oct-2001
|
|
|
|
UCHAR buffer[PRINT_BUFFER_LENGTH];
|
|
char bad_dialect_buf[BUFFER_LENGTH512];
|
|
bool print_warning = false;
|
|
|
|
global_dialect_spoken = 0;
|
|
|
|
if (!DB)
|
|
return;
|
|
DB->getInfo(fbStatus, sizeof(db_version_info), db_version_info,
|
|
sizeof(buffer), buffer);
|
|
if(ISQL_errmsg(fbStatus))
|
|
{
|
|
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));
|
|
p += sizeof(USHORT);
|
|
switch (item)
|
|
{
|
|
case isc_info_ods_version:
|
|
isqlGlob.major_ods = gds__vax_integer(p, length);
|
|
break;
|
|
case isc_info_ods_minor_version:
|
|
isqlGlob.minor_ods = gds__vax_integer(p, length);
|
|
break;
|
|
case isc_info_db_sql_dialect:
|
|
global_dialect_spoken = gds__vax_integer(p, length);
|
|
if (isqlGlob.major_ods < ODS_VERSION10)
|
|
{
|
|
if (isqlGlob.SQL_dialect > SQL_DIALECT_V5 && setValues.Warnings)
|
|
{
|
|
isqlGlob.printf(NEWLINE);
|
|
sprintf(bad_dialect_buf, "%s%s%s%d%s%s",
|
|
"WARNING: Pre IB V6 database only speaks",
|
|
" SQL dialect 1 and ",
|
|
"does not accept Client SQL dialect ",
|
|
isqlGlob.SQL_dialect,
|
|
" . Client SQL dialect is reset to 1.", NEWLINE);
|
|
isqlGlob.prints(bad_dialect_buf);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ODS 10 databases
|
|
|
|
switch (global_dialect_spoken)
|
|
{
|
|
case SQL_DIALECT_V5:
|
|
if (isqlGlob.SQL_dialect > SQL_DIALECT_V5)
|
|
print_warning = true;
|
|
break;
|
|
|
|
case SQL_DIALECT_V6:
|
|
if (isqlGlob.SQL_dialect != 0 && isqlGlob.SQL_dialect < SQL_DIALECT_V6)
|
|
print_warning = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (print_warning && setValues.Warnings)
|
|
{
|
|
print_warning = false;
|
|
isqlGlob.printf(NEWLINE);
|
|
sprintf(bad_dialect_buf, "%s%d%s%d%s%s",
|
|
"WARNING: This database speaks SQL dialect ",
|
|
global_dialect_spoken,
|
|
" but Client SQL dialect was set to ",
|
|
isqlGlob.SQL_dialect, " .", NEWLINE);
|
|
isqlGlob.prints(bad_dialect_buf);
|
|
}
|
|
}
|
|
break;
|
|
case isc_info_error:
|
|
// Error indicates an option was not understood by the
|
|
// remote server.
|
|
if (*p == isc_info_firebird_version)
|
|
{
|
|
// must be an old or non Firebird server
|
|
break;
|
|
}
|
|
if (isqlGlob.SQL_dialect && isqlGlob.SQL_dialect != SQL_DIALECT_V5 && setValues.Warnings)
|
|
{
|
|
isqlGlob.printf(NEWLINE);
|
|
if (call_by_create_db)
|
|
sprintf(bad_dialect_buf, "%s%s%d%s%s",
|
|
"WARNING: Pre IB V6 server only speaks SQL dialect 1",
|
|
" and does not accept Client SQL dialect ",
|
|
isqlGlob.SQL_dialect,
|
|
" . Client SQL dialect is reset to 1.", NEWLINE);
|
|
else
|
|
{
|
|
//connecting_to_pre_v6_server = true; Not used anywhere.
|
|
sprintf(bad_dialect_buf, "%s%s%d%s%s",
|
|
"ERROR: Pre IB V6 server only speaks SQL dialect 1",
|
|
" and does not accept Client SQL dialect ",
|
|
isqlGlob.SQL_dialect,
|
|
" . Client SQL dialect is reset to 1.", NEWLINE);
|
|
}
|
|
isqlGlob.prints(bad_dialect_buf);
|
|
}
|
|
else
|
|
{
|
|
if (isqlGlob.SQL_dialect == 0)
|
|
{
|
|
//connecting_to_pre_v6_server = true; Not used anywhere.
|
|
sprintf(bad_dialect_buf, "%s%s%d%s%s",
|
|
"ERROR: Pre IB V6 server only speaks SQL dialect 1",
|
|
" and does not accept Client SQL dialect ",
|
|
isqlGlob.SQL_dialect,
|
|
" . Client SQL dialect is reset to 1.", NEWLINE);
|
|
isqlGlob.prints(bad_dialect_buf);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case frb_info_att_charset:
|
|
isqlGlob.att_charset = gds__vax_integer(p, length);
|
|
break;
|
|
|
|
default:
|
|
isqlGlob.printf("Internal error: Unexpected isc_info_value %d%s",
|
|
item, NEWLINE);
|
|
break;
|
|
}
|
|
p += length;
|
|
}
|
|
|
|
if (isqlGlob.major_ods < ODS_VERSION8)
|
|
{
|
|
TEXT errbuf[MSG_LENGTH];
|
|
IUTILS_msg_get(SERVER_TOO_OLD, errbuf);
|
|
STDERROUT(errbuf);
|
|
return;
|
|
}
|
|
|
|
// If the remote server did not respond to our request for
|
|
// "dialects spoken", then we can assume it can only speak
|
|
// the V5 dialect. We automatically change the connection
|
|
// dialect to that spoken by the server. Otherwise the
|
|
// current dialect is set to whatever the user requested.
|
|
|
|
if (global_dialect_spoken == 0)
|
|
isqlGlob.SQL_dialect = SQL_DIALECT_V5;
|
|
else if (isqlGlob.major_ods < ODS_VERSION10)
|
|
isqlGlob.SQL_dialect = global_dialect_spoken;
|
|
else if (isqlGlob.SQL_dialect == 0) // client SQL dialect has not been set
|
|
isqlGlob.SQL_dialect = global_dialect_spoken;
|
|
|
|
if (global_dialect_spoken > 0)
|
|
isqlGlob.db_SQL_dialect = global_dialect_spoken;
|
|
else
|
|
isqlGlob.db_SQL_dialect = SQL_DIALECT_V5;
|
|
}
|
|
|
|
|
|
void ISQL_ri_action_print(const TEXT* ri_action_str,
|
|
const TEXT* ri_action_prefix_str,
|
|
bool all_caps)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I S Q L _ r i _ a c t i o n _ p r i n t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* prints the description of ref. integrity actions.
|
|
* The actions must be one of the cascading actions or RESTRICT.
|
|
* RESTRICT is used to indicate that the user did not specify any
|
|
* actions, so do not print it out.
|
|
*
|
|
**************************************/
|
|
for (const ri_actions* ref_int = ri_actions_all; ref_int->ri_action_name;
|
|
++ref_int)
|
|
{
|
|
if (!strcmp(ref_int->ri_action_name, ri_action_str))
|
|
{
|
|
if (*ref_int->ri_action_print_caps)
|
|
{
|
|
// we have something to print
|
|
if (all_caps)
|
|
isqlGlob.printf("%s %s", ri_action_prefix_str, ref_int->ri_action_print_caps);
|
|
else if (*ref_int->ri_action_print_mixed)
|
|
isqlGlob.printf("%s %s", ri_action_prefix_str, ref_int->ri_action_print_mixed);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
fb_assert(FALSE);
|
|
}
|
|
|
|
|
|
static bool get_numeric(const UCHAR* str2,
|
|
USHORT length,
|
|
SSHORT* scale,
|
|
SINT64* ptr)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ n u m e r i c
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert a numeric literal (str2) to its binary value.
|
|
*
|
|
* The binary value (int64) is stored at the
|
|
* address given by ptr.
|
|
*
|
|
**************************************/
|
|
SINT64 value = 0;
|
|
|
|
SSHORT local_scale = 0, sign = 0;
|
|
bool digit_seen = false, fraction = false;
|
|
|
|
const UCHAR* const end = str2 + length;
|
|
for (const UCHAR* p = str2; p < end; p++)
|
|
{
|
|
if (DIGIT(*p))
|
|
{
|
|
digit_seen = true;
|
|
|
|
// Before computing the next value, make sure there will be
|
|
// no overflow. Trying to detect overflow after the fact is
|
|
// tricky: the value doesn't always become negative after an
|
|
// overflow!
|
|
|
|
if (value > INT64_LIMIT)
|
|
return false;
|
|
|
|
if (value == INT64_LIMIT)
|
|
{
|
|
// possibility of an overflow
|
|
|
|
if ((*p > '8' && sign == -1) || (*p > '7' && sign != -1))
|
|
return false;
|
|
}
|
|
|
|
// Force the subtraction to be performed before the addition,
|
|
// thus preventing a possible signed arithmetic overflow.
|
|
value = value * 10 + (*p - '0');
|
|
if (fraction)
|
|
--local_scale;
|
|
}
|
|
else if (*p == '.')
|
|
{
|
|
if (fraction)
|
|
return false;
|
|
|
|
fraction = true;
|
|
}
|
|
else if (*p == '-' && !digit_seen && !sign && !fraction)
|
|
sign = -1;
|
|
else if (*p == '+' && !digit_seen && !sign && !fraction)
|
|
sign = 1;
|
|
else if (*p != BLANK)
|
|
return false;
|
|
}
|
|
|
|
if (!digit_seen)
|
|
return false;
|
|
|
|
*scale = local_scale;
|
|
*(SINT64*) ptr = ((sign == -1) ? -value : value);
|
|
return true;
|
|
}
|
|
|
|
|
|
// 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()
|
|
{
|
|
/**************************************
|
|
*
|
|
* p r i n t _ s e t s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Print the current set values
|
|
*
|
|
**************************************/
|
|
|
|
print_set("Print statistics:", setValues.Stats);
|
|
print_set("Echo commands:", setValues.Echo);
|
|
print_set("List format:", setValues.List);
|
|
print_set("Show Row Count:", setValues.Docount);
|
|
//print_set("Row Count:", setValues.Docount); // Changed print to the above to avoid confusion with next one
|
|
isqlGlob.printf("%-25s%lu%s", "Select maxrows limit:", setValues.maxRows, NEWLINE);
|
|
print_set("Autocommit DDL:", setValues.Autocommit);
|
|
print_set("Access Plan:", setValues.Plan);
|
|
print_set("Access Plan only:", setValues.Planonly);
|
|
print_set("Explain Access Plan:", setValues.ExplainPlan);
|
|
|
|
isqlGlob.printf("%-25s", "Display BLOB type:");
|
|
switch (setValues.Doblob)
|
|
{
|
|
case ALL_BLOBS:
|
|
isqlGlob.printf("ALL");
|
|
break;
|
|
case NO_BLOBS:
|
|
isqlGlob.printf("NONE");
|
|
break;
|
|
default:
|
|
isqlGlob.printf("%d", setValues.Doblob);
|
|
}
|
|
isqlGlob.printf(NEWLINE);
|
|
|
|
if (*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET)) {
|
|
isqlGlob.printf("%-25s%s%s", "Set names:", setValues.ISQL_charset, NEWLINE);
|
|
}
|
|
|
|
print_set("Column headings:", setValues.Heading);
|
|
|
|
if (setValues.global_Cols.count())
|
|
{
|
|
isqlGlob.printf("Column print widths:%s", NEWLINE);
|
|
const ColList::item* p = setValues.global_Cols.getHead();
|
|
while (p)
|
|
{
|
|
isqlGlob.printf("%s%s width: %d%s", TAB_AS_SPACES, p->col_name, p->col_len, NEWLINE);
|
|
p = p->next;
|
|
}
|
|
}
|
|
isqlGlob.printf("%-25s%s%s", "Terminator:", isqlGlob.global_Term, NEWLINE);
|
|
|
|
print_set("Time:", setValues.Time_display);
|
|
print_set("Warnings:", setValues.Warnings);
|
|
print_set("Bail on error:", setValues.BailOnError);
|
|
return SKIP;
|
|
}
|
|
|
|
|
|
static processing_state help(const TEXT* what)
|
|
{
|
|
/**************************************
|
|
*
|
|
* h e l p
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* List the known commands.
|
|
*
|
|
**************************************/
|
|
|
|
// Ordered list of help messages to display. Use -1 to terminate list,
|
|
// and 0 for an empty blank line
|
|
static const SSHORT help_ids[] =
|
|
{
|
|
HLP_FRONTEND, // Frontend commands:
|
|
HLP_BLOBDMP, // BLOBDUMP <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
|
|
HLP_INPUT, // INput <filename> -- take input from the named SQL file
|
|
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
|
|
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
|
|
0,
|
|
HLP_ALL, // All commands may be abbreviated to letters in CAPitals
|
|
-1 // end of list
|
|
};
|
|
|
|
static const SSHORT help_set_ids[] =
|
|
{
|
|
HLP_SETCOM, //Set commands:
|
|
HLP_SET, // SET -- display current SET options
|
|
HLP_SETAUTO, // SET AUTOddl -- toggle autocommit of DDL statements
|
|
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_SETMAXROWS, // SET MAXROWS [<n>] -- toggle limit of selected rows to <n>, zero is no limit
|
|
HLP_SETECHO, // SET ECHO -- toggle command echo on/off
|
|
HLP_SETEXPLAIN, // SET EXPLAIN -- toggle display of query plan in the explained form
|
|
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>
|
|
0,
|
|
HLP_ALL, // All commands may be abbreviated to letters in CAPitals
|
|
-1 // end of list
|
|
};
|
|
|
|
TEXT msg[MSG_LENGTH];
|
|
const SSHORT* msgid;
|
|
if (!strcmp(what, "SET")) {
|
|
msgid = help_set_ids;
|
|
}
|
|
else {
|
|
msgid = help_ids;
|
|
}
|
|
for (; *msgid != -1; msgid++)
|
|
{
|
|
if (*msgid != 0)
|
|
{
|
|
IUTILS_msg_get(*msgid, msg);
|
|
IUTILS_printf(Help, msg);
|
|
}
|
|
IUTILS_printf(Help, NEWLINE);
|
|
}
|
|
return (SKIP);
|
|
}
|
|
|
|
|
|
static bool isyesno(const TEXT* buffer)
|
|
{
|
|
/**********************************************
|
|
*
|
|
* 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.
|
|
*
|
|
**********************************************/
|
|
|
|
if (!have_trans)
|
|
{
|
|
// get the translation if we don't have it already
|
|
IUTILS_msg_get(YES_ANS, sizeof(yesword), yesword);
|
|
have_trans = true;
|
|
}
|
|
|
|
// Just check first byte of yes response -- could be multibyte problem
|
|
|
|
return UPPER7(buffer[0]) == UPPER7(yesword[0]);
|
|
}
|
|
|
|
|
|
static bool printUser(const char* dbName)
|
|
{
|
|
if (!frontendTransaction())
|
|
return false;
|
|
|
|
class FbTransCommit
|
|
{
|
|
public:
|
|
~FbTransCommit()
|
|
{
|
|
if (DB && fbTrans)
|
|
{
|
|
fbTrans->rollback(fbStatus);
|
|
if (succeeded())
|
|
fbTrans = NULL;
|
|
}
|
|
}
|
|
};
|
|
FbTransCommit fbTransCommit;
|
|
|
|
const char* sql = "SELECT CURRENT_USER, CURRENT_ROLE FROM RDB$DATABASE";
|
|
|
|
Firebird::RefPtr<Firebird::IStatement> st(Firebird::REF_NO_INCR,
|
|
DB->prepare(fbStatus, fbTrans, 0, sql, 3, Firebird::IStatement::PREPARE_PREFETCH_METADATA));
|
|
if (failed())
|
|
return false;
|
|
Firebird::RefPtr<Firebird::IMessageMetadata> m(Firebird::REF_NO_INCR,
|
|
st->getOutputMetadata(fbStatus));
|
|
if (failed())
|
|
return false;
|
|
unsigned bs = m->getMessageLength(fbStatus);
|
|
if (failed())
|
|
return false;
|
|
Firebird::UCharBuffer outBuf;
|
|
UCHAR* buf = outBuf.getBuffer(bs);
|
|
st->execute(fbStatus, fbTrans, NULL, NULL, m, buf);
|
|
if (failed())
|
|
return false;
|
|
|
|
struct FieldInfo
|
|
{
|
|
const char* text;
|
|
const char* skip;
|
|
};
|
|
FieldInfo fieldInfo[2] = {
|
|
{"User", NULL},
|
|
{"Role", "NONE"}
|
|
};
|
|
|
|
bool wasOut = dbName && dbName[0];
|
|
if (wasOut)
|
|
isqlGlob.printf("Database: %s", dbName);
|
|
|
|
for (unsigned i = 0; i < FB_NELEM(fieldInfo); ++i)
|
|
{
|
|
IsqlVar v;
|
|
if (ISQL_fill_var(&v, m, i, buf) == ps_ERR)
|
|
return false;
|
|
if (*v.nullInd)
|
|
continue;
|
|
|
|
Firebird::string txt;
|
|
switch(v.type & ~1)
|
|
{
|
|
case SQL_TEXT:
|
|
txt.assign(v.value.asChar, v.length);
|
|
break;
|
|
case SQL_VARYING:
|
|
txt.assign(v.value.asVary->vary_string, v.value.asVary->vary_length);
|
|
break;
|
|
}
|
|
|
|
txt.trim();
|
|
|
|
if (fieldInfo[i].skip && txt == fieldInfo[i].skip)
|
|
continue;
|
|
|
|
isqlGlob.printf("%s%s: %s", wasOut ? ", " : "", fieldInfo[i].text, txt.c_str());
|
|
wasOut = true;
|
|
}
|
|
|
|
if (wasOut)
|
|
isqlGlob.printf("%s", NEWLINE);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static processing_state newdb(TEXT* dbname,
|
|
const TEXT* usr,
|
|
const TEXT* psw,
|
|
int numbufs,
|
|
const TEXT* sql_role_nm,
|
|
bool start_user_trans)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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
|
|
* sql_role_nm -- sql role name
|
|
*
|
|
**************************************/
|
|
// No name of a database, just return an error
|
|
|
|
if (!dbname || !*dbname)
|
|
return ps_ERR;
|
|
|
|
// 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 SLONG len = static_cast<SLONG>(chop_at(dbname, MAXPATHLEN));
|
|
SCHAR* save_database = (SCHAR*) ISQL_ALLOC(len + 1);
|
|
if (!save_database)
|
|
return ps_ERR;
|
|
|
|
strcpy(save_database, dbname);
|
|
ISQL_disconnect_database(false);
|
|
strcpy(dbname, save_database);
|
|
ISQL_FREE(save_database);
|
|
|
|
TEXT local_psw[BUFFER_LENGTH128];
|
|
TEXT local_usr[BUFFER_LENGTH128];
|
|
TEXT local_sql_role[BUFFER_LENGTH256];
|
|
|
|
// global user and passwords are set only if they are not originally set
|
|
|
|
local_psw[0] = 0;
|
|
local_usr[0] = 0;
|
|
local_sql_role[0] = 0;
|
|
|
|
// Strip quotes if well-intentioned
|
|
|
|
strip_quotes(dbname, isqlGlob.global_Db_name);
|
|
if (usr)
|
|
strcpy(local_usr, usr);
|
|
strip_quotes(psw, local_psw);
|
|
|
|
// 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
|
|
|
|
if (sql_role_nm)
|
|
strcpy(local_sql_role, sql_role_nm);
|
|
|
|
if (!(strlen(local_sql_role)) && global_role)
|
|
strcpy(local_sql_role, isqlGlob.Role);
|
|
|
|
if (!(strlen(local_usr)) && global_usr)
|
|
strcpy(local_usr, isqlGlob.User);
|
|
|
|
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;
|
|
|
|
// Build up a dpb
|
|
Firebird::ClumpletWriter dpb(Firebird::ClumpletReader::Tagged, MAX_DPB_SIZE, isc_dpb_version1);
|
|
|
|
if (*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET)) {
|
|
dpb.insertString(isc_dpb_lc_ctype, setValues.ISQL_charset, fb_strlen(setValues.ISQL_charset));
|
|
}
|
|
|
|
FB_SIZE_T l;
|
|
if (l = fb_strlen(local_usr)) {
|
|
dpb.insertString(isc_dpb_user_name, local_usr, l);
|
|
}
|
|
|
|
if (l = fb_strlen(local_psw)) {
|
|
dpb.insertString(isc_dpb_password, local_psw, l);
|
|
}
|
|
|
|
if (l = fb_strlen(local_sql_role))
|
|
{
|
|
dpb.insertInt(isc_dpb_sql_dialect, isqlGlob.SQL_dialect);
|
|
dpb.insertString(isc_dpb_sql_role_name, local_sql_role, l);
|
|
}
|
|
|
|
if (local_numbufs > 0) {
|
|
dpb.insertInt(isc_dpb_num_buffers, local_numbufs);
|
|
}
|
|
|
|
if (Nodbtriggers)
|
|
dpb.insertInt(isc_dpb_no_db_triggers, 1);
|
|
|
|
#ifdef TRUSTED_AUTH
|
|
if (Trusted_auth) {
|
|
dpb.insertTag(isc_dpb_trusted_auth);
|
|
}
|
|
#endif
|
|
|
|
{ // scope
|
|
const TEXT* local_name = isqlGlob.global_Db_name;
|
|
|
|
DB = fbProvider->attachDatabase(fbStatus, local_name, dpb.getBufferLength(), dpb.getBuffer());
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
isqlGlob.global_Db_name[0] = '\0';
|
|
return FAIL;
|
|
}
|
|
|
|
// Make it read owner name to display grantor correctly
|
|
SHOW_read_owner();
|
|
|
|
// No use in cancel when running non-end-user operators
|
|
DB->cancelOperation(fbStatus, fb_cancel_disable);
|
|
} // scope
|
|
|
|
ISQL_get_version(false);
|
|
|
|
if (*local_sql_role)
|
|
{
|
|
switch (isqlGlob.SQL_dialect)
|
|
{
|
|
case SQL_DIALECT_V5:
|
|
// Uppercase the Sql isqlGlob.Role name
|
|
IUTILS_make_upper(local_sql_role);
|
|
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;
|
|
IUTILS_remove_and_unescape_quotes(local_sql_role, end_quote);
|
|
}
|
|
else {
|
|
IUTILS_make_upper(local_sql_role);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// CVC: We do not require those pesky transactions for -x or -a options.
|
|
// Metadata extraction works exclusively with default transaction gds__trans.
|
|
|
|
if (start_user_trans)
|
|
{
|
|
// Start the user transaction and default transaction
|
|
|
|
if (!M__trans)
|
|
{
|
|
M_Transaction();
|
|
if (D__trans)
|
|
commit_trans(&D__trans);
|
|
if (setValues.Autocommit)
|
|
D_Transaction();
|
|
}
|
|
}
|
|
|
|
// 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 && !printUser(dbname))
|
|
{
|
|
if (local_usr[0] != '\0')
|
|
{
|
|
if (local_sql_role[0] != '\0')
|
|
{
|
|
isqlGlob.printf("Database: %s, User: %s, Role: %s%s",
|
|
dbname, local_usr, local_sql_role, NEWLINE);
|
|
}
|
|
else {
|
|
isqlGlob.printf("Database: %s, User: %s%s", dbname, local_usr, NEWLINE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (local_sql_role[0] != '\0') {
|
|
isqlGlob.printf("Database: %s, Role: %s%s", dbname, local_sql_role, NEWLINE);
|
|
}
|
|
else {
|
|
isqlGlob.printf("Database: %s%s", dbname, NEWLINE);
|
|
}
|
|
}
|
|
}
|
|
|
|
global_Stmt = NULL;
|
|
|
|
return SKIP;
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
PathName file;
|
|
|
|
if (PathUtils::isRelative(path))
|
|
{
|
|
PathName newPath, temp;
|
|
PathUtils::splitLastComponent(newPath, temp, Filelist->Ifp().fileName(false));
|
|
|
|
PathUtils::concatPath(file, newPath, path);
|
|
}
|
|
else
|
|
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.c_str(), "r");
|
|
if (fp)
|
|
{
|
|
Filelist->insertIfp();
|
|
Filelist->Ifp().init(fp, file.c_str(), path);
|
|
}
|
|
else
|
|
{
|
|
IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << path);
|
|
STDERROUT(errbuf);
|
|
return FAIL;
|
|
}
|
|
|
|
Input_file = true;
|
|
return SKIP;
|
|
}
|
|
|
|
|
|
static processing_state newoutput(const TEXT* outfile)
|
|
{
|
|
/**************************************
|
|
*
|
|
* n e w o u t p u t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Change the current output file
|
|
*
|
|
* Parameters: outfile : Name of file to receive query output
|
|
*
|
|
**************************************/
|
|
processing_state ret = SKIP;
|
|
// If there is a file name, attempt to open it for append
|
|
|
|
if (*outfile)
|
|
{
|
|
TEXT path[MAXPATHLEN];
|
|
strip_quotes(outfile, path);
|
|
outfile = path;
|
|
FILE* fp = fopen(outfile, "a");
|
|
if (fp)
|
|
{
|
|
if (isqlGlob.Out && isqlGlob.Out != stdout)
|
|
fclose(isqlGlob.Out);
|
|
isqlGlob.Out = fp;
|
|
if (Merge_stderr)
|
|
isqlGlob.Errfp = isqlGlob.Out;
|
|
if (Merge_diagnostic)
|
|
Diag = isqlGlob.Out;
|
|
}
|
|
else
|
|
{
|
|
TEXT errbuf[MSG_LENGTH];
|
|
IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << outfile);
|
|
STDERROUT(errbuf);
|
|
ret = FAIL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Revert to stdout
|
|
if (isqlGlob.Out != stdout)
|
|
{
|
|
fclose(isqlGlob.Out);
|
|
isqlGlob.Out = stdout;
|
|
if (Merge_stderr)
|
|
isqlGlob.Errfp = isqlGlob.Out;
|
|
if (Merge_diagnostic)
|
|
Diag = isqlGlob.Out;
|
|
}
|
|
}
|
|
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
|
|
*
|
|
**************************************/
|
|
if (!*colname || (strlen(colname) >= QUOTEDLENGTH))
|
|
return ps_ERR;
|
|
|
|
char buf[QUOTEDLENGTH];
|
|
if (colname[0] == DBL_QUOTE)
|
|
{
|
|
strcpy(buf, colname);
|
|
IUTILS_remove_and_unescape_quotes(buf, DBL_QUOTE);
|
|
colname = buf;
|
|
}
|
|
|
|
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.
|
|
setValues.global_Cols.remove(colname);
|
|
return SKIP;
|
|
}
|
|
|
|
const int size = atoi(sizestr);
|
|
if (size <= 0)
|
|
return ps_ERR;
|
|
|
|
// We don't care if it was insertion or replacement.
|
|
setValues.global_Cols.put(colname, size);
|
|
return SKIP;
|
|
}
|
|
|
|
static processing_state newMaxRows(const TEXT* newMaxRowsStr)
|
|
{
|
|
/**************************************
|
|
*
|
|
* newMaxRows
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Sets the new value for the setValues.maxRows limit (max rows to be retrieved).
|
|
*
|
|
**************************************/
|
|
|
|
char* p;
|
|
|
|
errno = 0;
|
|
const long newMaxRows = strtol(newMaxRowsStr, &p, 10);
|
|
// I was going to use this one, but "-1" parses as max ulong without error
|
|
// and it would be politer to give an error.
|
|
//const ULONG newMaxRows = strtoul(newMaxRowsStr, &p, 10);
|
|
|
|
// CVC: I added this block because Windows wasn't working according to Mark's
|
|
// expectation: it only produces ERANGE. Thus, garbage like
|
|
// set maxrows abs; wasn't caught.
|
|
if (p == newMaxRowsStr)
|
|
{
|
|
IUTILS_put_errmsg(MAXROWS_INVALID, SafeArg() << newMaxRowsStr);
|
|
return ps_ERR;
|
|
}
|
|
|
|
switch (errno)
|
|
{
|
|
case 0: // everything correct
|
|
break;
|
|
case ERANGE:
|
|
// Only ERANGE is part of the ANSI standard here.
|
|
IUTILS_put_errmsg(MAXROWS_OUTOF_RANGE, SafeArg() << newMaxRowsStr << SLONG_MAX);
|
|
return ps_ERR;
|
|
default:
|
|
// EINVAL and the like
|
|
IUTILS_put_errmsg(MAXROWS_INVALID, SafeArg() << newMaxRowsStr);
|
|
return ps_ERR;
|
|
}
|
|
|
|
if (newMaxRows < 0)
|
|
{
|
|
IUTILS_put_errmsg(MAXROWS_NEGATIVE, SafeArg() << newMaxRowsStr);
|
|
return ps_ERR;
|
|
}
|
|
|
|
setValues.maxRows = newMaxRows;
|
|
return SKIP;
|
|
}
|
|
|
|
|
|
static processing_state newtrans(const TEXT* statement)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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, fbTrans, alone
|
|
* Parameters: The statement in its entirety
|
|
*
|
|
**************************************/
|
|
|
|
if (!ISQL_dbcheck())
|
|
return FAIL;
|
|
|
|
if (end_trans() == FAIL)
|
|
return FAIL;
|
|
|
|
M__trans = 0;
|
|
|
|
// M__trans = 0 after the commit or rollback. Ready for a new transaction
|
|
|
|
M__trans = DB->execute(fbStatus, NULL, 0, statement, isqlGlob.SQL_dialect,
|
|
NULL, NULL, NULL, NULL);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return FAIL;
|
|
}
|
|
|
|
return SKIP;
|
|
}
|
|
|
|
|
|
static processing_state parse_arg(int argc, SCHAR** argv, SCHAR* tabname)
|
|
// , FILE** sess) Last param was for wisql
|
|
{
|
|
/**************************************
|
|
*
|
|
* p a r s e _ a r g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Parse command line flags
|
|
* All flags except database are -X. Look for
|
|
* the - and make it so.
|
|
*
|
|
**************************************/
|
|
processing_state ret = SKIP;
|
|
|
|
TEXT errbuf[MSG_LENGTH];
|
|
|
|
// Initialize database name
|
|
|
|
isqlGlob.global_Db_name[0] = '\0';
|
|
isqlGlob.global_Target_db[0] = '\0';
|
|
Password[0] = '\0';
|
|
isqlGlob.User[0] = '\0';
|
|
isqlGlob.Role[0] = '\0';
|
|
global_numbufs = 0;
|
|
Quiet = false;
|
|
Exit_value = FINI_OK;
|
|
|
|
// default behavior in V6.0 is SQL_DIALECT_V6
|
|
|
|
requested_SQL_dialect = SQL_DIALECT_V6;
|
|
|
|
Merge_stderr = false;
|
|
Merge_diagnostic = false;
|
|
|
|
{ // scope
|
|
const size_t lgth = strlen(DEFCHARSET);
|
|
if (lgth < MAXCHARSET_SIZE)
|
|
strcpy(setValues.ISQL_charset, DEFCHARSET);
|
|
else
|
|
fb_utils::copy_terminate(setValues.ISQL_charset, DEFCHARSET, MAXCHARSET_SIZE);
|
|
} // scope
|
|
|
|
// redirected stdin means act like -i was set
|
|
|
|
Filelist->Ifp().init(stdin, "stdin", "stdin");
|
|
|
|
// Terminators are initialized
|
|
|
|
isqlGlob.Termlen = strlen(DEFTERM);
|
|
if (isqlGlob.Termlen < MAXTERM_SIZE)
|
|
strcpy(isqlGlob.global_Term, DEFTERM);
|
|
else
|
|
{
|
|
isqlGlob.Termlen = MAXTERM_SIZE - 1;
|
|
fb_utils::copy_terminate(isqlGlob.global_Term, DEFTERM, isqlGlob.Termlen + 1);
|
|
}
|
|
|
|
// Initialize list of input file pointers
|
|
|
|
//Filelist->clear();
|
|
|
|
// Interpret each command line argument
|
|
|
|
const SCHAR switchchar = '-';
|
|
|
|
#ifdef DEV_BUILD
|
|
bool istable = false;
|
|
#endif
|
|
|
|
Switches switches(isql_in_sw_table, FB_NELEM(isql_in_sw_table), true, true);
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
const char* s = argv[i];
|
|
// 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)
|
|
{
|
|
int swid = IN_SW_ISQL_0;
|
|
int swarg_int = 0;
|
|
char* swarg_str = NULL;
|
|
|
|
Firebird::string s2(s);
|
|
Switches::in_sw_tab_t* option = switches.findSwitchMod(s2);
|
|
if (option)
|
|
{
|
|
swid = option->in_sw;
|
|
if (option->in_sw_state)
|
|
{
|
|
IUTILS_msg_get(USAGE_DUPSW, errbuf, SafeArg() << s);
|
|
STDERROUT(errbuf);
|
|
ret = ps_ERR;
|
|
break;
|
|
}
|
|
//option->in_sw_state = true; It's not enough with switches that have multiple spellings
|
|
switches.activate(swid);
|
|
|
|
switch (option->in_sw_optype)
|
|
{
|
|
case iqoArgNone: // nothing to do
|
|
break;
|
|
case iqoArgString:
|
|
case iqoArgInteger:
|
|
// make sure swarg_str is really a pointer to argv, not something else !!
|
|
if (++i < argc)
|
|
swarg_str = argv[i];
|
|
if (!swarg_str || !*swarg_str)
|
|
{
|
|
IUTILS_msg_get(USAGE_NOARG, errbuf, SafeArg() << s);
|
|
STDERROUT(errbuf);
|
|
ret = ps_ERR;
|
|
}
|
|
else if (option->in_sw_optype == iqoArgInteger)
|
|
{
|
|
char* err;
|
|
long value = strtol(swarg_str, &err, 10);
|
|
if (*err)
|
|
{
|
|
// conversion error
|
|
IUTILS_msg_get(USAGE_NOTINT, errbuf, SafeArg() << swarg_str << s);
|
|
STDERROUT(errbuf);
|
|
ret = ps_ERR;
|
|
}
|
|
else if ((value < INT_MIN) || (value > INT_MAX))
|
|
{
|
|
IUTILS_msg_get(USAGE_RANGE, errbuf, SafeArg() << swarg_str << s);
|
|
STDERROUT(errbuf);
|
|
ret = ps_ERR;
|
|
}
|
|
else {
|
|
swarg_int = (int) value;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
}
|
|
|
|
// Quit the loop of interpreting if we got an error
|
|
if (ret == ps_ERR)
|
|
break;
|
|
|
|
switch (swid)
|
|
{
|
|
#ifdef DEV_BUILD
|
|
case IN_SW_ISQL_EXTRACTTBL:
|
|
istable = true;
|
|
ret = EXTRACT;
|
|
break;
|
|
#endif
|
|
case IN_SW_ISQL_EXTRACT:
|
|
ret = EXTRACT;
|
|
break;
|
|
|
|
case IN_SW_ISQL_EXTRACTALL:
|
|
ret = EXTRACTALL;
|
|
break;
|
|
|
|
case IN_SW_ISQL_BAIL:
|
|
setValues.BailOnError = true;
|
|
break;
|
|
|
|
case IN_SW_ISQL_ECHO:
|
|
setValues.Echo = true;
|
|
break;
|
|
|
|
case IN_SW_ISQL_MERGE:
|
|
Merge_stderr = true;
|
|
break;
|
|
|
|
case IN_SW_ISQL_MERGE2:
|
|
Merge_diagnostic = true;
|
|
break;
|
|
|
|
case IN_SW_ISQL_NOAUTOCOMMIT:
|
|
setValues.Autocommit = false;
|
|
break;
|
|
|
|
case IN_SW_ISQL_NODBTRIGGERS:
|
|
Nodbtriggers = true;
|
|
break;
|
|
|
|
case IN_SW_ISQL_NOWARN:
|
|
setValues.Warnings = false;
|
|
break;
|
|
|
|
case IN_SW_ISQL_OUTPUT:
|
|
if (newoutput(swarg_str) == FAIL) {
|
|
ret = ps_ERR;
|
|
}
|
|
break;
|
|
|
|
case IN_SW_ISQL_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 IN_SW_ISQL_TERM:
|
|
isqlGlob.Termlen = strlen(swarg_str);
|
|
if (isqlGlob.Termlen >= MAXTERM_SIZE) {
|
|
isqlGlob.Termlen = MAXTERM_SIZE - 1;
|
|
}
|
|
fb_utils::copy_terminate(isqlGlob.global_Term, swarg_str, isqlGlob.Termlen + 1);
|
|
break;
|
|
|
|
case IN_SW_ISQL_DATABASE:
|
|
fb_utils::copy_terminate(isqlGlob.global_Target_db, swarg_str, sizeof(isqlGlob.global_Target_db));
|
|
break;
|
|
|
|
case IN_SW_ISQL_PAGE:
|
|
if (swarg_int < 0)
|
|
{
|
|
IUTILS_msg_get(USAGE_RANGE, errbuf, SafeArg() << swarg_str << s);
|
|
STDERROUT(errbuf);
|
|
ret = ps_ERR;
|
|
}
|
|
else if (swarg_int == 0) // let's interpret -pag 0 as SET HEADING OFF
|
|
setValues.Heading = false;
|
|
Pagelength = swarg_int;
|
|
break;
|
|
|
|
case IN_SW_ISQL_PASSWORD:
|
|
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 IN_SW_ISQL_FETCHPASS:
|
|
{
|
|
const char* pass = NULL;
|
|
const fb_utils::FetchPassResult rez = fb_utils::fetchPassword(swarg_str, pass);
|
|
if (rez == fb_utils::FETCH_PASS_OK)
|
|
{
|
|
fb_utils::copy_terminate(Password, pass, sizeof(Password));
|
|
global_psw = true;
|
|
}
|
|
else
|
|
{
|
|
switch (rez)
|
|
{
|
|
case fb_utils::FETCH_PASS_FILE_OPEN_ERROR:
|
|
IUTILS_msg_get(PASS_FILE_OPEN, errbuf, SafeArg() << swarg_str << errno);
|
|
// could not open password file @1, errno @2
|
|
break;
|
|
case fb_utils::FETCH_PASS_FILE_READ_ERROR:
|
|
IUTILS_msg_get(PASS_FILE_READ, errbuf, SafeArg() << swarg_str << errno);
|
|
// could not read password file @1, errno @2
|
|
break;
|
|
case fb_utils::FETCH_PASS_FILE_EMPTY:
|
|
IUTILS_msg_get(EMPTY_PASS, errbuf, SafeArg() << swarg_str);
|
|
// empty password file @1
|
|
break;
|
|
}
|
|
STDERROUT(errbuf);
|
|
ret = ps_ERR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IN_SW_ISQL_USER:
|
|
fb_utils::copy_terminate(isqlGlob.User, swarg_str, sizeof(isqlGlob.User));
|
|
global_usr = true;
|
|
break;
|
|
|
|
case IN_SW_ISQL_ROLE:
|
|
fb_utils::copy_terminate(isqlGlob.Role, swarg_str, sizeof(isqlGlob.Role));
|
|
global_role = true;
|
|
break;
|
|
|
|
case IN_SW_ISQL_ROLE2:
|
|
strcpy(isqlGlob.Role, "\"");
|
|
fb_utils::copy_terminate(isqlGlob.Role + 1, swarg_str, sizeof(isqlGlob.Role) - 2);
|
|
strcat(isqlGlob.Role, "\"");
|
|
global_role = true;
|
|
break;
|
|
|
|
case IN_SW_ISQL_CACHE:
|
|
if (swarg_int <= 0)
|
|
{
|
|
IUTILS_msg_get(USAGE_RANGE, errbuf, SafeArg() << swarg_str << s);
|
|
STDERROUT(errbuf);
|
|
ret = ps_ERR;
|
|
}
|
|
global_numbufs = swarg_int;
|
|
has_global_numbufs = true;
|
|
break;
|
|
|
|
case IN_SW_ISQL_CHARSET:
|
|
{
|
|
fb_utils::copy_terminate(Charset, swarg_str, sizeof(Charset));
|
|
const size_t lgth = strlen(Charset);
|
|
if (lgth < MAXCHARSET_SIZE) {
|
|
strcpy(setValues.ISQL_charset, Charset);
|
|
}
|
|
else
|
|
fb_utils::copy_terminate(setValues.ISQL_charset, Charset, MAXCHARSET_SIZE);
|
|
}
|
|
break;
|
|
|
|
case IN_SW_ISQL_QUIET:
|
|
Quiet = true;
|
|
break;
|
|
|
|
#ifdef TRUSTED_AUTH
|
|
case IN_SW_ISQL_TRUSTED:
|
|
Trusted_auth = true;
|
|
break;
|
|
#endif
|
|
|
|
case IN_SW_ISQL_VERSION:
|
|
Version_info = true;
|
|
IUTILS_msg_get(VERSION, errbuf, SafeArg() << FB_VERSION);
|
|
isqlGlob.printf("%s%s", errbuf, NEWLINE);
|
|
break;
|
|
|
|
case IN_SW_ISQL_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;
|
|
|
|
case IN_SW_ISQL_HELP:
|
|
ret = ps_ERR;
|
|
break;
|
|
|
|
default: // unknown switch
|
|
IUTILS_msg_get(SWITCH, errbuf, SafeArg() << s);
|
|
STDERROUT(errbuf);
|
|
ret = ps_ERR;
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is not a switch, it is a db_name
|
|
if (isqlGlob.global_Db_name[0])
|
|
{
|
|
// We already have a database name
|
|
IUTILS_msg_get(USAGE_DUPDB, errbuf, SafeArg() << isqlGlob.global_Db_name << s);
|
|
STDERROUT(errbuf);
|
|
ret = ps_ERR;
|
|
}
|
|
else
|
|
{
|
|
fb_utils::copy_terminate(isqlGlob.global_Db_name, s, sizeof(isqlGlob.global_Db_name));
|
|
#ifdef DEV_BUILD
|
|
// If there is a table name, it follows
|
|
if (istable && (++i < argc) && (s = argv[i]) && *s)
|
|
fb_utils::copy_terminate(tabname, s, WORDLENGTH);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Quit the loop of interpreting if we got an error
|
|
if (ret == ps_ERR)
|
|
break;
|
|
}
|
|
|
|
// If not input, then set up first filelist
|
|
|
|
if (Filelist->readingStdin())
|
|
{
|
|
Filelist->insert(stdin, "stdin", "stdin");
|
|
fb_assert(Filelist->count() == 1);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// *********************
|
|
// 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)
|
|
{
|
|
if (!DB)
|
|
return FAIL;
|
|
|
|
M__trans = DB->execute(fbStatus, M__trans, 0, cmd, isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return ps_ERR;
|
|
|
|
return SKIP;
|
|
}
|
|
#endif
|
|
|
|
static bool checkSpecial(TEXT* const p, const int length, const double value)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c h e c k S p e c i a l
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Special case - Nan and Infinity.
|
|
* Some libraries (SFIO) work wrong with them.
|
|
*
|
|
**************************************/
|
|
const TEXT* t = NULL;
|
|
if (isnan(value))
|
|
t = "NaN";
|
|
else if (isinf(value))
|
|
t = Firebird::isNegativeInf(value) ? "-Infinity" : "Infinity";
|
|
else
|
|
return false;
|
|
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%s%s", t, NEWLINE);
|
|
}
|
|
sprintf(p, "%*.*s ", length, length, t);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static unsigned print_item(TEXT** s, const IsqlVar* var, const unsigned length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p r i n t _ i t e m
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Print an SQL data item as described.
|
|
*
|
|
**************************************/
|
|
TEXT d[32];
|
|
|
|
TEXT* p = *s;
|
|
*p = '\0';
|
|
|
|
unsigned dtype = var->type; // may be modified by print_item_blob()
|
|
const int dscale = var->scale;
|
|
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%-31s ", var->alias);
|
|
}
|
|
|
|
if (var->nullable && (*var->nullInd < 0))
|
|
{
|
|
// If field was missing print <null>
|
|
|
|
if (setValues.List) {
|
|
isqlGlob.printf("<null>%s", NEWLINE);
|
|
}
|
|
|
|
if (dtype == SQL_TEXT || dtype == SQL_VARYING || dtype == SQL_BOOLEAN)
|
|
sprintf(p, "%-*.*s ", length, length, "<null>");
|
|
else
|
|
sprintf(p, "%*.*s ", length, length, "<null>");
|
|
}
|
|
else if (!strncmp(var->field, "DB_KEY", 6))
|
|
{
|
|
// Special handling for db_keys printed in hex
|
|
|
|
// Keep a temp buf, d for building the binary string
|
|
|
|
for (const TEXT* t = var->value.asChar; t < var->value.asChar + var->length; t++)
|
|
{
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%02X", (unsigned int) (UCHAR) *t);
|
|
}
|
|
else
|
|
{
|
|
sprintf(d, "%02X", (unsigned int) (UCHAR) *t);
|
|
strcat(p, d);
|
|
}
|
|
}
|
|
if (setValues.List)
|
|
isqlGlob.printf(NEWLINE);
|
|
else
|
|
strcat(p, " ");
|
|
}
|
|
else
|
|
{
|
|
const ISC_QUAD* blobid;
|
|
TEXT blobbuf[30];
|
|
TEXT* str2;
|
|
tm times;
|
|
|
|
switch (dtype)
|
|
{
|
|
case SQL_ARRAY:
|
|
|
|
// Print blob-ids only here
|
|
|
|
blobid = var->value.blobid;
|
|
sprintf(blobbuf, "%" xLONGFORMAT":%" xLONGFORMAT, blobid->gds_quad_high,
|
|
blobid->gds_quad_low);
|
|
sprintf(p, "%*s ", MAX(17, length), blobbuf);
|
|
break;
|
|
|
|
case SQL_BLOB:
|
|
|
|
// Print blob-ids only here
|
|
|
|
blobid = var->value.blobid;
|
|
sprintf(blobbuf, "%" xLONGFORMAT":%" xLONGFORMAT, blobid->gds_quad_high,
|
|
blobid->gds_quad_low);
|
|
sprintf(p, "%*s ", MAX(17, length), blobbuf);
|
|
if (setValues.List)
|
|
{
|
|
isqlGlob.printf("%s%s", blobbuf, NEWLINE);
|
|
dtype = ISQL_print_item_blob(isqlGlob.Out, var, M__trans, setValues.Doblob);
|
|
isqlGlob.printf(NEWLINE);
|
|
}
|
|
break;
|
|
|
|
case SQL_SHORT:
|
|
case SQL_LONG:
|
|
case SQL_INT64:
|
|
{
|
|
SINT64 value;
|
|
|
|
switch (dtype)
|
|
{
|
|
case SQL_SHORT:
|
|
value = *var->value.asSmallint;
|
|
break;
|
|
case SQL_LONG:
|
|
value = *var->value.asInteger;
|
|
break;
|
|
case SQL_INT64:
|
|
value = *var->value.asBigint;
|
|
break;
|
|
}
|
|
|
|
Firebird::string str_buf;
|
|
print_item_numeric(value, length, dscale, str_buf.getBuffer(length));
|
|
sprintf(p, "%s ", str_buf.c_str());
|
|
if (setValues.List)
|
|
{
|
|
str_buf.ltrim(); // Added 1999-03-23 to left-justify in LIST ON mode
|
|
isqlGlob.printf("%s%s", str_buf.c_str(), NEWLINE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SQL_FLOAT:
|
|
{
|
|
//
|
|
// 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 = *var->value.asFloat;
|
|
|
|
if (checkSpecial(p, length, value))
|
|
{
|
|
break;
|
|
}
|
|
|
|
#if defined(MINGW)
|
|
if (value == 0)
|
|
{
|
|
sprintf(p, "% #*.*g ", length, (int) MIN(8, (length - 6)) - 1, value);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%.*g%s", FLOAT_LEN - 6 -1, value, NEWLINE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sprintf(p, "% #*.*g ", length, (int) MIN(8, (length - 6)), value);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%.*g%s", FLOAT_LEN - 6, value, NEWLINE);
|
|
}
|
|
}
|
|
#else
|
|
sprintf(p, "% #*.*g ", length, (int) MIN(8, (length - 6)), value);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%.*g%s", FLOAT_LEN - 6, value, NEWLINE);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case SQL_DOUBLE:
|
|
{
|
|
const double value = *var->value.asDouble;
|
|
|
|
if (checkSpecial(p, length, value))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Don't let numeric/decimal doubles overflow print length
|
|
// Special handling for 0 -- don't test log for length
|
|
unsigned rounded = 0;
|
|
if (dscale && (!value ||
|
|
(rounded = static_cast<unsigned int>(ceil(fabs(log10(fabs(value)))))) < length - 10))
|
|
{
|
|
unsigned precision = 0;
|
|
|
|
// 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;
|
|
|
|
// Take into account old database containing negative scales for double
|
|
if (dscale < 0 && precision > unsigned(-dscale))
|
|
{
|
|
precision = -dscale;
|
|
}
|
|
|
|
sprintf(p, "%*.*f ", length, precision, value);
|
|
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%.*f%s", precision, /*dscale,*/ value, NEWLINE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if defined(MINGW)
|
|
if (value == 0)
|
|
{
|
|
sprintf(p, "% #*.*g ", length, (int) MIN(16, (length - 7)) - 1, value);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%#.*g%s", DOUBLE_LEN - 7 - 1, value, NEWLINE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sprintf(p, "% #*.*g ", length, (int) MIN(16, (length - 7)), value);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%#.*g%s", DOUBLE_LEN - 7, value, NEWLINE);
|
|
}
|
|
}
|
|
#else
|
|
sprintf(p, "% #*.*g ", length, (int) MIN(16, (length - 7)), value);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%#.*g%s", DOUBLE_LEN - 7, value, NEWLINE);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SQL_TEXT:
|
|
str2 = var->value.asChar;
|
|
// See if it is character set OCTETS
|
|
if (var->charSet == 1)
|
|
{
|
|
const ULONG hex_len = 2 * var->length;
|
|
TEXT* buff2 = (TEXT*) ISQL_ALLOC(hex_len + 1);
|
|
// Convert the string to hex digits
|
|
for (unsigned i = 0; i < var->length; i++) {
|
|
sprintf(&buff2[i * 2], "%02X", (UCHAR)str2[i]);
|
|
}
|
|
buff2[hex_len] = 0;
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%-*s%s", var->length, buff2, NEWLINE);
|
|
}
|
|
else
|
|
sprintf(p, "%-*s ", length, buff2);
|
|
ISQL_FREE(buff2);
|
|
}
|
|
else if (setValues.List)
|
|
isqlGlob.printf("%-*.*s%s", var->length, var->length, str2, NEWLINE);
|
|
else
|
|
{
|
|
IcuUtil::pad(p, var->charSet,
|
|
static_cast<unsigned>(strnlen(var->value.asChar, var->length)),
|
|
var->value.asChar, length, false);
|
|
strcat(p, " ");
|
|
}
|
|
break;
|
|
|
|
case SQL_VARYING:
|
|
{
|
|
vary* avary = var->value.asVary;
|
|
|
|
// If CHARACTER SET OCTETS, print two hex digits per octet
|
|
if (var->charSet == 1)
|
|
{
|
|
const ULONG hex_len = 2 * avary->vary_length;
|
|
char* buff2 = static_cast<char*>(ISQL_ALLOC(hex_len + 1));
|
|
char* bp = buff2;
|
|
|
|
for (unsigned 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 (setValues.List)
|
|
{
|
|
isqlGlob.printf("%s%s", buff2, NEWLINE);
|
|
}
|
|
else
|
|
{
|
|
// Truncate if necessary
|
|
sprintf(p, "%-*.*s ", length, static_cast<unsigned int>(MIN(length, hex_len)), buff2);
|
|
}
|
|
ISQL_FREE(buff2);
|
|
}
|
|
else if (setValues.List)
|
|
isqlGlob.printf("%-*.*s%s", avary->vary_length, avary->vary_length, avary->vary_string, NEWLINE);
|
|
else
|
|
{
|
|
IcuUtil::pad(p, var->charSet, avary->vary_length, avary->vary_string, length, false);
|
|
strcat(p, " ");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SQL_TIMESTAMP:
|
|
isc_decode_timestamp(var->value.asDateTime, ×);
|
|
|
|
if (isqlGlob.SQL_dialect > SQL_DIALECT_V5)
|
|
{
|
|
sprintf(d, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d.%4.4" ULONGFORMAT,
|
|
times.tm_year + 1900, (times.tm_mon + 1),
|
|
times.tm_mday, times.tm_hour, times.tm_min,
|
|
times.tm_sec,
|
|
((ULONG*) var->value.asChar)[1] % ISC_TIME_SECONDS_PRECISION);
|
|
}
|
|
else
|
|
{
|
|
if (setValues.Time_display)
|
|
sprintf(d, "%2d-%s-%4d %2.2d:%2.2d:%2.2d.%4.4" ULONGFORMAT,
|
|
times.tm_mday, alpha_months[times.tm_mon],
|
|
times.tm_year + 1900, times.tm_hour, times.tm_min,
|
|
times.tm_sec,
|
|
((ULONG*) var->value.asChar)[1] % ISC_TIME_SECONDS_PRECISION);
|
|
else
|
|
sprintf(d, "%2d-%s-%4d", times.tm_mday,
|
|
alpha_months[times.tm_mon], times.tm_year + 1900);
|
|
}
|
|
|
|
sprintf(p, "%-*.*s ", length, length, d);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%s%s", d, NEWLINE);
|
|
}
|
|
break;
|
|
|
|
case SQL_TYPE_TIME:
|
|
isc_decode_sql_time(var->value.asTime, ×);
|
|
sprintf(d, "%2.2d:%2.2d:%2.2d.%4.4" ULONGFORMAT,
|
|
times.tm_hour, times.tm_min, times.tm_sec,
|
|
(*var->value.asTime) % ISC_TIME_SECONDS_PRECISION);
|
|
sprintf(p, "%-*.*s ", length, length, d);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%s%s", d, NEWLINE);
|
|
}
|
|
break;
|
|
|
|
case SQL_TYPE_DATE:
|
|
isc_decode_sql_date(var->value.asDate, ×);
|
|
sprintf(d, "%4.4d-%2.2d-%2.2d", times.tm_year + 1900,
|
|
(times.tm_mon + 1), times.tm_mday);
|
|
|
|
sprintf(p, "%-*.*s ", length, length, d);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%s%s", d, NEWLINE);
|
|
}
|
|
break;
|
|
|
|
case SQL_BOOLEAN:
|
|
strcpy(d, (*var->value.asBoolean ? "<true>" : "<false>"));
|
|
sprintf(p, "%-*.*s ", length, length, d);
|
|
if (setValues.List)
|
|
isqlGlob.printf("%s%s", d, NEWLINE);
|
|
break;
|
|
|
|
default:
|
|
sprintf(d, "Unknown type: %d", dtype);
|
|
sprintf(p, "%-*s ", length, d);
|
|
if (setValues.List) {
|
|
isqlGlob.printf("%s%s", d, NEWLINE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (*p)
|
|
++p;
|
|
|
|
*s = p;
|
|
|
|
return dtype;
|
|
}
|
|
|
|
|
|
processing_state ISQL_print_item_blob(FILE* fp, const IsqlVar* var, Firebird::ITransaction* trans, int subtype)
|
|
{
|
|
/******************************************
|
|
*
|
|
* I S Q L _ p r i n t _ i t e m _ b l o b
|
|
*
|
|
******************************************
|
|
*
|
|
* Functional description
|
|
* Print a User Selected BLOB field (as oppposed to
|
|
* any BLOB selected in a show or extract command)
|
|
*
|
|
**************************************/
|
|
TEXT msg[MSG_LENGTH];
|
|
|
|
ISC_QUAD* blobid = var->value.blobid;
|
|
|
|
// Don't bother with null blobs
|
|
|
|
if (UserBlob::blobIsNull(*blobid))
|
|
return CONT;
|
|
|
|
if ((var->subType != subtype) && (subtype != ALL_BLOBS))
|
|
{
|
|
IUTILS_msg_get(BLOB_SUBTYPE, msg, SafeArg() << subtype << var->subType);
|
|
// Blob display set to subtype %d. This blob: subtype = %d\n
|
|
IUTILS_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->subType;
|
|
if (blob_subtype == isc_blob_text)
|
|
{
|
|
// ASF: Since ODS11.1, BLOBs are automatically transliterated to the client charset.
|
|
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;
|
|
from_desc.blob_desc_subtype = var->subType;
|
|
from_desc.blob_desc_charset = var->charSet;
|
|
|
|
ISC_BLOB_DESC to_desc;
|
|
ISC_STATUS_ARRAY oldStat;
|
|
isc_blob_default_desc(&to_desc, (const UCHAR*) var->relation, (const UCHAR*) var->field);
|
|
if (!isc_blob_gen_bpb(oldStat, &to_desc, &from_desc, sizeof(bpb_buffer),
|
|
bpb_buffer, &bpb_length))
|
|
{
|
|
bpb = bpb_buffer;
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
Firebird::IBlob* blob = DB->openBlob(fbStatus, trans, blobid, bpb_length, bpb);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
|
|
TEXT buffer[BUFFER_LENGTH512];
|
|
|
|
do
|
|
{
|
|
unsigned int length;
|
|
int cc = blob->getSegment(fbStatus, sizeof(buffer) - 1, buffer, &length);
|
|
if (cc == Firebird::IStatus::RESULT_NO_DATA || cc == Firebird::IStatus::RESULT_ERROR)
|
|
break;
|
|
|
|
buffer[length] = 0;
|
|
|
|
// Special displays for blr or acl subtypes
|
|
|
|
if (blob_subtype > isc_blob_text && blob_subtype < isc_blob_max_predefined_subtype)
|
|
{
|
|
for (char* b = buffer + length - 1; b >= buffer;)
|
|
{
|
|
if (*b == '\n' || *b == '\t' || *b == BLANK)
|
|
*b-- = 0;
|
|
else
|
|
break;
|
|
}
|
|
|
|
IUTILS_printf2(fp, "%s %s%s", TAB_AS_SPACES, buffer, NEWLINE);
|
|
}
|
|
else
|
|
IUTILS_printf(fp, buffer);
|
|
|
|
} while (true);
|
|
|
|
if (failed())
|
|
{
|
|
ISQL_errmsg(fbStatus);
|
|
blob->close(fbStatus);
|
|
return ps_ERR;
|
|
}
|
|
|
|
blob->close(fbStatus);
|
|
return CONT;
|
|
}
|
|
|
|
|
|
static void print_item_numeric(SINT64 value, int length, int scale, TEXT* buf)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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
|
|
*
|
|
**************************************/
|
|
// Handle special case of no negative scale, no '.' required!
|
|
if (scale >= 0)
|
|
{
|
|
if (scale > 0)
|
|
value *= (SINT64) pow(10.0, (double) scale);
|
|
sprintf(buf, "%*" SQUADFORMAT, length, value);
|
|
return;
|
|
}
|
|
|
|
const bool neg = (value < 0);
|
|
|
|
// Use one less space than allowed, to leave room for '.'
|
|
length--;
|
|
sprintf(buf, "%*" SQUADFORMAT, length, value);
|
|
|
|
// start from LSByte towards MSByte
|
|
int from = length - 1;
|
|
int to = length;
|
|
|
|
// make space for decimal '.' point in buffer
|
|
buf[to + 1] = '\0';
|
|
for (; from >= 0 && DIGIT(buf[from]) && scale; from--, to--, ++scale)
|
|
buf[to] = buf[from];
|
|
|
|
// Check whether we need a '0' (zero) in front of the '.' point
|
|
const bool all_digits_used = !DIGIT(buf[from]);
|
|
|
|
// Insert new '0's to satisfy larger scale than input digits
|
|
// For e.g: 12345 with scale -7 would be .0012345
|
|
if (from > 0 && scale)
|
|
do {
|
|
buf[to--] = '0';
|
|
} while (++scale != 0);
|
|
|
|
// Insert '.' decimal point, and if required, '-' and '0'
|
|
buf[to--] = '.';
|
|
if (all_digits_used)
|
|
{
|
|
buf[to--] = '0';
|
|
if (neg)
|
|
buf[to--] = '-';
|
|
}
|
|
}
|
|
|
|
|
|
static processing_state print_line(Firebird::IMessageMetadata* message, UCHAR* buf, const unsigned pad[], TEXT line[])
|
|
{
|
|
/**************************************
|
|
*
|
|
* p r i n t _ l i n e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Print a line of SQL variables.
|
|
*
|
|
* Args: message, a message metadata
|
|
* pad = an array of the print lengths of all the columns
|
|
* line = pointer to the line buffer.
|
|
*
|
|
**************************************/
|
|
const unsigned maxblob = 20;
|
|
IsqlVar varlist[maxblob]; // No more than 20 blobs per line
|
|
unsigned varnum = 0;
|
|
|
|
{ // scope
|
|
TEXT* p = line;
|
|
unsigned n = message->getCount(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
for (unsigned i = 0; i < n; ++i)
|
|
{
|
|
IsqlVar var;
|
|
if (ISQL_fill_var(&var, message, i, buf) == ps_ERR)
|
|
return ps_ERR;
|
|
|
|
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;
|
|
}
|
|
}
|
|
*p = 0;
|
|
} // scope
|
|
|
|
if (setValues.List)
|
|
{
|
|
isqlGlob.printf(NEWLINE);
|
|
return (CONT);
|
|
}
|
|
|
|
isqlGlob.printf("%s%s", line, NEWLINE);
|
|
|
|
// If blobdisplay is not wanted, set varnum back to 0
|
|
|
|
if (setValues.Doblob == NO_BLOBS)
|
|
varnum = 0;
|
|
else if (varnum >= maxblob)
|
|
{
|
|
TEXT msg[MSG_LENGTH];
|
|
IUTILS_msg_get(ONLY_FIRST_BLOBS, msg, SafeArg() << maxblob);
|
|
isqlGlob.printf("%s%s", msg, NEWLINE);
|
|
}
|
|
|
|
// If there were Blobs to print, print them passing the blobid
|
|
|
|
for (unsigned i = 0; i < varnum; i++)
|
|
{
|
|
const IsqlVar* var = &varlist[i];
|
|
if (!var->nullable || *var->nullInd == 0)
|
|
{
|
|
// Print blob title
|
|
|
|
isqlGlob.printf(
|
|
"==============================================================================%s",
|
|
NEWLINE);
|
|
isqlGlob.printf("%s: %s", var->alias, NEWLINE);
|
|
if (ISQL_print_item_blob(isqlGlob.Out, var, M__trans, setValues.Doblob) != CONT)
|
|
return (ps_ERR);
|
|
isqlGlob.printf(
|
|
"%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 processing_state print_performance(const SINT64* perf_before)
|
|
{
|
|
// Translation of report strings. Do not remove "static" modifier.
|
|
static bool have_report = false;
|
|
static Firebird::GlobalPtr<Firebird::string> diag;
|
|
|
|
SINT64 perf_after[ISQL_COUNTERS];
|
|
Firebird::UtilInterfacePtr()->getPerfCounters(fbStatus, DB, ISQL_COUNTERS_SET, perf_after);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
|
|
if (!have_report)
|
|
{
|
|
TEXT report_1[MSG_LENGTH];
|
|
IUTILS_msg_get(REPORT_NEW1, report_1);
|
|
// Current memory = !\nDelta memory = !\nMax memory = !\nElapsed time= ~ sec\n
|
|
diag->assign(report_1);
|
|
|
|
#ifndef WIN_NT
|
|
IUTILS_msg_get(REPORT_NEW2, report_1);
|
|
// Cpu = !u sec\n
|
|
diag->append(report_1);
|
|
#endif
|
|
|
|
IUTILS_msg_get(REPORT_NEW3, report_1);
|
|
// Buffers = !b\nReads = !r\nWrites = !w\nFetches = !f\n
|
|
diag->append(report_1);
|
|
|
|
Firebird::string::size_type p;
|
|
while ((p = diag->find('!')) != Firebird::string::npos)
|
|
diag->replace(p, 1, "%" SQUADFORMAT);
|
|
while ((p = diag->find('~')) != Firebird::string::npos)
|
|
diag->replace(p, 1, "%" SQUADFORMAT".%.3" SQUADFORMAT);
|
|
|
|
have_report = true;
|
|
}
|
|
|
|
struct IsqlStatist
|
|
{
|
|
SINT64 curMem, deltaMem, maxMem;
|
|
SINT64 realTime, cpu;
|
|
SINT64 buffers, reads, writes, fetches;
|
|
|
|
// ISQL_COUNTERS_SET = "CurrentMemory, MaxMemory, RealTime, UserTime, Buffers, Reads, Writes, Fetches"
|
|
// 0 1 2 3 4 5 6 7
|
|
IsqlStatist(const SINT64* b, const SINT64* a)
|
|
{
|
|
curMem = a[0];
|
|
deltaMem = a[0] - b[0];
|
|
maxMem = a[1];
|
|
realTime = a[2] - b[2];
|
|
cpu = a[3] - b[3];
|
|
buffers = a[4];
|
|
reads = a[5] - b[5];
|
|
writes = a[6] - b[6];
|
|
fetches = a[7] - b[7];
|
|
}
|
|
};
|
|
|
|
IsqlStatist iStat(perf_before, perf_after);
|
|
|
|
// counters: "%" SQUADFORMAT
|
|
// time: "%" SQUADFORMAT".%.3" SQUADFORMAT
|
|
IUTILS_printf2(Diag, diag->c_str(), iStat.curMem, iStat.deltaMem, iStat.maxMem,
|
|
iStat.realTime / 1000, iStat.realTime % 1000,
|
|
#ifndef WIN_NT
|
|
iStat.cpu / 1000, iStat.cpu % 1000,
|
|
#endif
|
|
iStat.buffers, iStat.reads, iStat.writes, iStat.fetches);
|
|
IUTILS_printf2(Diag, "%s", NEWLINE);
|
|
|
|
return CONT;
|
|
}
|
|
|
|
|
|
// *********************************
|
|
// p r i n t _ m e s s a g e
|
|
// *********************************
|
|
// Show the contents of the input msg, used typically for parameters.
|
|
static void print_message(Firebird::IMessageMetadata* msg, const char* dir)
|
|
{
|
|
unsigned n = msg->getCount(fbStatus);
|
|
isqlGlob.printf(
|
|
"\n%sPUT message field count: %d\n", dir, n);
|
|
for (unsigned i = 0; i < n; ++i)
|
|
{
|
|
unsigned type = msg->getType(fbStatus, i);
|
|
unsigned subtype = msg->getSubType(fbStatus, i);
|
|
unsigned cs;
|
|
isqlGlob.printf("%02d: sqltype: %d %s %sscale: %d subtype: %d len: %d",
|
|
i + 1, type, sqltype_to_string(type), msg->isNullable(fbStatus, i) ? "Nullable " : "",
|
|
msg->getScale(fbStatus, i), subtype, msg->getLength(fbStatus, i));
|
|
|
|
switch(type)
|
|
{
|
|
case SQL_BLOB:
|
|
if (subtype != 1)
|
|
break;
|
|
|
|
case SQL_TEXT:
|
|
case SQL_VARYING:
|
|
cs = msg->getCharSet(fbStatus, i);
|
|
isqlGlob.printf(" charset: %d %s", cs, charset_to_string(cs));
|
|
break;
|
|
}
|
|
|
|
isqlGlob.printf("\n");
|
|
isqlGlob.printf(" : name: %s alias: %s\n",
|
|
msg->getField(fbStatus, i), msg->getAlias(fbStatus, i));
|
|
isqlGlob.printf(" : table: %s owner: %s\n",
|
|
msg->getRelation(fbStatus, i), msg->getOwner(fbStatus, i));
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************
|
|
// 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.
|
|
static void process_header(Firebird::IMessageMetadata* msg, const unsigned pad[], TEXT header[], TEXT header2[])
|
|
{
|
|
// Create the column header : Left justify strings
|
|
|
|
TEXT* p = header;
|
|
TEXT* p2 = header2;
|
|
unsigned n_cols = msg->getCount(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (unsigned i = 0; i < n_cols; ++i)
|
|
{
|
|
IsqlVar var;
|
|
if (ISQL_fill_var(&var, msg, i, NULL) == ps_ERR)
|
|
return;
|
|
|
|
const unsigned type = var.type;
|
|
|
|
IcuUtil::pad(p, isqlGlob.att_charset, static_cast<unsigned>(strlen(var.alias)), var.alias, pad[i],
|
|
(type != SQL_TEXT && type != SQL_VARYING));
|
|
strcat(p, " ");
|
|
|
|
p += strlen(p);
|
|
|
|
// Separators need not go on forever no more than a line
|
|
unsigned limit = IcuUtil::charLength(
|
|
isqlGlob.att_charset, static_cast<unsigned>(strlen(var.alias)), var.alias);
|
|
limit = MAX(limit, pad[i]) + 1;
|
|
|
|
for (unsigned j = 1; j < limit && j < 80; j++)
|
|
*p2++ = '=';
|
|
*p2++ = BLANK;
|
|
}
|
|
*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()
|
|
{
|
|
if (!global_Stmt)
|
|
return;
|
|
|
|
// Bug 7565: A plan larger than plan_buffer will not be displayed
|
|
// Bug 7565: Note also that the plan must fit into Print_Buffer
|
|
|
|
UCHAR plan_info[1];
|
|
plan_info[0] = setValues.ExplainPlan ? isc_info_sql_explain_plan : isc_info_sql_get_plan;
|
|
|
|
Firebird::HalfStaticArray<UCHAR, MAX_SSHORT> planBuffer;
|
|
unsigned planSize = MAX_SSHORT;
|
|
UCHAR* planPtr = planBuffer.getBuffer(planSize);
|
|
|
|
Firebird::string planString;
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
global_Stmt->getInfo(fbStatus, sizeof(plan_info), plan_info, planSize, planPtr);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return;
|
|
|
|
bool truncated = false;
|
|
|
|
for (const UCHAR* ptr = planPtr; ptr < planPtr + planSize;)
|
|
{
|
|
const UCHAR tag = *ptr++;
|
|
|
|
switch (tag)
|
|
{
|
|
case isc_info_sql_get_plan:
|
|
case isc_info_sql_explain_plan:
|
|
{
|
|
const ULONG len = gds__vax_integer(ptr, sizeof(USHORT));
|
|
ptr += sizeof(USHORT);
|
|
planString.assign((const char*) ptr, len);
|
|
ptr += len;
|
|
}
|
|
break;
|
|
case isc_info_truncated:
|
|
truncated = true;
|
|
break;
|
|
case isc_info_end:
|
|
break;
|
|
default:
|
|
IUTILS_printf2(Diag, "Unknown error while retrieving plan%s", NEWLINE);
|
|
return;
|
|
}
|
|
|
|
if (tag == isc_info_end || tag == isc_info_truncated)
|
|
break;
|
|
}
|
|
|
|
if (truncated && i == 0 &&
|
|
// Probably FB 2.1 and before will crash with MAX_USHORT
|
|
ENCODE_ODS(isqlGlob.major_ods, isqlGlob.minor_ods) >= ODS_11_2)
|
|
{
|
|
planSize = MAX_USHORT;
|
|
planBuffer.resize(planSize);
|
|
planPtr = planBuffer.begin();
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (planString.hasData())
|
|
IUTILS_printf2(Diag, "%s%s", planString.c_str(), 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.
|
|
static SINT64 process_record_count(const int statement_type)
|
|
{
|
|
if (!global_Stmt)
|
|
return -1;
|
|
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 UCHAR count_info[] = { isc_info_sql_records };
|
|
UCHAR count_buffer[33];
|
|
global_Stmt->getInfo(fbStatus, sizeof(count_info), count_info,
|
|
sizeof(count_buffer), count_buffer);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (count_buffer[0] == isc_info_sql_records)
|
|
{
|
|
SINT64 total = 0;
|
|
const UCHAR* p = count_buffer + 3;
|
|
while (*p != isc_info_end)
|
|
{
|
|
const UCHAR count_is = *p++;
|
|
const SSHORT len = gds__vax_integer(p, 2);
|
|
p += 2;
|
|
const ULONG count = gds__vax_integer(p, len);
|
|
p += len;
|
|
// CVC: Do as StatementMetadata::getAffectedRecords() does:
|
|
//if (count_is == count_type)
|
|
// return count;
|
|
if (count_is != isc_info_req_select_count)
|
|
total += count;
|
|
}
|
|
return total;
|
|
}
|
|
}
|
|
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 int process_request_type()
|
|
{
|
|
if (!global_Stmt)
|
|
return 0;
|
|
const UCHAR sqlda_info[] = { isc_info_sql_stmt_type };
|
|
UCHAR info_buffer[16];
|
|
global_Stmt->getInfo(fbStatus, sizeof(sqlda_info), sqlda_info, sizeof(info_buffer), info_buffer);
|
|
if (!ISQL_errmsg(fbStatus))
|
|
{
|
|
if (info_buffer[0] == isc_info_sql_stmt_type)
|
|
{
|
|
const UCHAR* b = info_buffer;
|
|
const SSHORT len = gds__vax_integer(b + 1, 2);
|
|
return gds__vax_integer(b + 3, len);
|
|
}
|
|
|
|
//IUTILS_msg_get(UNKNOWN_STATEMENT_TYPE, errmsg);
|
|
STDERROUT("Cannot determine statement type");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// *********************************************
|
|
// p r o c e s s _ m e s s a g e _ d i s p l a y
|
|
// *********************************************
|
|
// Calculate individual column widths and return total line width.
|
|
static unsigned process_message_display(Firebird::IMessageMetadata* message, unsigned pad[])
|
|
{
|
|
unsigned linelength = 0;
|
|
unsigned ncols = message->getCount(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return 0;
|
|
}
|
|
for (unsigned i = 0; i < ncols; ++i)
|
|
{
|
|
IsqlVar var;
|
|
if (ISQL_fill_var(&var, message, i, NULL) == ps_ERR)
|
|
return 0;
|
|
|
|
// Record the length of name and var, then convert to print
|
|
// length, later to be stored in pad array
|
|
|
|
unsigned data_length, disp_length, alignment;
|
|
data_length = disp_length = alignment = var.length;
|
|
unsigned namelength = IcuUtil::charLength(isqlGlob.att_charset,
|
|
static_cast<unsigned>(strlen(var.alias)), var.alias);
|
|
|
|
// Minimum display length should not be less than that needed
|
|
// for displaying null
|
|
|
|
if (namelength < NULL_DISP_LEN)
|
|
namelength = NULL_DISP_LEN;
|
|
|
|
const unsigned type = var.type;
|
|
const SSHORT charSet = TTYPE_TO_CHARSET(var.charSet);
|
|
|
|
switch (type)
|
|
{
|
|
case SQL_BLOB:
|
|
case SQL_ARRAY:
|
|
// enough room for the blob id to print
|
|
|
|
disp_length = 17;
|
|
break;
|
|
case SQL_TIMESTAMP:
|
|
if (setValues.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 (charSet == CS_BINARY)
|
|
disp_length = 2 * var.length;
|
|
else if (charSet == CS_UNICODE_FSS)
|
|
disp_length /= 3;
|
|
else if (charSet == CS_UTF8)
|
|
disp_length /= 4;
|
|
break;
|
|
case SQL_VARYING:
|
|
data_length += sizeof(USHORT) + 1;
|
|
alignment = sizeof(USHORT);
|
|
// OCTETS data is displayed in hex
|
|
if (charSet == CS_BINARY)
|
|
disp_length = 2 * var.length;
|
|
else if (charSet == CS_UNICODE_FSS)
|
|
disp_length /= 3;
|
|
else if (charSet == CS_UTF8)
|
|
disp_length /= 4;
|
|
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;
|
|
case SQL_BOOLEAN:
|
|
disp_length = BOOLEAN_LEN;
|
|
break;
|
|
default:
|
|
disp_length = UNKNOWN_LEN;
|
|
break;
|
|
}
|
|
|
|
// special case db_key alignment which arrives as an SQL_TEXT
|
|
|
|
if (!strncmp(var.field, "DB_KEY", 6))
|
|
{
|
|
alignment = 8;
|
|
disp_length = 2 * var.length;
|
|
}
|
|
|
|
// This is the print width of each column
|
|
|
|
if (disp_length < namelength)
|
|
disp_length = namelength;
|
|
|
|
pad[i] = disp_length;
|
|
|
|
// Is there a collist entry, then use that width, but only for text
|
|
if (type == SQL_TEXT || type == SQL_VARYING)
|
|
{
|
|
if (!setValues.global_Cols.find(var.alias, &pad[i]) && setValues.global_Col_default)
|
|
pad[i] = setValues.global_Col_default;
|
|
disp_length = pad[i];
|
|
|
|
if (charSet == 4)
|
|
disp_length *= 4;
|
|
}
|
|
|
|
// The total line length
|
|
linelength += disp_length + 1;
|
|
}
|
|
|
|
return linelength;
|
|
}
|
|
|
|
|
|
static processing_state process_statement(const TEXT* str2)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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 fbTrans.
|
|
* This function now returns CONT (success) or ps_ERR.
|
|
**************************************/
|
|
|
|
// Here we actively use the fact that fb_cancel_enable/disable commands may be send many times.
|
|
// They are ignored if cancel already has requested state. This let's us disable cancel
|
|
// in the middle of processing, but do not care about special scope for CancelHolder variable.
|
|
class CancelHolder
|
|
{
|
|
public:
|
|
CancelHolder()
|
|
{
|
|
if (DB)
|
|
DB->cancelOperation(fbStatus, fb_cancel_enable);
|
|
}
|
|
~CancelHolder()
|
|
{
|
|
if (DB)
|
|
DB->cancelOperation(fbStatus, fb_cancel_disable);
|
|
}
|
|
};
|
|
|
|
processing_state ret = CONT;
|
|
|
|
// enable CANCEL during statement processing
|
|
CancelHolder cHolder;
|
|
|
|
// If somebody did a commit or rollback, we are out of a transaction
|
|
|
|
if (!M_Transaction())
|
|
return ps_ERR;
|
|
|
|
// No need to start a default transaction unless there is no current one
|
|
|
|
if (setValues.Autocommit)
|
|
{
|
|
if (!D_Transaction())
|
|
return ps_ERR;
|
|
}
|
|
|
|
// If statistics are requested, then reserve them here
|
|
|
|
SINT64 perf_before[ISQL_COUNTERS];
|
|
if (setValues.Stats)
|
|
{
|
|
Firebird::UtilInterfacePtr()->getPerfCounters(fbStatus,
|
|
DB, ISQL_COUNTERS_SET, perf_before);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
}
|
|
|
|
// Prepare the dynamic query stored in string.
|
|
// But put this on the DDL transaction to get maximum visibility of
|
|
// metadata.
|
|
|
|
Firebird::ITransaction* prepare_trans = setValues.Autocommit ? D__trans : M__trans;
|
|
|
|
if (global_Stmt)
|
|
{
|
|
global_Stmt->free(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
return (SKIP);
|
|
}
|
|
|
|
global_Stmt = DB->prepare(fbStatus, prepare_trans, 0, str2, isqlGlob.SQL_dialect,
|
|
Firebird::IStatement::PREPARE_PREFETCH_METADATA);
|
|
if (failed())
|
|
{
|
|
if (isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION && Input_file)
|
|
{
|
|
isqlGlob.printf("%s%s%s%s%s%s",
|
|
NEWLINE,
|
|
"**** Error preparing statement:",
|
|
NEWLINE,
|
|
NEWLINE,
|
|
str2,
|
|
NEWLINE);
|
|
}
|
|
ISQL_errmsg(fbStatus);
|
|
return ps_ERR;
|
|
}
|
|
|
|
// check for warnings
|
|
ISQL_warning(fbStatus);
|
|
|
|
// Find out what kind of statement this is
|
|
const int statement_type = process_request_type();
|
|
if (!statement_type)
|
|
return ps_ERR;
|
|
|
|
if (setValues.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 ||
|
|
statement_type == isc_info_sql_stmt_get_segment;
|
|
|
|
if (can_have_input_parameters)
|
|
{
|
|
Firebird::RefPtr<Firebird::IMessageMetadata>
|
|
input(Firebird::REF_NO_INCR, global_Stmt->getInputMetadata(fbStatus));
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
|
|
// check for warnings
|
|
ISQL_warning(fbStatus);
|
|
|
|
print_message(input, "IN");
|
|
}
|
|
}
|
|
|
|
|
|
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 and this is not DDL, print out the plan now
|
|
|
|
if (setValues.Plan && statement_type != isc_info_sql_stmt_ddl &&
|
|
statement_type != isc_info_sql_stmt_set_generator)
|
|
{
|
|
process_plan();
|
|
if (setValues.Planonly && !is_selectable)
|
|
{
|
|
return ret; // do not execute
|
|
}
|
|
}
|
|
|
|
// If the statement isn't a select, execute it and be done
|
|
|
|
if (!is_selectable && !setValues.Planonly)
|
|
{
|
|
|
|
// If this is an autocommit, put the DDL stmt on a special trans
|
|
|
|
if (setValues.Autocommit &&
|
|
(statement_type == isc_info_sql_stmt_ddl ||
|
|
statement_type == isc_info_sql_stmt_set_generator))
|
|
{
|
|
DB->execute(fbStatus, D__trans, 0, str2, isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
ret = ps_ERR;
|
|
}
|
|
else
|
|
{
|
|
// check for warnings
|
|
ISQL_warning(fbStatus);
|
|
}
|
|
|
|
// CVC: DDL statements that aren't syntax errors are caught by DFW
|
|
// only at commit time, so we need to check here.
|
|
// AP: fbStatus will be cleaned in commit_trans()
|
|
if (!commit_trans(&D__trans))
|
|
ret = ps_ERR;
|
|
|
|
// check for warnings from COMMIT
|
|
ISQL_warning(fbStatus);
|
|
|
|
if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
|
|
ret = ps_ERR;
|
|
return ret;
|
|
}
|
|
|
|
// Check to see if this is a SET TRANSACTION statement
|
|
|
|
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_set() through get_statement(),
|
|
// so this place has little chance to be reached.
|
|
if (newtrans(str2) == FAIL)
|
|
return ps_ERR;
|
|
|
|
if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
|
|
ret = ps_ERR;
|
|
return ret;
|
|
}
|
|
|
|
// This is a non-select DML statement or trans
|
|
|
|
M__trans = global_Stmt->execute(fbStatus, M__trans, NULL, NULL, NULL, NULL);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
// CVC: Make this conditional if it causes problems. For example
|
|
// if (setValues.BailOnError)
|
|
ret = ps_ERR;
|
|
}
|
|
|
|
// check for warnings
|
|
ISQL_warning(fbStatus);
|
|
|
|
// We are executing a commit or rollback, commit default trans
|
|
|
|
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;
|
|
}
|
|
|
|
// Print statistics report
|
|
|
|
if (setValues.Docount)
|
|
{
|
|
const SINT64 count = process_record_count(statement_type);
|
|
if (count >= 0)
|
|
{
|
|
TEXT rec_count_msg[MSG_LENGTH];
|
|
IUTILS_msg_get(REC_COUNT, rec_count_msg, SafeArg() << count);
|
|
// Records affected: %ld
|
|
isqlGlob.printf("%s%s", rec_count_msg, NEWLINE);
|
|
}
|
|
}
|
|
|
|
if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
|
|
ret = ps_ERR;
|
|
return ret;
|
|
}
|
|
|
|
Firebird::RefPtr<Firebird::IMessageMetadata>
|
|
message(Firebird::REF_NO_INCR, global_Stmt->getOutputMetadata(fbStatus));
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
|
|
// check for warnings
|
|
ISQL_warning(fbStatus);
|
|
|
|
const unsigned n_cols = message->getCount(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
|
|
// To facilitate debugging, the option
|
|
// SET SQLDA_DISPLAY ON
|
|
// will activate code to display the SQLDA after each statement.
|
|
|
|
if (setValues.Sqlda_display)
|
|
print_message(message, "OUT");
|
|
|
|
if (setValues.Planonly)
|
|
return ret;
|
|
|
|
unsigned bufLen = message->getMessageLength(fbStatus);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
UCHAR* buffer = global_Buffer->getBuffer(bufLen);
|
|
|
|
// Pad is an array of lengths to be passed to the print_item
|
|
unsigned* pad = NULL;
|
|
if (n_cols) {
|
|
pad = (unsigned*) ISQL_ALLOC ((SLONG) (n_cols * sizeof(int)));
|
|
}
|
|
|
|
// Calculate display width and add a few for line termination, et al
|
|
const SLONG linelength = process_message_display(message, pad) + 10;
|
|
|
|
// Allocate the print line, the header line and the separator
|
|
|
|
TEXT* line = (TEXT*) ISQL_ALLOC(linelength);
|
|
TEXT* header = 0;
|
|
TEXT* header2 = 0;
|
|
|
|
if (setValues.Heading)
|
|
{
|
|
header = (TEXT*) ISQL_ALLOC(linelength);
|
|
header2 = (TEXT*) ISQL_ALLOC(linelength);
|
|
*header = '\0';
|
|
*header2 = '\0';
|
|
|
|
process_header(message, pad, header, header2);
|
|
}
|
|
|
|
// If this is an exec procedure, execute into the buffer with one fetch only
|
|
|
|
if (statement_type == isc_info_sql_stmt_exec_procedure)
|
|
{
|
|
global_Stmt->execute(fbStatus, M__trans, NULL, NULL, message, buffer);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
ret = ps_ERR;
|
|
}
|
|
else
|
|
{
|
|
if (n_cols)
|
|
{
|
|
// do not output unnecessary white text
|
|
isqlGlob.printf(NEWLINE);
|
|
if (!setValues.List && setValues.Heading)
|
|
{
|
|
isqlGlob.printf("%s%s%s%s",
|
|
header,
|
|
NEWLINE,
|
|
header2,
|
|
NEWLINE);
|
|
}
|
|
print_line(message, buffer, pad, line);
|
|
isqlGlob.printf(NEWLINE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, open the cursor to start things up
|
|
|
|
Firebird::IResultSet* curs = global_Stmt->openCursor(fbStatus, M__trans,
|
|
NULL, NULL, message, 0);
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
return ps_ERR;
|
|
}
|
|
|
|
// check for warnings
|
|
ISQL_warning(fbStatus);
|
|
|
|
// Now 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 = !setValues.List && setValues.Heading;
|
|
unsigned int lines;
|
|
for (lines = 0; !Interrupt_flag && !Abort_flag; ++lines)
|
|
{
|
|
|
|
// Check if exceeded setValues.maxRows value
|
|
if (setValues.maxRows != 0 && lines >= setValues.maxRows)
|
|
break;
|
|
|
|
// Fetch the current cursor
|
|
|
|
if (curs->fetchNext(fbStatus, buffer) == Firebird::IStatus::RESULT_NO_DATA)
|
|
break;
|
|
|
|
// Print the header every Pagelength number of lines for
|
|
// command-line ISQL only.
|
|
|
|
if (printHead &&
|
|
(Pagelength && (lines % Pagelength == 0) ||
|
|
!Pagelength && !lines) )
|
|
{
|
|
isqlGlob.printf("%s%s%s%s%s",
|
|
NEWLINE,
|
|
header,
|
|
NEWLINE,
|
|
header2,
|
|
NEWLINE);
|
|
}
|
|
|
|
if (!lines && !printHead)
|
|
isqlGlob.printf(NEWLINE);
|
|
|
|
if (ISQL_errmsg(fbStatus))
|
|
{
|
|
ret = ps_ERR;
|
|
break;
|
|
}
|
|
|
|
ret = print_line(message, buffer, pad, line);
|
|
}
|
|
|
|
if (lines)
|
|
isqlGlob.printf(NEWLINE);
|
|
|
|
// Record count printed here upon request
|
|
|
|
if (setValues.Docount)
|
|
{
|
|
TEXT rec_count_msg[MSG_LENGTH];
|
|
IUTILS_msg_get(REC_COUNT, rec_count_msg, SafeArg() << lines);
|
|
// Records affected: @1
|
|
isqlGlob.printf("%s%s", rec_count_msg, NEWLINE);
|
|
}
|
|
|
|
curs->close(fbStatus);
|
|
}
|
|
|
|
// Avoid cancel during cleanup
|
|
DB->cancelOperation(fbStatus, fb_cancel_disable);
|
|
|
|
// Statistics printed here upon request
|
|
|
|
if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
|
|
ret = ps_ERR;
|
|
|
|
if (pad)
|
|
ISQL_FREE(pad);
|
|
if (line)
|
|
ISQL_FREE(line);
|
|
if (header)
|
|
ISQL_FREE(header);
|
|
if (header2)
|
|
ISQL_FREE(header2);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
#ifdef WIN_NT
|
|
static BOOL CALLBACK query_abort(DWORD dwCtrlType)
|
|
#else
|
|
static int query_abort(const int reason, const int, void*)
|
|
#endif
|
|
{
|
|
/**************************************
|
|
*
|
|
* q u e r y _ a b o r t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Signal handler for interrupting output of a query.
|
|
* Note: this function is currently used in completelly different ways in Windows x POSIX.
|
|
*
|
|
**************************************/
|
|
|
|
bool flag = true;
|
|
|
|
#ifdef WIN_NT
|
|
if (dwCtrlType != CTRL_C_EVENT)
|
|
return FALSE;
|
|
#else
|
|
if (reason != fb_shutrsn_signal)
|
|
{
|
|
return FB_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
if (DB)
|
|
{
|
|
Firebird::LocalStatus ls;
|
|
Firebird::CheckStatusWrapper status(&ls);
|
|
DB->cancelOperation(&status, fb_cancel_raise);
|
|
flag = !(status.getState() & Firebird::IStatus::STATE_ERRORS);
|
|
}
|
|
|
|
if (flag)
|
|
{
|
|
if (Interactive)
|
|
Interrupt_flag = true;
|
|
else
|
|
Abort_flag = true;
|
|
}
|
|
|
|
#ifdef WIN_NT
|
|
return TRUE;
|
|
#else
|
|
// we do not want to proceed with shutdown except when exit() was called
|
|
return FB_FAILURE;
|
|
#endif
|
|
}
|
|
|
|
|
|
// 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);
|
|
const DWORD file_type = GetFileType(in);
|
|
if (file_type == FILE_TYPE_CHAR || file_type == FILE_TYPE_PIPE)
|
|
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.
|
|
// But it's useful for paths as it's now.
|
|
static void strip_quotes(const TEXT* in, TEXT* out)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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;
|
|
}
|
|
|
|
TEXT quote = 0;
|
|
// Skip any initial quote
|
|
if ((*in == DBL_QUOTE) || (*in == SINGLE_QUOTE))
|
|
quote = *in++;
|
|
const TEXT* p = in;
|
|
|
|
// Now copy characters until we see the same quote or EOS
|
|
while (*p && (*p != quote)) {
|
|
*out++ = *p++;
|
|
}
|
|
*out = 0;
|
|
}
|
|
|
|
|
|
static const char* sqltype_to_string(unsigned sqltype)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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)
|
|
{
|
|
case SQL_TEXT:
|
|
return "TEXT";
|
|
case SQL_VARYING:
|
|
return "VARYING";
|
|
case SQL_SHORT:
|
|
return "SHORT";
|
|
case SQL_LONG:
|
|
return "LONG";
|
|
case SQL_INT64:
|
|
return "INT64";
|
|
case SQL_FLOAT:
|
|
return "FLOAT";
|
|
case SQL_DOUBLE:
|
|
return "DOUBLE";
|
|
case SQL_D_FLOAT:
|
|
return "D_FLOAT";
|
|
case SQL_TIMESTAMP:
|
|
return "TIMESTAMP";
|
|
case SQL_TYPE_DATE:
|
|
return "SQL DATE";
|
|
case SQL_TYPE_TIME:
|
|
return "TIME";
|
|
case SQL_BLOB:
|
|
return "BLOB";
|
|
case SQL_ARRAY:
|
|
return "ARRAY";
|
|
case SQL_QUAD:
|
|
return "QUAD";
|
|
case SQL_BOOLEAN:
|
|
return "BOOLEAN";
|
|
case SQL_NULL:
|
|
return "NULL";
|
|
default:
|
|
return UNKNOWN;
|
|
}
|
|
}
|
|
|
|
|
|
static const char* charset_to_string(unsigned charset)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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
|
|
*
|
|
**************************************/
|
|
static Firebird::GlobalPtr<Firebird::GenericMap<Firebird::Pair<Firebird::Right<unsigned, Firebird::string> > > > csMap;
|
|
|
|
charset = TTYPE_TO_CHARSET(charset);
|
|
|
|
Firebird::string* text = csMap->get(charset);
|
|
if (text)
|
|
return text->c_str();
|
|
|
|
csMap->clear();
|
|
bool err = false;
|
|
|
|
if (!frontendTransaction())
|
|
return UNKNOWN;
|
|
|
|
FOR CS IN RDB$CHARACTER_SETS
|
|
if (!err)
|
|
{
|
|
try
|
|
{
|
|
fb_utils::exact_name(CS.RDB$CHARACTER_SET_NAME);
|
|
csMap->put(CS.RDB$CHARACTER_SET_ID, CS.RDB$CHARACTER_SET_NAME);
|
|
}
|
|
catch (const Firebird::Exception& ex)
|
|
{
|
|
err = true;
|
|
ex.stuffException(fbStatus);
|
|
ISQL_errmsg(fbStatus);
|
|
}
|
|
}
|
|
END_FOR
|
|
ON_ERROR
|
|
ISQL_errmsg(fbStatus);
|
|
END_ERROR;
|
|
|
|
text = csMap->get(charset);
|
|
if (text)
|
|
return text->c_str();
|
|
|
|
return UNKNOWN;
|
|
}
|