diff --git a/frontend/packages/db-structure/biome.jsonc b/frontend/packages/db-structure/biome.jsonc index e6843d979..76bc7fe03 100644 --- a/frontend/packages/db-structure/biome.jsonc +++ b/frontend/packages/db-structure/biome.jsonc @@ -1,3 +1,8 @@ { - "extends": ["../../packages/configs/biome.jsonc"] + "extends": ["../../packages/configs/biome.jsonc"], + "files": { + "ignore": [ + "src/parser/schemarb/parser.js" // Because it's generated + ] + } } diff --git a/frontend/packages/db-structure/package.json b/frontend/packages/db-structure/package.json index e006124cf..bb4aeb857 100644 --- a/frontend/packages/db-structure/package.json +++ b/frontend/packages/db-structure/package.json @@ -7,14 +7,19 @@ "lint:biome": "biome check .", "lint:tsc": "tsc --noEmit", "fmt": "conc -c auto pnpm:fmt:*", - "fmt:biome": "biome check --write --unsafe ." + "fmt:biome": "biome check --write --unsafe .", + "test": "vitest" }, "dependencies": { + "lodash.isequal": "^4.5.0", + "lodash.sortby": "^4.7.0", + "pluralize": "^8.0.0", "valibot": "^1.0.0-beta.5" }, "devDependencies": { "@biomejs/biome": "1.9.3", "@packages/configs": "workspace:*", - "typescript": "^5" + "typescript": "^5", + "vitest": "^2.1.4" } } diff --git a/frontend/packages/db-structure/src/parser/__snapshots__/index.test.ts.snap b/frontend/packages/db-structure/src/parser/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000..046d63315 --- /dev/null +++ b/frontend/packages/db-structure/src/parser/__snapshots__/index.test.ts.snap @@ -0,0 +1,41 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`parse > should parse schema.rb to JSON correctly 1`] = ` +{ + "relationships": {}, + "tables": { + "xero_users": { + "color": null, + "comment": null, + "fields": [ + { + "check": null, + "comment": null, + "default": null, + "increment": false, + "name": "id", + "notNull": false, + "primary": false, + "type": "varchar", + "unique": false, + }, + { + "check": null, + "comment": null, + "default": null, + "increment": false, + "name": "email_address", + "notNull": false, + "primary": false, + "type": "string", + "unique": false, + }, + ], + "indices": [], + "name": "xero_users", + "x": 0, + "y": 0, + }, + }, +} +`; diff --git a/frontend/packages/db-structure/src/parser/index.test.ts b/frontend/packages/db-structure/src/parser/index.test.ts new file mode 100644 index 000000000..e0ac89c40 --- /dev/null +++ b/frontend/packages/db-structure/src/parser/index.test.ts @@ -0,0 +1,16 @@ +import fs from 'node:fs' +import path from 'node:path' +import { describe, expect, it } from 'vitest' +import { parse } from '.' + +describe(parse, () => { + it('should parse schema.rb to JSON correctly', () => { + const schemaText = fs.readFileSync( + path.resolve(__dirname, './schemarb/input/schema1.in.rb'), + 'utf-8', + ) + + const result = parse(schemaText, 'schemarb') + expect(result).toMatchSnapshot() + }) +}) diff --git a/frontend/packages/db-structure/src/parser/index.ts b/frontend/packages/db-structure/src/parser/index.ts new file mode 100644 index 000000000..158fb77ef --- /dev/null +++ b/frontend/packages/db-structure/src/parser/index.ts @@ -0,0 +1,56 @@ +import type { DBStructure, Table } from 'src/schema' +import { schemaRbParser } from './schemarb' + +type SupportedFormat = 'schemarb' | 'postgres' + +// biome-ignore lint/suspicious/noExplicitAny: TODO: Generate types with pegjs +const convertToDBStructure = (data: any): DBStructure => { + return { + // biome-ignore lint/suspicious/noExplicitAny: TODO: Generate types with pegjs + tables: data.tables.reduce((acc: Record, table: any) => { + acc[table.name] = { + comment: null, + // biome-ignore lint/suspicious/noExplicitAny: TODO: Generate types with pegjs + fields: table.fields.map((field: any) => ({ + check: null, + comment: null, + default: null, + increment: false, + name: field.name, + notNull: false, + primary: false, + type: field.type.type_name, + unique: false, + })), + indices: [], + name: table.name, + x: 0, + y: 0, + color: null, + } + return acc + }, {}), + relationships: {}, + } +} + +// biome-ignore lint/suspicious/noExplicitAny: TODO: Generate types with pegjs +const selectParser = (format: SupportedFormat): any => { + switch (format) { + case 'schemarb': + return schemaRbParser + default: + throw new Error(`Unsupported format: ${format}`) + } +} + +export const parse = (str: string, format: SupportedFormat): DBStructure => { + try { + const parser = selectParser(format) + const parsedSchema = parser.parse(str) + const dbStructure = convertToDBStructure(parsedSchema) + return dbStructure + } catch (_error) { + throw new Error('Failed to parse schema') + } +} diff --git a/frontend/packages/db-structure/src/parser/schemarb/index.ts b/frontend/packages/db-structure/src/parser/schemarb/index.ts new file mode 100644 index 000000000..494965880 --- /dev/null +++ b/frontend/packages/db-structure/src/parser/schemarb/index.ts @@ -0,0 +1,3 @@ +import schemaRbParser from './parser' + +export { schemaRbParser } diff --git a/frontend/packages/db-structure/src/parser/schemarb/input/schema1.in.rb b/frontend/packages/db-structure/src/parser/schemarb/input/schema1.in.rb new file mode 100644 index 000000000..0c8390136 --- /dev/null +++ b/frontend/packages/db-structure/src/parser/schemarb/input/schema1.in.rb @@ -0,0 +1,3 @@ +create_table "xero_users", id: :uuid, default: -> { "uuid_generate_v4()" }, force: :cascade do |t| + t.string "email_address" +end diff --git a/frontend/packages/db-structure/src/parser/schemarb/parser.js b/frontend/packages/db-structure/src/parser/schemarb/parser.js new file mode 100644 index 000000000..bbe76f0e8 --- /dev/null +++ b/frontend/packages/db-structure/src/parser/schemarb/parser.js @@ -0,0 +1,2318 @@ +// @ts-nocheck +// @generated by Peggy 4.1.1. +// +// https://peggyjs.org/ + +"use strict"; + + +function peg$subclass(child, parent) { + function C() { this.constructor = child; } + C.prototype = parent.prototype; + child.prototype = new C(); +} + +function peg$SyntaxError(message, expected, found, location) { + var self = Error.call(this, message); + // istanbul ignore next Check is a necessary evil to support older environments + if (Object.setPrototypeOf) { + Object.setPrototypeOf(self, peg$SyntaxError.prototype); + } + self.expected = expected; + self.found = found; + self.location = location; + self.name = "SyntaxError"; + return self; +} + +peg$subclass(peg$SyntaxError, Error); + +function peg$padEnd(str, targetLength, padString) { + padString = padString || " "; + if (str.length > targetLength) { return str; } + targetLength -= str.length; + padString += padString.repeat(targetLength); + return str + padString.slice(0, targetLength); +} + +peg$SyntaxError.prototype.format = function(sources) { + var str = "Error: " + this.message; + if (this.location) { + var src = null; + var k; + for (k = 0; k < sources.length; k++) { + if (sources[k].source === this.location.source) { + src = sources[k].text.split(/\r\n|\n|\r/g); + break; + } + } + var s = this.location.start; + var offset_s = (this.location.source && (typeof this.location.source.offset === "function")) + ? this.location.source.offset(s) + : s; + var loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column; + if (src) { + var e = this.location.end; + var filler = peg$padEnd("", offset_s.line.toString().length, ' '); + var line = src[s.line - 1]; + var last = s.line === e.line ? e.column : line.length + 1; + var hatLen = (last - s.column) || 1; + str += "\n --> " + loc + "\n" + + filler + " |\n" + + offset_s.line + " | " + line + "\n" + + filler + " | " + peg$padEnd("", s.column - 1, ' ') + + peg$padEnd("", hatLen, "^"); + } else { + str += "\n at " + loc; + } + } + return str; +}; + +peg$SyntaxError.buildMessage = function(expected, found) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + class: function(expectation) { + var escapedParts = expectation.parts.map(function(part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + + return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]"; + }, + + any: function() { + return "any character"; + }, + + end: function() { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = expected.map(describeExpectation); + var i, j; + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; +}; + +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + + var peg$FAILED = {}; + var peg$source = options.grammarSource; + + var peg$startRuleFunctions = { schema: peg$parseschema }; + var peg$startRuleFunction = peg$parseschema; + + var peg$c0 = ","; + var peg$c1 = ":"; + var peg$c2 = "on_delete"; + var peg$c3 = "on_update"; + var peg$c4 = "add_index"; + var peg$c5 = "ActiveRecord::Schema.define"; + var peg$c6 = "create_table"; + var peg$c7 = ".index"; + var peg$c8 = ".references"; + var peg$c9 = "add_foreign_key"; + var peg$c10 = "column"; + var peg$c11 = "primary_key"; + var peg$c12 = "version"; + var peg$c13 = "do"; + var peg$c14 = "end"; + var peg$c15 = "=>"; + var peg$c16 = "->"; + var peg$c17 = "."; + var peg$c18 = "#"; + var peg$c19 = "'"; + var peg$c20 = "\""; + var peg$c21 = "|"; + var peg$c22 = "//"; + var peg$c23 = "\r\n"; + var peg$c24 = "\n"; + var peg$c25 = " "; + + var peg$r0 = /^[^"\n]/; + var peg$r1 = /^[^'\n]/; + var peg$r2 = /^[0-9]/i; + var peg$r3 = /^[a-z0-9_.]/i; + var peg$r4 = /^[^\t\r\n]/; + var peg$r5 = /^[^\n]/; + var peg$r6 = /^[ \t\r\n\r]/; + + var peg$e0 = peg$literalExpectation(",", false); + var peg$e1 = peg$literalExpectation(":", false); + var peg$e2 = peg$literalExpectation("on_delete", true); + var peg$e3 = peg$literalExpectation("on_update", true); + var peg$e4 = peg$otherExpectation("add index"); + var peg$e5 = peg$literalExpectation("add_index", false); + var peg$e6 = peg$otherExpectation("schema define"); + var peg$e7 = peg$literalExpectation("ActiveRecord::Schema.define", false); + var peg$e8 = peg$otherExpectation("create table"); + var peg$e9 = peg$literalExpectation("create_table", true); + var peg$e10 = peg$otherExpectation("do |t|"); + var peg$e11 = peg$otherExpectation("index"); + var peg$e12 = peg$literalExpectation(".index", false); + var peg$e13 = peg$otherExpectation("references"); + var peg$e14 = peg$literalExpectation(".references", false); + var peg$e15 = peg$otherExpectation("add foreign key"); + var peg$e16 = peg$literalExpectation("add_foreign_key", true); + var peg$e17 = peg$otherExpectation("column"); + var peg$e18 = peg$literalExpectation("column", false); + var peg$e19 = peg$otherExpectation("primary key"); + var peg$e20 = peg$literalExpectation("primary_key", false); + var peg$e21 = peg$literalExpectation("version", false); + var peg$e22 = peg$literalExpectation("do", false); + var peg$e23 = peg$literalExpectation("end", false); + var peg$e24 = peg$otherExpectation("lambda function"); + var peg$e25 = peg$literalExpectation("=>", false); + var peg$e26 = peg$literalExpectation("->", false); + var peg$e27 = peg$classExpectation(["\"", "\n"], true, false); + var peg$e28 = peg$classExpectation(["'", "\n"], true, false); + var peg$e29 = peg$literalExpectation(".", false); + var peg$e30 = peg$anyExpectation(); + var peg$e31 = peg$classExpectation([["0", "9"]], false, true); + var peg$e32 = peg$otherExpectation("letter, number or underscore"); + var peg$e33 = peg$classExpectation([["a", "z"], ["0", "9"], "_", "."], false, true); + var peg$e34 = peg$otherExpectation("comment line"); + var peg$e35 = peg$literalExpectation("#", false); + var peg$e36 = peg$otherExpectation("whatever"); + var peg$e37 = peg$classExpectation(["\t", "\r", "\n"], true, false); + var peg$e38 = peg$literalExpectation("'", false); + var peg$e39 = peg$literalExpectation("\"", false); + var peg$e40 = peg$literalExpectation("|", false); + var peg$e41 = peg$otherExpectation("comment"); + var peg$e42 = peg$literalExpectation("//", false); + var peg$e43 = peg$classExpectation(["\n"], true, false); + var peg$e44 = peg$otherExpectation("newline"); + var peg$e45 = peg$literalExpectation("\r\n", false); + var peg$e46 = peg$literalExpectation("\n", false); + var peg$e47 = peg$otherExpectation("whitespace"); + var peg$e48 = peg$classExpectation([" ", "\t", "\r", "\n", "\r"], false, false); + var peg$e49 = peg$literalExpectation(" ", false); + + var peg$f0 = function() { + return implicityRef(data) +}; + var peg$f1 = function(tableData) { + const { table, refs } = tableData; + pushTable(table); + pushRefs(refs) +}; + var peg$f2 = function(r) { + pushRef(r); + }; + var peg$f3 = function(fromTable, toTable, props) { + const foreign = refactorForeign(createForeign(fromTable, toTable, props)); + return foreign; + }; + var peg$f4 = function(columnName) { return ({ columnName }) }; + var peg$f5 = function(primaryKey) { return ({ primaryKey }) }; + var peg$f6 = function(r, value) { + switch (r.toLowerCase()) { + case 'on_delete': + return { + onDelete: value.split('_').join(' ') + } + case 'on_update': + return { + onUpdate: value.split('_').join(' ') + } + } + }; + var peg$f7 = function(name, body) { + const table = ({ + name, + fields: addPrimaryKey(body.fields), + // index: _.union(...body.index) + }) + return { + table, + refs: createRefFromTableWithReference(table, body.references) + }; + }; + var peg$f8 = function(fields) { + return ({ + fields: fields.filter(field => field.isField).map(field => field.field), + index: fields.filter(field => field.isIndex).map(field => field.index), + references: fields.filter(field => field.isReferences).map(field => field.reference), + }); + }; + var peg$f9 = function(field) { return field }; + var peg$f10 = function(reference) { return ({ reference, isReferences: true })}; + var peg$f11 = function(field) { return ({ field, isField: true }) }; + var peg$f12 = function(reference) { + return reference; + }; + var peg$f13 = function(type, name) { + return ({ + name: name, + type: {type_name: type}, + }) + }; + var peg$f14 = function(reference) { return reference }; + var peg$f15 = function(reference) { return reference }; + var peg$f16 = function(c) { return c.join("") }; + var peg$f17 = function(c) { return c.join("") }; + var peg$f18 = function(c) { return c.join("") }; + var peg$f19 = function(c) { return c.join("") }; + var peg$f20 = function(c) { return c.join("") }; + var peg$f21 = function() {return text()}; + var peg$currPos = options.peg$currPos | 0; + var peg$savedPos = peg$currPos; + var peg$posDetailsCache = [{ line: 1, column: 1 }]; + var peg$maxFailPos = peg$currPos; + var peg$maxFailExpected = options.peg$maxFailExpected || []; + var peg$silentFails = options.peg$silentFails | 0; + + var peg$result; + + if (options.startRule) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function offset() { + return peg$savedPos; + } + + function range() { + return { + source: peg$source, + start: peg$savedPos, + end: peg$currPos + }; + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos]; + var p; + + if (details) { + return details; + } else { + if (pos >= peg$posDetailsCache.length) { + p = peg$posDetailsCache.length - 1; + } else { + p = pos; + while (!peg$posDetailsCache[--p]) {} + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + + return details; + } + } + + function peg$computeLocation(startPos, endPos, offset) { + var startPosDetails = peg$computePosDetails(startPos); + var endPosDetails = peg$computePosDetails(endPos); + + var res = { + source: peg$source, + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + if (offset && peg$source && (typeof peg$source.offset === "function")) { + res.start = peg$source.offset(res.start); + res.end = peg$source.offset(res.end); + } + return res; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parseschema() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseline_rule(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseline_rule(); + } + peg$savedPos = s0; + s1 = peg$f0(); + s0 = s1; + + return s0; + } + + function peg$parseline_rule() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsewhitespace(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsewhitespace(); + } + s2 = peg$parserule(); + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parsecomment_line(); + if (s0 === peg$FAILED) { + s0 = peg$parseend_line(); + if (s0 === peg$FAILED) { + s0 = peg$parse__(); + } + } + } + + return s0; + } + + function peg$parserule() { + var s0, s1; + + s0 = peg$currPos; + s1 = peg$parsecreate_table_syntax(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f1(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseadd_foreign_key_syntax(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f2(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$parseother_class_prop(); + } + } + + return s0; + } + + function peg$parseadd_foreign_key_syntax() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsesp(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsesp(); + } + s2 = peg$parseadd_foreign_key(); + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parsesp(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parsesp(); + } + s4 = peg$parsename(); + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c0; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parsesp(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parsesp(); + } + s7 = peg$parsename(); + if (s7 !== peg$FAILED) { + s8 = []; + s9 = peg$parseadd_foreign_key_props_syntax(); + while (s9 !== peg$FAILED) { + s8.push(s9); + s9 = peg$parseadd_foreign_key_props_syntax(); + } + peg$savedPos = s0; + s0 = peg$f3(s4, s7, s8); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseadd_foreign_key_props_syntax() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 44) { + s1 = peg$c0; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesp(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesp(); + } + s3 = peg$parsecolumn(); + if (s3 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s4 = peg$c1; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s4 !== peg$FAILED) { + s5 = []; + s6 = peg$parsesp(); + while (s6 !== peg$FAILED) { + s5.push(s6); + s6 = peg$parsesp(); + } + s6 = peg$parsename(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f4(s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 44) { + s1 = peg$c0; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesp(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesp(); + } + s3 = peg$parseprimary_key(); + if (s3 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s4 = peg$c1; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s4 !== peg$FAILED) { + s5 = []; + s6 = peg$parsesp(); + while (s6 !== peg$FAILED) { + s5.push(s6); + s6 = peg$parsesp(); + } + s6 = peg$parsename(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f5(s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 44) { + s1 = peg$c0; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesp(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesp(); + } + s3 = peg$parsereferential_actions(); + if (s3 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s4 = peg$c1; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s4 !== peg$FAILED) { + s5 = []; + s6 = peg$parsesp(); + while (s6 !== peg$FAILED) { + s5.push(s6); + s6 = peg$parsesp(); + } + s6 = peg$parsesymbol(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f6(s3, s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + + return s0; + } + + function peg$parsecreate_table_syntax() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parsecreate_table(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesp(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesp(); + } + s3 = peg$parsename(); + if (s3 !== peg$FAILED) { + s4 = peg$parsewhateters(); + s5 = peg$parseendline(); + if (s5 !== peg$FAILED) { + s6 = peg$parsetable_body(); + s7 = peg$parseend_line(); + if (s7 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f7(s3, s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsetable_body() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsefield(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsefield(); + } + peg$savedPos = s0; + s1 = peg$f8(s1); + s0 = s1; + + return s0; + } + + function peg$parsefield() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsewhitespace(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsewhitespace(); + } + s2 = peg$parsetable_field_syntax(); + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parsewhatever(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parsewhatever(); + } + s4 = peg$parseendline(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f9(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsetable_field_syntax() { + var s0, s1; + + s0 = peg$parsefield_index_syntax(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsefield_reference_syntax(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f10(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsefield_type_syntax(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f11(s1); + } + s0 = s1; + } + } + + return s0; + } + + function peg$parsefield_index_syntax() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parseindex(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesp(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesp(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parsewhateters(); + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefield_reference_syntax() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsereferences(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesp(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesp(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parsereference_value(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f12(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefield_type_syntax() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsefield_type(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesp(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesp(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parsename(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f13(s1, s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsereference_value() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 58) { + s1 = peg$c1; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsevariable(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f14(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsename(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f15(s1); + } + s0 = s1; + } + + return s0; + } + + function peg$parsereferential_actions() { + var s0; + + s0 = input.substr(peg$currPos, 9); + if (s0.toLowerCase() === peg$c2) { + peg$currPos += 9; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s0 === peg$FAILED) { + s0 = input.substr(peg$currPos, 9); + if (s0.toLowerCase() === peg$c3) { + peg$currPos += 9; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e3); } + } + } + + return s0; + } + + function peg$parseadd_index() { + var s0, s1; + + peg$silentFails++; + if (input.substr(peg$currPos, 9) === peg$c4) { + s0 = peg$c4; + peg$currPos += 9; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + + return s0; + } + + function peg$parseschema_define() { + var s0, s1; + + peg$silentFails++; + if (input.substr(peg$currPos, 27) === peg$c5) { + s0 = peg$c5; + peg$currPos += 27; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e7); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + + return s0; + } + + function peg$parsecreate_table() { + var s0, s1; + + peg$silentFails++; + s0 = input.substr(peg$currPos, 12); + if (s0.toLowerCase() === peg$c6) { + peg$currPos += 12; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e8); } + } + + return s0; + } + + function peg$parseend_create_table() { + var s0, s1, s2, s3, s4, s5, s6; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parsedo(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesp(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesp(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parseabs(); + if (s3 !== peg$FAILED) { + s4 = peg$parsecharacter(); + if (s4 !== peg$FAILED) { + s5 = peg$parseabs(); + if (s5 !== peg$FAILED) { + s6 = peg$parseendline(); + if (s6 !== peg$FAILED) { + s1 = [s1, s2, s3, s4, s5, s6]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + + return s0; + } + + function peg$parseindex() { + var s0, s1, s2; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parsecharacter(); + if (s1 !== peg$FAILED) { + if (input.substr(peg$currPos, 6) === peg$c7) { + s2 = peg$c7; + peg$currPos += 6; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e12); } + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e11); } + } + + return s0; + } + + function peg$parsereferences() { + var s0, s1, s2; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parsecharacter(); + if (s1 !== peg$FAILED) { + if (input.substr(peg$currPos, 11) === peg$c8) { + s2 = peg$c8; + peg$currPos += 11; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e13); } + } + + return s0; + } + + function peg$parseadd_foreign_key() { + var s0, s1; + + peg$silentFails++; + s0 = input.substr(peg$currPos, 15); + if (s0.toLowerCase() === peg$c9) { + peg$currPos += 15; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + + return s0; + } + + function peg$parsecolumn() { + var s0, s1; + + peg$silentFails++; + if (input.substr(peg$currPos, 6) === peg$c10) { + s0 = peg$c10; + peg$currPos += 6; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e18); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + + return s0; + } + + function peg$parseprimary_key() { + var s0, s1; + + peg$silentFails++; + if (input.substr(peg$currPos, 11) === peg$c11) { + s0 = peg$c11; + peg$currPos += 11; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e20); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e19); } + } + + return s0; + } + + function peg$parseversion() { + var s0; + + if (input.substr(peg$currPos, 7) === peg$c12) { + s0 = peg$c12; + peg$currPos += 7; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e21); } + } + + return s0; + } + + function peg$parsedo() { + var s0; + + if (input.substr(peg$currPos, 2) === peg$c13) { + s0 = peg$c13; + peg$currPos += 2; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e22); } + } + + return s0; + } + + function peg$parseend() { + var s0; + + if (input.substr(peg$currPos, 3) === peg$c14) { + s0 = peg$c14; + peg$currPos += 3; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e23); } + } + + return s0; + } + + function peg$parselambda_function() { + var s0, s1; + + peg$silentFails++; + if (input.substr(peg$currPos, 2) === peg$c15) { + s0 = peg$c15; + peg$currPos += 2; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e25); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 2) === peg$c16) { + s0 = peg$c16; + peg$currPos += 2; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } + } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e24); } + } + + return s0; + } + + function peg$parseother_class_prop() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsevariable(); + if (s1 !== peg$FAILED) { + s2 = peg$parsewhateters(); + s3 = peg$parseendline(); + if (s3 === peg$FAILED) { + s3 = null; + } + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsename() { + var s0; + + s0 = peg$parsedouble_quote_name(); + if (s0 === peg$FAILED) { + s0 = peg$parsesingle_quote_name(); + } + + return s0; + } + + function peg$parsedouble_quote_name() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsedouble_quote(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = input.charAt(peg$currPos); + if (peg$r0.test(s3)) { + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e27); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = input.charAt(peg$currPos); + if (peg$r0.test(s3)) { + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e27); } + } + } + s3 = peg$parsedouble_quote(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f16(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesingle_quote_name() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsesingle_quote(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = input.charAt(peg$currPos); + if (peg$r1.test(s3)) { + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e28); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = input.charAt(peg$currPos); + if (peg$r1.test(s3)) { + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e28); } + } + } + s3 = peg$parsesingle_quote(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f17(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesymbol() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 58) { + s1 = peg$c1; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsecharacter(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsecharacter(); + } + peg$savedPos = s0; + s0 = peg$f18(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsevariable() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsecharacter(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsecharacter(); + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f19(s1); + } + s0 = s1; + + return s0; + } + + function peg$parsefield_type() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parsecharacter(); + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 46) { + s2 = peg$c17; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e29); } + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parsecharacter(); + if (s4 !== peg$FAILED) { + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parsecharacter(); + } + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f20(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsenot_whitespace() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = peg$currPos; + peg$silentFails++; + s2 = peg$parsewhitespace(); + peg$silentFails--; + if (s2 === peg$FAILED) { + s1 = undefined; + } else { + peg$currPos = s1; + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + if (input.length > peg$currPos) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e30); } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f21(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsenumber() { + var s0; + + s0 = input.charAt(peg$currPos); + if (peg$r2.test(s0)) { + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e31); } + } + + return s0; + } + + function peg$parsecharacter() { + var s0, s1; + + peg$silentFails++; + s0 = input.charAt(peg$currPos); + if (peg$r3.test(s0)) { + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e33); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e32); } + } + + return s0; + } + + function peg$parseend_line() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsewhitespace(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsewhitespace(); + } + s2 = peg$parseend(); + if (s2 !== peg$FAILED) { + s3 = peg$parseendline(); + if (s3 === peg$FAILED) { + s3 = null; + } + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsewhatever_line() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsewhitespace(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsewhitespace(); + } + s2 = peg$currPos; + s3 = peg$parsewhateters(); + s4 = peg$currPos; + peg$silentFails++; + s5 = peg$parseend(); + peg$silentFails--; + if (s5 === peg$FAILED) { + s4 = undefined; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s3 = [s3, s4]; + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parseendline(); + if (s3 === peg$FAILED) { + s3 = null; + } + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsecomment_line() { + var s0, s1, s2, s3, s4; + + peg$silentFails++; + s0 = peg$currPos; + s1 = []; + s2 = peg$parsewhitespace(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsewhitespace(); + } + if (input.charCodeAt(peg$currPos) === 35) { + s2 = peg$c18; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e35); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsewhateters(); + s4 = peg$parseendline(); + if (s4 === peg$FAILED) { + s4 = null; + } + s1 = [s1, s2, s3, s4]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e34); } + } + + return s0; + } + + function peg$parsewhateters() { + var s0, s1; + + peg$silentFails++; + s0 = []; + s1 = input.charAt(peg$currPos); + if (peg$r4.test(s1)) { + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e37); } + } + while (s1 !== peg$FAILED) { + s0.push(s1); + s1 = input.charAt(peg$currPos); + if (peg$r4.test(s1)) { + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e37); } + } + } + peg$silentFails--; + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e36); } + + return s0; + } + + function peg$parsewhatever() { + var s0; + + s0 = input.charAt(peg$currPos); + if (peg$r4.test(s0)) { + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e37); } + } + + return s0; + } + + function peg$parsesingle_quote() { + var s0; + + if (input.charCodeAt(peg$currPos) === 39) { + s0 = peg$c19; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e38); } + } + + return s0; + } + + function peg$parsedouble_quote() { + var s0; + + if (input.charCodeAt(peg$currPos) === 34) { + s0 = peg$c20; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e39); } + } + + return s0; + } + + function peg$parse_() { + var s0, s1; + + s0 = []; + s1 = peg$parsecomment(); + if (s1 === peg$FAILED) { + s1 = peg$parsewhitespace(); + } + while (s1 !== peg$FAILED) { + s0.push(s1); + s1 = peg$parsecomment(); + if (s1 === peg$FAILED) { + s1 = peg$parsewhitespace(); + } + } + + return s0; + } + + function peg$parse__() { + var s0, s1; + + s0 = []; + s1 = peg$parsecomment(); + if (s1 === peg$FAILED) { + s1 = peg$parsewhitespace(); + } + if (s1 !== peg$FAILED) { + while (s1 !== peg$FAILED) { + s0.push(s1); + s1 = peg$parsecomment(); + if (s1 === peg$FAILED) { + s1 = peg$parsewhitespace(); + } + } + } else { + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseabs() { + var s0; + + if (input.charCodeAt(peg$currPos) === 124) { + s0 = peg$c21; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e40); } + } + + return s0; + } + + function peg$parseendline() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsesp(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsesp(); + } + s2 = peg$parsenewline(); + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsecomment() { + var s0, s1, s2; + + peg$silentFails++; + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c22) { + s1 = peg$c22; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e42); } + } + if (s1 !== peg$FAILED) { + s2 = input.charAt(peg$currPos); + if (peg$r5.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e43); } + } + if (s2 === peg$FAILED) { + s2 = null; + } + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e41); } + } + + return s0; + } + + function peg$parsenewline() { + var s0, s1; + + peg$silentFails++; + if (input.substr(peg$currPos, 2) === peg$c23) { + s0 = peg$c23; + peg$currPos += 2; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e45); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 10) { + s0 = peg$c24; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e46); } + } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e44); } + } + + return s0; + } + + function peg$parsewhitespace() { + var s0, s1; + + peg$silentFails++; + s0 = input.charAt(peg$currPos); + if (peg$r6.test(s0)) { + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e48); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e47); } + } + + return s0; + } + + function peg$parsesp() { + var s0; + + if (input.charCodeAt(peg$currPos) === 32) { + s0 = peg$c25; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e49); } + } + + return s0; + } + + + var pluralize = require('pluralize'); + var lodash = require('lodash'); + + let data = { + tables: [], + refs: [] + } + + function pushTable(table) { + if (data.tables.find(t => t.name == table.name)) { + error("Duplicated table name"); + } else { + const idField = table.fields.find(field => field.name === "id"); + if (!idField) { + table.fields.unshift({ + name: "id", + type: { type_name: "varchar" } + }); + } + data.tables.push(table); + } + } + + function addPrimaryKey(fields = [], props = []) { + const primaryKey = props.find(prop => prop.name === 'primary_key'); + if (!primaryKey) return fields; + if (fields.find(key => key.name === primaryKey.value)) { + return fields.map(({ name, type}) => ({ + name, type, + PK: primaryKey.value === field.name, + })) + } + const newFields = [{ + name: primaryKey.value, + type: { type_name: "varchar" }, + PK: true, + }]; + return newFields.concat(fields); + } + + function findTableByNameOrAlias(name) { + const table = data.tables.find(t => t.name == name || t.alias == name); + if (table === undefined) { + error("Table " + name +" not found"); + } + return table; + } + + function isSameEndpoints (endpoint1, endpoint2) { + return endpoint1.tableName == endpoint2.tableName && + lodash.isEqual(lodash.sortBy(endpoint1.fieldNames), lodash.sortBy(endpoint2.fieldNames)) + } + + function isSameEndpointsPairs (endpointsPair1, endpointsPair2) { + return isSameEndpoints(endpointsPair1[0], endpointsPair2[0]) + && isSameEndpoints(endpointsPair1[1], endpointsPair2[1]) + } + + function isSameEndpointsRefs(ref1, ref2) { + return isSameEndpointsPairs (ref1.endpoints, ref2.endpoints) + || isSameEndpointsPairs(ref1.endpoints, ref2.endpoints.slice().reverse()) + } + + function pushRef(ref) { + if (!ref) return; + if (data.refs.find(p => isSameEndpointsRefs(p, ref))) { + error("Duplicated references"); + } + data.refs.push(ref); + } + function pushRefs(refs = []) { + if (!refs || refs.length === 0) return + for(let i = 0; i < refs.length; i += 1) { + pushRef(refs[i]); + } + } + function refactorForeign(ref) { + // add relation + const { tables } = data; + const { endpoints } = ref; + const fromTable = tables.find(table => table.name === endpoints[0].tableName) + if (!fromTable) { + // TODO: handle error + // throw { + // message: `Table ${endpoints[0].table} not found` + // } + // return ref; + return null; + } + const toTable = tables.find(table => table.name === endpoints[1].tableName) + if (!toTable) { + // TODO: handle error + // throw { + // message: `Table ${endpoints[1].table} not found` + // } + // return ref; + return null; + } + if (!endpoints[0].fieldNames) { + const singleNameOfPrimaryTable = pluralize.singular(endpoints[1].tableName) + const columnName = `${singleNameOfPrimaryTable}_id`; + endpoints[0].fieldNames = [columnName]; + const columnField = fromTable.fields.find(field => field.name === columnName); + if (!columnField) { + // TODO: handle erro + // throw { + // message: `Field ${columnName} not found in table ${endpoints[0].table}` + // } + // return ref; + return null; + } + endpoints[0].fieldNames = [columnName]; + } + if (!endpoints[1].fieldNames) { + const primaryKey = 'id'; + endpoints[1].fieldNames = [primaryKey]; + } + return ref; + } + + function createForeign(fromTable, toTable, props) { + const endpoints = ([{ + tableName: fromTable, + relation: '1', + }, + { + tableName: toTable, + relation: '1', + }]); + let refProp = {}; + for (let i = 0; i < props.length; i += 1) { + const currentProp = props[i]; + if (currentProp.columnName) { + endpoints[0].fieldNames = [currentProp.columnName]; + } + if (currentProp.primaryKey) { + endpoints[1].fieldNames = [currentProp.primaryKey]; + } + if (currentProp.onDelete) { + refProp = { + ...refProp, + onDelete: currentProp.onDelete + } + } + if (currentProp.onUpdate) { + refProp = { + ...refProp, + onUpdate: currentProp.onUpdate + } + } + } + return { + name: `fk_rails_${fromTable}_${toTable}`, + endpoints, + ...refProp + }; + } + + function createRefFromTableWithReference(table, references) { + if (!references || references.length === 0) { + return []; + } + const refs = []; + for (let i = 0; i < references.length; i += 1) { + const reference = references[i]; + const referenceTable = pluralize.plural(reference); + const { tables } = data; + + const toTable = tables.find(table => table.name === referenceTable) + + if (!toTable) { + continue; + } + // add field to table if not exists (`${reference}_id`) + // auto add type of new field to be varchar if primaryKey not found + const columnName = `${reference}_id`; + const primaryKeyName = 'id'; + const column = table.fields.find(field => field.name === columnName); + const primaryKey = toTable.fields.find(field => field.name === primaryKeyName); + if (!column) { + table.fields.push({ + name: columnName, + type: { type_name: primaryKey ? primaryKey.type.type_name : 'varchar'}, + }) + } + + refs.push({ + name: `fk_rails_${table.name}_${referenceTable}`, + endpoints: [ + { + tableName: table.name, + fieldNames: [columnName], + relation: '1', + }, + { + tableName: referenceTable, + fieldNames: [primaryKeyName], + relation: '1', + } + ] + }) + } + return refs; + } + function implicityRef(data) { + const { tables, refs } = data; + const tableWithFieldName = tables.map(table => { + const { name } = table; + const singularName = pluralize.singular(name); + return ({ + name, + field: `${singularName}_id` + }); + }) + for (let i = 0; i < tables.length; i += 1) { + const table = tables[i]; + const { fields } = table; + for (let j = 0; j < fields.length; j += 1) { + const field = fields[j]; + const refWithTable = tableWithFieldName.find(table => table.field === field.name); + if (refWithTable) { + const newRef = ({ + name: `fk_rails_${table.name}_${refWithTable.name}`, + endpoints: [ + { + tableName: table.name, + fieldNames: [field.name], + relation: '1', + }, + { + tableName: refWithTable.name, + fieldNames: ['id'], + relation: '1', + } + ] + }); + const duplicateRef = refs.find(ref => isSameEndpointsRefs(ref, newRef)); + if (!duplicateRef) { + refs.push(newRef) + } + } + } + } + return data; + } + + peg$result = peg$startRuleFunction(); + + if (options.peg$library) { + return /** @type {any} */ ({ + peg$result, + peg$currPos, + peg$FAILED, + peg$maxFailExpected, + peg$maxFailPos + }); + } + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } +} + +module.exports = { + StartRules: ["schema"], + SyntaxError: peg$SyntaxError, + parse: peg$parse +}; diff --git a/frontend/packages/db-structure/src/parser/schemarb/parser.pegjs b/frontend/packages/db-structure/src/parser/schemarb/parser.pegjs new file mode 100644 index 000000000..9b4c710c8 --- /dev/null +++ b/frontend/packages/db-structure/src/parser/schemarb/parser.pegjs @@ -0,0 +1,399 @@ +// This file is a modified version of schemarb/parser.pegjs from the dbml +// repository (https://github.com/holistics/dbml). +// Last updated: 2024-11-18 + +// Copyright 2019 Holistics Software Pte Ltd. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ + var pluralize = require('pluralize'); + var isEqual = require('lodash/isEqual'); + var sortBy = require('lodash/sortBy'); + + let data = { + tables: [], + refs: [] + } + + function pushTable(table) { + if (data.tables.find(t => t.name == table.name)) { + error("Duplicated table name"); + } else { + const idField = table.fields.find(field => field.name === "id"); + if (!idField) { + table.fields.unshift({ + name: "id", + type: { type_name: "varchar" } + }); + } + data.tables.push(table); + } + } + + function addPrimaryKey(fields = [], props = []) { + const primaryKey = props.find(prop => prop.name === 'primary_key'); + if (!primaryKey) return fields; + if (fields.find(key => key.name === primaryKey.value)) { + return fields.map(({ name, type}) => ({ + name, type, + PK: primaryKey.value === field.name, + })) + } + const newFields = [{ + name: primaryKey.value, + type: { type_name: "varchar" }, + PK: true, + }]; + return newFields.concat(fields); + } + + function findTableByNameOrAlias(name) { + const table = data.tables.find(t => t.name == name || t.alias == name); + if (table === undefined) { + error("Table " + name +" not found"); + } + return table; + } + + function isSameEndpoints (endpoint1, endpoint2) { + return endpoint1.tableName == endpoint2.tableName && + isEqual(sortBy(endpoint1.fieldNames), sortBy(endpoint2.fieldNames)) + } + + function isSameEndpointsPairs (endpointsPair1, endpointsPair2) { + return isSameEndpoints(endpointsPair1[0], endpointsPair2[0]) + && isSameEndpoints(endpointsPair1[1], endpointsPair2[1]) + } + + function isSameEndpointsRefs(ref1, ref2) { + return isSameEndpointsPairs (ref1.endpoints, ref2.endpoints) + || isSameEndpointsPairs(ref1.endpoints, ref2.endpoints.slice().reverse()) + } + + function pushRef(ref) { + if (!ref) return; + if (data.refs.find(p => isSameEndpointsRefs(p, ref))) { + error("Duplicated references"); + } + data.refs.push(ref); + } + function pushRefs(refs = []) { + if (!refs || refs.length === 0) return + for(let i = 0; i < refs.length; i += 1) { + pushRef(refs[i]); + } + } + function refactorForeign(ref) { + // add relation + const { tables } = data; + const { endpoints } = ref; + const fromTable = tables.find(table => table.name === endpoints[0].tableName) + if (!fromTable) { + // TODO: handle error + // throw { + // message: `Table ${endpoints[0].table} not found` + // } + // return ref; + return null; + } + const toTable = tables.find(table => table.name === endpoints[1].tableName) + if (!toTable) { + // TODO: handle error + // throw { + // message: `Table ${endpoints[1].table} not found` + // } + // return ref; + return null; + } + if (!endpoints[0].fieldNames) { + const singleNameOfPrimaryTable = pluralize.singular(endpoints[1].tableName) + const columnName = `${singleNameOfPrimaryTable}_id`; + endpoints[0].fieldNames = [columnName]; + const columnField = fromTable.fields.find(field => field.name === columnName); + if (!columnField) { + // TODO: handle error + // throw { + // message: `Field ${columnName} not found in table ${endpoints[0].table}` + // } + // return ref; + return null; + } + endpoints[0].fieldNames = [columnName]; + } + if (!endpoints[1].fieldNames) { + const primaryKey = 'id'; + endpoints[1].fieldNames = [primaryKey]; + } + return ref; + } + + function createForeign(fromTable, toTable, props) { + const endpoints = ([{ + tableName: fromTable, + relation: '1', + }, + { + tableName: toTable, + relation: '1', + }]); + let refProp = {}; + for (let i = 0; i < props.length; i += 1) { + const currentProp = props[i]; + if (currentProp.columnName) { + endpoints[0].fieldNames = [currentProp.columnName]; + } + if (currentProp.primaryKey) { + endpoints[1].fieldNames = [currentProp.primaryKey]; + } + if (currentProp.onDelete) { + refProp = { + ...refProp, + onDelete: currentProp.onDelete + } + } + if (currentProp.onUpdate) { + refProp = { + ...refProp, + onUpdate: currentProp.onUpdate + } + } + } + return { + name: `fk_rails_${fromTable}_${toTable}`, + endpoints, + ...refProp + }; + } + + function createRefFromTableWithReference(table, references) { + if (!references || references.length === 0) { + return []; + } + const refs = []; + for (let i = 0; i < references.length; i += 1) { + const reference = references[i]; + const referenceTable = pluralize.plural(reference); + const { tables } = data; + + const toTable = tables.find(table => table.name === referenceTable) + + if (!toTable) { + continue; + } + // add field to table if not exists (`${reference}_id`) + // auto add type of new field to be varchar if primaryKey not found + const columnName = `${reference}_id`; + const primaryKeyName = 'id'; + const column = table.fields.find(field => field.name === columnName); + const primaryKey = toTable.fields.find(field => field.name === primaryKeyName); + if (!column) { + table.fields.push({ + name: columnName, + type: { type_name: primaryKey ? primaryKey.type.type_name : 'varchar'}, + }) + } + + refs.push({ + name: `fk_rails_${table.name}_${referenceTable}`, + endpoints: [ + { + tableName: table.name, + fieldNames: [columnName], + relation: '1', + }, + { + tableName: referenceTable, + fieldNames: [primaryKeyName], + relation: '1', + } + ] + }) + } + return refs; + } + function implicityRef(data) { + const { tables, refs } = data; + const tableWithFieldName = tables.map(table => { + const { name } = table; + const singularName = pluralize.singular(name); + return ({ + name, + field: `${singularName}_id` + }); + }) + for (let i = 0; i < tables.length; i += 1) { + const table = tables[i]; + const { fields } = table; + for (let j = 0; j < fields.length; j += 1) { + const field = fields[j]; + const refWithTable = tableWithFieldName.find(table => table.field === field.name); + if (refWithTable) { + const newRef = ({ + name: `fk_rails_${table.name}_${refWithTable.name}`, + endpoints: [ + { + tableName: table.name, + fieldNames: [field.name], + relation: '1', + }, + { + tableName: refWithTable.name, + fieldNames: ['id'], + relation: '1', + } + ] + }); + const duplicateRef = refs.find(ref => isSameEndpointsRefs(ref, newRef)); + if (!duplicateRef) { + refs.push(newRef) + } + } + } + } + return data; + } +} + +schema = line_rule* { + return implicityRef(data) +} + +line_rule + = whitespace* rule + / comment_line + / end_line + / __ + +rule = tableData:create_table_syntax { + const { table, refs } = tableData; + pushTable(table); + pushRefs(refs) +} +/ r:add_foreign_key_syntax { + pushRef(r); + } +/ other_class_prop + +add_foreign_key_syntax + = sp* add_foreign_key sp* fromTable:name","sp* toTable:name props:add_foreign_key_props_syntax* { + const foreign = refactorForeign(createForeign(fromTable, toTable, props)); + return foreign; + } + +add_foreign_key_props_syntax += "," sp* column":" sp* columnName:name { return ({ columnName }) } +/ "," sp* primary_key":" sp* primaryKey:name { return ({ primaryKey }) } +/ "," sp* r:referential_actions":" sp* value:symbol { + switch (r.toLowerCase()) { + case 'on_delete': + return { + onDelete: value.split('_').join(' ') + } + case 'on_update': + return { + onUpdate: value.split('_').join(' ') + } + } + } + +create_table_syntax + = create_table sp* name:name whateters endline + body:table_body + end_line { + const table = ({ + name, + fields: addPrimaryKey(body.fields), + // index: _.union(...body.index) + }) + return { + table, + refs: createRefFromTableWithReference(table, body.references) + }; + } + +table_body = fields:field* { + return ({ + fields: fields.filter(field => field.isField).map(field => field.field), + index: fields.filter(field => field.isIndex).map(field => field.index), + references: fields.filter(field => field.isReferences).map(field => field.reference), + }); + } + +field = whitespace* field:table_field_syntax whatever* endline { return field } + +table_field_syntax + = field_index_syntax + / reference: field_reference_syntax { return ({ reference, isReferences: true })} + / field:field_type_syntax { return ({ field, isField: true }) } + +field_index_syntax = index sp+ whateters +field_reference_syntax = references sp+ reference:reference_value { + return reference; + } +field_type_syntax = type:field_type sp+ name:name { + return ({ + name: name, + type: {type_name: type}, + }) + } + +reference_value = ":"reference:variable { return reference } + / reference:name { return reference } + +referential_actions = "on_delete"i / "on_update"i + +// Keywords +add_index "add index" = "add_index" +schema_define "schema define" = "ActiveRecord::Schema.define" +create_table "create table" = "create_table"i +end_create_table "do |t|" = do sp+ abs character abs endline +index "index" = character ".index" +references "references" = character ".references" +add_foreign_key "add foreign key" = "add_foreign_key"i +column "column" = "column" +primary_key "primary key" = "primary_key" +version = "version" +do = "do" +end = "end" +lambda_function "lambda function" = "->" +other_class_prop = variable whateters endline? + +// normal syntax +name = double_quote_name / single_quote_name +double_quote_name = double_quote c:[^\"\n]* double_quote { return c.join("") } +single_quote_name = single_quote c:[^\'\n]* single_quote { return c.join("") } +symbol = ":" c:character* { return c.join("") } +variable = c:(character+) { return c.join("") } +field_type = character"."c:(character+) { return c.join("") } +not_whitespace = !whitespace . {return text()} +number = [0-9]i +character "letter, number or underscore" = [a-z0-9_.]i +end_line = whitespace* end endline? +whatever_line = whitespace* (whateters ! end) endline? +comment_line "comment line" = whitespace* "#" whateters endline? +whateters "whatever" = [^\t\r\n]* +whatever = [^\t\r\n] +single_quote = "'" +double_quote = "\"" + +// Ignored +_ = (comment/whitespace)* +__ = (comment/whitespace)+ + +abs = "|" +endline = sp* newline; +comment "comment" = "//" [^\n]? +newline "newline" = "\r\n" / "\n" +whitespace "whitespace" = [ \t\r\n\r] +sp = " " diff --git a/frontend/packages/db-structure/src/schema/index.ts b/frontend/packages/db-structure/src/schema/index.ts index bec9f3607..950d80ac8 100644 --- a/frontend/packages/db-structure/src/schema/index.ts +++ b/frontend/packages/db-structure/src/schema/index.ts @@ -29,11 +29,13 @@ const tableSchema = v.object({ x: v.number(), y: v.number(), fields: v.array(fieldSchema), - comment: v.string(), + comment: v.nullable(v.string()), indices: v.array(indexSchema), - color: v.string(), + color: v.nullable(v.string()), }) +export type Table = v.InferOutput + const relationshipSchema = v.object({ name: relationshipNameSchema, startTableName: tableNameSchema, diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 55f4e3876..4db9cb046 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -84,6 +84,15 @@ importers: packages/db-structure: dependencies: + lodash.isequal: + specifier: ^4.5.0 + version: 4.5.0 + lodash.sortby: + specifier: ^4.7.0 + version: 4.7.0 + pluralize: + specifier: ^8.0.0 + version: 8.0.0 valibot: specifier: ^1.0.0-beta.5 version: 1.0.0-beta.5(typescript@5.6.2) @@ -97,6 +106,9 @@ importers: typescript: specifier: ^5 version: 5.6.2 + vitest: + specifier: ^2.1.4 + version: 2.1.4(@types/node@22.9.0) packages/erd-core: dependencies: @@ -2257,6 +2269,12 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -2575,6 +2593,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -5570,6 +5592,10 @@ snapshots: lodash.get@4.4.2: {} + lodash.isequal@4.5.0: {} + + lodash.sortby@4.7.0: {} + lodash@4.17.21: {} log-symbols@3.0.0: @@ -5903,6 +5929,8 @@ snapshots: picomatch@2.3.1: {} + pluralize@8.0.0: {} + possible-typed-array-names@1.0.0: {} postcss-modules-extract-imports@3.1.0(postcss@8.4.47):