diff --git a/IscDbc/IscCallableStatement.cpp b/IscDbc/IscCallableStatement.cpp index 9ac74f9..aaf9193 100644 --- a/IscDbc/IscCallableStatement.cpp +++ b/IscDbc/IscCallableStatement.cpp @@ -105,9 +105,11 @@ bool IscCallableStatement::execute() ThrowStatusWrapper status( connection->GDS->_status ); try { - Sqlda::ExecBuilder execBuilder(inputSqlda); + inputSqlda.checkAndRebuild(); + auto* _imeta = inputSqlda.useExecBufferMeta ? inputSqlda.execMeta : inputSqlda.meta; + auto& _ibuf = inputSqlda.useExecBufferMeta ? inputSqlda.execBuffer : inputSqlda.buffer; - statementHandle->execute( &status, transHandle, execBuilder.getMeta(), execBuilder.getBuffer(), + statementHandle->execute( &status, transHandle, _imeta, _ibuf.data(), outputSqlda.meta, outputSqlda.buffer.data() ); } catch( const FbException& error ) diff --git a/IscDbc/IscStatement.cpp b/IscDbc/IscStatement.cpp index 08f16be..5c93962 100644 --- a/IscDbc/IscStatement.cpp +++ b/IscDbc/IscStatement.cpp @@ -656,16 +656,18 @@ bool IscStatement::execute() // Make sure there is a transaction ITransaction* transHandle = startTransaction(); - Sqlda::ExecBuilder execBuilder(inputSqlda); + inputSqlda.checkAndRebuild(); + auto* _imeta = inputSqlda.useExecBufferMeta ? inputSqlda.execMeta : inputSqlda.meta; + auto& _ibuf = inputSqlda.useExecBufferMeta ? inputSqlda.execBuffer : inputSqlda.buffer; if( openCursor == false ) { - statementHandle->execute( &status, transHandle, execBuilder.getMeta(), execBuilder.getBuffer(), NULL, NULL); + statementHandle->execute( &status, transHandle, _imeta, _ibuf.data(), NULL, NULL); } else { fbResultSet = statementHandle->openCursor( &status, transHandle, - execBuilder.getMeta(), execBuilder.getBuffer(), + _imeta, _ibuf.data(), outputSqlda.meta, 0 ); } } @@ -737,10 +739,12 @@ bool IscStatement::executeProcedure() // Make sure there is a transaction ITransaction* transHandle = startTransaction(); - Sqlda::ExecBuilder execBuilder(inputSqlda); + inputSqlda.checkAndRebuild(); + auto* _imeta = inputSqlda.useExecBufferMeta ? inputSqlda.execMeta : inputSqlda.meta; + auto& _ibuf = inputSqlda.useExecBufferMeta ? inputSqlda.execBuffer : inputSqlda.buffer; statementHandle->execute( &status, transHandle, - execBuilder.getMeta(), execBuilder.getBuffer(), + _imeta, _ibuf.data(), outputSqlda.meta, outputSqlda.buffer.data() ); } catch( const FbException& error ) diff --git a/IscDbc/Sqlda.cpp b/IscDbc/Sqlda.cpp index 020f129..ce83588 100644 --- a/IscDbc/Sqlda.cpp +++ b/IscDbc/Sqlda.cpp @@ -313,7 +313,7 @@ class CDataStaticCursor // Construction/Destruction Sqlda ////////////////////////////////////////////////////////////////////// -Sqlda::Sqlda( IscConnection* conn ) : connection{conn}, buffer{} +Sqlda::Sqlda(IscConnection* conn) : connection{ conn }, buffer{}, execBuffer{}, useExecBufferMeta{ false } { init(); } @@ -325,7 +325,7 @@ Sqlda::~Sqlda() void Sqlda::init() { - meta = nullptr; + execMeta = meta = nullptr; sqlvar.clear(); dataStaticCursor = nullptr; columnsCount = 0; @@ -347,11 +347,17 @@ void Sqlda::deleteSqlda() { sqlvar.clear(); buffer.clear(); + execBuffer.clear(); if( meta ) { meta->release(); meta = nullptr; } + if ( execMeta ) { + execMeta->release(); + execMeta = nullptr; + } + useExecBufferMeta = false; } void Sqlda::allocBuffer ( IscStatement *stmt, IMessageMetadata* msgMetadata ) @@ -362,6 +368,14 @@ void Sqlda::allocBuffer ( IscStatement *stmt, IMessageMetadata* msgMetadata ) if( meta ) meta->release(); meta = msgMetadata; + if (execMeta) + { + execMeta->release(); + execMeta = nullptr; + } + execBuffer.clear(); + useExecBufferMeta = false; + ThrowStatusWrapper status( connection->GDS->_status ); try { @@ -377,92 +391,109 @@ void Sqlda::allocBuffer ( IscStatement *stmt, IMessageMetadata* msgMetadata ) mapSqlAttributes( stmt ); } -Sqlda::ExecBuilder::ExecBuilder(Sqlda& sqlda) : _sqlda{sqlda}, _localMeta { nullptr }, _localBuffer{} +/* +* This method is used to build meta & buffer just before the execution, taking into account sqlvar change during bind +*/ +bool Sqlda::checkAndRebuild() { - bool overrideFlag = sqlda.isExternalOverriden(); + const bool overrideFlag = isExternalOverriden(); - _meta = overrideFlag ? &_localMeta : &_sqlda.meta; - _buffer = overrideFlag ? &_localBuffer : &_sqlda.buffer; - - IMetadataBuilder* metaBuilder = nullptr; - ThrowStatusWrapper status(_sqlda.connection->GDS->_status); - - auto build_new_meta = [&]()->IMessageMetadata* + if (overrideFlag) { - if (!_sqlda.meta) - throw SQLEXCEPTION(RUNTIME_ERROR, "Sqlda::ExecBuilder(): sqlda.meta==null before execute!"); - - metaBuilder = _sqlda.meta->getBuilder(&status); + IMetadataBuilder* metaBuilder = nullptr; + ThrowStatusWrapper status(connection->GDS->_status); - for (auto& var : _sqlda.sqlvar) + auto build_new_meta = [&]()->IMessageMetadata* { - const auto i = var.index - 1; - metaBuilder->setType(&status, i, var.sqltype | (short)(var.isNullable ? 1 : 0)); + if (!meta) + throw SQLEXCEPTION(RUNTIME_ERROR, "Sqlda::ExecBuilder(): sqlda.meta==null before execute!"); - //ooapi provides charset in separated field, so we should set subtype=0 for text types - const auto subtype = (var.sqltype == SQL_TEXT || var.sqltype == SQL_VARYING) ? 0 : var.sqlsubtype; - metaBuilder->setSubType(&status, i, subtype); + metaBuilder = meta->getBuilder(&status); - metaBuilder->setCharSet(&status, i, var.sqlcharset); - metaBuilder->setScale(&status, i, var.sqlscale); - metaBuilder->setLength(&status, i, var.sqllen); - } + for (const auto& var : sqlvar) + { + const auto i = var.index - 1; + metaBuilder->setType(&status, i, var.sqltype | (short)(var.isNullable ? 1 : 0)); - auto * res = metaBuilder->getMetadata(&status); - metaBuilder->release(); - metaBuilder = nullptr; - return res; - }; + //ooapi provides charset in separated field, so we should set subtype=0 for text types + const auto subtype = (var.sqltype == SQL_TEXT || var.sqltype == SQL_VARYING) ? 0 : var.sqlsubtype; + metaBuilder->setSubType(&status, i, subtype); - try - { - if (overrideFlag) + metaBuilder->setCharSet(&status, i, var.sqlcharset); + metaBuilder->setScale(&status, i, var.sqlscale); + metaBuilder->setLength(&status, i, var.sqllen); + } + + auto* res = metaBuilder->getMetadata(&status); + metaBuilder->release(); + metaBuilder = nullptr; + return res; + }; + + try { - //build the new metadata - _localMeta = build_new_meta(); + //printf("Rebuilding metadata due to sqlvar changes...\n"); - if ((*_meta)->getCount(&status) != _sqlda.columnsCount) - throw SQLEXCEPTION(RUNTIME_ERROR, "Sqlda::ExecBuilder(): incorrect columns count"); + if (execMeta) + { + execMeta->release(); + } - //resize the new buffer to required length - _buffer->resize((*_meta)->getMessageLength(&status)); - } + execMeta = build_new_meta(); - //fill exec buffer with data - regardless of whether it was rebuilt or not - for (auto& var : _sqlda.sqlvar) - { - const auto i = var.index - 1; - const auto offs = (*_meta)->getOffset(&status, i); - const auto offsNull = (*_meta)->getNullOffset(&status, i); + if (execMeta->getCount(&status) != columnsCount) + { + throw SQLEXCEPTION(RUNTIME_ERROR, "Sqlda::checkAndRebuild(): incorrect columns count"); + } - if ( (*(short*)(_buffer->data() + offsNull) = *var.sqlind) != sqlNull ) + lengthBufferRows = execMeta->getMessageLength(&status); + execBuffer.clear(); + execBuffer.resize(lengthBufferRows); + + for (auto& var : sqlvar) { - memcpy(_buffer->data() + offs, var.sqldata, (*_meta)->getLength(&status, i)); + const auto i = var.index - 1; + //save effective sqldata&sqlind, pointing to the new execBuffer + var.eff_sqldata = &execBuffer.at(execMeta->getOffset(&status, i)); + var.eff_sqlind = (short*)&execBuffer.at(execMeta->getNullOffset(&status, i)); + //update last sqlvars to avoid buffers rebuilt next time + var.lastSqlProperties = var; } + + useExecBufferMeta = true; + } + catch (const FbException& error) + { + if (metaBuilder) metaBuilder->release(); + THROW_ISC_EXCEPTION(connection, error.getStatus()); + } + catch (...) + { + if (metaBuilder) metaBuilder->release(); + throw; } } - catch (const FbException& error) - { - if (metaBuilder) metaBuilder->release(); - THROW_ISC_EXCEPTION(_sqlda.connection, error.getStatus()); - } - catch (...) + else { - if (metaBuilder) metaBuilder->release(); - throw; + //printf("Metadata is actual\n"); } -} -Sqlda::ExecBuilder::~ExecBuilder() -{ - //save new meta - if (_localMeta) { - for (auto& var : _sqlda.sqlvar) var.orgSqlProperties = *static_cast(&var); - if(_sqlda.meta) _sqlda.meta->release(); - _sqlda.meta = _localMeta; - _sqlda.buffer = _localBuffer; + for (const auto& var : sqlvar) + { + if (var.eff_sqlind != var.sqlind) + { + memcpy(var.eff_sqlind, var.sqlind, sizeof(short)); + } + + if (var.eff_sqldata != var.sqldata && *var.eff_sqlind != sqlNull) + { + memcpy(var.eff_sqldata, var.sqldata, var.sqllen); + } + } } + + return overrideFlag; } void Sqlda::mapSqlAttributes( IscStatement *stmt ) diff --git a/IscDbc/Sqlda.h b/IscDbc/Sqlda.h index 512b3b4..9947195 100644 --- a/IscDbc/Sqlda.h +++ b/IscDbc/Sqlda.h @@ -43,7 +43,7 @@ struct SqlProperties short sqlscale; /* scale factor */ short sqlsubtype; /* datatype subtype - BLOBs & Text types only */ unsigned sqlcharset; - short sqllen; /* length of data area */ + unsigned sqllen; /* length of data area */ inline SqlProperties() : offsetData{ 0 }, @@ -78,6 +78,8 @@ class CAttrSqlVar : public SqlProperties sqldata{ nullptr }, sqlind{ nullptr }, + eff_sqldata{ nullptr }, eff_sqlind{ nullptr }, + array{ nullptr }, index{ 0 }, replaceForParamArray{ false } @@ -117,7 +119,8 @@ class CAttrSqlVar : public SqlProperties // ++index; //to make it 1-based) - orgSqlProperties = *static_cast(this); // save original props + orgSqlProperties = *this; // save original props + lastSqlProperties = *this; } public: @@ -128,59 +131,62 @@ class CAttrSqlVar : public SqlProperties } inline void assignBuffer(buffer_t& buffer ) { - sqldata = &buffer.at( offsetData ); - sqlind = (short*)&buffer.at( offsetNull ); + eff_sqldata = sqldata = &buffer.at( offsetData ); + eff_sqlind = sqlind = (short*)&buffer.at( offsetNull ); } inline bool propertiesOverriden() { - return !( *this == orgSqlProperties ); + return !( *this == lastSqlProperties); } char* sqldata; short* sqlind; + char* eff_sqldata; + short* eff_sqlind; CAttrArray *array; unsigned index; // 1-based parameter index bool replaceForParamArray; SqlProperties orgSqlProperties; // original properties after prepare + SqlProperties lastSqlProperties; // last used properties after prev exec }; class Value; class IscConnection; class CDataStaticCursor; -class Sqlda +class Sqlda { public: using buffer_t = std::vector; protected: - buffer_t& initStaticCursor(IscStatement *stmt); + buffer_t& initStaticCursor(IscStatement* stmt); buffer_t& addRowSqldaInBufferStaticCursor(); void restoreOrgAdressFieldsStaticCursor(); public: - const char* getOwnerName (int index); - int findColumn (const char *columnName); - void setBlob (CAttrSqlVar* var, Value * value, IscStatement *stmt); - void setArray (CAttrSqlVar* var, Value *value, IscStatement *stmt); - void setValue (int slot, Value *value, IscStatement *stmt); - const char* getTableName (int index); - int getSqlType (CAttrSqlVar *var, int &realSqlType); - const char* getSqlTypeName (CAttrSqlVar *var); - bool isNullable (int index); - int getScale (int index); - int getPrecision (int index); + const char* getOwnerName(int index); + int findColumn(const char* columnName); + void setBlob(CAttrSqlVar* var, Value* value, IscStatement* stmt); + void setArray(CAttrSqlVar* var, Value* value, IscStatement* stmt); + void setValue(int slot, Value* value, IscStatement* stmt); + const char* getTableName(int index); + int getSqlType(CAttrSqlVar* var, int& realSqlType); + const char* getSqlTypeName(CAttrSqlVar* var); + bool isNullable(int index); + int getScale(int index); + int getPrecision(int index); int getNumPrecRadix(int index); - const char* getColumnLabel (int index); - const char* getColumnName (int index); - int getColumnDisplaySize (int index); + const char* getColumnLabel(int index); + const char* getColumnName(int index); + int getColumnDisplaySize(int index); short getSubType(int index); - int getColumnType (int index, int &realSqlType); - const char * getColumnTypeName (int index); + int getColumnType(int index, int& realSqlType); + const char* getColumnTypeName(int index); void print(); bool setCurrentRowInBufferStaticCursor(int nRow); - void getAdressFieldFromCurrentRowInBufferStaticCursor(int column, char *& sqldata, short *& sqlind); + void getAdressFieldFromCurrentRowInBufferStaticCursor(int column, char*& sqldata, short*& sqlind); void copyNextSqldaInBufferStaticCursor(); void copyNextSqldaFromBufferStaticCursor(); void saveCurrentSqldaToBuffer(); @@ -189,13 +195,13 @@ class Sqlda int getColumnCount(); void init(); void remove(); - void allocBuffer(IscStatement *stmt, Firebird::IMessageMetadata* msgMetadata); - void mapSqlAttributes(IscStatement *stmt); + void allocBuffer(IscStatement* stmt, Firebird::IMessageMetadata* msgMetadata); + void mapSqlAttributes(IscStatement* stmt); void deleteSqlda(); void clearSqlda(); - CAttrSqlVar* Var(int index) { return &sqlvar.at( index - 1 ); } + CAttrSqlVar* Var(int index) { return &sqlvar.at(index - 1); } - Sqlda( IscConnection* conn ); + Sqlda(IscConnection* conn); ~Sqlda(); int isBlobOrArray(int index); @@ -203,25 +209,25 @@ class Sqlda void setNull(int index); void setNotNull(int index); - bool getBoolean (int index); - short getShort (int index); - int getInt (int index); - char * getText (int index, int &len); - char * getVarying (int index, int &len); + bool getBoolean(int index); + short getShort(int index); + int getInt(int index); + char* getText(int index, int& len); + char* getVarying(int index, int& len); - void updateBoolean (int index, bool value); - void updateShort (int index, short value); - void updateInt (int index, int value); - void updateText (int index, const char* value); - void updateVarying (int index, const char* dst); + void updateBoolean(int index, bool value); + void updateShort(int index, short value); + void updateInt(int index, int value); + void updateText(int index, const char* value); + void updateVarying(int index, const char* dst); - CDataStaticCursor * dataStaticCursor; + CDataStaticCursor* dataStaticCursor; int lengthBufferRows; IscConnection* connection; - Firebird::IMessageMetadata* meta; - - buffer_t buffer; + Firebird::IMessageMetadata* meta, * execMeta; + buffer_t buffer, execBuffer; + bool useExecBufferMeta; //flag, if true - use execMeta/execBuffer instead of meta/buffer using orgsqlvar_t = std::vector; orgsqlvar_t sqlvar; @@ -231,34 +237,11 @@ class Sqlda friend class IscResultSet; inline bool isExternalOverriden() { - for( auto & var : sqlvar ) if (var.propertiesOverriden()) return true; + for (auto& var : sqlvar) if (var.propertiesOverriden()) return true; return false; } - /* - * This class is used to build meta & buffer just before the execution, taking into account sqlvar change during bind - * It restores the original sqlvars in dtor - */ - class ExecBuilder - { - public: - ExecBuilder(Sqlda& sqlda); - ~ExecBuilder(); - - inline Firebird::IMessageMetadata* getMeta() { - return *_meta; - } - inline char* getBuffer() { - return _buffer->data(); - } - private: - Sqlda& _sqlda; - Firebird::IMessageMetadata** _meta; - buffer_t* _buffer; - - Firebird::IMessageMetadata* _localMeta; - buffer_t _localBuffer; - }; + bool checkAndRebuild(); }; }; // end namespace IscDbcLibrary