/* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include static const char* opt_dbname = 0; static bool opt_check_orphans = false; static bool opt_delete_orphans = false; static const char* opt_dump_file = 0; static bool opt_verbose = false; static FILE* g_dump_file = 0; static FileOutputStream* g_dump_out = 0; static NdbOut g_dump; static Ndb_cluster_connection* g_ncc = 0; static Ndb* g_ndb = 0; static NdbDictionary::Dictionary* g_dic = 0; static const char* g_tabname = 0; static const NdbDictionary::Table* g_tab = 0; struct Pk { // table pk const char* colname; Pk() { colname = 0; } ~Pk() { delete [] colname; } }; static Pk* g_pklist = 0; static int g_pkcount = 0; struct Blob { // blob column and table int blobno; int colno; const char* colname; const char* blobname; const NdbDictionary::Table* blobtab; Blob() { blobno = -1; colno = -1; colname = 0; blobname = 0; blobtab = 0; } ~Blob() { delete [] colname; delete [] blobname; } }; static Blob* g_bloblist = 0; static int g_blobcount = 0; static NdbTransaction* g_scantx = 0; static NdbScanOperation* g_scanop = 0; struct Val { // attr value scanned from blob const char* colname; NdbRecAttr* ra; Val() { colname = 0; ra = 0; } ~Val() { delete [] colname; } }; static Val* g_vallist = 0; static int g_valcount = 0; #define CHK1(b) \ if (!(b)) { \ ret = -1; \ break; \ } #define CHK2(b, e) \ if (!(b)) { \ g_err << "ERR: " << #b << " failed at line " << __LINE__ \ << ": " << e << endl; \ ret = -1; \ break; \ } // re-inventing strdup static const char* newstr(const char* s) { require(s != 0); char* s2 = new char [strlen(s) + 1]; strcpy(s2, s); return s2; } static NdbError getNdbError(Ndb_cluster_connection* ncc) { NdbError err; err.code = g_ncc->get_latest_error(); err.message = g_ncc->get_latest_error_msg(); return err; } static int doconnect() { int ret = 0; do { g_ncc = new Ndb_cluster_connection(opt_ndb_connectstring); CHK2(g_ncc->connect(opt_connect_retries - 1, opt_connect_retry_delay) == 0, getNdbError(g_ncc)); CHK2(g_ncc->wait_until_ready(30, 10) == 0, getNdbError(g_ncc)); g_ndb = new Ndb(g_ncc, opt_dbname); CHK2(g_ndb->init() == 0, g_ndb->getNdbError()); CHK2(g_ndb->waitUntilReady(30) == 0, g_ndb->getNdbError()); g_dic = g_ndb->getDictionary(); g_info << "connected" << endl; } while (0); return ret; } static void dodisconnect() { delete g_ndb; delete g_ncc; g_info << "disconnected" << endl; } static int scanblobstart(const Blob& b) { int ret = 0; do { require(g_scantx == 0); g_scantx = g_ndb->startTransaction(); CHK2(g_scantx != 0, g_ndb->getNdbError()); g_scanop = g_scantx->getNdbScanOperation(b.blobtab); CHK2(g_scanop != 0, g_scantx->getNdbError()); const NdbOperation::LockMode lm = NdbOperation::LM_Exclusive; CHK2(g_scanop->readTuples(lm) == 0, g_scanop->getNdbError()); for (int i = 0; i < g_valcount; i++) { Val& v = g_vallist[i]; v.ra = g_scanop->getValue(v.colname); CHK2(v.ra != 0, v.colname << ": " << g_scanop->getNdbError()); } CHK1(ret == 0); CHK2(g_scantx->execute(NoCommit) == 0, g_scantx->getNdbError()); } while (0); return ret; } static int scanblobnext(const Blob& b, int& res) { int ret = 0; do { res = g_scanop->nextResult(); CHK2(res == 0 || res == 1, g_scanop->getNdbError()); g_info << b.blobname << ": nextResult: res=" << res << endl; } while (0); return ret; } static void scanblobclose(const Blob& b) { if (g_scantx != 0) { g_ndb->closeTransaction(g_scantx); g_scantx = 0; } } static int checkorphan(const Blob&b, int& res) { int ret = 0; NdbTransaction* tx = 0; NdbOperation* op = 0; do { tx = g_ndb->startTransaction(); CHK2(tx != 0, g_ndb->getNdbError()); op = tx->getNdbOperation(g_tab); CHK2(op != 0, tx->getNdbError()); const NdbOperation::LockMode lm = NdbOperation::LM_Read; CHK2(op->readTuple(lm) == 0, op->getNdbError()); for (int i = 0; i < g_pkcount; i++) { Val& v = g_vallist[i]; require(v.ra != 0); require(v.ra->isNULL() == 0); const char* data = v.ra->aRef(); CHK2(op->equal(v.colname, data) == 0, op->getNdbError()); } CHK1(ret == 0); // read something to be safe NdbRecAttr* ra0 = op->getValue(g_vallist[0].colname); require(ra0 != 0); // not sure about the rules require(tx->getNdbError().code == 0); tx->execute(Commit); if (tx->getNdbError().code == 626) { g_info << "parent not found" << endl; res = 1; // not found } else { CHK2(tx->getNdbError().code == 0, tx->getNdbError()); res = 0; // found } } while (0); if (tx != 0) g_ndb->closeTransaction(tx); return ret; } static int deleteorphan(const Blob& b) { int ret = 0; NdbTransaction* tx = 0; do { tx = g_ndb->startTransaction(); CHK2(tx != 0, g_ndb->getNdbError()); CHK2(g_scanop->deleteCurrentTuple(tx) == 0, g_scanop->getNdbError()); CHK2(tx->execute(Commit) == 0, tx->getNdbError()); } while (0); if (tx != 0) g_ndb->closeTransaction(tx); return ret; } static int doorphan(const Blob& b) { int ret = 0; do { g_err << "processing blob #" << b.blobno << " " << b.colname << " " << b.blobname << endl; if (opt_dump_file) { g_dump << "column: " << b.colname << endl; g_dump << "blob: " << b.blobname << endl; g_dump << "orphans (table key; blob part number):" << endl; } int totcount = 0; int orphancount = 0; CHK1(scanblobstart(b) == 0); while (1) { int res; res = -1; CHK1(scanblobnext(b, res) == 0); if (res != 0) break; totcount++; res = -1; CHK1(checkorphan(b, res) == 0); if (res != 0) { orphancount++; if (opt_dump_file) { g_dump << "key: "; for (int i = 0; i < g_valcount; i++) { const Val& v = g_vallist[i]; g_dump << *v.ra; if (i + 1 < g_valcount) g_dump << ";"; } g_dump << endl; } if (opt_delete_orphans) { CHK1(deleteorphan(b) == 0); } } } CHK1(ret == 0); g_err << "total parts: " << totcount << endl; g_err << "orphan parts: " << orphancount << endl; if (opt_dump_file) { g_dump << "total parts: " << totcount << endl; g_dump << "orphan parts: " << orphancount << endl; } } while (0); scanblobclose(b); return ret; } static int doorphans() { int ret = 0; g_err << "processing " << g_blobcount << " blobs" << endl; for (int i = 0; i < g_blobcount; i++) { const Blob& b = g_bloblist[i]; CHK1(doorphan(b) == 0); } return ret; } static int isblob(const NdbDictionary::Column* c) { if (c->getType() == NdbDictionary::Column::Blob || c->getType() == NdbDictionary::Column::Text) if (c->getPartSize() != 0) return 1; return 0; } static int getobjs() { int ret = 0; do { g_tab = g_dic->getTable(g_tabname); CHK2(g_tab != 0, g_tabname << ": " << g_dic->getNdbError()); const int tabid = g_tab->getObjectId(); const int ncol = g_tab->getNoOfColumns(); g_pklist = new Pk [NDB_MAX_NO_OF_ATTRIBUTES_IN_KEY]; for (int i = 0; i < ncol; i++) { const NdbDictionary::Column* c = g_tab->getColumn(i); if (c->getPrimaryKey()) { Pk& p = g_pklist[g_pkcount++]; const char* colname = c->getName(); p.colname = newstr(colname); } } require(g_pkcount != 0 && g_pkcount == g_tab->getNoOfPrimaryKeys()); g_valcount = g_pkcount + 1; g_vallist = new Val [g_valcount]; for (int i = 0; i < g_valcount; i++) { Val& v = g_vallist[i]; if (i < g_pkcount) { const Pk& p = g_pklist[i]; v.colname = newstr(p.colname); } if (i == g_pkcount + 0) // first blob attr to scan { v.colname = newstr("NDB$PART"); } } if (g_blobcount == 0) { for (int i = 0; i < ncol; i++) { const NdbDictionary::Column* c = g_tab->getColumn(i); if (isblob(c)) { Blob& b = g_bloblist[g_blobcount++]; const char* colname = c->getName(); b.colname = newstr(colname); } } } for (int i = 0; i < g_blobcount; i++) { Blob& b = g_bloblist[i]; b.blobno = i; const NdbDictionary::Column* c = g_tab->getColumn(b.colname); CHK2(c != 0, g_tabname << ": " << b.colname << ": no such column"); CHK2(isblob(c), g_tabname << ": " << b.colname << ": not a blob"); b.colno = c->getColumnNo(); { char blobname[100]; sprintf(blobname, "NDB$BLOB_%d_%d", tabid, b.colno); b.blobname = newstr(blobname); } b.blobtab = g_dic->getTable(b.blobname); CHK2(b.blobtab != 0, g_tabname << ": " << b.colname << ": " << b.blobname << ": " << g_dic->getNdbError()); } CHK1(ret == 0); } while (0); return ret; } static int doall() { int ret = 0; do { if (opt_dump_file) { g_dump_file = fopen(opt_dump_file, "w"); CHK2(g_dump_file != 0, opt_dump_file << ": " << strerror(errno)); g_dump_out = new FileOutputStream(g_dump_file); new (&g_dump) NdbOut(*g_dump_out); const char* action = 0; if (opt_check_orphans) action = "check"; if (opt_delete_orphans) action = "delete"; g_dump << "table: " << g_tabname << endl; g_dump << "action: " << action << endl; } CHK1(doconnect() == 0); CHK1(getobjs() == 0); if (g_blobcount == 0) { g_err << g_tabname << ": no blob columns" << endl; break; } CHK1(doorphans() == 0); } while (0); dodisconnect(); if (g_dump_file != 0) { g_dump << "result: "<< (ret == 0 ? "ok" : "failed") << endl; flush(g_dump); if (fclose(g_dump_file) != 0) { g_err << opt_dump_file << ": write failed: " << strerror(errno) << endl; } g_dump_file = 0; } return ret; } static struct my_option my_long_options[] = { NDB_STD_OPTS("ndb_blob_tool"), { "database", 'd', "Name of database table is in", (uchar**) &opt_dbname, (uchar**) &opt_dbname, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "check-orphans", NDB_OPT_NOSHORT, "Check for orphan blob parts", (uchar **)&opt_check_orphans, (uchar **)&opt_check_orphans, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "delete-orphans", NDB_OPT_NOSHORT, "Delete orphan blob parts", (uchar **)&opt_delete_orphans, (uchar **)&opt_delete_orphans, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "dump-file", NDB_OPT_NOSHORT, "Write orphan keys (table key and part number) into file", (uchar **)&opt_dump_file, (uchar **)&opt_dump_file, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "verbose", 'v', "Verbose messages", (uchar **)&opt_verbose, (uchar **)&opt_verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 } }; static void short_usage_sub() { ndb_short_usage_sub("table [blobcolumn..]"); printf("Default is to process all blob columns\n"); printf("(1) Check orphans with --check --dump=out1.txt\n"); printf("(2) Delete orphans with --delete --dump=out2.txt\n"); } static void usage() { printf("%s: check and repair blobs\n", my_progname); } static int checkopts(int argc, char** argv) { if (opt_dbname == 0) opt_dbname = "TEST_DB"; if (argc < 1) { g_err << "Table name required" << endl; return 1; } g_tabname = newstr(argv[0]); g_bloblist = new Blob [NDB_MAX_ATTRIBUTES_IN_TABLE]; g_blobcount = argc - 1; for (int i = 0; i < g_blobcount; i++) { Blob& b = g_bloblist[i]; b.colname = newstr(argv[1 + i]); } if (opt_check_orphans || opt_delete_orphans) { if (opt_check_orphans && opt_delete_orphans) { g_err << "Specify only one action (--check-orphans etc)" << endl; return 1; } } else { g_err << "Action (--check-orphans etc) required" << endl; return 1; } return 0; } static void freeall() { delete [] g_tabname; delete [] g_pklist; delete [] g_bloblist; delete [] g_vallist; delete g_dump_out; if (g_dump_file != 0) (void)fclose(g_dump_file); } int main(int argc, char** argv) { NDB_INIT(argv[0]); Ndb_opts opts(argc, argv, my_long_options); ndb_opt_set_usage_funcs(short_usage_sub, usage); int ret = opts.handle_options(); if (ret != 0 || checkopts(argc, argv) != 0) return NDBT_ProgramExit(NDBT_WRONGARGS); setOutputLevel(opt_verbose ? 2 : 0); ret = doall(); freeall(); if (ret == -1) return NDBT_ProgramExit(NDBT_FAILED); return NDBT_ProgramExit(NDBT_OK); }