polardbxengine/storage/ndb/tools/ndb_blob_tool.cpp

619 lines
14 KiB
C++

/* 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 <ndb_global.h>
#include <ndb_opts.h>
#include <NdbOut.hpp>
#include <NdbApi.hpp>
#include <NDBT.hpp>
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);
}