@onn-software/ddl-to-gql
Version:
Convert a SQL DDL to a GraphQL implementation with all relations.
203 lines (202 loc) • 8.35 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DdlInterpreter = void 0;
const util_1 = require("../util");
const ddl_interpreter_templates_1 = require("./ddl-interpreter.templates");
class DdlInterpreter {
execute(ddl, overrides) {
const splittedSchema = ddl.split(/create table /i).slice(1);
const tableDefs = this.createTwoWayRelations(splittedSchema.map((part) => {
const { tableName, lines } = this.extractTableContents(part);
const singleLinesPartial = this.joinMultilineStatementsIntoSingleLine(lines);
const singleLines = this.splitSingleLineMutipleStatments(singleLinesPartial);
const { fields, meta } = this.splitIntoFieldsAndMeta(singleLines);
const columns = this.parseToColumns(tableName, fields, overrides);
const { primaryKey, relations } = this.parseMeta(tableName, fields, meta, overrides);
const columnMap = (0, util_1.associateBy)(columns, (f) => f.key);
if (primaryKey) {
columnMap[primaryKey].unique = true;
}
return {
tableName,
columns,
relations,
};
}));
tableDefs.sort((l, r) => l.tableName.localeCompare(r.tableName));
return tableDefs;
}
parseToColumns(tableName, fields, overrides) {
if (fields.length === 0) {
throw new Error(`Table ${tableName} has no fields`);
}
return fields
.map((fields) => fields.replaceAll(', ', ','))
.map((field) => {
const upperField = field.toUpperCase();
const parts = field.split(' ').filter((part) => part.trim().length > 0);
const [sqlKey, sqlType] = parts.map((part) => part.trim());
const { key, requiresMapping } = this.keyFromSqlKey(tableName, sqlKey, overrides);
const override = this.keyOverrite(tableName, key);
const res = {
sqlType,
sqlKey: requiresMapping ? sqlKey : undefined,
key,
unique: upperField.indexOf('PRIMARY KEY') >= 0 || upperField.indexOf('UNIQUE') >= 0,
type: ddl_interpreter_templates_1.typeMap[sqlType.split('(')[0].toUpperCase()] ?? 'string',
nullable: upperField.indexOf('NOT NULL') < 0,
};
return res;
});
}
keyFromSqlKey(tableName, sqlKey, overrides) {
const override = overrides[tableName] ? overrides[tableName][sqlKey] : undefined;
let key = (override ?? sqlKey).replaceAll('`', '');
let requiresMapping = !!override;
if (ddl_interpreter_templates_1.reservedNames.indexOf(key.toUpperCase()) >= 0 ||
!/^[a-zA-Z]/.test(key) ||
/[^a-zA-Z0-9_]/.test(key)) {
key = '_' + key.replaceAll(/[^a-zA-Z0-9]/g, '_');
requiresMapping = true;
}
return { key, requiresMapping };
}
extractTableContents(part) {
const tableName = part
.substring(0, part.indexOf(' '))
.substring(0, part.indexOf('('))
.replaceAll('\r', '')
.split('\n')[0]
.replaceAll('`', '')
.replaceAll('.', '__');
let count = 1;
let index = part.indexOf('(') + 1;
const maxIndex = part.length;
while (count > 0 && index <= maxIndex) {
if (part[index] === ')')
count--;
if (part[index] === '(')
count++;
index++;
}
const lines = part
.substring(part.indexOf('(') + 1, index - 1)
.replaceAll('\r', '')
.split('\n')
.filter((line) => line.trim().length > 0);
return { tableName, lines };
}
splitSingleLineMutipleStatments(lines) {
const res = [];
let braceCount = 0;
lines.forEach((line) => {
if (line.indexOf(',') < 0) {
res.push(line);
}
else {
let builder = '';
for (let i = 0; i < line.length; i++) {
if (line[i] === '(')
braceCount++;
if (line[i] === ')')
braceCount--;
if (braceCount === 0 && line[i] === ',') {
res.push(builder.trim());
builder = '';
}
else {
builder = builder + line[i];
}
}
res.push(builder.trim());
}
});
return res;
}
joinMultilineStatementsIntoSingleLine(lines) {
const singleLines = [];
let currentLine = '';
lines.forEach((l) => {
const line = l.trim();
currentLine = `${currentLine}${line}`;
if (l.endsWith(',')) {
singleLines.push(currentLine.substring(0, currentLine.length - 1));
currentLine = '';
}
else {
currentLine = `${currentLine} `;
}
});
if (currentLine.length > 1) {
singleLines.push(currentLine.trim());
}
return singleLines;
}
splitIntoFieldsAndMeta(singleLines) {
const splitIndex = singleLines.findIndex((value) => ddl_interpreter_templates_1.reservedNames.indexOf(value.split(' ')[0].toUpperCase()) >= 0);
if (splitIndex < 0) {
return { fields: singleLines, meta: [] };
}
const fields = singleLines.slice(0, splitIndex);
const meta = singleLines.slice(splitIndex);
return { fields, meta };
}
parseMeta(tableName, fields, meta, overrides) {
let primaryKey = '';
const relations = [];
meta
.map((met) => met.trim())
.forEach((met) => {
const upperMeta = met.toUpperCase();
if (upperMeta.startsWith('PRIMARY KEY (')) {
const keyJoined = met.substring(met.indexOf('(') + 1, met.indexOf(')'));
const keys = keyJoined.split(',').map((k) => this.keyFromSqlKey(tableName, k.trim(), overrides).key);
if (keys.length === 1) {
primaryKey = keys[0];
}
return;
}
const foreignKeyKeywordIndex = upperMeta.indexOf('FOREIGN KEY');
if (foreignKeyKeywordIndex >= 0) {
const relationPart = met.substring(foreignKeyKeywordIndex + 10);
const myKey = this.keyFromSqlKey(tableName, relationPart.substring(relationPart.indexOf('(') + 1, relationPart.indexOf(')')), overrides).key;
const [foreignName, foreignKeyDirty] = relationPart
.split(/references/i)[1]
.trim()
.split(' ');
const foreignTable = foreignName.replaceAll('`', '').replaceAll('.', '__');
const foreignKey = this.keyFromSqlKey(foreignTable, foreignKeyDirty.substring(foreignKeyDirty.indexOf('(') + 1, foreignKeyDirty.indexOf(')')), overrides).key;
relations.push({
from: { table: tableName, key: myKey },
to: { table: foreignTable, key: foreignKey },
many: false,
enabled: true,
type: 'foreignKey',
nullable: false,
});
return;
}
});
return { primaryKey, relations };
}
createTwoWayRelations(tables) {
const recordTables = {};
tables.forEach((t) => (recordTables[t.tableName] = JSON.parse(JSON.stringify(t))));
tables.forEach((table) => {
table.relations.forEach((rel) => {
recordTables[rel.to.table].relations.push({
to: rel.from,
from: rel.to,
many: true,
enabled: true,
type: 'foreignKey',
nullable: false,
});
});
});
return Object.values(recordTables);
}
keyOverrite(tableName, key) {
}
}
exports.DdlInterpreter = DdlInterpreter;