knex-schema-inspector
Version:
Utility for extracting information about existing DB schema
323 lines (322 loc) • 16.2 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseDefaultValue = exports.rawColumnToColumn = void 0;
var strip_quotes_1 = require("../utils/strip-quotes");
function rawColumnToColumn(rawColumn) {
var _a;
var dataType = rawColumn.COLUMN_TYPE.replace(/\(.*?\)/, '');
if (rawColumn.COLUMN_TYPE.startsWith('tinyint(1)')) {
dataType = 'boolean';
}
return {
name: rawColumn.COLUMN_NAME,
table: rawColumn.TABLE_NAME,
data_type: dataType,
default_value: parseDefaultValue(rawColumn.COLUMN_DEFAULT),
generation_expression: rawColumn.GENERATION_EXPRESSION || null,
max_length: rawColumn.CHARACTER_MAXIMUM_LENGTH,
numeric_precision: rawColumn.NUMERIC_PRECISION,
numeric_scale: rawColumn.NUMERIC_SCALE,
is_generated: !!((_a = rawColumn.EXTRA) === null || _a === void 0 ? void 0 : _a.endsWith('GENERATED')),
is_nullable: rawColumn.IS_NULLABLE === 'YES',
is_unique: rawColumn.COLUMN_KEY === 'UNI',
is_primary_key: rawColumn.CONSTRAINT_NAME === 'PRIMARY' || rawColumn.COLUMN_KEY === 'PRI',
has_auto_increment: rawColumn.EXTRA === 'auto_increment',
foreign_key_column: rawColumn.REFERENCED_COLUMN_NAME,
foreign_key_table: rawColumn.REFERENCED_TABLE_NAME,
comment: rawColumn.COLUMN_COMMENT,
};
}
exports.rawColumnToColumn = rawColumnToColumn;
function parseDefaultValue(value) {
if (value === null || value.trim().toLowerCase() === 'null')
return null;
return (0, strip_quotes_1.stripQuotes)(value);
}
exports.parseDefaultValue = parseDefaultValue;
var MySQL = /** @class */ (function () {
function MySQL(knex) {
this.knex = knex;
}
// Tables
// ===============================================================================================
/**
* List all existing tables in the current schema/database
*/
MySQL.prototype.tables = function () {
return __awaiter(this, void 0, void 0, function () {
var records;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.knex
.select('TABLE_NAME')
.from('INFORMATION_SCHEMA.TABLES')
.where({
TABLE_TYPE: 'BASE TABLE',
TABLE_SCHEMA: this.knex.client.database(),
})];
case 1:
records = _a.sent();
return [2 /*return*/, records.map(function (_a) {
var TABLE_NAME = _a.TABLE_NAME;
return TABLE_NAME;
})];
}
});
});
};
MySQL.prototype.tableInfo = function (table) {
return __awaiter(this, void 0, void 0, function () {
var query, rawTable, records;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
query = this.knex
.select('TABLE_NAME', 'ENGINE', 'TABLE_SCHEMA', 'TABLE_COLLATION', 'TABLE_COMMENT')
.from('information_schema.tables')
.where({
table_schema: this.knex.client.database(),
table_type: 'BASE TABLE',
});
if (!table) return [3 /*break*/, 2];
return [4 /*yield*/, query
.andWhere({ table_name: table })
.first()];
case 1:
rawTable = _a.sent();
return [2 /*return*/, {
name: rawTable.TABLE_NAME,
schema: rawTable.TABLE_SCHEMA,
comment: rawTable.TABLE_COMMENT,
collation: rawTable.TABLE_COLLATION,
engine: rawTable.ENGINE,
}];
case 2: return [4 /*yield*/, query];
case 3:
records = _a.sent();
return [2 /*return*/, records.map(function (rawTable) {
return {
name: rawTable.TABLE_NAME,
schema: rawTable.TABLE_SCHEMA,
comment: rawTable.TABLE_COMMENT,
collation: rawTable.TABLE_COLLATION,
engine: rawTable.ENGINE,
};
})];
}
});
});
};
/**
* Check if a table exists in the current schema/database
*/
MySQL.prototype.hasTable = function (table) {
return __awaiter(this, void 0, void 0, function () {
var result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.knex
.count({ count: '*' })
.from('information_schema.tables')
.where({
table_schema: this.knex.client.database(),
table_name: table,
})
.first()];
case 1:
result = _a.sent();
return [2 /*return*/, (result && result.count === 1) || false];
}
});
});
};
// Columns
// ===============================================================================================
/**
* Get all the available columns in the current schema/database. Can be filtered to a specific table
*/
MySQL.prototype.columns = function (table) {
return __awaiter(this, void 0, void 0, function () {
var query, records;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
query = this.knex
.select('TABLE_NAME', 'COLUMN_NAME')
.from('INFORMATION_SCHEMA.COLUMNS')
.where({ TABLE_SCHEMA: this.knex.client.database() });
if (table) {
query.andWhere({ TABLE_NAME: table });
}
return [4 /*yield*/, query];
case 1:
records = _a.sent();
return [2 /*return*/, records.map(function (_a) {
var TABLE_NAME = _a.TABLE_NAME, COLUMN_NAME = _a.COLUMN_NAME;
return ({
table: TABLE_NAME,
column: COLUMN_NAME,
});
})];
}
});
});
};
MySQL.prototype.columnInfo = function (table, column) {
return __awaiter(this, void 0, void 0, function () {
var query, rawColumn, records;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
query = this.knex
.select('c.TABLE_NAME', 'c.COLUMN_NAME', 'c.COLUMN_DEFAULT', 'c.COLUMN_TYPE', 'c.CHARACTER_MAXIMUM_LENGTH', 'c.IS_NULLABLE', 'c.COLUMN_KEY', 'c.EXTRA', 'c.COLLATION_NAME', 'c.COLUMN_COMMENT', 'c.NUMERIC_PRECISION', 'c.NUMERIC_SCALE', 'c.GENERATION_EXPRESSION', 'fk.REFERENCED_TABLE_NAME', 'fk.REFERENCED_COLUMN_NAME', 'fk.CONSTRAINT_NAME', 'rc.UPDATE_RULE', 'rc.DELETE_RULE', 'rc.MATCH_OPTION')
.from('INFORMATION_SCHEMA.COLUMNS as c')
.leftJoin('INFORMATION_SCHEMA.KEY_COLUMN_USAGE as fk', function () {
this.on('c.TABLE_NAME', '=', 'fk.TABLE_NAME')
.andOn('fk.COLUMN_NAME', '=', 'c.COLUMN_NAME')
.andOn('fk.CONSTRAINT_SCHEMA', '=', 'c.TABLE_SCHEMA');
})
.leftJoin('INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS as rc', function () {
this.on('rc.TABLE_NAME', '=', 'fk.TABLE_NAME')
.andOn('rc.CONSTRAINT_NAME', '=', 'fk.CONSTRAINT_NAME')
.andOn('rc.CONSTRAINT_SCHEMA', '=', 'fk.CONSTRAINT_SCHEMA');
})
.where({
'c.TABLE_SCHEMA': this.knex.client.database(),
});
if (table) {
query.andWhere({ 'c.TABLE_NAME': table });
}
if (!column) return [3 /*break*/, 2];
return [4 /*yield*/, query
.andWhere({ 'c.column_name': column })
.first()];
case 1:
rawColumn = _a.sent();
return [2 /*return*/, rawColumnToColumn(rawColumn)];
case 2: return [4 /*yield*/, query];
case 3:
records = _a.sent();
return [2 /*return*/, records
.map(rawColumnToColumn)
.sort(function (column) { return +!column.foreign_key_column; })
.filter(function (column, index, records) {
var first = records.findIndex(function (_column) {
return column.name === _column.name && column.table === _column.table;
});
return first === index;
})];
}
});
});
};
/**
* Check if a table exists in the current schema/database
*/
MySQL.prototype.hasColumn = function (table, column) {
return __awaiter(this, void 0, void 0, function () {
var result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.knex
.count('*', { as: 'count' })
.from('information_schema.columns')
.where({
table_schema: this.knex.client.database(),
table_name: table,
column_name: column,
})
.first()];
case 1:
result = _a.sent();
return [2 /*return*/, !!(result && result.count)];
}
});
});
};
/**
* Get the primary key column for the given table
*/
MySQL.prototype.primary = function (table) {
return __awaiter(this, void 0, void 0, function () {
var results;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.knex.raw("SHOW KEYS FROM ?? WHERE Key_name = 'PRIMARY'", table)];
case 1:
results = _a.sent();
if (results && results.length && results[0].length) {
return [2 /*return*/, results[0][0]['Column_name']];
}
return [2 /*return*/, null];
}
});
});
};
// Foreign Keys
// ===============================================================================================
MySQL.prototype.foreignKeys = function (table) {
return __awaiter(this, void 0, void 0, function () {
var query, result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
query = this.knex
.select("rc.TABLE_NAME AS table", "kcu.COLUMN_NAME AS column", "rc.REFERENCED_TABLE_NAME AS foreign_key_table", "kcu.REFERENCED_COLUMN_NAME AS foreign_key_column", "rc.CONSTRAINT_NAME AS constraint_name", "rc.UPDATE_RULE AS on_update", "rc.DELETE_RULE AS on_delete")
.from("information_schema.referential_constraints AS rc")
.leftJoin("information_schema.key_column_usage AS kcu ", function () {
this.on("rc.CONSTRAINT_NAME", "=", "kcu.CONSTRAINT_NAME").andOn("kcu.CONSTRAINT_SCHEMA", "=", "rc.CONSTRAINT_SCHEMA");
})
.where({
'rc.CONSTRAINT_SCHEMA': this.knex.client.database(),
});
if (table) {
query.andWhere({ 'rc.TABLE_NAME': table });
}
return [4 /*yield*/, query];
case 1:
result = _a.sent();
return [2 /*return*/, result];
}
});
});
};
return MySQL;
}());
exports.default = MySQL;