mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 16:43:03 +01:00
New interface Batch helping to efficiently implement JDBC prepared statement batches (#99)
Batch interface implementation
This commit is contained in:
parent
e8f65cb09d
commit
f53c23c17a
@ -343,8 +343,11 @@ fb_ping
|
||||
|
||||
fb_get_master_interface
|
||||
|
||||
# Legacy handles translation
|
||||
|
||||
fb_get_database_handle
|
||||
fb_get_transaction_handle
|
||||
#fb_get_statement_interface
|
||||
|
||||
fb_database_crypt_callback
|
||||
fb_dsql_set_timeout
|
||||
|
@ -114,6 +114,7 @@
|
||||
<ClInclude Include="..\..\..\src\common\classes\array.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\auto.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BaseStream.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BatchCompletionState.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BlrReader.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BlrWriter.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\ByteChunk.h" />
|
||||
|
@ -222,21 +222,6 @@
|
||||
<ClCompile Include="..\..\..\src\common\DecFloat.cpp">
|
||||
<Filter>classes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\common\tomcrypt\crypt_argchk.c">
|
||||
<Filter>tomcrypt</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\common\tomcrypt\md5.c">
|
||||
<Filter>tomcrypt</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\common\tomcrypt\sha1.c">
|
||||
<Filter>tomcrypt</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\common\tomcrypt\sha256.c">
|
||||
<Filter>tomcrypt</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\common\tomcrypt\sha512.c">
|
||||
<Filter>tomcrypt</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\common\classes\TomCryptHash.cpp">
|
||||
<Filter>classes</Filter>
|
||||
</ClCompile>
|
||||
@ -557,6 +542,9 @@
|
||||
<ClInclude Include="..\..\..\src\common\DecFloat.h">
|
||||
<Filter>headers</Filter>
|
||||
</ClInclude>
|
||||
<<<<<<< HEAD
|
||||
<ClInclude Include="..\..\..\src\common\classes\BatchCompletionState.h">
|
||||
=======
|
||||
<ClInclude Include="..\..\..\src\common\tomcrypt\tomcrypt.h">
|
||||
<Filter>headers</Filter>
|
||||
</ClInclude>
|
||||
@ -594,6 +582,7 @@
|
||||
<Filter>headers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\src\common\tomcrypt\tomcrypt_prng.h">
|
||||
>>>>>>> master
|
||||
<Filter>headers</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
|
@ -37,6 +37,7 @@
|
||||
<ClCompile Include="..\..\..\src\dsql\BoolNodes.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\ddl.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\dsql.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlBatch.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlCompilerScratch.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlCursor.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DSqlDataTypeUtil.cpp" />
|
||||
@ -168,6 +169,7 @@
|
||||
<ClInclude Include="..\..\..\src\dsql\DdlNodes.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\ddl_proto.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\dsql.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlBatch.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlCompilerScratch.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlCursor.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DSqlDataTypeUtil.h" />
|
||||
|
@ -459,6 +459,12 @@
|
||||
<ClCompile Include="..\..\..\src\jrd\extds\ValidatePassword.cpp">
|
||||
<Filter>JRD files\EXTDS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\jrd\Savepoint.cpp">
|
||||
<Filter>JRD files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlBatch.cpp">
|
||||
<Filter>DSQL</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\..\src\jrd\recsrc\RecordSource.h">
|
||||
@ -986,6 +992,9 @@
|
||||
<ClInclude Include="..\..\..\src\jrd\extds\ValidatePassword.h">
|
||||
<Filter>Header files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlBatch.h">
|
||||
<Filter>Header files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\src\dsql\DdlNodes.epp">
|
||||
|
@ -105,6 +105,7 @@
|
||||
<ClInclude Include="..\..\..\src\common\classes\array.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\auto.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BaseStream.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BatchCompletionState.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BlrReader.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BlrWriter.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\ByteChunk.h" />
|
||||
|
@ -539,5 +539,8 @@
|
||||
<ClInclude Include="..\..\..\src\common\DecFloat.h">
|
||||
<Filter>headers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\src\common\classes\BatchCompletionState.h">
|
||||
<Filter>headers</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -37,6 +37,7 @@
|
||||
<ClCompile Include="..\..\..\src\dsql\BoolNodes.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\ddl.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\dsql.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlBatch.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlCompilerScratch.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlCursor.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DSqlDataTypeUtil.cpp" />
|
||||
@ -168,6 +169,7 @@
|
||||
<ClInclude Include="..\..\..\src\dsql\DdlNodes.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\ddl_proto.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\dsql.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlBatch.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlCompilerScratch.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlCursor.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DSqlDataTypeUtil.h" />
|
||||
|
@ -462,6 +462,9 @@
|
||||
<ClCompile Include="..\..\..\src\jrd\Savepoint.cpp">
|
||||
<Filter>JRD files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlBatch.cpp">
|
||||
<Filter>DSQL</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\..\src\jrd\recsrc\RecordSource.h">
|
||||
@ -989,6 +992,9 @@
|
||||
<ClInclude Include="..\..\..\src\jrd\extds\ValidatePassword.h">
|
||||
<Filter>Header files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlBatch.h">
|
||||
<Filter>Header files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\src\dsql\DdlNodes.epp">
|
||||
|
@ -105,6 +105,7 @@
|
||||
<ClInclude Include="..\..\..\src\common\classes\array.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\auto.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BaseStream.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BatchCompletionState.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BlrReader.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\BlrWriter.h" />
|
||||
<ClInclude Include="..\..\..\src\common\classes\ByteChunk.h" />
|
||||
|
@ -539,5 +539,8 @@
|
||||
<ClInclude Include="..\..\..\src\common\DecFloat.h">
|
||||
<Filter>headers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\src\common\classes\BatchCompletionState.h">
|
||||
<Filter>headers</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -37,6 +37,7 @@
|
||||
<ClCompile Include="..\..\..\src\dsql\BoolNodes.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\ddl.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\dsql.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlBatch.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlCompilerScratch.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlCursor.cpp" />
|
||||
<ClCompile Include="..\..\..\src\dsql\DSqlDataTypeUtil.cpp" />
|
||||
@ -168,6 +169,7 @@
|
||||
<ClInclude Include="..\..\..\src\dsql\DdlNodes.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\ddl_proto.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\dsql.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlBatch.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlCompilerScratch.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlCursor.h" />
|
||||
<ClInclude Include="..\..\..\src\dsql\DSqlDataTypeUtil.h" />
|
||||
|
@ -462,6 +462,9 @@
|
||||
<ClCompile Include="..\..\..\src\jrd\Savepoint.cpp">
|
||||
<Filter>JRD files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\dsql\DsqlBatch.cpp">
|
||||
<Filter>DSQL</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\..\src\jrd\recsrc\RecordSource.h">
|
||||
@ -989,6 +992,9 @@
|
||||
<ClInclude Include="..\..\..\src\jrd\extds\ValidatePassword.h">
|
||||
<Filter>Header files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\src\dsql\DsqlBatch.h">
|
||||
<Filter>Header files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\src\dsql\DdlNodes.epp">
|
||||
|
@ -6,6 +6,15 @@
|
||||
<meta name="generator" content="LibreOffice 5.2.7.2 (Linux)"/>
|
||||
<meta name="author" content="alex "/>
|
||||
<meta name="created" content="00:00:00"/>
|
||||
<meta name="changed" content="2017-10-19T18:08:13.851823604"/>
|
||||
<meta name="created" content="00:00:00">
|
||||
<meta name="changed" content="2017-10-12T20:21:46.080329427">
|
||||
<meta name="created" content="00:00:00">
|
||||
<meta name="changed" content="2017-10-10T12:13:42.449014488">
|
||||
<meta name="created" content="00:00:00">
|
||||
<meta name="changed" content="2017-07-27T13:17:58.205479048">
|
||||
<meta name="created" content="00:00:00">
|
||||
<meta name="changed" content="2017-07-26T10:39:38.045248271">
|
||||
<meta name="changed" content="2017-10-16T18:57:56.725603578"/>
|
||||
<meta name="created" content="00:00:00">
|
||||
<meta name="changed" content="2017-02-02T17:00:07.121995034">
|
||||
@ -35,8 +44,10 @@ independent</b></font> – <font size="4" style="font-size: 14pt">that
|
||||
means that to define/use them one need not use language specific
|
||||
constructions like </font><font size="4" style="font-size: 14pt"><i>class</i></font>
|
||||
<font size="4" style="font-size: 14pt">in C++, interface may be
|
||||
defined using any language having concepts of array and pointer to
|
||||
procedure/function. Next interfaces are </font><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><span style="font-style: normal"><b>versioned</b></span></font></span>
|
||||
defined using any language </font><font size="4" style="font-size: 14pt">able
|
||||
to call functions using C calling conventions and </font><font size="4" style="font-size: 14pt">having
|
||||
concepts of array and pointer to procedure/function. Next interfaces
|
||||
are </font><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><span style="font-style: normal"><b>versioned</b></span></font></span>
|
||||
– <font size="4" style="font-size: 14pt">i.e. we support different
|
||||
versions of same interface. Binary layout of interfaces is designed
|
||||
to support that features very efficient (there is no need in
|
||||
@ -637,6 +648,9 @@ the following:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_BOOLEAN</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_CHAR(len)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_DATE</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_DECFIXED(scale)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_DECFLOAT16</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_DECFLOAT34</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_DOUBLE</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_FLOAT</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_INTEGER</font></p>
|
||||
@ -644,9 +658,9 @@ the following:</font></p>
|
||||
charSet)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_INTL_VARCHAR(len,
|
||||
charSet)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_SCALED_BIGINT(x)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_SCALED_INTEGER(x)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_SCALED_SMALLINT(x)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_SCALED_BIGINT(scale)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_SCALED_INTEGER(scale)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_SCALED_SMALLINT(scale)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_SMALLINT</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_TIME</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">FB_TIMESTAMP</font></p>
|
||||
@ -829,10 +843,322 @@ completion code that function is notified (by passing false as last
|
||||
parameter) that segment was not read completely and continuation is
|
||||
expected at next call.</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">After
|
||||
finishing with blob do not forget top close it:</font></p>
|
||||
finishing with blob do not forget to close it:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>blob->close(&status);</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
</p>
|
||||
<h1><a name="Modifying data in a batch"></a><font size="4" style="font-size: 14pt">Modifying
|
||||
data in a batch.</font></h1>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">Since
|
||||
version 4 firebird supports batch execution of statements with input
|
||||
parameters – that means sending more than single set of parameters
|
||||
when executing statement. <a href="#Batch">Batch</a> interface is
|
||||
designed (first of all) in order to satisfy JDBC requirements for
|
||||
prepared statement’s batch processing but has some serious
|
||||
differences:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">-
|
||||
like all operations with data in firebird it’s oriented on
|
||||
messages, not single field;</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">-
|
||||
as an important extension out batch interface supports inline use of
|
||||
blobs (specially efficient when working with small blobs);</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">-
|
||||
execute() method returns not plain array of integers but special
|
||||
<a href="#BatchCompletionState">BatchCompletionState</a> interface
|
||||
which can (depending upon batch creation parameters) contain both
|
||||
update records info and in addition to error flag detailed status
|
||||
vectors for messages that caused execution errors.</font></p>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
</p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><a href="#Batch">Batch</a>
|
||||
(exactly like <a href="#ResultSet">ResultSet</a>) may be created in 2
|
||||
ways – using <a href="#Statement">Statement</a> or <a href="#Attachment">Attachment</a>
|
||||
interface, in both cases createBatch() method of appropriate
|
||||
interface is called. In second case text of SQL statement to be
|
||||
executed in a batch is passed directly to createBatch(). Tuning of
|
||||
batch operation is performed using <a href="#Batch_PB">Batch
|
||||
parameters block</a> which has format more or less similar to DPB v.2
|
||||
– tag in the beginning (IBatch::CURRENT_VERSION) followed by the
|
||||
set of wide clumplets: 1-byte tag, 4-byte length, length-byte value.
|
||||
Possible tags are <a href="#Batch_PB">described</a> in batch
|
||||
interface. The simplest (and recommended) way to create parameters
|
||||
block for batch creation is to use appropriate <a href="#XpbBuilder">XpbBuilder</a>
|
||||
interface:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>IXpbBuilder*
|
||||
pb = utl->getXpbBuilder(&status, IXpbBuilder::BATCH, NULL, 0);</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>pb->insertInt(&status,
|
||||
IBatch::RECORD_COUNTS, 1);</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">Use
|
||||
of such parameters block directs batch to account number of updated
|
||||
records on per-message basis.</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">To
|
||||
create batch interface with desired parameters pass parameters block
|
||||
to createBatch() call:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>IBatch*
|
||||
batch = att->createBatch(&status, tra, 0, sqlStmtText,
|
||||
SQL_DIALECT_V6, NULL,</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>pb->getBufferLength(&status),
|
||||
pb->getBuffer(&status));</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">In
|
||||
this sample batch interface is created with default format of
|
||||
messages cause NULL is passed instead input metadata format.</font></p>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
</p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">In
|
||||
order to proceed with created batch interface we need to know format
|
||||
of messages in it. It can be obtained using getMetadata() method:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>IMessageMetadata*
|
||||
meta = batch->getMetadata(&status);</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">Certainly
|
||||
if you have passed your own format of messages to the batch you may
|
||||
simply use it.</font></p>
|
||||
<p style="margin-bottom: 0cm; font-variant: normal; font-style: normal">
|
||||
<font size="4" style="font-size: 14pt">In the former text I suppose
|
||||
that some function fillNextMessage(unsigned char* data,
|
||||
IMessageMetadata* metadata) is present and can fill buffer ‘data’
|
||||
according to passed format ‘metadata’. In order to work with
|
||||
messages we need a buffer for a data:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>unsigned
|
||||
char* data = new unsigned char[meta->getMessageLength(&status)];</i></font></p>
|
||||
<p style="margin-bottom: 0cm; font-variant: normal; font-style: normal">
|
||||
<font size="4" style="font-size: 14pt">Now we can add some messages
|
||||
full of data to the batch:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>fillNextMessage(data,
|
||||
meta);</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>batch->add(&status,
|
||||
1, data);</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>fillNextMessage(data,
|
||||
meta);</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>batch->add(&status,
|
||||
1, data);</i></font></p>
|
||||
<p style="margin-bottom: 0cm; font-variant: normal; font-style: normal">
|
||||
<font size="4" style="font-size: 14pt">An alternative way of working
|
||||
with messages (using FB_MESSAGE macro) is present in the sample of
|
||||
using batch interface 11.batch.cpp.</font></p>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
</p>
|
||||
<p style="margin-bottom: 0cm; font-variant: normal; font-style: normal">
|
||||
<font size="4" style="font-size: 14pt">Finally batch should be
|
||||
executed:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i><a href="#BatchCompletionState">IBatchCompletionState</a>*
|
||||
cs = batch->execute(&status, tra);</i></font></p>
|
||||
<p style="margin-bottom: 0cm"><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><span style="font-style: normal">We
|
||||
requested accounting of the number of modified (inserted, updated or
|
||||
deleted) records per message. To print it we must use
|
||||
<a href="#BatchCompletionState">BatchCompletionState</a> interface.
|
||||
Determine total number of messages processed by batch (it can be less
|
||||
than the number of messages passed to the batch if error happened and
|
||||
an option enabling multiple errors during batch processing was not
|
||||
turned on):</span></font></span></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i><span style="background: #ffffff">unsigned
|
||||
total = cs->getSize(&status);</span></i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt">Now print the state of each
|
||||
message:</font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i><span style="background: #ffffff">for
|
||||
(unsigned p = 0; p < total; ++p) printf(“Msg %u state %d\n”,
|
||||
p, cs->getState(&status, p));</span></i></font></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt"><span style="background: #ffffff">When
|
||||
finished analyzing completion state don’t forget to dispose it:</span></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i><span style="background: #ffffff">cs->dispose();</span></i></font></p>
|
||||
<p><font color="#000000"><font size="4" style="font-size: 14pt"><span style="background: #ffffff">Full
|
||||
sample of printing contents</span></font></font><font color="#000000"><span style="background: #ffffff">
|
||||
of </span></font><span style="font-variant: normal"><font color="#000000"><font size="4" style="font-size: 14pt"><span style="font-style: normal"><span style="background: #ffffff"><a href="#BatchCompletionState">BatchCompletionState</a>
|
||||
is in print_cs() function in sample 11.batch.cpp.</span></span></font></font></span></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt"><span style="background: #ffffff">If
|
||||
for some reason you want to make batch buffers empty not executing it
|
||||
(i.e. prepare for new portion of messages to process) use cancel()
|
||||
method:</span></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i><span style="background: #ffffff">batch->cancel(&status);</span></i></font></p>
|
||||
<p style="margin-bottom: 0cm; font-variant: normal; font-style: normal">
|
||||
<font size="4" style="font-size: 14pt"><span style="background: #ffffff">Being
|
||||
reference counted Batch does not have special method to close it –
|
||||
standard release() call:</span></font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i><span style="background: #ffffff">batch->release();</span></i></font></p>
|
||||
<p style="margin-bottom: 0cm; font-variant: normal; font-style: normal">
|
||||
<font size="4" style="font-size: 14pt"><span style="background: #ffffff">Described
|
||||
methods help to implement all what one needs for JDBC-style prepared
|
||||
statement batch operations. </span></font>
|
||||
</p>
|
||||
<p><br/>
|
||||
<br/>
|
||||
|
||||
</p>
|
||||
<p><font size="4" style="font-size: 14pt">One can add more than
|
||||
single message in one call to the batch. When doing it please
|
||||
remember – messages should be appropriately aligned for this
|
||||
feature to work correctly. Required alignment and aligned size of the
|
||||
message should be obtained from <a href="#MessageMetadata">MessageMetadata</a>
|
||||
interface, for example:</font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>unsigned aligned =
|
||||
meta->getAlignedLength(&status);</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt">Later that size will be
|
||||
useful when allocating an array of messages and working with it:</font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>unsigned char* data =
|
||||
new unsigned char[aligned * N]; // N is desired number of messages</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>for (int n = 0; n <
|
||||
N; ++n) fillNextMessage(&data[aligned * n], meta);</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>batch->add(&status,
|
||||
N, data);</i></font></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt">After
|
||||
it batch may be executed or next portion of messages added to it.</font></p>
|
||||
<p><br/>
|
||||
<br/>
|
||||
|
||||
</p>
|
||||
<p><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><span style="font-style: normal">Blobs
|
||||
in general are not compatible with batches – batch is efficient
|
||||
when one needs to pass a lot of small data to the server in single
|
||||
step, blobs are treated as large objects and therefore in general it
|
||||
makes no sense to use them in batches. But on practice it often
|
||||
happens that blobs are not too big – and in this case use of
|
||||
traditional blob API (create blob, pass segments to the server, close
|
||||
blob, pass blobs ID in the message) kills performance, specially when
|
||||
used over WAN. Therefore in firebird batch supports passing blobs to
|
||||
server inline, together with other messages. To use that feature
|
||||
first of all <a href="#Batch_Blob_Policy">blob usage policy</a> for a
|
||||
batch to be created should be set (as an option in <a href="#Batch_PB">parameters
|
||||
block</a>):</span></font></span></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>pb->insertInt(&status,
|
||||
IBatch::BLOB_IDS, IBatch::BLOB_IDS_ENGINE);</i></font></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt">In
|
||||
this example temporal blob IDs needed to keep a link between blob and
|
||||
a message where it’s used will be generated by firebird engine –
|
||||
that’s the simplest and rather common usage. Imagine that the
|
||||
message is described as follows:</font></p>
|
||||
<p style="font-variant: normal"><font size="4" style="font-size: 14pt"><i>FB_MESSAGE(Msg,
|
||||
ThrowStatusWrapper,</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>(FB_VARCHAR(5), id)</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>(FB_VARCHAR(10), name)</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>(FB_BLOB, desc)</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>) project(&status,
|
||||
master);</i></font></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt">In
|
||||
that case to send a message containing blob to the server one can do
|
||||
something like this:</font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>project->id =
|
||||
++idCounter;</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>project->name.set(currentName);</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>batch->addBlob(&status,
|
||||
descriptionSize, descriptionText, &project->desc);</i></font></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>batch->add(&status,
|
||||
1, project.getData());</i></font></p>
|
||||
<p style="margin-bottom: 0cm; font-variant: normal; font-style: normal">
|
||||
<font size="4" style="font-size: 14pt">If some blob happened to be
|
||||
big enough not to fit into your existing buffer you may instead
|
||||
reallocating buffer use appendBlobData() method. It appends more data
|
||||
to last added blob. </font>
|
||||
</p>
|
||||
<p style="font-variant: normal"><font size="4" style="font-size: 14pt"><i>batch->addBlob(&status,
|
||||
descriptionSize, descriptionText, &project→desc, bpbLength,
|
||||
bpb);</i></font></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt">After
|
||||
adding first part of blob get next portion of data into
|
||||
descriptionText, update descriptionSize and:</font></p>
|
||||
<p style="font-variant: normal"><font size="4" style="font-size: 14pt"><i>batch->appendBlobData(&status,
|
||||
descriptionSize, descriptionText);</i></font></p>
|
||||
<p><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><span style="font-style: normal">This
|
||||
may be done in a loop but take care not to overflow internal batch
|
||||
buffers – it’s size is controlled by <a href="#Batch_PB">BUFFER_BYTES_SIZE</a>
|
||||
option when creating batch interface but can’t exceed 40Mb (default
|
||||
is 10Mb). If you need to process such big blob (for example on the
|
||||
background of a lot of small one – this can explain use of batch)
|
||||
just use standard blob API and <a href="#Batch::registerBlob">registerBlob</a>
|
||||
method of <a href="#Batch">Batch</a> interface.</span></font></span></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt">One
|
||||
more possible choice of blob policy is BLOB_IDS_USER. Usage at the
|
||||
first look does not change much – before calling addBlob() correct
|
||||
and unique per batch execution ID should be placed to the memory
|
||||
referenced by last parameter. Certainly same ID should be passed in
|
||||
the data message to the blob. Taking into an account that generation
|
||||
of blob IDs by engine is very fast such policy may seem useless but
|
||||
imagine a case when you get blobs and other data in relatively
|
||||
independent streams (blocks in a file for example) and some good IDs
|
||||
are already present in them. In such case use of user-supplied blob
|
||||
IDs can greatly simplify your code.</font></p>
|
||||
<p><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><span style="font-style: normal">Please
|
||||
take into an account – unlike blobs created using regular
|
||||
<a href="#Attachment">createBlob</a>() blobs created by <a href="#Batch">Batch</a>
|
||||
interface are by default stream, not segmented. Segmented blobs
|
||||
provide nothing interesting compared with stream one and therefore
|
||||
not recommended to be used in new development, we support that format
|
||||
only for backward compatibility reasons. If you really need segmented
|
||||
blobs this default may be overridden by calling:</span></font></span></p>
|
||||
<p><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><i>batch->setDefaultBpb(&status,</i></font></span><span style="font-variant: normal">
|
||||
</span><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><i>bpbLength,
|
||||
bpb);</i></font></span></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt">Certainly
|
||||
passed BPB may contain any other blob creation parameters too. As you
|
||||
may have already noticed you may also pass BPB directly to addBlob()
|
||||
but if most of blobs you are going to add have same non-default
|
||||
format use of setDefaultBpb() is slightly more efficient. Returning
|
||||
to segmented blobs – call to addBlob() will add first segment to
|
||||
the blob, following calls to appendBlobData() will add more segments.
|
||||
Do not forget that segment size is limited to 64Kb – 1, an attempt
|
||||
to pass more data in a single call with cause an error.</font></p>
|
||||
<p><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><span style="font-style: normal">Next
|
||||
step when working with existing blob streams is use of
|
||||
addBlobStream() method. Using it one can add more than one blob to
|
||||
the batch per single call. Blob stream is a sequence of blobs, each
|
||||
starts with blob header. Header should be appropriately aligned -
|
||||
<a href="#Batch">Batch</a> interface provides special call for this
|
||||
purpose:</span></font></span></p>
|
||||
<p style="font-variant: normal"><font size="4" style="font-size: 14pt"><i>unsigned
|
||||
alignment = batch->getBlobAlignment(&status);</i></font></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt">It’s
|
||||
supposed that all components of blob stream in a batch should be
|
||||
aligned at least at alignment boundary, including size of stream
|
||||
potions passed to addBlobStream() which should be a multiple of this
|
||||
alignment. Header contains 3 fields – 8-byte blob ID (must be
|
||||
non-zero), 4-byte total blob size and 4 byte BPB size. Total blob
|
||||
size includes BPB inside, i.e. one will always find next blob in the
|
||||
stream in blob-size bytes after the header (taking into account the
|
||||
alignment). BPB (if present, i.e. if BPB size is not zero) is placed
|
||||
right after the header. After BPB blob data goes, it’s format
|
||||
depends upon blob type – stream or segmented. In case of stream
|
||||
blob it’s a plain sequence of bytes having size blob-size –
|
||||
BPB-size. With segmented blob things are a bit more compicated: blob
|
||||
data is a set of segments where each segment has the following format
|
||||
– 2-bytes size of segment (this should be aligned at
|
||||
IBatch::BLOB_SEGHDR_ALIGN boundary) followed by stored in this 2
|
||||
bytes number of bytes.</font></p>
|
||||
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt">When
|
||||
big blob is added to the stream it’s size is not always known in
|
||||
advance. In order not to have too big buffer for that blob (remember,
|
||||
size should be provided in blob header, before blob data) blob
|
||||
continuation record may be used. In blob header you leave blob size
|
||||
at a value known when creating that header and add continuation
|
||||
record that has format absolutely same as blob header, but here blob
|
||||
ID must be zero and BPB size must always be zero too. Typically you
|
||||
will want to have one continuation record per addBlobStream() call.</font></p>
|
||||
<p><a name="Batch::registerBlob"></a><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><span style="font-style: normal">Last
|
||||
method used to work with blobs stands alone from the first three that
|
||||
pass blob data inline with the rest of batch data </span></font></span><span style="font-variant: normal">–
|
||||
</span><span style="font-variant: normal"><font size="4" style="font-size: 14pt"><span style="font-style: normal">it’s
|
||||
needed to register in a batch ID of a blob created using standard
|
||||
blob API. This may be unavoidable if one needs to pass to a batch
|
||||
really big blob. Do not use ID of such blob in batch directly –
|
||||
that will cause invalid blob ID error during batch execution. Instead
|
||||
do:</span></font></span></p>
|
||||
<p><font size="4" style="font-size: 14pt"><i>batch->registerBlob(&status,
|
||||
&realId, &msg->desc);</i></font></p>
|
||||
<p style="margin-bottom: 0cm; font-variant: normal; font-style: normal">
|
||||
<font size="4" style="font-size: 14pt">If blob policy makes firebird
|
||||
engine generate blob IDs this code is enough to correctly register
|
||||
existing blob in a batch. In other cases you will have to assign
|
||||
correct (from batch POV) ID to msg->desc.</font></p>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
</p>
|
||||
<p style="margin-bottom: 0cm; font-variant: normal; font-style: normal">
|
||||
<font size="4" style="font-size: 14pt">Almost all mentioned methods
|
||||
are used in 11.batch.cpp – please use it to see an alive sample of
|
||||
batching in firebird.</font></p>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
</p>
|
||||
<h1><font size="4" style="font-size: 14pt">Working with events.</font></h1>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">Events
|
||||
@ -1469,6 +1795,16 @@ interface – replaces isc_db_handle:</font></p>
|
||||
cursorFlags is needed to open bidirectional cursor setting it's
|
||||
value to Istatement::CURSOR_TYPE_SCROLLABLE.</font></p>
|
||||
<li/>
|
||||
<p><font size="4" style="font-size: 14pt">I</font><font face="Liberation Serif, serif"><font size="4" style="font-size: 14pt">Batch*
|
||||
createBatch(StatusType* status, ITransaction* transaction, unsigned
|
||||
stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata*
|
||||
inMetadata, unsigned</font></font> <font face="Liberation Serif, serif"><font size="4" style="font-size: 14pt">parLength,
|
||||
const unsigned char* par) – prepares sqlStmt and creates <a href="#Batch">Batch</a>
|
||||
interface ready to accept multiple sets of input parameters in
|
||||
inMetadata format. Leaving inMetadata</font></font> <font face="Liberation Serif, serif"><font size="4" style="font-size: 14pt">NULL
|
||||
makes batch use default format for sqlStmt. Parameters block may be
|
||||
passed to createBatch() making it possible to adjust batch behavior.</font></font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">IEvents*
|
||||
queEvents(StatusType* status, IEventCallback* callback, unsigned
|
||||
length, const unsigned char* events) – replaces isc_que_events()
|
||||
@ -1494,13 +1830,177 @@ interface – replaces isc_db_handle:</font></p>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
</p>
|
||||
<p style="margin-bottom: 0cm"><a name="Batch"></a><font size="4" style="font-size: 14pt">Batch
|
||||
interface – makes it possible to process multiple sets of
|
||||
parameters in single statement execution.</font></p>
|
||||
<ol>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
add(StatusType* status, unsigned</font> <font size="4" style="font-size: 14pt">count,
|
||||
const void* inBuffer) – adds count messages from inBuffer to the
|
||||
batch. Total size of messages that can be added to the batch is
|
||||
limited by BUFFER_BYTES_SIZE <a href="#Batch_PB">parameter</a> of
|
||||
batch creation.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
addBlob(StatusType* status, unsigned</font> <font size="4" style="font-size: 14pt">length,
|
||||
const void* inBuffer, ISC_QUAD* blobId, </font><font size="4" style="font-size: 14pt">unsigned
|
||||
bpbLength, const unsigned char* bpb</font><font size="4" style="font-size: 14pt">)
|
||||
– adds single blob having length bytes from inBuffer to the batch,
|
||||
blob identifier is located at blobId address. </font><font size="4" style="font-size: 14pt">If
|
||||
blob should be created with non-default parameters BPB may be passed
|
||||
(format matches one used in <a href="#Attachment">Attachment</a>::createBlob).</font><font size="4" style="font-size: 14pt">Total
|
||||
size of inline blobs that can be added to the batch </font><font size="4" style="font-size: 14pt">(including
|
||||
optional BPBs, blob headers, segment sizes and taking into an
|
||||
accoount alignment) </font><font size="4" style="font-size: 14pt">is
|
||||
limited by BUFFER_BYTES_SIZE <a href="#Batch_PB">parameter</a> of
|
||||
batch creation (affects all blob-oriented methods except
|
||||
registerBlob()). </font>
|
||||
</p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
appendBlobData(StatusType* status, unsigned length, const void*
|
||||
inBuffer) – extend last added blob: append length bytes taken from
|
||||
inBuffer address to it.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
addBlobStream(StatusType* status, unsigned length, const void*
|
||||
inBuffer) – adds blob data (this can be multiple objects or part
|
||||
of single blob) to the batch. Header of each blob in the stream is
|
||||
aligned at getBlobAlignment() boundary and contains 3 fields: first
|
||||
- 8-bytes blob identifier (in ISC_QUAD format), second - 4-bytes
|
||||
length of blob, third – 4-bytes length of BPB. Blob header should
|
||||
not cross boundaries of buffer in this function call. BPB data is
|
||||
placed right after header, blob data goes next. Length of blob
|
||||
includes BPB (if it present). All data may be distributed between
|
||||
multiple addBlobStream() calls. Blob data in turn may be structured
|
||||
in case of segmented blob, see chapter “<a href="#Modifying data in a batch">Modifying
|
||||
data in a batch</a>”.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
registerBlob(StatusType* status, const ISC_QUAD* existingBlob,
|
||||
ISC_QUAD* blobId) – makes it possible to use in batch blobs added
|
||||
using standard <a href="#Blob">Blob</a> interface. This function
|
||||
contains 2 ISC_QUAD* parameters, it’s important not to mix them –
|
||||
second parameter (existingBlob) is a pointer to blob identifier,
|
||||
already added out of batch scope, third (blobId) points to blob
|
||||
identifier that will be placed in a message in this batch.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><a href="#BatchCompletionState">IBatchCompletionState</a>*
|
||||
execute(StatusType* status, ITransaction* transaction) – execute
|
||||
batch with parameters passed to it in the messages. If parameter
|
||||
MULTIERROR is not set in <a href="#Batch_PB">parameters block</a>
|
||||
when creating the batch execution will be stopped after first error,
|
||||
in MULTIERROR mode an unlimited number of errors can happen, after
|
||||
an error execution is continued from the next message. This function
|
||||
returns BatchCompletionState interface that contains all requested
|
||||
nformation about the results of batch execution.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
cancel(StatusType* status) – clear messages and blobs buffers,
|
||||
return batch to a state it had right after creation. Notice –
|
||||
being reference counted interface batch does not contain any special
|
||||
function to close it, please use release() for this purposes. </font>
|
||||
</p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">unsigned
|
||||
getBlobAlignment(StatusType* status) – returns required alignment
|
||||
for the data placed into the buffer of addBlobStream().</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">IMessageMetadata*
|
||||
getMetadata(StatusType* status) – return format of metadata used
|
||||
in batch’s messages.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
setDefaultBpb(StatusType* status, unsigned parLength, const unsigned
|
||||
char* par) – sets BPB which will be used for all blobs missing
|
||||
non-default BPB. Must be called before adding any message or blob to
|
||||
batch.</font></p>
|
||||
</ol>
|
||||
<p style="margin-bottom: 0cm"><a name="Batch_PB"></a><font size="4" style="font-size: 14pt">Tag
|
||||
for parameters block:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">VERSION1</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">Tags
|
||||
for clumplets in parameters block:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">MULTIERROR
|
||||
(0/1) – can have >1 message with errors</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">RECORD_COUNTS
|
||||
(0/1) - per-message modified records accounting</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">BUFFER_BYTES_SIZE
|
||||
(integer) - maximum possible buffer size (default 10Mb, maximum 40Mb)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">BLOB_IDS
|
||||
- <a href="#Batch_Blob_Policy">policy</a> used to store blobs</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">DETAILED_ERRORS
|
||||
(integer) - how many vectors with detailed error info are stored in
|
||||
completion state (default 64, maximum 256)</font></p>
|
||||
<p style="margin-bottom: 0cm"><a name="Batch_Blob_Policy"></a><font size="4" style="font-size: 14pt">Policies
|
||||
used to store blobs:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">BLOB_IDS_NONE
|
||||
– inline blobs can't be used (registerBlob() works anyway)</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">BLOB_IDS_ENGINE
|
||||
- blobs are added one by one, IDs are generated by firebird engine</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">BLOB_IDS_USER
|
||||
- blobs are added one by one, IDs are generated by user</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">BLOB_IDS_STREAM
|
||||
- blobs are added in a stream, IDs are generated by user</font></p>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
</p>
|
||||
<p style="margin-bottom: 0cm"><a name="BatchCompletionState"></a><font size="4" style="font-size: 14pt">BatchCompletionState
|
||||
– disposable interface, always returned by execute() method of
|
||||
<a href="#Batch">Batch</a> interface. It contains more or less
|
||||
(depending upon parameters passed when <a href="#Batch">Batch</a> was
|
||||
created) detailed information about the results of batch execution.</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">{</font></p>
|
||||
<ol>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">uint
|
||||
getSize(StatusType* status) – returns the total number of
|
||||
processed messages.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">int
|
||||
getState(StatusType* status, uint pos) – returns the result of
|
||||
execution of message number ‘pos’. On any error with the message
|
||||
this is EXECUTE_FAILED constant, value returned on success depends
|
||||
upon presence of RECORD_COUNTS <a href="#Batch_PB">parameter</a> of
|
||||
batch creation. When it present and has non-zero value number of
|
||||
records inserted, updated or deleted during particular message
|
||||
processing is returned, else SUCCESS_NO_INFO constant is returned.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">uint
|
||||
findError(StatusType* status, uint pos) – finds next (starting
|
||||
with pos) message which processing caused an error. When such
|
||||
message is missing NO_MORE_ERRORS constant is returned. Number of
|
||||
status vectors, returned in this interface, is limited by the value
|
||||
of DETAILED_ERRORS <a href="#Batch_PB">parameter</a> of batch
|
||||
creation.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
getStatus(StatusType* status, IStatus* to, uint pos) – returns
|
||||
detailed information (full status vector) about an error that took
|
||||
place when processing ‘pos’ message. In order to distinguish
|
||||
between errors (in <a href="#Batch">Batch</a>::execute() or in
|
||||
<a href="#BatchCompletionState">BatchCompletionState</a>::getStatus())
|
||||
that status is returned in separate ‘to’ parameter unlike errors
|
||||
in this call that are placed into ‘status’ parameter.</font></p>
|
||||
</ol>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">Special
|
||||
values returned by getState():</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">EXECUTE_FAILED
|
||||
- error happened when processing this message</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">SUCCESS_NO_INFO
|
||||
- record update info was not collected</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">Special
|
||||
value returned by findError():</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">NO_MORE_ERRORS
|
||||
– no more messages with errors in this batch</font></p>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
</p>
|
||||
<p style="margin-bottom: 0cm"><a name="Blob"></a><font size="4" style="font-size: 14pt">Blob
|
||||
interface – replaces isc_blob_handle:</font></p>
|
||||
<ol>
|
||||
<li/>
|
||||
<li value="1"/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
getInfo(StatusType* status, unsigned itemsLength, const unsigned
|
||||
char* items, unsigned bufferLength, unsigned char* buffer) –
|
||||
@ -1881,7 +2381,17 @@ with execution of SQL statements.</font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">unsigned
|
||||
getMessageLength(StatusType* status) - returns length of message
|
||||
buffer (use it to allocate memory for the buffer).</font></p>
|
||||
buffer (use it to allocate memory for the buffer).</font></font></p>
|
||||
<li/>
|
||||
<p><font face="Liberation Serif, serif"><font size="4" style="font-size: 14pt"><span style="background: #ffffff">unsigned
|
||||
getAlignment(StatusType* status) – returns alignment required for
|
||||
message buffer.</span></font></font></p>
|
||||
<li/>
|
||||
<p><font face="Liberation Serif, serif"><font size="4" style="font-size: 14pt">unsigned</font></font>
|
||||
<font face="Liberation Serif, serif"><font size="4" style="font-size: 14pt">getAlignedLength(StatusType*
|
||||
status) – returns length of message buffer taking into an account
|
||||
alignment requirements (use it to allocate memory for an array of
|
||||
buffers and navigate through that array).</font></font></p>
|
||||
</ol>
|
||||
<p style="margin-bottom: 0cm"><br/>
|
||||
|
||||
@ -2270,21 +2780,31 @@ interface – replaces (partially) isc_stmt_handle.</font></p>
|
||||
outMetadata, void* outBuffer) – executes any SQL statement except
|
||||
returning multiple rows of data. Partial analogue of
|
||||
isc_dsql_execute2() - in and out XSLQDAs replaced with input and
|
||||
output messages with appropriate buffers.</font></p>
|
||||
output messages with appropriate buffers.</font></font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">IResultSet*
|
||||
<p style="margin-bottom: 0cm"><font face="Liberation Serif, serif"><font size="4" style="font-size: 14pt">IResultSet*
|
||||
openCursor(StatusType* status, ITransaction* transaction,
|
||||
IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata*
|
||||
outMetadata, unsigned flags) – executes SQL statement potentially
|
||||
returning multiple rows of data. Returns ResultSet interface which
|
||||
should be used to fetch that data. Format of output data is defined
|
||||
by outMetadata parameter, leaving it NULL default format may be
|
||||
used. Parameter flags is needed to open bidirectional cursor setting
|
||||
it's value to Istatement::CURSOR_TYPE_SCROLLABLE.</font></p>
|
||||
returning multiple rows of data. Returns <a href="#ResultSet">ResultSet</a>
|
||||
interface which should be used to fetch that data. Format of output
|
||||
data is defined by outMetadata parameter, leaving it NULL default
|
||||
format may be used. Parameter flags is needed to open bidirectional
|
||||
cursor setting it's value to Istatement::CURSOR_TYPE_SCROLLABLE.</font></font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
<p style="margin-bottom: 0cm"><font face="Liberation Serif, serif"><font size="4" style="font-size: 14pt">IBatch*
|
||||
createBatch(StatusType* status, IMessageMetadata* inMetadata, uint
|
||||
parLength, const uchar* par) – creates <a href="#Batch">Batch</a>
|
||||
interface to SQL statement with input parameters making it possible
|
||||
to execute that statement with multiple sets of parameters. Format
|
||||
of input data is defined by inMetadata parameter, leaving it NULL
|
||||
makes batch use default format from this interface. Parameters block
|
||||
may be passed to createBatch() making it possible to adjust batch
|
||||
behavior.</font></font></p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font face="Liberation Serif, serif"><font size="4" style="font-size: 14pt">void
|
||||
setCursorName(StatusType* status, const char* name) – replaces
|
||||
isc_dsql_set_cursor_name(). </font>
|
||||
isc_dsql_set_cursor_name(). </font></font>
|
||||
</p>
|
||||
<li/>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">void
|
||||
@ -2710,6 +3230,7 @@ defined by XpbBuilder interface:</font></p>
|
||||
</p>
|
||||
<p style="margin-bottom: 0cm"><a name="Valid builder types"></a><font size="4" style="font-size: 14pt">Valid
|
||||
builder types:</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">BATCH</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">DPB</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">SPB_ATTACH</font></p>
|
||||
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt">SPB_START</font></p>
|
||||
|
510
examples/interfaces/11.batch.cpp
Normal file
510
examples/interfaces/11.batch.cpp
Normal file
@ -0,0 +1,510 @@
|
||||
/*
|
||||
* PROGRAM: Object oriented API samples.
|
||||
* MODULE: 11.batch.cpp
|
||||
* DESCRIPTION: A trivial sample of using Batch interface.
|
||||
*
|
||||
* Example for the following interfaces:
|
||||
* IBatch - interface to work with FB pipes
|
||||
*
|
||||
* c++ 11.batch.cpp -lfbclient
|
||||
*
|
||||
* 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 Alexander Peshkoff
|
||||
* for the Firebird Open Source RDBMS project.
|
||||
*
|
||||
* Copyright (c) 2017 Alexander Peshkoff <peshkoff@mail.ru>
|
||||
* and all contributors signed below.
|
||||
*
|
||||
* All Rights Reserved.
|
||||
* Contributor(s): ______________________________________.
|
||||
*/
|
||||
|
||||
#include "ifaceExamples.h"
|
||||
#include <firebird/Message.h>
|
||||
|
||||
static IMaster* master = fb_get_master_interface();
|
||||
|
||||
|
||||
// output error message to user
|
||||
|
||||
static void errPrint(IStatus* status)
|
||||
{
|
||||
char buf[256];
|
||||
master->getUtilInterface()->formatStatus(buf, sizeof(buf), status);
|
||||
fprintf(stderr, "%s\n", buf);
|
||||
}
|
||||
|
||||
|
||||
// align target to alignment boundary
|
||||
|
||||
template <typename T>
|
||||
static inline T align(T target, uintptr_t alignment)
|
||||
{
|
||||
return (T) ((((uintptr_t) target) + alignment - 1) & ~(alignment - 1));
|
||||
}
|
||||
|
||||
|
||||
// append given message to buffer ptr
|
||||
|
||||
static void putMsg(unsigned char*& ptr, const void* from, unsigned size, unsigned alignment)
|
||||
{
|
||||
memcpy(ptr, from, size);
|
||||
ptr += align(size, alignment);
|
||||
}
|
||||
|
||||
|
||||
// append blob header with BPB to buffer ptr
|
||||
// return pointer to blob size field - prefilled with BPB size
|
||||
|
||||
static unsigned* putBlobHdr(unsigned char*& ptr, unsigned alignment, ISC_QUAD* id, unsigned bpbSize, const unsigned char* bpb)
|
||||
{
|
||||
ptr = align(ptr, alignment);
|
||||
|
||||
memcpy(ptr, id, sizeof(ISC_QUAD));
|
||||
ptr += sizeof(ISC_QUAD);
|
||||
|
||||
unsigned* rc = reinterpret_cast<unsigned*>(ptr);
|
||||
|
||||
memcpy(ptr, &bpbSize, sizeof(unsigned));
|
||||
ptr += sizeof(unsigned);
|
||||
memcpy(ptr, &bpbSize, sizeof(unsigned));
|
||||
ptr += sizeof(unsigned);
|
||||
|
||||
memcpy(ptr, bpb, bpbSize);
|
||||
ptr += bpbSize;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
// append given blob to buffer ptr
|
||||
|
||||
static void putBlob(unsigned char*& ptr, const void* from, unsigned size, unsigned alignment, ISC_QUAD* id)
|
||||
{
|
||||
unsigned* sizePtr = putBlobHdr(ptr, alignment, id, 0, NULL);
|
||||
memcpy(ptr, from, size);
|
||||
*sizePtr += size;
|
||||
ptr += size;
|
||||
|
||||
ptr = align(ptr, alignment);
|
||||
}
|
||||
|
||||
|
||||
// append given segment to buffer ptr
|
||||
|
||||
unsigned putSegment(unsigned char*& ptr, const char* testData)
|
||||
{
|
||||
ptr = align(ptr, IBatch::BLOB_SEGHDR_ALIGN);
|
||||
unsigned short l = strlen(testData);
|
||||
memcpy(ptr, &l, sizeof l);
|
||||
ptr += sizeof l;
|
||||
memcpy(ptr, testData, l);
|
||||
ptr += l;
|
||||
return align(l + sizeof l, IBatch::BLOB_SEGHDR_ALIGN);
|
||||
}
|
||||
|
||||
// BatchCompletionState printer - prints all what we know about completed batch
|
||||
|
||||
static void print_cs(ThrowStatusWrapper& status, IBatchCompletionState* cs, IUtil* utl)
|
||||
{
|
||||
unsigned p = 0;
|
||||
IStatus* s2 = NULL;
|
||||
bool pr1 = false, pr2 = false;
|
||||
|
||||
// 1. Print per-message state info
|
||||
|
||||
unsigned upcount = cs->getSize(&status);
|
||||
unsigned unk = 0, succ = 0;
|
||||
for (p = 0; p < upcount; ++p)
|
||||
{
|
||||
int s = cs->getState(&status, p);
|
||||
switch (s)
|
||||
{
|
||||
case IBatchCompletionState::EXECUTE_FAILED:
|
||||
if (!pr1)
|
||||
{
|
||||
printf("Message Status\n", p);
|
||||
pr1 = true;
|
||||
}
|
||||
printf("%5u Execute failed\n", p);
|
||||
break;
|
||||
|
||||
case IBatchCompletionState::SUCCESS_NO_INFO:
|
||||
++unk;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!pr1)
|
||||
{
|
||||
printf("Message Status\n", p);
|
||||
pr1 = true;
|
||||
}
|
||||
printf("%5u Updated %d record(s)\n", p, s);
|
||||
++succ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("Summary: total=%u success=%u success(but no update info)=%u\n", upcount, succ, unk);
|
||||
|
||||
// 2. Print detailed errors (if exist) for messages
|
||||
|
||||
s2 = master->getStatus();
|
||||
for(p = 0; (p = cs->findError(&status, p)) != IBatchCompletionState::NO_MORE_ERRORS; ++p)
|
||||
{
|
||||
try
|
||||
{
|
||||
cs->getStatus(&status, s2, p);
|
||||
|
||||
char text[1024];
|
||||
utl->formatStatus(text, sizeof(text) - 1, s2);
|
||||
text[sizeof(text) - 1] = 0;
|
||||
if (!pr2)
|
||||
{
|
||||
printf("\nDetailed errors status:\n", p);
|
||||
pr2 = true;
|
||||
}
|
||||
printf("Message %u: %s\n", p, text);
|
||||
}
|
||||
catch (const FbException& error)
|
||||
{
|
||||
// handle error
|
||||
fprintf(stderr, "\nError describing message %u\n", p);
|
||||
errPrint(error.getStatus());
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (s2)
|
||||
s2->dispose();
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
// set default password if none specified in environment
|
||||
setenv("ISC_USER", "sysdba", 0);
|
||||
setenv("ISC_PASSWORD", "masterkey", 0);
|
||||
|
||||
// With ThrowStatusWrapper passed as status interface FbException will be thrown on error
|
||||
ThrowStatusWrapper status(master->getStatus());
|
||||
|
||||
// Declare pointers to required interfaces
|
||||
IProvider* prov = master->getDispatcher();
|
||||
IUtil* utl = master->getUtilInterface();
|
||||
IAttachment* att = NULL;
|
||||
ITransaction* tra = NULL;
|
||||
IBatch* batch = NULL;
|
||||
IBatchCompletionState* cs = NULL;
|
||||
IXpbBuilder* pb = NULL;
|
||||
|
||||
unsigned char streamBuf[10240]; // big enough for demo
|
||||
unsigned char* stream = NULL;
|
||||
|
||||
try
|
||||
{
|
||||
// attach employee db
|
||||
att = prov->attachDatabase(&status, "employee", 0, NULL);
|
||||
tra = att->startTransaction(&status, 0, NULL);
|
||||
|
||||
// cleanup
|
||||
att->execute(&status, tra, 0, "delete from project where proj_id like 'BAT%'", SAMPLES_DIALECT,
|
||||
NULL, NULL, NULL, NULL);
|
||||
|
||||
//
|
||||
printf("\nPart 1. Simple messages. Adding one by one or by groups of messages.\n");
|
||||
//
|
||||
|
||||
// Message to store in a table
|
||||
FB_MESSAGE(Msg1, ThrowStatusWrapper,
|
||||
(FB_VARCHAR(5), id)
|
||||
(FB_VARCHAR(10), name)
|
||||
) project1(&status, master);
|
||||
project1.clear();
|
||||
IMessageMetadata* meta = project1.getMetadata();
|
||||
|
||||
// sizes & alignments
|
||||
unsigned mesAlign = meta->getAlignment(&status);
|
||||
unsigned mesLength = meta->getMessageLength(&status);
|
||||
unsigned char* streamStart = align(streamBuf, mesAlign);
|
||||
|
||||
// set batch parameters
|
||||
pb = utl->getXpbBuilder(&status, IXpbBuilder::BATCH, NULL, 0);
|
||||
// collect per-message statistics
|
||||
pb->insertInt(&status, IBatch::TAG_RECORD_COUNTS, 1);
|
||||
|
||||
// create batch
|
||||
const char* sqlStmt1 = "insert into project(proj_id, proj_name) values(?, ?)";
|
||||
batch = att->createBatch(&status, tra, 0, sqlStmt1, SAMPLES_DIALECT, meta,
|
||||
pb->getBufferLength(&status), pb->getBuffer(&status));
|
||||
|
||||
// fill batch with data record by record
|
||||
project1->id.set("BAT11");
|
||||
project1->name.set("SNGL_REC");
|
||||
batch->add(&status, 1, project1.getData());
|
||||
|
||||
project1->id.set("BAT12");
|
||||
project1->name.set("SNGL_REC2");
|
||||
batch->add(&status, 1, project1.getData());
|
||||
|
||||
// execute it
|
||||
cs = batch->execute(&status, tra);
|
||||
print_cs(status, cs, utl);
|
||||
|
||||
// fill batch with data using many records at once
|
||||
stream = streamStart;
|
||||
|
||||
project1->id.set("BAT13");
|
||||
project1->name.set("STRM_REC_A");
|
||||
putMsg(stream, project1.getData(), mesLength, mesAlign);
|
||||
|
||||
project1->id.set("BAT14");
|
||||
project1->name.set("STRM_REC_B");
|
||||
putMsg(stream, project1.getData(), mesLength, mesAlign);
|
||||
|
||||
project1->id.set("BAT15");
|
||||
project1->name.set("STRM_REC_C");
|
||||
putMsg(stream, project1.getData(), mesLength, mesAlign);
|
||||
|
||||
batch->add(&status, 3, streamStart);
|
||||
|
||||
stream = streamStart;
|
||||
|
||||
project1->id.set("BAT15"); // constraint violation
|
||||
project1->name.set("STRM_REC_D");
|
||||
putMsg(stream, project1.getData(), mesLength, mesAlign);
|
||||
|
||||
project1->id.set("BAT16");
|
||||
project1->name.set("STRM_REC_E");
|
||||
putMsg(stream, project1.getData(), mesLength, mesAlign);
|
||||
|
||||
batch->add(&status, 1, streamStart);
|
||||
|
||||
// execute it
|
||||
cs = batch->execute(&status, tra);
|
||||
print_cs(status, cs, utl);
|
||||
|
||||
// close batch
|
||||
batch->release();
|
||||
batch = NULL;
|
||||
|
||||
//
|
||||
printf("\nPart 2. Simple BLOBs. Multiple errors return.\n");
|
||||
//
|
||||
|
||||
// Message to store in a table
|
||||
FB_MESSAGE(Msg2, ThrowStatusWrapper,
|
||||
(FB_VARCHAR(5), id)
|
||||
(FB_VARCHAR(10), name)
|
||||
(FB_BLOB, desc)
|
||||
) project2(&status, master);
|
||||
project2.clear();
|
||||
meta = project2.getMetadata();
|
||||
|
||||
mesAlign = meta->getAlignment(&status);
|
||||
mesLength = meta->getMessageLength(&status);
|
||||
streamStart = align(streamBuf, mesAlign);
|
||||
|
||||
// set batch parameters
|
||||
pb->clear(&status);
|
||||
// continue batch processing in case of errors in some messages
|
||||
pb->insertInt(&status, IBatch::TAG_MULTIERROR, 1);
|
||||
// enable blobs processing - IDs generated by firebird engine
|
||||
pb->insertInt(&status, IBatch::TAG_BLOB_POLICY, IBatch::BLOB_ID_ENGINE);
|
||||
|
||||
// create batch
|
||||
const char* sqlStmt2 = "insert into project(proj_id, proj_name, proj_desc) values(?, ?, ?)";
|
||||
batch = att->createBatch(&status, tra, 0, sqlStmt2, SAMPLES_DIALECT, meta,
|
||||
pb->getBufferLength(&status), pb->getBuffer(&status));
|
||||
|
||||
// fill batch with data
|
||||
project2->id.set("BAT21");
|
||||
project2->name.set("SNGL_BLOB");
|
||||
batch->addBlob(&status, strlen(sqlStmt2), sqlStmt2, &project2->desc, 0, NULL);
|
||||
batch->appendBlobData(&status, 1, "\n");
|
||||
batch->appendBlobData(&status, strlen(sqlStmt1), sqlStmt1);
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
// execute it
|
||||
cs = batch->execute(&status, tra);
|
||||
print_cs(status, cs, utl);
|
||||
|
||||
// fill batch with data
|
||||
project2->id.set("BAT22");
|
||||
project2->name.set("SNGL_REC1");
|
||||
batch->addBlob(&status, strlen(sqlStmt2), sqlStmt2, &project2->desc, 0, NULL);
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
project2->id.set("BAT22");
|
||||
project2->name.set("SNGL_REC2"); // constraint violation
|
||||
batch->addBlob(&status, 2, "r2", &project2->desc, 0, NULL);
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
project2->id.set("BAT23");
|
||||
project2->name.set("SNGL_REC3");
|
||||
batch->addBlob(&status, 2, "r3", &project2->desc, 0, NULL);
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
project2->id.set("BAT23"); // constraint violation
|
||||
project2->name.set("SNGL_REC4");
|
||||
batch->addBlob(&status, 2, "r4", &project2->desc, 0, NULL);
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
// execute it
|
||||
cs = batch->execute(&status, tra);
|
||||
print_cs(status, cs, utl);
|
||||
|
||||
// close batch
|
||||
batch->release();
|
||||
batch = NULL;
|
||||
|
||||
//
|
||||
printf("\nPart 3. BLOB stream, including segmented BLOB.\n");
|
||||
//
|
||||
|
||||
// use Msg2/project2/sqlStmt2 to store in a table
|
||||
|
||||
// set batch parameters
|
||||
pb->clear(&status);
|
||||
// enable blobs processing - blobs are placed in a stream
|
||||
pb->insertInt(&status, IBatch::TAG_BLOB_POLICY, IBatch::BLOB_STREAM);
|
||||
|
||||
// create batch
|
||||
batch = att->createBatch(&status, tra, 0, sqlStmt2, SAMPLES_DIALECT, meta,
|
||||
pb->getBufferLength(&status), pb->getBuffer(&status));
|
||||
|
||||
unsigned blobAlign = batch->getBlobAlignment(&status);
|
||||
|
||||
// prepare blob IDs
|
||||
ISC_QUAD v1={0,1}, v2={0,2}, v3={0,3};
|
||||
|
||||
// send messages to batch
|
||||
project2->id.set("BAT31");
|
||||
project2->name.set("STRM_BLB_A");
|
||||
project2->desc = v1;
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
project2->id.set("BAT32");
|
||||
project2->name.set("STRM_BLB_B");
|
||||
project2->desc = v2;
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
project2->id.set("BAT33");
|
||||
project2->name.set("STRM_BLB_C");
|
||||
project2->desc = v3;
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
// prepare blobs in the stream buffer
|
||||
|
||||
const char* d1 = "1111111111111111111";
|
||||
const char* d2 = "22222222222222222222";
|
||||
const char* d3 = "33333333333333333333333333333333333333333333333333333";
|
||||
|
||||
stream = streamStart;
|
||||
putBlob(stream, d1, strlen(d1), blobAlign, &v1);
|
||||
putBlob(stream, d2, strlen(d2), blobAlign, &v2);
|
||||
putBlob(stream, d3, strlen(d3), blobAlign, &v3);
|
||||
|
||||
batch->addBlobStream(&status, stream - streamStart, streamStart);
|
||||
|
||||
// Put segmented Blob in the stream
|
||||
|
||||
// add message
|
||||
ISC_QUAD vSeg={0,10};
|
||||
project2->id.set("BAT35");
|
||||
project2->name.set("STRM_B_SEG");
|
||||
project2->desc = vSeg;
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
// build BPB
|
||||
pb->dispose();
|
||||
pb = NULL;
|
||||
pb = utl->getXpbBuilder(&status, IXpbBuilder::BPB, NULL, 0);
|
||||
pb->insertInt(&status, isc_bpb_type, isc_bpb_type_segmented);
|
||||
|
||||
// make stream
|
||||
stream = streamStart;
|
||||
unsigned* size = putBlobHdr(stream, blobAlign, &vSeg, pb->getBufferLength(&status), pb->getBuffer(&status));
|
||||
*size += putSegment(stream, d1);
|
||||
*size += putSegment(stream, "\n");
|
||||
*size += putSegment(stream, d2);
|
||||
*size += putSegment(stream, "\n");
|
||||
*size += putSegment(stream, d3);
|
||||
|
||||
// add stream to the batch
|
||||
stream = align(stream, blobAlign);
|
||||
batch->addBlobStream(&status, stream - streamStart, streamStart);
|
||||
|
||||
// execute batch
|
||||
cs = batch->execute(&status, tra);
|
||||
print_cs(status, cs, utl);
|
||||
|
||||
//
|
||||
printf("\nPart 4. BLOB created using IBlob interface.\n");
|
||||
//
|
||||
|
||||
// use Msg2/project2/sqlStmt2 to store in a table
|
||||
// registerBlob() may be called in BLOB_STREAM batch, ID should be generated by user in this case
|
||||
// also demonstrates execution of same batch multiple times
|
||||
|
||||
// create blob
|
||||
ISC_QUAD realId;
|
||||
IBlob* blob = att->createBlob(&status, tra, &realId, 0, NULL);
|
||||
const char* text = "Blob created using traditional API";
|
||||
blob->putSegment(&status, strlen(text), text);
|
||||
blob->close(&status);
|
||||
|
||||
// add message
|
||||
project2->id.set("BAT38");
|
||||
project2->name.set("FRGN_BLB");
|
||||
project2->desc = v1; // after execute may reuse IDs
|
||||
batch->registerBlob(&status, &realId, &project2->desc);
|
||||
batch->add(&status, 1, project2.getData());
|
||||
|
||||
// execute it
|
||||
cs = batch->execute(&status, tra);
|
||||
print_cs(status, cs, utl);
|
||||
|
||||
// cleanup
|
||||
batch->release();
|
||||
batch = NULL;
|
||||
tra->commit(&status);
|
||||
tra = NULL;
|
||||
att->detach(&status);
|
||||
att = NULL;
|
||||
}
|
||||
catch (const FbException& error)
|
||||
{
|
||||
// handle error
|
||||
rc = 1;
|
||||
errPrint(error.getStatus());
|
||||
}
|
||||
|
||||
// release interfaces after error caught
|
||||
if (cs)
|
||||
cs->dispose();
|
||||
if (batch)
|
||||
batch->release();
|
||||
if (tra)
|
||||
tra->release();
|
||||
if (att)
|
||||
att->release();
|
||||
|
||||
// cleanup
|
||||
if (pb)
|
||||
pb->dispose();
|
||||
status.dispose();
|
||||
prov->release();
|
||||
|
||||
return rc;
|
||||
}
|
@ -295,21 +295,34 @@ void MsgMetadata::addItem(const MetaName& name, bool nullable, const dsc& desc)
|
||||
// returns ~0 on success or index of not finished item
|
||||
unsigned MsgMetadata::makeOffsets()
|
||||
{
|
||||
length = 0;
|
||||
length = alignedLength = 0;
|
||||
alignment = type_alignments[dtype_short]; // NULL indicator
|
||||
|
||||
for (unsigned n = 0; n < items.getCount(); ++n)
|
||||
{
|
||||
Item* param = &items[n];
|
||||
if (!param->finished)
|
||||
{
|
||||
length = 0;
|
||||
length = alignment = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
unsigned dtype;
|
||||
length = fb_utils::sqlTypeToDsc(length, param->type, param->length,
|
||||
NULL /*dtype*/, NULL /*length*/, ¶m->offset, ¶m->nullInd);
|
||||
&dtype, NULL /*length*/, ¶m->offset, ¶m->nullInd);
|
||||
|
||||
if (dtype >= DTYPE_TYPE_MAX)
|
||||
{
|
||||
length = alignment = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
alignment = MAX(alignment, type_alignments[dtype]);
|
||||
}
|
||||
|
||||
return ~0;
|
||||
alignedLength = FB_ALIGN(length, alignment);
|
||||
|
||||
return ~0u;
|
||||
}
|
||||
|
||||
|
||||
|
@ -99,20 +99,26 @@ public:
|
||||
public:
|
||||
explicit MsgMetadata(MsgMetadata* from)
|
||||
: items(getPool(), from->items),
|
||||
length(from->length)
|
||||
length(from->length),
|
||||
alignment(from->alignment),
|
||||
alignedLength(from->alignedLength)
|
||||
{
|
||||
}
|
||||
|
||||
explicit MsgMetadata(IMessageMetadata* from)
|
||||
: items(getPool()),
|
||||
length(0)
|
||||
length(0),
|
||||
alignment(0),
|
||||
alignedLength(0)
|
||||
{
|
||||
assign(from);
|
||||
}
|
||||
|
||||
MsgMetadata()
|
||||
: items(getPool()),
|
||||
length(0)
|
||||
length(0),
|
||||
alignment(0),
|
||||
alignedLength(0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -266,6 +272,16 @@ public:
|
||||
return length;
|
||||
}
|
||||
|
||||
unsigned getAlignment(CheckStatusWrapper* /*status*/)
|
||||
{
|
||||
return alignment;
|
||||
}
|
||||
|
||||
unsigned getAlignedLength(CheckStatusWrapper* /*status*/)
|
||||
{
|
||||
return alignedLength;
|
||||
}
|
||||
|
||||
public:
|
||||
void addItem(const MetaName& name, bool nullable, const dsc& desc);
|
||||
unsigned makeOffsets();
|
||||
@ -281,7 +297,7 @@ private:
|
||||
|
||||
private:
|
||||
ObjectsArray<Item> items;
|
||||
unsigned length;
|
||||
unsigned length, alignment, alignedLength;
|
||||
};
|
||||
|
||||
//class AttMetadata : public IMessageMetadataBaseImpl<AttMetadata, CheckStatusWrapper, MsgMetadata>
|
||||
|
185
src/common/classes/BatchCompletionState.h
Normal file
185
src/common/classes/BatchCompletionState.h
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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 Alexander Peshkov
|
||||
* for the Firebird Open Source RDBMS project.
|
||||
*
|
||||
* Copyright (c) 2017 Alexander Peshkov <peshkoff@mail.ru>
|
||||
* and all contributors signed below.
|
||||
*
|
||||
* All Rights Reserved.
|
||||
* Contributor(s): ________________________________
|
||||
*/
|
||||
|
||||
#include "firebird.h"
|
||||
|
||||
#include "../common/classes/auto.h"
|
||||
#include "../common/classes/array.h"
|
||||
#include "../common/utils_proto.h"
|
||||
|
||||
namespace Firebird {
|
||||
|
||||
class Transliterate
|
||||
{
|
||||
public:
|
||||
virtual void transliterate(IStatus* status) = 0;
|
||||
};
|
||||
|
||||
class BatchCompletionState FB_FINAL :
|
||||
public DisposeIface<Firebird::IBatchCompletionStateImpl<BatchCompletionState, CheckStatusWrapper> >
|
||||
{
|
||||
public:
|
||||
BatchCompletionState(bool storeCounts, ULONG lim)
|
||||
: rare(getPool()),
|
||||
reccount(0u),
|
||||
detailedLimit(lim)
|
||||
{
|
||||
if (storeCounts)
|
||||
array = FB_NEW_POOL(getPool()) DenseArray(getPool());
|
||||
}
|
||||
|
||||
~BatchCompletionState()
|
||||
{
|
||||
for (unsigned i = 0; i < rare.getCount() && rare[i].second; ++i)
|
||||
rare[i].second->dispose();
|
||||
}
|
||||
|
||||
void dispose()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
void regError(IStatus* errStatus, Transliterate* transliterate)
|
||||
{
|
||||
IStatus* newVector = nullptr;
|
||||
if (rare.getCount() < detailedLimit)
|
||||
{
|
||||
newVector = errStatus->clone();
|
||||
if (transliterate)
|
||||
transliterate->transliterate(newVector);
|
||||
}
|
||||
rare.add(StatusPair(reccount, newVector));
|
||||
|
||||
regUpdate(IBatchCompletionState::EXECUTE_FAILED);
|
||||
}
|
||||
|
||||
void regErrorAt(ULONG at, IStatus* errStatus)
|
||||
{
|
||||
IStatus* newVector = nullptr;
|
||||
if ((rare.getCount() < detailedLimit) && errStatus)
|
||||
newVector = errStatus->clone();
|
||||
rare.add(StatusPair(at, newVector));
|
||||
}
|
||||
|
||||
void regUpdate(SLONG count)
|
||||
{
|
||||
if (array)
|
||||
array->push(count);
|
||||
|
||||
++reccount;
|
||||
}
|
||||
|
||||
void regSize(ULONG total)
|
||||
{
|
||||
reccount = total;
|
||||
}
|
||||
|
||||
// IBatchCompletionState implementation
|
||||
unsigned getSize(CheckStatusWrapper*)
|
||||
{
|
||||
return reccount;
|
||||
}
|
||||
|
||||
int getState(CheckStatusWrapper* status, unsigned pos)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (pos >= reccount)
|
||||
(Arg::Gds(isc_random) << "Position is out of range").raise();
|
||||
if (array)
|
||||
return (*array)[pos];
|
||||
|
||||
ULONG index = find(pos);
|
||||
return (index >= rare.getCount() || rare[index].first != pos) ?
|
||||
SUCCESS_NO_INFO : EXECUTE_FAILED;
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned findError(CheckStatusWrapper* status, unsigned pos)
|
||||
{
|
||||
try
|
||||
{
|
||||
ULONG index = find(pos);
|
||||
if (index < rare.getCount())
|
||||
return rare[index].first;
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
return NO_MORE_ERRORS;
|
||||
}
|
||||
|
||||
void getStatus(CheckStatusWrapper* status, IStatus* to, unsigned pos)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (pos >= reccount)
|
||||
(Arg::Gds(isc_random) << "Position is out of range").raise();
|
||||
|
||||
ULONG index = find(pos);
|
||||
if (index < rare.getCount() && rare[index].first == pos)
|
||||
{
|
||||
if (rare[index].second)
|
||||
{
|
||||
CheckStatusWrapper w(to);
|
||||
fb_utils::copyStatus(&w, rare[index].second);
|
||||
return;
|
||||
}
|
||||
(Arg::Gds(isc_random) << "Detailed error info is missing in batch").raise();
|
||||
}
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
typedef Pair<NonPooled<ULONG, IStatus*> > StatusPair;
|
||||
typedef Array<StatusPair> RarefiedArray;
|
||||
RarefiedArray rare;
|
||||
typedef Array<SLONG> DenseArray;
|
||||
AutoPtr<DenseArray> array;
|
||||
ULONG reccount, detailedLimit;
|
||||
|
||||
ULONG find(ULONG recno) const
|
||||
{
|
||||
ULONG high = rare.getCount(), low = 0;
|
||||
while (high > low)
|
||||
{
|
||||
ULONG med = (high + low) / 2;
|
||||
if (recno > rare[med].first)
|
||||
low = med + 1;
|
||||
else
|
||||
high = med;
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
};
|
||||
}
|
@ -129,7 +129,7 @@ namespace Firebird {
|
||||
delete stk_cache;
|
||||
}
|
||||
|
||||
void push(Object e)
|
||||
void push(const Object& e)
|
||||
{
|
||||
if (!stk && stk_cache)
|
||||
{
|
||||
@ -184,7 +184,7 @@ namespace Firebird {
|
||||
class AutoPushPop
|
||||
{
|
||||
public:
|
||||
AutoPushPop(Stack<Object, Capacity>& s, Object& o)
|
||||
AutoPushPop(Stack<Object, Capacity>& s, const Object& o)
|
||||
: stack(s)
|
||||
{
|
||||
stack.push(o);
|
||||
|
@ -36,7 +36,7 @@
|
||||
namespace Firebird {
|
||||
|
||||
// Very fast static array of simple types
|
||||
template <typename T, FB_SIZE_T Capacity>
|
||||
template <typename T, FB_SIZE_T Capacity, typename A = char>
|
||||
class Vector
|
||||
{
|
||||
public:
|
||||
@ -85,6 +85,14 @@ public:
|
||||
return &data[index];
|
||||
}
|
||||
|
||||
T* removeCount(const FB_SIZE_T index, const FB_SIZE_T n) throw()
|
||||
{
|
||||
fb_assert(index + n <= count);
|
||||
memmove(data + index, data + index + n, sizeof(T) * (count - index - n));
|
||||
count -= n;
|
||||
return &data[index];
|
||||
}
|
||||
|
||||
void shrink(FB_SIZE_T newCount) throw()
|
||||
{
|
||||
fb_assert(newCount <= count);
|
||||
@ -118,6 +126,20 @@ public:
|
||||
return data[count];
|
||||
}
|
||||
|
||||
void push(const T* items, const FB_SIZE_T itemsCount)
|
||||
{
|
||||
fb_assert(count <= FB_MAX_SIZEOF - itemsCount);
|
||||
fb_assert(count + itemsCount <= Capacity);
|
||||
memcpy(data + count, items, sizeof(T) * itemsCount);
|
||||
count += itemsCount;
|
||||
}
|
||||
|
||||
void append(const T* items, const FB_SIZE_T itemsCount)
|
||||
{
|
||||
push(items, itemsCount);
|
||||
}
|
||||
|
||||
|
||||
// This method only assigns "pos" if the element is found.
|
||||
// Maybe we should modify it to iterate directy with "pos".
|
||||
bool find(const T& item, FB_SIZE_T& pos) const
|
||||
@ -134,7 +156,13 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
FB_SIZE_T count;
|
||||
union
|
||||
{
|
||||
FB_SIZE_T count;
|
||||
A align;
|
||||
};
|
||||
// Do not insert data members between align and data:
|
||||
// alignment of data is ensured by preceding union
|
||||
T data[Capacity];
|
||||
};
|
||||
|
||||
|
@ -201,7 +201,8 @@ const Config::ConfigEntry Config::entries[MAX_CONFIG_KEY] =
|
||||
{TYPE_INTEGER, "MaxIdentifierCharLength", (ConfigValue) -1},
|
||||
{TYPE_BOOLEAN, "AllowEncryptedSecurityDatabase", (ConfigValue) false},
|
||||
{TYPE_INTEGER, "StatementTimeout", (ConfigValue) 0},
|
||||
{TYPE_INTEGER, "ConnectionIdleTimeout", (ConfigValue) 0}
|
||||
{TYPE_INTEGER, "ConnectionIdleTimeout", (ConfigValue) 0},
|
||||
{TYPE_INTEGER, "ClientBatchBuffer", (ConfigValue) (128 * 1024)}
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
@ -832,3 +833,9 @@ unsigned int Config::getConnIdleTimeout() const
|
||||
{
|
||||
return get<unsigned int>(KEY_CONN_IDLE_TIMEOUT);
|
||||
}
|
||||
|
||||
unsigned int Config::getClientBatchBuffer() const
|
||||
{
|
||||
return get<unsigned int>(KEY_CLIENT_BATCH_BUFFER);
|
||||
}
|
||||
|
||||
|
@ -145,6 +145,7 @@ public:
|
||||
KEY_ENCRYPT_SECURITY_DATABASE,
|
||||
KEY_STMT_TIMEOUT,
|
||||
KEY_CONN_IDLE_TIMEOUT,
|
||||
KEY_CLIENT_BATCH_BUFFER,
|
||||
MAX_CONFIG_KEY // keep it last
|
||||
};
|
||||
|
||||
@ -359,6 +360,8 @@ public:
|
||||
unsigned int getStatementTimeout() const;
|
||||
// set in minutes
|
||||
unsigned int getConnIdleTimeout() const;
|
||||
|
||||
unsigned int getClientBatchBuffer() const;
|
||||
};
|
||||
|
||||
// Implementation of interface to access master configuration file
|
||||
|
@ -52,6 +52,8 @@
|
||||
#include "../common/StatusArg.h"
|
||||
#include "../common/os/os_utils.h"
|
||||
#include "../dsql/sqlda_pub.h"
|
||||
#include "../common/classes/ClumpletReader.h"
|
||||
#include "../common/StatusArg.h"
|
||||
|
||||
#ifdef WIN_NT
|
||||
#include <direct.h>
|
||||
@ -1169,7 +1171,7 @@ unsigned int mergeStatus(ISC_STATUS* const dest, unsigned int space,
|
||||
return copied;
|
||||
}
|
||||
|
||||
void copyStatus(Firebird::CheckStatusWrapper* to, const Firebird::CheckStatusWrapper* from) throw()
|
||||
void copyStatus(Firebird::CheckStatusWrapper* to, const Firebird::IStatus* from) throw()
|
||||
{
|
||||
to->init();
|
||||
|
||||
@ -1625,4 +1627,21 @@ const char* dpbItemUpper(const char* s, FB_SIZE_T l, Firebird::string& buf)
|
||||
return buf.c_str();
|
||||
}
|
||||
|
||||
bool isBpbSegmented(unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
if (parLength && !par)
|
||||
(Firebird::Arg::Gds(isc_random) << "Malformed BPB").raise();
|
||||
|
||||
Firebird::ClumpletReader bpb(Firebird::ClumpletReader::Tagged, par, parLength);
|
||||
if (bpb.getBufferTag() != isc_bpb_version1)
|
||||
(Firebird::Arg::Gds(isc_random) << "Malformed BPB").raise();
|
||||
|
||||
if (!bpb.find(isc_bpb_type))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
int type = bpb.getInt();
|
||||
return type & isc_bpb_type_stream ? false : true;
|
||||
}
|
||||
|
||||
} // namespace fb_utils
|
||||
|
@ -136,7 +136,7 @@ namespace fb_utils
|
||||
|
||||
unsigned int copyStatus(ISC_STATUS* const to, const unsigned int space,
|
||||
const ISC_STATUS* const from, const unsigned int count) throw();
|
||||
void copyStatus(Firebird::CheckStatusWrapper* to, const Firebird::CheckStatusWrapper* from) throw();
|
||||
void copyStatus(Firebird::CheckStatusWrapper* to, const Firebird::IStatus* from) throw();
|
||||
unsigned int mergeStatus(ISC_STATUS* const to, unsigned int space, const Firebird::IStatus* from) throw();
|
||||
void setIStatus(Firebird::CheckStatusWrapper* to, const ISC_STATUS* from) throw();
|
||||
unsigned int statusLength(const ISC_STATUS* const status) throw();
|
||||
@ -206,6 +206,9 @@ namespace fb_utils
|
||||
if (up)
|
||||
name = up;
|
||||
}
|
||||
|
||||
// Frequently used actions with clumplets
|
||||
bool isBpbSegmented(unsigned parLength, const unsigned char* par);
|
||||
} // namespace fb_utils
|
||||
|
||||
#endif // INCLUDE_UTILS_PROTO_H
|
||||
|
@ -931,7 +931,7 @@ void DdlNode::executeDdlTrigger(thread_db* tdbb, jrd_tra* transaction, DdlTrigge
|
||||
context.newObjectName = when == DTW_BEFORE ? oldNewObjectName : objectName;
|
||||
}
|
||||
|
||||
Stack<DdlTriggerContext>::AutoPushPop autoContext(attachment->ddlTriggersContext, context);
|
||||
Stack<DdlTriggerContext*>::AutoPushPop autoContext(attachment->ddlTriggersContext, &context);
|
||||
AutoSavePoint savePoint(tdbb, transaction);
|
||||
|
||||
EXE_execute_ddl_triggers(tdbb, transaction, when == DTW_BEFORE, action);
|
||||
|
988
src/dsql/DsqlBatch.cpp
Normal file
988
src/dsql/DsqlBatch.cpp
Normal file
@ -0,0 +1,988 @@
|
||||
/*
|
||||
* 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 Alexander Peshkov
|
||||
* for the Firebird Open Source RDBMS project.
|
||||
*
|
||||
* Copyright (c) 2017 Alexander Peshkov <peshkoff@mail.ru>
|
||||
* and all contributors signed below.
|
||||
*
|
||||
* All Rights Reserved.
|
||||
* Contributor(s): ________________________________
|
||||
*/
|
||||
|
||||
#include "firebird.h"
|
||||
|
||||
#include "../dsql/DsqlBatch.h"
|
||||
|
||||
#include "../jrd/EngineInterface.h"
|
||||
#include "../jrd/jrd.h"
|
||||
#include "../jrd/status.h"
|
||||
#include "../jrd/exe_proto.h"
|
||||
#include "../dsql/dsql.h"
|
||||
#include "../dsql/errd_proto.h"
|
||||
#include "../common/classes/ClumpletReader.h"
|
||||
#include "../common/classes/auto.h"
|
||||
#include "../common/classes/fb_string.h"
|
||||
#include "../common/utils_proto.h"
|
||||
#include "../common/classes/BatchCompletionState.h"
|
||||
|
||||
using namespace Firebird;
|
||||
using namespace Jrd;
|
||||
|
||||
namespace {
|
||||
const char* const TEMP_NAME = "fb_batch";
|
||||
const UCHAR initBlobParameters[] = {isc_bpb_version1, isc_bpb_type, 1, isc_bpb_type_stream};
|
||||
|
||||
class JTransliterate : public Firebird::Transliterate
|
||||
{
|
||||
public:
|
||||
JTransliterate(thread_db* tdbb)
|
||||
: m_tdbb(tdbb)
|
||||
{ }
|
||||
|
||||
void transliterate(IStatus* status)
|
||||
{
|
||||
JRD_transliterate(m_tdbb, status);
|
||||
}
|
||||
|
||||
private:
|
||||
thread_db* m_tdbb;
|
||||
};
|
||||
}
|
||||
|
||||
DsqlBatch::DsqlBatch(dsql_req* req, const dsql_msg* /*message*/, IMessageMetadata* inMeta, ClumpletReader& pb)
|
||||
: m_request(req),
|
||||
m_batch(NULL),
|
||||
m_meta(inMeta),
|
||||
m_messages(m_request->getPool()),
|
||||
m_blobs(m_request->getPool()),
|
||||
m_blobMap(m_request->getPool()),
|
||||
m_blobMeta(m_request->getPool()),
|
||||
m_defaultBpb(m_request->getPool()),
|
||||
m_messageSize(0),
|
||||
m_alignedMessage(0),
|
||||
m_alignment(0),
|
||||
m_flags(0),
|
||||
m_detailed(DETAILED_LIMIT),
|
||||
m_bufferSize(BUFFER_LIMIT),
|
||||
m_lastBlob(MAX_ULONG),
|
||||
m_setBlobSize(false),
|
||||
m_blobPolicy(IBatch::BLOB_NONE)
|
||||
{
|
||||
memset(&m_genId, 0, sizeof(m_genId));
|
||||
|
||||
FbLocalStatus st;
|
||||
m_messageSize = m_meta->getMessageLength(&st);
|
||||
m_alignedMessage = m_meta->getAlignedLength(&st);
|
||||
m_alignment = m_meta->getAlignment(&st);
|
||||
check(&st);
|
||||
|
||||
if (m_messageSize > RAM_BATCH) // hops - message does not fit in our buffer
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Message too long");
|
||||
}
|
||||
|
||||
for (pb.rewind(); !pb.isEof(); pb.moveNext())
|
||||
{
|
||||
UCHAR t = pb.getClumpTag();
|
||||
|
||||
switch (t)
|
||||
{
|
||||
case IBatch::TAG_MULTIERROR:
|
||||
case IBatch::TAG_RECORD_COUNTS:
|
||||
setFlag(t, pb.getInt());
|
||||
break;
|
||||
|
||||
case IBatch::TAG_BLOB_POLICY:
|
||||
m_blobPolicy = pb.getInt();
|
||||
|
||||
switch (m_blobPolicy)
|
||||
{
|
||||
case IBatch::BLOB_ID_ENGINE:
|
||||
case IBatch::BLOB_ID_USER:
|
||||
case IBatch::BLOB_STREAM:
|
||||
break;
|
||||
default:
|
||||
m_blobPolicy = IBatch::BLOB_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case IBatch::TAG_DETAILED_ERRORS:
|
||||
m_detailed = pb.getInt();
|
||||
if (m_detailed > DETAILED_LIMIT * 4)
|
||||
m_detailed = DETAILED_LIMIT * 4;
|
||||
break;
|
||||
|
||||
case IBatch::TAG_BUFFER_BYTES_SIZE:
|
||||
m_bufferSize = pb.getInt();
|
||||
if (m_bufferSize > HARD_BUFFER_LIMIT)
|
||||
m_bufferSize = HARD_BUFFER_LIMIT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// parse message to detect blobs
|
||||
unsigned fieldsCount = m_meta->getCount(&st);
|
||||
check(&st);
|
||||
|
||||
for (unsigned i = 0; i < fieldsCount; ++i)
|
||||
{
|
||||
unsigned t = m_meta->getType(&st, i);
|
||||
check(&st);
|
||||
|
||||
switch (t)
|
||||
{
|
||||
case SQL_BLOB:
|
||||
case SQL_ARRAY:
|
||||
{
|
||||
BlobMeta bm;
|
||||
bm.offset = m_meta->getOffset(&st, i);
|
||||
check(&st);
|
||||
bm.nullOffset = m_meta->getNullOffset(&st, i);
|
||||
check(&st);
|
||||
m_blobMeta.push(bm);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// allocate data buffers
|
||||
m_messages.setBuf(m_bufferSize);
|
||||
if (m_blobMeta.hasData())
|
||||
m_blobs.setBuf(m_bufferSize);
|
||||
|
||||
// assign initial default BPB
|
||||
setDefBpb(FB_NELEM(initBlobParameters), initBlobParameters);
|
||||
}
|
||||
|
||||
|
||||
DsqlBatch::~DsqlBatch()
|
||||
{
|
||||
if (m_batch)
|
||||
m_batch->resetHandle();
|
||||
if (m_request)
|
||||
m_request->req_batch = NULL;
|
||||
}
|
||||
|
||||
Attachment* DsqlBatch::getAttachment() const
|
||||
{
|
||||
return m_request->req_dbb->dbb_attachment;
|
||||
}
|
||||
|
||||
void DsqlBatch::setInterfacePtr(JBatch* interfacePtr) throw()
|
||||
{
|
||||
fb_assert(!m_batch);
|
||||
m_batch = interfacePtr;
|
||||
}
|
||||
|
||||
DsqlBatch* DsqlBatch::open(thread_db* tdbb, dsql_req* req, IMessageMetadata* inMetadata,
|
||||
unsigned parLength, const UCHAR* par)
|
||||
{
|
||||
SET_TDBB(tdbb);
|
||||
Jrd::ContextPoolHolder context(tdbb, &req->getPool());
|
||||
|
||||
// Validate cursor or batch being not already open
|
||||
|
||||
if (req->req_cursor)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
|
||||
Arg::Gds(isc_dsql_cursor_open_err));
|
||||
}
|
||||
|
||||
if (req->req_batch)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
|
||||
Arg::Gds(isc_random) << "Request has active batch");
|
||||
}
|
||||
|
||||
// Sanity checks before creating batch
|
||||
|
||||
if (!req->req_request)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
|
||||
Arg::Gds(isc_unprepared_stmt));
|
||||
}
|
||||
|
||||
const DsqlCompiledStatement* statement = req->getStatement();
|
||||
|
||||
if (statement->getFlags() & DsqlCompiledStatement::FLAG_ORPHAN)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
|
||||
Arg::Gds(isc_bad_req_handle));
|
||||
}
|
||||
|
||||
switch (statement->getType())
|
||||
{
|
||||
case DsqlCompiledStatement::TYPE_INSERT:
|
||||
case DsqlCompiledStatement::TYPE_DELETE:
|
||||
case DsqlCompiledStatement::TYPE_UPDATE:
|
||||
case DsqlCompiledStatement::TYPE_EXEC_PROCEDURE:
|
||||
case DsqlCompiledStatement::TYPE_EXEC_BLOCK:
|
||||
break;
|
||||
|
||||
default:
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
|
||||
Arg::Gds(isc_random) << "Invalid type of statement used in batch");
|
||||
}
|
||||
|
||||
const dsql_msg* message = statement->getSendMsg();
|
||||
if (! (inMetadata && message && req->parseMetadata(inMetadata, message->msg_parameters)))
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
|
||||
Arg::Gds(isc_random) << "Statement used in batch must have parameters");
|
||||
}
|
||||
|
||||
// Open reader for parameters block
|
||||
|
||||
ClumpletReader pb(ClumpletReader::WideTagged, par, parLength);
|
||||
if (pb.getBufferLength() && (pb.getBufferTag() != IBatch::VERSION1))
|
||||
ERRD_post(Arg::Gds(isc_random) << "Invalid tag in parameters block");
|
||||
|
||||
// Create batch
|
||||
|
||||
DsqlBatch* b = FB_NEW_POOL(req->getPool()) DsqlBatch(req, message, inMetadata, pb);
|
||||
req->req_batch = b;
|
||||
return b;
|
||||
}
|
||||
|
||||
IMessageMetadata* DsqlBatch::getMetadata(thread_db* tdbb)
|
||||
{
|
||||
m_meta->addRef();
|
||||
return m_meta;
|
||||
}
|
||||
|
||||
void DsqlBatch::add(thread_db* tdbb, ULONG count, const void* inBuffer)
|
||||
{
|
||||
if (!count)
|
||||
return;
|
||||
m_messages.align(m_alignment);
|
||||
m_messages.put(inBuffer, (count - 1) * m_alignedMessage + m_messageSize);
|
||||
}
|
||||
|
||||
void DsqlBatch::blobCheckMeta()
|
||||
{
|
||||
if (!m_blobMeta.hasData())
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "There are no blobs in associated statement");
|
||||
}
|
||||
}
|
||||
|
||||
void DsqlBatch::blobCheckMode(bool stream, const char* fname)
|
||||
{
|
||||
blobCheckMeta();
|
||||
|
||||
switch (m_blobPolicy)
|
||||
{
|
||||
case IBatch::BLOB_ID_ENGINE:
|
||||
case IBatch::BLOB_ID_USER:
|
||||
if (!stream)
|
||||
return;
|
||||
break;
|
||||
case IBatch::BLOB_STREAM:
|
||||
if (stream)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "This *** call can't be used with current blob policy" <<
|
||||
Arg::Gds(isc_random) << fname);
|
||||
}
|
||||
|
||||
void DsqlBatch::blobSetSize()
|
||||
{
|
||||
// Store size of previous blob if it was changed by appendBlobData()
|
||||
unsigned blobSize = m_blobs.getSize();
|
||||
if (m_setBlobSize)
|
||||
{
|
||||
blobSize -= (m_lastBlob + SIZEOF_BLOB_HEAD);
|
||||
m_blobs.put3(&blobSize, sizeof(blobSize), m_lastBlob + sizeof(ISC_QUAD));
|
||||
m_setBlobSize = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DsqlBatch::blobPrepare()
|
||||
{
|
||||
blobSetSize();
|
||||
|
||||
// Align blob stream
|
||||
m_blobs.align(BLOB_STREAM_ALIGN);
|
||||
}
|
||||
|
||||
void DsqlBatch::setDefaultBpb(thread_db* tdbb, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
if (m_blobs.getSize())
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "setDefaultBpb() call can be used only with empty batch (no blobs added)");
|
||||
}
|
||||
setDefBpb(parLength, par);
|
||||
}
|
||||
|
||||
void DsqlBatch::setDefBpb(unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
m_defaultBpb.clear();
|
||||
m_defaultBpb.add(par, parLength);
|
||||
setFlag(FLAG_DEFAULT_SEGMENTED, fb_utils::isBpbSegmented(m_defaultBpb.getCount(), m_defaultBpb.begin()));
|
||||
}
|
||||
|
||||
void DsqlBatch::addBlob(thread_db* tdbb, ULONG length, const void* inBuffer, ISC_QUAD* blobId,
|
||||
unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
blobCheckMode(false, "addBlob");
|
||||
blobPrepare();
|
||||
|
||||
// Get ready to appendBlobData()
|
||||
m_lastBlob = m_blobs.getSize();
|
||||
fb_assert(m_lastBlob % BLOB_STREAM_ALIGN == 0);
|
||||
|
||||
// Generate auto blob ID if needed
|
||||
if (m_blobPolicy == IBatch::BLOB_ID_ENGINE)
|
||||
genBlobId(blobId);
|
||||
|
||||
// Determine type of current blob
|
||||
setFlag(FLAG_CURRENT_SEGMENTED, parLength ? fb_utils::isBpbSegmented(parLength, par) : m_flags & (1 << FLAG_DEFAULT_SEGMENTED));
|
||||
|
||||
// Store header
|
||||
m_blobs.put(blobId, sizeof(ISC_QUAD));
|
||||
ULONG fullLength = length + parLength;
|
||||
m_blobs.put(&fullLength, sizeof(ULONG));
|
||||
m_blobs.put(&parLength, sizeof(ULONG));
|
||||
|
||||
// Store BPB
|
||||
if (parLength)
|
||||
m_blobs.put(par, parLength);
|
||||
|
||||
// Finally store user data
|
||||
putSegment(length, inBuffer);
|
||||
}
|
||||
|
||||
void DsqlBatch::appendBlobData(thread_db* tdbb, ULONG length, const void* inBuffer)
|
||||
{
|
||||
blobCheckMode(false, "appendBlobData");
|
||||
|
||||
if (m_lastBlob == MAX_ULONG)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "appendBlobData() is used to append data to last blob "
|
||||
"but no such blob was added to the batch");
|
||||
}
|
||||
|
||||
m_setBlobSize = true;
|
||||
putSegment(length, inBuffer);
|
||||
}
|
||||
|
||||
void DsqlBatch::putSegment(ULONG length, const void* inBuffer)
|
||||
{
|
||||
if (m_flags & (1 << FLAG_CURRENT_SEGMENTED))
|
||||
{
|
||||
if (length > MAX_USHORT)
|
||||
{
|
||||
ERR_post(Arg::Gds(isc_imp_exc) << Arg::Gds(isc_blobtoobig) <<
|
||||
Arg::Gds(isc_random) << "Segment size >= 64Kb");
|
||||
}
|
||||
USHORT l = length;
|
||||
m_blobs.align(IBatch::BLOB_SEGHDR_ALIGN);
|
||||
m_blobs.put(&l, sizeof(l));
|
||||
m_setBlobSize = true;
|
||||
}
|
||||
m_blobs.put(inBuffer, length);
|
||||
}
|
||||
|
||||
void DsqlBatch::addBlobStream(thread_db* tdbb, unsigned length, const void* inBuffer)
|
||||
{
|
||||
// Sanity checks
|
||||
if (length == 0)
|
||||
return;
|
||||
if (length % BLOB_STREAM_ALIGN)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Portions of data, passed as blob stream, should have size "
|
||||
"multiple to the alignment required for blobs");
|
||||
}
|
||||
|
||||
blobCheckMode(true, "addBlobStream");
|
||||
blobPrepare();
|
||||
|
||||
// We have no idea where is the last blob located in the stream
|
||||
m_lastBlob = MAX_ULONG;
|
||||
|
||||
// store stream for further processing
|
||||
fb_assert(m_blobs.getSize() % BLOB_STREAM_ALIGN == 0);
|
||||
m_blobs.put(inBuffer, length);
|
||||
}
|
||||
|
||||
void DsqlBatch::registerBlob(thread_db*, const ISC_QUAD* existingBlob, ISC_QUAD* blobId)
|
||||
{
|
||||
blobCheckMeta();
|
||||
|
||||
// Generate auto blob ID if needed
|
||||
if (m_blobPolicy == IBatch::BLOB_ID_ENGINE)
|
||||
genBlobId(blobId);
|
||||
|
||||
registerBlob(existingBlob, blobId);
|
||||
}
|
||||
|
||||
void DsqlBatch::registerBlob(const ISC_QUAD* engineBlob, const ISC_QUAD* batchBlob)
|
||||
{
|
||||
ISC_QUAD* idPtr = m_blobMap.put(*batchBlob);
|
||||
if (!idPtr)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Repeated BlobId in registerBlob(): is ***");
|
||||
}
|
||||
|
||||
*idPtr = *engineBlob;
|
||||
}
|
||||
|
||||
Firebird::IBatchCompletionState* DsqlBatch::execute(thread_db* tdbb)
|
||||
{
|
||||
// todo - add new trace event here
|
||||
// TraceDSQLExecute trace(req_dbb->dbb_attachment, this);
|
||||
|
||||
jrd_tra* transaction = tdbb->getTransaction();
|
||||
|
||||
// execution timer
|
||||
thread_db::TimerGuard timerGuard(tdbb, m_request->setupTimer(tdbb), true);
|
||||
|
||||
// sync internal buffers
|
||||
if (!m_messages.done())
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Internal message buffer overflow - batch too big");
|
||||
}
|
||||
|
||||
// insert blobs here
|
||||
if (m_blobMeta.hasData())
|
||||
{
|
||||
// This code expects the following to work correctly
|
||||
fb_assert(RAM_BATCH % BLOB_STREAM_ALIGN == 0);
|
||||
|
||||
blobSetSize(); // Needed after appendBlobData()
|
||||
|
||||
if (!m_blobs.done())
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Internal BLOB buffer overflow - batch too big");
|
||||
}
|
||||
|
||||
struct BlobFlow
|
||||
{
|
||||
ULONG remains;
|
||||
UCHAR* data;
|
||||
ULONG currentBlobSize;
|
||||
ULONG byteCount;
|
||||
|
||||
BlobFlow()
|
||||
: remains(0), data(NULL), currentBlobSize(0), byteCount(0)
|
||||
{ }
|
||||
|
||||
void newHdr(ULONG blobSize)
|
||||
{
|
||||
currentBlobSize = blobSize;
|
||||
move3(SIZEOF_BLOB_HEAD);
|
||||
}
|
||||
|
||||
void move(ULONG step)
|
||||
{
|
||||
move3(step);
|
||||
currentBlobSize -= step;
|
||||
}
|
||||
|
||||
bool align(ULONG alignment)
|
||||
{
|
||||
ULONG a = byteCount % alignment;
|
||||
if (a)
|
||||
{
|
||||
a = alignment - a;
|
||||
move3(a);
|
||||
if (currentBlobSize)
|
||||
currentBlobSize -= a;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
private:
|
||||
void move3(ULONG step)
|
||||
{
|
||||
data += step;
|
||||
byteCount += step;
|
||||
remains -= step;
|
||||
}
|
||||
};
|
||||
BlobFlow flow;
|
||||
blb* blob = nullptr;
|
||||
try
|
||||
{
|
||||
while ((flow.remains = m_blobs.get(&flow.data)) > 0)
|
||||
{
|
||||
while (flow.remains)
|
||||
{
|
||||
// should we get next blob header
|
||||
if (!flow.currentBlobSize)
|
||||
{
|
||||
// align data stream
|
||||
if (flow.align(BLOB_STREAM_ALIGN))
|
||||
continue;
|
||||
|
||||
// check for partial header in the buffer
|
||||
if (flow.remains < SIZEOF_BLOB_HEAD)
|
||||
flow.remains = m_blobs.reget(flow.remains, &flow.data, BLOB_STREAM_ALIGN);
|
||||
if (flow.remains < SIZEOF_BLOB_HEAD)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Blob buffer format error: useless data remained in buffer");
|
||||
}
|
||||
|
||||
// parse blob header
|
||||
fb_assert(intptr_t(flow.data) % BLOB_STREAM_ALIGN == 0);
|
||||
ISC_QUAD* batchBlobId = reinterpret_cast<ISC_QUAD*>(flow.data);
|
||||
ULONG* blobSize = reinterpret_cast<ULONG*>(flow.data + sizeof(ISC_QUAD));
|
||||
ULONG* bpbSize = reinterpret_cast<ULONG*>(flow.data + sizeof(ISC_QUAD) + sizeof(ULONG));
|
||||
flow.newHdr(*blobSize);
|
||||
ULONG currentBpbSize = *bpbSize;
|
||||
|
||||
if (batchBlobId->gds_quad_high == 0 && batchBlobId->gds_quad_low == 0)
|
||||
{
|
||||
// Sanity check
|
||||
if (*bpbSize)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Blob buffer format error: blob continuation should not contain BPB");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// get BPB
|
||||
Bpb localBpb;
|
||||
Bpb* bpb;
|
||||
bool segmentedMode;
|
||||
if (currentBpbSize)
|
||||
{
|
||||
if (currentBpbSize > flow.remains)
|
||||
flow.remains = m_blobs.reget(flow.remains, &flow.data, BLOB_STREAM_ALIGN);
|
||||
if (currentBpbSize > flow.remains)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Blob buffer format error: size of BPB greater than remaining data"); // <<currentBpbSize
|
||||
}
|
||||
localBpb.add(flow.data, currentBpbSize);
|
||||
bpb = &localBpb;
|
||||
segmentedMode = fb_utils::isBpbSegmented(currentBpbSize, flow.data);
|
||||
flow.move(currentBpbSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
bpb = &m_defaultBpb;
|
||||
segmentedMode = m_flags & (1 << FLAG_DEFAULT_SEGMENTED);
|
||||
}
|
||||
setFlag(FLAG_CURRENT_SEGMENTED, segmentedMode);
|
||||
|
||||
// create blob
|
||||
if (blob)
|
||||
{
|
||||
blob->BLB_close(tdbb);
|
||||
blob = nullptr;
|
||||
}
|
||||
bid engineBlobId;
|
||||
blob = blb::create2(tdbb, transaction, &engineBlobId, bpb->getCount(),
|
||||
bpb->begin(), true);
|
||||
registerBlob(reinterpret_cast<ISC_QUAD*>(&engineBlobId), batchBlobId);
|
||||
}
|
||||
}
|
||||
|
||||
// store data
|
||||
ULONG dataSize = MIN(flow.currentBlobSize, flow.remains);
|
||||
if (dataSize)
|
||||
{
|
||||
if (m_flags & (1 << FLAG_CURRENT_SEGMENTED))
|
||||
{
|
||||
if (flow.align(IBatch::BLOB_SEGHDR_ALIGN))
|
||||
continue;
|
||||
|
||||
fb_assert(dataSize >= sizeof(USHORT));
|
||||
USHORT* segSize = reinterpret_cast<USHORT*>(flow.data);
|
||||
flow.move(sizeof(USHORT));
|
||||
|
||||
dataSize = *segSize;
|
||||
if (dataSize > flow.currentBlobSize)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Blob buffer format error: size of segment exceeds remaining data"); // <<dataSize, currentBlobSize
|
||||
}
|
||||
if (dataSize > flow.remains)
|
||||
{
|
||||
flow.remains = m_blobs.reget(flow.remains, &flow.data, BLOB_STREAM_ALIGN);
|
||||
if (dataSize > flow.remains)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Blob buffer format error: size of segment exceeds RAM buffer"); // <<dataSize, flow.remains
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blob->BLB_put_segment(tdbb, flow.data, dataSize);
|
||||
flow.move(dataSize);
|
||||
}
|
||||
}
|
||||
|
||||
m_blobs.remained(0);
|
||||
}
|
||||
|
||||
if (blob)
|
||||
{
|
||||
blob->BLB_close(tdbb);
|
||||
blob = nullptr;
|
||||
}
|
||||
}
|
||||
catch (const Exception&)
|
||||
{
|
||||
if (blob)
|
||||
blob->BLB_cancel(tdbb);
|
||||
cancel(tdbb);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// execute request
|
||||
m_request->req_transaction = transaction;
|
||||
jrd_req* req = m_request->req_request;
|
||||
fb_assert(req);
|
||||
|
||||
// prepare completion interface
|
||||
AutoPtr<BatchCompletionState, SimpleDispose<BatchCompletionState> > completionState
|
||||
(FB_NEW BatchCompletionState(m_flags & (1 << IBatch::TAG_RECORD_COUNTS), m_detailed));
|
||||
AutoSetRestore<bool> batchFlag(&req->req_batch, true);
|
||||
const dsql_msg* message = m_request->getStatement()->getSendMsg();
|
||||
bool startRequest = true;
|
||||
|
||||
// process messages
|
||||
ULONG remains;
|
||||
UCHAR* data;
|
||||
while ((remains = m_messages.get(&data)) > 0)
|
||||
{
|
||||
if (remains < m_messageSize)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Internal error: useless data remained in batch buffer");
|
||||
}
|
||||
|
||||
while (remains >= m_messageSize)
|
||||
{
|
||||
if (startRequest)
|
||||
{
|
||||
EXE_unwind(tdbb, req);
|
||||
EXE_start(tdbb, req, transaction);
|
||||
startRequest = false;
|
||||
}
|
||||
|
||||
// skip alignment data
|
||||
UCHAR* alignedData = FB_ALIGN(data, m_alignment);
|
||||
if (alignedData != data)
|
||||
{
|
||||
remains -= (alignedData - data);
|
||||
data = alignedData;
|
||||
continue;
|
||||
}
|
||||
|
||||
// translate blob IDs
|
||||
fb_assert(intptr_t(data) % m_alignment == 0);
|
||||
for (unsigned i = 0; i < m_blobMeta.getCount(); ++i)
|
||||
{
|
||||
const SSHORT* nullFlag = reinterpret_cast<const SSHORT*>(&data[m_blobMeta[i].nullOffset]);
|
||||
if (*nullFlag)
|
||||
continue;
|
||||
|
||||
ISC_QUAD* id = reinterpret_cast<ISC_QUAD*>(&data[m_blobMeta[i].offset]);
|
||||
ISC_QUAD newId;
|
||||
if (!m_blobMap.get(*id, newId))
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Unknown blob ID in the message: is ***" <<
|
||||
Arg::Gds(isc_random) << Arg::Num(id->gds_quad_high) <<
|
||||
Arg::Gds(isc_random) << Arg::Num(id->gds_quad_low));
|
||||
}
|
||||
|
||||
m_blobMap.remove(*id);
|
||||
*id = newId;
|
||||
}
|
||||
|
||||
// map message to internal engine format
|
||||
m_request->mapInOut(tdbb, false, message, m_meta, NULL, data);
|
||||
data += m_messageSize;
|
||||
remains -= m_messageSize;
|
||||
|
||||
UCHAR* msgBuffer = m_request->req_msg_buffers[message->msg_buffer_number];
|
||||
DEB_BATCH(fprintf(stderr, "\n\n+++ Send\n\n"));
|
||||
try
|
||||
{
|
||||
ULONG before = req->req_records_inserted + req->req_records_updated +
|
||||
req->req_records_deleted;
|
||||
EXE_send(tdbb, req, message->msg_number, message->msg_length, msgBuffer);
|
||||
ULONG after = req->req_records_inserted + req->req_records_updated +
|
||||
req->req_records_deleted;
|
||||
completionState->regUpdate(after - before);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
FbLocalStatus status;
|
||||
ex.stuffException(&status);
|
||||
tdbb->tdbb_status_vector->init();
|
||||
|
||||
JTransliterate trLit(tdbb);
|
||||
completionState->regError(&status, &trLit);
|
||||
|
||||
if (!(m_flags & (1 << IBatch::TAG_MULTIERROR)))
|
||||
{
|
||||
cancel(tdbb);
|
||||
remains = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
startRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
UCHAR* alignedData = FB_ALIGN(data, m_alignment);
|
||||
m_messages.remained(remains, alignedData - data);
|
||||
}
|
||||
|
||||
// reset to initial state
|
||||
cancel(tdbb);
|
||||
|
||||
return completionState.release();
|
||||
}
|
||||
|
||||
void DsqlBatch::cancel(thread_db* tdbb)
|
||||
{
|
||||
m_messages.clear();
|
||||
if (m_blobMeta.hasData())
|
||||
{
|
||||
m_blobs.clear();
|
||||
m_setBlobSize = false;
|
||||
m_lastBlob = MAX_ULONG;
|
||||
memset(&m_genId, 0, sizeof(m_genId));
|
||||
m_blobMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void DsqlBatch::genBlobId(ISC_QUAD* blobId)
|
||||
{
|
||||
if (++m_genId.gds_quad_low == 0)
|
||||
++m_genId.gds_quad_high;
|
||||
memcpy(blobId, &m_genId, sizeof(m_genId));
|
||||
}
|
||||
|
||||
void DsqlBatch::DataCache::setBuf(ULONG size)
|
||||
{
|
||||
m_limit = size;
|
||||
|
||||
// create ram cache
|
||||
fb_assert(!m_cache);
|
||||
m_cache = FB_NEW_POOL(getPool()) Cache;
|
||||
}
|
||||
|
||||
void DsqlBatch::DataCache::put3(const void* data, ULONG dataSize, ULONG offset)
|
||||
{
|
||||
// This assertion guarantees that data always fits as a whole into m_cache or m_space,
|
||||
// never placed half in one storage, half - in another.
|
||||
fb_assert((DsqlBatch::RAM_BATCH % dataSize == 0) && (offset % dataSize == 0));
|
||||
|
||||
if (offset >= m_used)
|
||||
{
|
||||
// data in cache
|
||||
UCHAR* to = m_cache->begin();
|
||||
to += (offset - m_used);
|
||||
fb_assert(to < m_cache->end());
|
||||
memcpy(to, data, dataSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
const FB_UINT64 writtenBytes = m_space->write(offset, data, dataSize);
|
||||
fb_assert(writtenBytes == dataSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DsqlBatch::DataCache::put(const void* d, ULONG dataSize)
|
||||
{
|
||||
if (m_used + (m_cache ? m_cache->getCount() : 0) + dataSize > m_limit)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
Arg::Gds(isc_random) << "Internal buffer overflow - batch too big");
|
||||
}
|
||||
|
||||
const UCHAR* data = reinterpret_cast<const UCHAR*>(d);
|
||||
|
||||
// Coefficient affecting direct data write to tempspace
|
||||
const ULONG K = 4;
|
||||
|
||||
// ensure ram cache presence
|
||||
fb_assert(m_cache);
|
||||
|
||||
// swap to secondary cache if needed
|
||||
if (m_cache->getCount() + dataSize > m_cache->getCapacity())
|
||||
{
|
||||
// store data in the end of ram cache if needed
|
||||
// avoid copy in case of huge buffer passed
|
||||
ULONG delta = m_cache->getCapacity() - m_cache->getCount();
|
||||
if (dataSize - delta < m_cache->getCapacity() / K)
|
||||
{
|
||||
m_cache->append(data, delta);
|
||||
data += delta;
|
||||
dataSize -= delta;
|
||||
}
|
||||
|
||||
// swap ram cache to tempspace
|
||||
if (!m_space)
|
||||
m_space = FB_NEW_POOL(getPool()) TempSpace(getPool(), TEMP_NAME);
|
||||
|
||||
const FB_UINT64 writtenBytes = m_space->write(m_used, m_cache->begin(), m_cache->getCount());
|
||||
fb_assert(writtenBytes == m_cache->getCount());
|
||||
m_used += m_cache->getCount();
|
||||
m_cache->clear();
|
||||
|
||||
// in a case of huge buffer write directly to tempspace
|
||||
if (dataSize > m_cache->getCapacity() / K)
|
||||
{
|
||||
const FB_UINT64 writtenBytes = m_space->write(m_used, data, dataSize);
|
||||
fb_assert(writtenBytes == dataSize);
|
||||
m_used += dataSize;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_cache->append(data, dataSize);
|
||||
}
|
||||
|
||||
void DsqlBatch::DataCache::align(ULONG alignment)
|
||||
{
|
||||
ULONG a = getSize() % alignment;
|
||||
if (a)
|
||||
{
|
||||
fb_assert(alignment <= sizeof(SINT64));
|
||||
SINT64 zero = 0;
|
||||
put(&zero, alignment - a);
|
||||
}
|
||||
}
|
||||
|
||||
bool DsqlBatch::DataCache::done()
|
||||
{
|
||||
fb_assert(m_cache);
|
||||
|
||||
if (m_cache->getCount() == 0 && m_used == 0)
|
||||
return true; // false?
|
||||
|
||||
if (m_cache->getCount() && m_used)
|
||||
{
|
||||
fb_assert(m_space);
|
||||
|
||||
const FB_UINT64 writtenBytes = m_space->write(m_used, m_cache->begin(), m_cache->getCount());
|
||||
fb_assert(writtenBytes == m_cache->getCount());
|
||||
m_used += m_cache->getCount();
|
||||
m_cache->clear();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ULONG DsqlBatch::DataCache::get(UCHAR** buffer)
|
||||
{
|
||||
if (m_used > m_got)
|
||||
{
|
||||
// get data from tempspace
|
||||
ULONG dlen = m_cache->getCount();
|
||||
ULONG delta = m_cache->getCapacity() - dlen;
|
||||
if (delta > m_used - m_got)
|
||||
delta = m_used - m_got;
|
||||
UCHAR* buf = m_cache->getBuffer(dlen + delta);
|
||||
buf += dlen;
|
||||
const FB_UINT64 readBytes = m_space->read(m_got, buf, delta);
|
||||
fb_assert(readBytes == delta);
|
||||
m_got += delta;
|
||||
}
|
||||
|
||||
if (m_cache->getCount())
|
||||
{
|
||||
if (m_shift)
|
||||
m_cache->removeCount(0, m_shift);
|
||||
|
||||
// return buffer full of data
|
||||
*buffer = m_cache->begin();
|
||||
fb_assert(intptr_t(*buffer) % FB_ALIGNMENT == 0);
|
||||
return m_cache->getCount();
|
||||
}
|
||||
|
||||
// no more data
|
||||
*buffer = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ULONG DsqlBatch::DataCache::reget(ULONG remains, UCHAR** buffer, ULONG alignment)
|
||||
{
|
||||
ULONG a = remains % alignment;
|
||||
if (a)
|
||||
{
|
||||
a = alignment - a;
|
||||
remains += a;
|
||||
}
|
||||
fb_assert(remains < m_cache->getCount());
|
||||
|
||||
m_cache->removeCount(0, m_cache->getCount() - remains);
|
||||
ULONG size = get(buffer);
|
||||
size -= a;
|
||||
*buffer += a;
|
||||
return size;
|
||||
}
|
||||
|
||||
void DsqlBatch::DataCache::remained(ULONG size, ULONG alignment)
|
||||
{
|
||||
if (size > alignment)
|
||||
{
|
||||
size -= alignment;
|
||||
alignment = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
alignment -= size;
|
||||
size = 0;
|
||||
}
|
||||
|
||||
if (!size)
|
||||
m_cache->clear();
|
||||
else
|
||||
m_cache->removeCount(0, m_cache->getCount() - size);
|
||||
|
||||
m_shift = alignment;
|
||||
}
|
||||
|
||||
ULONG DsqlBatch::DataCache::getSize() const
|
||||
{
|
||||
if(!m_cache)
|
||||
return 0;
|
||||
|
||||
fb_assert((MAX_ULONG - 1) - m_used > m_cache->getCount());
|
||||
return m_used + m_cache->getCount();
|
||||
}
|
||||
|
||||
void DsqlBatch::DataCache::clear()
|
||||
{
|
||||
m_cache->clear();
|
||||
if (m_space && m_used)
|
||||
m_space->releaseSpace(0, m_used);
|
||||
m_used = m_got = m_shift = 0;
|
||||
}
|
160
src/dsql/DsqlBatch.h
Normal file
160
src/dsql/DsqlBatch.h
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 Alexander Peshkov
|
||||
* for the Firebird Open Source RDBMS project.
|
||||
*
|
||||
* Copyright (c) 2017 Alexander Peshkov <peshkoff@mail.ru>
|
||||
* and all contributors signed below.
|
||||
*
|
||||
* All Rights Reserved.
|
||||
* Contributor(s): ________________________________
|
||||
*/
|
||||
|
||||
#ifndef DSQL_BATCH_H
|
||||
#define DSQL_BATCH_H
|
||||
|
||||
#include "../jrd/TempSpace.h"
|
||||
#include "../common/classes/alloc.h"
|
||||
#include "../common/classes/RefCounted.h"
|
||||
#include "../common/classes/vector.h"
|
||||
#include "../common/classes/GenericMap.h"
|
||||
|
||||
#define DEB_BATCH(x)
|
||||
|
||||
|
||||
namespace Firebird {
|
||||
|
||||
class ClumpletReader;
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace Jrd {
|
||||
|
||||
class dsql_req;
|
||||
class dsql_msg;
|
||||
class thread_db;
|
||||
class JBatch;
|
||||
class Attachment;
|
||||
|
||||
class DsqlBatch
|
||||
{
|
||||
public:
|
||||
DsqlBatch(dsql_req* req, const dsql_msg* message, Firebird::IMessageMetadata* inMetadata,
|
||||
Firebird::ClumpletReader& pb);
|
||||
~DsqlBatch();
|
||||
|
||||
static const ULONG RAM_BATCH = 128 * 1024;
|
||||
static const ULONG BUFFER_LIMIT = 16 * 1024 * 1024;
|
||||
static const ULONG HARD_BUFFER_LIMIT = 256 * 1024 * 1024;
|
||||
static const ULONG DETAILED_LIMIT = 64;
|
||||
static const ULONG SIZEOF_BLOB_HEAD = sizeof(ISC_QUAD) + 2 * sizeof(ULONG);
|
||||
static const unsigned BLOB_STREAM_ALIGN = 4;
|
||||
|
||||
static DsqlBatch* open(thread_db* tdbb, dsql_req* req, Firebird::IMessageMetadata* inMetadata,
|
||||
unsigned parLength, const UCHAR* par);
|
||||
|
||||
Attachment* getAttachment() const;
|
||||
void setInterfacePtr(JBatch* interfacePtr) throw();
|
||||
|
||||
void add(thread_db* tdbb, ULONG count, const void* inBuffer);
|
||||
void addBlob(thread_db* tdbb, ULONG length, const void* inBuffer, ISC_QUAD* blobId, unsigned parLength, const unsigned char* par);
|
||||
void appendBlobData(thread_db* tdbb, ULONG length, const void* inBuffer);
|
||||
void addBlobStream(thread_db* tdbb, unsigned length, const void* inBuffer);
|
||||
void registerBlob(thread_db* tdbb, const ISC_QUAD* existingBlob, ISC_QUAD* blobId);
|
||||
Firebird::IBatchCompletionState* execute(thread_db* tdbb);
|
||||
Firebird::IMessageMetadata* getMetadata(thread_db* tdbb);
|
||||
void cancel(thread_db* tdbb);
|
||||
void setDefaultBpb(thread_db* tdbb, unsigned parLength, const unsigned char* par);
|
||||
|
||||
// Additional flags - start from the maximum one
|
||||
static const UCHAR FLAG_DEFAULT_SEGMENTED = 31;
|
||||
static const UCHAR FLAG_CURRENT_SEGMENTED = 30;
|
||||
|
||||
private:
|
||||
void genBlobId(ISC_QUAD* blobId);
|
||||
void blobPrepare();
|
||||
void blobSetSize();
|
||||
void blobCheckMode(bool stream, const char* fname);
|
||||
void blobCheckMeta();
|
||||
void registerBlob(const ISC_QUAD* engineBlob, const ISC_QUAD* batchBlob);
|
||||
void setDefBpb(unsigned parLength, const unsigned char* par);
|
||||
void putSegment(ULONG length, const void* inBuffer);
|
||||
|
||||
void setFlag(UCHAR bit, bool value)
|
||||
{
|
||||
if (value)
|
||||
m_flags |= (1 << bit);
|
||||
else
|
||||
m_flags &= ~(1 << bit);
|
||||
}
|
||||
|
||||
dsql_req* const m_request;
|
||||
JBatch* m_batch;
|
||||
Firebird::RefPtr<Firebird::IMessageMetadata> m_meta;
|
||||
|
||||
class DataCache : public Firebird::PermanentStorage
|
||||
{
|
||||
public:
|
||||
DataCache(MemoryPool& p)
|
||||
: PermanentStorage(p),
|
||||
m_used(0), m_got(0), m_limit(0), m_shift(0)
|
||||
{ }
|
||||
|
||||
void setBuf(ULONG size);
|
||||
|
||||
void put(const void* data, ULONG dataSize);
|
||||
void put3(const void* data, ULONG dataSize, ULONG offset);
|
||||
void align(ULONG alignment);
|
||||
bool done();
|
||||
ULONG get(UCHAR** buffer);
|
||||
ULONG reget(ULONG size, UCHAR** buffer, ULONG alignment);
|
||||
void remained(ULONG size, ULONG alignment = 0);
|
||||
ULONG getSize() const;
|
||||
void clear();
|
||||
|
||||
private:
|
||||
typedef Firebird::Vector<UCHAR, DsqlBatch::RAM_BATCH, SINT64> Cache;
|
||||
Firebird::AutoPtr<Cache> m_cache;
|
||||
Firebird::AutoPtr<TempSpace> m_space;
|
||||
ULONG m_used, m_got, m_limit, m_shift;
|
||||
};
|
||||
|
||||
struct BlobMeta
|
||||
{
|
||||
unsigned nullOffset, offset;
|
||||
};
|
||||
|
||||
class QuadComparator
|
||||
{
|
||||
public:
|
||||
static bool greaterThan(const ISC_QUAD& i1, const ISC_QUAD& i2)
|
||||
{
|
||||
return memcmp(&i1, &i2, sizeof(ISC_QUAD)) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
DataCache m_messages, m_blobs;
|
||||
Firebird::GenericMap<Firebird::Pair<Firebird::NonPooled<ISC_QUAD, ISC_QUAD> >, QuadComparator> m_blobMap;
|
||||
Firebird::HalfStaticArray<BlobMeta, 4> m_blobMeta;
|
||||
typedef Firebird::HalfStaticArray<UCHAR, 64> Bpb;
|
||||
Bpb m_defaultBpb;
|
||||
ISC_QUAD m_genId;
|
||||
ULONG m_messageSize, m_alignedMessage, m_alignment, m_flags, m_detailed, m_bufferSize, m_lastBlob;
|
||||
bool m_setBlobSize;
|
||||
UCHAR m_blobPolicy;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // DSQL_BATCH_H
|
@ -6470,14 +6470,16 @@ const StmtNode* PostEventNode::execute(thread_db* tdbb, jrd_req* request, ExeSta
|
||||
|
||||
|
||||
static RegisterNode<ReceiveNode> regReceiveNode(blr_receive);
|
||||
static RegisterNode<ReceiveNode> regReceiveNodeBatch(blr_receive_batch);
|
||||
|
||||
DmlNode* ReceiveNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
||||
DmlNode* ReceiveNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
|
||||
{
|
||||
ReceiveNode* node = FB_NEW_POOL(pool) ReceiveNode(pool);
|
||||
|
||||
USHORT n = csb->csb_blr_reader.getByte();
|
||||
node->message = csb->csb_rpt[n].csb_message;
|
||||
node->statement = PAR_parse_stmt(tdbb, csb);
|
||||
node->batchFlag = (blrOp == blr_receive_batch);
|
||||
|
||||
return node;
|
||||
}
|
||||
@ -6493,6 +6495,7 @@ string ReceiveNode::internalPrint(NodePrinter& printer) const
|
||||
|
||||
NODE_PRINT(printer, statement);
|
||||
NODE_PRINT(printer, message);
|
||||
NODE_PRINT(printer, batchFlag);
|
||||
|
||||
return "ReceiveNode";
|
||||
}
|
||||
@ -6522,6 +6525,11 @@ const StmtNode* ReceiveNode::execute(thread_db* /*tdbb*/, jrd_req* request, ExeS
|
||||
{
|
||||
switch (request->req_operation)
|
||||
{
|
||||
case jrd_req::req_return:
|
||||
if (!(request->req_batch && batchFlag))
|
||||
break;
|
||||
// fall into
|
||||
|
||||
case jrd_req::req_evaluate:
|
||||
request->req_operation = jrd_req::req_receive;
|
||||
request->req_message = message;
|
||||
@ -6533,8 +6541,10 @@ const StmtNode* ReceiveNode::execute(thread_db* /*tdbb*/, jrd_req* request, ExeS
|
||||
return statement;
|
||||
|
||||
default:
|
||||
return parentStmt;
|
||||
break;
|
||||
}
|
||||
|
||||
return parentStmt;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1223,7 +1223,8 @@ public:
|
||||
explicit ReceiveNode(MemoryPool& pool)
|
||||
: TypedNode<StmtNode, StmtNode::TYPE_RECEIVE>(pool),
|
||||
statement(NULL),
|
||||
message(NULL)
|
||||
message(NULL),
|
||||
batchFlag(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -1240,6 +1241,7 @@ public:
|
||||
public:
|
||||
NestConst<StmtNode> statement;
|
||||
NestConst<MessageNode> message;
|
||||
bool batchFlag;
|
||||
};
|
||||
|
||||
|
||||
|
@ -69,6 +69,7 @@
|
||||
#include "../common/classes/init.h"
|
||||
#include "../common/utils_proto.h"
|
||||
#include "../common/StatusArg.h"
|
||||
#include "../dsql/DsqlBatch.h"
|
||||
|
||||
#ifdef HAVE_CTYPE_H
|
||||
#include <ctype.h>
|
||||
@ -80,9 +81,6 @@ using namespace Firebird;
|
||||
|
||||
static ULONG get_request_info(thread_db*, dsql_req*, ULONG, UCHAR*);
|
||||
static dsql_dbb* init(Jrd::thread_db*, Jrd::Attachment*);
|
||||
static void map_in_out(Jrd::thread_db*, dsql_req*, bool, const dsql_msg*, IMessageMetadata*, UCHAR*,
|
||||
const UCHAR* = NULL);
|
||||
static USHORT parse_metadata(dsql_req*, IMessageMetadata*, const Array<dsql_par*>&);
|
||||
static dsql_req* prepareRequest(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, bool);
|
||||
static dsql_req* prepareStatement(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, bool);
|
||||
static UCHAR* put_item(UCHAR, const USHORT, const UCHAR*, UCHAR*, const UCHAR* const);
|
||||
@ -212,7 +210,7 @@ DsqlCursor* DSQL_open(thread_db* tdbb,
|
||||
if (!reqTypeWithCursor(statement->getType()))
|
||||
Arg::Gds(isc_no_cursor).raise();
|
||||
|
||||
// Validate cursor being not already open
|
||||
// Validate cursor or batch being not already open
|
||||
|
||||
if (request->req_cursor)
|
||||
{
|
||||
@ -220,6 +218,12 @@ DsqlCursor* DSQL_open(thread_db* tdbb,
|
||||
Arg::Gds(isc_dsql_cursor_open_err));
|
||||
}
|
||||
|
||||
if (request->req_batch)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
|
||||
Arg::Gds(isc_random) << "Request has active batch");
|
||||
}
|
||||
|
||||
request->req_transaction = *tra_handle;
|
||||
request->execute(tdbb, tra_handle, in_meta, in_msg, out_meta, NULL, false);
|
||||
|
||||
@ -297,7 +301,7 @@ bool DsqlDmlRequest::fetch(thread_db* tdbb, UCHAR* msgBuffer)
|
||||
return false;
|
||||
}
|
||||
|
||||
map_in_out(tdbb, this, true, message, delayedFormat, msgBuffer);
|
||||
mapInOut(tdbb, true, message, delayedFormat, msgBuffer);
|
||||
delayedFormat = NULL;
|
||||
|
||||
trace.fetch(false, ITracePlugin::RESULT_SUCCESS);
|
||||
@ -681,9 +685,9 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
|
||||
|
||||
const dsql_msg* message = statement->getSendMsg();
|
||||
if (message)
|
||||
map_in_out(tdbb, this, false, message, inMetadata, NULL, inMsg);
|
||||
mapInOut(tdbb, false, message, inMetadata, NULL, inMsg);
|
||||
|
||||
// we need to map_in_out before tracing of execution start to let trace
|
||||
// we need to mapInOut() before tracing of execution start to let trace
|
||||
// manager know statement parameters values
|
||||
TraceDSQLExecute trace(req_dbb->dbb_attachment, this);
|
||||
|
||||
@ -721,7 +725,7 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
|
||||
}
|
||||
|
||||
if (outMetadata && message)
|
||||
parse_metadata(this, outMetadata, message->msg_parameters);
|
||||
parseMetadata(outMetadata, message->msg_parameters);
|
||||
|
||||
if ((outMsg && message) || isBlock)
|
||||
{
|
||||
@ -744,7 +748,7 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
|
||||
JRD_receive(tdbb, req_request, message->msg_number, message->msg_length, msgBuffer);
|
||||
|
||||
if (outMsg)
|
||||
map_in_out(tdbb, this, true, message, NULL, outMsg);
|
||||
mapInOut(tdbb, true, message, NULL, outMsg);
|
||||
|
||||
// if this is a singleton select, make sure there's in fact one record
|
||||
|
||||
@ -1015,7 +1019,7 @@ static dsql_dbb* init(thread_db* tdbb, Jrd::Attachment* attachment)
|
||||
|
||||
/**
|
||||
|
||||
map_in_out
|
||||
mapInOut
|
||||
|
||||
@brief Map data from external world into message or
|
||||
from message to external world.
|
||||
@ -1029,10 +1033,10 @@ static dsql_dbb* init(thread_db* tdbb, Jrd::Attachment* attachment)
|
||||
@param in_dsql_msg_buf
|
||||
|
||||
**/
|
||||
static void map_in_out(thread_db* tdbb, dsql_req* request, bool toExternal, const dsql_msg* message,
|
||||
void dsql_req::mapInOut(thread_db* tdbb, bool toExternal, const dsql_msg* message,
|
||||
IMessageMetadata* meta, UCHAR* dsql_msg_buf, const UCHAR* in_dsql_msg_buf)
|
||||
{
|
||||
USHORT count = parse_metadata(request, meta, message->msg_parameters);
|
||||
USHORT count = parseMetadata(meta, message->msg_parameters);
|
||||
|
||||
// Sanity check
|
||||
|
||||
@ -1067,7 +1071,7 @@ static void map_in_out(thread_db* tdbb, dsql_req* request, bool toExternal, cons
|
||||
// Make sure the message given to us is long enough
|
||||
|
||||
dsc desc;
|
||||
if (!request->req_user_descs.get(parameter, desc))
|
||||
if (!req_user_descs.get(parameter, desc))
|
||||
desc.clear();
|
||||
|
||||
/***
|
||||
@ -1085,14 +1089,14 @@ static void map_in_out(thread_db* tdbb, dsql_req* request, bool toExternal, cons
|
||||
Arg::Gds(isc_dsql_sqlvar_index) << Arg::Num(parameter->par_index-1));
|
||||
}
|
||||
|
||||
UCHAR* msgBuffer = request->req_msg_buffers[parameter->par_message->msg_buffer_number];
|
||||
UCHAR* msgBuffer = req_msg_buffers[parameter->par_message->msg_buffer_number];
|
||||
|
||||
SSHORT* flag = NULL;
|
||||
dsql_par* const null_ind = parameter->par_null;
|
||||
if (null_ind != NULL)
|
||||
{
|
||||
dsc userNullDesc;
|
||||
if (!request->req_user_descs.get(null_ind, userNullDesc))
|
||||
if (!req_user_descs.get(null_ind, userNullDesc))
|
||||
userNullDesc.clear();
|
||||
|
||||
const ULONG null_offset = (IPTR) userNullDesc.dsc_address;
|
||||
@ -1155,7 +1159,7 @@ static void map_in_out(thread_db* tdbb, dsql_req* request, bool toExternal, cons
|
||||
Arg::Gds(isc_dsql_wrong_param_num) << Arg::Num(count) <<Arg::Num(count2));
|
||||
}
|
||||
|
||||
const DsqlCompiledStatement* statement = request->getStatement();
|
||||
const DsqlCompiledStatement* statement = getStatement();
|
||||
const dsql_par* parameter;
|
||||
|
||||
const dsql_par* dbkey;
|
||||
@ -1164,7 +1168,7 @@ static void map_in_out(thread_db* tdbb, dsql_req* request, bool toExternal, cons
|
||||
{
|
||||
UCHAR* parentMsgBuffer = statement->getParentRequest() ?
|
||||
statement->getParentRequest()->req_msg_buffers[dbkey->par_message->msg_buffer_number] : NULL;
|
||||
UCHAR* msgBuffer = request->req_msg_buffers[parameter->par_message->msg_buffer_number];
|
||||
UCHAR* msgBuffer = req_msg_buffers[parameter->par_message->msg_buffer_number];
|
||||
|
||||
fb_assert(parentMsgBuffer);
|
||||
|
||||
@ -1194,7 +1198,7 @@ static void map_in_out(thread_db* tdbb, dsql_req* request, bool toExternal, cons
|
||||
UCHAR* parentMsgBuffer = statement->getParentRequest() ?
|
||||
statement->getParentRequest()->req_msg_buffers[rec_version->par_message->msg_buffer_number] :
|
||||
NULL;
|
||||
UCHAR* msgBuffer = request->req_msg_buffers[parameter->par_message->msg_buffer_number];
|
||||
UCHAR* msgBuffer = req_msg_buffers[parameter->par_message->msg_buffer_number];
|
||||
|
||||
fb_assert(parentMsgBuffer);
|
||||
|
||||
@ -1221,7 +1225,7 @@ static void map_in_out(thread_db* tdbb, dsql_req* request, bool toExternal, cons
|
||||
|
||||
/**
|
||||
|
||||
parse_metadata
|
||||
parseMetadata
|
||||
|
||||
@brief Parse the message of a request.
|
||||
|
||||
@ -1231,8 +1235,7 @@ static void map_in_out(thread_db* tdbb, dsql_req* request, bool toExternal, cons
|
||||
@param parameters_list
|
||||
|
||||
**/
|
||||
static USHORT parse_metadata(dsql_req* request, IMessageMetadata* meta,
|
||||
const Array<dsql_par*>& parameters_list)
|
||||
USHORT dsql_req::parseMetadata(IMessageMetadata* meta, const Array<dsql_par*>& parameters_list)
|
||||
{
|
||||
HalfStaticArray<const dsql_par*, 16> parameters;
|
||||
|
||||
@ -1306,7 +1309,7 @@ static USHORT parse_metadata(dsql_req* request, IMessageMetadata* meta,
|
||||
if (desc.isText() && desc.getTextType() == ttype_dynamic)
|
||||
desc.setTextType(ttype_none);
|
||||
|
||||
request->req_user_descs.put(parameter, desc);
|
||||
req_user_descs.put(parameter, desc);
|
||||
|
||||
dsql_par* null = parameter->par_null;
|
||||
if (null)
|
||||
@ -1317,7 +1320,7 @@ static USHORT parse_metadata(dsql_req* request, IMessageMetadata* meta,
|
||||
desc.dsc_length = sizeof(SSHORT);
|
||||
desc.dsc_address = (UCHAR*)(IPTR) nullOffset;
|
||||
|
||||
request->req_user_descs.put(null, desc);
|
||||
req_user_descs.put(null, desc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1564,6 +1567,7 @@ dsql_req::dsql_req(MemoryPool& pool)
|
||||
req_msg_buffers(req_pool),
|
||||
req_cursor_name(req_pool),
|
||||
req_cursor(NULL),
|
||||
req_batch(NULL),
|
||||
req_user_descs(req_pool),
|
||||
req_traced(false),
|
||||
req_timeout(0)
|
||||
@ -1618,10 +1622,10 @@ void dsql_req::setTimeout(unsigned int timeOut)
|
||||
req_timeout = timeOut;
|
||||
}
|
||||
|
||||
void dsql_req::setupTimer(thread_db* tdbb)
|
||||
TimeoutTimer* dsql_req::setupTimer(thread_db* tdbb)
|
||||
{
|
||||
if (statement->getFlags() & JrdStatement::FLAG_INTERNAL)
|
||||
return;
|
||||
return req_timer;
|
||||
|
||||
if (req_request)
|
||||
{
|
||||
@ -1632,7 +1636,7 @@ void dsql_req::setupTimer(thread_db* tdbb)
|
||||
{
|
||||
if (req_timer)
|
||||
req_timer->setup(0, 0);
|
||||
return;
|
||||
return req_timer;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1672,6 +1676,8 @@ void dsql_req::setupTimer(thread_db* tdbb)
|
||||
req_timer->setup(timeOut, toutErr);
|
||||
req_timer->start();
|
||||
}
|
||||
|
||||
return req_timer;
|
||||
}
|
||||
|
||||
// Release a dynamic request.
|
||||
@ -1708,6 +1714,12 @@ void dsql_req::destroy(thread_db* tdbb, dsql_req* request, bool drop)
|
||||
if (request->req_cursor)
|
||||
DsqlCursor::close(tdbb, request->req_cursor);
|
||||
|
||||
if (request->req_batch)
|
||||
{
|
||||
delete request->req_batch;
|
||||
request->req_batch = nullptr;
|
||||
}
|
||||
|
||||
Jrd::Attachment* att = request->req_dbb->dbb_attachment;
|
||||
const bool need_trace_free = request->req_traced && TraceManager::need_dsql_free(att);
|
||||
if (need_trace_free)
|
||||
@ -1959,6 +1971,14 @@ static void sql_info(thread_db* tdbb,
|
||||
return;
|
||||
break;
|
||||
|
||||
case isc_info_sql_stmt_blob_align:
|
||||
value = DsqlBatch::BLOB_STREAM_ALIGN;
|
||||
|
||||
length = put_vax_long(buffer, value);
|
||||
if (!(info = put_item(item, length, buffer, info, end_info)))
|
||||
return;
|
||||
break;
|
||||
|
||||
case isc_info_sql_get_plan:
|
||||
case isc_info_sql_explain_plan:
|
||||
{
|
||||
|
@ -598,7 +598,11 @@ public:
|
||||
|
||||
// Evaluate actual timeout value, consider config- and session-level timeout values,
|
||||
// setup and start timer
|
||||
void setupTimer(thread_db* tdbb);
|
||||
TimeoutTimer* setupTimer(thread_db* tdbb);
|
||||
|
||||
USHORT parseMetadata(Firebird::IMessageMetadata* meta, const Firebird::Array<dsql_par*>& parameters_list);
|
||||
void mapInOut(Jrd::thread_db* tdbb, bool toExternal, const dsql_msg* message, Firebird::IMessageMetadata* meta,
|
||||
UCHAR* dsql_msg_buf, const UCHAR* in_dsql_msg_buf = NULL);
|
||||
|
||||
static void destroy(thread_db* tdbb, dsql_req* request, bool drop);
|
||||
|
||||
@ -616,6 +620,7 @@ public:
|
||||
Firebird::Array<UCHAR*> req_msg_buffers;
|
||||
Firebird::string req_cursor_name; // Cursor name, if any
|
||||
DsqlCursor* req_cursor; // Open cursor, if any
|
||||
DsqlBatch* req_batch; // Active batch, if any
|
||||
Firebird::GenericMap<Firebird::NonPooled<const dsql_par*, dsc> > req_user_descs; // SQLDA data type
|
||||
|
||||
Firebird::AutoPtr<Jrd::RuntimeStatistics> req_fetch_baseline; // State of request performance counters when we reported it last time
|
||||
|
@ -292,7 +292,7 @@ void GEN_request(DsqlCompilerScratch* scratch, DmlNode* node)
|
||||
else
|
||||
{
|
||||
GEN_port(scratch, message);
|
||||
scratch->appendUChar(blr_receive);
|
||||
scratch->appendUChar(blr_receive_batch);
|
||||
scratch->appendUChar(message->msg_number);
|
||||
}
|
||||
message = statement->getReceiveMsg();
|
||||
|
@ -365,6 +365,10 @@ interface MessageMetadata : ReferenceCounted
|
||||
|
||||
MetadataBuilder getBuilder(Status status);
|
||||
uint getMessageLength(Status status);
|
||||
|
||||
version: // 3.0 => 4.0
|
||||
uint getAlignment(Status status);
|
||||
uint getAlignedLength(Status status);
|
||||
}
|
||||
|
||||
interface MetadataBuilder : ReferenceCounted
|
||||
@ -447,8 +451,66 @@ version: // 3.0 => 4.0
|
||||
// Statement execution timeout, milliseconds
|
||||
uint getTimeout(Status status);
|
||||
void setTimeout(Status status, uint timeOut);
|
||||
|
||||
// Batch API
|
||||
Batch createBatch(Status status, MessageMetadata inMetadata, uint parLength, const uchar* par);
|
||||
|
||||
/*
|
||||
Pipe createPipe(Status status, Transaction transaction, MessageMetadata inMetadata,
|
||||
void* inBuffer, MessageMetadata outMetadata, uint parLength, const uchar* par);
|
||||
*/
|
||||
}
|
||||
|
||||
interface Batch : ReferenceCounted
|
||||
{
|
||||
const uchar VERSION1 = 1; // Tag for parameters block
|
||||
|
||||
const uchar TAG_MULTIERROR = 1; // Can have >1 buffers with errors
|
||||
const uchar TAG_RECORD_COUNTS = 2; // Per-record modified records accountung
|
||||
const uchar TAG_BUFFER_BYTES_SIZE = 3; // Maximum possible buffer size
|
||||
const uchar TAG_BLOB_POLICY = 4; // What policy is used to store blobs
|
||||
const uchar TAG_DETAILED_ERRORS = 5; // How many vectors with detailed error info are stored
|
||||
|
||||
const uchar BLOB_NONE = 0; // Blobs can't be used
|
||||
const uchar BLOB_ID_ENGINE = 1; // Blobs are added one by one, IDs are generated by firebird
|
||||
const uchar BLOB_ID_USER = 2; // Blobs are added one by one, IDs are generated by user
|
||||
const uchar BLOB_STREAM = 3; // Blobs are added in a stream, IDs are generated by user
|
||||
|
||||
const uint BLOB_SEGHDR_ALIGN = 2; // Alignment of segment header in the stream
|
||||
|
||||
void add(Status status, uint count, const void* inBuffer);
|
||||
void addBlob(Status status, uint length, const void* inBuffer, ISC_QUAD* blobId, uint parLength, const uchar* par);
|
||||
void appendBlobData(Status status, uint length, const void* inBuffer);
|
||||
void addBlobStream(Status status, uint length, const void* inBuffer);
|
||||
void registerBlob(Status status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId);
|
||||
BatchCompletionState execute(Status status, Transaction transaction);
|
||||
void cancel(Status status);
|
||||
uint getBlobAlignment(Status status);
|
||||
MessageMetadata getMetadata(Status status);
|
||||
void setDefaultBpb(Status status, uint parLength, const uchar* par);
|
||||
}
|
||||
|
||||
interface BatchCompletionState : Disposable
|
||||
{
|
||||
const int EXECUTE_FAILED = -1; // Error happened when processing record
|
||||
const int SUCCESS_NO_INFO = -2; // Record update info was not collected
|
||||
const uint NO_MORE_ERRORS = 0xFFFFFFFF; // Special value returned by findError()
|
||||
|
||||
uint getSize(Status status);
|
||||
int getState(Status status, uint pos);
|
||||
uint findError(Status status, uint pos);
|
||||
void getStatus(Status status, Status to, uint pos);
|
||||
}
|
||||
|
||||
/*
|
||||
interface Pipe : ReferenceCounted
|
||||
{
|
||||
uint add(Status status, uint count, void* inBuffer);
|
||||
uint fetch(Status status, uint count, void* outBuffer);
|
||||
void close(Status status);
|
||||
}
|
||||
*/
|
||||
|
||||
interface Request : ReferenceCounted
|
||||
{
|
||||
void receive(Status status, int level, uint msgType,
|
||||
@ -533,6 +595,15 @@ version: // 3.0 => 4.0
|
||||
// Statement execution timeout, milliseconds
|
||||
uint getStatementTimeout(Status status);
|
||||
void setStatementTimeout(Status status, uint timeOut);
|
||||
|
||||
// Batch API
|
||||
Batch createBatch(Status status, Transaction transaction, uint stmtLength, const string sqlStmt,
|
||||
uint dialect, MessageMetadata inMetadata, uint parLength, const uchar* par);
|
||||
/*
|
||||
Pipe createPipe(Status status, uint stmtLength, const string sqlStmt, uint dialect,
|
||||
Transaction transaction, MessageMetadata inMetadata, void* inBuffer,
|
||||
MessageMetadata outMetadata, uint parLength, const uchar* par);
|
||||
*/
|
||||
}
|
||||
|
||||
interface Service : ReferenceCounted
|
||||
@ -1004,6 +1075,8 @@ interface XpbBuilder : Disposable
|
||||
const uint SPB_ATTACH = 2;
|
||||
const uint SPB_START = 3;
|
||||
const uint TPB = 4;
|
||||
const uint BATCH = 5;
|
||||
const uint BPB = 6;
|
||||
|
||||
// removing data
|
||||
void clear(Status status);
|
||||
|
@ -49,6 +49,8 @@ namespace Firebird
|
||||
class IMetadataBuilder;
|
||||
class IResultSet;
|
||||
class IStatement;
|
||||
class IBatch;
|
||||
class IBatchCompletionState;
|
||||
class IRequest;
|
||||
class IEvents;
|
||||
class IEventBlock;
|
||||
@ -1186,6 +1188,8 @@ namespace Firebird
|
||||
unsigned (CLOOP_CARG *getNullOffset)(IMessageMetadata* self, IStatus* status, unsigned index) throw();
|
||||
IMetadataBuilder* (CLOOP_CARG *getBuilder)(IMessageMetadata* self, IStatus* status) throw();
|
||||
unsigned (CLOOP_CARG *getMessageLength)(IMessageMetadata* self, IStatus* status) throw();
|
||||
unsigned (CLOOP_CARG *getAlignment)(IMessageMetadata* self, IStatus* status) throw();
|
||||
unsigned (CLOOP_CARG *getAlignedLength)(IMessageMetadata* self, IStatus* status) throw();
|
||||
};
|
||||
|
||||
protected:
|
||||
@ -1199,7 +1203,7 @@ namespace Firebird
|
||||
}
|
||||
|
||||
public:
|
||||
static const unsigned VERSION = 3;
|
||||
static const unsigned VERSION = 4;
|
||||
|
||||
template <typename StatusType> unsigned getCount(StatusType* status)
|
||||
{
|
||||
@ -1320,6 +1324,34 @@ namespace Firebird
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename StatusType> unsigned getAlignment(StatusType* status)
|
||||
{
|
||||
if (cloopVTable->version < 4)
|
||||
{
|
||||
StatusType::setVersionError(status, "IMessageMetadata", cloopVTable->version, 4);
|
||||
StatusType::checkException(status);
|
||||
return 0;
|
||||
}
|
||||
StatusType::clearException(status);
|
||||
unsigned ret = static_cast<VTable*>(this->cloopVTable)->getAlignment(this, status);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename StatusType> unsigned getAlignedLength(StatusType* status)
|
||||
{
|
||||
if (cloopVTable->version < 4)
|
||||
{
|
||||
StatusType::setVersionError(status, "IMessageMetadata", cloopVTable->version, 4);
|
||||
StatusType::checkException(status);
|
||||
return 0;
|
||||
}
|
||||
StatusType::clearException(status);
|
||||
unsigned ret = static_cast<VTable*>(this->cloopVTable)->getAlignedLength(this, status);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class IMetadataBuilder : public IReferenceCounted
|
||||
@ -1561,6 +1593,7 @@ namespace Firebird
|
||||
unsigned (CLOOP_CARG *getFlags)(IStatement* self, IStatus* status) throw();
|
||||
unsigned (CLOOP_CARG *getTimeout)(IStatement* self, IStatus* status) throw();
|
||||
void (CLOOP_CARG *setTimeout)(IStatement* self, IStatus* status, unsigned timeOut) throw();
|
||||
IBatch* (CLOOP_CARG *createBatch)(IStatement* self, IStatus* status, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) throw();
|
||||
};
|
||||
|
||||
protected:
|
||||
@ -1701,6 +1734,196 @@ namespace Firebird
|
||||
static_cast<VTable*>(this->cloopVTable)->setTimeout(this, status, timeOut);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
|
||||
template <typename StatusType> IBatch* createBatch(StatusType* status, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
if (cloopVTable->version < 4)
|
||||
{
|
||||
StatusType::setVersionError(status, "IStatement", cloopVTable->version, 4);
|
||||
StatusType::checkException(status);
|
||||
return 0;
|
||||
}
|
||||
StatusType::clearException(status);
|
||||
IBatch* ret = static_cast<VTable*>(this->cloopVTable)->createBatch(this, status, inMetadata, parLength, par);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class IBatch : public IReferenceCounted
|
||||
{
|
||||
public:
|
||||
struct VTable : public IReferenceCounted::VTable
|
||||
{
|
||||
void (CLOOP_CARG *add)(IBatch* self, IStatus* status, unsigned count, const void* inBuffer) throw();
|
||||
void (CLOOP_CARG *addBlob)(IBatch* self, IStatus* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId, unsigned parLength, const unsigned char* par) throw();
|
||||
void (CLOOP_CARG *appendBlobData)(IBatch* self, IStatus* status, unsigned length, const void* inBuffer) throw();
|
||||
void (CLOOP_CARG *addBlobStream)(IBatch* self, IStatus* status, unsigned length, const void* inBuffer) throw();
|
||||
void (CLOOP_CARG *registerBlob)(IBatch* self, IStatus* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId) throw();
|
||||
IBatchCompletionState* (CLOOP_CARG *execute)(IBatch* self, IStatus* status, ITransaction* transaction) throw();
|
||||
void (CLOOP_CARG *cancel)(IBatch* self, IStatus* status) throw();
|
||||
unsigned (CLOOP_CARG *getBlobAlignment)(IBatch* self, IStatus* status) throw();
|
||||
IMessageMetadata* (CLOOP_CARG *getMetadata)(IBatch* self, IStatus* status) throw();
|
||||
void (CLOOP_CARG *setDefaultBpb)(IBatch* self, IStatus* status, unsigned parLength, const unsigned char* par) throw();
|
||||
};
|
||||
|
||||
protected:
|
||||
IBatch(DoNotInherit)
|
||||
: IReferenceCounted(DoNotInherit())
|
||||
{
|
||||
}
|
||||
|
||||
~IBatch()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
static const unsigned VERSION = 3;
|
||||
|
||||
static const unsigned char VERSION1 = 1;
|
||||
static const unsigned char TAG_MULTIERROR = 1;
|
||||
static const unsigned char TAG_RECORD_COUNTS = 2;
|
||||
static const unsigned char TAG_BUFFER_BYTES_SIZE = 3;
|
||||
static const unsigned char TAG_BLOB_POLICY = 4;
|
||||
static const unsigned char TAG_DETAILED_ERRORS = 5;
|
||||
static const unsigned char BLOB_NONE = 0;
|
||||
static const unsigned char BLOB_ID_ENGINE = 1;
|
||||
static const unsigned char BLOB_ID_USER = 2;
|
||||
static const unsigned char BLOB_STREAM = 3;
|
||||
static const unsigned BLOB_SEGHDR_ALIGN = 2;
|
||||
|
||||
template <typename StatusType> void add(StatusType* status, unsigned count, const void* inBuffer)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
static_cast<VTable*>(this->cloopVTable)->add(this, status, count, inBuffer);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
|
||||
template <typename StatusType> void addBlob(StatusType* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
static_cast<VTable*>(this->cloopVTable)->addBlob(this, status, length, inBuffer, blobId, parLength, par);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
|
||||
template <typename StatusType> void appendBlobData(StatusType* status, unsigned length, const void* inBuffer)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
static_cast<VTable*>(this->cloopVTable)->appendBlobData(this, status, length, inBuffer);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
|
||||
template <typename StatusType> void addBlobStream(StatusType* status, unsigned length, const void* inBuffer)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
static_cast<VTable*>(this->cloopVTable)->addBlobStream(this, status, length, inBuffer);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
|
||||
template <typename StatusType> void registerBlob(StatusType* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
static_cast<VTable*>(this->cloopVTable)->registerBlob(this, status, existingBlob, blobId);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
|
||||
template <typename StatusType> IBatchCompletionState* execute(StatusType* status, ITransaction* transaction)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
IBatchCompletionState* ret = static_cast<VTable*>(this->cloopVTable)->execute(this, status, transaction);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename StatusType> void cancel(StatusType* status)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
static_cast<VTable*>(this->cloopVTable)->cancel(this, status);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
|
||||
template <typename StatusType> unsigned getBlobAlignment(StatusType* status)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
unsigned ret = static_cast<VTable*>(this->cloopVTable)->getBlobAlignment(this, status);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename StatusType> IMessageMetadata* getMetadata(StatusType* status)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
IMessageMetadata* ret = static_cast<VTable*>(this->cloopVTable)->getMetadata(this, status);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename StatusType> void setDefaultBpb(StatusType* status, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
static_cast<VTable*>(this->cloopVTable)->setDefaultBpb(this, status, parLength, par);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
};
|
||||
|
||||
class IBatchCompletionState : public IDisposable
|
||||
{
|
||||
public:
|
||||
struct VTable : public IDisposable::VTable
|
||||
{
|
||||
unsigned (CLOOP_CARG *getSize)(IBatchCompletionState* self, IStatus* status) throw();
|
||||
int (CLOOP_CARG *getState)(IBatchCompletionState* self, IStatus* status, unsigned pos) throw();
|
||||
unsigned (CLOOP_CARG *findError)(IBatchCompletionState* self, IStatus* status, unsigned pos) throw();
|
||||
void (CLOOP_CARG *getStatus)(IBatchCompletionState* self, IStatus* status, IStatus* to, unsigned pos) throw();
|
||||
};
|
||||
|
||||
protected:
|
||||
IBatchCompletionState(DoNotInherit)
|
||||
: IDisposable(DoNotInherit())
|
||||
{
|
||||
}
|
||||
|
||||
~IBatchCompletionState()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
static const unsigned VERSION = 3;
|
||||
|
||||
static const int EXECUTE_FAILED = -1;
|
||||
static const int SUCCESS_NO_INFO = -2;
|
||||
static const unsigned NO_MORE_ERRORS = -1;
|
||||
|
||||
template <typename StatusType> unsigned getSize(StatusType* status)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
unsigned ret = static_cast<VTable*>(this->cloopVTable)->getSize(this, status);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename StatusType> int getState(StatusType* status, unsigned pos)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
int ret = static_cast<VTable*>(this->cloopVTable)->getState(this, status, pos);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename StatusType> unsigned findError(StatusType* status, unsigned pos)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
unsigned ret = static_cast<VTable*>(this->cloopVTable)->findError(this, status, pos);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename StatusType> void getStatus(StatusType* status, IStatus* to, unsigned pos)
|
||||
{
|
||||
StatusType::clearException(status);
|
||||
static_cast<VTable*>(this->cloopVTable)->getStatus(this, status, to, pos);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
};
|
||||
|
||||
class IRequest : public IReferenceCounted
|
||||
@ -1898,6 +2121,7 @@ namespace Firebird
|
||||
void (CLOOP_CARG *setIdleTimeout)(IAttachment* self, IStatus* status, unsigned timeOut) throw();
|
||||
unsigned (CLOOP_CARG *getStatementTimeout)(IAttachment* self, IStatus* status) throw();
|
||||
void (CLOOP_CARG *setStatementTimeout)(IAttachment* self, IStatus* status, unsigned timeOut) throw();
|
||||
IBatch* (CLOOP_CARG *createBatch)(IAttachment* self, IStatus* status, ITransaction* transaction, unsigned stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) throw();
|
||||
};
|
||||
|
||||
protected:
|
||||
@ -2102,6 +2326,20 @@ namespace Firebird
|
||||
static_cast<VTable*>(this->cloopVTable)->setStatementTimeout(this, status, timeOut);
|
||||
StatusType::checkException(status);
|
||||
}
|
||||
|
||||
template <typename StatusType> IBatch* createBatch(StatusType* status, ITransaction* transaction, unsigned stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
if (cloopVTable->version < 4)
|
||||
{
|
||||
StatusType::setVersionError(status, "IAttachment", cloopVTable->version, 4);
|
||||
StatusType::checkException(status);
|
||||
return 0;
|
||||
}
|
||||
StatusType::clearException(status);
|
||||
IBatch* ret = static_cast<VTable*>(this->cloopVTable)->createBatch(this, status, transaction, stmtLength, sqlStmt, dialect, inMetadata, parLength, par);
|
||||
StatusType::checkException(status);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class IService : public IReferenceCounted
|
||||
@ -3932,6 +4170,8 @@ namespace Firebird
|
||||
static const unsigned SPB_ATTACH = 2;
|
||||
static const unsigned SPB_START = 3;
|
||||
static const unsigned TPB = 4;
|
||||
static const unsigned BATCH = 5;
|
||||
static const unsigned BPB = 6;
|
||||
|
||||
template <typename StatusType> void clear(StatusType* status)
|
||||
{
|
||||
@ -7705,6 +7945,8 @@ namespace Firebird
|
||||
this->getNullOffset = &Name::cloopgetNullOffsetDispatcher;
|
||||
this->getBuilder = &Name::cloopgetBuilderDispatcher;
|
||||
this->getMessageLength = &Name::cloopgetMessageLengthDispatcher;
|
||||
this->getAlignment = &Name::cloopgetAlignmentDispatcher;
|
||||
this->getAlignedLength = &Name::cloopgetAlignedLengthDispatcher;
|
||||
}
|
||||
} vTable;
|
||||
|
||||
@ -7936,6 +8178,36 @@ namespace Firebird
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned CLOOP_CARG cloopgetAlignmentDispatcher(IMessageMetadata* self, IStatus* status) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::getAlignment(&status2);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<unsigned>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned CLOOP_CARG cloopgetAlignedLengthDispatcher(IMessageMetadata* self, IStatus* status) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::getAlignedLength(&status2);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<unsigned>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopaddRefDispatcher(IReferenceCounted* self) throw()
|
||||
{
|
||||
try
|
||||
@ -7990,6 +8262,8 @@ namespace Firebird
|
||||
virtual unsigned getNullOffset(StatusType* status, unsigned index) = 0;
|
||||
virtual IMetadataBuilder* getBuilder(StatusType* status) = 0;
|
||||
virtual unsigned getMessageLength(StatusType* status) = 0;
|
||||
virtual unsigned getAlignment(StatusType* status) = 0;
|
||||
virtual unsigned getAlignedLength(StatusType* status) = 0;
|
||||
};
|
||||
|
||||
template <typename Name, typename StatusType, typename Base>
|
||||
@ -8491,6 +8765,7 @@ namespace Firebird
|
||||
this->getFlags = &Name::cloopgetFlagsDispatcher;
|
||||
this->getTimeout = &Name::cloopgetTimeoutDispatcher;
|
||||
this->setTimeout = &Name::cloopsetTimeoutDispatcher;
|
||||
this->createBatch = &Name::cloopcreateBatchDispatcher;
|
||||
}
|
||||
} vTable;
|
||||
|
||||
@ -8688,6 +8963,21 @@ namespace Firebird
|
||||
}
|
||||
}
|
||||
|
||||
static IBatch* CLOOP_CARG cloopcreateBatchDispatcher(IStatement* self, IStatus* status, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::createBatch(&status2, inMetadata, parLength, par);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<IBatch*>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopaddRefDispatcher(IReferenceCounted* self) throw()
|
||||
{
|
||||
try
|
||||
@ -8740,6 +9030,347 @@ namespace Firebird
|
||||
virtual unsigned getFlags(StatusType* status) = 0;
|
||||
virtual unsigned getTimeout(StatusType* status) = 0;
|
||||
virtual void setTimeout(StatusType* status, unsigned timeOut) = 0;
|
||||
virtual IBatch* createBatch(StatusType* status, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) = 0;
|
||||
};
|
||||
|
||||
template <typename Name, typename StatusType, typename Base>
|
||||
class IBatchBaseImpl : public Base
|
||||
{
|
||||
public:
|
||||
typedef IBatch Declaration;
|
||||
|
||||
IBatchBaseImpl(DoNotInherit = DoNotInherit())
|
||||
{
|
||||
static struct VTableImpl : Base::VTable
|
||||
{
|
||||
VTableImpl()
|
||||
{
|
||||
this->version = Base::VERSION;
|
||||
this->addRef = &Name::cloopaddRefDispatcher;
|
||||
this->release = &Name::cloopreleaseDispatcher;
|
||||
this->add = &Name::cloopaddDispatcher;
|
||||
this->addBlob = &Name::cloopaddBlobDispatcher;
|
||||
this->appendBlobData = &Name::cloopappendBlobDataDispatcher;
|
||||
this->addBlobStream = &Name::cloopaddBlobStreamDispatcher;
|
||||
this->registerBlob = &Name::cloopregisterBlobDispatcher;
|
||||
this->execute = &Name::cloopexecuteDispatcher;
|
||||
this->cancel = &Name::cloopcancelDispatcher;
|
||||
this->getBlobAlignment = &Name::cloopgetBlobAlignmentDispatcher;
|
||||
this->getMetadata = &Name::cloopgetMetadataDispatcher;
|
||||
this->setDefaultBpb = &Name::cloopsetDefaultBpbDispatcher;
|
||||
}
|
||||
} vTable;
|
||||
|
||||
this->cloopVTable = &vTable;
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopaddDispatcher(IBatch* self, IStatus* status, unsigned count, const void* inBuffer) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::add(&status2, count, inBuffer);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopaddBlobDispatcher(IBatch* self, IStatus* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId, unsigned parLength, const unsigned char* par) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::addBlob(&status2, length, inBuffer, blobId, parLength, par);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopappendBlobDataDispatcher(IBatch* self, IStatus* status, unsigned length, const void* inBuffer) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::appendBlobData(&status2, length, inBuffer);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopaddBlobStreamDispatcher(IBatch* self, IStatus* status, unsigned length, const void* inBuffer) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::addBlobStream(&status2, length, inBuffer);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopregisterBlobDispatcher(IBatch* self, IStatus* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::registerBlob(&status2, existingBlob, blobId);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
}
|
||||
}
|
||||
|
||||
static IBatchCompletionState* CLOOP_CARG cloopexecuteDispatcher(IBatch* self, IStatus* status, ITransaction* transaction) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::execute(&status2, transaction);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<IBatchCompletionState*>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopcancelDispatcher(IBatch* self, IStatus* status) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::cancel(&status2);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned CLOOP_CARG cloopgetBlobAlignmentDispatcher(IBatch* self, IStatus* status) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::getBlobAlignment(&status2);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<unsigned>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static IMessageMetadata* CLOOP_CARG cloopgetMetadataDispatcher(IBatch* self, IStatus* status) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::getMetadata(&status2);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<IMessageMetadata*>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopsetDefaultBpbDispatcher(IBatch* self, IStatus* status, unsigned parLength, const unsigned char* par) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::setDefaultBpb(&status2, parLength, par);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopaddRefDispatcher(IReferenceCounted* self) throw()
|
||||
{
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::addRef();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(0);
|
||||
}
|
||||
}
|
||||
|
||||
static int CLOOP_CARG cloopreleaseDispatcher(IReferenceCounted* self) throw()
|
||||
{
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::release();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(0);
|
||||
return static_cast<int>(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Name, typename StatusType, typename Base = IReferenceCountedImpl<Name, StatusType, Inherit<IVersionedImpl<Name, StatusType, Inherit<IBatch> > > > >
|
||||
class IBatchImpl : public IBatchBaseImpl<Name, StatusType, Base>
|
||||
{
|
||||
protected:
|
||||
IBatchImpl(DoNotInherit = DoNotInherit())
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
virtual ~IBatchImpl()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void add(StatusType* status, unsigned count, const void* inBuffer) = 0;
|
||||
virtual void addBlob(StatusType* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId, unsigned parLength, const unsigned char* par) = 0;
|
||||
virtual void appendBlobData(StatusType* status, unsigned length, const void* inBuffer) = 0;
|
||||
virtual void addBlobStream(StatusType* status, unsigned length, const void* inBuffer) = 0;
|
||||
virtual void registerBlob(StatusType* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId) = 0;
|
||||
virtual IBatchCompletionState* execute(StatusType* status, ITransaction* transaction) = 0;
|
||||
virtual void cancel(StatusType* status) = 0;
|
||||
virtual unsigned getBlobAlignment(StatusType* status) = 0;
|
||||
virtual IMessageMetadata* getMetadata(StatusType* status) = 0;
|
||||
virtual void setDefaultBpb(StatusType* status, unsigned parLength, const unsigned char* par) = 0;
|
||||
};
|
||||
|
||||
template <typename Name, typename StatusType, typename Base>
|
||||
class IBatchCompletionStateBaseImpl : public Base
|
||||
{
|
||||
public:
|
||||
typedef IBatchCompletionState Declaration;
|
||||
|
||||
IBatchCompletionStateBaseImpl(DoNotInherit = DoNotInherit())
|
||||
{
|
||||
static struct VTableImpl : Base::VTable
|
||||
{
|
||||
VTableImpl()
|
||||
{
|
||||
this->version = Base::VERSION;
|
||||
this->dispose = &Name::cloopdisposeDispatcher;
|
||||
this->getSize = &Name::cloopgetSizeDispatcher;
|
||||
this->getState = &Name::cloopgetStateDispatcher;
|
||||
this->findError = &Name::cloopfindErrorDispatcher;
|
||||
this->getStatus = &Name::cloopgetStatusDispatcher;
|
||||
}
|
||||
} vTable;
|
||||
|
||||
this->cloopVTable = &vTable;
|
||||
}
|
||||
|
||||
static unsigned CLOOP_CARG cloopgetSizeDispatcher(IBatchCompletionState* self, IStatus* status) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::getSize(&status2);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<unsigned>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static int CLOOP_CARG cloopgetStateDispatcher(IBatchCompletionState* self, IStatus* status, unsigned pos) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::getState(&status2, pos);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<int>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned CLOOP_CARG cloopfindErrorDispatcher(IBatchCompletionState* self, IStatus* status, unsigned pos) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::findError(&status2, pos);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<unsigned>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopgetStatusDispatcher(IBatchCompletionState* self, IStatus* status, IStatus* to, unsigned pos) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::getStatus(&status2, to, pos);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopdisposeDispatcher(IDisposable* self) throw()
|
||||
{
|
||||
try
|
||||
{
|
||||
static_cast<Name*>(self)->Name::dispose();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Name, typename StatusType, typename Base = IDisposableImpl<Name, StatusType, Inherit<IVersionedImpl<Name, StatusType, Inherit<IBatchCompletionState> > > > >
|
||||
class IBatchCompletionStateImpl : public IBatchCompletionStateBaseImpl<Name, StatusType, Base>
|
||||
{
|
||||
protected:
|
||||
IBatchCompletionStateImpl(DoNotInherit = DoNotInherit())
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
virtual ~IBatchCompletionStateImpl()
|
||||
{
|
||||
}
|
||||
|
||||
virtual unsigned getSize(StatusType* status) = 0;
|
||||
virtual int getState(StatusType* status, unsigned pos) = 0;
|
||||
virtual unsigned findError(StatusType* status, unsigned pos) = 0;
|
||||
virtual void getStatus(StatusType* status, IStatus* to, unsigned pos) = 0;
|
||||
};
|
||||
|
||||
template <typename Name, typename StatusType, typename Base>
|
||||
@ -9168,6 +9799,7 @@ namespace Firebird
|
||||
this->setIdleTimeout = &Name::cloopsetIdleTimeoutDispatcher;
|
||||
this->getStatementTimeout = &Name::cloopgetStatementTimeoutDispatcher;
|
||||
this->setStatementTimeout = &Name::cloopsetStatementTimeoutDispatcher;
|
||||
this->createBatch = &Name::cloopcreateBatchDispatcher;
|
||||
}
|
||||
} vTable;
|
||||
|
||||
@ -9494,6 +10126,21 @@ namespace Firebird
|
||||
}
|
||||
}
|
||||
|
||||
static IBatch* CLOOP_CARG cloopcreateBatchDispatcher(IAttachment* self, IStatus* status, ITransaction* transaction, unsigned stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) throw()
|
||||
{
|
||||
StatusType status2(status);
|
||||
|
||||
try
|
||||
{
|
||||
return static_cast<Name*>(self)->Name::createBatch(&status2, transaction, stmtLength, sqlStmt, dialect, inMetadata, parLength, par);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
StatusType::catchException(&status2);
|
||||
return static_cast<IBatch*>(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void CLOOP_CARG cloopaddRefDispatcher(IReferenceCounted* self) throw()
|
||||
{
|
||||
try
|
||||
@ -9555,6 +10202,7 @@ namespace Firebird
|
||||
virtual void setIdleTimeout(StatusType* status, unsigned timeOut) = 0;
|
||||
virtual unsigned getStatementTimeout(StatusType* status) = 0;
|
||||
virtual void setStatementTimeout(StatusType* status, unsigned timeOut) = 0;
|
||||
virtual IBatch* createBatch(StatusType* status, ITransaction* transaction, unsigned stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) = 0;
|
||||
};
|
||||
|
||||
template <typename Name, typename StatusType, typename Base>
|
||||
|
@ -312,7 +312,7 @@ public:
|
||||
Firebird::PathName att_filename; // alias used to attach the database
|
||||
const Firebird::TimeStamp att_timestamp; // Connection date and time
|
||||
Firebird::StringMap att_context_vars; // Context variables for the connection
|
||||
Firebird::Stack<DdlTriggerContext> ddlTriggersContext; // Context variables for DDL trigger event
|
||||
Firebird::Stack<DdlTriggerContext*> ddlTriggersContext; // Context variables for DDL trigger event
|
||||
Firebird::string att_network_protocol; // Network protocol used by client for connection
|
||||
Firebird::string att_remote_address; // Protocol-specific address of remote client
|
||||
SLONG att_remote_pid; // Process id of remote client
|
||||
|
@ -34,6 +34,7 @@ namespace Jrd {
|
||||
class blb;
|
||||
class jrd_tra;
|
||||
class DsqlCursor;
|
||||
class DsqlBatch;
|
||||
class dsql_req;
|
||||
class JrdStatement;
|
||||
class StableAttachmentPart;
|
||||
@ -181,6 +182,44 @@ private:
|
||||
void freeEngineData(Firebird::CheckStatusWrapper* status);
|
||||
};
|
||||
|
||||
class JBatch FB_FINAL :
|
||||
public Firebird::RefCntIface<Firebird::IBatchImpl<JBatch, Firebird::CheckStatusWrapper> >
|
||||
{
|
||||
public:
|
||||
// IBatch implementation
|
||||
int release();
|
||||
void add(Firebird::CheckStatusWrapper* status, unsigned count, const void* inBuffer);
|
||||
void addBlob(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId,
|
||||
unsigned parLength, const unsigned char* par);
|
||||
void appendBlobData(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer);
|
||||
void addBlobStream(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer);
|
||||
void registerBlob(Firebird::CheckStatusWrapper* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId);
|
||||
Firebird::IBatchCompletionState* execute(Firebird::CheckStatusWrapper* status, Firebird::ITransaction* transaction);
|
||||
void cancel(Firebird::CheckStatusWrapper* status);
|
||||
unsigned getBlobAlignment(Firebird::CheckStatusWrapper* status);
|
||||
Firebird::IMessageMetadata* getMetadata(Firebird::CheckStatusWrapper* status);
|
||||
void setDefaultBpb(Firebird::CheckStatusWrapper* status, unsigned parLength, const unsigned char* par);
|
||||
|
||||
public:
|
||||
JBatch(DsqlBatch* handle, JStatement* aStatement);
|
||||
|
||||
StableAttachmentPart* getAttachment();
|
||||
|
||||
DsqlBatch* getHandle() throw()
|
||||
{
|
||||
return batch;
|
||||
}
|
||||
|
||||
void resetHandle()
|
||||
{
|
||||
batch = NULL;
|
||||
}
|
||||
|
||||
private:
|
||||
DsqlBatch* batch;
|
||||
Firebird::RefPtr<JStatement> statement;
|
||||
};
|
||||
|
||||
class JStatement FB_FINAL :
|
||||
public Firebird::RefCntIface<Firebird::IStatementImpl<JStatement, Firebird::CheckStatusWrapper> >
|
||||
{
|
||||
@ -207,6 +246,8 @@ public:
|
||||
|
||||
unsigned int getTimeout(Firebird::CheckStatusWrapper* status);
|
||||
void setTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
|
||||
JBatch* createBatch(Firebird::CheckStatusWrapper* status, Firebird::IMessageMetadata* inMetadata,
|
||||
unsigned parLength, const unsigned char* par);
|
||||
|
||||
public:
|
||||
JStatement(dsql_req* handle, StableAttachmentPart* sa, Firebird::Array<UCHAR>& meta);
|
||||
@ -352,6 +393,9 @@ public:
|
||||
void setIdleTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
|
||||
unsigned int getStatementTimeout(Firebird::CheckStatusWrapper* status);
|
||||
void setStatementTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
|
||||
Firebird::IBatch* createBatch(Firebird::CheckStatusWrapper* status, Firebird::ITransaction* transaction,
|
||||
unsigned stmtLength, const char* sqlStmt, unsigned dialect,
|
||||
Firebird::IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par);
|
||||
|
||||
public:
|
||||
explicit JAttachment(StableAttachmentPart* js);
|
||||
|
@ -2529,44 +2529,44 @@ dsc* evlGetContext(thread_db* tdbb, const SysFunction*, const NestValueArray& ar
|
||||
if (!attachment->ddlTriggersContext.hasData())
|
||||
status_exception::raise(Arg::Gds(isc_sysf_invalid_trig_namespace));
|
||||
|
||||
const DdlTriggerContext& context = Stack<DdlTriggerContext>::const_iterator(
|
||||
const DdlTriggerContext* context = Stack<DdlTriggerContext*>::const_iterator(
|
||||
attachment->ddlTriggersContext).object();
|
||||
|
||||
if (nameStr == EVENT_TYPE_NAME)
|
||||
resultStr = context.eventType;
|
||||
resultStr = context->eventType;
|
||||
else if (nameStr == OBJECT_TYPE_NAME)
|
||||
resultStr = context.objectType;
|
||||
resultStr = context->objectType;
|
||||
else if (nameStr == DDL_EVENT_NAME)
|
||||
resultStr = context.eventType + " " + context.objectType;
|
||||
resultStr = context->eventType + " " + context->objectType;
|
||||
else if (nameStr == OBJECT_NAME)
|
||||
{
|
||||
resultStr = context.objectName.c_str();
|
||||
resultStr = context->objectName.c_str();
|
||||
resultType = ttype_metadata;
|
||||
}
|
||||
else if (nameStr == OLD_OBJECT_NAME)
|
||||
{
|
||||
if (context.oldObjectName.isEmpty())
|
||||
if (context->oldObjectName.isEmpty())
|
||||
return NULL;
|
||||
|
||||
resultStr = context.oldObjectName.c_str();
|
||||
resultStr = context->oldObjectName.c_str();
|
||||
resultType = ttype_metadata;
|
||||
}
|
||||
else if (nameStr == NEW_OBJECT_NAME)
|
||||
{
|
||||
if (context.newObjectName.isEmpty())
|
||||
if (context->newObjectName.isEmpty())
|
||||
return NULL;
|
||||
|
||||
resultStr = context.newObjectName.c_str();
|
||||
resultStr = context->newObjectName.c_str();
|
||||
resultType = ttype_metadata;
|
||||
}
|
||||
else if (nameStr == SQL_TEXT_NAME)
|
||||
{
|
||||
if (context.sqlText.isEmpty())
|
||||
if (context->sqlText.isEmpty())
|
||||
return NULL;
|
||||
|
||||
blb* blob = blb::create(tdbb, transaction, &impure->vlu_misc.vlu_bid);
|
||||
blob->BLB_put_data(tdbb, reinterpret_cast<const UCHAR*>(context.sqlText.c_str()),
|
||||
context.sqlText.length());
|
||||
blob->BLB_put_data(tdbb, reinterpret_cast<const UCHAR*>(context->sqlText.c_str()),
|
||||
context->sqlText.length());
|
||||
blob->BLB_close(tdbb);
|
||||
|
||||
dsc result;
|
||||
|
@ -129,8 +129,9 @@
|
||||
#define blr_maximum (unsigned char)29
|
||||
#define blr_minimum (unsigned char)30
|
||||
#define blr_total (unsigned char)31
|
||||
#define blr_receive_batch (unsigned char)32
|
||||
|
||||
// unused codes: 32..33
|
||||
// unused code: 33
|
||||
|
||||
#define blr_add (unsigned char)34
|
||||
#define blr_subtract (unsigned char)35
|
||||
|
@ -479,6 +479,10 @@ ISC_STATUS ISC_EXPORT fb_dsql_set_timeout(ISC_STATUS*,
|
||||
isc_stmt_handle*,
|
||||
ISC_ULONG);
|
||||
|
||||
/*ISC_STATUS ISC_EXPORT fb_get_statement_interface(ISC_STATUS*,
|
||||
FB_API_HANDLE*,
|
||||
void**);
|
||||
*/
|
||||
void ISC_EXPORT isc_encode_date(const void*,
|
||||
ISC_QUAD*);
|
||||
|
||||
|
@ -430,6 +430,7 @@ enum info_db_provider
|
||||
#define isc_info_sql_stmt_flags 27
|
||||
#define isc_info_sql_stmt_timeout_user 28
|
||||
#define isc_info_sql_stmt_timeout_run 29
|
||||
#define isc_info_sql_stmt_blob_align 30
|
||||
|
||||
/*********************************/
|
||||
/* SQL information return values */
|
||||
|
402
src/jrd/jrd.cpp
402
src/jrd/jrd.cpp
@ -132,6 +132,7 @@
|
||||
|
||||
#include "../dsql/dsql.h"
|
||||
#include "../dsql/dsql_proto.h"
|
||||
#include "../dsql/DsqlBatch.h"
|
||||
|
||||
using namespace Jrd;
|
||||
using namespace Firebird;
|
||||
@ -650,6 +651,14 @@ namespace
|
||||
validateHandle(tdbb, cursor->getAttachment());
|
||||
}
|
||||
|
||||
inline void validateHandle(thread_db* tdbb, DsqlBatch* const batch)
|
||||
{
|
||||
if (!batch)
|
||||
status_exception::raise(Arg::Gds(isc_bad_req_handle)); // isc_bad_batch_handle !!!!!!!!!!
|
||||
|
||||
validateHandle(tdbb, batch->getAttachment());
|
||||
}
|
||||
|
||||
class AttachmentHolder
|
||||
{
|
||||
public:
|
||||
@ -1158,12 +1167,21 @@ ISC_STATUS transliterateException(thread_db* tdbb, const Exception& ex, FbStatus
|
||||
attachment->att_trace_manager->event_error(&conn, &traceStatus, func);
|
||||
}
|
||||
|
||||
JRD_transliterate(tdbb, vector);
|
||||
|
||||
return vector->getErrors()[1];
|
||||
}
|
||||
|
||||
|
||||
// Transliterate status vector to the client charset.
|
||||
void JRD_transliterate(thread_db* tdbb, Firebird::IStatus* vector) throw()
|
||||
{
|
||||
Jrd::Attachment* attachment = tdbb->getAttachment();
|
||||
USHORT charSet;
|
||||
if (!attachment || (charSet = attachment->att_client_charset) == CS_METADATA ||
|
||||
charSet == CS_NONE)
|
||||
{
|
||||
return vector->getErrors()[1];
|
||||
return;
|
||||
}
|
||||
|
||||
const ISC_STATUS* const vectorStart = vector->getErrors();
|
||||
@ -1236,12 +1254,10 @@ ISC_STATUS transliterateException(thread_db* tdbb, const Exception& ex, FbStatus
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ex.stuffException(vector);
|
||||
return vector->getErrors()[1];
|
||||
return;
|
||||
}
|
||||
|
||||
vector->setErrors2(newVector.getCount() - 1, newVector.begin());
|
||||
return vector->getErrors()[1];
|
||||
}
|
||||
|
||||
|
||||
@ -4793,6 +4809,21 @@ ITransaction* JAttachment::execute(CheckStatusWrapper* user_status, ITransaction
|
||||
}
|
||||
|
||||
|
||||
IBatch* JAttachment::createBatch(CheckStatusWrapper* status, ITransaction* transaction,
|
||||
unsigned stmtLength, const char* sqlStmt, unsigned dialect,
|
||||
IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
RefPtr<IStatement> tmpStatement(REF_NO_INCR, prepare(status, transaction, stmtLength, sqlStmt,
|
||||
dialect, 0));
|
||||
if (status->getState() & IStatus::STATE_ERRORS)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return tmpStatement->createBatch(status, inMetadata, parLength, par);
|
||||
}
|
||||
|
||||
|
||||
int JResultSet::fetchNext(CheckStatusWrapper* user_status, void* buffer)
|
||||
{
|
||||
try
|
||||
@ -5472,6 +5503,369 @@ void JStatement::setTimeout(CheckStatusWrapper* user_status, unsigned int timeOu
|
||||
}
|
||||
|
||||
|
||||
JBatch* JStatement::createBatch(Firebird::CheckStatusWrapper* status, Firebird::IMessageMetadata* inMetadata,
|
||||
unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
JBatch* batch = NULL;
|
||||
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
RefPtr<IMessageMetadata> defaultIn;
|
||||
if (!inMetadata)
|
||||
{
|
||||
defaultIn.assignRefNoIncr(metadata.getInputMetadata());
|
||||
if (defaultIn)
|
||||
{
|
||||
inMetadata = defaultIn;
|
||||
}
|
||||
}
|
||||
|
||||
DsqlBatch* const b = DsqlBatch::open(tdbb, getHandle(), inMetadata, parLength, par);
|
||||
|
||||
batch = FB_NEW JBatch(b, this);
|
||||
batch->addRef();
|
||||
b->setInterfacePtr(batch);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JStatement::createBatch");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JStatement::createBatch");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
return batch;
|
||||
}
|
||||
|
||||
|
||||
JBatch::JBatch(DsqlBatch* handle, JStatement* aStatement)
|
||||
: batch(handle),
|
||||
statement(aStatement)
|
||||
{ }
|
||||
|
||||
|
||||
StableAttachmentPart* JBatch::getAttachment()
|
||||
{
|
||||
return statement->getAttachment();
|
||||
}
|
||||
|
||||
|
||||
int JBatch::release()
|
||||
{
|
||||
if (--refCounter != 0)
|
||||
return 1;
|
||||
|
||||
if (batch)
|
||||
delete batch;
|
||||
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void JBatch::add(CheckStatusWrapper* status, unsigned count, const void* inBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
DsqlBatch* b = getHandle();
|
||||
b->add(tdbb, count, inBuffer);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JBatch::add");
|
||||
return;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JBatch::add");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
}
|
||||
|
||||
|
||||
void JBatch::addBlob(CheckStatusWrapper* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId,
|
||||
unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
DsqlBatch* b = getHandle();
|
||||
b->addBlob(tdbb, length, inBuffer, blobId, parLength, par);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JBatch::addBlob");
|
||||
return;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JBatch::addBlob");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
}
|
||||
|
||||
|
||||
void JBatch::appendBlobData(CheckStatusWrapper* status, unsigned length, const void* inBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
DsqlBatch* b = getHandle();
|
||||
b->appendBlobData(tdbb, length, inBuffer);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JBatch::appendBlobData");
|
||||
return;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JBatch::appendBlobData");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
}
|
||||
|
||||
|
||||
void JBatch::addBlobStream(CheckStatusWrapper* status, unsigned length, const void* inBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
DsqlBatch* b = getHandle();
|
||||
b->addBlobStream(tdbb, length, inBuffer);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JBatch::addBlobStream");
|
||||
return;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JBatch::addBlobStream");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
}
|
||||
|
||||
|
||||
void JBatch::setDefaultBpb(CheckStatusWrapper* status, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
DsqlBatch* b = getHandle();
|
||||
b->setDefaultBpb(tdbb, parLength, par);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JBatch::setDefaultBpb");
|
||||
return;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JBatch::setDefaultBpb");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
}
|
||||
|
||||
|
||||
unsigned JBatch::getBlobAlignment(CheckStatusWrapper*)
|
||||
{
|
||||
return DsqlBatch::BLOB_STREAM_ALIGN;
|
||||
}
|
||||
|
||||
|
||||
IMessageMetadata* JBatch::getMetadata(CheckStatusWrapper* status)
|
||||
{
|
||||
IMessageMetadata* meta;
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
DsqlBatch* b = getHandle();
|
||||
meta = b->getMetadata(tdbb);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JBatch::getMetadata");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JBatch::getMetadata");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
return meta;
|
||||
}
|
||||
|
||||
|
||||
void JBatch::registerBlob(CheckStatusWrapper* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
DsqlBatch* b = getHandle();
|
||||
b->registerBlob(tdbb, existingBlob, blobId);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JBatch::registerBlob");
|
||||
return;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JBatch::registerBlob");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
}
|
||||
|
||||
|
||||
IBatchCompletionState* JBatch::execute(CheckStatusWrapper* status, ITransaction* transaction)
|
||||
{
|
||||
IBatchCompletionState* cs;
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
|
||||
jrd_tra* tra = nullptr;
|
||||
if (transaction)
|
||||
{
|
||||
JTransaction* jt = getAttachment()->getTransactionInterface(status, transaction);
|
||||
if (jt)
|
||||
tra = jt->getHandle();
|
||||
}
|
||||
|
||||
validateHandle(tdbb, tra);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
DsqlBatch* b = getHandle();
|
||||
cs = b->execute(tdbb);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JBatch::execute");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JBatch::execute");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
return cs;
|
||||
}
|
||||
|
||||
|
||||
void JBatch::cancel(CheckStatusWrapper* status)
|
||||
{
|
||||
try
|
||||
{
|
||||
EngineContextHolder tdbb(status, this, FB_FUNCTION);
|
||||
check_database(tdbb);
|
||||
|
||||
try
|
||||
{
|
||||
DsqlBatch* b = getHandle();
|
||||
b->cancel(tdbb);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
transliterateException(tdbb, ex, status, "JBatch::cancel");
|
||||
return;
|
||||
}
|
||||
|
||||
trace_warning(tdbb, status, "JBatch::cancel");
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
return;
|
||||
}
|
||||
|
||||
successful_completion(status);
|
||||
}
|
||||
|
||||
|
||||
void JAttachment::ping(CheckStatusWrapper* user_status)
|
||||
{
|
||||
/**************************************
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
#include "../common/classes/fb_string.h"
|
||||
#include "../common/classes/objects_array.h"
|
||||
#include "../jrd/status.h"
|
||||
|
||||
namespace Jrd {
|
||||
class Database;
|
||||
@ -80,6 +81,7 @@ void JRD_shutdown_attachment(Jrd::Attachment* attachment);
|
||||
void JRD_shutdown_attachments(Jrd::Database* dbb);
|
||||
void JRD_cancel_operation(Jrd::thread_db* tdbb, Jrd::Attachment* attachment, int option);
|
||||
void JRD_make_role_name(Firebird::MetaName& userIdRole, const int dialect);
|
||||
void JRD_transliterate(Jrd::thread_db* tdbb, Firebird::IStatus* vector) throw();
|
||||
|
||||
bool JRD_shutdown_database(Jrd::Database* dbb, const unsigned flags = 0);
|
||||
// JRD_shutdown_database() flags
|
||||
|
@ -284,6 +284,7 @@ public:
|
||||
} req_operation; // operation for next node
|
||||
|
||||
StatusXcp req_last_xcp; // last known exception
|
||||
bool req_batch;
|
||||
|
||||
template <typename T> T* getImpure(unsigned offset)
|
||||
{
|
||||
|
@ -430,7 +430,7 @@ static void build_iberror_h()
|
||||
if (last_code + 1 != N.CODE && N.FAC_CODE == 0)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Warning: missing codes between %d and %d (exclusive)\n",
|
||||
"Warning: 1 missing codes between %d and %d (exclusive)\n",
|
||||
last_code, (int) N.CODE);
|
||||
}
|
||||
last_code = N.CODE;
|
||||
@ -474,7 +474,7 @@ static void build_iberror_h()
|
||||
if (last_code + 1 != N.CODE && N.FAC_CODE == 0)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Warning: missing codes between %d and %d (exclusive)\n",
|
||||
"Warning: 2 missing codes between %d and %d (exclusive)\n",
|
||||
last_code, (int) N.CODE);
|
||||
}
|
||||
last_code = N.CODE;
|
||||
@ -539,7 +539,7 @@ static void build_iberror_h()
|
||||
// if (last_code + 1 != N.CODE && N.FAC_CODE == 0)
|
||||
// {
|
||||
// fprintf(stderr,
|
||||
// "Warning: missing codes between %d and %d (exclusive)\n",
|
||||
// "Warning: 3 missing codes between %d and %d (exclusive)\n",
|
||||
// last_code, (int) N.CODE);
|
||||
// }
|
||||
// last_code = N.CODE;
|
||||
@ -610,7 +610,7 @@ static void build_iberror_h()
|
||||
// if (last_code + 1 != N.CODE && N.FAC_CODE == 0)
|
||||
// {
|
||||
// fprintf(stderr,
|
||||
// "Warning: missing codes between %d and %d (exclusive)\n",
|
||||
// "Warning: 4 missing codes between %d and %d (exclusive)\n",
|
||||
// last_code, (int) N.CODE);
|
||||
// }
|
||||
// last_code = N.CODE;
|
||||
@ -820,7 +820,7 @@ static void build_other_headers()
|
||||
if (last_code + 1 != N.CODE && N.FAC_CODE == 0)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Warning: missing codes between %d and %d (exclusive)\n",
|
||||
"Warning: 5 missing codes between %d and %d (exclusive)\n",
|
||||
last_code, (int) N.CODE);
|
||||
}
|
||||
last_code = N.CODE;
|
||||
|
@ -55,6 +55,7 @@
|
||||
#include "../yvalve/gds_proto.h"
|
||||
#include "../common/isc_f_proto.h"
|
||||
#include "../common/classes/ClumpletWriter.h"
|
||||
#include "../common/classes/BatchCompletionState.h"
|
||||
#include "../common/config/config.h"
|
||||
#include "../common/utils_proto.h"
|
||||
#include "../common/classes/DbImplementation.h"
|
||||
@ -68,9 +69,8 @@
|
||||
#include "../auth/SecureRemotePassword/client/SrpClient.h"
|
||||
#include "../auth/trusted/AuthSspi.h"
|
||||
#include "../plugins/crypt/arc4/Arc4.h"
|
||||
|
||||
#include "BlrFromMessage.h"
|
||||
|
||||
#include "../dsql/DsqlBatch.h"
|
||||
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
@ -299,6 +299,218 @@ int ResultSet::release()
|
||||
return 0;
|
||||
}
|
||||
|
||||
class Batch FB_FINAL : public RefCntIface<IBatchImpl<Batch, CheckStatusWrapper> >
|
||||
{
|
||||
public:
|
||||
Batch(Statement* s, IMessageMetadata* inFmt, unsigned parLength, const unsigned char* par);
|
||||
|
||||
// IResultSet implementation
|
||||
int release();
|
||||
void add(Firebird::CheckStatusWrapper* status, unsigned count, const void* inBuffer);
|
||||
void addBlob(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId,
|
||||
unsigned parLength, const unsigned char* par);
|
||||
void appendBlobData(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer);
|
||||
void addBlobStream(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer);
|
||||
void registerBlob(Firebird::CheckStatusWrapper* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId);
|
||||
Firebird::IBatchCompletionState* execute(Firebird::CheckStatusWrapper* status, Firebird::ITransaction* transaction);
|
||||
void cancel(Firebird::CheckStatusWrapper* status);
|
||||
unsigned getBlobAlignment(Firebird::CheckStatusWrapper* status);
|
||||
void setDefaultBpb(Firebird::CheckStatusWrapper* status, unsigned parLength, const unsigned char* par);
|
||||
Firebird::IMessageMetadata* getMetadata(Firebird::CheckStatusWrapper* status);
|
||||
|
||||
private:
|
||||
void freeClientData(CheckStatusWrapper* status, bool force = false);
|
||||
void releaseStatement();
|
||||
void setBlobAlignment();
|
||||
|
||||
void genBlobId(ISC_QUAD* blobId)
|
||||
{
|
||||
if (++genId.gds_quad_low == 0)
|
||||
++genId.gds_quad_high;
|
||||
memcpy(blobId, &genId, sizeof(genId));
|
||||
}
|
||||
|
||||
bool batchHasData()
|
||||
{
|
||||
return batchActive;
|
||||
}
|
||||
|
||||
// working with message stream buffer
|
||||
void putMessageData(ULONG count, const void* p)
|
||||
{
|
||||
fb_assert(messageStreamBuffer);
|
||||
|
||||
const UCHAR* ptr = reinterpret_cast<const UCHAR*>(p);
|
||||
|
||||
while(count)
|
||||
{
|
||||
ULONG remainSpace = messageBufferSize - messageStream;
|
||||
ULONG step = MIN(count, remainSpace);
|
||||
if (step == messageBufferSize)
|
||||
{
|
||||
// direct packet sent
|
||||
sendMessagePacket(step, ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// use buffer
|
||||
memcpy(&messageStreamBuffer[messageStream * alignedSize], ptr, step * alignedSize);
|
||||
messageStream += step;
|
||||
if (messageStream == messageBufferSize)
|
||||
{
|
||||
sendMessagePacket(messageBufferSize, messageStreamBuffer);
|
||||
messageStream = 0;
|
||||
}
|
||||
}
|
||||
|
||||
count -= step;
|
||||
ptr += step * alignedSize;
|
||||
}
|
||||
}
|
||||
|
||||
// working with blob stream buffer
|
||||
void newBlob()
|
||||
{
|
||||
alignBlobBuffer(blobAlign);
|
||||
|
||||
fb_assert(blobStream - blobStreamBuffer <= blobBufferSize);
|
||||
ULONG space = blobBufferSize - (blobStream - blobStreamBuffer);
|
||||
if (space < Rsr::BatchStream::SIZEOF_BLOB_HEAD)
|
||||
{
|
||||
sendBlobPacket(blobStream - blobStreamBuffer, blobStreamBuffer);
|
||||
blobStream = blobStreamBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
void alignBlobBuffer(unsigned alignment, ULONG* bs = NULL)
|
||||
{
|
||||
FB_UINT64 zeroFill = 0;
|
||||
UCHAR* newPointer = FB_ALIGN(blobStream, alignment);
|
||||
ULONG align = FB_ALIGN(blobStream, alignment) - blobStream;
|
||||
putBlobData(align, &zeroFill);
|
||||
if (bs)
|
||||
*bs += align;
|
||||
}
|
||||
|
||||
void putBlobData(ULONG size, const void* p)
|
||||
{
|
||||
fb_assert(blobStreamBuffer);
|
||||
|
||||
const UCHAR* ptr = reinterpret_cast<const UCHAR*>(p);
|
||||
|
||||
while(size)
|
||||
{
|
||||
ULONG space = blobBufferSize - (blobStream - blobStreamBuffer);
|
||||
ULONG step = MIN(size, space);
|
||||
if (step == blobBufferSize)
|
||||
{
|
||||
// direct packet sent
|
||||
sendBlobPacket(blobBufferSize, ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// use buffer
|
||||
memcpy(blobStream, ptr, step);
|
||||
blobStream += step;
|
||||
if (blobStream - blobStreamBuffer == blobBufferSize)
|
||||
{
|
||||
sendBlobPacket(blobBufferSize, blobStreamBuffer);
|
||||
blobStream = blobStreamBuffer;
|
||||
sizePointer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
size -= step;
|
||||
ptr += step;
|
||||
}
|
||||
}
|
||||
|
||||
void setSizePointer()
|
||||
{
|
||||
fb_assert(FB_ALIGN(blobStream, sizeof(*sizePointer)) == blobStream);
|
||||
sizePointer = reinterpret_cast<ULONG*>(blobStream);
|
||||
}
|
||||
|
||||
void putSegment(ULONG size, const void* ptr)
|
||||
{
|
||||
if (!sizePointer)
|
||||
{
|
||||
newBlob();
|
||||
|
||||
ISC_QUAD zero = {0, 0};
|
||||
putBlobData(sizeof zero, &zero);
|
||||
setSizePointer();
|
||||
ULONG z2 = 0;
|
||||
putBlobData(sizeof z2, &z2);
|
||||
putBlobData(sizeof z2, &z2);
|
||||
}
|
||||
|
||||
*sizePointer += size;
|
||||
if (segmented)
|
||||
{
|
||||
if (size > MAX_USHORT)
|
||||
{
|
||||
(Arg::Gds(isc_imp_exc) << Arg::Gds(isc_blobtoobig)
|
||||
<< Arg::Gds(isc_random) << "Segment size >= 64Kb").raise();
|
||||
}
|
||||
|
||||
alignBlobBuffer(BLOB_SEGHDR_ALIGN, sizePointer);
|
||||
*sizePointer += sizeof(USHORT);
|
||||
USHORT segSize = size;
|
||||
putBlobData(sizeof segSize, &segSize);
|
||||
}
|
||||
putBlobData(size, ptr);
|
||||
}
|
||||
|
||||
void flashBatch()
|
||||
{
|
||||
alignBlobBuffer(blobAlign);
|
||||
ULONG size = blobStream - blobStreamBuffer;
|
||||
if (size)
|
||||
{
|
||||
sendBlobPacket(size, blobStreamBuffer);
|
||||
blobStream = blobStreamBuffer;
|
||||
}
|
||||
|
||||
batchActive = false;
|
||||
}
|
||||
|
||||
void sendBlobPacket(unsigned size, const UCHAR* ptr);
|
||||
void sendMessagePacket(unsigned size, const UCHAR* ptr);
|
||||
|
||||
Firebird::AutoPtr<UCHAR, Firebird::ArrayDelete<UCHAR> > messageStreamBuffer, blobStreamBuffer;
|
||||
ULONG messageStream;
|
||||
UCHAR* blobStream;
|
||||
ULONG* sizePointer;
|
||||
|
||||
ULONG messageSize, alignedSize, blobBufferSize, messageBufferSize, flags;
|
||||
Statement* stmt;
|
||||
RefPtr<IMessageMetadata> format;
|
||||
ISC_QUAD genId;
|
||||
int blobAlign;
|
||||
UCHAR blobPolicy;
|
||||
bool segmented, defSegmented, batchActive;
|
||||
|
||||
public:
|
||||
bool tmpStatement;
|
||||
};
|
||||
|
||||
int Batch::release()
|
||||
{
|
||||
if (--refCounter != 0)
|
||||
return 1;
|
||||
|
||||
if (stmt)
|
||||
{
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper status(&ls);
|
||||
freeClientData(&status, true);
|
||||
}
|
||||
delete this;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
class Statement FB_FINAL : public RefCntIface<IStatementImpl<Statement, CheckStatusWrapper> >
|
||||
{
|
||||
public:
|
||||
@ -344,6 +556,9 @@ public:
|
||||
statement->rsr_timeout = timeOut;
|
||||
}
|
||||
|
||||
Batch* createBatch(CheckStatusWrapper* status, IMessageMetadata* inMetadata,
|
||||
unsigned parLength, const unsigned char* par);
|
||||
|
||||
public:
|
||||
Statement(Rsr* handle, Attachment* a, unsigned aDialect)
|
||||
: metadata(getPool(), this, NULL),
|
||||
@ -359,6 +574,11 @@ public:
|
||||
return statement;
|
||||
}
|
||||
|
||||
Attachment* getAttachment()
|
||||
{
|
||||
return remAtt;
|
||||
}
|
||||
|
||||
void parseMetadata(const Array<UCHAR>& buffer)
|
||||
{
|
||||
metadata.clear();
|
||||
@ -536,6 +756,10 @@ public:
|
||||
unsigned int getStatementTimeout(CheckStatusWrapper* status);
|
||||
void setStatementTimeout(CheckStatusWrapper* status, unsigned int timeOut);
|
||||
|
||||
Batch* createBatch(Firebird::CheckStatusWrapper* status, ITransaction* transaction,
|
||||
unsigned stmtLength, const char* sqlStmt, unsigned dialect,
|
||||
IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par);
|
||||
|
||||
public:
|
||||
Attachment(Rdb* handle, const PathName& path)
|
||||
: rdb(handle), dbPath(getPool(), path)
|
||||
@ -1856,7 +2080,693 @@ void Attachment::setStatementTimeout(CheckStatusWrapper* status, unsigned int ti
|
||||
}
|
||||
|
||||
|
||||
Firebird::ITransaction* Statement::execute(CheckStatusWrapper* status, Firebird::ITransaction* apiTra,
|
||||
Batch* Attachment::createBatch(CheckStatusWrapper* status, ITransaction* transaction,
|
||||
unsigned stmtLength, const char* sqlStmt, unsigned dialect,
|
||||
IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
/**************************************
|
||||
*
|
||||
* c r e a t e B a t c h
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Create jdbc-style batch for SQL statement.
|
||||
*
|
||||
**************************************/
|
||||
Statement* stmt = prepare(status, transaction, stmtLength, sqlStmt, dialect, 0);
|
||||
if (status->getState() & Firebird::IStatus::STATE_ERRORS)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Batch* rc = stmt->createBatch(status, inMetadata, parLength, par);
|
||||
if (status->getState() & Firebird::IStatus::STATE_ERRORS)
|
||||
{
|
||||
stmt->release();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc->tmpStatement = true;
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
Batch* Statement::createBatch(CheckStatusWrapper* status, IMessageMetadata* inMetadata,
|
||||
unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
/**************************************
|
||||
*
|
||||
* c r e a t e B a t c h
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Create jdbc-style batch for prepared statement.
|
||||
*
|
||||
**************************************/
|
||||
|
||||
try
|
||||
{
|
||||
reset(status);
|
||||
|
||||
// Check and validate handles, etc.
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
|
||||
if (port->port_protocol < PROTOCOL_VERSION16)
|
||||
unsupported();
|
||||
|
||||
// Build input BLR
|
||||
RefPtr<IMessageMetadata> meta;
|
||||
if (!inMetadata)
|
||||
{
|
||||
meta.assignRefNoIncr(getInputMetadata(status));
|
||||
check(status);
|
||||
inMetadata = meta;
|
||||
}
|
||||
|
||||
BlrFromMessage inBlr(inMetadata, dialect, port->port_protocol);
|
||||
const unsigned int in_blr_length = inBlr.getLength();
|
||||
const UCHAR* const in_blr = inBlr.getBytes();
|
||||
|
||||
// Validate data length
|
||||
CHECK_LENGTH(port, in_blr_length);
|
||||
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
|
||||
delete statement->rsr_bind_format;
|
||||
statement->rsr_bind_format = NULL;
|
||||
|
||||
if (port->port_statement)
|
||||
{
|
||||
delete port->port_statement->rsr_select_format;
|
||||
port->port_statement->rsr_select_format = NULL;
|
||||
}
|
||||
|
||||
// Parse the blr describing the message, if there is any.
|
||||
if (in_blr_length)
|
||||
statement->rsr_bind_format = PARSE_msg_format(in_blr, in_blr_length);
|
||||
|
||||
RMessage* message = NULL;
|
||||
if (!statement->rsr_buffer)
|
||||
{
|
||||
statement->rsr_buffer = message = FB_NEW RMessage(0);
|
||||
statement->rsr_message = message;
|
||||
|
||||
message->msg_next = message;
|
||||
|
||||
statement->rsr_fmt_length = 0;
|
||||
}
|
||||
else
|
||||
message = statement->rsr_message = statement->rsr_buffer;
|
||||
|
||||
statement->rsr_flags.clear(Rsr::FETCHED);
|
||||
statement->rsr_format = statement->rsr_bind_format;
|
||||
statement->rsr_batch_stream.blobRemaining = 0;
|
||||
statement->clearException();
|
||||
|
||||
// set up the packet for the other guy...
|
||||
|
||||
PACKET* packet = &rdb->rdb_packet;
|
||||
packet->p_operation = op_batch_create;
|
||||
P_BATCH_CREATE* batch = &packet->p_batch_create;
|
||||
batch->p_batch_statement = statement->rsr_id;
|
||||
batch->p_batch_blr.cstr_length = in_blr_length;
|
||||
batch->p_batch_blr.cstr_address = in_blr;
|
||||
batch->p_batch_msglen = inMetadata->getMessageLength(status);
|
||||
check(status);
|
||||
batch->p_batch_pb.cstr_length = parLength;
|
||||
batch->p_batch_pb.cstr_address = par;
|
||||
|
||||
send_partial_packet(port, packet);
|
||||
defer_packet(port, packet, true);
|
||||
message->msg_address = NULL;
|
||||
|
||||
Batch* b = FB_NEW Batch(this, inMetadata, parLength, par);
|
||||
b->addRef();
|
||||
return b;
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
Batch::Batch(Statement* s, IMessageMetadata* inFmt, unsigned parLength, const unsigned char* par)
|
||||
: messageStream(0), blobStream(nullptr), sizePointer(nullptr),
|
||||
messageSize(0), alignedSize(0), blobBufferSize(0), messageBufferSize(0), flags(0),
|
||||
stmt(s), format(inFmt), blobAlign(0), blobPolicy(BLOB_NONE),
|
||||
segmented(false), defSegmented(false), batchActive(false), tmpStatement(false)
|
||||
{
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper st(&ls);
|
||||
|
||||
messageSize = format->getMessageLength(&st);
|
||||
check(&st);
|
||||
alignedSize = format->getAlignedLength(&st);
|
||||
check(&st);
|
||||
|
||||
memset(&genId, 0, sizeof(genId));
|
||||
|
||||
ClumpletReader rdr(ClumpletReader::WideTagged, par, parLength);
|
||||
|
||||
for (rdr.rewind(); !rdr.isEof(); rdr.moveNext())
|
||||
{
|
||||
UCHAR t = rdr.getClumpTag();
|
||||
|
||||
switch (t)
|
||||
{
|
||||
case TAG_MULTIERROR:
|
||||
case TAG_RECORD_COUNTS:
|
||||
if (rdr.getInt())
|
||||
flags |= (1 << t);
|
||||
else
|
||||
flags &= ~(1 << t);
|
||||
break;
|
||||
|
||||
case TAG_BLOB_POLICY:
|
||||
blobPolicy = rdr.getInt();
|
||||
|
||||
switch (blobPolicy)
|
||||
{
|
||||
case BLOB_ID_ENGINE:
|
||||
case BLOB_ID_USER:
|
||||
case BLOB_STREAM:
|
||||
break;
|
||||
default:
|
||||
blobPolicy = BLOB_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
s->getStatement()->rsr_batch_flags = flags;
|
||||
|
||||
// allocate buffers
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
blobBufferSize = port->getPortConfig()->getClientBatchBuffer();
|
||||
messageBufferSize = blobBufferSize / alignedSize;
|
||||
if (!messageBufferSize)
|
||||
messageBufferSize = 1;
|
||||
|
||||
messageStreamBuffer.reset(FB_NEW UCHAR[messageBufferSize * alignedSize]);
|
||||
if (blobPolicy != BLOB_NONE)
|
||||
{
|
||||
blobStreamBuffer.reset(FB_NEW UCHAR[blobBufferSize]);
|
||||
blobStream = blobStreamBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Batch::add(CheckStatusWrapper* status, unsigned count, const void* inBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check and validate handles, etc.
|
||||
|
||||
if (!stmt)
|
||||
{
|
||||
Arg::Gds(isc_bad_req_handle).raise();
|
||||
}
|
||||
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
putMessageData(count, inBuffer);
|
||||
|
||||
batchActive = true;
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Batch::sendMessagePacket(unsigned count, const UCHAR* ptr)
|
||||
{
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
|
||||
PACKET* packet = &rdb->rdb_packet;
|
||||
packet->p_operation = op_batch_msg;
|
||||
P_BATCH_MSG* batch = &packet->p_batch_msg;
|
||||
batch->p_batch_statement = statement->rsr_id;
|
||||
batch->p_batch_messages = count;
|
||||
batch->p_batch_data.cstr_address = const_cast<UCHAR*>(ptr);
|
||||
statement->rsr_batch_size = alignedSize;
|
||||
|
||||
send_partial_packet(port, packet);
|
||||
defer_packet(port, packet, true);
|
||||
}
|
||||
|
||||
|
||||
void Batch::addBlob(CheckStatusWrapper* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId,
|
||||
unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check and validate handles, etc.
|
||||
if (!stmt)
|
||||
{
|
||||
Arg::Gds(isc_bad_req_handle).raise();
|
||||
}
|
||||
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
|
||||
// Policy check
|
||||
switch(blobPolicy)
|
||||
{
|
||||
case IBatch::BLOB_ID_ENGINE:
|
||||
genBlobId(blobId);
|
||||
break;
|
||||
case IBatch::BLOB_ID_USER:
|
||||
break;
|
||||
default:
|
||||
(Arg::Gds(isc_random) << "Invalid blob policy for appendBlobData call").raise();
|
||||
}
|
||||
|
||||
// Build blob HDR in stream
|
||||
newBlob();
|
||||
putBlobData(sizeof *blobId, blobId);
|
||||
setSizePointer();
|
||||
putBlobData(sizeof parLength, &parLength);
|
||||
putBlobData(sizeof parLength, &parLength);
|
||||
putBlobData(parLength, par);
|
||||
segmented = parLength ? fb_utils::isBpbSegmented(parLength, par) : defSegmented;
|
||||
|
||||
// Store blob data
|
||||
putSegment(length, inBuffer);
|
||||
|
||||
batchActive = true;
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Batch::appendBlobData(CheckStatusWrapper* status, unsigned length, const void* inBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check and validate handles, etc.
|
||||
if (!stmt)
|
||||
{
|
||||
Arg::Gds(isc_bad_req_handle).raise();
|
||||
}
|
||||
|
||||
// Policy check
|
||||
switch(blobPolicy)
|
||||
{
|
||||
case IBatch::BLOB_ID_USER:
|
||||
case IBatch::BLOB_ID_ENGINE:
|
||||
break;
|
||||
default:
|
||||
(Arg::Gds(isc_random) << "Invalid blob policy for appendBlobData call").raise();
|
||||
}
|
||||
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
|
||||
// Store blob data
|
||||
putSegment(length, inBuffer);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Batch::addBlobStream(CheckStatusWrapper* status, unsigned length, const void* inBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check and validate handles, etc.
|
||||
if (!stmt)
|
||||
{
|
||||
Arg::Gds(isc_bad_req_handle).raise();
|
||||
}
|
||||
|
||||
// Policy check
|
||||
if (blobPolicy != IBatch::BLOB_STREAM)
|
||||
{
|
||||
(Arg::Gds(isc_random) << "Invalid blob policy for addBlobStream() call").raise();
|
||||
}
|
||||
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
|
||||
// Store stream data
|
||||
putBlobData(length, inBuffer);
|
||||
|
||||
batchActive = true;
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Batch::sendBlobPacket(unsigned size, const UCHAR* ptr)
|
||||
{
|
||||
Rsr* statement = stmt->getStatement();
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
rem_port* port = rdb->rdb_port;
|
||||
|
||||
setBlobAlignment();
|
||||
fb_assert(!(size % blobAlign));
|
||||
|
||||
PACKET* packet = &rdb->rdb_packet;
|
||||
packet->p_operation = op_batch_blob_stream;
|
||||
P_BATCH_BLOB* batch = &packet->p_batch_blob;
|
||||
batch->p_batch_statement = statement->rsr_id;
|
||||
batch->p_batch_blob_data.cstr_address = const_cast<UCHAR*>(ptr);
|
||||
batch->p_batch_blob_data.cstr_length = size;
|
||||
|
||||
send_partial_packet(port, packet);
|
||||
defer_packet(port, packet, true);
|
||||
}
|
||||
|
||||
|
||||
void Batch::setDefaultBpb(CheckStatusWrapper* status, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check and validate handles, etc.
|
||||
if (!stmt)
|
||||
Arg::Gds(isc_bad_req_handle).raise();
|
||||
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
|
||||
// Check for presence of any data in batch buffers
|
||||
if (batchHasData())
|
||||
(Arg::Gds(isc_random) << "Can't change default BPB after adding any data to batch").raise();
|
||||
|
||||
// Set default segmentation flag
|
||||
defSegmented = fb_utils::isBpbSegmented(parLength, par);
|
||||
|
||||
// Prepare and send the packet
|
||||
PACKET* packet = &rdb->rdb_packet;
|
||||
packet->p_operation = op_batch_set_bpb;
|
||||
P_BATCH_SETBPB* batch = &packet->p_batch_setbpb;
|
||||
batch->p_batch_statement = statement->rsr_id;
|
||||
batch->p_batch_blob_bpb.cstr_address = par;
|
||||
batch->p_batch_blob_bpb.cstr_length = parLength;
|
||||
|
||||
send_partial_packet(port, packet);
|
||||
defer_packet(port, packet, true);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned Batch::getBlobAlignment(CheckStatusWrapper* status)
|
||||
{
|
||||
try
|
||||
{
|
||||
setBlobAlignment();
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
|
||||
return blobAlign;
|
||||
}
|
||||
|
||||
|
||||
void Batch::setBlobAlignment()
|
||||
{
|
||||
if (blobAlign)
|
||||
return;
|
||||
|
||||
// Check and validate handles, etc.
|
||||
if (!stmt)
|
||||
{
|
||||
Arg::Gds(isc_bad_req_handle).raise();
|
||||
}
|
||||
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
|
||||
// Perform info call to server
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper s(&ls);
|
||||
UCHAR item = isc_info_sql_stmt_blob_align;
|
||||
UCHAR buffer[16];
|
||||
info(&s, rdb, op_info_sql, statement->rsr_id, 0,
|
||||
1, &item, 0, 0, sizeof(buffer), buffer);
|
||||
check(&s);
|
||||
|
||||
// Extract from buffer
|
||||
if (buffer[0] != item)
|
||||
(Arg::Gds(isc_random) << "Unexpected info buffer structure").raise();
|
||||
|
||||
int len = gds__vax_integer(&buffer[1], 2);
|
||||
statement->rsr_batch_stream.alignment = blobAlign = gds__vax_integer(&buffer[3], len);
|
||||
}
|
||||
|
||||
|
||||
IMessageMetadata* Batch::getMetadata(CheckStatusWrapper* status)
|
||||
{
|
||||
reset(status);
|
||||
|
||||
format->addRef();
|
||||
return format;
|
||||
}
|
||||
|
||||
|
||||
void Batch::registerBlob(CheckStatusWrapper* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check and validate handles, etc.
|
||||
|
||||
if (!stmt)
|
||||
{
|
||||
Arg::Gds(isc_bad_req_handle).raise();
|
||||
}
|
||||
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
rem_port* port = rdb->rdb_port;
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
|
||||
if (blobPolicy == IBatch::BLOB_ID_ENGINE)
|
||||
genBlobId(blobId);
|
||||
|
||||
PACKET* packet = &rdb->rdb_packet;
|
||||
packet->p_operation = op_batch_regblob;
|
||||
P_BATCH_REGBLOB* batch = &packet->p_batch_regblob;
|
||||
batch->p_batch_statement = statement->rsr_id;
|
||||
batch->p_batch_exist_id = *existingBlob;
|
||||
batch->p_batch_blob_id = *blobId;
|
||||
|
||||
send_partial_packet(port, packet);
|
||||
defer_packet(port, packet, true);
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IBatchCompletionState* Batch::execute(CheckStatusWrapper* status, ITransaction* apiTra)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check and validate handles, etc.
|
||||
|
||||
if (!stmt)
|
||||
{
|
||||
Arg::Gds(isc_bad_req_handle).raise();
|
||||
}
|
||||
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
CHECK_HANDLE(rdb, isc_bad_db_handle);
|
||||
|
||||
rem_port* port = rdb->rdb_port;
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
|
||||
Rtr* transaction = NULL;
|
||||
Transaction* rt = stmt->getAttachment()->remoteTransactionInterface(apiTra);
|
||||
if (rt)
|
||||
{
|
||||
transaction = rt->getTransaction();
|
||||
CHECK_HANDLE(transaction, isc_bad_trans_handle);
|
||||
}
|
||||
|
||||
// Sanity checks complete - flash data in buffers
|
||||
flashBatch();
|
||||
|
||||
// Prepare and send execute packet
|
||||
PACKET* packet = &rdb->rdb_packet;
|
||||
packet->p_operation = op_batch_exec;
|
||||
P_BATCH_EXEC* batch = &packet->p_batch_exec;
|
||||
batch->p_batch_statement = statement->rsr_id;
|
||||
batch->p_batch_transaction = transaction->rtr_id;
|
||||
send_packet(port, packet);
|
||||
|
||||
statement->rsr_batch_size = alignedSize;
|
||||
AutoPtr<BatchCompletionState, SimpleDispose<BatchCompletionState> >
|
||||
cs(FB_NEW BatchCompletionState(flags & (1 << IBatch::TAG_RECORD_COUNTS), 256));
|
||||
statement->rsr_batch_cs = cs;
|
||||
receive_packet(port, packet);
|
||||
statement->rsr_batch_cs = nullptr;
|
||||
|
||||
switch (packet->p_operation)
|
||||
{
|
||||
case op_response:
|
||||
REMOTE_check_response(status, rdb, packet);
|
||||
break;
|
||||
|
||||
case op_batch_cs:
|
||||
return cs.release();
|
||||
|
||||
default:
|
||||
(Arg::Gds(isc_random) << "Unexpected packet type").raise();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void Batch::cancel(CheckStatusWrapper* status)
|
||||
{
|
||||
try
|
||||
{
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Batch::freeClientData(CheckStatusWrapper* status, bool force)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check and validate handles, etc.
|
||||
|
||||
if (!stmt)
|
||||
{
|
||||
Arg::Gds(isc_dsql_cursor_err).raise();
|
||||
}
|
||||
|
||||
Rsr* statement = stmt->getStatement();
|
||||
CHECK_HANDLE(statement, isc_bad_req_handle);
|
||||
Rdb* rdb = statement->rsr_rdb;
|
||||
rem_port* port = rdb->rdb_port;
|
||||
RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION);
|
||||
|
||||
PACKET* packet = &rdb->rdb_packet;
|
||||
packet->p_operation = op_batch_rls;
|
||||
|
||||
P_BATCH_FREE* batch = &packet->p_batch_free;
|
||||
batch->p_batch_statement = statement->rsr_id;
|
||||
|
||||
if (rdb->rdb_port->port_flags & PORT_lazy)
|
||||
{
|
||||
defer_packet(rdb->rdb_port, packet);
|
||||
packet->p_resp.p_resp_object = statement->rsr_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
send_and_receive(status, rdb, packet);
|
||||
}
|
||||
catch (const Exception&)
|
||||
{
|
||||
if (!force)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
releaseStatement();
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
ex.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Batch::releaseStatement()
|
||||
{
|
||||
if (tmpStatement)
|
||||
{
|
||||
stmt->release();
|
||||
}
|
||||
|
||||
stmt = NULL;
|
||||
}
|
||||
|
||||
|
||||
ITransaction* Statement::execute(CheckStatusWrapper* status, ITransaction* apiTra,
|
||||
IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, void* outBuffer)
|
||||
{
|
||||
/**************************************
|
||||
@ -7321,7 +8231,8 @@ static void send_packet(rem_port* port, PACKET* packet)
|
||||
if (!p->sent)
|
||||
{
|
||||
if (!port->send_partial(&p->packet))
|
||||
Arg::Gds(isc_net_write_err).raise();
|
||||
(Arg::Gds(isc_net_write_err) <<
|
||||
Arg::Gds(isc_random) << "send_packet/send_partial").raise();
|
||||
|
||||
p->sent = true;
|
||||
}
|
||||
@ -7330,7 +8241,7 @@ static void send_packet(rem_port* port, PACKET* packet)
|
||||
|
||||
if (!port->send(packet))
|
||||
{
|
||||
Arg::Gds(isc_net_write_err).raise();
|
||||
(Arg::Gds(isc_net_write_err)<< Arg::Gds(isc_random) << "send_packet/send").raise();
|
||||
}
|
||||
}
|
||||
|
||||
@ -7368,7 +8279,8 @@ static void send_partial_packet(rem_port* port, PACKET* packet)
|
||||
{
|
||||
if (!port->send_partial(&p->packet))
|
||||
{
|
||||
Arg::Gds(isc_net_write_err).raise();
|
||||
(Arg::Gds(isc_net_write_err) <<
|
||||
Arg::Gds(isc_random) << "send_partial_packet/send_partial").raise();
|
||||
}
|
||||
p->sent = true;
|
||||
}
|
||||
@ -7376,7 +8288,8 @@ static void send_partial_packet(rem_port* port, PACKET* packet)
|
||||
|
||||
if (!port->send_partial(packet))
|
||||
{
|
||||
Arg::Gds(isc_net_write_err).raise();
|
||||
(Arg::Gds(isc_net_write_err) <<
|
||||
Arg::Gds(isc_random) << "send_partial_packet/send").raise();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,9 @@
|
||||
#include "../common/sdl_proto.h"
|
||||
#include "../common/StatusHolder.h"
|
||||
#include "../common/classes/stack.h"
|
||||
#include "../common/classes/BatchCompletionState.h"
|
||||
#include "../common/utils_proto.h"
|
||||
#include "../dsql/DsqlBatch.h"
|
||||
|
||||
using namespace Firebird;
|
||||
|
||||
@ -70,7 +73,7 @@ inline bool_t P_TRUE(XDR*, PACKET*)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
inline bool_t P_FALSE(XDR*, PACKET*)
|
||||
inline bool_t P_FALSE(XDR* xdrs, PACKET*)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@ -85,6 +88,8 @@ inline void DEBUG_XDR_FREE(XDR*, const void*, const void*, ULONG)
|
||||
}
|
||||
#endif // DEBUG_XDR_MEMORY
|
||||
|
||||
#define P_CHECK(xdr, p, st) if (st.getState() & IStatus::STATE_ERRORS) return P_FALSE(xdr, p)
|
||||
|
||||
#define MAP(routine, ptr) if (!routine (xdrs, &ptr)) return P_FALSE(xdrs, p);
|
||||
const ULONG MAX_OPAQUE = 32768;
|
||||
|
||||
@ -112,6 +117,10 @@ static bool_t xdr_sql_blr(XDR*, SLONG, CSTRING*, bool, SQL_STMT_TYPE);
|
||||
static bool_t xdr_sql_message(XDR*, SLONG);
|
||||
static bool_t xdr_trrq_blr(XDR*, CSTRING*);
|
||||
static bool_t xdr_trrq_message(XDR*, USHORT);
|
||||
static bool_t xdr_bytes(XDR*, void*, ULONG);
|
||||
static bool_t xdr_blob_stream(XDR*, SSHORT, CSTRING*);
|
||||
static Rsr* getStatement(XDR*, USHORT);
|
||||
|
||||
|
||||
#include "../common/xdr_proto.h"
|
||||
|
||||
@ -818,6 +827,281 @@ bool_t xdr_protocol(XDR* xdrs, PACKET* p)
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
case op_batch_create:
|
||||
{
|
||||
P_BATCH_CREATE* b = &p->p_batch_create;
|
||||
MAP(xdr_short, reinterpret_cast<SSHORT&>(b->p_batch_statement));
|
||||
MAP(xdr_cstring_const, b->p_batch_blr);
|
||||
MAP(xdr_u_long, b->p_batch_msglen);
|
||||
MAP(xdr_cstring_const, b->p_batch_pb);
|
||||
|
||||
DEBUG_PRINTSIZE(xdrs, p->p_operation);
|
||||
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
case op_batch_msg:
|
||||
{
|
||||
P_BATCH_MSG* b = &p->p_batch_msg;
|
||||
MAP(xdr_short, reinterpret_cast<SSHORT&>(b->p_batch_statement));
|
||||
MAP(xdr_u_long, b->p_batch_messages);
|
||||
|
||||
if (xdrs->x_op == XDR_FREE)
|
||||
{
|
||||
MAP(xdr_cstring, b->p_batch_data);
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
rem_port* port = (rem_port*) xdrs->x_public;
|
||||
SSHORT statement_id = b->p_batch_statement;
|
||||
Rsr* statement;
|
||||
if (statement_id >= 0)
|
||||
{
|
||||
if (static_cast<ULONG>(statement_id) >= port->port_objects.getCount())
|
||||
return P_FALSE(xdrs, p);
|
||||
|
||||
try
|
||||
{
|
||||
statement = port->port_objects[statement_id];
|
||||
}
|
||||
catch (const status_exception&)
|
||||
{
|
||||
return P_FALSE(xdrs, p);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
statement = port->port_statement;
|
||||
}
|
||||
|
||||
if (!statement)
|
||||
return P_FALSE(xdrs, p);
|
||||
|
||||
ULONG count = b->p_batch_messages;
|
||||
ULONG size = statement->rsr_batch_size;
|
||||
if (xdrs->x_op == XDR_DECODE)
|
||||
{
|
||||
b->p_batch_data.cstr_length = (count ? count : 1) * size;
|
||||
alloc_cstring(xdrs, &b->p_batch_data);
|
||||
}
|
||||
|
||||
RMessage* message = statement->rsr_buffer;
|
||||
if (!message)
|
||||
return P_FALSE(xdrs, p);
|
||||
statement->rsr_buffer = message->msg_next;
|
||||
message->msg_address = b->p_batch_data.cstr_address;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
DEB_BATCH(fprintf(stderr, "BatRem: xdr packed msg\n"));
|
||||
if (!xdr_packed_message(xdrs, message, statement->rsr_format))
|
||||
return P_FALSE(xdrs, p);
|
||||
message->msg_address += statement->rsr_batch_size;
|
||||
}
|
||||
|
||||
message->msg_address = nullptr;
|
||||
DEBUG_PRINTSIZE(xdrs, p->p_operation);
|
||||
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
case op_batch_exec:
|
||||
{
|
||||
P_BATCH_EXEC* b = &p->p_batch_exec;
|
||||
MAP(xdr_short, reinterpret_cast<SSHORT&>(b->p_batch_statement));
|
||||
MAP(xdr_short, reinterpret_cast<SSHORT&>(b->p_batch_transaction));
|
||||
|
||||
if (xdrs->x_op != XDR_FREE)
|
||||
DEB_BATCH(fprintf(stderr, "BatRem: xdr execute\n"));
|
||||
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
case op_batch_cs:
|
||||
{
|
||||
P_BATCH_CS* b = &p->p_batch_cs;
|
||||
MAP(xdr_short, reinterpret_cast<SSHORT&>(b->p_batch_statement));
|
||||
MAP(xdr_u_long, b->p_batch_reccount);
|
||||
MAP(xdr_u_long, b->p_batch_updates);
|
||||
MAP(xdr_u_long, b->p_batch_vectors);
|
||||
MAP(xdr_u_long, b->p_batch_errors);
|
||||
|
||||
if (xdrs->x_op == XDR_FREE)
|
||||
return P_TRUE(xdrs, p);
|
||||
|
||||
rem_port* port = (rem_port*) xdrs->x_public;
|
||||
SSHORT statement_id = b->p_batch_statement;
|
||||
DEB_BATCH(fprintf(stderr, "BatRem: xdr CS %d\n", statement_id));
|
||||
Rsr* statement;
|
||||
|
||||
if (statement_id >= 0)
|
||||
{
|
||||
if (static_cast<ULONG>(statement_id) >= port->port_objects.getCount())
|
||||
return P_FALSE(xdrs, p);
|
||||
|
||||
try
|
||||
{
|
||||
statement = port->port_objects[statement_id];
|
||||
}
|
||||
catch (const status_exception&)
|
||||
{
|
||||
return P_FALSE(xdrs, p);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
statement = port->port_statement;
|
||||
}
|
||||
|
||||
if (!statement)
|
||||
return P_FALSE(xdrs, p);
|
||||
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper status_vector(&ls);
|
||||
|
||||
if ((xdrs->x_op == XDR_DECODE) && (!b->p_batch_updates))
|
||||
{
|
||||
DEB_BATCH(fprintf(stderr, "BatRem: xdr reccount=%d\n", b->p_batch_reccount));
|
||||
statement->rsr_batch_cs->regSize(b->p_batch_reccount);
|
||||
}
|
||||
|
||||
// Process update counters
|
||||
DEB_BATCH(fprintf(stderr, "BatRem: xdr up %d\n", b->p_batch_updates));
|
||||
for (unsigned i = 0; i < b->p_batch_updates; ++i)
|
||||
{
|
||||
SLONG v;
|
||||
|
||||
if (xdrs->x_op == XDR_ENCODE)
|
||||
{
|
||||
v = statement->rsr_batch_ics->getState(&status_vector, i);
|
||||
P_CHECK(xdrs, p, status_vector);
|
||||
}
|
||||
|
||||
MAP(xdr_long, v);
|
||||
|
||||
if (xdrs->x_op == XDR_DECODE)
|
||||
{
|
||||
statement->rsr_batch_cs->regUpdate(v);
|
||||
}
|
||||
}
|
||||
|
||||
// Process status vectors
|
||||
ULONG pos = 0u;
|
||||
LocalStatus to;
|
||||
DEB_BATCH(fprintf(stderr, "BatRem: xdr sv %d\n", b->p_batch_vectors));
|
||||
|
||||
for (unsigned i = 0; i < b->p_batch_vectors; ++i, ++pos)
|
||||
{
|
||||
DynamicStatusVector s;
|
||||
DynamicStatusVector* ptr = NULL;
|
||||
|
||||
if (xdrs->x_op == XDR_ENCODE)
|
||||
{
|
||||
pos = statement->rsr_batch_ics->findError(&status_vector, pos);
|
||||
P_CHECK(xdrs, p, status_vector);
|
||||
if (pos == IBatchCompletionState::NO_MORE_ERRORS)
|
||||
return P_FALSE(xdrs, p);
|
||||
|
||||
statement->rsr_batch_ics->getStatus(&status_vector, &to, pos);
|
||||
if (status_vector.getState() & IStatus::STATE_ERRORS)
|
||||
continue;
|
||||
|
||||
s.load(&to);
|
||||
ptr = &s;
|
||||
}
|
||||
|
||||
MAP(xdr_u_long, pos);
|
||||
|
||||
if (!xdr_status_vector(xdrs, ptr))
|
||||
return P_FALSE(xdrs, p);
|
||||
|
||||
if (xdrs->x_op == XDR_DECODE)
|
||||
{
|
||||
Firebird::Arg::StatusVector sv(ptr->value());
|
||||
sv.copyTo(&to);
|
||||
delete ptr;
|
||||
statement->rsr_batch_cs->regErrorAt(pos, &to);
|
||||
}
|
||||
}
|
||||
|
||||
// Process status-less errors
|
||||
pos = 0u;
|
||||
DEB_BATCH(fprintf(stderr, "BatRem: xdr err %d\n", b->p_batch_errors));
|
||||
|
||||
for (unsigned i = 0; i < b->p_batch_errors; ++i, ++pos)
|
||||
{
|
||||
if (xdrs->x_op == XDR_ENCODE)
|
||||
{
|
||||
pos = statement->rsr_batch_ics->findError(&status_vector, pos);
|
||||
P_CHECK(xdrs, p, status_vector);
|
||||
if (pos == IBatchCompletionState::NO_MORE_ERRORS)
|
||||
return P_FALSE(xdrs, p);
|
||||
|
||||
statement->rsr_batch_ics->getStatus(&status_vector, &to, pos);
|
||||
if (!(status_vector.getState() & IStatus::STATE_ERRORS))
|
||||
continue;
|
||||
}
|
||||
|
||||
MAP(xdr_u_long, pos);
|
||||
|
||||
if (xdrs->x_op == XDR_DECODE)
|
||||
{
|
||||
statement->rsr_batch_cs->regErrorAt(pos, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
case op_batch_rls:
|
||||
{
|
||||
P_BATCH_FREE* b = &p->p_batch_free;
|
||||
MAP(xdr_short, reinterpret_cast<SSHORT&>(b->p_batch_statement));
|
||||
|
||||
if (xdrs->x_op != XDR_FREE)
|
||||
DEB_BATCH(fprintf(stderr, "BatRem: xdr release\n"));
|
||||
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
case op_batch_set_bpb:
|
||||
{
|
||||
P_BATCH_SETBPB* b = &p->p_batch_setbpb;
|
||||
MAP(xdr_short, reinterpret_cast<SSHORT&>(b->p_batch_statement));
|
||||
MAP(xdr_cstring_const, b->p_batch_blob_bpb);
|
||||
|
||||
Rsr* statement = getStatement(xdrs, b->p_batch_statement);
|
||||
if (!statement)
|
||||
return P_FALSE(xdrs, p);
|
||||
if (fb_utils::isBpbSegmented(b->p_batch_blob_bpb.cstr_length, b->p_batch_blob_bpb.cstr_address))
|
||||
statement->rsr_batch_flags |= (1 << Jrd::DsqlBatch::FLAG_DEFAULT_SEGMENTED);
|
||||
else
|
||||
statement->rsr_batch_flags &= ~(1 << Jrd::DsqlBatch::FLAG_DEFAULT_SEGMENTED);
|
||||
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
case op_batch_regblob:
|
||||
{
|
||||
P_BATCH_REGBLOB* b = &p->p_batch_regblob;
|
||||
MAP(xdr_short, reinterpret_cast<SSHORT&>(b->p_batch_statement));
|
||||
MAP(xdr_quad, b->p_batch_exist_id);
|
||||
MAP(xdr_quad, b->p_batch_blob_id);
|
||||
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
case op_batch_blob_stream:
|
||||
{
|
||||
P_BATCH_BLOB* b = &p->p_batch_blob;
|
||||
MAP(xdr_short, reinterpret_cast<SSHORT&>(b->p_batch_statement));
|
||||
if (!xdr_blob_stream(xdrs, b->p_batch_statement, &b->p_batch_blob_data))
|
||||
return P_FALSE(xdrs, p);
|
||||
|
||||
return P_TRUE(xdrs, p);
|
||||
}
|
||||
|
||||
|
||||
///case op_insert:
|
||||
default:
|
||||
#ifdef DEV_BUILD
|
||||
@ -831,6 +1115,25 @@ bool_t xdr_protocol(XDR* xdrs, PACKET* p)
|
||||
}
|
||||
|
||||
|
||||
static bool_t xdr_bytes(XDR* xdrs, void* bytes, ULONG size)
|
||||
{
|
||||
switch (xdrs->x_op)
|
||||
{
|
||||
case XDR_ENCODE:
|
||||
if (!xdrs->x_ops->x_putbytes(xdrs, reinterpret_cast<const SCHAR*>(bytes), size))
|
||||
return FALSE;
|
||||
break;
|
||||
|
||||
case XDR_DECODE:
|
||||
if (!xdrs->x_ops->x_getbytes(xdrs, reinterpret_cast<SCHAR*>(bytes), size))
|
||||
return FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
ULONG xdr_protocol_overhead(P_OP op)
|
||||
{
|
||||
/**************************************
|
||||
@ -1888,3 +2191,245 @@ static void reset_statement( XDR* xdrs, SSHORT statement_id)
|
||||
{} // no-op
|
||||
}
|
||||
}
|
||||
|
||||
static Rsr* getStatement(XDR* xdrs, USHORT statement_id)
|
||||
{
|
||||
rem_port* port = (rem_port*) xdrs->x_public;
|
||||
|
||||
if (statement_id >= 0)
|
||||
{
|
||||
if (statement_id >= port->port_objects.getCount())
|
||||
return nullptr;
|
||||
|
||||
try
|
||||
{
|
||||
return port->port_objects[statement_id];
|
||||
}
|
||||
catch (const status_exception&)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return port->port_statement;
|
||||
}
|
||||
|
||||
static bool_t xdr_blob_stream(XDR* xdrs, SSHORT statement_id, CSTRING* strmPortion)
|
||||
{
|
||||
if (xdrs->x_op == XDR_FREE)
|
||||
return xdr_cstring(xdrs, strmPortion);
|
||||
|
||||
Rsr* statement = getStatement(xdrs, statement_id);
|
||||
if (!statement)
|
||||
return FALSE;
|
||||
|
||||
// create local copy - required in a case when packet is not complete and will be restarted
|
||||
Rsr::BatchStream localStrm(statement->rsr_batch_stream);
|
||||
|
||||
struct BlobFlow
|
||||
{
|
||||
ULONG remains;
|
||||
UCHAR* streamPtr;
|
||||
ULONG& blobSize;
|
||||
ULONG& bpbSize;
|
||||
ULONG& segSize;
|
||||
|
||||
BlobFlow(Rsr::BatchStream* bs)
|
||||
: remains(0), streamPtr(NULL),
|
||||
blobSize(bs->blobRemaining), bpbSize(bs->bpbRemaining), segSize(bs->segRemaining)
|
||||
{ }
|
||||
|
||||
void newBlob(ULONG totalSize, ULONG parSize)
|
||||
{
|
||||
blobSize = totalSize;
|
||||
bpbSize = parSize;
|
||||
segSize = 0;
|
||||
}
|
||||
|
||||
void move(ULONG step)
|
||||
{
|
||||
move2(step);
|
||||
blobSize -= step;
|
||||
}
|
||||
|
||||
void moveBpb(ULONG step)
|
||||
{
|
||||
move(step);
|
||||
bpbSize -= step;
|
||||
}
|
||||
|
||||
void moveSeg(ULONG step)
|
||||
{
|
||||
move(step);
|
||||
segSize -= step;
|
||||
}
|
||||
|
||||
bool align(ULONG alignment)
|
||||
{
|
||||
ULONG a = IPTR(streamPtr) % alignment;
|
||||
if (a)
|
||||
{
|
||||
a = alignment - a;
|
||||
move2(a);
|
||||
if (blobSize)
|
||||
blobSize -= a;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
private:
|
||||
void move2(ULONG step)
|
||||
{
|
||||
streamPtr += step;
|
||||
remains -= step;
|
||||
}
|
||||
};
|
||||
|
||||
BlobFlow flow(&localStrm);
|
||||
|
||||
if (xdrs->x_op == XDR_ENCODE)
|
||||
{
|
||||
flow.remains = strmPortion->cstr_length;
|
||||
strmPortion->cstr_length += localStrm.hdrPrevious;
|
||||
}
|
||||
if (!xdr_u_long(xdrs, &strmPortion->cstr_length))
|
||||
return FALSE;
|
||||
if (xdrs->x_op == XDR_DECODE)
|
||||
flow.remains = strmPortion->cstr_length;
|
||||
|
||||
fb_assert(localStrm.alignment);
|
||||
if (flow.remains % localStrm.alignment)
|
||||
return FALSE;
|
||||
if (!flow.remains)
|
||||
return TRUE;
|
||||
|
||||
if (xdrs->x_op == XDR_DECODE)
|
||||
alloc_cstring(xdrs, strmPortion);
|
||||
|
||||
flow.streamPtr = strmPortion->cstr_address;
|
||||
if (IPTR(flow.streamPtr) % localStrm.alignment != 0)
|
||||
return FALSE;
|
||||
|
||||
while (flow.remains)
|
||||
{
|
||||
if (!flow.blobSize) // we should process next blob header
|
||||
{
|
||||
// align data stream
|
||||
if (flow.align(localStrm.alignment))
|
||||
continue;
|
||||
|
||||
// check for partial header in the stream
|
||||
if (flow.remains + localStrm.hdrPrevious < Rsr::BatchStream::SIZEOF_BLOB_HEAD)
|
||||
{
|
||||
// On the receiver that means packet protocol processing is complete: actual
|
||||
// size of packet is sligtly less than passed in batch_blob_data.cstr_length.
|
||||
if (xdrs->x_op == XDR_DECODE)
|
||||
strmPortion->cstr_length -= flow.remains;
|
||||
// On transmitter reserve partial header for future use
|
||||
else
|
||||
localStrm.saveData(flow.streamPtr, flow.remains);
|
||||
|
||||
// Done with packet
|
||||
break;
|
||||
}
|
||||
|
||||
// parse blob header
|
||||
fb_assert(intptr_t(flow.streamPtr) % localStrm.alignment == 0);
|
||||
unsigned char* hdrPtr = flow.streamPtr; // default is to use header in main buffer
|
||||
unsigned hdrOffset = Rsr::BatchStream::SIZEOF_BLOB_HEAD;
|
||||
if (localStrm.hdrPrevious)
|
||||
{
|
||||
// on transmitter reserved partial header may be used
|
||||
fb_assert(xdrs->x_op == XDR_ENCODE);
|
||||
hdrOffset -= localStrm.hdrPrevious;
|
||||
localStrm.saveData(flow.streamPtr, hdrOffset);
|
||||
hdrPtr = localStrm.hdr;
|
||||
}
|
||||
|
||||
ISC_QUAD* batchBlobId = reinterpret_cast<ISC_QUAD*>(hdrPtr);
|
||||
ULONG* blobSize = reinterpret_cast<ULONG*>(hdrPtr + sizeof(ISC_QUAD));
|
||||
ULONG* bpbSize = reinterpret_cast<ULONG*>(hdrPtr + sizeof(ISC_QUAD) + sizeof(ULONG));
|
||||
if (!xdr_quad(xdrs, batchBlobId))
|
||||
return FALSE;
|
||||
if (!xdr_u_long(xdrs, blobSize))
|
||||
return FALSE;
|
||||
if (!xdr_u_long(xdrs, bpbSize))
|
||||
return FALSE;
|
||||
|
||||
flow.move(hdrOffset);
|
||||
localStrm.hdrPrevious = 0;
|
||||
flow.newBlob(*blobSize, *bpbSize);
|
||||
localStrm.curBpb.clear();
|
||||
|
||||
if (!flow.bpbSize)
|
||||
localStrm.segmented = statement->rsr_batch_flags & (1 << Jrd::DsqlBatch::FLAG_DEFAULT_SEGMENTED);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// process BPB
|
||||
if (flow.bpbSize)
|
||||
{
|
||||
ULONG size = MIN(flow.bpbSize, flow.remains);
|
||||
if (!xdr_bytes(xdrs, flow.streamPtr, size))
|
||||
return FALSE;
|
||||
localStrm.curBpb.add(flow.streamPtr, size);
|
||||
flow.moveBpb(size);
|
||||
if (flow.bpbSize == 0) // bpb is passed completely
|
||||
{
|
||||
try
|
||||
{
|
||||
localStrm.segmented = fb_utils::isBpbSegmented(localStrm.curBpb.getCount(),
|
||||
localStrm.curBpb.begin());
|
||||
}
|
||||
catch (const Exception&)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
localStrm.curBpb.clear();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// pass data
|
||||
ULONG dataSize = MIN(flow.blobSize, flow.remains);
|
||||
if (dataSize)
|
||||
{
|
||||
if (localStrm.segmented)
|
||||
{
|
||||
if (!flow.segSize)
|
||||
{
|
||||
if (flow.align(IBatch::BLOB_SEGHDR_ALIGN))
|
||||
continue;
|
||||
|
||||
USHORT* segSize = reinterpret_cast<USHORT*>(flow.streamPtr);
|
||||
if (!xdr_u_short(xdrs, segSize))
|
||||
return FALSE;
|
||||
flow.segSize = *segSize;
|
||||
flow.move(sizeof(USHORT));
|
||||
|
||||
if (flow.segSize > flow.blobSize)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dataSize = MIN(flow.segSize, flow.remains);
|
||||
if (!xdr_bytes(xdrs, flow.streamPtr, dataSize))
|
||||
return FALSE;
|
||||
flow.moveSeg(dataSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!xdr_bytes(xdrs, flow.streamPtr, dataSize))
|
||||
return FALSE;
|
||||
flow.move(dataSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// packet processed successfully - save stream data for next one
|
||||
statement->rsr_batch_stream = localStrm;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -276,6 +276,15 @@ enum P_OP
|
||||
op_cond_accept = 98, // Server accepts connection, returns some data to client
|
||||
// and asks client to continue authentication before attach call
|
||||
|
||||
op_batch_create = 99,
|
||||
op_batch_msg = 100,
|
||||
op_batch_exec = 101,
|
||||
op_batch_rls = 102,
|
||||
op_batch_cs = 103,
|
||||
op_batch_regblob = 104,
|
||||
op_batch_blob_stream = 105,
|
||||
op_batch_set_bpb = 106,
|
||||
|
||||
op_max
|
||||
};
|
||||
|
||||
@ -645,6 +654,63 @@ typedef struct p_crypt_callback
|
||||
} P_CRYPT_CALLBACK;
|
||||
|
||||
|
||||
// Batch definitions
|
||||
|
||||
typedef struct p_batch_create
|
||||
{
|
||||
OBJCT p_batch_statement; // statement object
|
||||
CSTRING_CONST p_batch_blr; // blr describing input messages
|
||||
ULONG p_batch_msglen; // explicit message length
|
||||
CSTRING_CONST p_batch_pb; // parameters block
|
||||
} P_BATCH_CREATE;
|
||||
|
||||
typedef struct p_batch_msg
|
||||
{
|
||||
OBJCT p_batch_statement; // statement object
|
||||
ULONG p_batch_messages; // number of messages
|
||||
CSTRING p_batch_data;
|
||||
} P_BATCH_MSG;
|
||||
|
||||
typedef struct p_batch_exec
|
||||
{
|
||||
OBJCT p_batch_statement; // statement object
|
||||
OBJCT p_batch_transaction; // transaction object
|
||||
} P_BATCH_EXEC;
|
||||
|
||||
typedef struct p_batch_cs // completion state
|
||||
{
|
||||
OBJCT p_batch_statement; // statement object
|
||||
ULONG p_batch_reccount; // total records
|
||||
ULONG p_batch_updates; // update counters
|
||||
ULONG p_batch_vectors; // recnum + status vector pairs
|
||||
ULONG p_batch_errors; // error's recnums
|
||||
} P_BATCH_CS;
|
||||
|
||||
typedef struct p_batch_free
|
||||
{
|
||||
OBJCT p_batch_statement; // statement object
|
||||
} P_BATCH_FREE;
|
||||
|
||||
typedef struct p_batch_blob
|
||||
{
|
||||
OBJCT p_batch_statement; // statement object
|
||||
CSTRING p_batch_blob_data; // data
|
||||
} P_BATCH_BLOB;
|
||||
|
||||
typedef struct p_batch_regblob
|
||||
{
|
||||
OBJCT p_batch_statement; // statement object
|
||||
SQUAD p_batch_exist_id; // id of blob to register
|
||||
SQUAD p_batch_blob_id; // blob id
|
||||
} P_BATCH_REGBLOB;
|
||||
|
||||
typedef struct p_batch_setbpb
|
||||
{
|
||||
OBJCT p_batch_statement; // statement object
|
||||
CSTRING_CONST p_batch_blob_bpb; // BPB
|
||||
} P_BATCH_SETBPB;
|
||||
|
||||
|
||||
// Generalize packet (sic!)
|
||||
|
||||
typedef struct packet
|
||||
@ -688,6 +754,14 @@ typedef struct packet
|
||||
P_AUTH_CONT p_auth_cont; // Request more auth data
|
||||
P_CRYPT p_crypt; // Start wire crypt
|
||||
P_CRYPT_CALLBACK p_cc; // Database crypt callback
|
||||
P_BATCH_CREATE p_batch_create; // Create batch interface
|
||||
P_BATCH_MSG p_batch_msg; // Add messages to batch
|
||||
P_BATCH_EXEC p_batch_exec; // Run batch
|
||||
P_BATCH_FREE p_batch_free; // Destroy batch
|
||||
P_BATCH_CS p_batch_cs; // Batch completion state
|
||||
P_BATCH_BLOB p_batch_blob; // BLOB stream portion in batch
|
||||
P_BATCH_REGBLOB p_batch_regblob; // Register already existing BLOB in batch
|
||||
P_BATCH_SETBPB p_batch_setbpb; // Set default BPB for batch
|
||||
|
||||
public:
|
||||
packet()
|
||||
|
@ -64,6 +64,8 @@
|
||||
//#define COMPRESS_DEBUG 1
|
||||
#endif // WIRE_COMPRESS_SUPPORT
|
||||
|
||||
#define DEB_BATCH(x)
|
||||
|
||||
#define REM_SEND_OFFSET(bs) (0)
|
||||
#define REM_RECV_OFFSET(bs) (bs)
|
||||
|
||||
@ -103,6 +105,7 @@ const ULONG MAX_BATCH_CACHE_SIZE = 1024 * 1024; // 1 MB
|
||||
// fwd. decl.
|
||||
namespace Firebird {
|
||||
class Exception;
|
||||
class BatchCompletionState;
|
||||
}
|
||||
|
||||
#ifdef WIN_NT
|
||||
@ -127,6 +130,7 @@ typedef Firebird::RefPtr<Firebird::IBlob> ServBlob;
|
||||
typedef Firebird::RefPtr<Firebird::ITransaction> ServTransaction;
|
||||
typedef Firebird::RefPtr<Firebird::IStatement> ServStatement;
|
||||
typedef Firebird::RefPtr<Firebird::IResultSet> ServCursor;
|
||||
typedef Firebird::RefPtr<Firebird::IBatch> ServBatch;
|
||||
typedef Firebird::RefPtr<Firebird::IRequest> ServRequest;
|
||||
typedef Firebird::RefPtr<Firebird::IEvents> ServEvents;
|
||||
typedef Firebird::RefPtr<Firebird::IService> ServService;
|
||||
@ -464,6 +468,7 @@ struct Rsr : public Firebird::GlobalStorage, public TypedHandle<rem_type_rsr>
|
||||
Rtr* rsr_rtr;
|
||||
ServStatement rsr_iface;
|
||||
ServCursor rsr_cursor;
|
||||
ServBatch rsr_batch;
|
||||
rem_fmt* rsr_bind_format; // Format of bind message
|
||||
rem_fmt* rsr_select_format; // Format of select message
|
||||
rem_fmt* rsr_user_select_format; // Format of user's select message
|
||||
@ -485,6 +490,41 @@ struct Rsr : public Firebird::GlobalStorage, public TypedHandle<rem_type_rsr>
|
||||
unsigned int rsr_timeout; // Statement timeout to be set on open\execute
|
||||
Rsr** rsr_self;
|
||||
|
||||
ULONG rsr_batch_size; // Aligned message size for IBatch operations
|
||||
ULONG rsr_batch_flags; // Flags for batch processing
|
||||
union // BatchCS passed to XDR protocol
|
||||
{
|
||||
Firebird::IBatchCompletionState* rsr_batch_ics; // server
|
||||
Firebird::BatchCompletionState* rsr_batch_cs; // client
|
||||
};
|
||||
|
||||
struct BatchStream
|
||||
{
|
||||
BatchStream()
|
||||
: curBpb(*getDefaultMemoryPool()), hdrPrevious(0), segmented(false)
|
||||
{ }
|
||||
|
||||
static const ULONG SIZEOF_BLOB_HEAD = sizeof(ISC_QUAD) + 2 * sizeof(ULONG);
|
||||
|
||||
typedef Firebird::HalfStaticArray<UCHAR, 64> Bpb;
|
||||
Bpb curBpb;
|
||||
UCHAR hdr[SIZEOF_BLOB_HEAD];
|
||||
ULONG blobRemaining; // Remaining to transfer size of blob data
|
||||
ULONG bpbRemaining; // Remaining to transfer size of BPB
|
||||
ULONG segRemaining; // Remaining to transfer size of segment data
|
||||
USHORT alignment; // Alignment in BLOB stream
|
||||
USHORT hdrPrevious; // Header data left from previous block (in hdr)
|
||||
bool segmented; // Current blob kind
|
||||
|
||||
void saveData(const UCHAR* data, ULONG size)
|
||||
{
|
||||
fb_assert(size + hdrPrevious <= SIZEOF_BLOB_HEAD);
|
||||
memcpy(&hdr[hdrPrevious], data, size);
|
||||
hdrPrevious += size;
|
||||
}
|
||||
};
|
||||
BatchStream rsr_batch_stream;
|
||||
|
||||
public:
|
||||
// Values for rsr_flags.
|
||||
enum {
|
||||
@ -500,7 +540,7 @@ public:
|
||||
|
||||
public:
|
||||
Rsr() :
|
||||
rsr_next(0), rsr_rdb(0), rsr_rtr(0), rsr_iface(NULL), rsr_cursor(NULL),
|
||||
rsr_next(0), rsr_rdb(0), rsr_rtr(0), rsr_iface(NULL), rsr_cursor(NULL), rsr_batch(NULL),
|
||||
rsr_bind_format(0), rsr_select_format(0), rsr_user_select_format(0),
|
||||
rsr_format(0), rsr_message(0), rsr_buffer(0), rsr_status(0),
|
||||
rsr_id(0), rsr_fmt_length(0),
|
||||
@ -532,6 +572,7 @@ public:
|
||||
static ISC_STATUS badHandle() { return isc_bad_req_handle; }
|
||||
void checkIface(ISC_STATUS code = isc_unprepared_stmt);
|
||||
void checkCursor();
|
||||
void checkBatch();
|
||||
};
|
||||
|
||||
|
||||
@ -1211,6 +1252,13 @@ public:
|
||||
ISC_STATUS transact_request(P_TRRQ *, PACKET*);
|
||||
SSHORT asyncReceive(PACKET* asyncPacket, const UCHAR* buffer, SSHORT dataSize);
|
||||
void start_crypt(P_CRYPT*, PACKET*);
|
||||
void batch_create(P_BATCH_CREATE*, PACKET*);
|
||||
void batch_msg(P_BATCH_MSG*, PACKET*);
|
||||
void batch_blob_stream(P_BATCH_BLOB*, PACKET*);
|
||||
void batch_regblob(P_BATCH_REGBLOB*, PACKET*);
|
||||
void batch_exec(P_BATCH_EXEC*, PACKET*);
|
||||
void batch_rls(P_BATCH_FREE*, PACKET*);
|
||||
void batch_bpb(P_BATCH_SETBPB*, PACKET*);
|
||||
|
||||
Firebird::string getRemoteId() const;
|
||||
void auxAcceptError(PACKET* packet);
|
||||
|
@ -2059,6 +2059,13 @@ void Rsr::checkCursor()
|
||||
}
|
||||
|
||||
|
||||
void Rsr::checkBatch()
|
||||
{
|
||||
if (!rsr_batch)
|
||||
(Arg::Gds(isc_random) << "Batch is not created").raise();
|
||||
}
|
||||
|
||||
|
||||
static ISC_STATUS allocate_statement( rem_port* port, /*P_RLSE* allocate,*/ PACKET* send)
|
||||
{
|
||||
/**************************************
|
||||
@ -3318,6 +3325,231 @@ ISC_STATUS rem_port::execute_immediate(P_OP op, P_SQLST * exnow, PACKET* sendL)
|
||||
}
|
||||
|
||||
|
||||
void rem_port::batch_create(P_BATCH_CREATE* batch, PACKET* sendL)
|
||||
{
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper status_vector(&ls);
|
||||
Rsr* statement;
|
||||
getHandle(statement, batch->p_batch_statement);
|
||||
statement->checkIface();
|
||||
|
||||
const ULONG blr_length = batch->p_batch_blr.cstr_length;
|
||||
const UCHAR* blr = batch->p_batch_blr.cstr_address;
|
||||
if (!blr)
|
||||
(Arg::Gds(isc_random) << "Missing required format info in createBatch()").raise();
|
||||
InternalMessageBuffer msgBuffer(blr_length, blr, batch->p_batch_msglen, NULL);
|
||||
|
||||
// Flush out any previous format information
|
||||
// that might be hanging around from an earlier execution.
|
||||
|
||||
delete statement->rsr_bind_format;
|
||||
statement->rsr_bind_format = PARSE_msg_format(blr, blr_length);
|
||||
|
||||
// If we know the length of the message, make sure there is a buffer
|
||||
// large enough to hold it.
|
||||
|
||||
if (!(statement->rsr_format = statement->rsr_bind_format))
|
||||
(Arg::Gds(isc_random) << "Error parsing message format in createBatch()").raise();
|
||||
|
||||
RMessage* message = statement->rsr_buffer;
|
||||
if (!message || statement->rsr_format->fmt_length > statement->rsr_fmt_length)
|
||||
{
|
||||
RMessage* const org_message = message;
|
||||
const ULONG org_length = message ? statement->rsr_fmt_length : 0;
|
||||
statement->rsr_fmt_length = statement->rsr_format->fmt_length;
|
||||
statement->rsr_buffer = message = FB_NEW RMessage(statement->rsr_fmt_length);
|
||||
statement->rsr_message = message;
|
||||
message->msg_next = message;
|
||||
if (org_length)
|
||||
{
|
||||
// dimitr: the original buffer might have something useful inside
|
||||
// (filled by a prior xdr_sql_message() call, for example),
|
||||
// so its contents must be preserved (see CORE-3730)
|
||||
memcpy(message->msg_buffer, org_message->msg_buffer, org_length);
|
||||
}
|
||||
REMOTE_release_messages(org_message);
|
||||
}
|
||||
|
||||
ClumpletWriter wrt(ClumpletReader::WideTagged, MAX_DPB_SIZE,
|
||||
batch->p_batch_pb.cstr_address, batch->p_batch_pb.cstr_length);
|
||||
if (wrt.getBufferLength() && (wrt.getBufferTag() != IBatch::VERSION1))
|
||||
(Arg::Gds(isc_random) << "Invalid tag in parameters block").raise();
|
||||
statement->rsr_batch_flags = (wrt.find(IBatch::TAG_RECORD_COUNTS) && wrt.getInt()) ?
|
||||
(1 << IBatch::TAG_RECORD_COUNTS) : 0;
|
||||
if (wrt.find(IBatch::TAG_BLOB_POLICY) && (wrt.getInt() != IBatch::BLOB_STREAM))
|
||||
{
|
||||
// we always send blobs in a stream therefore change policy
|
||||
wrt.deleteClumplet();
|
||||
wrt.insertInt(IBatch::TAG_BLOB_POLICY, IBatch::BLOB_STREAM);
|
||||
}
|
||||
|
||||
statement->rsr_batch =
|
||||
statement->rsr_iface->createBatch(&status_vector, msgBuffer.metadata,
|
||||
wrt.getBufferLength(), wrt.getBuffer());
|
||||
|
||||
statement->rsr_batch_size = 0;
|
||||
statement->rsr_batch_stream.blobRemaining = 0;
|
||||
if (!(status_vector.getState() & Firebird::IStatus::STATE_ERRORS))
|
||||
{
|
||||
if (msgBuffer.metadata)
|
||||
{
|
||||
statement->rsr_batch_size = msgBuffer.metadata->getAlignedLength(&status_vector);
|
||||
check(&status_vector);
|
||||
}
|
||||
else
|
||||
{
|
||||
IMessageMetadata* m = statement->rsr_iface->getInputMetadata(&status_vector);
|
||||
check(&status_vector);
|
||||
statement->rsr_batch_size = m->getAlignedLength(&status_vector);
|
||||
m->release();
|
||||
check(&status_vector);
|
||||
}
|
||||
|
||||
statement->rsr_batch_stream.alignment = statement->rsr_batch->getBlobAlignment(&status_vector);
|
||||
check(&status_vector);
|
||||
}
|
||||
|
||||
this->send_response(sendL, 0, 0, &status_vector, true);
|
||||
}
|
||||
|
||||
void rem_port::batch_msg(P_BATCH_MSG* batch, PACKET* sendL)
|
||||
{
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper status_vector(&ls);
|
||||
|
||||
Rsr* statement;
|
||||
getHandle(statement, batch->p_batch_statement);
|
||||
statement->checkIface();
|
||||
statement->checkBatch();
|
||||
|
||||
const ULONG count = batch->p_batch_messages;
|
||||
const void* data = batch->p_batch_data.cstr_address;
|
||||
|
||||
statement->rsr_batch->add(&status_vector, count, data);
|
||||
|
||||
this->send_response(sendL, 0, 0, &status_vector, true);
|
||||
}
|
||||
|
||||
|
||||
void rem_port::batch_blob_stream(P_BATCH_BLOB* batch, PACKET* sendL)
|
||||
{
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper status_vector(&ls);
|
||||
|
||||
Rsr* statement;
|
||||
getHandle(statement, batch->p_batch_statement);
|
||||
statement->checkIface();
|
||||
statement->checkBatch();
|
||||
|
||||
statement->rsr_batch->addBlobStream(&status_vector,
|
||||
batch->p_batch_blob_data.cstr_length, batch->p_batch_blob_data.cstr_address);
|
||||
|
||||
this->send_response(sendL, 0, 0, &status_vector, true);
|
||||
}
|
||||
|
||||
void rem_port::batch_bpb(P_BATCH_SETBPB* batch, PACKET* sendL)
|
||||
{
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper status_vector(&ls);
|
||||
|
||||
Rsr* statement;
|
||||
getHandle(statement, batch->p_batch_statement);
|
||||
statement->checkIface();
|
||||
statement->checkBatch();
|
||||
|
||||
statement->rsr_batch->setDefaultBpb(&status_vector,
|
||||
batch->p_batch_blob_bpb.cstr_length, batch->p_batch_blob_bpb.cstr_address);
|
||||
|
||||
this->send_response(sendL, 0, 0, &status_vector, true);
|
||||
}
|
||||
|
||||
|
||||
void rem_port::batch_regblob(P_BATCH_REGBLOB* batch, PACKET* sendL)
|
||||
{
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper status_vector(&ls);
|
||||
|
||||
Rsr* statement;
|
||||
getHandle(statement, batch->p_batch_statement);
|
||||
statement->checkIface();
|
||||
statement->checkBatch();
|
||||
|
||||
statement->rsr_batch->registerBlob(&status_vector, &batch->p_batch_exist_id,
|
||||
&batch->p_batch_blob_id);
|
||||
|
||||
this->send_response(sendL, 0, 0, &status_vector, true);
|
||||
}
|
||||
|
||||
|
||||
void rem_port::batch_exec(P_BATCH_EXEC* batch, PACKET* sendL)
|
||||
{
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper status_vector(&ls);
|
||||
|
||||
Rsr* statement;
|
||||
getHandle(statement, batch->p_batch_statement);
|
||||
statement->checkIface();
|
||||
statement->checkBatch();
|
||||
|
||||
Rtr* transaction = NULL;
|
||||
getHandle(transaction, batch->p_batch_transaction);
|
||||
|
||||
AutoPtr<IBatchCompletionState, SimpleDispose<IBatchCompletionState> >
|
||||
ics(statement->rsr_batch->execute(&status_vector, transaction->rtr_iface));
|
||||
|
||||
if (status_vector.getState() & IStatus::STATE_ERRORS)
|
||||
{
|
||||
this->send_response(sendL, 0, 0, &status_vector, false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool recordCounts = statement->rsr_batch_flags & (1 << IBatch::TAG_RECORD_COUNTS);
|
||||
P_BATCH_CS* pcs = &sendL->p_batch_cs;
|
||||
sendL->p_operation = op_batch_cs;
|
||||
pcs->p_batch_statement = statement->rsr_id;
|
||||
pcs->p_batch_reccount = ics->getSize(&status_vector);
|
||||
check(&status_vector);
|
||||
pcs->p_batch_updates = recordCounts ? pcs->p_batch_reccount : 0;
|
||||
pcs->p_batch_errors = pcs->p_batch_vectors = 0;
|
||||
|
||||
for (unsigned int pos = 0u;
|
||||
(pos = ics->findError(&status_vector, pos)) != IBatchCompletionState::NO_MORE_ERRORS;
|
||||
++pos)
|
||||
{
|
||||
check(&status_vector);
|
||||
|
||||
LocalStatus dummy;
|
||||
ics->getStatus(&status_vector, &dummy, pos);
|
||||
if (status_vector.getState() & IStatus::STATE_ERRORS)
|
||||
pcs->p_batch_errors++;
|
||||
else
|
||||
pcs->p_batch_vectors++;
|
||||
}
|
||||
|
||||
check(&status_vector);
|
||||
|
||||
statement->rsr_batch_ics = ics;
|
||||
this->send(sendL);
|
||||
}
|
||||
|
||||
|
||||
void rem_port::batch_rls(P_BATCH_FREE* batch, PACKET* sendL)
|
||||
{
|
||||
LocalStatus ls;
|
||||
CheckStatusWrapper status_vector(&ls);
|
||||
|
||||
Rsr* statement;
|
||||
getHandle(statement, batch->p_batch_statement);
|
||||
statement->checkIface();
|
||||
statement->checkBatch();
|
||||
|
||||
statement->rsr_batch->release();
|
||||
statement->rsr_batch = nullptr;
|
||||
|
||||
this->send_response(sendL, 0, 0, &status_vector, true);
|
||||
}
|
||||
|
||||
|
||||
ISC_STATUS rem_port::execute_statement(P_OP op, P_SQLDATA* sqldata, PACKET* sendL)
|
||||
{
|
||||
/*****************************************
|
||||
@ -4533,6 +4765,33 @@ static bool process_packet(rem_port* port, PACKET* sendL, PACKET* receive, rem_p
|
||||
port->start_crypt(&receive->p_crypt, sendL);
|
||||
break;
|
||||
|
||||
case op_batch_create:
|
||||
port->batch_create(&receive->p_batch_create, sendL);
|
||||
break;
|
||||
|
||||
case op_batch_msg:
|
||||
port->batch_msg(&receive->p_batch_msg, sendL);
|
||||
break;
|
||||
|
||||
case op_batch_exec:
|
||||
port->batch_exec(&receive->p_batch_exec, sendL);
|
||||
break;
|
||||
|
||||
case op_batch_rls:
|
||||
port->batch_rls(&receive->p_batch_free, sendL);
|
||||
break;
|
||||
|
||||
case op_batch_blob_stream:
|
||||
port->batch_blob_stream(&receive->p_batch_blob, sendL);
|
||||
break;
|
||||
|
||||
case op_batch_regblob:
|
||||
port->batch_regblob(&receive->p_batch_regblob, sendL);
|
||||
break;
|
||||
|
||||
case op_batch_set_bpb:
|
||||
port->batch_bpb(&receive->p_batch_setbpb, sendL);
|
||||
|
||||
///case op_insert:
|
||||
default:
|
||||
gds__log("SERVER/process_packet: don't understand packet type %d", receive->p_operation);
|
||||
|
@ -335,6 +335,34 @@ public:
|
||||
YStatement* statement;
|
||||
};
|
||||
|
||||
class YBatch FB_FINAL :
|
||||
public YHelper<YBatch, Firebird::IBatchImpl<YBatch, Firebird::CheckStatusWrapper> >
|
||||
{
|
||||
public:
|
||||
static const ISC_STATUS ERROR_CODE = isc_bad_result_set; // isc_bad_batch
|
||||
|
||||
YBatch(YAttachment* anAttachment, Firebird::IBatch* aNext);
|
||||
|
||||
void destroy(unsigned dstrFlags);
|
||||
|
||||
// IBatch implementation
|
||||
void add(Firebird::CheckStatusWrapper* status, unsigned count, const void* inBuffer);
|
||||
void addBlob(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId,
|
||||
unsigned parLength, const unsigned char* par);
|
||||
void appendBlobData(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer);
|
||||
void addBlobStream(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer);
|
||||
unsigned getBlobAlignment(Firebird::CheckStatusWrapper* status);
|
||||
Firebird::IMessageMetadata* getMetadata(Firebird::CheckStatusWrapper* status);
|
||||
void registerBlob(Firebird::CheckStatusWrapper* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId);
|
||||
Firebird::IBatchCompletionState* execute(Firebird::CheckStatusWrapper* status, Firebird::ITransaction* transaction);
|
||||
void cancel(Firebird::CheckStatusWrapper* status);
|
||||
void setDefaultBpb(Firebird::CheckStatusWrapper* status, unsigned parLength, const unsigned char* par);
|
||||
|
||||
public:
|
||||
YAttachment* attachment;
|
||||
};
|
||||
|
||||
|
||||
class YMetadata
|
||||
{
|
||||
public:
|
||||
@ -381,6 +409,8 @@ public:
|
||||
|
||||
unsigned int getTimeout(Firebird::CheckStatusWrapper* status);
|
||||
void setTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
|
||||
YBatch* createBatch(Firebird::CheckStatusWrapper* status, Firebird::IMessageMetadata* inMetadata,
|
||||
unsigned parLength, const unsigned char* par);
|
||||
|
||||
public:
|
||||
Firebird::Mutex statementMutex;
|
||||
@ -477,6 +507,9 @@ public:
|
||||
void setIdleTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
|
||||
unsigned int getStatementTimeout(Firebird::CheckStatusWrapper* status);
|
||||
void setStatementTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
|
||||
YBatch* createBatch(Firebird::CheckStatusWrapper* status, Firebird::ITransaction* transaction,
|
||||
unsigned stmtLength, const char* sqlStmt, unsigned dialect,
|
||||
Firebird::IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par);
|
||||
|
||||
public:
|
||||
Firebird::IProvider* provider;
|
||||
|
@ -757,8 +757,16 @@ public:
|
||||
k = ClumpletReader::Tpb;
|
||||
tag = isc_tpb_version3;
|
||||
break;
|
||||
case BATCH:
|
||||
k = ClumpletReader::WideTagged;
|
||||
tag = IBatch::VERSION1;
|
||||
break;
|
||||
case BPB:
|
||||
k = ClumpletReader::Tagged;
|
||||
tag = isc_bpb_version1;
|
||||
break;
|
||||
default:
|
||||
fatal_exception::raiseFmt("Wrong parameters block kind %d, should be from %d to %d", kind, DPB, TPB);
|
||||
fatal_exception::raiseFmt("Wrong parameters block kind %d, should be from %d to %d", kind, DPB, BPB);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -148,6 +148,8 @@ public:
|
||||
|
||||
IMetadataBuilder* getBuilder(CheckStatusWrapper* status);
|
||||
unsigned getMessageLength(CheckStatusWrapper* status);
|
||||
unsigned getAlignment(CheckStatusWrapper* status);
|
||||
unsigned getAlignedLength(CheckStatusWrapper* status);
|
||||
|
||||
void gatherData(DataBuffer& to); // Copy data from SQLDA into target buffer.
|
||||
void scatterData(DataBuffer& from);
|
||||
@ -170,7 +172,7 @@ private:
|
||||
unsigned indOffset;
|
||||
} *offsets;
|
||||
|
||||
unsigned length;
|
||||
unsigned length, alignment;
|
||||
bool speedHackEnabled; // May be user by stupid luck use right buffer format even with SQLDA interface?..
|
||||
};
|
||||
|
||||
@ -223,7 +225,7 @@ private:
|
||||
};
|
||||
|
||||
SQLDAMetadata::SQLDAMetadata(const XSQLDA* aSqlda)
|
||||
: sqlda(aSqlda), count(0), offsets(NULL), length(0), speedHackEnabled(false)
|
||||
: sqlda(aSqlda), count(0), offsets(NULL), length(0), alignment(0), speedHackEnabled(false)
|
||||
{
|
||||
if (sqlda && sqlda->version != SQLDA_VERSION1)
|
||||
{
|
||||
@ -457,10 +459,14 @@ void SQLDAMetadata::assign()
|
||||
}
|
||||
// No matter how good or bad is the way data is placed in message buffer, it cannot be changed
|
||||
// because changing of it on current codebase will completely kill remote module and may be the engine as well
|
||||
unsigned dtype;
|
||||
length = fb_utils::sqlTypeToDsc(length, var.sqltype, var.sqllen,
|
||||
NULL /*dtype*/, NULL /*length*/, &it.offset, &it.indOffset);
|
||||
&dtype, NULL /*length*/, &it.offset, &it.indOffset);
|
||||
if (it.offset != var.sqldata - base || it.indOffset != ((ISC_SCHAR*) (var.sqlind)) - base)
|
||||
speedHackEnabled = false; // No luck
|
||||
|
||||
if (dtype < DTYPE_TYPE_MAX)
|
||||
alignment = MAX(alignment, type_alignments[dtype]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,6 +477,20 @@ unsigned SQLDAMetadata::getMessageLength(CheckStatusWrapper* status)
|
||||
return length;
|
||||
}
|
||||
|
||||
unsigned SQLDAMetadata::getAlignment(CheckStatusWrapper* status)
|
||||
{
|
||||
if (!offsets)
|
||||
assign();
|
||||
return alignment;
|
||||
}
|
||||
|
||||
unsigned SQLDAMetadata::getAlignedLength(CheckStatusWrapper* status)
|
||||
{
|
||||
if (!offsets)
|
||||
assign();
|
||||
return FB_ALIGN(length, alignment);
|
||||
}
|
||||
|
||||
void SQLDAMetadata::gatherData(DataBuffer& to)
|
||||
{
|
||||
fb_assert(sqlda); // Ensure that data is gathered before take off because later they can be already changed
|
||||
@ -2687,6 +2707,32 @@ ISC_STATUS API_ROUTINE fb_dsql_set_timeout(ISC_STATUS* userStatus, FB_API_HANDLE
|
||||
}
|
||||
|
||||
|
||||
// Get interface by legacy handle
|
||||
/*ISC_STATUS API_ROUTINE fb_get_statement_interface(ISC_STATUS* userStatus, FB_API_HANDLE* stmtHandle,
|
||||
void** stmtIface)
|
||||
{
|
||||
StatusVector status(userStatus);
|
||||
CheckStatusWrapper statusWrapper(&status);
|
||||
|
||||
try
|
||||
{
|
||||
RefPtr<IscStatement> statement(translateHandle(statements, stmtHandle));
|
||||
statement->checkPrepared();
|
||||
|
||||
fb_assert(statement->statement);
|
||||
IStatement* rc = statement->statement;
|
||||
rc->addRef();
|
||||
*stmtIface = rc;
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(&statusWrapper);
|
||||
}
|
||||
|
||||
return status[1];
|
||||
}
|
||||
*/
|
||||
|
||||
// Provide information on sql statement.
|
||||
ISC_STATUS API_ROUTINE isc_dsql_sql_info(ISC_STATUS* userStatus, FB_API_HANDLE* stmtHandle,
|
||||
SSHORT itemLength, const SCHAR* items, SSHORT bufferLength, SCHAR* buffer)
|
||||
@ -4317,7 +4363,7 @@ ITransaction* YStatement::execute(CheckStatusWrapper* status, ITransaction* tran
|
||||
return transaction;
|
||||
}
|
||||
|
||||
IResultSet* YStatement::openCursor(Firebird::CheckStatusWrapper* status, ITransaction* transaction,
|
||||
IResultSet* YStatement::openCursor(CheckStatusWrapper* status, ITransaction* transaction,
|
||||
IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, unsigned int flags)
|
||||
{
|
||||
try
|
||||
@ -4369,6 +4415,31 @@ void YStatement::free(CheckStatusWrapper* status)
|
||||
}
|
||||
}
|
||||
|
||||
YBatch* YStatement::createBatch(CheckStatusWrapper* status, IMessageMetadata* inMetadata, unsigned parLength,
|
||||
const unsigned char* par)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YStatement> entry(status, this);
|
||||
|
||||
IBatch* batch = entry.next()->createBatch(status, inMetadata, parLength, par);
|
||||
if (status->getState() & Firebird::IStatus::STATE_ERRORS)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
YBatch* newBatch = FB_NEW YBatch(attachment, batch);
|
||||
newBatch->addRef();
|
||||
return newBatch;
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
IscStatement::~IscStatement()
|
||||
@ -4756,6 +4827,177 @@ void YResultSet::close(CheckStatusWrapper* status)
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
|
||||
YBatch::YBatch(YAttachment* anAttachment, IBatch* aNext)
|
||||
: YHelper(aNext),
|
||||
attachment(anAttachment)
|
||||
{ }
|
||||
|
||||
|
||||
void YBatch::destroy(unsigned dstrFlags)
|
||||
{
|
||||
destroy2(dstrFlags);
|
||||
}
|
||||
|
||||
|
||||
void YBatch::add(CheckStatusWrapper* status, unsigned count, const void* inBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
entry.next()->add(status, count, inBuffer);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void YBatch::addBlob(CheckStatusWrapper* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId,
|
||||
unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
entry.next()->addBlob(status, length, inBuffer, blobId, parLength, par);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void YBatch::appendBlobData(CheckStatusWrapper* status, unsigned length, const void* inBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
entry.next()->appendBlobData(status, length, inBuffer);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void YBatch::addBlobStream(CheckStatusWrapper* status, unsigned length, const void* inBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
entry.next()->addBlobStream(status, length, inBuffer);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned YBatch::getBlobAlignment(CheckStatusWrapper* status)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
return entry.next()->getBlobAlignment(status);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void YBatch::setDefaultBpb(CheckStatusWrapper* status, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
entry.next()->setDefaultBpb(status, parLength, par);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IMessageMetadata* YBatch::getMetadata(CheckStatusWrapper* status)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
return entry.next()->getMetadata(status);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void YBatch::registerBlob(CheckStatusWrapper* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
entry.next()->registerBlob(status, existingBlob, blobId);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IBatchCompletionState* YBatch::execute(CheckStatusWrapper* status, ITransaction* transaction)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
return entry.next()->execute(status, transaction);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void YBatch::cancel(CheckStatusWrapper* status)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YBatch> entry(status, this);
|
||||
|
||||
entry.next()->cancel(status);
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
@ -5655,8 +5897,39 @@ void YAttachment::setStatementTimeout(CheckStatusWrapper* status, unsigned int t
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
YBatch* YAttachment::createBatch(CheckStatusWrapper* status, ITransaction* transaction,
|
||||
unsigned stmtLength, const char* sqlStmt, unsigned dialect,
|
||||
IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par)
|
||||
{
|
||||
try
|
||||
{
|
||||
YEntry<YAttachment> entry(status, this);
|
||||
|
||||
NextTransaction trans;
|
||||
if (transaction)
|
||||
getNextTransaction(status, transaction, trans);
|
||||
|
||||
IBatch* batch = entry.next()->createBatch(status, trans, stmtLength, sqlStmt, dialect,
|
||||
inMetadata, parLength, par);
|
||||
if (status->getState() & Firebird::IStatus::STATE_ERRORS)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
YBatch* newBatch = FB_NEW YBatch(this, batch);
|
||||
newBatch->addRef();
|
||||
return newBatch;
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
e.stuffException(status);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
YService::YService(IProvider* aProvider, IService* aNext, bool utf8)
|
||||
: YHelper(aNext),
|
||||
|
@ -90,6 +90,7 @@ ISC_STATUS API_ROUTINE isc_dsql_prepare_m(ISC_STATUS*, FB_API_HANDLE*,
|
||||
ISC_STATUS API_ROUTINE isc_dsql_set_cursor_name(ISC_STATUS*, FB_API_HANDLE*,
|
||||
const SCHAR*, USHORT);
|
||||
ISC_STATUS API_ROUTINE fb_dsql_set_timeout(ISC_STATUS*, FB_API_HANDLE*, ULONG timeout);
|
||||
//ISC_STATUS API_ROUTINE fb_get_statement_interface(ISC_STATUS*, FB_API_HANDLE*, void**);
|
||||
ISC_STATUS API_ROUTINE isc_dsql_sql_info(ISC_STATUS*, FB_API_HANDLE*, SSHORT,
|
||||
const SCHAR*, SSHORT, SCHAR*);
|
||||
//ISC_STATUS API_ROUTINE isc_prepare_transaction2(ISC_STATUS*, FB_API_HANDLE*, USHORT,
|
||||
|
Loading…
Reference in New Issue
Block a user