From 06607e36616503f9f3e34ff6f3cfbc04b51a5f1c Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Fri, 11 Sep 2020 14:23:07 +0300 Subject: [PATCH 1/5] feat: generate CONSTRAINT for pg SQL DDL Refs: https://github.com/metarhia/metasql/issues/6 --- lib/ddl.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/ddl.js b/lib/ddl.js index df70c5e..2c25d4b 100644 --- a/lib/ddl.js +++ b/lib/ddl.js @@ -45,6 +45,15 @@ const createIndex = (entityName, { name, unique, index }) => { return `CREATE ${uni}INDEX ${idxName} ON ${idxOn};`; }; +const createForeignKey = (entityName, { name, type }) => { + const fk = 'fk' + entityName + toUpperCamel(name); + return ( + `ALTER TABLE "${entityName}" ADD CONSTRAINT "${fk}" ` + + `FOREIGN KEY ("${name}") ` + + `REFERENCES "${type}" ("${type}Id") ON DELETE CASCADE;` + ); +}; + class PgSchema extends DatabaseSchema { async preprocessEntity(name, entity) { const fields = Object.keys(entity); @@ -75,6 +84,7 @@ class PgSchema extends DatabaseSchema { if (!pgType) throw new Error(`Unknown type: ${def.type}`); const pgField = def.name + (kind === DB_FIELD ? '' : 'Id'); sql.push(` "${pgField}" ${pgType}${nullable},`); + if (kind === DB_RELATION) idx.push(createForeignKey(name, def)); } if (def.unique || def.index) idx.push(createIndex(name, def)); } From 7e2a0382509d535a39f51dc9aa7d8b0bc05ca7c3 Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Fri, 11 Sep 2020 18:30:27 +0300 Subject: [PATCH 2/5] refactor: pg SQL generation templates/functions --- lib/ddl.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/ddl.js b/lib/ddl.js index 2c25d4b..665d9bc 100644 --- a/lib/ddl.js +++ b/lib/ddl.js @@ -45,7 +45,7 @@ const createIndex = (entityName, { name, unique, index }) => { return `CREATE ${uni}INDEX ${idxName} ON ${idxOn};`; }; -const createForeignKey = (entityName, { name, type }) => { +const foreignKey = (entityName, { name, type }) => { const fk = 'fk' + entityName + toUpperCamel(name); return ( `ALTER TABLE "${entityName}" ADD CONSTRAINT "${fk}" ` + @@ -54,6 +54,12 @@ const createForeignKey = (entityName, { name, type }) => { ); }; +const primaryKey = entityName => { + const fieldName = toLowerCamel(entityName) + 'Id'; + const constraint = `"pk${entityName}" PRIMARY KEY ("${fieldName}")`; + return `ALTER TABLE "${entityName}" ADD CONSTRAINT ${constraint};`; +}; + class PgSchema extends DatabaseSchema { async preprocessEntity(name, entity) { const fields = Object.keys(entity); @@ -72,8 +78,7 @@ class PgSchema extends DatabaseSchema { sql.push(`CREATE TABLE "${name}" (`); const pk = toLowerCamel(name) + 'Id'; sql.push(` "${pk}" bigint unsigned generated always as identity,`); - const constraint = `"pk${name}" PRIMARY KEY ("${pk}")`; - idx.push(`ALTER TABLE "${name}" ADD CONSTRAINT ${constraint};`); + idx.push(primaryKey(name)); const fields = Object.keys(entity); for (const fieldName of fields) { const def = entity[fieldName]; @@ -84,7 +89,7 @@ class PgSchema extends DatabaseSchema { if (!pgType) throw new Error(`Unknown type: ${def.type}`); const pgField = def.name + (kind === DB_FIELD ? '' : 'Id'); sql.push(` "${pgField}" ${pgType}${nullable},`); - if (kind === DB_RELATION) idx.push(createForeignKey(name, def)); + if (kind === DB_RELATION) idx.push(foreignKey(name, def)); } if (def.unique || def.index) idx.push(createIndex(name, def)); } From fe59c561258f3580e89781a9ca3ba134e7cbcd4c Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Fri, 11 Sep 2020 19:28:23 +0300 Subject: [PATCH 3/5] refactor: move functions to utilities --- lib/ddl.js | 5 +---- lib/utils.js | 7 +++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/ddl.js b/lib/ddl.js index 665d9bc..7a8b2c3 100644 --- a/lib/ddl.js +++ b/lib/ddl.js @@ -1,10 +1,7 @@ 'use strict'; const { DatabaseSchema } = require('./schema-db.js'); - -const toLowerCamel = s => s.charAt(0).toLowerCase() + s.slice(1); -const toUpperCamel = s => s.charAt(0).toUpperCase() + s.slice(1); -const isUpperCamel = s => s[0] === s[0].toUpperCase(); +const { toLowerCamel, toUpperCamel, isUpperCamel } = require('./utils.js'); const DB_RELATION = 1; const DB_FIELD = 2; diff --git a/lib/utils.js b/lib/utils.js index 5b3ba3f..0f0a2b0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -30,9 +30,16 @@ const joinIterable = (val, sep) => { return res; }; +const toLowerCamel = s => s.charAt(0).toLowerCase() + s.slice(1); +const toUpperCamel = s => s.charAt(0).toUpperCase() + s.slice(1); +const isUpperCamel = s => !!s && s[0] === s[0].toUpperCase(); + module.exports = { escapeIdentifier, escapeKey, mapJoinIterable, joinIterable, + toLowerCamel, + toUpperCamel, + isUpperCamel, }; From 86270e7a36be294020aae3366e73e7f66c787983 Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Fri, 11 Sep 2020 19:29:14 +0300 Subject: [PATCH 4/5] feat: create entities in certain order Closes: https://github.com/metarhia/metasql/issues/6 --- lib/schema-db.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/schema-db.js b/lib/schema-db.js index ab8b5c4..e99be2e 100644 --- a/lib/schema-db.js +++ b/lib/schema-db.js @@ -3,12 +3,14 @@ const path = require('path'); const fs = require('fs').promises; +const { isUpperCamel } = require('./utils.js'); const { Schema } = require('./schema.js'); class DatabaseSchema { constructor(schemaPath) { this.path = schemaPath; this.entities = new Map(); + this.order = new Set(); return this.load(); } @@ -30,6 +32,11 @@ class DatabaseSchema { for (const [name, entity] of this.entities) { await this.preprocessEntity(name, entity); } + for (const [name, entity] of this.entities) { + if (!this.order.has(name)) { + await this.reorderEntity(name, entity); + } + } } async preprocessEntity(name, entity) { @@ -38,9 +45,21 @@ class DatabaseSchema { throw new Error(`Method is not implemented: preprocessEntity(${args})`); } + async reorderEntity(name, entity) { + const fields = Object.keys(entity); + for (const field of fields) { + const { type } = entity[field]; + if (isUpperCamel(type) && !this.order.has(type)) { + await this.reorderEntity(type, this.entities.get(type)); + } + } + this.order.add(name); + } + async validate() { console.log('Validating metaschema'); - for (const [name, entity] of this.entities) { + for (const name of this.order) { + const entity = this.entities.get(name); const fields = Object.keys(entity); if (entity) console.log(` ${name}: ${fields.length} fields`); } @@ -49,7 +68,8 @@ class DatabaseSchema { async generate(outputPath) { console.log('Generating SQL DDL script ' + outputPath); const script = []; - for (const [name, entity] of this.entities) { + for (const name of this.order) { + const entity = this.entities.get(name); const sql = await this.generateEntity(name, entity); script.push(sql); } From 3472fedcdda6600ea9606e591f90092587be7d37 Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Fri, 11 Sep 2020 19:30:30 +0300 Subject: [PATCH 5/5] test: add example schemas for test Refs: https://github.com/metarhia/metasql/issues/6 --- test/schema/Country.js | 1 + test/schema/District.js | 4 ++++ test/schema/Planet.js | 3 +++ 3 files changed, 8 insertions(+) create mode 100644 test/schema/District.js create mode 100644 test/schema/Planet.js diff --git a/test/schema/Country.js b/test/schema/Country.js index 915d8e5..bf9dca5 100644 --- a/test/schema/Country.js +++ b/test/schema/Country.js @@ -1,3 +1,4 @@ ({ + planet: 'Planet', name: { type: 'string', unique: true }, }); diff --git a/test/schema/District.js b/test/schema/District.js new file mode 100644 index 0000000..1d2e9a0 --- /dev/null +++ b/test/schema/District.js @@ -0,0 +1,4 @@ +({ + city: 'City', + name: { type: 'string', unique: true }, +}); diff --git a/test/schema/Planet.js b/test/schema/Planet.js new file mode 100644 index 0000000..915d8e5 --- /dev/null +++ b/test/schema/Planet.js @@ -0,0 +1,3 @@ +({ + name: { type: 'string', unique: true }, +});