UNPKG

@onn-software/ddl-to-gql

Version:

Convert a SQL DDL to a GraphQL implementation with all relations.

203 lines (202 loc) 8.35 kB
"use strict"; 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;