UNPKG

indexdump

Version:
280 lines (273 loc) 12.5 kB
#!/usr/bin/env node 'use strict'; 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")); })();