8
0
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:
Alexander Peshkov 2017-10-23 17:10:49 +03:00 committed by GitHub
parent e8f65cb09d
commit f53c23c17a
53 changed files with 5958 additions and 118 deletions

View File

@ -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

View File

@ -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" />

View File

@ -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>

View File

@ -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" />

View File

@ -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">

View File

@ -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" />

View File

@ -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>

View File

@ -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" />

View File

@ -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">

View File

@ -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" />

View File

@ -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>

View File

@ -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" />

View File

@ -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">

View File

@ -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-&gt;close(&amp;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 statements 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 its 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-&gt;getXpbBuilder(&amp;status, IXpbBuilder::BATCH, NULL, 0);</i></font></p>
<p style="margin-bottom: 0cm"><font size="4" style="font-size: 14pt"><i>pb-&gt;insertInt(&amp;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-&gt;createBatch(&amp;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-&gt;getBufferLength(&amp;status),
pb-&gt;getBuffer(&amp;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-&gt;getMetadata(&amp;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-&gt;getMessageLength(&amp;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-&gt;add(&amp;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-&gt;add(&amp;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-&gt;execute(&amp;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-&gt;getSize(&amp;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 &lt; total; ++p) printf(“Msg %u state %d\n”,
p, cs-&gt;getState(&amp;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 dont forget to dispose it:</span></font></p>
<p><font size="4" style="font-size: 14pt"><i><span style="background: #ffffff">cs-&gt;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-&gt;cancel(&amp;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-&gt;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-&gt;getAlignedLength(&amp;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 &lt;
N; ++n) fillNextMessage(&amp;data[aligned * n], meta);</i></font></p>
<p><font size="4" style="font-size: 14pt"><i>batch-&gt;add(&amp;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-&gt;insertInt(&amp;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 its used will be generated by firebird engine
thats 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(&amp;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-&gt;id =
++idCounter;</i></font></p>
<p><font size="4" style="font-size: 14pt"><i>project-&gt;name.set(currentName);</i></font></p>
<p><font size="4" style="font-size: 14pt"><i>batch-&gt;addBlob(&amp;status,
descriptionSize, descriptionText, &amp;project-&gt;desc);</i></font></p>
<p><font size="4" style="font-size: 14pt"><i>batch-&gt;add(&amp;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-&gt;addBlob(&amp;status,
descriptionSize, descriptionText, &amp;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-&gt;appendBlobData(&amp;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 its size is controlled by <a href="#Batch_PB">BUFFER_BYTES_SIZE</a>
option when creating batch interface but cant 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-&gt;setDefaultBpb(&amp;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-&gt;getBlobAlignment(&amp;status);</i></font></p>
<p style="font-variant: normal; font-style: normal"><font size="4" style="font-size: 14pt">Its
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, its format
depends upon blob type stream or segmented. In case of stream
blob its 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 its 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">its
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-&gt;registerBlob(&amp;status,
&amp;realId, &amp;msg-&gt;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-&gt;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, its 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 batchs 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 &gt;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>

View 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;
}

View File

@ -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*/, &param->offset, &param->nullInd);
&dtype, NULL /*length*/, &param->offset, &param->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;
}

View File

@ -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>

View 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;
}
};
}

View File

@ -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);

View File

@ -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];
};

View File

@ -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);
}

View File

@ -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

View 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

View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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:
{

View File

@ -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

View File

@ -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();

View File

@ -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);

View File

@ -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>

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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*);

View File

@ -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 */

View File

@ -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)
{
/**************************************

View File

@ -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

View File

@ -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)
{

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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()

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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),

View File

@ -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,