indexdump
Version:
Manticore Search index dump utility
280 lines (273 loc) • 12.5 kB
JavaScript
;
var fs = require('fs');
var path = require('path');
var margv = require('margv');
var mariadb = require('mariadb');
var process$1 = require('process');
var perf_hooks = require('perf_hooks');
var chalk = require('chalk');
var archiver = require('archiver');
var stream = require('stream');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
var path__namespace = /*#__PURE__*/_interopNamespace(path);
var margv__default = /*#__PURE__*/_interopDefaultLegacy(margv);
var mariadb__default = /*#__PURE__*/_interopDefaultLegacy(mariadb);
var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
var archiver__default = /*#__PURE__*/_interopDefaultLegacy(archiver);
(async () => {
const start = perf_hooks.performance.now();
const argv = margv__default["default"]();
const showVersion = argv.v || argv.version;
if (showVersion) {
process$1.stdout.write("indexdump: 1.2.4\n");
process.exit(0);
}
if (argv.$.length < 3) {
console.error("Error parse arguments. Use: -h<host> -P<port> <index> > dump.sql");
process.exit(0);
}
const showHelp = !!argv.help;
if (showHelp) {
const TAB3 = " ";
const TAB4 = " ";
const TAB5 = " ";
process$1.stdout.write(chalk__default["default"].green("List of settings\n"));
process$1.stdout.write(chalk__default["default"].bold("-h/--host") + TAB4 + "host (default: 127.0.0.1)\n");
process$1.stdout.write(chalk__default["default"].bold("-P/--port") + TAB4 + "port (default: 9306)\n");
process$1.stdout.write(chalk__default["default"].bold("--dry-run") + TAB4 + "run in dry mode\n");
process$1.stdout.write(chalk__default["default"].bold("-ch/--chunk") + TAB4 + "chunk size for bulk inserts\n");
process$1.stdout.write(chalk__default["default"].bold("--add-drop-index\n--add-drop-table") + " add DROP TABLE\n");
process$1.stdout.write(chalk__default["default"].bold("--add-locks") + TAB4 + "add LOCK " + chalk__default["default"].red("not currently implemented in Manticore Search\n"));
process$1.stdout.write(chalk__default["default"].bold("--add-freeze") + TAB4 + "add FREEZE\n");
process$1.stdout.write(chalk__default["default"].bold("--to-index") + TAB4 + "rename to index in backup " + chalk__default["default"].red("only for single index\n"));
process$1.stdout.write(chalk__default["default"].bold("--prefix") + TAB4 + "add prefix for all indexes\n");
process$1.stdout.write(chalk__default["default"].bold("--indexes test1\n--indexes test2\n--indexes=test1,test2") + " indexes list for dump\n");
process$1.stdout.write(chalk__default["default"].bold("--all") + TAB5 + "all indexes\n");
process$1.stdout.write(chalk__default["default"].bold("--limit") + TAB5 + "add limit for all indexes, --limit=0 dump only structure\n");
process$1.stdout.write(chalk__default["default"].bold("--path") + TAB5 + "path from which the index will be restored (default: current)\n");
process$1.stdout.write(chalk__default["default"].bold("--data-dir") + TAB4 + "allow to set manticore data path\n");
process$1.stdout.write(chalk__default["default"].bold("--add-config") + TAB3 + "add manticore.json to dump\n");
process.exit(0);
}
const host = (argv.$.find((v) => v.indexOf("-h") === 0) || "").replace("-h", "") || argv["h"] || argv["host"] || "127.0.01";
const port = (argv.$.find((v) => v.indexOf("-P") === 0) || "").replace("-P", "") || argv["P"] || argv["port"] || 9306;
const chunk = (argv.$.find((v) => v.indexOf("-ch") === 0) || "").replace("-ch", "") || argv["ch"] || argv["chunk"] || 1e3;
const dropTable = !!(argv["add-drop-index"] || argv["add-drop-table"]);
const addLocks = !!argv["add-locks"];
const addFreeze = !!argv["add-freeze"];
const toTable = argv["to-index"] || argv["to-table"];
const toPrefix = argv["prefix"] || "";
const tables = argv["indexes"] || argv["tables"];
const isAll = argv["all"];
const limit = argv["limit"] ? parseInt(argv["limit"]) : argv["limit"];
const filesPath = argv["path"] || path__namespace.resolve();
const index = tables || argv.$.pop();
const dryRun = !!argv["dry-run"];
const dataDir = argv["data-dir"] || "";
const addConfig = !!argv["add-config"];
const filesToCopy = [];
if (toTable && tables) {
console.error("Error parse arguments. You can`t use to-table and tables together");
process.exit(0);
}
dryRun && process$1.stdout.write(chalk__default["default"].yellow("\n----- Start dry run -----\n"));
const devNull = new stream.Writable({
write(chunk2, encoding, callback) {
setImmediate(callback);
}
});
const arch = archiver__default["default"]("tar", { gzip: true });
!dryRun && arch.pipe(process$1.stdout);
dryRun && arch.pipe(devNull);
const promisifyStream = (file, name) => {
if (!dryRun) {
try {
fs__namespace.accessSync(file, fs__namespace.constants.R_OK);
} catch (e) {
return;
}
}
dryRun && process$1.stdout.write(`${file} => ./${name}`);
return new Promise((resolve) => {
const entryListener = () => {
arch.off("entry", entryListener);
arch.off("error", errorListener);
dryRun && process$1.stdout.write(chalk__default["default"].green(" done\n"));
resolve(true);
};
const errorListener = () => {
arch.off("entry", entryListener);
arch.off("error", errorListener);
dryRun && process$1.stdout.write(chalk__default["default"].red(" not accessible\n"));
resolve(true);
};
arch.append(fs__namespace.createReadStream(file), { name }).on("entry", entryListener).on("error", errorListener);
});
};
const createStream = (name) => {
dryRun && process$1.stdout.write(`./${name}
`);
const stream2 = new stream.PassThrough();
arch.append(stream2, { name });
return stream2;
};
const pathReplacer = async (strIndexCreate, pathToFile, index2) => {
for (const label of ["exceptions", "stopwords", "wordforms"]) {
const matches = strIndexCreate.match(RegExp(`${label}='([^']*)'`));
const files = (matches?.[1]?.split(/\s+/) || []).filter((file) => /^([A-Z]:)?\//.test(file));
if (files.length) {
const newFiles = files.map((file) => path__namespace.resolve(pathToFile, `./${path__namespace.basename(file)}`));
const newFilesString = newFiles.join(" ");
strIndexCreate = strIndexCreate.replace(matches[0], `${label}='${newFilesString}'`);
files.map((file) => filesToCopy.push({ file, path: path__namespace.join(index2, path__namespace.basename(file)) }));
}
}
return strIndexCreate;
};
const conn = await mariadb__default["default"].createConnection({ host, port });
const dumpTable = async (index2, output) => {
const toIndex = toTable ? `${toPrefix}${toTable}` : `${toPrefix}${index2}`;
const startChunk = (cols) => output.write(`INSERT INTO ${toIndex} (${cols.join(",")}) VALUES`);
const endChunk = (rows) => output.write(`${rows.join(",\n")};
`);
output.write(`-- START DUMP ${index2} --
`);
toIndex !== (index2 || toPrefix) && output.write(`-- WITH TABLE NAME CHANGE TO ${toIndex} --
`);
if (dropTable) {
output.write(`DROP TABLE IF EXISTS ${toIndex};
`);
}
const describe = await conn.query(`DESCRIBE ${index2};`);
const types = describe.reduce((ac, row) => {
ac[row["Field"]] = row["Type"];
if (row["Type"] === "text" && row["Properties"].indexOf("stored") === -1) {
output.write(`-- WARNING: field ${row["Field"]} NOT stored, so not included in output --
`);
}
return ac;
}, {});
const allowsFields = describe.filter((row) => !["tokencount"].includes(row["Type"])).map((row) => row["Field"]);
const allFields = describe.map((row) => row["Field"]);
const createTableString = (await conn.query(`SHOW CREATE TABLE ${index2};`))[0]["Create Table"].replace(RegExp(`CREATE[\\s\\t]+TABLE[\\s\\t]+${index2}`, "i"), `CREATE TABLE ${toIndex}`);
let createTableFiltered = createTableString.split(/\r?\n/).filter((row) => !allFields.includes(row.split(/\s+/)[0]) || allowsFields.includes(row.split(/\s+/)[0])).join("\n");
createTableFiltered = await pathReplacer(createTableFiltered, path__namespace.resolve(filesPath, `./${toIndex}`), toIndex);
output.write(createTableFiltered + ";\n");
if (limit !== 0) {
let count = 0, lastId = 0, next = true;
while (next) {
let i = 0, rows = [];
addLocks && await conn.query(`LOCK TABLES ${index2} WRITE;`);
addFreeze && await conn.query(`FREEZE ${index2}`);
await new Promise((resolve) => {
const stream2 = conn.queryStream(`SELECT *
FROM ${index2}
WHERE id > ${lastId}
ORDER BY id ASC
LIMIT ${chunk}
OPTION max_matches =
${chunk};`);
stream2.on("error", (err) => {
console.error(err);
process.exit(0);
}).on("data", (row) => {
Object.keys(row).forEach((key) => {
if (!allowsFields.includes(key)) {
delete row[key];
}
});
i === 0 && startChunk(Object.keys(row));
if (!limit || count < limit) {
rows.push(`(${Object.entries(row).map(([key, value]) => types[key] === "mva" ? `(${value})` : conn.escape(value)).join(",")})`);
lastId = row["id"];
i++;
count++;
}
if (limit && limit === count) {
next = false;
}
}).on("end", async () => {
if (!rows.length) {
next = false;
} else {
endChunk(rows);
}
addFreeze && await conn.query(`UNFREEZE ${index2}`);
addLocks && await conn.query(`UNLOCK TABLES;`);
resolve(true);
});
});
}
output.write(`-- COUNT: ${count} --
`);
}
output.write(`-- END DUMP ${index2} --
`);
};
let tablesList = [index];
if (tables) {
if (Array.isArray(tables)) {
tablesList = tables;
} else {
tablesList = tables.split(",");
}
}
if (isAll) {
tablesList = (await conn.query(`SHOW TABLES;`)).map((row) => row["Index"]);
}
const stream$1 = createStream("dump.sql");
for (const current of tablesList) {
dryRun && process$1.stdout.write(`${current}`);
await dumpTable(current, stream$1);
dryRun && process$1.stdout.write(chalk__default["default"].green(" done\n"));
}
stream$1.write(`
`);
const interval = Math.round((perf_hooks.performance.now() - start) / 10) / 100;
stream$1.write(`-- TIME: ${interval}s --
`);
stream$1.end();
await new Promise((resolve) => setTimeout(resolve, 100));
for (const { file, path: path2 } of filesToCopy) {
await promisifyStream(file, path2);
}
if ((isAll || addConfig) && filesToCopy.length) {
const base = dataDir || path__namespace.dirname(filesToCopy[0].file);
if (base) {
const manticoreJson = path__namespace.join(base, "..", "manticore.json");
try {
dryRun && process$1.stdout.write(chalk__default["default"].green(`manticore.json
`));
await promisifyStream(manticoreJson, path__namespace.basename(manticoreJson));
} catch (e) {
}
}
}
await new Promise((resolve) => setTimeout(resolve, 100));
dryRun && process$1.stdout.write(`
`);
dryRun && process$1.stdout.write(`TIME: ${interval}s
`);
await arch.finalize();
await conn.end();
dryRun && process$1.stdout.write(chalk__default["default"].yellow("----- End dry run -----\n\n"));
})();