| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- /*
- *
- * Sample replication plugin
- *
- */
- #include <algorithm>
- #include <atomic>
- #define __USE_MINGW_ANSI_STDIO 1
- #include <stdio.h>
- #include <time.h>
- #include "firebird/Interface.h"
- using namespace Firebird;
- #define WriteLog(file, ...) fprintf(file, __VA_ARGS__), fflush(file)
- class ReplPlugin : public IReplicatedSessionImpl<ReplPlugin, CheckStatusWrapper>
- {
- public:
- ReplPlugin(IPluginConfig* config);
- virtual ~ReplPlugin();
- // IReferenceCounted implementation
- void addRef() override;
- int release() override;
- // IPluginBase implementation
- void setOwner(IReferenceCounted* r) override;
- IReferenceCounted* getOwner() override;
- // IReplicatedSession implementation
- IStatus* getStatus() override;
- void setAttachment(IAttachment* attachment) override;
- IReplicatedTransaction* startTransaction(ITransaction* transaction, ISC_INT64 number) override;
- FB_BOOLEAN cleanupTransaction(ISC_INT64 number) override;
- FB_BOOLEAN setSequence(const char* name, ISC_INT64 value) override;
- private:
- friend class ReplTransaction;
- IAttachment* att = nullptr;
- FILE* log = nullptr;
- IStatus* status = nullptr;
- std::atomic_int refCounter;
- IReferenceCounted* owner;
- void dumpInfo(const unsigned char* buffer, size_t length);
- };
- class ReplTransaction: public IReplicatedTransactionImpl<ReplTransaction, CheckStatusWrapper>
- {
- public:
- ReplTransaction(ReplPlugin* session, ITransaction* transaction, ISC_INT64 number);
- ~ReplTransaction();
- // IDisposable implementation
- void dispose() override;
- // IReplicatedTransaction implementation
- FB_BOOLEAN prepare() override;
- FB_BOOLEAN commit() override;
- FB_BOOLEAN rollback() override;
- FB_BOOLEAN startSavepoint() override;
- FB_BOOLEAN releaseSavepoint() override;
- FB_BOOLEAN rollbackSavepoint() override;
- FB_BOOLEAN insertRecord(const char* name, IReplicatedRecord* record) override;
- FB_BOOLEAN updateRecord(const char* name, IReplicatedRecord* orgRecord, IReplicatedRecord* newRecord) override;
- FB_BOOLEAN deleteRecord(const char* name, IReplicatedRecord* record) override;
- FB_BOOLEAN executeSql(const char* sql) override;
- FB_BOOLEAN executeSqlIntl(unsigned charset, const char* sql) override;
- private:
- ReplPlugin* parent;
- ITransaction* trans;
- bool dumpData(IReplicatedRecord* record);
- };
- IMaster* master = nullptr;
- class PluginModule : public IPluginModuleImpl<PluginModule, CheckStatusWrapper>
- {
- public:
- void doClean() override {}
- void threadDetach() override {}
- } module;
- class Factory : public IPluginFactoryImpl<Factory, CheckStatusWrapper>
- {
- public:
- IPluginBase* createPlugin(CheckStatusWrapper* status, IPluginConfig* factoryParameter)
- {
- IPluginBase* p = new ReplPlugin(factoryParameter);
- p->addRef();
- return p;
- }
- } factory;
- extern "C"
- {
- #if defined(__WIN32__)
- void __declspec(dllexport) FB_PLUGIN_ENTRY_POINT(IMaster* m);
- #else
- void FB_PLUGIN_ENTRY_POINT(IMaster* m)
- __attribute__((visibility("default")));
- #endif // __WIN32__
- void FB_PLUGIN_ENTRY_POINT(IMaster* m)
- {
- master = m;
- IPluginManager* pm = m->getPluginManager();
- pm->registerModule(&module);
- pm->registerPluginFactory(IPluginManager::TYPE_REPLICATOR, "fbSampleReplicator", &factory);
- }
- }
- static std::atomic_int logCounter;
- static const ISC_STATUS err[] = { isc_arg_gds, isc_random, isc_arg_string, (ISC_STATUS)"Intolerable integer value", isc_arg_end };
- static const ISC_STATUS wrn[] = { isc_arg_gds, isc_random, isc_arg_string, (ISC_STATUS)"Just a warning", isc_arg_end };
- ReplPlugin::ReplPlugin(IPluginConfig* conf)
- {
- char fn[100];
- sprintf(fn, "session_%08x_%d.log", (unsigned)time(nullptr), logCounter++);
- log = fopen(fn, "w");
- WriteLog(log, "%p\tReplicatedSession constructed\n", this);
- status = master->getStatus();
- }
- ReplPlugin::~ReplPlugin()
- {
- if (log != nullptr)
- {
- WriteLog(log, "%p\tReplicatedSession destructed\n", this);
- fclose(log);
- }
- if (att != nullptr)
- att->release();
- if (status != nullptr)
- status->dispose();
- }
- void ReplPlugin::addRef()
- {
- WriteLog(log, "%p\taddRef() to %d\n", this, ++refCounter);
- }
- int ReplPlugin::release()
- {
- WriteLog(log, "%p\trelease at %d\n", this, refCounter.load());
- if (--refCounter == 0)
- {
- delete this;
- return 0;
- }
- return 1;
- }
- void ReplPlugin::setOwner(IReferenceCounted* r)
- {
- WriteLog(log, "%p\tsetOwner(%p)\n", this, r);
- owner = r;
- }
- IReferenceCounted* ReplPlugin::getOwner()
- {
- WriteLog(log, "%p\tgetOwner()\n", this);
- return owner;
- }
- IStatus* ReplPlugin::getStatus()
- {
- WriteLog(log, "%p\tgetStatus()\n", this);
- return status;
- }
- void ReplPlugin::dumpInfo(const unsigned char* buffer, size_t length)
- {
- const unsigned char* p = buffer;
- while (p < buffer + length)
- {
- unsigned char item = *p++;
- // Handle terminating items fist
- if (item == isc_info_end)
- {
- return;
- }
- if (item == isc_info_truncated)
- {
- WriteLog(log, "\t\tDatabase info truncated\n");
- return;
- }
- // Now data items
- const unsigned len = p[0] | p[1] << 8;
- p += 2;
- switch (item)
- {
- case fb_info_db_guid:
- {
- WriteLog(log, "\t\tDatabase GUID = %.*s\n", len, p);
- break;
- }
- case isc_info_error:
- {
- unsigned err = p[1];
- for (unsigned i = 1; i < std::min(len, 4U); i++)
- {
- err |= p[i + 1] << (8 * i);
- }
- WriteLog(log, "\t\tDatabase info error %u for item %d\n", err, p[0]);
- return;
- }
- default:
- WriteLog(log, "\t\tUnexpected info item %d\n", item);
- break;
- }
- p += len;
- }
- WriteLog(log, "\t\tSuspicious exit from info parse loop\n");
- }
- void ReplPlugin::setAttachment(IAttachment* attachment)
- {
- WriteLog(log, "%p\tAssigned attachment %p\n", this, attachment);
- att = attachment;
- att->addRef();
- CheckStatusWrapper ExtStatus(status);
- const unsigned char items[] = { fb_info_db_guid };
- unsigned char response[80];
- att->getInfo(&ExtStatus, sizeof(items), items, sizeof(response), response);
- if (status->getState() == 0)
- {
- dumpInfo(response, sizeof(response));
- }
- }
- IReplicatedTransaction* ReplPlugin::startTransaction(ITransaction* transaction, ISC_INT64 number)
- {
- WriteLog(log, "%p\tstartTransaction(%p, %lld)\n", this, transaction, number);
- return new ReplTransaction(this, transaction, number);
- }
- FB_BOOLEAN ReplPlugin::cleanupTransaction(ISC_INT64 number)
- {
- WriteLog(log, "%p\tcleanupTransaction(%lld)\n", this, number);
- return FB_TRUE;
- }
- FB_BOOLEAN ReplPlugin::setSequence(const char* name, ISC_INT64 value)
- {
- WriteLog(log, "%p\tsetSequence(%s, %lld)\n", this, name, value);
- return FB_TRUE;
- }
- ReplTransaction::ReplTransaction(ReplPlugin* session, ITransaction* transaction, ISC_INT64 number):
- parent(session), trans(transaction)
- {
- parent->addRef(); // Lock parent from disappearing
- trans->addRef();
- WriteLog(parent->log, "%p\tTransaction started\n", this);
- }
- ReplTransaction::~ReplTransaction()
- {
- WriteLog(parent->log, "%p\tTransaction destructed\n", this);
- trans->release();
- parent->release();
- }
- void ReplTransaction::dispose()
- {
- WriteLog(parent->log, "%p\tdispose()\n", this);
- delete this;
- }
- FB_BOOLEAN ReplTransaction::prepare()
- {
- WriteLog(parent->log, "%p\tprepare()\n", this);
- return FB_TRUE;
- }
- FB_BOOLEAN ReplTransaction::commit()
- {
- WriteLog(parent->log, "%p\tcommit()\n", this);
- return FB_TRUE;
- }
- FB_BOOLEAN ReplTransaction::rollback()
- {
- WriteLog(parent->log, "%p\trollback()\n", this);
- parent->status->setWarnings(wrn);
- return FB_FALSE;
- }
- FB_BOOLEAN ReplTransaction::startSavepoint()
- {
- WriteLog(parent->log, "%p\tstartSavepoint()\n", this);
- return FB_TRUE;
- }
- FB_BOOLEAN ReplTransaction::releaseSavepoint()
- {
- WriteLog(parent->log, "%p\treleaseSavepoint()\n", this);
- return FB_TRUE;
- }
- FB_BOOLEAN ReplTransaction::rollbackSavepoint()
- {
- WriteLog(parent->log, "%p\trollbackSavepoint()\n", this);
- return FB_TRUE;
- }
- bool ReplTransaction::dumpData(IReplicatedRecord* record)
- {
- for (unsigned i = 0; i < record->getCount(); i++)
- {
- IReplicatedField* field = record->getField(i);
- if (field == nullptr)
- {
- WriteLog(parent->log, "\t\tNO FIELD %u FOUND\n", i);
- continue;
- }
- unsigned fieldType = field->getType();
- WriteLog(parent->log, "\tfield %u (%s), type %u:\n", i, field->getName(), fieldType);
- const void* fieldData = field->getData();
- if (fieldData == nullptr)
- {
- WriteLog(parent->log, "\t\tNULL\n");
- }
- else
- {
- switch (fieldType)
- {
- case SQL_TEXT:
- {
- unsigned length = field->getLength();
- unsigned charSet = field->getCharSet();
- if (charSet == 1) // OCTETS
- {
- WriteLog(parent->log, "\t\tBINARY data length %u: ", length);
- const unsigned char* data = reinterpret_cast<const unsigned char*>(fieldData);
- for (unsigned j = 0; j < length; j++)
- WriteLog(parent->log, "%02u", *data++);
- WriteLog(parent->log, "\n");
- }
- else
- WriteLog(parent->log, "\t\tTEXT with charset %u, length %u: \"%.*s\"\n", charSet, length, length, reinterpret_cast<const char*>(fieldData));
- break;
- }
- case SQL_VARYING:
- {
- unsigned charSet = field->getCharSet();
- const paramvary* data = static_cast<const paramvary*>(fieldData);
- if (charSet == 1) // OCTETS
- {
- fprintf(parent->log, "\t\tVARBINARY data length %u: ", data->vary_length);
- for (unsigned j = 0; j < data->vary_length; j++)
- fprintf(parent->log, "%02u", data->vary_string[j]);
- WriteLog(parent->log, "\n");
- }
- else
- WriteLog(parent->log, "\t\tVARCHAR with charset %u, length %u: \"%.*s\"\n", charSet, data->vary_length, data->vary_length, data->vary_string);
- break;
- }
- case SQL_SHORT:
- {
- WriteLog(parent->log, "\t\tSMALLINT with scale %u: %d\n", field->getScale(), *reinterpret_cast<const int16_t*>(fieldData));
- break;
- }
- case SQL_LONG:
- {
- int value = *reinterpret_cast<const int32_t*>(fieldData);
- WriteLog(parent->log, "\t\tINTEGER with scale %u: %d\n", field->getScale(), value);
- if (value == 666)
- throw value;
- break;
- }
- case SQL_ARRAY:
- {
- WriteLog(parent->log, "\t\tARRAY\n");
- break;
- }
- case SQL_BLOB:
- {
- unsigned subType = field->getSubType();
- WriteLog(parent->log, "\t\tBLOB subtype %u:\n", subType);
- unsigned charSet = 1;
- if (subType == 1)
- {
- charSet = field->getCharSet();
- }
- CheckStatusWrapper ExtStatus(parent->status);
- ISC_QUAD blobId = *reinterpret_cast<const ISC_QUAD*>(fieldData);
- IBlob* blob(parent->att->openBlob(&ExtStatus, trans, &blobId, 0, nullptr));
- if (ExtStatus.getState() & IStatus::STATE_ERRORS)
- return false;
- char buffer[USHRT_MAX];
- do
- {
- unsigned length;
- int ret = blob->getSegment(&ExtStatus, sizeof(buffer), buffer, &length);
- if (ret == IStatus::RESULT_ERROR)
- {
- blob->release();
- return false;
- }
- if (ret == IStatus::RESULT_NO_DATA)
- break;
- if (length > 0)
- {
- fprintf(parent->log, "\t\t - segment of length %u: ", length);
- if (subType != 1 || charSet == 1)
- {
- for (unsigned j = 0; j < length; j++)
- fprintf(parent->log, "%02u", buffer[j]);
- WriteLog(parent->log, "\n");
- }
- else
- WriteLog(parent->log, "(charset %u) \"%.*s\"\n", charSet, length, buffer);
- }
- } while (true);
- blob->close(&ExtStatus);
- blob->release();
- if (ExtStatus.getState() & IStatus::STATE_ERRORS)
- return false;
- break;
- }
- case SQL_FLOAT:
- {
- WriteLog(parent->log, "\t\tFLOAT: %f\n", *reinterpret_cast<const float*>(fieldData));
- break;
- }
- case SQL_DOUBLE:
- {
- WriteLog(parent->log, "\t\tDOUBLE: %f\n", *reinterpret_cast<const double*>(fieldData));
- break;
- }
- case SQL_TYPE_DATE:
- {
- ISC_DATE value = *reinterpret_cast<const ISC_DATE*>(fieldData);
- IUtil* utl = master->getUtilInterface();
- unsigned year, month, day;
- utl->decodeDate(value, &year, &month, &day);
- WriteLog(parent->log, "\t\tDATE: %04u-%02u-%02u\n", year, month, day);
- break;
- }
- case SQL_TYPE_TIME:
- {
- ISC_TIME value = *reinterpret_cast<const ISC_TIME*>(fieldData);
- IUtil* utl = master->getUtilInterface();
- unsigned hours, minutes, seconds, fractions;
- utl->decodeTime(value, &hours, &minutes, &seconds, &fractions);
- WriteLog(parent->log, "\t\tTIME: %02u:%02u:%02u.%04u\n", hours, minutes, seconds, fractions);
- break;
- }
- case SQL_TIMESTAMP:
- {
- ISC_TIMESTAMP value = *reinterpret_cast<const ISC_TIMESTAMP*>(fieldData);
- IUtil* utl = master->getUtilInterface();
- unsigned year, month, day, hours, minutes, seconds, fractions;
- utl->decodeDate(value.timestamp_date, &year, &month, &day);
- utl->decodeTime(value.timestamp_time, &hours, &minutes, &seconds, &fractions);
- WriteLog(parent->log, "\t\tTIMESTAMP: %04u-%02u-%02u %02u:%02u:%02u.%04u\n", year, month, day, hours, minutes, seconds, fractions);
- break;
- }
- case SQL_INT64:
- {
- WriteLog(parent->log, "\t\tBIGINT with scale %u: %lld\n", field->getScale(), *reinterpret_cast<const int64_t*>(fieldData));
- break;
- }
- case SQL_BOOLEAN:
- {
- WriteLog(parent->log, "\t\tBOOLEAN: %s\n",
- *reinterpret_cast<const FB_BOOLEAN*>(fieldData) == FB_TRUE ?
- "true" : "false");
- break;
- }
- case SQL_INT128:
- {
- char buffer[50];
- unsigned scale = field->getScale();
- CheckStatusWrapper ExtStatus(parent->status);
- master->getUtilInterface()->getInt128(&ExtStatus)->toString(&ExtStatus,
- reinterpret_cast<const FB_I128*>(fieldData), scale,
- sizeof(buffer), buffer);
- WriteLog(parent->log, "\t\tINT128 with scale %u: %s\n", scale, buffer);
- break;
- }
- default:
- {
- WriteLog(parent->log, "\t\twhatever\n");
- }
- }
- }
- }
- return true;
- }
- FB_BOOLEAN ReplTransaction::insertRecord(const char* name, IReplicatedRecord* record)
- {
- WriteLog(parent->log, "%p\tInsert record into %s\n", this, name);
- try
- {
- return dumpData(record) ? FB_TRUE : FB_FALSE;
- }
- catch (const int)
- {
- parent->status->setErrors(err);
- return FB_FALSE;
- }
- }
- FB_BOOLEAN ReplTransaction::updateRecord(const char* name, IReplicatedRecord* orgRecord, IReplicatedRecord* newRecord)
- {
- WriteLog(parent->log, "%p\tUpdate %s\nOldData:\n", this, name);
- try
- {
- if (!dumpData(orgRecord))
- return FB_FALSE;
- WriteLog(parent->log, "NewData:\n");
- return dumpData(newRecord) ? FB_TRUE : FB_FALSE;
- }
- catch (const int)
- {
- parent->status->setErrors(err);
- return FB_FALSE;
- }
- }
- FB_BOOLEAN ReplTransaction::deleteRecord(const char* name, IReplicatedRecord* record)
- {
- WriteLog(parent->log, "%p\tDelete from %s\n", this, name);
- try
- {
- return dumpData(record) ? FB_TRUE : FB_FALSE;
- }
- catch (const int)
- {
- parent->status->setErrors(err);
- return FB_FALSE;
- }
- }
- FB_BOOLEAN ReplTransaction::executeSql(const char* sql)
- {
- WriteLog(parent->log, "%p\tExecuteSql(%s)\n", this, sql);
- return FB_TRUE;
- }
- FB_BOOLEAN ReplTransaction::executeSqlIntl(unsigned charset, const char* sql)
- {
- WriteLog(parent->log, "%p\tExecuteSqlIntl(%u, %s)\n", this, charset, sql);
- return FB_TRUE;
- }
|