knex-schema-inspector
Version:
Utility for extracting information about existing DB schema
288 lines (287 loc) • 21.8 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
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 = void 0;
var strip_quotes_1 = require("../utils/strip-quotes");
/**
* Converts Postgres default value to JS
* Eg `'example'::character varying` => `example`
*/
function parseDefaultValue(value) {
if (value === null)
return null;
if (value.startsWith('nextval('))
return value;
value = value.split('::')[0];
if (value.trim().toLowerCase() === 'null')
return null;
return (0, strip_quotes_1.stripQuotes)(value);
}
exports.parseDefaultValue = parseDefaultValue;
var Postgres = /** @class */ (function () {
function Postgres(knex) {
this.knex = knex;
var config = knex.client.config;
if (!config.searchPath) {
this.schema = 'public';
this.explodedSchema = [this.schema];
}
else if (typeof config.searchPath === 'string') {
this.schema = config.searchPath;
this.explodedSchema = [config.searchPath];
}
else {
this.schema = config.searchPath[0];
this.explodedSchema = config.searchPath;
}
}
// Postgres specific
// ===============================================================================================
/**
* Set the schema to be used in other methods
*/
Postgres.prototype.withSchema = function (schema) {
this.schema = schema;
this.explodedSchema = [this.schema];
return this;
};
// Tables
// ===============================================================================================
/**
* List all existing tables in the current schema/database
*/
Postgres.prototype.tables = function () {
return __awaiter(this, void 0, void 0, function () {
var schemaIn, result;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
schemaIn = this.explodedSchema.map(function (schemaName) { return "".concat(_this.knex.raw('?', [schemaName]), "::regnamespace"); });
return [4 /*yield*/, this.knex.raw("\n SELECT\n rel.relname AS name\n FROM\n pg_class rel\n WHERE\n rel.relnamespace IN (".concat(schemaIn, ")\n AND rel.relkind = 'r'\n ORDER BY rel.relname\n "))];
case 1:
result = _a.sent();
return [2 /*return*/, result.rows.map(function (row) { return row.name; })];
}
});
});
};
Postgres.prototype.tableInfo = function (table) {
return __awaiter(this, void 0, void 0, function () {
var schemaIn, bindings, result;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
schemaIn = this.explodedSchema.map(function (schemaName) { return "".concat(_this.knex.raw('?', [schemaName]), "::regnamespace"); });
bindings = [];
if (table)
bindings.push(table);
return [4 /*yield*/, this.knex.raw("\n SELECT\n rel.relnamespace::regnamespace::text AS schema,\n rel.relname AS name,\n des.description AS comment\n FROM\n pg_class rel\n LEFT JOIN pg_description des ON rel.oid = des.objoid AND des.objsubid = 0\n WHERE\n rel.relnamespace IN (".concat(schemaIn, ")\n ").concat(table ? 'AND rel.relname = ?' : '', "\n AND rel.relkind = 'r'\n ORDER BY rel.relname\n "), bindings)];
case 1:
result = _a.sent();
if (table)
return [2 /*return*/, result.rows[0]];
return [2 /*return*/, result.rows];
}
});
});
};
/**
* Check if a table exists in the current schema/database
*/
Postgres.prototype.hasTable = function (table) {
return __awaiter(this, void 0, void 0, function () {
var schemaIn, result;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
schemaIn = this.explodedSchema.map(function (schemaName) { return "".concat(_this.knex.raw('?', [schemaName]), "::regnamespace"); });
return [4 /*yield*/, this.knex.raw("\n SELECT\n rel.relname AS name\n FROM\n pg_class rel\n WHERE\n rel.relnamespace IN (".concat(schemaIn, ")\n AND rel.relkind = 'r'\n AND rel.relname = ?\n ORDER BY rel.relname\n "), [table])];
case 1:
result = _a.sent();
return [2 /*return*/, result.rows.length > 0];
}
});
});
};
// Columns
// ===============================================================================================
/**
* Get all the available columns in the current schema/database. Can be filtered to a specific table
*/
Postgres.prototype.columns = function (table) {
return __awaiter(this, void 0, void 0, function () {
var bindings, schemaIn, result;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
bindings = [];
if (table)
bindings.push(table);
schemaIn = this.explodedSchema.map(function (schemaName) { return "".concat(_this.knex.raw('?', [schemaName]), "::regnamespace"); });
return [4 /*yield*/, this.knex.raw("\n SELECT\n att.attname AS column,\n rel.relname AS table\n FROM\n pg_attribute att\n LEFT JOIN pg_class rel ON att.attrelid = rel.oid\n WHERE\n rel.relnamespace IN (".concat(schemaIn, ")\n ").concat(table ? 'AND rel.relname = ?' : '', "\n AND rel.relkind = 'r'\n AND att.attnum > 0\n AND NOT att.attisdropped;\n "), bindings)];
case 1:
result = _a.sent();
return [2 /*return*/, result.rows];
}
});
});
};
Postgres.prototype.columnInfo = function (table, column) {
var _a, _b, _c, _d, _e;
return __awaiter(this, void 0, void 0, function () {
var knex, bindings, schemaIn, versionResponse, majorVersion, generationSelect, _f, columns, constraints, parsedColumms;
var _this = this;
return __generator(this, function (_g) {
switch (_g.label) {
case 0:
knex = this.knex;
bindings = [];
if (table)
bindings.push(table);
if (column)
bindings.push(column);
schemaIn = this.explodedSchema.map(function (schemaName) { return "".concat(_this.knex.raw('?', [schemaName]), "::regnamespace"); });
return [4 /*yield*/, this.knex.raw("SHOW server_version")];
case 1:
versionResponse = _g.sent();
majorVersion = (_e = (_d = (_c = (_b = (_a = versionResponse.rows) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.server_version) === null || _c === void 0 ? void 0 : _c.split('.')) === null || _d === void 0 ? void 0 : _d[0]) !== null && _e !== void 0 ? _e : 10;
generationSelect = "\n NULL AS generation_expression,\n pg_get_expr(ad.adbin, ad.adrelid) AS default_value,\n FALSE AS is_generated,\n ";
if (Number(majorVersion) >= 12) {
generationSelect = "\n CASE WHEN att.attgenerated = 's' THEN pg_get_expr(ad.adbin, ad.adrelid) ELSE null END AS generation_expression,\n CASE WHEN att.attgenerated = '' THEN pg_get_expr(ad.adbin, ad.adrelid) ELSE null END AS default_value,\n att.attgenerated = 's' AS is_generated,\n ";
}
return [4 /*yield*/, Promise.all([
knex.raw("\n SELECT\n \tatt.attname AS name,\n rel.relname AS table,\n rel.relnamespace::regnamespace::text as schema,\n att.atttypid::regtype::text AS data_type,\n NOT att.attnotnull AS is_nullable,\n ".concat(generationSelect, "\n CASE\n WHEN att.atttypid IN (1042, 1043) THEN (att.atttypmod - 4)::int4\n WHEN att.atttypid IN (1560, 1562) THEN (att.atttypmod)::int4\n ELSE NULL\n END AS max_length,\n des.description AS comment,\n CASE att.atttypid\n WHEN 21 THEN 16\n WHEN 23 THEN 32\n WHEN 20 THEN 64\n WHEN 1700 THEN\n CASE WHEN atttypmod = -1 THEN NULL\n ELSE (((atttypmod - 4) >> 16) & 65535)::int4\n END\n WHEN 700 THEN 24\n WHEN 701 THEN 53\n ELSE NULL\n END AS numeric_precision,\n CASE\n WHEN atttypid IN (21, 23, 20) THEN 0\n WHEN atttypid = 1700 THEN\n CASE\n WHEN atttypmod = -1 THEN NULL\n ELSE ((atttypmod - 4) & 65535)::int4\n END\n ELSE null\n END AS numeric_scale\n FROM\n pg_attribute att\n LEFT JOIN pg_class rel ON att.attrelid = rel.oid\n LEFT JOIN pg_attrdef ad ON (att.attrelid, att.attnum) = (ad.adrelid, ad.adnum)\n LEFT JOIN pg_description des ON (att.attrelid, att.attnum) = (des.objoid, des.objsubid)\n WHERE\n rel.relnamespace IN (").concat(schemaIn, ")\n ").concat(table ? 'AND rel.relname = ?' : '', "\n ").concat(column ? 'AND att.attname = ?' : '', "\n AND rel.relkind = 'r'\n AND att.attnum > 0\n AND NOT att.attisdropped\n ORDER BY rel.relname, att.attnum;\n "), bindings),
knex.raw("\n SELECT\n con.contype AS type,\n rel.relname AS table,\n att.attname AS column,\n frel.relnamespace::regnamespace::text AS foreign_key_schema,\n frel.relname AS foreign_key_table,\n fatt.attname AS foreign_key_column,\n CASE\n WHEN con.contype = 'p' THEN pg_get_serial_sequence(att.attrelid::regclass::text, att.attname) != ''\n ELSE NULL\n END AS has_auto_increment\n FROM\n pg_constraint con\n LEFT JOIN pg_class rel ON con.conrelid = rel.oid\n LEFT JOIN pg_class frel ON con.confrelid = frel.oid\n LEFT JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = con.conkey[1]\n LEFT JOIN pg_attribute fatt ON fatt.attrelid = con.confrelid AND fatt.attnum = con.confkey[1]\n WHERE con.connamespace IN (".concat(schemaIn, ")\n AND array_length(con.conkey, 1) <= 1\n AND (con.confkey IS NULL OR array_length(con.confkey, 1) = 1)\n ").concat(table ? 'AND rel.relname = ?' : '', "\n ").concat(column ? 'AND att.attname = ?' : '', "\n "), bindings),
])];
case 2:
_f = _g.sent(), columns = _f[0], constraints = _f[1];
parsedColumms = columns.rows.map(function (col) {
var _a, _b, _c;
var constraintsForColumn = constraints.rows.filter(function (constraint) {
return constraint.table === col.table && constraint.column === col.name;
});
var foreignKeyConstraint = constraintsForColumn.find(function (constraint) { return constraint.type === 'f'; });
return __assign(__assign({}, col), { is_unique: constraintsForColumn.some(function (constraint) {
return ['u', 'p'].includes(constraint.type);
}), is_primary_key: constraintsForColumn.some(function (constraint) { return constraint.type === 'p'; }), has_auto_increment: constraintsForColumn.some(function (constraint) { return constraint.has_auto_increment; }), default_value: parseDefaultValue(col.default_value), foreign_key_schema: (_a = foreignKeyConstraint === null || foreignKeyConstraint === void 0 ? void 0 : foreignKeyConstraint.foreign_key_schema) !== null && _a !== void 0 ? _a : null, foreign_key_table: (_b = foreignKeyConstraint === null || foreignKeyConstraint === void 0 ? void 0 : foreignKeyConstraint.foreign_key_table) !== null && _b !== void 0 ? _b : null, foreign_key_column: (_c = foreignKeyConstraint === null || foreignKeyConstraint === void 0 ? void 0 : foreignKeyConstraint.foreign_key_column) !== null && _c !== void 0 ? _c : null });
});
if (table && column)
return [2 /*return*/, parsedColumms[0]];
return [2 /*return*/, parsedColumms];
}
});
});
};
/**
* Check if the given table contains the given column
*/
Postgres.prototype.hasColumn = function (table, column) {
return __awaiter(this, void 0, void 0, function () {
var schemaIn, result;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
schemaIn = this.explodedSchema.map(function (schemaName) { return "".concat(_this.knex.raw('?', [schemaName]), "::regnamespace"); });
return [4 /*yield*/, this.knex.raw("\n SELECT\n att.attname AS column,\n rel.relname AS table\n FROM\n pg_attribute att\n LEFT JOIN pg_class rel ON att.attrelid = rel.oid\n WHERE\n rel.relnamespace IN (".concat(schemaIn, ")\n AND rel.relname = ?\n AND att.attname = ?\n AND rel.relkind = 'r'\n AND att.attnum > 0\n AND NOT att.attisdropped;\n "), [table, column])];
case 1:
result = _a.sent();
return [2 /*return*/, result.rows];
}
});
});
};
/**
* Get the primary key column for the given table
*/
Postgres.prototype.primary = function (table) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function () {
var schemaIn, result;
var _this = this;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
schemaIn = this.explodedSchema.map(function (schemaName) { return "".concat(_this.knex.raw('?', [schemaName]), "::regnamespace"); });
return [4 /*yield*/, this.knex.raw("\n SELECT\n att.attname AS column\n FROM\n pg_constraint con\n LEFT JOIN pg_class rel ON con.conrelid = rel.oid\n LEFT JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = con.conkey[1]\n WHERE con.connamespace IN (".concat(schemaIn, ")\n AND con.contype = 'p'\n AND array_length(con.conkey, 1) <= 1\n AND rel.relname = ?\n "), [table])];
case 1:
result = _d.sent();
return [2 /*return*/, (_c = (_b = (_a = result.rows) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.column) !== null && _c !== void 0 ? _c : null];
}
});
});
};
// Foreign Keys
// ===============================================================================================
Postgres.prototype.foreignKeys = function (table) {
return __awaiter(this, void 0, void 0, function () {
var schemaIn, bindings, result;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
schemaIn = this.explodedSchema.map(function (schemaName) { return "".concat(_this.knex.raw('?', [schemaName]), "::regnamespace"); });
bindings = [];
if (table)
bindings.push(table);
return [4 /*yield*/, this.knex.raw("\n SELECT\n con.conname AS constraint_name,\n rel.relname AS table,\n att.attname AS column,\n frel.relnamespace::regnamespace::text AS foreign_key_schema,\n frel.relname AS foreign_key_table,\n fatt.attname AS foreign_key_column,\n CASE con.confupdtype\n WHEN 'r' THEN\n 'RESTRICT'\n WHEN 'c' THEN\n 'CASCADE'\n WHEN 'n' THEN\n 'SET NULL'\n WHEN 'd' THEN\n 'SET DEFAULT'\n WHEN 'a' THEN\n 'NO ACTION'\n ELSE\n NULL\n END AS on_update,\n CASE con.confdeltype\n WHEN 'r' THEN\n 'RESTRICT'\n WHEN 'c' THEN\n 'CASCADE'\n WHEN 'n' THEN\n 'SET NULL'\n WHEN 'd' THEN\n 'SET DEFAULT'\n WHEN 'a' THEN\n 'NO ACTION'\n ELSE\n NULL\n END AS on_delete\n FROM\n pg_constraint con\n LEFT JOIN pg_class rel ON con.conrelid = rel.oid\n LEFT JOIN pg_class frel ON con.confrelid = frel.oid\n LEFT JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = con.conkey[1]\n LEFT JOIN pg_attribute fatt ON fatt.attrelid = con.confrelid AND fatt.attnum = con.confkey[1]\n WHERE con.connamespace IN (".concat(schemaIn, ")\n AND array_length(con.conkey, 1) <= 1\n AND (con.confkey IS NULL OR array_length(con.confkey, 1) = 1)\n AND con.contype = 'f'\n ").concat(table ? 'AND rel.relname = ?' : '', "\n "), bindings)];
case 1:
result = _a.sent();
return [2 /*return*/, result.rows];
}
});
});
};
return Postgres;
}());
exports.default = Postgres;