#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::doDeletes(const char * where, const char * order) { switch (db.bulkDeleteStyle()) { case BulkDeleteUsingSubSelect: { // ----------------------------------------------------------------- // Build SQL to delete keys ---------------------------------------- // ----------------------------------------------------------------- Buffer toDelSql; toDelSql.appendf("DELETE FROM %s WHERE (", dest.c_str()); foreach (PKI, pk, pki) { if (pki != pk.begin()) { toDelSql.append(", "); } toDelSql.appendf("%s.%s", dest.c_str(), pki->c_str()); } // ----------------------------------------------------------------- // Build SQL to select keys to delete ------------------------------ // ----------------------------------------------------------------- toDelSql.append(") IN (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 ON ", dest.c_str(), src.c_str()); foreach (PKI, pk, pki) { if (pki != pk.begin()) { 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 && *order) { toDelSql.appendf(" ORDER BY %s", order); } toDelSql.append(")"); ModifyCommand * del = db.newModifyCommand(toDelSql); del->execute(); delete del; break; } case BulkDeleteUsingUsingAlias: case BulkDeleteUsingUsing: { Buffer toDelSql; toDelSql.appendf("DELETE FROM %s USING %s a LEFT OUTER JOIN %s b ", (db.bulkDeleteStyle() == BulkDeleteUsingUsingAlias ? "a" : dest.c_str()), dest.c_str(), src.c_str()); foreach (PKI, pk, pki) { if (pki != pk.begin()) { toDelSql.append(" AND "); } else { toDelSql.append(" ON "); } toDelSql.appendf(" a.%s = b.%s", pki->c_str(), pki->c_str()); } foreach (PKI, pk, pki) { if (pki != pk.begin()) { toDelSql.append(" AND "); } else { toDelSql.append(" WHERE "); } toDelSql.appendf(" b.%s IS NULL", pki->c_str()); } if (where && *where) { toDelSql.appendf(" AND %s", where); } if (order && *order) { toDelSql.appendf(" ORDER BY %s", order); } ModifyCommand * del = db.newModifyCommand(toDelSql); del->execute(); delete del; break; } } } 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; } switch (db.bulkUpdateStyle()) { case BulkUpdateByIteration: { // ----------------------------------------------------------------- // Build SQL for list of updates to perform ------------------------ // ----------------------------------------------------------------- Buffer toUpdSel; toUpdSel.append("SELECT "); foreach (Columns::const_iterator, cols, col) { if (pk.find(*col) == pk.end()) { toUpdSel.appendf("b.%s, ", col->c_str()); } } foreach (Columns::const_iterator, cols, col) { if (pk.find(*col) != pk.end()) { toUpdSel.appendf("b.%s, ", col->c_str()); } } toUpdSel.appendf("0 FROM %s a, %s b", 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 && *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.newSelectCommand(toUpdSel); ModifyCommand * upd = db.newModifyCommand(updSql); int cs = cols.size(); toUpd->execute(); for (int c = 0; c < cs; c += 1) { (*toUpd)[c].rebind(upd, c); } while (toUpd->fetch()) { upd->execute(false); } delete toUpd; delete upd; } break; case BulkUpdateUsingFromSrc: { // ----------------------------------------------------------------- // Build SQL for list of updates to perform ------------------------ // ----------------------------------------------------------------- Buffer updSql; updSql.appendf("UPDATE %s a SET ", dest.c_str()); bool first = true; foreach (Columns::const_iterator, cols, col) { if (pk.find(*col) == pk.end()) { if (!first) { updSql.append(", "); } first = false; updSql.appendf(" %s = b.%s ", col->c_str(), col->c_str()); } } updSql.appendf(" FROM %s b ", src.c_str()); foreach (PKI, pk, pki) { if (pki == pk.begin()) { updSql.append(" WHERE "); } else { updSql.append(" AND "); } updSql.appendf(" a.%s = b.%s ", pki->c_str(), pki->c_str()); } updSql.append(" AND ("); first = true; foreach (Columns::const_iterator, cols, col) { if (pk.find(*col) == pk.end()) { if (!first) { updSql.append(" OR "); } first = false; updSql.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()); } } updSql.append(")"); if (where && *where) { updSql.appendf(" AND %s ", where); } if (order && *order) { updSql.appendf(" ORDER BY %s", order); } // ----------------------------------------------------------------- // Execute the bulk update command --------------------------------- // ----------------------------------------------------------------- ModifyCommand * upd = db.newModifyCommand(updSql); upd->execute(true); delete upd; break; } case BulkUpdateUsingJoin: { // ----------------------------------------------------------------- // Build SQL for list of updates to perform ------------------------ // ----------------------------------------------------------------- Buffer updSql; updSql.appendf("UPDATE %s a, %s b SET ", dest.c_str(), src.c_str()); bool first = true; foreach (Columns::const_iterator, cols, col) { if (pk.find(*col) == pk.end()) { if (!first) { updSql.append(", "); } first = false; updSql.appendf(" a.%s = b.%s ", col->c_str(), col->c_str()); } } foreach (PKI, pk, pki) { if (pki == pk.begin()) { updSql.append(" WHERE "); } else { updSql.append(" AND "); } updSql.appendf(" a.%s = b.%s ", pki->c_str(), pki->c_str()); } updSql.append(" AND ("); first = true; foreach (Columns::const_iterator, cols, col) { if (pk.find(*col) == pk.end()) { if (!first) { updSql.append(" OR "); } first = false; updSql.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()); } } updSql.append(")"); if (where && *where) { updSql.appendf(" AND %s ", where); } if (order && *order) { updSql.appendf(" ORDER BY %s", order); } // ----------------------------------------------------------------- // Execute the bulk update command --------------------------------- // ----------------------------------------------------------------- ModifyCommand * upd = db.newModifyCommand(updSql); upd->execute(true); delete upd; break; } } } 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 && *order) { toInsSql.appendf(" ORDER BY %s", order); } ModifyCommand * ins = db.newModifyCommand(toInsSql); ins->execute(); delete ins; } const char * TablePatch::PatchCheckFailure::what() const throw() { return "Santiy checks failed: check table names and keys"; }