From 8575db6d58ea47de420499c95a9115e00dfb68be Mon Sep 17 00:00:00 2001 From: randomdan Date: Sun, 8 Aug 2010 17:24:19 +0000 Subject: Tidy up hierarchy Add SqlMergeTask --- project2/console/p2consoleMain.cpp | 6 + project2/fileRows.cpp | 6 +- project2/fileRows.h | 4 +- project2/iterate.cpp | 8 ++ project2/iterate.h | 18 ++- project2/noOutputExecute.h | 1 + project2/perRowValues.h | 3 + project2/rawView.cpp | 18 +++ project2/rawView.h | 5 +- project2/rowBase.h | 14 -- project2/sourceObject.cpp | 6 + project2/sourceObject.h | 1 + project2/sqlMergeTask.cpp | 196 +++++++++++++++++++++++++++ project2/sqlMergeTask.h | 119 ++++++++++++++++ project2/sqlRows.h | 4 +- project2/tablepatch.cpp | 271 +++++++++++++++++++++++++++++++++++++ project2/tablepatch.h | 47 +++++++ project2/task.cpp | 3 + project2/view.h | 12 ++ 19 files changed, 717 insertions(+), 25 deletions(-) delete mode 100644 project2/rowBase.h create mode 100644 project2/sqlMergeTask.cpp create mode 100644 project2/sqlMergeTask.h create mode 100644 project2/tablepatch.cpp create mode 100644 project2/tablepatch.h diff --git a/project2/console/p2consoleMain.cpp b/project2/console/p2consoleMain.cpp index a8d1ffc..3bc95e7 100644 --- a/project2/console/p2consoleMain.cpp +++ b/project2/console/p2consoleMain.cpp @@ -7,16 +7,22 @@ int main(int argc, char ** argv) { ConsoleEnvironment env(argc, argv); BOOST_FOREACH(const boost::filesystem::path & file, env.todolist) { +#if DEBUG try { +#endif ConsoleApplicationEngine app(&env, file); app.process(); +#if DEBUG } catch (const std::exception & e) { std::cerr << "Kaboom!" << std::endl << std::endl << e.what() << std::endl; + throw; } catch (...) { std::cerr << "Kaboom!" << std::endl << std::endl << "Unknown exception." << std::endl; + throw; } +#endif } } diff --git a/project2/fileRows.cpp b/project2/fileRows.cpp index 855136f..955767c 100644 --- a/project2/fileRows.cpp +++ b/project2/fileRows.cpp @@ -3,8 +3,8 @@ _FileRows::_FileRows(const xmlpp::Element * p) : path(p->get_attribute_value("path")), - fieldSep(p->get_attribute_value("fieldSeps")[0]), - quoteChar(p->get_attribute_value("quoteChars")[0]), + fieldSep(p->get_attribute_value("fieldSep")[0]), + quoteChar(p->get_attribute_value("quoteChar")[0]), newline(p->get_attribute_value("newline")), encoding(p->get_attribute_value("encoding")) { @@ -83,7 +83,7 @@ _FileRows::execute() const } } if (!mkCols) { - while (curCol != columns.end()) { + while (values.size() < columns.size()) { values.push_back(ValPtr(new Glib::ustring())); curCol++; } diff --git a/project2/fileRows.h b/project2/fileRows.h index 335e723..cf1158e 100644 --- a/project2/fileRows.h +++ b/project2/fileRows.h @@ -6,10 +6,9 @@ #include #include "view.h" #include "iterate.h" -#include "rowBase.h" #include "fileStarGlibIoChannel.h" -class _FileRows : public RowBase { +class _FileRows : public PerRowValues { public: _FileRows(const xmlpp::Element * p); ~_FileRows(); @@ -19,6 +18,7 @@ class _FileRows : public RowBase { const Glib::ustring & getColumnName(unsigned int col) const; const Glib::ustring & getCurrentValue(const Glib::ustring & id) const; const Glib::ustring & getCurrentValue(unsigned int col) const; + virtual void rowReady() const = 0; typedef std::set CharSet; const Glib::ustring path; diff --git a/project2/iterate.cpp b/project2/iterate.cpp index 3fc0e48..f6d4820 100644 --- a/project2/iterate.cpp +++ b/project2/iterate.cpp @@ -29,6 +29,14 @@ _Iterate::AddLoaders(Loaders & l, NoOutputExecutes & iterates) l.insert(LoadersVT("prociterate", _LoaderBase::Make<_ProcIterate, _NoOutputExecute, unsigned int, _SourceObject, &_SourceObject::order>(&iterates))); } +void +_Iterate::AddLoaders(Loaders & l, Iterates & iterates) +{ + l.insert(LoadersVT("sqliterate", _LoaderBase::Make<_SqlIterate, _Iterate, std::string, _SourceObject, &_SourceObject::name>(&iterates))); + l.insert(LoadersVT("fileiterate", _LoaderBase::Make<_FileIterate, _Iterate, std::string, _SourceObject, &_SourceObject::name>(&iterates))); + l.insert(LoadersVT("prociterate", _LoaderBase::Make<_ProcIterate, _Iterate, std::string, _SourceObject, &_SourceObject::name>(&iterates))); +} + void _Iterate::executeChildren() const { diff --git a/project2/iterate.h b/project2/iterate.h index 53f7050..17a046c 100644 --- a/project2/iterate.h +++ b/project2/iterate.h @@ -19,13 +19,13 @@ class _Iterate : public virtual _SourceObject, public PerRowValues, public _NoOu _Iterate(const xmlpp::Element * p); virtual ~_Iterate(); - virtual void execute() const = 0; - virtual const Glib::ustring & getCurrentValue(const Glib::ustring & id) const = 0; static void AddLoaders(Loaders & l, NoOutputExecutes & vs); + static void AddLoaders(Loaders & l, Iterates & vs); + + NoOutputExecutes subNOEs; protected: void executeChildren() const; - NoOutputExecutes subNOEs; }; template @@ -33,10 +33,22 @@ class _GenericIterate : public _Iterate, public Driver { public: _GenericIterate(const xmlpp::Element * p); + virtual unsigned int columnCount() const + { + return Driver::columnCount(); + } + virtual const Glib::ustring & getCurrentValue(const unsigned int id) const + { + return Driver::getCurrentValue(id); + } virtual const Glib::ustring & getCurrentValue(const Glib::ustring & id) const { return Driver::getCurrentValue(id); } + virtual const Glib::ustring & getColumnName(const unsigned int id) const + { + return Driver::getColumnName(id); + } virtual void executeChildren() const; virtual void execute() const; virtual void rowReady() const; diff --git a/project2/noOutputExecute.h b/project2/noOutputExecute.h index 56e7b76..01abb5c 100644 --- a/project2/noOutputExecute.h +++ b/project2/noOutputExecute.h @@ -13,6 +13,7 @@ typedef std::map NoOutputExecutes; class _NoOutputExecute : public virtual _SourceObject { public: _NoOutputExecute(const xmlpp::Element * p) : _SourceObject(p) { }; + _NoOutputExecute(const std::string & n) : _SourceObject(n) { }; virtual ~_NoOutputExecute() { } virtual void execute() const = 0; }; diff --git a/project2/perRowValues.h b/project2/perRowValues.h index 5b5b9ab..4d898ff 100644 --- a/project2/perRowValues.h +++ b/project2/perRowValues.h @@ -21,7 +21,10 @@ class PerRowValues { PerRowValues(); virtual ~PerRowValues() = 0; + virtual unsigned int columnCount() const = 0; + virtual const Glib::ustring & getColumnName(unsigned int col) const = 0; virtual const Glib::ustring & getCurrentValue(const Glib::ustring & id) const = 0; + virtual const Glib::ustring & getCurrentValue(unsigned int col) const = 0; void use(const RowUser * r) const { rowUsers.insert(r); } static const RowValuesStack & Stack() { return stack; } diff --git a/project2/rawView.cpp b/project2/rawView.cpp index 3fe925a..4d41e63 100644 --- a/project2/rawView.cpp +++ b/project2/rawView.cpp @@ -12,12 +12,30 @@ _RawView::_RawView(const xmlpp::Element * p) : { } +unsigned int +_RawView::columnCount() const +{ + throw std::runtime_error("_RawView::columnCount not implemented"); +} + +const Glib::ustring & +_RawView::getCurrentValue(unsigned int id) const +{ + throw std::runtime_error("_RawView::getCurrentValue not implemented"); +} + const Glib::ustring & _RawView::getCurrentValue(const Glib::ustring & id) const { throw std::runtime_error("_RawView::getCurrentValue not implemented"); } +const Glib::ustring & +_RawView::getColumnName(unsigned int id) const +{ + throw std::runtime_error("_RawView::getColumnName not implemented"); +} + void _RawView::execute(xmlpp::Element * par) const { BOOST_FOREACH(xmlpp::Node * node, copyRoot->get_children()) { diff --git a/project2/rawView.h b/project2/rawView.h index 8909927..33d0d1d 100644 --- a/project2/rawView.h +++ b/project2/rawView.h @@ -12,7 +12,10 @@ class _RawView : public _View { public: _RawView(const xmlpp::Element * p); void execute(xmlpp::Element *) const; - const Glib::ustring & getCurrentValue(const Glib::ustring & id) const; + virtual unsigned int columnCount() const; + virtual const Glib::ustring & getColumnName(unsigned int col) const; + virtual const Glib::ustring & getCurrentValue(const Glib::ustring & id) const; + virtual const Glib::ustring & getCurrentValue(unsigned int col) const; private: const xmlpp::Element * copyRoot; }; diff --git a/project2/rowBase.h b/project2/rowBase.h deleted file mode 100644 index c9397ef..0000000 --- a/project2/rowBase.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef ROWBASE_H -#define ROWBASE_H - -class RowBase { - public: - virtual unsigned int columnCount() const = 0; - virtual const Glib::ustring & getColumnName(unsigned int col) const = 0; - virtual const Glib::ustring & getCurrentValue(const Glib::ustring & id) const = 0; - virtual const Glib::ustring & getCurrentValue(unsigned int col) const = 0; - virtual void rowReady() const = 0; -}; - -#endif - diff --git a/project2/sourceObject.cpp b/project2/sourceObject.cpp index bb989b5..533ab5b 100644 --- a/project2/sourceObject.cpp +++ b/project2/sourceObject.cpp @@ -8,6 +8,12 @@ _SourceObject::_SourceObject(const xmlpp::Element * p) : { } +_SourceObject::_SourceObject(const std::string & n) : + name(n), + order(loadOrder++) +{ +} + _SourceObject::~_SourceObject() { } diff --git a/project2/sourceObject.h b/project2/sourceObject.h index b4472d4..da94614 100644 --- a/project2/sourceObject.h +++ b/project2/sourceObject.h @@ -7,6 +7,7 @@ class _SourceObject { public: _SourceObject(const xmlpp::Element * p); + _SourceObject(const std::string & name); virtual ~_SourceObject() = 0; const std::string name; const unsigned int order; diff --git a/project2/sqlMergeTask.cpp b/project2/sqlMergeTask.cpp new file mode 100644 index 0000000..5ecd113 --- /dev/null +++ b/project2/sqlMergeTask.cpp @@ -0,0 +1,196 @@ +#include "sqlMergeTask.h" +#include "appEngine.h" +#include "rdbmsDataSource.h" +#include +#include + +bool _SqlMergeTask::defaultUseTempTable = true; + +// Conversion logic +_SqlMergeTask::_SqlMergeTask(const xmlpp::Element * p) : + _SourceObject(p), + _Task(p), + updateWhere(p->get_attribute_value("updatewhere")), + patchOrder(p->get_attribute_value("patchorder")), + earlyKeys(p->get_attribute_value("earlykeys") == "yes"), + tempTableCreated(false), + destdb(NULL), + dataSource(p->get_attribute_value("datasource")), + dtable(p->get_attribute_value("targettable")), + dtablet(stringf("tmp_%s_%d", dtable.c_str(), getpid())) +{ + Loaders loaders; + _Iterate::AddLoaders(loaders, sources); + _LoaderBase::collectAll(loaders, "project2", p, true, true); + + BOOST_FOREACH(xmlpp::Node * psi, p->find("columns/column")) { + xmlpp::Element * e = static_cast(psi); + cols.insert(e->get_child_text()->get_content()); + } + BOOST_FOREACH(xmlpp::Node * psi, p->find("columns/column[@key='true']")) { + xmlpp::Element * e = static_cast(psi); + keys.insert(e->get_child_text()->get_content()); + } + BOOST_FOREACH(xmlpp::Node * psi, p->find("sql")) { + xmlpp::Element * e = static_cast(psi); + if (e->get_child_text()) { + sqls.push_back(e->get_child_text()->get_content()); + } + } +} + +_SqlMergeTask::~_SqlMergeTask() +{ +} + +void +_SqlMergeTask::execute() const +{ + destdb = &ApplicationEngine::getCurrent()->dataSource<_RdbmsDataSource>(dataSource)->getWritable(); + createTempTable(); + if (earlyKeys) { + createTempKey(); + copyToTempTable(); + } + else { + copyToTempTable(); + createTempKey(); + } + TablePatch tp(*destdb, dtablet, dtable, cols); + foreach(Keys::const_iterator, keys, k) { + tp.addKey(*k); + } + tp.patch(updateWhere.empty() ? NULL : updateWhere.c_str(), + patchOrder.empty() ? NULL : patchOrder.c_str()); + dropTempTable(); +} + +void +_SqlMergeTask::createTempTable() const +{ + ModifyCommand(*destdb, stringf( + "CREATE TEMPORARY TABLE %s AS SELECT * FROM %s WHERE 0=1", + dtablet.c_str(), + dtable.c_str())).execute(); + tempTableCreated = true; +} +void +_SqlMergeTask::dropTempTable() const +{ + if (tempTableCreated) { + ModifyCommand(*destdb, "DROP TABLE " + dtablet).execute(); + } +} +void +_SqlMergeTask::createTempKey() const +{ + /* Primary key */ + Buffer idx; + idx.appendf("ALTER TABLE %s ADD CONSTRAINT pk_%s PRIMARY KEY(", + dtablet.c_str(), dtablet.c_str()); + foreach(Keys::const_iterator, keys, k) { + if (k != keys.begin()) { + idx.append(", "); + } + idx.appendf("%s", k->c_str()); + } + idx.append(")"); + ModifyCommand(*destdb, idx.c_str()).execute(); + /* Indexes */ + int n = 0; + foreach(Keys::const_iterator, indexes, i) { + ModifyCommand(*destdb, stringf( + "CREATE INDEX idx_%s_%d ON %s(%s)", + dtablet.c_str(), n, dtablet.c_str(), i->c_str())).execute(); + n += 1; + } +} +ModifyCommand * +_SqlMergeTask::insertCommand() const +{ + Buffer insC; + Buffer insV; + insC.appendf("INSERT INTO %s(", dtablet.c_str()); + insV.append(") VALUES ("); + foreach(Columns::const_iterator, cols, c) { + if (c != cols.begin()) { + insC.append(", "); + insV.append(", "); + } + insC.appendf("%s", c->c_str()); + insV.append("?"); + } + Buffer res; + res.appendf("%s%s)", insC.c_str(), insV.c_str()); + return new ModifyCommand(*destdb, res.c_str()); +} + +class _Populate : public _NoOutputExecute { + public: + _Populate(Iterate i, ModifyCommand * c) : + _SourceObject(__FUNCTION__), + _NoOutputExecute(__FUNCTION__), + iter(i), + cmd(c) + { + } + + void execute() const + { + if (idxs.empty()) { + idxs.resize(iter->columnCount()); + // Sure this can be improved upon... but hey. + // Find each columns alphabetical index + unsigned int cols = iter->columnCount(); + for (unsigned int c = 0; c < cols; c += 1) { + idxs[c] = 0; + for (unsigned int d = 0; d < cols; d += 1) { + if (c != d && iter->getColumnName(d) < iter->getColumnName(c)) { + idxs[c]++; + } + } + } + } + unsigned int cols = iter->columnCount(); + for (unsigned int c = 0; c < cols; c += 1) { + cmd->bindParamS(idxs[c], iter->getCurrentValue(c)); + } + cmd->execute(); + } + private: + mutable std::vector idxs; + Iterate iter; + ModifyCommand * cmd; +}; +typedef boost::shared_ptr<_Populate> Populate; + +void +_SqlMergeTask::copyToTempTable() const +{ + ModifyCommand * insert = insertCommand(); + BOOST_FOREACH(const Iterates::value_type & i, sources) { + Populate p(new _Populate(i.second, insert)); + i.second->subNOEs.insert(NoOutputExecutes::value_type(0, p)); + i.second->execute(); + i.second->subNOEs.erase(0); + } + delete insert; + BOOST_FOREACH(const std::string & sql, sqls) { + Buffer insC; + Buffer insV; + insC.appendf("INSERT INTO %s(", dtablet.c_str()); + insV.append(") SELECT "); + BOOST_FOREACH(const std::string & c, cols) { + if (c != *cols.begin()) { + insC.append(", "); + insV.append(", "); + } + insC.append(c.c_str()); + insV.append(c.c_str()); + } + Buffer res; + res.appendf("%s%s FROM (%s) tmp_src", insC.c_str(), insV.c_str(), sql.c_str()); + ModifyCommand(*destdb, res.c_str()).execute(); + } +} + diff --git a/project2/sqlMergeTask.h b/project2/sqlMergeTask.h new file mode 100644 index 0000000..be5f6fa --- /dev/null +++ b/project2/sqlMergeTask.h @@ -0,0 +1,119 @@ +#ifndef SQLMERGETASK_H +#define SQLMERGETASK_H + +#include +#include +#include +#include +#include "tablepatch.h" +#include "task.h" +#include "iterate.h" +#include +#include +#include +#include + +class _SqlMergeTask : public _Task { + public: + typedef std::string Table; + typedef std::string Column; + + typedef std::set Columns; + typedef Columns Keys; + + _SqlMergeTask(const xmlpp::Element * p); + virtual ~_SqlMergeTask(); + + void execute() const; + Columns cols; + Keys keys; + Keys indexes; + const std::string updateWhere; + const std::string patchOrder; + bool earlyKeys; + + private: + virtual void copyToTempTable() const; + void createTempTable() const; + void dropTempTable() const; + void createTempKey() const; + + mutable bool tempTableCreated; + Iterates sources; + std::list sqls; + protected: + ModifyCommand * insertCommand() const; + + public: + mutable ODBC::Connection * destdb; + const std::string dataSource; + const Table dtable; + const Table dtablet; + + static unsigned int defaultVerbosity; + static bool defaultUseTempTable; +}; + +/* +class CopyBasedDbConversion : public DbConversion { + public: + CopyBasedDbConversion(const Connection & dest, const std::string dt); + virtual ~CopyBasedDbConversion(); + private: + void copyToTempTable() const; + virtual SelectCommand * selectCommand() const = 0; +}; +class SimpleDbConversion : public CopyBasedDbConversion { + public: + SimpleDbConversion(const Connection & src, const std::string st, const Connection & dest, + const std::string dt); + ~SimpleDbConversion(); + SelectCommand * selectCommand() const; + private: + const Connection & srcdb; + const Table stable; + mutable SelectCommand * srcCmd; +}; +class CustomDbConversion : public CopyBasedDbConversion { + public: + CustomDbConversion(SelectCommand * srcc, const Connection & dest, const std::string dt); + ~CustomDbConversion(); + private: + SelectCommand * selectCommand() const; + SelectCommand * srcCmd; +}; +class EasyFillDbConversion : public DbConversion { + public: + EasyFillDbConversion(const std::string src, const Connection & db, const std::string dt); + ~EasyFillDbConversion(); + + private: + void copyToTempTable() const; + const std::string srcSql; +}; +class FileFillDbConversion : public DbConversion { + public: + FileFillDbConversion(const std::string src, const Connection & db, const std::string dt); + ~FileFillDbConversion(); + + std::list post; + + private: + void copyToTempTable() const; + const std::string srcFile; +}; +class CustomFillDbConversion : public DbConversion { + public: + typedef void(*FillFunc)(const CustomFillDbConversion *); + + CustomFillDbConversion(FillFunc fill, const Connection & dest, const std::string dt); + ~CustomFillDbConversion(); + + private: + void copyToTempTable() const; + const FillFunc filler; +}; +*/ + +#endif + diff --git a/project2/sqlRows.h b/project2/sqlRows.h index 98a20f3..eff32bc 100644 --- a/project2/sqlRows.h +++ b/project2/sqlRows.h @@ -8,11 +8,10 @@ #include "iterate.h" #include "view.h" #include "iHaveParameters.h" -#include "rowBase.h" class ApplicationEngine; -class _SqlRows : public IHaveParameters, public RowBase { +class _SqlRows : public IHaveParameters, public PerRowValues { public: _SqlRows(const xmlpp::Element * p); ~_SqlRows(); @@ -22,6 +21,7 @@ class _SqlRows : public IHaveParameters, public RowBase { const Glib::ustring & getColumnName(unsigned int col) const; const Glib::ustring & getCurrentValue(const Glib::ustring & id) const; const Glib::ustring & getCurrentValue(unsigned int col) const; + virtual void rowReady() const = 0; const std::string dataSource; const Glib::ustring sql; diff --git a/project2/tablepatch.cpp b/project2/tablepatch.cpp new file mode 100644 index 0000000..3f057df --- /dev/null +++ b/project2/tablepatch.cpp @@ -0,0 +1,271 @@ +#include "tablepatch.h" +#include +#include +#include +#include +#include +#include + +TablePatch::TablePatch(const Connection & wdb, const TablePatch::Table & s, const TablePatch::Table & d, + const TablePatch::Columns & c) : + src(s), + dest(d), + cols(c), + db(wdb) +{ + if (!src.length()) { + throw PatchCheckFailure(); + } + if (!dest.length()) { + throw PatchCheckFailure(); + } + if (!db.inTx()) { + throw PatchCheckFailure(); + } +} + +void +TablePatch::addKey(const TablePatch::Column & c) +{ + pk.insert(c); +} + +void +TablePatch::patch(const char * where, const char * order) +{ + if (pk.size() == 0) { + throw PatchCheckFailure(); + } + doDeletes(where, order); + doUpdates(where, order); + doInserts(order); +} + +void +TablePatch::copyBind(const SelectCommand * src, ModifyCommand * dest, int cola, int colb) +{ + (*src)[cola].rebind(dest, colb); +} + +void +TablePatch::doDeletes(const char * where, const char * order) +{ + // ----------------------------------------------------------------- + // Build SQL to select keys to delete ------------------------------ + // ----------------------------------------------------------------- + Buffer toDelSql; + toDelSql.append("SELECT "); + foreach (PKI, pk, pki) { + if (pki != pk.begin()) { + toDelSql.append(", "); + } + toDelSql.appendf("a.%s", + pki->c_str()); + } + toDelSql.appendf(" FROM %s a LEFT OUTER JOIN %s b", + dest.c_str(), src.c_str()); + foreach (PKI, pk, pki) { + if (pki == pk.begin()) { + toDelSql.append(" ON "); + } + else { + toDelSql.append(" AND "); + } + toDelSql.appendf(" a.%s = b.%s", + pki->c_str(), pki->c_str()); + } + foreach (PKI, pk, pki) { + if (pki == pk.begin()) { + toDelSql.append(" WHERE "); + } + else { + toDelSql.append(" AND "); + } + toDelSql.appendf(" b.%s IS NULL", + pki->c_str()); + } + if (where && *where) { + toDelSql.appendf(" AND %s", where); + } + if (order) { + toDelSql.appendf(" ORDER BY %s", order); + } + // ----------------------------------------------------------------- + // Build SQL to delete keys ---------------------------------------- + // ----------------------------------------------------------------- + Buffer delSql; + delSql.appendf("DELETE FROM %s d WHERE (", + dest.c_str()); + foreach (PKI, pk, pki) { + if (pki != pk.begin()) { + delSql.append(", "); + } + delSql.appendf("d.%s", + pki->c_str()); + } + delSql.appendf(") IN (%s)", toDelSql.c_str()); + ModifyCommand del(db, delSql.c_str()); + del.execute(); +} + +void +TablePatch::doUpdates(const char * where, const char * order) +{ + if (cols.size() == pk.size()) { + // Can't "change" anything... it's all part of the key + return; + } + // ----------------------------------------------------------------- + // Build SQL for list of updates to perform ------------------------ + // ----------------------------------------------------------------- + Buffer toUpdSel; + Buffer ch; + Buffer k; + foreach (Columns::const_iterator, cols, col) { + if (pk.find(*col) == pk.end()) { + ch.appendf("b.%s, ", + col->c_str()); + } + else { + if (k.length()) { + k.append(", "); + } + k.appendf("b.%s ", + col->c_str()); + } + } + toUpdSel.appendf("SELECT %s %s FROM %s a, %s b", + ch.c_str(), k.c_str(), dest.c_str(), src.c_str()); + foreach (PKI, pk, pki) { + if (pki == pk.begin()) { + toUpdSel.append(" WHERE "); + } + else { + toUpdSel.append(" AND "); + } + toUpdSel.appendf(" a.%s = b.%s", + pki->c_str(), pki->c_str()); + } + if (where && *where) { + toUpdSel.appendf(" AND %s", where); + } + toUpdSel.append(" AND ("); + bool first = true; + foreach (Columns::const_iterator, cols, col) { + if (pk.find(*col) == pk.end()) { + if (!first) { + toUpdSel.append(" OR "); + } + first = false; + toUpdSel.appendf( + " (((CASE WHEN (a.%s IS NULL AND b.%s IS NULL) THEN 1 ELSE 0 END) \ + + (CASE WHEN(a.%s = b.%s) THEN 1 ELSE 0 END)) = 0)", + col->c_str(), col->c_str(), col->c_str(), col->c_str()); + } + } + toUpdSel.append(")"); + if (order) { + toUpdSel.appendf(" ORDER BY %s", order); + } + // ----------------------------------------------------------------- + // Build SQL to perform updates ------------------------------------ + // ----------------------------------------------------------------- + Buffer updSql; + updSql.appendf("UPDATE %s SET ", + dest.c_str()); + first = true; + foreach (Columns::const_iterator, cols, col) { + if (pk.find(*col) == pk.end()) { + if (!first) { + updSql.append(", "); + } + first = false; + updSql.appendf(" %s = ?", + col->c_str()); + } + } + foreach (PKI, pk, pki) { + if (pki == pk.begin()) { + updSql.append(" WHERE "); + } + else { + updSql.append(" AND "); + } + updSql.appendf(" %s = ?", + pki->c_str()); + } + // ----------------------------------------------------------------- + // Iterator over update list make changes -------------------------- + // ----------------------------------------------------------------- + SelectCommand toUpd(db, toUpdSel.c_str()); + ModifyCommand upd(db, updSql.c_str()); + int cs = cols.size(); + while (toUpd.fetch()) { + for (int c = 0; c < cs; c += 1) { + copyBind(&toUpd, &upd, c, c); + } + upd.execute(false); + } +} + +void +TablePatch::doInserts(const char * order) +{ + // ----------------------------------------------------------------- + // Build SQL for copying new records ------------------------------- + // ----------------------------------------------------------------- + Buffer toInsSql; + toInsSql.appendf("INSERT INTO %s", + dest.c_str()); + foreach (Columns::const_iterator, cols, col) { + if (col == cols.begin()) { + toInsSql.append("("); + } + else { + toInsSql.append(", "); + } + toInsSql.appendf("%s", + col->c_str()); + } + toInsSql.append(") SELECT "); + foreach (Columns::const_iterator, cols, col) { + if (col != cols.begin()) { + toInsSql.append(", "); + } + toInsSql.appendf("b.%s", + col->c_str()); + } + toInsSql.appendf(" FROM %s b LEFT OUTER JOIN %s a", + src.c_str(), dest.c_str()); + foreach (PKI, pk, pki) { + if (pki == pk.begin()) { + toInsSql.append(" ON "); + } + else { + toInsSql.append(" AND "); + } + toInsSql.appendf(" a.%s = b.%s", + pki->c_str(), pki->c_str()); + } + foreach (PKI, pk, pki) { + if (pki == pk.begin()) { + toInsSql.append(" WHERE "); + } + else { + toInsSql.append(" AND "); + } + toInsSql.appendf(" a.%s IS NULL", + pki->c_str()); + } + if (order) { + toInsSql.appendf(" ORDER BY %s", order); + } + ModifyCommand(db, toInsSql.c_str()).execute(); +} + +const char * +TablePatch::PatchCheckFailure::what() const throw() +{ + return "Santiy checks failed: check table names and keys"; +} + diff --git a/project2/tablepatch.h b/project2/tablepatch.h new file mode 100644 index 0000000..533ee16 --- /dev/null +++ b/project2/tablepatch.h @@ -0,0 +1,47 @@ +#ifndef TABLEPATCH_H +#define TABLEPATCH_H + +#include +#include +#include +#include +#include +#include +#include + +using namespace ODBC; + +class TablePatch { + public: + typedef std::string Table; + typedef std::string Column; + typedef std::set Columns; + typedef Columns PrimaryKey; + typedef PrimaryKey::const_iterator PKI; + + class PatchCheckFailure : public std::exception { + public: + const char * what() const throw(); + }; + + TablePatch(const Connection & db, const Table & src, const Table & dest, const Columns & cols); + + void addKey(const Column & col); + void patch(const char * where, const char * order); + + static void copyBind(const SelectCommand * src, ModifyCommand * dest, int cola, int colb); + + private: + void doDeletes(const char * where, const char * order); + void doUpdates(const char * where, const char * order); + void doInserts(const char * order); + + Table src; + Table dest; + PrimaryKey pk; + Columns cols; + const Connection &db; +}; + +#endif + diff --git a/project2/task.cpp b/project2/task.cpp index 65ce359..ee5bb18 100644 --- a/project2/task.cpp +++ b/project2/task.cpp @@ -2,6 +2,7 @@ #include #include "xmlObjectLoader.h" #include "sqlTask.h" +#include "sqlMergeTask.h" #include "sessionClearTask.h" #include "sessionSetTask.h" #include "sendmailTask.h" @@ -20,6 +21,7 @@ void _Task::AddLoaders(Loaders & l, OrderedTasks & tasks) { l.insert(LoadersVT("sqltask", _LoaderBase::Make<_SqlTask, _Task, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); + l.insert(LoadersVT("sqlmerge", _LoaderBase::Make<_SqlMergeTask, _Task, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); l.insert(LoadersVT("sessionclear", _LoaderBase::Make<_SessionClearTask, _Task, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); l.insert(LoadersVT("sessionset", _LoaderBase::Make<_SessionSetTask, _Task, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); l.insert(LoadersVT("sendmail", _LoaderBase::Make<_SendMailTask, _Task, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); @@ -29,6 +31,7 @@ void _Task::AddLoaders(Loaders & l, NoOutputExecutes & tasks) { l.insert(LoadersVT("sqltask", _LoaderBase::Make<_SqlTask, _NoOutputExecute, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); + l.insert(LoadersVT("sqlmerge", _LoaderBase::Make<_SqlMergeTask, _NoOutputExecute, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); l.insert(LoadersVT("sessionclear", _LoaderBase::Make<_SessionClearTask, _NoOutputExecute, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); l.insert(LoadersVT("sessionset", _LoaderBase::Make<_SessionSetTask, _NoOutputExecute, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); l.insert(LoadersVT("sendmail", _LoaderBase::Make<_SendMailTask, _NoOutputExecute, unsigned int, _SourceObject, &_SourceObject::order>(&tasks))); diff --git a/project2/view.h b/project2/view.h index 165ed12..23b7464 100644 --- a/project2/view.h +++ b/project2/view.h @@ -33,10 +33,22 @@ class _GenericView : public _View, public Driver { public: _GenericView(const xmlpp::Element * p); + virtual unsigned int columnCount() const + { + return Driver::columnCount(); + } + virtual const Glib::ustring & getCurrentValue(const unsigned int id) const + { + return Driver::getCurrentValue(id); + } virtual const Glib::ustring & getCurrentValue(const Glib::ustring & id) const { return Driver::getCurrentValue(id); } + virtual const Glib::ustring & getColumnName(const unsigned int id) const + { + return Driver::getColumnName(id); + } virtual void execute(xmlpp::Element * e) const; virtual void rowReady() const; private: -- cgit v1.2.3