mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 16:43:03 +01:00
Feature SET AUTOTERM in ISQL. (#7868)
This commit is contained in:
parent
c7f88f1b9c
commit
f3205ff81d
@ -23,6 +23,7 @@
|
|||||||
<ClCompile Include="..\..\..\src\isql\Extender.cpp" />
|
<ClCompile Include="..\..\..\src\isql\Extender.cpp" />
|
||||||
<ClCompile Include="..\..\..\gen\isql\extract.cpp" />
|
<ClCompile Include="..\..\..\gen\isql\extract.cpp" />
|
||||||
<ClCompile Include="..\..\..\src\common\fb_exception.cpp" />
|
<ClCompile Include="..\..\..\src\common\fb_exception.cpp" />
|
||||||
|
<ClCompile Include="..\..\..\src\isql\FrontendLexer.cpp" />
|
||||||
<ClCompile Include="..\..\..\src\isql\InputDevices.cpp" />
|
<ClCompile Include="..\..\..\src\isql\InputDevices.cpp" />
|
||||||
<ClCompile Include="..\..\..\gen\isql\isql.cpp" />
|
<ClCompile Include="..\..\..\gen\isql\isql.cpp" />
|
||||||
<ClCompile Include="..\..\..\src\isql\iutils.cpp" />
|
<ClCompile Include="..\..\..\src\isql\iutils.cpp" />
|
||||||
@ -38,6 +39,7 @@
|
|||||||
<ClInclude Include="..\..\..\src\isql\ColList.h" />
|
<ClInclude Include="..\..\..\src\isql\ColList.h" />
|
||||||
<ClInclude Include="..\..\..\src\isql\Extender.h" />
|
<ClInclude Include="..\..\..\src\isql\Extender.h" />
|
||||||
<ClInclude Include="..\..\..\src\isql\extra_proto.h" />
|
<ClInclude Include="..\..\..\src\isql\extra_proto.h" />
|
||||||
|
<ClInclude Include="..\..\..\src\isql\FrontendLexer.h" />
|
||||||
<ClInclude Include="..\..\..\src\isql\InputDevices.h" />
|
<ClInclude Include="..\..\..\src\isql\InputDevices.h" />
|
||||||
<ClInclude Include="..\..\..\src\isql\isql.h" />
|
<ClInclude Include="..\..\..\src\isql\isql.h" />
|
||||||
<ClInclude Include="..\..\..\src\isql\isql_proto.h" />
|
<ClInclude Include="..\..\..\src\isql\isql_proto.h" />
|
||||||
|
@ -27,6 +27,9 @@
|
|||||||
<ClCompile Include="..\..\..\src\common\fb_exception.cpp">
|
<ClCompile Include="..\..\..\src\common\fb_exception.cpp">
|
||||||
<Filter>ISQL files</Filter>
|
<Filter>ISQL files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\..\src\isql\FrontendLexer.cpp">
|
||||||
|
<Filter>ISQL files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\..\src\isql\InputDevices.cpp">
|
<ClCompile Include="..\..\..\src\isql\InputDevices.cpp">
|
||||||
<Filter>ISQL files</Filter>
|
<Filter>ISQL files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -67,6 +70,9 @@
|
|||||||
<ClInclude Include="..\..\..\src\isql\extra_proto.h">
|
<ClInclude Include="..\..\..\src\isql\extra_proto.h">
|
||||||
<Filter>Header files</Filter>
|
<Filter>Header files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\..\src\isql\FrontendLexer.h">
|
||||||
|
<Filter>Header files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\..\..\src\isql\InputDevices.h">
|
<ClInclude Include="..\..\..\src\isql\InputDevices.h">
|
||||||
<Filter>Header files</Filter>
|
<Filter>Header files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -176,6 +176,7 @@
|
|||||||
</ResourceCompile>
|
</ResourceCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClCompile Include="..\..\..\src\isql\tests\FrontendLexerTest.cpp" />
|
||||||
<ClCompile Include="..\..\..\src\isql\tests\ISqlTest.cpp" />
|
<ClCompile Include="..\..\..\src\isql\tests\ISqlTest.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
</Filter>
|
</Filter>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClCompile Include="..\..\..\src\isql\tests\FrontendLexerTest.cpp">
|
||||||
|
<Filter>source</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\..\src\isql\tests\ISqlTest.cpp">
|
<ClCompile Include="..\..\..\src\isql\tests\ISqlTest.cpp">
|
||||||
<Filter>source</Filter>
|
<Filter>source</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -330,7 +330,7 @@ SQL> SET PER_TAB OFF;
|
|||||||
Isql enhancements in Firebird v6.
|
Isql enhancements in Firebird v6.
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
EXPLAIN statement.
|
12) EXPLAIN statement.
|
||||||
|
|
||||||
Author: Adriano dos Santos Fernandes
|
Author: Adriano dos Santos Fernandes
|
||||||
|
|
||||||
@ -353,3 +353,69 @@ CON> select id from employees where id = ? into id;
|
|||||||
CON> end!
|
CON> end!
|
||||||
SQL>
|
SQL>
|
||||||
SQL> set term ;!
|
SQL> set term ;!
|
||||||
|
|
||||||
|
|
||||||
|
13) SET AUTOTERM ON/OFF
|
||||||
|
|
||||||
|
Author: Adriano dos Santos Fernandes
|
||||||
|
|
||||||
|
When set to ON, terminator defined with SET TERM is changed to semicolon and a new logic
|
||||||
|
for TERM detection is used, where engine helps ISQL to detect valid usage of semicolons
|
||||||
|
inside statements.
|
||||||
|
|
||||||
|
At each semicolon (outside quotes or comments), ISQL prepares the query buffer with
|
||||||
|
engine using flag IStatement::PREPARE_REQUIRE_SEMICOLON.
|
||||||
|
|
||||||
|
If engine prepares the statement correctly, it's run and ISQL is put in new statement
|
||||||
|
mode.
|
||||||
|
|
||||||
|
If engine returns error isc_command_end_err2, then ISQL is put in statement
|
||||||
|
continuation mode and asks for another line, repeating the process.
|
||||||
|
|
||||||
|
If engine returns a different error, the error is shown and ISQL is put in new statement
|
||||||
|
mode.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- This option can also be activated with command line parameter -autot(erm)
|
||||||
|
- It can only be used with Firebird engine/server v6 or later
|
||||||
|
- SET TERM command automatically sets AUTOTERM to OFF
|
||||||
|
- SET AUTOTERM ON command automatically sets TERM to semicolon
|
||||||
|
- While AUTOTERM ON can be used in non-interactive scripts, at each semicolon,
|
||||||
|
statement may be tried to be compiled using the server/engine.
|
||||||
|
That may be slow for big scripts with PSQL statements spanning many lines.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
SQL> SET AUTOTERM ON;
|
||||||
|
|
||||||
|
SQL> execute block returns (o1 integer)
|
||||||
|
CON> as
|
||||||
|
CON> begin
|
||||||
|
CON> o1 = 1;
|
||||||
|
CON> suspend;
|
||||||
|
CON> end;
|
||||||
|
|
||||||
|
O1
|
||||||
|
============
|
||||||
|
1
|
||||||
|
|
||||||
|
SQL> select 1 from rdb$database;
|
||||||
|
|
||||||
|
CONSTANT
|
||||||
|
============
|
||||||
|
1
|
||||||
|
|
||||||
|
SQL> select 1
|
||||||
|
CON> from rdb$database;
|
||||||
|
|
||||||
|
CONSTANT
|
||||||
|
============
|
||||||
|
1
|
||||||
|
|
||||||
|
SQL> select 1
|
||||||
|
CON> from rdb$database
|
||||||
|
CON> where true;
|
||||||
|
|
||||||
|
CONSTANT
|
||||||
|
============
|
||||||
|
1
|
||||||
|
@ -41,12 +41,14 @@ using namespace Jrd;
|
|||||||
|
|
||||||
|
|
||||||
Parser::Parser(thread_db* tdbb, MemoryPool& pool, MemoryPool* aStatementPool, DsqlCompilerScratch* aScratch,
|
Parser::Parser(thread_db* tdbb, MemoryPool& pool, MemoryPool* aStatementPool, DsqlCompilerScratch* aScratch,
|
||||||
USHORT aClientDialect, USHORT aDbDialect, const TEXT* string, size_t length, SSHORT charSetId)
|
USHORT aClientDialect, USHORT aDbDialect, bool aRequireSemicolon,
|
||||||
|
const TEXT* string, size_t length, SSHORT charSetId)
|
||||||
: PermanentStorage(pool),
|
: PermanentStorage(pool),
|
||||||
statementPool(aStatementPool),
|
statementPool(aStatementPool),
|
||||||
scratch(aScratch),
|
scratch(aScratch),
|
||||||
client_dialect(aClientDialect),
|
client_dialect(aClientDialect),
|
||||||
db_dialect(aDbDialect),
|
db_dialect(aDbDialect),
|
||||||
|
requireSemicolon(aRequireSemicolon),
|
||||||
transformedString(pool),
|
transformedString(pool),
|
||||||
strMarks(pool),
|
strMarks(pool),
|
||||||
stmt_ambiguous(false)
|
stmt_ambiguous(false)
|
||||||
|
@ -132,7 +132,8 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
Parser(thread_db* tdbb, MemoryPool& pool, MemoryPool* aStatementPool, DsqlCompilerScratch* aScratch,
|
Parser(thread_db* tdbb, MemoryPool& pool, MemoryPool* aStatementPool, DsqlCompilerScratch* aScratch,
|
||||||
USHORT aClientDialect, USHORT aDbDialect, const TEXT* string, size_t length, SSHORT charSetId);
|
USHORT aClientDialect, USHORT aDbDialect, bool aRequireSemicolon,
|
||||||
|
const TEXT* string, size_t length, SSHORT charSetId);
|
||||||
~Parser();
|
~Parser();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -363,6 +364,7 @@ private:
|
|||||||
DsqlCompilerScratch* scratch;
|
DsqlCompilerScratch* scratch;
|
||||||
USHORT client_dialect;
|
USHORT client_dialect;
|
||||||
USHORT db_dialect;
|
USHORT db_dialect;
|
||||||
|
const bool requireSemicolon;
|
||||||
USHORT parser_version;
|
USHORT parser_version;
|
||||||
CharSet* charSet;
|
CharSet* charSet;
|
||||||
|
|
||||||
|
@ -84,9 +84,9 @@ using namespace Firebird;
|
|||||||
|
|
||||||
static ULONG get_request_info(thread_db*, DsqlRequest*, ULONG, UCHAR*);
|
static ULONG get_request_info(thread_db*, DsqlRequest*, ULONG, UCHAR*);
|
||||||
static dsql_dbb* init(Jrd::thread_db*, Jrd::Attachment*);
|
static dsql_dbb* init(Jrd::thread_db*, Jrd::Attachment*);
|
||||||
static DsqlRequest* prepareRequest(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, bool);
|
static DsqlRequest* prepareRequest(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, unsigned, bool);
|
||||||
static RefPtr<DsqlStatement> prepareStatement(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT,
|
static RefPtr<DsqlStatement> prepareStatement(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT,
|
||||||
bool, ntrace_result_t* traceResult);
|
unsigned, bool, ntrace_result_t* traceResult);
|
||||||
static UCHAR* put_item(UCHAR, const USHORT, const UCHAR*, UCHAR*, const UCHAR* const);
|
static UCHAR* put_item(UCHAR, const USHORT, const UCHAR*, UCHAR*, const UCHAR* const);
|
||||||
static void sql_info(thread_db*, DsqlRequest*, ULONG, const UCHAR*, ULONG, UCHAR*);
|
static void sql_info(thread_db*, DsqlRequest*, ULONG, const UCHAR*, ULONG, UCHAR*);
|
||||||
static UCHAR* var_info(const dsql_msg*, const UCHAR*, const UCHAR* const, UCHAR*,
|
static UCHAR* var_info(const dsql_msg*, const UCHAR*, const UCHAR* const, UCHAR*,
|
||||||
@ -261,7 +261,7 @@ DsqlRequest* DSQL_prepare(thread_db* tdbb,
|
|||||||
// Allocate a new request block and then prepare the request.
|
// Allocate a new request block and then prepare the request.
|
||||||
|
|
||||||
dsqlRequest = prepareRequest(tdbb, database, transaction, length, string, dialect,
|
dsqlRequest = prepareRequest(tdbb, database, transaction, length, string, dialect,
|
||||||
isInternalRequest);
|
prepareFlags, isInternalRequest);
|
||||||
|
|
||||||
// Can not prepare a CREATE DATABASE/SCHEMA statement
|
// Can not prepare a CREATE DATABASE/SCHEMA statement
|
||||||
|
|
||||||
@ -336,7 +336,7 @@ void DSQL_execute_immediate(thread_db* tdbb, Jrd::Attachment* attachment, jrd_tr
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
dsqlRequest = prepareRequest(tdbb, database, *tra_handle, length, string, dialect,
|
dsqlRequest = prepareRequest(tdbb, database, *tra_handle, length, string, dialect,
|
||||||
isInternalRequest);
|
0, isInternalRequest);
|
||||||
|
|
||||||
const auto dsqlStatement = dsqlRequest->getDsqlStatement();
|
const auto dsqlStatement = dsqlRequest->getDsqlStatement();
|
||||||
|
|
||||||
@ -443,7 +443,7 @@ static dsql_dbb* init(thread_db* tdbb, Jrd::Attachment* attachment)
|
|||||||
// Prepare a request for execution.
|
// Prepare a request for execution.
|
||||||
// Note: caller is responsible for pool handling.
|
// Note: caller is responsible for pool handling.
|
||||||
static DsqlRequest* prepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction,
|
static DsqlRequest* prepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction,
|
||||||
ULONG textLength, const TEXT* text, USHORT clientDialect, bool isInternalRequest)
|
ULONG textLength, const TEXT* text, USHORT clientDialect, unsigned prepareFlags, bool isInternalRequest)
|
||||||
{
|
{
|
||||||
TraceDSQLPrepare trace(database->dbb_attachment, transaction, textLength, text, isInternalRequest);
|
TraceDSQLPrepare trace(database->dbb_attachment, transaction, textLength, text, isInternalRequest);
|
||||||
|
|
||||||
@ -451,7 +451,7 @@ static DsqlRequest* prepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_tra*
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto statement = prepareStatement(tdbb, database, transaction, textLength, text,
|
auto statement = prepareStatement(tdbb, database, transaction, textLength, text,
|
||||||
clientDialect, isInternalRequest, &traceResult);
|
clientDialect, prepareFlags, isInternalRequest, &traceResult);
|
||||||
|
|
||||||
auto dsqlRequest = statement->createRequest(tdbb, database);
|
auto dsqlRequest = statement->createRequest(tdbb, database);
|
||||||
|
|
||||||
@ -472,7 +472,8 @@ static DsqlRequest* prepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_tra*
|
|||||||
// Prepare a statement for execution.
|
// Prepare a statement for execution.
|
||||||
// Note: caller is responsible for pool handling.
|
// Note: caller is responsible for pool handling.
|
||||||
static RefPtr<DsqlStatement> prepareStatement(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction,
|
static RefPtr<DsqlStatement> prepareStatement(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction,
|
||||||
ULONG textLength, const TEXT* text, USHORT clientDialect, bool isInternalRequest, ntrace_result_t* traceResult)
|
ULONG textLength, const TEXT* text, USHORT clientDialect, unsigned prepareFlags, bool isInternalRequest,
|
||||||
|
ntrace_result_t* traceResult)
|
||||||
{
|
{
|
||||||
Database* const dbb = tdbb->getDatabase();
|
Database* const dbb = tdbb->getDatabase();
|
||||||
|
|
||||||
@ -493,15 +494,18 @@ static RefPtr<DsqlStatement> prepareStatement(thread_db* tdbb, dsql_dbb* databas
|
|||||||
Arg::Gds(isc_command_end_err2) << Arg::Num(1) << Arg::Num(1));
|
Arg::Gds(isc_command_end_err2) << Arg::Num(1) << Arg::Num(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get rid of the trailing ";" if there is one.
|
if (!(prepareFlags & IStatement::PREPARE_REQUIRE_SEMICOLON))
|
||||||
|
|
||||||
for (const TEXT* p = text + textLength; p-- > text;)
|
|
||||||
{
|
{
|
||||||
if (*p != ' ')
|
// Get rid of the trailing ";" if there is one.
|
||||||
|
|
||||||
|
for (const TEXT* p = text + textLength; p-- > text;)
|
||||||
{
|
{
|
||||||
if (*p == ';')
|
if (*p != ' ')
|
||||||
textLength = p - text;
|
{
|
||||||
break;
|
if (*p == ';')
|
||||||
|
textLength = p - text;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,7 +560,9 @@ static RefPtr<DsqlStatement> prepareStatement(thread_db* tdbb, dsql_dbb* databas
|
|||||||
scratch->flags |= DsqlCompilerScratch::FLAG_INTERNAL_REQUEST;
|
scratch->flags |= DsqlCompilerScratch::FLAG_INTERNAL_REQUEST;
|
||||||
|
|
||||||
Parser parser(tdbb, *scratchPool, statementPool, scratch, clientDialect,
|
Parser parser(tdbb, *scratchPool, statementPool, scratch, clientDialect,
|
||||||
dbDialect, text, textLength, charSetId);
|
dbDialect,
|
||||||
|
(prepareFlags & IStatement::PREPARE_REQUIRE_SEMICOLON),
|
||||||
|
text, textLength, charSetId);
|
||||||
|
|
||||||
// Parse the SQL statement. If it croaks, return
|
// Parse the SQL statement. If it croaks, return
|
||||||
dsqlStatement = parser.parse();
|
dsqlStatement = parser.parse();
|
||||||
|
@ -868,8 +868,15 @@ using namespace Firebird;
|
|||||||
// list of possible statements
|
// list of possible statements
|
||||||
|
|
||||||
top
|
top
|
||||||
: statement { parsedStatement = $1; }
|
: statement
|
||||||
| statement ';' { parsedStatement = $1; }
|
{
|
||||||
|
if (requireSemicolon)
|
||||||
|
yyerrorIncompleteCmd(YYPOSNARG(1));
|
||||||
|
|
||||||
|
parsedStatement = $1;
|
||||||
|
}
|
||||||
|
| statement ';'
|
||||||
|
{ parsedStatement = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
%type <dsqlStatement> statement
|
%type <dsqlStatement> statement
|
||||||
|
@ -474,6 +474,7 @@ interface Statement : ReferenceCounted
|
|||||||
const uint PREPARE_PREFETCH_DETAILED_PLAN = 0x10;
|
const uint PREPARE_PREFETCH_DETAILED_PLAN = 0x10;
|
||||||
const uint PREPARE_PREFETCH_AFFECTED_RECORDS = 0x20; // not used yet
|
const uint PREPARE_PREFETCH_AFFECTED_RECORDS = 0x20; // not used yet
|
||||||
const uint PREPARE_PREFETCH_FLAGS = 0x40;
|
const uint PREPARE_PREFETCH_FLAGS = 0x40;
|
||||||
|
const uint PREPARE_REQUIRE_SEMICOLON = 0x80;
|
||||||
const uint PREPARE_PREFETCH_METADATA =
|
const uint PREPARE_PREFETCH_METADATA =
|
||||||
PREPARE_PREFETCH_TYPE | PREPARE_PREFETCH_FLAGS |
|
PREPARE_PREFETCH_TYPE | PREPARE_PREFETCH_FLAGS |
|
||||||
PREPARE_PREFETCH_INPUT_PARAMETERS | PREPARE_PREFETCH_OUTPUT_PARAMETERS;
|
PREPARE_PREFETCH_INPUT_PARAMETERS | PREPARE_PREFETCH_OUTPUT_PARAMETERS;
|
||||||
|
@ -1914,6 +1914,7 @@ namespace Firebird
|
|||||||
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_DETAILED_PLAN = 0x10;
|
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_DETAILED_PLAN = 0x10;
|
||||||
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_AFFECTED_RECORDS = 0x20;
|
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_AFFECTED_RECORDS = 0x20;
|
||||||
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_FLAGS = 0x40;
|
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_FLAGS = 0x40;
|
||||||
|
static CLOOP_CONSTEXPR unsigned PREPARE_REQUIRE_SEMICOLON = 0x80;
|
||||||
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_METADATA = IStatement::PREPARE_PREFETCH_TYPE | IStatement::PREPARE_PREFETCH_FLAGS | IStatement::PREPARE_PREFETCH_INPUT_PARAMETERS | IStatement::PREPARE_PREFETCH_OUTPUT_PARAMETERS;
|
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_METADATA = IStatement::PREPARE_PREFETCH_TYPE | IStatement::PREPARE_PREFETCH_FLAGS | IStatement::PREPARE_PREFETCH_INPUT_PARAMETERS | IStatement::PREPARE_PREFETCH_OUTPUT_PARAMETERS;
|
||||||
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_ALL = IStatement::PREPARE_PREFETCH_METADATA | IStatement::PREPARE_PREFETCH_LEGACY_PLAN | IStatement::PREPARE_PREFETCH_DETAILED_PLAN | IStatement::PREPARE_PREFETCH_AFFECTED_RECORDS;
|
static CLOOP_CONSTEXPR unsigned PREPARE_PREFETCH_ALL = IStatement::PREPARE_PREFETCH_METADATA | IStatement::PREPARE_PREFETCH_LEGACY_PLAN | IStatement::PREPARE_PREFETCH_DETAILED_PLAN | IStatement::PREPARE_PREFETCH_AFFECTED_RECORDS;
|
||||||
static CLOOP_CONSTEXPR unsigned FLAG_HAS_CURSOR = 0x1;
|
static CLOOP_CONSTEXPR unsigned FLAG_HAS_CURSOR = 0x1;
|
||||||
|
@ -202,3 +202,5 @@ FB_IMPL_MSG_SYMBOL(ISQL, 202, NO_PUBLICATIONS, "There is no publications in this
|
|||||||
FB_IMPL_MSG_SYMBOL(ISQL, 203, MSG_PUBLICATIONS, "Publications:")
|
FB_IMPL_MSG_SYMBOL(ISQL, 203, MSG_PUBLICATIONS, "Publications:")
|
||||||
FB_IMPL_MSG_SYMBOL(ISQL, 204, MSG_PROCEDURES, "Procedures:")
|
FB_IMPL_MSG_SYMBOL(ISQL, 204, MSG_PROCEDURES, "Procedures:")
|
||||||
FB_IMPL_MSG_SYMBOL(ISQL, 205, HLP_EXPLAIN, "EXPLAIN -- explain a query access plan")
|
FB_IMPL_MSG_SYMBOL(ISQL, 205, HLP_EXPLAIN, "EXPLAIN -- explain a query access plan")
|
||||||
|
FB_IMPL_MSG_SYMBOL(ISQL, 206, USAGE_AUTOTERM, " -autot(erm) use auto statement terminator (set autoterm on)")
|
||||||
|
FB_IMPL_MSG_SYMBOL(ISQL, 207, AUTOTERM_NOT_SUPPORTED, "SET AUTOTERM ON is not supported in engine/server and has been disabled")
|
||||||
|
@ -1533,6 +1533,7 @@ type
|
|||||||
const PREPARE_PREFETCH_DETAILED_PLAN = Cardinal($10);
|
const PREPARE_PREFETCH_DETAILED_PLAN = Cardinal($10);
|
||||||
const PREPARE_PREFETCH_AFFECTED_RECORDS = Cardinal($20);
|
const PREPARE_PREFETCH_AFFECTED_RECORDS = Cardinal($20);
|
||||||
const PREPARE_PREFETCH_FLAGS = Cardinal($40);
|
const PREPARE_PREFETCH_FLAGS = Cardinal($40);
|
||||||
|
const PREPARE_REQUIRE_SEMICOLON = Cardinal($80);
|
||||||
const PREPARE_PREFETCH_METADATA = Cardinal(IStatement.PREPARE_PREFETCH_TYPE or IStatement.PREPARE_PREFETCH_FLAGS or IStatement.PREPARE_PREFETCH_INPUT_PARAMETERS or IStatement.PREPARE_PREFETCH_OUTPUT_PARAMETERS);
|
const PREPARE_PREFETCH_METADATA = Cardinal(IStatement.PREPARE_PREFETCH_TYPE or IStatement.PREPARE_PREFETCH_FLAGS or IStatement.PREPARE_PREFETCH_INPUT_PARAMETERS or IStatement.PREPARE_PREFETCH_OUTPUT_PARAMETERS);
|
||||||
const PREPARE_PREFETCH_ALL = Cardinal(IStatement.PREPARE_PREFETCH_METADATA or IStatement.PREPARE_PREFETCH_LEGACY_PLAN or IStatement.PREPARE_PREFETCH_DETAILED_PLAN or IStatement.PREPARE_PREFETCH_AFFECTED_RECORDS);
|
const PREPARE_PREFETCH_ALL = Cardinal(IStatement.PREPARE_PREFETCH_METADATA or IStatement.PREPARE_PREFETCH_LEGACY_PLAN or IStatement.PREPARE_PREFETCH_DETAILED_PLAN or IStatement.PREPARE_PREFETCH_AFFECTED_RECORDS);
|
||||||
const FLAG_HAS_CURSOR = Cardinal($1);
|
const FLAG_HAS_CURSOR = Cardinal($1);
|
||||||
|
386
src/isql/FrontendLexer.cpp
Normal file
386
src/isql/FrontendLexer.cpp
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
/*
|
||||||
|
* The contents of this file are subject to the Initial
|
||||||
|
* Developer's 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.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed AS IS,
|
||||||
|
* 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 Adriano dos Santos Fernandes
|
||||||
|
* for the Firebird Open Source RDBMS project.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Adriano dos Santos Fernandes <adrianosf at gmail.com>
|
||||||
|
* and all contributors signed below.
|
||||||
|
*
|
||||||
|
* All Rights Reserved.
|
||||||
|
* Contributor(s): ______________________________________.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "firebird.h"
|
||||||
|
#include "../isql/FrontendLexer.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
|
||||||
|
static std::string trim(std::string_view str);
|
||||||
|
|
||||||
|
|
||||||
|
static std::string trim(std::string_view str)
|
||||||
|
{
|
||||||
|
auto finish = str.end();
|
||||||
|
auto start = str.begin();
|
||||||
|
|
||||||
|
while (start != finish && isspace(*start))
|
||||||
|
++start;
|
||||||
|
|
||||||
|
--finish;
|
||||||
|
|
||||||
|
while (finish > start && isspace(*finish))
|
||||||
|
--finish;
|
||||||
|
|
||||||
|
return std::string(start, finish + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string FrontendLexer::stripComments(std::string_view statement)
|
||||||
|
{
|
||||||
|
FrontendLexer lexer(statement);
|
||||||
|
std::string processedStatement;
|
||||||
|
|
||||||
|
while (lexer.pos < lexer.end)
|
||||||
|
{
|
||||||
|
auto oldPos = lexer.pos;
|
||||||
|
|
||||||
|
lexer.skipSpacesAndComments();
|
||||||
|
|
||||||
|
if (lexer.pos > oldPos)
|
||||||
|
processedStatement += ' ';
|
||||||
|
|
||||||
|
oldPos = lexer.pos;
|
||||||
|
|
||||||
|
if (!lexer.getStringToken().has_value() && lexer.pos < lexer.end)
|
||||||
|
++lexer.pos;
|
||||||
|
|
||||||
|
processedStatement += std::string(oldPos, lexer.pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim(processedStatement);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrontendLexer::isBufferEmpty() const
|
||||||
|
{
|
||||||
|
return trim(std::string(deletePos, end)).empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrontendLexer::appendBuffer(std::string_view newBuffer)
|
||||||
|
{
|
||||||
|
const auto posIndex = pos - buffer.begin();
|
||||||
|
const auto deletePosIndex = deletePos - buffer.begin();
|
||||||
|
buffer.append(newBuffer);
|
||||||
|
pos = buffer.begin() + posIndex;
|
||||||
|
end = buffer.end();
|
||||||
|
deletePos = buffer.begin() + deletePosIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrontendLexer::reset()
|
||||||
|
{
|
||||||
|
buffer.clear();
|
||||||
|
pos = buffer.begin();
|
||||||
|
end = buffer.end();
|
||||||
|
deletePos = buffer.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::variant<FrontendLexer::SingleStatement, FrontendLexer::IncompleteTokenError> FrontendLexer::getSingleStatement(
|
||||||
|
std::string_view term)
|
||||||
|
{
|
||||||
|
const auto posIndex = pos - deletePos;
|
||||||
|
buffer.erase(buffer.begin(), deletePos);
|
||||||
|
pos = buffer.begin() + posIndex;
|
||||||
|
end = buffer.end();
|
||||||
|
deletePos = buffer.begin();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (pos < end)
|
||||||
|
{
|
||||||
|
skipSpacesAndComments();
|
||||||
|
|
||||||
|
const auto savePos = pos;
|
||||||
|
|
||||||
|
if (pos + 1 < end && *pos == '?')
|
||||||
|
{
|
||||||
|
if (*++pos == '\r')
|
||||||
|
++pos;
|
||||||
|
|
||||||
|
if (pos < end && *pos == '\n')
|
||||||
|
{
|
||||||
|
deletePos = ++pos;
|
||||||
|
const auto statement = trim(std::string(buffer.cbegin(), pos));
|
||||||
|
return SingleStatement{statement, statement};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = savePos;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (pos < end)
|
||||||
|
{
|
||||||
|
if (end - pos >= term.length() && std::equal(term.begin(), term.end(), pos))
|
||||||
|
{
|
||||||
|
const auto initialStatement = std::string(buffer.cbegin(), pos);
|
||||||
|
pos += term.length();
|
||||||
|
const auto trailingPos = pos;
|
||||||
|
skipSpacesAndComments();
|
||||||
|
deletePos = pos;
|
||||||
|
|
||||||
|
const auto statement1 = initialStatement + std::string(trailingPos, pos);
|
||||||
|
const auto statement2 = initialStatement + ";" + std::string(trailingPos, pos);
|
||||||
|
|
||||||
|
return SingleStatement{trim(statement1), trim(statement2)};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getStringToken().has_value() && pos < end)
|
||||||
|
++pos;
|
||||||
|
|
||||||
|
skipSpacesAndComments();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const IncompleteTokenError& error)
|
||||||
|
{
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IncompleteTokenError{false};
|
||||||
|
}
|
||||||
|
|
||||||
|
FrontendLexer::Token FrontendLexer::getToken()
|
||||||
|
{
|
||||||
|
skipSpacesAndComments();
|
||||||
|
|
||||||
|
Token token;
|
||||||
|
|
||||||
|
if (pos >= end)
|
||||||
|
{
|
||||||
|
token.type = Token::TYPE_EOF;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto optStringToken = getStringToken(); optStringToken.has_value())
|
||||||
|
return optStringToken.value();
|
||||||
|
|
||||||
|
const auto start = pos;
|
||||||
|
|
||||||
|
switch (toupper(*pos))
|
||||||
|
{
|
||||||
|
case '(':
|
||||||
|
token.type = Token::TYPE_OPEN_PAREN;
|
||||||
|
token.processedText = *pos++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ')':
|
||||||
|
token.type = Token::TYPE_CLOSE_PAREN;
|
||||||
|
token.processedText = *pos++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
token.type = Token::TYPE_COMMA;
|
||||||
|
token.processedText = *pos++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ';':
|
||||||
|
token.type = Token::TYPE_OTHER;
|
||||||
|
token.processedText = *pos++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
while (pos != end && !isspace(*pos))
|
||||||
|
++pos;
|
||||||
|
|
||||||
|
token.processedText = std::string(start, pos);
|
||||||
|
std::transform(token.processedText.begin(), token.processedText.end(),
|
||||||
|
token.processedText.begin(), toupper);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.rawText = std::string(start, pos);
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<FrontendLexer::Token> FrontendLexer::getStringToken()
|
||||||
|
{
|
||||||
|
Token token;
|
||||||
|
|
||||||
|
if (pos >= end)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
const auto start = pos;
|
||||||
|
|
||||||
|
switch (toupper(*pos))
|
||||||
|
{
|
||||||
|
case '\'':
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
const auto quote = *pos++;
|
||||||
|
|
||||||
|
while (pos != end)
|
||||||
|
{
|
||||||
|
if (*pos == quote)
|
||||||
|
{
|
||||||
|
if ((pos + 1) < end && *(pos + 1) == quote)
|
||||||
|
++pos;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.processedText += *pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos == end)
|
||||||
|
{
|
||||||
|
pos = start;
|
||||||
|
throw IncompleteTokenError{false};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++pos;
|
||||||
|
token.type = quote == '\'' ? Token::TYPE_STRING : Token::TYPE_META_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'Q':
|
||||||
|
if (pos + 1 != end && pos[1] == '\'')
|
||||||
|
{
|
||||||
|
if (pos + 4 < end)
|
||||||
|
{
|
||||||
|
char endChar;
|
||||||
|
|
||||||
|
switch (pos[2])
|
||||||
|
{
|
||||||
|
case '{':
|
||||||
|
endChar = '}';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '[':
|
||||||
|
endChar = ']';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '(':
|
||||||
|
endChar = ')';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '<':
|
||||||
|
endChar = '>';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
endChar = pos[2];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += 3;
|
||||||
|
|
||||||
|
while (pos + 1 < end)
|
||||||
|
{
|
||||||
|
if (*pos == endChar && pos[1] == '\'')
|
||||||
|
{
|
||||||
|
pos += 2;
|
||||||
|
token.type = Token::TYPE_STRING;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.processedText += *pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.type != Token::TYPE_STRING)
|
||||||
|
{
|
||||||
|
pos = start;
|
||||||
|
throw IncompleteTokenError{false};
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
|
||||||
|
default:
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.rawText = std::string(start, pos);
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrontendLexer::skipSpacesAndComments()
|
||||||
|
{
|
||||||
|
while (pos != end && (isspace(*pos) || *pos == '-' || *pos == '/'))
|
||||||
|
{
|
||||||
|
while (pos != end && isspace(*pos))
|
||||||
|
++pos;
|
||||||
|
|
||||||
|
if (pos == end)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (*pos == '-')
|
||||||
|
{
|
||||||
|
if (pos + 1 != end && pos[1] == '-')
|
||||||
|
{
|
||||||
|
pos += 2;
|
||||||
|
|
||||||
|
while (pos != end)
|
||||||
|
{
|
||||||
|
const auto c = *pos++;
|
||||||
|
|
||||||
|
if (c == '\r')
|
||||||
|
{
|
||||||
|
if (pos != end && *pos == '\n')
|
||||||
|
++pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (c == '\n')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (*pos == '/')
|
||||||
|
{
|
||||||
|
const auto start = pos;
|
||||||
|
|
||||||
|
if (pos + 1 != end && pos[1] == '*')
|
||||||
|
{
|
||||||
|
bool finished = false;
|
||||||
|
pos += 2;
|
||||||
|
|
||||||
|
while (pos != end)
|
||||||
|
{
|
||||||
|
const auto c = *pos++;
|
||||||
|
|
||||||
|
if (c == '*' && pos != end && *pos == '/')
|
||||||
|
{
|
||||||
|
++pos;
|
||||||
|
finished = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!finished)
|
||||||
|
{
|
||||||
|
pos = start;
|
||||||
|
throw IncompleteTokenError{true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
113
src/isql/FrontendLexer.h
Normal file
113
src/isql/FrontendLexer.h
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* The contents of this file are subject to the Initial
|
||||||
|
* Developer's 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.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed AS IS,
|
||||||
|
* 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 Adriano dos Santos Fernandes
|
||||||
|
* for the Firebird Open Source RDBMS project.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Adriano dos Santos Fernandes <adrianosf at gmail.com>
|
||||||
|
* and all contributors signed below.
|
||||||
|
*
|
||||||
|
* All Rights Reserved.
|
||||||
|
* Contributor(s): ______________________________________.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FB_ISQL_FRONTEND_LEXER_H
|
||||||
|
#define FB_ISQL_FRONTEND_LEXER_H
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
class FrontendLexer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Token
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
TYPE_EOF,
|
||||||
|
TYPE_STRING,
|
||||||
|
TYPE_META_STRING,
|
||||||
|
TYPE_OPEN_PAREN,
|
||||||
|
TYPE_CLOSE_PAREN,
|
||||||
|
TYPE_COMMA,
|
||||||
|
TYPE_OTHER
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type = TYPE_OTHER;
|
||||||
|
std::string rawText;
|
||||||
|
std::string processedText;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SingleStatement
|
||||||
|
{
|
||||||
|
std::string withoutSemicolon;
|
||||||
|
std::string withSemicolon;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IncompleteTokenError
|
||||||
|
{
|
||||||
|
bool insideComment;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrontendLexer(std::string_view aBuffer = {})
|
||||||
|
: buffer(aBuffer),
|
||||||
|
pos(buffer.begin()),
|
||||||
|
end(buffer.end()),
|
||||||
|
deletePos(buffer.begin())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FrontendLexer(const FrontendLexer&) = delete;
|
||||||
|
FrontendLexer& operator=(const FrontendLexer&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static std::string stripComments(std::string_view statement);
|
||||||
|
|
||||||
|
public:
|
||||||
|
auto getBuffer() const
|
||||||
|
{
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getPos() const
|
||||||
|
{
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rewind()
|
||||||
|
{
|
||||||
|
deletePos = buffer.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isBufferEmpty() const;
|
||||||
|
|
||||||
|
void appendBuffer(std::string_view newBuffer);
|
||||||
|
void reset();
|
||||||
|
std::variant<SingleStatement, FrontendLexer::IncompleteTokenError> getSingleStatement(std::string_view term);
|
||||||
|
Token getToken();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<Token> getStringToken();
|
||||||
|
void skipSpacesAndComments();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string buffer;
|
||||||
|
std::string::const_iterator pos;
|
||||||
|
std::string::const_iterator end;
|
||||||
|
std::string::const_iterator deletePos;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FB_ISQL_FRONTEND_LEXER_H
|
@ -251,7 +251,8 @@ void InputDevices::saveCommand(const char* statement, const char* term)
|
|||||||
if (f)
|
if (f)
|
||||||
{
|
{
|
||||||
fputs(statement, f);
|
fputs(statement, f);
|
||||||
fputs(term, f);
|
if (*term)
|
||||||
|
fputs(term, f);
|
||||||
// Add newline to make the file more readable.
|
// Add newline to make the file more readable.
|
||||||
fputc('\n', f);
|
fputc('\n', f);
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include "../isql/FrontendLexer.h"
|
||||||
#include "../common/utils_proto.h"
|
#include "../common/utils_proto.h"
|
||||||
#include "../common/classes/array.h"
|
#include "../common/classes/array.h"
|
||||||
#include "../common/classes/init.h"
|
#include "../common/classes/init.h"
|
||||||
@ -123,10 +124,7 @@ enum literal_string_type
|
|||||||
#include "../common/classes/MsgPrint.h"
|
#include "../common/classes/MsgPrint.h"
|
||||||
#include "../common/classes/array.h"
|
#include "../common/classes/array.h"
|
||||||
|
|
||||||
using Firebird::string;
|
using namespace Firebird;
|
||||||
using Firebird::PathName;
|
|
||||||
using Firebird::TempFile;
|
|
||||||
using Firebird::TimeZoneUtil;
|
|
||||||
using MsgFormat::SafeArg;
|
using MsgFormat::SafeArg;
|
||||||
|
|
||||||
#include "../isql/ColList.h"
|
#include "../isql/ColList.h"
|
||||||
@ -304,11 +302,6 @@ static inline int fb_isspace(const char c)
|
|||||||
return isspace((int)(UCHAR)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)
|
static inline int fb_isdigit(const char c)
|
||||||
{
|
{
|
||||||
return isdigit((int)(UCHAR)c);
|
return isdigit((int)(UCHAR)c);
|
||||||
@ -466,6 +459,7 @@ static processing_state add_row(TEXT*);
|
|||||||
static processing_state blobedit(const TEXT*, const TEXT* const*);
|
static processing_state blobedit(const TEXT*, const TEXT* const*);
|
||||||
static processing_state bulk_insert_hack(const char* command);
|
static processing_state bulk_insert_hack(const char* command);
|
||||||
static bool bulk_insert_retriever(const char* prompt);
|
static bool bulk_insert_retriever(const char* prompt);
|
||||||
|
static void check_autoterm();
|
||||||
static bool check_date(const tm& times);
|
static bool check_date(const tm& times);
|
||||||
static bool check_time(const tm& times);
|
static bool check_time(const tm& times);
|
||||||
static bool check_timestamp(const tm& times, const int msec);
|
static bool check_timestamp(const tm& times, const int msec);
|
||||||
@ -488,7 +482,6 @@ static void frontend_load_parms(const TEXT* p, TEXT* parms[], TEXT* lparms[],
|
|||||||
static processing_state do_set_command(const TEXT*, bool*);
|
static processing_state do_set_command(const TEXT*, bool*);
|
||||||
static processing_state get_dialect(const char* const dialect_str,
|
static processing_state get_dialect(const char* const dialect_str,
|
||||||
char* const bad_dialect_buf, bool& bad_dialect);
|
char* const bad_dialect_buf, bool& bad_dialect);
|
||||||
static processing_state get_statement(string&, const TEXT*);
|
|
||||||
static bool get_numeric(const UCHAR*, USHORT, SSHORT*, SINT64*);
|
static bool get_numeric(const UCHAR*, USHORT, SSHORT*, SINT64*);
|
||||||
static void print_set(const char* str, bool v);
|
static void print_set(const char* str, bool v);
|
||||||
static processing_state print_sets();
|
static processing_state print_sets();
|
||||||
@ -515,7 +508,7 @@ static void process_plan();
|
|||||||
static void process_exec_path();
|
static void process_exec_path();
|
||||||
static SINT64 process_record_count(const unsigned statement_type);
|
static SINT64 process_record_count(const unsigned statement_type);
|
||||||
static unsigned process_message_display(Firebird::IMessageMetadata* msg, unsigned pad[]);
|
static unsigned process_message_display(Firebird::IMessageMetadata* msg, unsigned pad[]);
|
||||||
static processing_state process_statement(const TEXT*);
|
static processing_state process_statement(const std::string&);
|
||||||
#ifdef WIN_NT
|
#ifdef WIN_NT
|
||||||
static BOOL CALLBACK query_abort(DWORD);
|
static BOOL CALLBACK query_abort(DWORD);
|
||||||
#else
|
#else
|
||||||
@ -581,6 +574,7 @@ public:
|
|||||||
Plan = false;
|
Plan = false;
|
||||||
Planonly = false;
|
Planonly = false;
|
||||||
ExplainPlan = false;
|
ExplainPlan = false;
|
||||||
|
AutoTerm = false;
|
||||||
Heading = true;
|
Heading = true;
|
||||||
BailOnError = false;
|
BailOnError = false;
|
||||||
StmtTimeout = 0;
|
StmtTimeout = 0;
|
||||||
@ -607,6 +601,7 @@ public:
|
|||||||
bool Plan;
|
bool Plan;
|
||||||
bool Planonly;
|
bool Planonly;
|
||||||
bool ExplainPlan;
|
bool ExplainPlan;
|
||||||
|
bool AutoTerm;
|
||||||
bool Heading;
|
bool Heading;
|
||||||
bool BailOnError;
|
bool BailOnError;
|
||||||
unsigned int StmtTimeout;
|
unsigned int StmtTimeout;
|
||||||
@ -625,7 +620,7 @@ static FILE* Help;
|
|||||||
|
|
||||||
static const TEXT* const sql_prompt = "SQL> ";
|
static const TEXT* const sql_prompt = "SQL> ";
|
||||||
|
|
||||||
// Keep in sync with the chars that have their own "case" in get_statement(...).
|
// Keep in sync with the chars that have their own "case" in the frontend lexer.
|
||||||
static const char FORBIDDEN_TERM_CHARS[] = { '\n', '-', '*', '/', SINGLE_QUOTE, DBL_QUOTE };
|
static const char FORBIDDEN_TERM_CHARS[] = { '\n', '-', '*', '/', SINGLE_QUOTE, DBL_QUOTE };
|
||||||
static const char FORBIDDEN_TERM_CHARS_DISPLAY[] = "<ENTER>, -, *, /, SINGLE_QUOTE, DOUBLE_QUOTE";
|
static const char FORBIDDEN_TERM_CHARS_DISPLAY[] = "<ENTER>, -, *, /, SINGLE_QUOTE, DOUBLE_QUOTE";
|
||||||
|
|
||||||
@ -721,6 +716,32 @@ private:
|
|||||||
static Firebird::GlobalPtr<PerTableStats> perTableStats;
|
static Firebird::GlobalPtr<PerTableStats> perTableStats;
|
||||||
|
|
||||||
|
|
||||||
|
class StatementGetter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StatementGetter()
|
||||||
|
{
|
||||||
|
// Lookup the continuation prompt once
|
||||||
|
if (!*conPrompt)
|
||||||
|
IUTILS_msg_get(CON_PROMPT, conPrompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::pair<FrontendLexer::SingleStatement, processing_state> getStatement();
|
||||||
|
|
||||||
|
void rewind()
|
||||||
|
{
|
||||||
|
lexer.rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static TEXT conPrompt[MSG_LENGTH];
|
||||||
|
FrontendLexer lexer;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEXT StatementGetter::conPrompt[MSG_LENGTH] = "";
|
||||||
|
|
||||||
|
|
||||||
static UCHAR predefined_blob_subtype_bpb[] =
|
static UCHAR predefined_blob_subtype_bpb[] =
|
||||||
{
|
{
|
||||||
isc_bpb_version1,
|
isc_bpb_version1,
|
||||||
@ -3984,6 +4005,51 @@ static bool bulk_insert_retriever(const char* prompt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check if SET AUTOTERM is allowed. If not, disable it.
|
||||||
|
static void check_autoterm()
|
||||||
|
{
|
||||||
|
if (!DB || !setValues.AutoTerm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static const UCHAR protocolInfo[] =
|
||||||
|
{
|
||||||
|
fb_info_protocol_version,
|
||||||
|
isc_info_end
|
||||||
|
};
|
||||||
|
|
||||||
|
UCHAR buffer[BUFFER_LENGTH128];
|
||||||
|
|
||||||
|
DB->getInfo(fbStatus, sizeof(protocolInfo), protocolInfo, sizeof(buffer), buffer);
|
||||||
|
if (ISQL_errmsg(fbStatus))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SLONG protocolVersion = -1;
|
||||||
|
|
||||||
|
for (ClumpletReader p(ClumpletReader::InfoResponse, buffer, sizeof(buffer)); !p.isEof(); p.moveNext())
|
||||||
|
{
|
||||||
|
switch (p.getClumpTag())
|
||||||
|
{
|
||||||
|
case isc_info_end:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case fb_info_protocol_version:
|
||||||
|
protocolVersion = p.getInt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(protocolVersion == 0 || protocolVersion >= 19) && // PROTOCOL_VERSION19
|
||||||
|
ENCODE_ODS(isqlGlob.major_ods, isqlGlob.minor_ods) >= ODS_13_2)
|
||||||
|
{
|
||||||
|
setValues.AutoTerm = false;
|
||||||
|
|
||||||
|
TEXT errbuf[MSG_LENGTH];
|
||||||
|
IUTILS_msg_get(AUTOTERM_NOT_SUPPORTED, errbuf);
|
||||||
|
STDERROUT(errbuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// *******************
|
// *******************
|
||||||
// c h e c k _ d a t e
|
// c h e c k _ d a t e
|
||||||
// *******************
|
// *******************
|
||||||
@ -4432,13 +4498,11 @@ static void do_isql()
|
|||||||
// Read statements and process them from Ifp until the ret
|
// Read statements and process them from Ifp until the ret
|
||||||
// code tells us we are done
|
// code tells us we are done
|
||||||
|
|
||||||
string stmt;
|
StatementGetter statementGetter;
|
||||||
processing_state ret;
|
|
||||||
|
|
||||||
bool done = false;
|
bool done = false;
|
||||||
|
|
||||||
while (!done)
|
while (!done)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (Abort_flag)
|
if (Abort_flag)
|
||||||
{
|
{
|
||||||
if (D__trans)
|
if (D__trans)
|
||||||
@ -4498,7 +4562,10 @@ static void do_isql()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = get_statement(stmt, sql_prompt);
|
auto [statement, ret] = statementGetter.getStatement();
|
||||||
|
|
||||||
|
if (!statement.withoutSemicolon.empty())
|
||||||
|
ret = frontend(FrontendLexer::stripComments(statement.withoutSemicolon).c_str());
|
||||||
|
|
||||||
// If there is no database yet, remind us of the need to connect
|
// If there is no database yet, remind us of the need to connect
|
||||||
|
|
||||||
@ -4511,6 +4578,7 @@ static void do_isql()
|
|||||||
IUTILS_msg_get(NO_DB, errbuf);
|
IUTILS_msg_get(NO_DB, errbuf);
|
||||||
STDERROUT(errbuf);
|
STDERROUT(errbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Interactive && setValues.BailOnError)
|
if (!Interactive && setValues.BailOnError)
|
||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
else
|
else
|
||||||
@ -4520,16 +4588,29 @@ static void do_isql()
|
|||||||
switch (ret)
|
switch (ret)
|
||||||
{
|
{
|
||||||
case CONT:
|
case CONT:
|
||||||
if (process_statement(stmt.c_str()) == ps_ERR)
|
switch (process_statement(setValues.AutoTerm ? statement.withSemicolon : statement.withoutSemicolon))
|
||||||
{
|
{
|
||||||
Exit_value = FINI_ERROR;
|
case TRUNCATED:
|
||||||
if (!Interactive && setValues.BailOnError)
|
statementGetter.rewind();
|
||||||
Abort_flag = true;
|
break;
|
||||||
|
|
||||||
|
case ps_ERR:
|
||||||
|
Exit_value = FINI_ERROR;
|
||||||
|
if (!Interactive && setValues.BailOnError)
|
||||||
|
Abort_flag = true;
|
||||||
|
[[fallthrough]];
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Place each non frontend statement in the temp file if we are reading from stdin.
|
||||||
|
Filelist->saveCommand(
|
||||||
|
(setValues.AutoTerm ? statement.withSemicolon : statement.withoutSemicolon).c_str(),
|
||||||
|
(setValues.AutoTerm ? "" : isqlGlob.global_Term));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case END:
|
case END:
|
||||||
case EOF:
|
case FOUND_EOF:
|
||||||
case EXIT:
|
case EXIT:
|
||||||
if (Abort_flag)
|
if (Abort_flag)
|
||||||
{
|
{
|
||||||
@ -4623,10 +4704,6 @@ static void do_isql()
|
|||||||
done = true;
|
done = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ERR_BUFFER_OVERFLOW:
|
|
||||||
IUTILS_msg_get(BUFFER_OVERFLOW, errbuf);
|
|
||||||
STDERROUT(errbuf);
|
|
||||||
|
|
||||||
case EXTRACT:
|
case EXTRACT:
|
||||||
case EXTRACTALL:
|
case EXTRACTALL:
|
||||||
default:
|
default:
|
||||||
@ -5368,7 +5445,7 @@ static processing_state frontend_set(const char* cmd, const char* const* parms,
|
|||||||
enum set_commands
|
enum set_commands
|
||||||
{
|
{
|
||||||
stat, count, list, plan, planonly, explain, blobdisplay, echo, autoddl,
|
stat, count, list, plan, planonly, explain, blobdisplay, echo, autoddl,
|
||||||
width, transaction, terminator, names, time,
|
autoterm, width, transaction, terminator, names, time,
|
||||||
sqlda_display,
|
sqlda_display,
|
||||||
exec_path_display,
|
exec_path_display,
|
||||||
sql, warning, sqlCont, heading, bail,
|
sql, warning, sqlCont, heading, bail,
|
||||||
@ -5392,6 +5469,7 @@ static processing_state frontend_set(const char* cmd, const char* const* parms,
|
|||||||
{SetOptions::blobdisplay, "BLOBDISPLAY", 4},
|
{SetOptions::blobdisplay, "BLOBDISPLAY", 4},
|
||||||
{SetOptions::echo, "ECHO", 0},
|
{SetOptions::echo, "ECHO", 0},
|
||||||
{SetOptions::autoddl, "AUTODDL", 4},
|
{SetOptions::autoddl, "AUTODDL", 4},
|
||||||
|
{SetOptions::autoterm, "AUTOTERM", 5},
|
||||||
{SetOptions::width, "WIDTH", 0},
|
{SetOptions::width, "WIDTH", 0},
|
||||||
{SetOptions::transaction, "TRANSACTION", 5},
|
{SetOptions::transaction, "TRANSACTION", 5},
|
||||||
{SetOptions::terminator, "TERMINATOR", 4},
|
{SetOptions::terminator, "TERMINATOR", 4},
|
||||||
@ -5482,6 +5560,15 @@ static processing_state frontend_set(const char* cmd, const char* const* parms,
|
|||||||
ret = do_set_command(parms[2], &setValues.Autocommit);
|
ret = do_set_command(parms[2], &setValues.Autocommit);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SetOptions::autoterm:
|
||||||
|
ret = do_set_command(parms[2], &setValues.AutoTerm);
|
||||||
|
if (setValues.AutoTerm)
|
||||||
|
{
|
||||||
|
isqlGlob.Termlen = 1;
|
||||||
|
strcpy(isqlGlob.global_Term, ";");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case SetOptions::width:
|
case SetOptions::width:
|
||||||
ret = newsize(parms[2][0] == '"' ? lparms[2] : parms[2], parms[3]);
|
ret = newsize(parms[2][0] == '"' ? lparms[2] : parms[2], parms[3]);
|
||||||
break;
|
break;
|
||||||
@ -5504,6 +5591,8 @@ static processing_state frontend_set(const char* cmd, const char* const* parms,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setValues.AutoTerm = false;
|
||||||
|
|
||||||
isqlGlob.Termlen = strlen(a);
|
isqlGlob.Termlen = strlen(a);
|
||||||
if (isqlGlob.Termlen < MAXTERM_SIZE)
|
if (isqlGlob.Termlen < MAXTERM_SIZE)
|
||||||
{
|
{
|
||||||
@ -5808,85 +5897,59 @@ static processing_state get_dialect(const char* const dialect_str,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static processing_state get_statement(string& statement,
|
std::pair<FrontendLexer::SingleStatement, processing_state> StatementGetter::getStatement()
|
||||||
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;
|
Filelist->Ifp().indev_line = Filelist->Ifp().indev_aux;
|
||||||
bool done = false;
|
|
||||||
|
|
||||||
enum
|
const auto* prompt = lexer.isBufferEmpty() ? sql_prompt : conPrompt;
|
||||||
|
std::string_view term(isqlGlob.global_Term, isqlGlob.Termlen);
|
||||||
|
std::string buffer;
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
normal,
|
if ((Interactive && !Input_file) || setValues.Echo)
|
||||||
in_single_line_comment,
|
ISQL_prompt(prompt);
|
||||||
in_block_comment,
|
|
||||||
in_single_quoted_string,
|
|
||||||
in_double_quoted_string
|
|
||||||
} state = normal;
|
|
||||||
|
|
||||||
char lastChar = '\0';
|
|
||||||
char altQuoteChar = '\0';
|
|
||||||
unsigned altQuoteStringLength = 0;
|
|
||||||
|
|
||||||
while (!done)
|
|
||||||
{
|
|
||||||
SSHORT c = getNextInputChar();
|
SSHORT c = getNextInputChar();
|
||||||
switch (c)
|
|
||||||
|
if (c == EOF)
|
||||||
{
|
{
|
||||||
case EOF:
|
|
||||||
// Go back to getc if we get interrupted by a signal.
|
// Go back to getc if we get interrupted by a signal.
|
||||||
|
|
||||||
if (SYSCALL_INTERRUPTED(errno))
|
if (SYSCALL_INTERRUPTED(errno))
|
||||||
{
|
{
|
||||||
errno = 0;
|
errno = 0;
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lexer.appendBuffer(buffer);
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
// If there was something valuable before EOF - error
|
// If there was something valuable before EOF - error
|
||||||
if (valuable_count > 0)
|
if (!lexer.isBufferEmpty())
|
||||||
{
|
{
|
||||||
TEXT errbuf[MSG_LENGTH];
|
bool isEmpty = false;
|
||||||
IUTILS_msg_get(UNEXPECTED_EOF, errbuf);
|
|
||||||
STDERROUT(errbuf);
|
try
|
||||||
Exit_value = FINI_ERROR;
|
{
|
||||||
ret = FAIL;
|
isEmpty = FrontendLexer::stripComments(lexer.getBuffer()).empty();
|
||||||
|
}
|
||||||
|
catch (const FrontendLexer::IncompleteTokenError&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEmpty)
|
||||||
|
{
|
||||||
|
TEXT errbuf[MSG_LENGTH];
|
||||||
|
IUTILS_msg_get(UNEXPECTED_EOF, errbuf);
|
||||||
|
STDERROUT(errbuf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we hit EOF at the top of the flist, exit time
|
// If we hit EOF at the top of the flist, exit time
|
||||||
|
|
||||||
if (Filelist->count() == 1)
|
if (Filelist->count() == 1)
|
||||||
return FOUND_EOF;
|
return {{}, FOUND_EOF};
|
||||||
|
|
||||||
// If this is not tmpfile, close it
|
// If this is not tmpfile, close it
|
||||||
|
|
||||||
@ -5898,7 +5961,7 @@ static processing_state get_statement(string& statement,
|
|||||||
Filelist->removeIntoIfp();
|
Filelist->removeIntoIfp();
|
||||||
|
|
||||||
if ((Interactive && !Input_file) || setValues.Echo)
|
if ((Interactive && !Input_file) || setValues.Echo)
|
||||||
ISQL_prompt(statement_prompt);
|
prompt = sql_prompt;
|
||||||
|
|
||||||
// CVC: Let's detect if we went back to the first level.
|
// CVC: Let's detect if we went back to the first level.
|
||||||
if (Filelist->readingStdin())
|
if (Filelist->readingStdin())
|
||||||
@ -5910,182 +5973,43 @@ static processing_state get_statement(string& statement,
|
|||||||
// Try to convince the new routines to go back to previous file(s)
|
// Try to convince the new routines to go back to previous file(s)
|
||||||
// This should fix the INPUT bug introduced with editline.
|
// This should fix the INPUT bug introduced with editline.
|
||||||
getColumn = -1;
|
getColumn = -1;
|
||||||
break;
|
|
||||||
|
|
||||||
case '\n':
|
if (!lexer.isBufferEmpty())
|
||||||
// case '\0': // In particular with readline the \n is removed
|
|
||||||
if (state == in_single_line_comment)
|
|
||||||
{
|
{
|
||||||
state = normal;
|
lexer.reset();
|
||||||
}
|
Exit_value = FINI_ERROR;
|
||||||
|
return {{}, FAIL};
|
||||||
// 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();
|
|
||||||
altQuoteStringLength = statement.length();
|
|
||||||
|
|
||||||
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 || (statement.length() != altQuoteStringLength + 1 && 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// 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++;
|
buffer += (char) c;
|
||||||
if (valuable_count == 1) // this is the first valuable char in stream
|
|
||||||
|
if (c == '\n' ||
|
||||||
|
(buffer.length() >= isqlGlob.Termlen &&
|
||||||
|
std::equal(buffer.end() - isqlGlob.Termlen, buffer.end(), term.begin())))
|
||||||
{
|
{
|
||||||
// ignore all previous crap
|
lexer.appendBuffer(buffer);
|
||||||
statement.resize(0);
|
buffer.clear();
|
||||||
non_comment_pos = 0;
|
|
||||||
|
const auto singleStatementVar = lexer.getSingleStatement(term);
|
||||||
|
|
||||||
|
if (const auto singleStatement = std::get_if<FrontendLexer::SingleStatement>(&singleStatementVar))
|
||||||
|
return {*singleStatement, CONT};
|
||||||
|
else if (const auto incompleteTokenError =
|
||||||
|
std::get_if<FrontendLexer::IncompleteTokenError>(&singleStatementVar))
|
||||||
|
{
|
||||||
|
prompt =
|
||||||
|
incompleteTokenError->insideComment ? "---> " :
|
||||||
|
lexer.isBufferEmpty() ? sql_prompt :
|
||||||
|
conPrompt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statement += (lastChar = c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this was a null statement, skip it
|
fb_assert(false);
|
||||||
if (ret == CONT && statement.isEmpty())
|
return {{}, FOUND_EOF};
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -6484,6 +6408,9 @@ static processing_state print_sets()
|
|||||||
p = p->next;
|
p = p->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print_set("Auto Term:", setValues.AutoTerm);
|
||||||
|
|
||||||
isqlGlob.printf("%-25s%s%s", "Terminator:", isqlGlob.global_Term, NEWLINE);
|
isqlGlob.printf("%-25s%s%s", "Terminator:", isqlGlob.global_Term, NEWLINE);
|
||||||
|
|
||||||
print_set("Time:", setValues.Time_display);
|
print_set("Time:", setValues.Time_display);
|
||||||
@ -6991,6 +6918,8 @@ static processing_state newdb(TEXT* dbname,
|
|||||||
|
|
||||||
global_Stmt = NULL;
|
global_Stmt = NULL;
|
||||||
|
|
||||||
|
check_autoterm();
|
||||||
|
|
||||||
return SKIP;
|
return SKIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7463,6 +7392,7 @@ static processing_state parse_arg(int argc, SCHAR** argv, SCHAR* tabname)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case IN_SW_ISQL_TERM:
|
case IN_SW_ISQL_TERM:
|
||||||
|
setValues.AutoTerm = false;
|
||||||
isqlGlob.Termlen = strlen(swarg_str);
|
isqlGlob.Termlen = strlen(swarg_str);
|
||||||
if (isqlGlob.Termlen >= MAXTERM_SIZE) {
|
if (isqlGlob.Termlen >= MAXTERM_SIZE) {
|
||||||
isqlGlob.Termlen = MAXTERM_SIZE - 1;
|
isqlGlob.Termlen = MAXTERM_SIZE - 1;
|
||||||
@ -7604,6 +7534,10 @@ static processing_state parse_arg(int argc, SCHAR** argv, SCHAR* tabname)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case IN_SW_ISQL_AUTOTERM:
|
||||||
|
setValues.AutoTerm = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case IN_SW_ISQL_HELP:
|
case IN_SW_ISQL_HELP:
|
||||||
ret = ps_ERR;
|
ret = ps_ERR;
|
||||||
break;
|
break;
|
||||||
@ -8942,7 +8876,7 @@ static unsigned process_message_display(Firebird::IMessageMetadata* message, uns
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static processing_state process_statement(const TEXT* str2)
|
static processing_state process_statement(const std::string& str)
|
||||||
{
|
{
|
||||||
/**************************************
|
/**************************************
|
||||||
*
|
*
|
||||||
@ -9033,9 +8967,20 @@ static processing_state process_statement(const TEXT* str2)
|
|||||||
flags |= Firebird::IStatement::PREPARE_PREFETCH_LEGACY_PLAN;
|
flags |= Firebird::IStatement::PREPARE_PREFETCH_LEGACY_PLAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
global_Stmt = DB->prepare(fbStatus, prepare_trans, 0, str2, isqlGlob.SQL_dialect, flags);
|
if (setValues.AutoTerm)
|
||||||
|
flags |= IStatement::PREPARE_REQUIRE_SEMICOLON;
|
||||||
|
|
||||||
|
global_Stmt = DB->prepare(fbStatus, prepare_trans,
|
||||||
|
0, str.c_str(), isqlGlob.SQL_dialect, flags);
|
||||||
|
|
||||||
if (failed())
|
if (failed())
|
||||||
{
|
{
|
||||||
|
if (setValues.AutoTerm &&
|
||||||
|
fb_utils::containsErrorCode(fbStatus->getErrors(), isc_command_end_err2))
|
||||||
|
{
|
||||||
|
return TRUNCATED;
|
||||||
|
}
|
||||||
|
|
||||||
if (isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION && Input_file)
|
if (isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION && Input_file)
|
||||||
{
|
{
|
||||||
isqlGlob.printf("%s%s%s%s%s%s",
|
isqlGlob.printf("%s%s%s%s%s%s",
|
||||||
@ -9043,7 +8988,7 @@ static processing_state process_statement(const TEXT* str2)
|
|||||||
"**** Error preparing statement:",
|
"**** Error preparing statement:",
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
str2,
|
str.c_str(),
|
||||||
NEWLINE);
|
NEWLINE);
|
||||||
}
|
}
|
||||||
ISQL_errmsg(fbStatus);
|
ISQL_errmsg(fbStatus);
|
||||||
@ -9160,7 +9105,7 @@ static processing_state process_statement(const TEXT* str2)
|
|||||||
(statement_type == isc_info_sql_stmt_ddl ||
|
(statement_type == isc_info_sql_stmt_ddl ||
|
||||||
statement_type == isc_info_sql_stmt_set_generator))
|
statement_type == isc_info_sql_stmt_set_generator))
|
||||||
{
|
{
|
||||||
DB->execute(fbStatus, D__trans, 0, str2, isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL);
|
DB->execute(fbStatus, D__trans, 0, str.c_str(), isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL);
|
||||||
setValues.StmtTimeout = 0;
|
setValues.StmtTimeout = 0;
|
||||||
if (ISQL_errmsg(fbStatus))
|
if (ISQL_errmsg(fbStatus))
|
||||||
{
|
{
|
||||||
@ -9195,9 +9140,9 @@ static processing_state process_statement(const TEXT* str2)
|
|||||||
if (statement_type == isc_info_sql_stmt_start_trans)
|
if (statement_type == isc_info_sql_stmt_start_trans)
|
||||||
{
|
{
|
||||||
// CVC: Starting a txn can fail, too. Let's check it, although I
|
// CVC: Starting a txn can fail, too. Let's check it, although I
|
||||||
// suspect isql will catch it in frontend_set() through get_statement(),
|
// suspect isql will catch it in frontend_set() through StatementGetter,
|
||||||
// so this place has little chance to be reached.
|
// so this place has little chance to be reached.
|
||||||
if (newtrans(str2) == FAIL)
|
if (newtrans(str.c_str()) == FAIL)
|
||||||
return ps_ERR;
|
return ps_ERR;
|
||||||
|
|
||||||
if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
|
if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
|
||||||
|
@ -73,7 +73,7 @@ enum processing_state {
|
|||||||
EXTRACTALL = 8,
|
EXTRACTALL = 8,
|
||||||
FETCH = 9,
|
FETCH = 9,
|
||||||
OBJECT_NOT_FOUND = 10,
|
OBJECT_NOT_FOUND = 10,
|
||||||
ERR_BUFFER_OVERFLOW = 11
|
TRUNCATED = 11
|
||||||
};
|
};
|
||||||
|
|
||||||
// Which blob subtypes to print
|
// Which blob subtypes to print
|
||||||
|
@ -32,36 +32,37 @@ enum isql_switches
|
|||||||
{
|
{
|
||||||
IN_SW_ISQL_0 = 0,
|
IN_SW_ISQL_0 = 0,
|
||||||
IN_SW_ISQL_EXTRACTALL = 1,
|
IN_SW_ISQL_EXTRACTALL = 1,
|
||||||
IN_SW_ISQL_BAIL = 2,
|
IN_SW_ISQL_AUTOTERM = 2,
|
||||||
IN_SW_ISQL_CACHE = 3,
|
IN_SW_ISQL_BAIL = 3,
|
||||||
IN_SW_ISQL_CHARSET = 4,
|
IN_SW_ISQL_CACHE = 4,
|
||||||
IN_SW_ISQL_DATABASE = 5,
|
IN_SW_ISQL_CHARSET = 5,
|
||||||
IN_SW_ISQL_ECHO = 6,
|
IN_SW_ISQL_DATABASE = 6,
|
||||||
IN_SW_ISQL_EXTRACT = 7,
|
IN_SW_ISQL_ECHO = 7,
|
||||||
IN_SW_ISQL_FETCHPASS = 8,
|
IN_SW_ISQL_EXTRACT = 8,
|
||||||
IN_SW_ISQL_INPUT = 9,
|
IN_SW_ISQL_FETCHPASS = 9,
|
||||||
IN_SW_ISQL_MERGE = 10,
|
IN_SW_ISQL_INPUT = 10,
|
||||||
IN_SW_ISQL_MERGE2 = 11,
|
IN_SW_ISQL_MERGE = 11,
|
||||||
IN_SW_ISQL_NOAUTOCOMMIT = 12,
|
IN_SW_ISQL_MERGE2 = 12,
|
||||||
IN_SW_ISQL_NODBTRIGGERS = 13,
|
IN_SW_ISQL_NOAUTOCOMMIT = 13,
|
||||||
IN_SW_ISQL_NOWARN = 14,
|
IN_SW_ISQL_NODBTRIGGERS = 14,
|
||||||
IN_SW_ISQL_OUTPUT = 15,
|
IN_SW_ISQL_NOWARN = 15,
|
||||||
IN_SW_ISQL_PAGE = 16,
|
IN_SW_ISQL_OUTPUT = 16,
|
||||||
IN_SW_ISQL_PASSWORD = 17,
|
IN_SW_ISQL_PAGE = 17,
|
||||||
IN_SW_ISQL_QUIET = 18,
|
IN_SW_ISQL_PASSWORD = 18,
|
||||||
IN_SW_ISQL_ROLE = 19,
|
IN_SW_ISQL_QUIET = 19,
|
||||||
IN_SW_ISQL_ROLE2 = 20,
|
IN_SW_ISQL_ROLE = 20,
|
||||||
IN_SW_ISQL_SQLDIALECT = 21,
|
IN_SW_ISQL_ROLE2 = 21,
|
||||||
IN_SW_ISQL_TERM = 22,
|
IN_SW_ISQL_SQLDIALECT = 22,
|
||||||
|
IN_SW_ISQL_TERM = 23,
|
||||||
#ifdef TRUSTED_AUTH
|
#ifdef TRUSTED_AUTH
|
||||||
IN_SW_ISQL_TRUSTED = 23,
|
IN_SW_ISQL_TRUSTED = 24,
|
||||||
#endif
|
#endif
|
||||||
IN_SW_ISQL_USER = 24,
|
IN_SW_ISQL_USER = 25,
|
||||||
IN_SW_ISQL_VERSION = 25,
|
IN_SW_ISQL_VERSION = 26,
|
||||||
#ifdef DEV_BUILD
|
#ifdef DEV_BUILD
|
||||||
IN_SW_ISQL_EXTRACTTBL = 26,
|
IN_SW_ISQL_EXTRACTTBL = 27,
|
||||||
#endif
|
#endif
|
||||||
IN_SW_ISQL_HELP = 27
|
IN_SW_ISQL_HELP = 28
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -69,7 +70,8 @@ enum IsqlOptionType { iqoArgNone, iqoArgInteger, iqoArgString };
|
|||||||
|
|
||||||
static const Switches::in_sw_tab_t isql_in_sw_table[] =
|
static const Switches::in_sw_tab_t isql_in_sw_table[] =
|
||||||
{
|
{
|
||||||
{IN_SW_ISQL_EXTRACTALL , 0, "ALL" , 0, 0, 0, false, false, 11 , 1, NULL, iqoArgNone},
|
{IN_SW_ISQL_EXTRACTALL , 0, "ALL" , 0, 0, 0, false, false, 11 , 1, NULL, iqoArgNone},
|
||||||
|
{IN_SW_ISQL_AUTOTERM , 0, "AUTOTERM" , 0, 0, 0, false, false, 205 , 5, NULL, iqoArgNone},
|
||||||
{IN_SW_ISQL_BAIL , 0, "BAIL" , 0, 0, 0, false, false, 104 , 1, NULL, iqoArgNone},
|
{IN_SW_ISQL_BAIL , 0, "BAIL" , 0, 0, 0, false, false, 104 , 1, NULL, iqoArgNone},
|
||||||
{IN_SW_ISQL_CACHE , 0, "CACHE" , 0, 0, 0, false, false, 111 , 1, NULL, iqoArgInteger},
|
{IN_SW_ISQL_CACHE , 0, "CACHE" , 0, 0, 0, false, false, 111 , 1, NULL, iqoArgInteger},
|
||||||
{IN_SW_ISQL_CHARSET , 0, "CHARSET" , 0, 0, 0, false, false, 122 , 2, NULL, iqoArgString},
|
{IN_SW_ISQL_CHARSET , 0, "CHARSET" , 0, 0, 0, false, false, 122 , 2, NULL, iqoArgString},
|
||||||
|
148
src/isql/tests/FrontendLexerTest.cpp
Normal file
148
src/isql/tests/FrontendLexerTest.cpp
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* The contents of this file are subject to the Initial
|
||||||
|
* Developer's 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.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed AS IS,
|
||||||
|
* 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 Adriano dos Santos Fernandes
|
||||||
|
* for the Firebird Open Source RDBMS project.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Adriano dos Santos Fernandes <adrianosf at gmail.com>
|
||||||
|
* and all contributors signed below.
|
||||||
|
*
|
||||||
|
* All Rights Reserved.
|
||||||
|
* Contributor(s): ______________________________________.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "firebird.h"
|
||||||
|
#include "boost/test/unit_test.hpp"
|
||||||
|
#include "../FrontendLexer.h"
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
using namespace Firebird;
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(ISqlSuite)
|
||||||
|
BOOST_AUTO_TEST_SUITE(FrontendLexerSuite)
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(FrontendLexerTests)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(StripCommentsTest)
|
||||||
|
{
|
||||||
|
BOOST_TEST(FrontendLexer::stripComments(
|
||||||
|
"/* comment */ select 1 from rdb$database /* comment */") == "select 1 from rdb$database");
|
||||||
|
|
||||||
|
BOOST_TEST(FrontendLexer::stripComments(
|
||||||
|
"-- comment\nselect '123' /* comment */ from rdb$database -- comment") == "select '123' from rdb$database");
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(GetSingleStatementTest)
|
||||||
|
{
|
||||||
|
{ // scope
|
||||||
|
const std::string s1 =
|
||||||
|
"select /* ; */ -- ;\n"
|
||||||
|
" ';' || q'{;}'\n"
|
||||||
|
" from rdb$database;";
|
||||||
|
|
||||||
|
BOOST_TEST(std::get<FrontendLexer::SingleStatement>(FrontendLexer(
|
||||||
|
s1 + "\nselect ...").getSingleStatement(";")).withSemicolon == s1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // scope
|
||||||
|
const std::string s1 = "select 1 from rdb$database; -- comment";
|
||||||
|
const std::string s2 = "select 2 from rdb$database;";
|
||||||
|
const std::string s3 = "select 3 from rdb$database;";
|
||||||
|
const std::string s4 = "execute block returns (o1 integer) as begin o1 = 1;";
|
||||||
|
const std::string s5 = "end;";
|
||||||
|
const std::string s6 = "?";
|
||||||
|
const std::string s7 = "? set;";
|
||||||
|
|
||||||
|
FrontendLexer lexer(s1 + "\n" + s2);
|
||||||
|
|
||||||
|
BOOST_TEST(std::get<FrontendLexer::SingleStatement>(lexer.getSingleStatement(";")).withSemicolon == s1);
|
||||||
|
|
||||||
|
lexer.appendBuffer(s3 + "\n" + s4 + s5);
|
||||||
|
|
||||||
|
BOOST_TEST(std::get<FrontendLexer::SingleStatement>(lexer.getSingleStatement(";")).withSemicolon == s2);
|
||||||
|
BOOST_TEST(std::get<FrontendLexer::SingleStatement>(lexer.getSingleStatement(";")).withSemicolon == s3);
|
||||||
|
|
||||||
|
BOOST_TEST(std::get<FrontendLexer::SingleStatement>(lexer.getSingleStatement(";")).withSemicolon == s4);
|
||||||
|
lexer.rewind();
|
||||||
|
BOOST_TEST(std::get<FrontendLexer::SingleStatement>(lexer.getSingleStatement(";")).withSemicolon == s4 + s5);
|
||||||
|
|
||||||
|
lexer.appendBuffer(s6 + "\n" + s7);
|
||||||
|
BOOST_TEST(std::get<FrontendLexer::SingleStatement>(lexer.getSingleStatement(";")).withSemicolon == s6);
|
||||||
|
BOOST_TEST(std::get<FrontendLexer::SingleStatement>(lexer.getSingleStatement(";")).withSemicolon == s7);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_TEST(!std::get<FrontendLexer::IncompleteTokenError>(FrontendLexer(
|
||||||
|
"select 1 from rdb$database").getSingleStatement(";")).insideComment);
|
||||||
|
BOOST_TEST(std::get<FrontendLexer::IncompleteTokenError>(FrontendLexer(
|
||||||
|
"select 1 from rdb$database; /*").getSingleStatement(";")).insideComment);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(SkipSingleLineCommentsTest)
|
||||||
|
{
|
||||||
|
FrontendLexer lexer(
|
||||||
|
"-- comment 0\r\n"
|
||||||
|
"set -- comment 1\n"
|
||||||
|
"stats -- comment 2\r\n"
|
||||||
|
"- -- comment 3\n"
|
||||||
|
"-- comment 4"
|
||||||
|
);
|
||||||
|
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "SET");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "STATS");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "-");
|
||||||
|
BOOST_TEST(lexer.getToken().type == FrontendLexer::Token::TYPE_EOF);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(SkipMultiLineCommentsTest)
|
||||||
|
{
|
||||||
|
FrontendLexer lexer(
|
||||||
|
"/* comment 0 */\r\n"
|
||||||
|
"set /* comment 1\n"
|
||||||
|
"comment 1 continuation\n"
|
||||||
|
"*/ stats /* comment 2\r\n"
|
||||||
|
"* */ / /* comment 3*/ /* comment 4*/"
|
||||||
|
);
|
||||||
|
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "SET");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "STATS");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "/");
|
||||||
|
BOOST_TEST(lexer.getToken().type == FrontendLexer::Token::TYPE_EOF);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(ParseStringsTest)
|
||||||
|
{
|
||||||
|
FrontendLexer lexer(
|
||||||
|
"'ab''c\"d' "
|
||||||
|
"\"ab''c\"\"d\" "
|
||||||
|
"q'{ab'c\"d}' "
|
||||||
|
"q'(ab'c\"d)' "
|
||||||
|
"q'[ab'c\"d]' "
|
||||||
|
"q'<ab'c\"d>' "
|
||||||
|
"q'!ab'c\"d!' "
|
||||||
|
);
|
||||||
|
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "ab'c\"d");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "ab''c\"d");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "ab'c\"d");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "ab'c\"d");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "ab'c\"d");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "ab'c\"d");
|
||||||
|
BOOST_TEST(lexer.getToken().processedText == "ab'c\"d");
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END() // FrontendLexerTests
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END() // FrontendLexerSuite
|
||||||
|
BOOST_AUTO_TEST_SUITE_END() // ISqlSuite
|
@ -4131,6 +4131,7 @@ Statement* Attachment::prepare(CheckStatusWrapper* status, ITransaction* apiTra,
|
|||||||
prepare->p_sqlst_items.cstr_length = (ULONG) items.getCount();
|
prepare->p_sqlst_items.cstr_length = (ULONG) items.getCount();
|
||||||
prepare->p_sqlst_items.cstr_address = items.begin();
|
prepare->p_sqlst_items.cstr_address = items.begin();
|
||||||
prepare->p_sqlst_buffer_length = (ULONG) buffer.getCount();
|
prepare->p_sqlst_buffer_length = (ULONG) buffer.getCount();
|
||||||
|
prepare->p_sqlst_flags = flags;
|
||||||
|
|
||||||
send_packet(rdb->rdb_port, packet);
|
send_packet(rdb->rdb_port, packet);
|
||||||
|
|
||||||
|
@ -716,7 +716,8 @@ rem_port* INET_analyze(ClntAuthBlock* cBlock,
|
|||||||
REMOTE_PROTOCOL(PROTOCOL_VERSION15, ptype_lazy_send, 6),
|
REMOTE_PROTOCOL(PROTOCOL_VERSION15, ptype_lazy_send, 6),
|
||||||
REMOTE_PROTOCOL(PROTOCOL_VERSION16, ptype_lazy_send, 7),
|
REMOTE_PROTOCOL(PROTOCOL_VERSION16, ptype_lazy_send, 7),
|
||||||
REMOTE_PROTOCOL(PROTOCOL_VERSION17, ptype_lazy_send, 8),
|
REMOTE_PROTOCOL(PROTOCOL_VERSION17, ptype_lazy_send, 8),
|
||||||
REMOTE_PROTOCOL(PROTOCOL_VERSION18, ptype_lazy_send, 9)
|
REMOTE_PROTOCOL(PROTOCOL_VERSION18, ptype_lazy_send, 9),
|
||||||
|
REMOTE_PROTOCOL(PROTOCOL_VERSION19, ptype_lazy_send, 10)
|
||||||
};
|
};
|
||||||
fb_assert(FB_NELEM(protocols_to_try) <= FB_NELEM(cnct->p_cnct_versions));
|
fb_assert(FB_NELEM(protocols_to_try) <= FB_NELEM(cnct->p_cnct_versions));
|
||||||
cnct->p_cnct_count = FB_NELEM(protocols_to_try);
|
cnct->p_cnct_count = FB_NELEM(protocols_to_try);
|
||||||
|
@ -307,7 +307,8 @@ rem_port* XNET_analyze(ClntAuthBlock* cBlock,
|
|||||||
REMOTE_PROTOCOL(PROTOCOL_VERSION15, ptype_batch_send, 6),
|
REMOTE_PROTOCOL(PROTOCOL_VERSION15, ptype_batch_send, 6),
|
||||||
REMOTE_PROTOCOL(PROTOCOL_VERSION16, ptype_batch_send, 7),
|
REMOTE_PROTOCOL(PROTOCOL_VERSION16, ptype_batch_send, 7),
|
||||||
REMOTE_PROTOCOL(PROTOCOL_VERSION17, ptype_batch_send, 8),
|
REMOTE_PROTOCOL(PROTOCOL_VERSION17, ptype_batch_send, 8),
|
||||||
REMOTE_PROTOCOL(PROTOCOL_VERSION18, ptype_batch_send, 9)
|
REMOTE_PROTOCOL(PROTOCOL_VERSION18, ptype_batch_send, 9),
|
||||||
|
REMOTE_PROTOCOL(PROTOCOL_VERSION19, ptype_lazy_send, 10)
|
||||||
};
|
};
|
||||||
fb_assert(FB_NELEM(protocols_to_try) <= FB_NELEM(cnct->p_cnct_versions));
|
fb_assert(FB_NELEM(protocols_to_try) <= FB_NELEM(cnct->p_cnct_versions));
|
||||||
cnct->p_cnct_count = FB_NELEM(protocols_to_try);
|
cnct->p_cnct_count = FB_NELEM(protocols_to_try);
|
||||||
|
@ -700,6 +700,10 @@ bool_t xdr_protocol(RemoteXdr* xdrs, PACKET* p)
|
|||||||
MAP(xdr_long, reinterpret_cast<SLONG&>(prep_stmt->p_sqlst_buffer_length));
|
MAP(xdr_long, reinterpret_cast<SLONG&>(prep_stmt->p_sqlst_buffer_length));
|
||||||
// p_sqlst_buffer_length was USHORT in older versions
|
// p_sqlst_buffer_length was USHORT in older versions
|
||||||
fixupLength(xdrs, prep_stmt->p_sqlst_buffer_length);
|
fixupLength(xdrs, prep_stmt->p_sqlst_buffer_length);
|
||||||
|
|
||||||
|
if (port->port_protocol >= PROTOCOL_VERSION19)
|
||||||
|
MAP(xdr_short, reinterpret_cast<SSHORT&>(prep_stmt->p_sqlst_flags));
|
||||||
|
|
||||||
DEBUG_PRINTSIZE(xdrs, p->p_operation);
|
DEBUG_PRINTSIZE(xdrs, p->p_operation);
|
||||||
return P_TRUE(xdrs, p);
|
return P_TRUE(xdrs, p);
|
||||||
|
|
||||||
|
@ -104,6 +104,11 @@ const USHORT PROTOCOL_VERSION17 = (FB_PROTOCOL_FLAG | 17);
|
|||||||
const USHORT PROTOCOL_VERSION18 = (FB_PROTOCOL_FLAG | 18);
|
const USHORT PROTOCOL_VERSION18 = (FB_PROTOCOL_FLAG | 18);
|
||||||
const USHORT PROTOCOL_FETCH_SCROLL = PROTOCOL_VERSION18;
|
const USHORT PROTOCOL_FETCH_SCROLL = PROTOCOL_VERSION18;
|
||||||
|
|
||||||
|
// Protocol 19:
|
||||||
|
// - supports passing flags to IStatement::prepare
|
||||||
|
|
||||||
|
const USHORT PROTOCOL_VERSION19 = (FB_PROTOCOL_FLAG | 19);
|
||||||
|
|
||||||
// Architecture types
|
// Architecture types
|
||||||
|
|
||||||
enum P_ARCH
|
enum P_ARCH
|
||||||
@ -612,6 +617,7 @@ typedef struct p_sqlst
|
|||||||
USHORT p_sqlst_messages; // Number of messages
|
USHORT p_sqlst_messages; // Number of messages
|
||||||
CSTRING p_sqlst_out_blr; // blr describing output message
|
CSTRING p_sqlst_out_blr; // blr describing output message
|
||||||
USHORT p_sqlst_out_message_number;
|
USHORT p_sqlst_out_message_number;
|
||||||
|
USHORT p_sqlst_flags; // prepare flags
|
||||||
} P_SQLST;
|
} P_SQLST;
|
||||||
|
|
||||||
typedef struct p_sqldata
|
typedef struct p_sqldata
|
||||||
|
@ -1925,7 +1925,7 @@ static bool accept_connection(rem_port* port, P_CNCT* connect, PACKET* send)
|
|||||||
{
|
{
|
||||||
if ((protocol->p_cnct_version == PROTOCOL_VERSION10 ||
|
if ((protocol->p_cnct_version == PROTOCOL_VERSION10 ||
|
||||||
(protocol->p_cnct_version >= PROTOCOL_VERSION11 &&
|
(protocol->p_cnct_version >= PROTOCOL_VERSION11 &&
|
||||||
protocol->p_cnct_version <= PROTOCOL_VERSION18)) &&
|
protocol->p_cnct_version <= PROTOCOL_VERSION19)) &&
|
||||||
(protocol->p_cnct_architecture == arch_generic ||
|
(protocol->p_cnct_architecture == arch_generic ||
|
||||||
protocol->p_cnct_architecture == ARCHITECTURE) &&
|
protocol->p_cnct_architecture == ARCHITECTURE) &&
|
||||||
protocol->p_cnct_weight >= weight)
|
protocol->p_cnct_weight >= weight)
|
||||||
@ -4840,7 +4840,8 @@ ISC_STATUS rem_port::prepare_statement(P_SQLST * prepareL, PACKET* sendL)
|
|||||||
// stuff isc_info_length in front of info items buffer
|
// stuff isc_info_length in front of info items buffer
|
||||||
*info = isc_info_length;
|
*info = isc_info_length;
|
||||||
memmove(info + 1, prepareL->p_sqlst_items.cstr_address, infoLength++);
|
memmove(info + 1, prepareL->p_sqlst_items.cstr_address, infoLength++);
|
||||||
const unsigned int flags = StatementMetadata::buildInfoFlags(infoLength, info);
|
unsigned flags = StatementMetadata::buildInfoFlags(infoLength, info) |
|
||||||
|
prepareL->p_sqlst_flags;
|
||||||
|
|
||||||
ITransaction* iface = NULL;
|
ITransaction* iface = NULL;
|
||||||
if (transaction)
|
if (transaction)
|
||||||
|
Loading…
Reference in New Issue
Block a user