From fef898c8639948779992a08d88bfec345be96c0c Mon Sep 17 00:00:00 2001 From: Dmitry Ovsyanko Date: Tue, 15 Apr 2025 13:07:49 +0300 Subject: [PATCH 1/5] DbLang +createDbObject --- lib/DbLang.js | 12 ++++++++++++ lib/model/DbSchema.js | 6 ++---- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/DbLang.js b/lib/DbLang.js index 3827708..34b0355 100644 --- a/lib/DbLang.js +++ b/lib/DbLang.js @@ -93,6 +93,18 @@ class DbLang { } + createDbObject (options) { + + const clazz = this.getDbObjectClass (options) + + const dbObject = new clazz (options) + + dbObject.setLang (this) + + return dbObject + + } + getDbObjectClassesToDiscover () { return [DbTable] diff --git a/lib/model/DbSchema.js b/lib/model/DbSchema.js index e9182ab..25ef64b 100644 --- a/lib/model/DbSchema.js +++ b/lib/model/DbSchema.js @@ -42,14 +42,12 @@ class DbSchema extends require ('events') { create (options) { - const {model} = this, {lang} = model + const {model} = this, {lang} = model options.model = model options.schema = this - const dbObject = new (lang.getDbObjectClass (options)) (options) - - dbObject.setLang (lang) + const dbObject = lang.createDbObject (options) this.emit ('object-created', dbObject) diff --git a/package-lock.json b/package-lock.json index 837bbe5..dec48f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "doix-db", - "version": "1.0.73", + "version": "1.0.74", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "doix-db", - "version": "1.0.73", + "version": "1.0.74", "license": "MIT", "dependencies": { "string-escape-map": "^1.0.0" diff --git a/package.json b/package.json index fb597bb..d116303 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "doix-db", - "version": "1.0.73", + "version": "1.0.74", "description": "Shared database related code for doix", "main": "index.js", "files": [ From 31ede1cbb4c40237e9675f2f4b8136df2928d4e4 Mon Sep 17 00:00:00 2001 From: Dmitry Ovsyanko Date: Wed, 16 Apr 2025 10:45:13 +0300 Subject: [PATCH 2/5] DbLang +parseColumn* (from DbColumn) --- __tests__/DbColumn.js | 52 +++++++----- __tests__/DbCsvPrinter.js | 2 +- __tests__/DbLang.js | 91 +++++++++++---------- __tests__/DbRelation.js | 8 +- lib/DbCsvPrinter.js | 2 + lib/DbLang.js | 157 ++++++++++++++++++++++++++++++++++++ lib/model/DbColumn.js | 164 ++------------------------------------ 7 files changed, 251 insertions(+), 225 deletions(-) diff --git a/__tests__/DbColumn.js b/__tests__/DbColumn.js index 522de9f..01ab274 100644 --- a/__tests__/DbColumn.js +++ b/__tests__/DbColumn.js @@ -1,5 +1,15 @@ const {DbColumn, DbReference, DbLang} = require ('..') +const newCol = src => { + + if (typeof src === 'string') src = (new DbLang ()).parseColumn (src) + + return new DbColumn (src) + +} + +const tst = (src, dst) => expect (newCol (src)).toEqual ({src, ...dst}) + test ('Object', () => { const o = {name: 'id', type: 'int'} @@ -7,16 +17,14 @@ test ('Object', () => { const on = {name: 'id', type: 'int', nullable: false} const ond = {name: 'id', type: 'int', default: 1, nullable: true} - expect (new DbColumn (o)).toEqual ({...o, nullable: true}) - expect (new DbColumn (od)).toEqual ({...od, nullable: false}) - expect (new DbColumn (on)).toEqual (on) - expect (new DbColumn (ond)).toEqual (ond) + expect (newCol (o)).toEqual ({...o, nullable: true}) + expect (newCol (od)).toEqual ({...od, nullable: false}) + expect (newCol (on)).toEqual (on) + expect (newCol (ond)).toEqual (ond) expect (new DbReference (o)).toEqual ({...o, on: {}}) }) -const tst = (src, dst) => expect (new DbColumn (src)).toEqual ({src, ...dst}) - test ('comment', () => { tst (' int', {type: 'int', nullable: true}) @@ -38,8 +46,8 @@ test ('pattern', () => { tst ('text /^[0-9\\/]+$/', {type: 'text', pattern: '^[0-9\\/]+$', nullable: true}) tst ('\t text = OK /^(OK|ERROR)$/ // \t\t\t status', {type: 'text', pattern: '^(OK|ERROR)$', default: 'OK', comment: 'status', nullable: false}) - expect (() => new DbColumn ('text=/^/')).toThrow () - expect (() => new DbColumn ('text / // ?')).toThrow () + expect (() => newCol ('text=/^/')).toThrow () + expect (() => newCol ('text / // ?')).toThrow () }) @@ -51,8 +59,8 @@ test ('range', () => { tst ('date = 1980-01-01 { 1970-01-01 .. NOW } /-01$/ // created ', {type: 'date', min: '1970-01-01', default: '1980-01-01', max: 'NOW', pattern: '-01$', comment: 'created', nullable: false}) - expect (() => new DbColumn ('date }')).toThrow () - expect (() => new DbColumn ('date {}')).toThrow () + expect (() => newCol ('date }')).toThrow () + expect (() => newCol ('date {}')).toThrow () }) @@ -73,9 +81,9 @@ test ('dimension', () => { nullable: false, }) - expect (() => new DbColumn ('date )')).toThrow () - expect (() => new DbColumn ('decimal (10,zz)')).toThrow () - expect (() => new DbColumn ('decimal (+,1)')).toThrow () + expect (() => newCol ('date )')).toThrow () + expect (() => newCol ('decimal (10,zz)')).toThrow () + expect (() => newCol ('decimal (+,1)')).toThrow () }) @@ -107,13 +115,13 @@ test ('nullable', () => { nullable: false, }) - expect (() => new DbColumn ('=0')).toThrow () + expect (() => newCol ('=0')).toThrow () }) const tst_ref = (src, dst) => { - const col = new DbColumn (src) + const col = newCol (src) expect (col.reference.column).toBe (col) @@ -135,7 +143,7 @@ test ('reference', () => { tst_ref ('(~users)', {type: undefined, nullable: true, reference: {targetRelationName: 'users', on: {DELETE: 'SET NULL'}}}) tst_ref ('(~users)=current_user()', {type: undefined, nullable: false, default: 'current_user()', reference: {targetRelationName: 'users', on: {DELETE: 'SET DEFAULT'}}}) - expect (() => new DbColumn ('()')).toThrow () + expect (() => newCol ('()')).toThrow () expect (() => new DbReference ('(((')).toThrow () expect (() => new DbReference (')))')).toThrow () @@ -147,37 +155,37 @@ test ('typeDim', () => { const lang = new DbLang () { - const col = new DbColumn ({name: 'parent', ref: 'deps'}) + const col = newCol ({name: 'parent', ref: 'deps'}) col.setLang (lang) expect (col.typeDim).toBeUndefined } { - const col = new DbColumn ({name: 'dt', type: 'date'}) + const col = newCol ({name: 'dt', type: 'date'}) col.setLang (lang) expect (col.typeDim).toBe ('DATE') } { - const col = new DbColumn ({name: 'cc', type: 'char', size: 2}) + const col = newCol ({name: 'cc', type: 'char', size: 2}) col.setLang (lang) expect (col.typeDim).toBe ('CHAR(2)') } { - const col = new DbColumn ({name: 'amount', type: 'decimal', size: 10, scale: 2}) + const col = newCol ({name: 'amount', type: 'decimal', size: 10, scale: 2}) col.setLang (lang) expect (col.typeDim).toBe ('NUMERIC(10,2)') } { - const col = new DbColumn ({name: 'amount', type: 'char', size: 10, scale: undefined}) + const col = newCol ({name: 'amount', type: 'char', size: 10, scale: undefined}) col.setLang (lang) expect (col.typeDim).toBe ('CHAR(10)') } { - const col = new DbColumn ({name: 'amount', type: 'bool', size: undefined, scale: undefined}) + const col = newCol ({name: 'amount', type: 'bool', size: undefined, scale: undefined}) col.setLang (lang) expect (col.typeDim).toBe ('BOOL') } diff --git a/__tests__/DbCsvPrinter.js b/__tests__/DbCsvPrinter.js index 1945728..652f150 100644 --- a/__tests__/DbCsvPrinter.js +++ b/__tests__/DbCsvPrinter.js @@ -82,7 +82,7 @@ test ('infty', async () => { const p = new DbCsvPrinter ({ lang: new DbLang (), - columns: {id: 'int'}, + columns: {id: {type: 'int'}}, }) await new Promise ((ok, fail) => { diff --git a/__tests__/DbLang.js b/__tests__/DbLang.js index 710d6c8..31c628c 100644 --- a/__tests__/DbLang.js +++ b/__tests__/DbLang.js @@ -13,6 +13,15 @@ const src = Path.join (__dirname, 'data', 'root1') const lang = new DbLang () +const newCol = src => { + + if (typeof src === 'string') src = lang.parseColumn (src) + + return new DbColumn (src) + +} + + test ('getDbObjectClass', () => { expect (() => lang.getDbObjectClass ()).toThrow () @@ -171,50 +180,50 @@ test ('isAdequateColumnTypeDim', () => { expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('int'), - new DbColumn ('int') + newCol ('int'), + newCol ('int') ) ).toBe (true) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('int'), - new DbColumn ('smallint=0') + newCol ('int'), + newCol ('smallint=0') ) ).toBe (true) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('varchar(255)'), - new DbColumn ('varchar(10)') + newCol ('varchar(255)'), + newCol ('varchar(10)') ) ).toBe (true) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('char(5)'), - new DbColumn ('char(10)') + newCol ('char(5)'), + newCol ('char(10)') ) ).toBe (false) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('decimal(10, 3)'), - new DbColumn ('numeric(5, 2)') + newCol ('decimal(10, 3)'), + newCol ('numeric(5, 2)') ) ).toBe (true) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('decimal(5, 3)'), - new DbColumn ('numeric(10, 3)') + newCol ('decimal(5, 3)'), + newCol ('numeric(10, 3)') ) ).toBe (false) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('decimal(10, 2)'), - new DbColumn ('numeric(5, 3)') + newCol ('decimal(10, 2)'), + newCol ('numeric(5, 3)') ) ).toBe (false) @@ -224,50 +233,50 @@ test ('isAdequateColumnTypeDim', () => { expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('int'), - new DbColumn ('int') + newCol ('int'), + newCol ('int') ) ).toBe (true) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('int'), - new DbColumn ('smallint=0') + newCol ('int'), + newCol ('smallint=0') ) ).toBe (true) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('varchar(255)'), - new DbColumn ('varchar(10)') + newCol ('varchar(255)'), + newCol ('varchar(10)') ) ).toBe (true) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('char(5)'), - new DbColumn ('char(10)') + newCol ('char(5)'), + newCol ('char(10)') ) ).toBe (false) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('decimal(10, 3)'), - new DbColumn ('numeric(5, 2)') + newCol ('decimal(10, 3)'), + newCol ('numeric(5, 2)') ) ).toBe (true) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('decimal(5, 3)'), - new DbColumn ('numeric(10, 3)') + newCol ('decimal(5, 3)'), + newCol ('numeric(10, 3)') ) ).toBe (false) expect ( lang.isAdequateColumnTypeDim ( - new DbColumn ('decimal(10, 2)'), - new DbColumn ('numeric(5, 3)') + newCol ('decimal(10, 2)'), + newCol ('numeric(5, 3)') ) ).toBe (false) @@ -275,7 +284,7 @@ test ('isAdequateColumnTypeDim', () => { test ('genColumnDefinition', () => { - const column = new DbColumn ('int=1') + const column = newCol ('int=1') column.name = 'id' column.setLang (lang) @@ -295,43 +304,43 @@ test ('compareColumns', () => { expect ( lang.compareColumns ( - new DbColumn ('int'), - new DbColumn ('int') + newCol ('int'), + newCol ('int') ) ).toStrictEqual ([]) expect ( lang.compareColumns ( - new DbColumn ('int'), - new DbColumn ('int!') + newCol ('int'), + newCol ('int!') ) ).toStrictEqual (['nullable']) expect ( lang.compareColumns ( - new DbColumn ('int!'), - new DbColumn ('int=0') + newCol ('int!'), + newCol ('int=0') ) ).toStrictEqual (['default']) expect ( lang.compareColumns ( - new DbColumn ('bigint'), - new DbColumn ('int') + newCol ('bigint'), + newCol ('int') ) ).toStrictEqual ([]) expect ( lang.compareColumns ( - new DbColumn ('int'), - new DbColumn ('bigint') + newCol ('int'), + newCol ('bigint') ) ).toStrictEqual (['typeDim']) expect ( lang.compareColumns ( - new DbColumn ('numeric(5,2)'), - new DbColumn ('numeric(10,2)') + newCol ('numeric(5,2)'), + newCol ('numeric(10,2)') ) ).toStrictEqual (['typeDim']) diff --git a/__tests__/DbRelation.js b/__tests__/DbRelation.js index ee42d73..cd7a84f 100644 --- a/__tests__/DbRelation.js +++ b/__tests__/DbRelation.js @@ -1,4 +1,6 @@ -const {DbRelation} = require ('..') +const {DbRelation, DbLang} = require ('..') + +const lang = new DbLang () test ('bad', () => { @@ -29,7 +31,7 @@ test ('good', () => { name: 't', columns: { id: {type: 'int'}, - label: 'text', + label: lang.parseColumn ('text'), old_slack: null, }, pk: 'id', @@ -58,7 +60,7 @@ test ('good columns is function', () => { expect (this.name).toBe('t') - return { id: {type: 'int'} } + return { id: lang.parseColumn ('int') } }, pk: 'id', }) diff --git a/lib/DbCsvPrinter.js b/lib/DbCsvPrinter.js index 88c423e..0bac819 100644 --- a/lib/DbCsvPrinter.js +++ b/lib/DbCsvPrinter.js @@ -59,6 +59,8 @@ class DbCsvPrinter extends Transform { if (!Array.isArray (columns)) columns = Object.entries (columns).map (([name, def]) => { + if (typeof def === 'string') def = this.lang.parseColumn (def) + const col = new DbColumn (def) col.name = name diff --git a/lib/DbLang.js b/lib/DbLang.js index 34b0355..e9b3884 100644 --- a/lib/DbLang.js +++ b/lib/DbLang.js @@ -14,6 +14,15 @@ const DbTypeCharacter = require ('./model/types/DbTypeCharacter.js') const DbTypeDate = require ('./model/types/DbTypeDate.js') const DbTypeTimestamp = require ('./model/types/DbTypeTimestamp.js') +const CH_ROUND_CLOSE = ')'.charCodeAt (0) +const CH_CURLY_CLOSE = '}'.charCodeAt (0) +const CH_SLASH = '/'.charCodeAt (0) +const CH_BACK_SLASH = '\\'.charCodeAt (0) +const CH_QUESTION = '?'.charCodeAt (0) +const CH_EXCLAMATION = '!'.charCodeAt (0) + +const RE_INT = /^[1-9][0-9]*$/ + const CH_Q = "'", CH_QQ = '"' const Q_ESC = new stringEscape ([ @@ -95,6 +104,18 @@ class DbLang { createDbObject (options) { + { + + const {columns} = options; if (columns) + + for (const name in columns) + + if (typeof columns [name] === 'string') + + columns [name] = this.parseColumn (columns [name]) + + } + const clazz = this.getDbObjectClass (options) const dbObject = new clazz (options) @@ -560,6 +581,142 @@ class DbLang { } + parseColumn (src) { + + const o = {src} + + this.parseColumnComment (o) + this.parseColumnDefault (o) + this.parseColumnPattern (o) + this.parseColumnRange (o) + this.parseColumnNullable (o) + + o.isReference = o.type.startsWith ('(') + + if (!o.isReference) { + this.parseColumnDimension (o) + } + + return o + + } + + parseColumnComment (o) { + + const {src} = o, pos = src.lastIndexOf ('//') + + if (pos < 0) return o.type = src.trim () + + o.type = src.slice (0, pos).trim () + + o.comment = src.slice (pos + 2).trim () + + } + + parseColumnDefault (o) { + + const pos = o.type.indexOf ('='); if (pos < 0) return + + o.default = o.type.slice (pos + 1).trim () + + o.type = o.type.slice (0, pos).trim () + + } + + parseColumnPattern (o) { + + const last = o.default ? 'default' : 'type', src = o [last] + + let pos = src.length - 1; if (src.charCodeAt (pos) !== CH_SLASH) return + + while (true) { + + pos = src.lastIndexOf ('/', pos - 1) + + if (pos < 0) throw Error (`Invalid column definition: cannot find opening '/' for pattern in '${o.src}'`) + + if (pos === 0) throw Error (`Invalid column definition: the ${last} is empty in '${o.src}'`) + + if (src.charCodeAt (pos - 1) !== CH_BACK_SLASH) break + + } + + o.pattern = src.slice (pos + 1, -1) + + o [last] = src.slice (0, pos).trim () + + } + + parseColumnRange (o) { + + const last = o.default ? 'default' : 'type', src = o [last] + + if (src.charCodeAt (src.length - 1) !== CH_CURLY_CLOSE) return + + const begin = src.lastIndexOf ('{'); if (begin < 0) throw Error (`Invalid column definition: cannot find opening '{' for range in '${o.src}'`) + + const range = src.slice (begin + 1, -1) + + o [last] = src.slice (0, begin).trim () + + const pos = range.indexOf ('..'); if (pos < 0) throw Error (`Invalid column definition: cannot find '..' between '{' and '}' in '${o.src}'`) + + const min = range.slice (0, pos).trim (); if (min.length !== 0) o.min = min + + const max = range.slice (pos + 2).trim (); if (max.length !== 0) o.max = max + + } + + parseColumnNullable (o) { + + let nullable = !('default' in o) + + const src = o.type, {length} = src; if (length === 0) throw Error (`Invalid column definition: cannot determine type in '${o.src}'`) + + const override = nullable ? CH_EXCLAMATION : CH_QUESTION + + if (src.charCodeAt (length - 1) === override) { + + nullable = !nullable + + o.type = src.slice (0, -1).trim () + + } + + o.nullable = nullable + + } + + parseColumnDimension (o) { + + const src = o.type + + if (src.charCodeAt (src.length - 1) !== CH_ROUND_CLOSE) return + + const begin = src.lastIndexOf ('('); if (begin < 0) throw Error (`Invalid column definition: cannot find opening '(' for dimension in '${o.src}'`) + + let dimension = src.slice (begin + 1, -1) + + o.type = src.slice (0, begin).trim () + + const pos = dimension.indexOf (','); if (pos >= 0) { + + const scale = dimension.slice (pos + 1).trim () + + if (!RE_INT.test (scale)) throw Error (`Invalid column definition: not a positive integer as scale in '${o.src}'`) + + o.scale = parseInt (scale) + + dimension = dimension.slice (0, pos).trim () + + } + + if (!RE_INT.test (dimension)) throw Error (`Invalid column definition: not a positive integer as column dimension in '${o.src}'`) + + o.size = parseInt (dimension) + + } + } module.exports = DbLang \ No newline at end of file diff --git a/lib/model/DbColumn.js b/lib/model/DbColumn.js index 2952616..4467750 100644 --- a/lib/model/DbColumn.js +++ b/lib/model/DbColumn.js @@ -1,175 +1,23 @@ const DbReference = require ('./DbReference.js') -const CH_ROUND_OPEN = '('.charCodeAt (0) -const CH_ROUND_CLOSE = ')'.charCodeAt (0) -const CH_CURLY_CLOSE = '}'.charCodeAt (0) -const CH_SLASH = '/'.charCodeAt (0) -const CH_BACK_SLASH = '\\'.charCodeAt (0) -const CH_QUESTION = '?'.charCodeAt (0) -const CH_EXCLAMATION = '!'.charCodeAt (0) - -const RE_INT = /^[1-9][0-9]*$/ - class DbColumn { constructor (o) { - if (typeof o === 'string') { - - this.src = o - - this.parse () - - } - else { - - for (const k in o) this [k] = o [k] - - if (!('nullable' in this)) this.nullable = !('default' in this) - - } - - } - - parse () { - - const {src} = this, pos = src.lastIndexOf ('//') - - if (pos < 0) { - - this.type = src.trim () - - } - else { - - this.type = src.slice (0, pos).trim () + const {isReference} = o - this.comment = src.slice (pos + 2).trim () + for (const k in o) if (k !== 'isReference') this [k] = o [k] - } - - this.parseDefault () - this.parsePattern () - this.parseRange () - this.parseNullable () - - if (this.type.charCodeAt (0) === CH_ROUND_OPEN) { + if (isReference) { this.reference = new DbReference (this.type, this) - + this.type = undefined - - } - else { - - this.parseDimension () - - } - } + } - parseDefault () { - - const pos = this.type.indexOf ('='); if (pos < 0) return - - this.default = this.type.slice (pos + 1).trim () - - this.type = this.type.slice (0, pos).trim () - - } - - parsePattern () { - - const last = this.default ? 'default' : 'type', src = this [last] - - let pos = src.length - 1; if (src.charCodeAt (pos) !== CH_SLASH) return - - while (true) { - - pos = src.lastIndexOf ('/', pos - 1) - - if (pos < 0) throw Error (`Invalid column definition: cannot find opening '/' for pattern in '${this.src}'`) - - if (pos === 0) throw Error (`Invalid column definition: the ${last} is empty in '${this.src}'`) - - if (src.charCodeAt (pos - 1) !== CH_BACK_SLASH) break + if (!('nullable' in this)) this.nullable = !('default' in this) - } - - this.pattern = src.slice (pos + 1, -1) - - this [last] = src.slice (0, pos).trim () - - } - - parseRange () { - - const last = this.default ? 'default' : 'type', src = this [last] - - if (src.charCodeAt (src.length - 1) !== CH_CURLY_CLOSE) return - - const begin = src.lastIndexOf ('{'); if (begin < 0) throw Error (`Invalid column definition: cannot find opening '{' for range in '${this.src}'`) - - const range = src.slice (begin + 1, -1) - - this [last] = src.slice (0, begin).trim () - - const pos = range.indexOf ('..'); if (pos < 0) throw Error (`Invalid column definition: cannot find '..' between '{' and '}' in '${this.src}'`) - - const min = range.slice (0, pos).trim (); if (min.length !== 0) this.min = min - - const max = range.slice (pos + 2).trim (); if (max.length !== 0) this.max = max - - } - - parseNullable () { - - let nullable = !('default' in this) - - const src = this.type, {length} = src; if (length === 0) throw Error (`Invalid column definition: cannot determine type in '${this.src}'`) - - const override = nullable ? CH_EXCLAMATION : CH_QUESTION - - if (src.charCodeAt (length - 1) === override) { - - nullable = !nullable - - this.type = src.slice (0, -1).trim () - - } - - this.nullable = nullable - - } - - parseDimension () { - - const src = this.type - - if (src.charCodeAt (src.length - 1) !== CH_ROUND_CLOSE) return - - const begin = src.lastIndexOf ('('); if (begin < 0) throw Error (`Invalid column definition: cannot find opening '(' for dimension in '${this.src}'`) - - let dimension = src.slice (begin + 1, -1) - - this.type = src.slice (0, begin).trim () - - const pos = dimension.indexOf (','); if (pos >= 0) { - - const scale = dimension.slice (pos + 1).trim () - - if (!RE_INT.test (scale)) throw Error (`Invalid column definition: not a positive integer as scale in '${this.src}'`) - - this.scale = parseInt (scale) - - dimension = dimension.slice (0, pos).trim () - - } - - if (!RE_INT.test (dimension)) throw Error (`Invalid column definition: not a positive integer as column dimension in '${this.src}'`) - - this.size = parseInt (dimension) - } setLang (lang) { From 57db4911fad172c2d74cfa9a111ad62ecf94a0e6 Mon Sep 17 00:00:00 2001 From: Dmitry Ovsyanko Date: Wed, 16 Apr 2025 14:45:17 +0300 Subject: [PATCH 3/5] DbLang: parseColumn* code cleanup --- lib/DbLang.js | 108 +++++++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/lib/DbLang.js b/lib/DbLang.js index e9b3884..86a52d8 100644 --- a/lib/DbLang.js +++ b/lib/DbLang.js @@ -583,59 +583,57 @@ class DbLang { parseColumn (src) { - const o = {src} + const o = {src: src.trim ()} - this.parseColumnComment (o) - this.parseColumnDefault (o) - this.parseColumnPattern (o) - this.parseColumnRange (o) - this.parseColumnNullable (o) + try { - o.isReference = o.type.startsWith ('(') - - if (!o.isReference) { - this.parseColumnDimension (o) + this.parseColumnComment (o) + this.parseColumnPattern (o) + this.parseColumnRange (o) + this.parseColumnDefault (o) + + if (o.src.length === 0) throw Error (`Cannot determine the type`) + + this.parseColumnNullable (o) + + o.isReference = o.type.startsWith ('(') + + if (!o.isReference) { + this.parseColumnDimension (o) + } + + o.src = src + + return o + } + catch (cause) { + + if (o.src.length === 0) throw Error (`Invalid column definition: '${src}'`, {cause}) - return o + } } parseColumnComment (o) { - const {src} = o, pos = src.lastIndexOf ('//') - - if (pos < 0) return o.type = src.trim () - - o.type = src.slice (0, pos).trim () + const {src} = o, pos = src.lastIndexOf ('//'); if (pos < 0) return - o.comment = src.slice (pos + 2).trim () - - } - - parseColumnDefault (o) { - - const pos = o.type.indexOf ('='); if (pos < 0) return - - o.default = o.type.slice (pos + 1).trim () + o.comment = src.substring (pos + 2).trimStart () - o.type = o.type.slice (0, pos).trim () + o.src = src.substring (0, pos).trimEnd () } - + parseColumnPattern (o) { - const last = o.default ? 'default' : 'type', src = o [last] + const {src} = o let pos = src.length - 1; if (src.charCodeAt (pos) !== CH_SLASH) return while (true) { - pos = src.lastIndexOf ('/', pos - 1) - - if (pos < 0) throw Error (`Invalid column definition: cannot find opening '/' for pattern in '${o.src}'`) - - if (pos === 0) throw Error (`Invalid column definition: the ${last} is empty in '${o.src}'`) + pos = src.lastIndexOf ('/', pos - 1); if (pos < 0) throw Error (`Cannot find opening '/' for the pattern`) if (src.charCodeAt (pos - 1) !== CH_BACK_SLASH) break @@ -643,48 +641,60 @@ class DbLang { o.pattern = src.slice (pos + 1, -1) - o [last] = src.slice (0, pos).trim () - + o.src = src.substring (0, pos).trimEnd () + } parseColumnRange (o) { - const last = o.default ? 'default' : 'type', src = o [last] + const {src} = o if (src.charCodeAt (src.length - 1) !== CH_CURLY_CLOSE) return - const begin = src.lastIndexOf ('{'); if (begin < 0) throw Error (`Invalid column definition: cannot find opening '{' for range in '${o.src}'`) + const begin = src.lastIndexOf ('{'); if (begin < 0) throw Error (`Cannot find the opening '{' for range`) const range = src.slice (begin + 1, -1) - o [last] = src.slice (0, begin).trim () + o.src = src.substring (0, begin).trimEnd () - const pos = range.indexOf ('..'); if (pos < 0) throw Error (`Invalid column definition: cannot find '..' between '{' and '}' in '${o.src}'`) + const pos = range.indexOf ('..'); if (pos < 0) throw Error (`Cannot find '..' between '{' and '}'`) - const min = range.slice (0, pos).trim (); if (min.length !== 0) o.min = min + const min = range.substring (0, pos).trim (); if (min.length !== 0) o.min = min - const max = range.slice (pos + 2).trim (); if (max.length !== 0) o.max = max + const max = range.substring (pos + 2).trim (); if (max.length !== 0) o.max = max } - + + parseColumnDefault (o) { + + const {src} = o, pos = src.indexOf ('='); if (pos < 0) return + + o.default = src.substring (pos + 1).trimStart () + + if (o.default.length === 0) throw Error (`Empty (but not NULL neither '') default definition`) + + o.src = src.substring (0, pos).trimEnd () + + } + parseColumnNullable (o) { let nullable = !('default' in o) - const src = o.type, {length} = src; if (length === 0) throw Error (`Invalid column definition: cannot determine type in '${o.src}'`) - - const override = nullable ? CH_EXCLAMATION : CH_QUESTION + const {src} = o, {length} = src, override = nullable ? CH_EXCLAMATION : CH_QUESTION if (src.charCodeAt (length - 1) === override) { nullable = !nullable - o.type = src.slice (0, -1).trim () + o.src = src.slice (0, -1).trim () } o.nullable = nullable + o.type = o.src + } parseColumnDimension (o) { @@ -693,7 +703,7 @@ class DbLang { if (src.charCodeAt (src.length - 1) !== CH_ROUND_CLOSE) return - const begin = src.lastIndexOf ('('); if (begin < 0) throw Error (`Invalid column definition: cannot find opening '(' for dimension in '${o.src}'`) + const begin = src.lastIndexOf ('('); if (begin < 0) throw Error (`Cannot find opening '(' for dimension`) let dimension = src.slice (begin + 1, -1) @@ -703,7 +713,7 @@ class DbLang { const scale = dimension.slice (pos + 1).trim () - if (!RE_INT.test (scale)) throw Error (`Invalid column definition: not a positive integer as scale in '${o.src}'`) + if (!RE_INT.test (scale)) throw Error (`Not a positive integer as scale`) o.scale = parseInt (scale) @@ -711,7 +721,7 @@ class DbLang { } - if (!RE_INT.test (dimension)) throw Error (`Invalid column definition: not a positive integer as column dimension in '${o.src}'`) + if (!RE_INT.test (dimension)) throw Error (`Not a positive integer as column dimension`) o.size = parseInt (dimension) From a9ea1d15293c69f297dde37ad7d04077098da5c5 Mon Sep 17 00:00:00 2001 From: Dmitry Ovsyanko Date: Fri, 18 Apr 2025 13:27:24 +0300 Subject: [PATCH 4/5] DbLang parseColumnRange INCOMPATIBLE CHANGE '[]' instead of '{}' --- __tests__/DbColumn.js | 18 +++++++++--------- lib/DbLang.js | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/__tests__/DbColumn.js b/__tests__/DbColumn.js index 01ab274..02c6a57 100644 --- a/__tests__/DbColumn.js +++ b/__tests__/DbColumn.js @@ -53,14 +53,14 @@ test ('pattern', () => { test ('range', () => { - tst ('int {0..10}', {type: 'int', min: '0', max: '10', nullable: true}) - tst ('int {0..}', {type: 'int', min: '0', nullable: true}) - tst ('int {..10}', {type: 'int', max: '10', nullable: true}) + tst ('int [0..10]', {type: 'int', min: '0', max: '10', nullable: true}) + tst ('int [0..]', {type: 'int', min: '0', nullable: true}) + tst ('int [..10]', {type: 'int', max: '10', nullable: true}) - tst ('date = 1980-01-01 { 1970-01-01 .. NOW } /-01$/ // created ', {type: 'date', min: '1970-01-01', default: '1980-01-01', max: 'NOW', pattern: '-01$', comment: 'created', nullable: false}) + tst ('date = 1980-01-01 [ 1970-01-01 .. NOW ] /-01$/ // created ', {type: 'date', min: '1970-01-01', default: '1980-01-01', max: 'NOW', pattern: '-01$', comment: 'created', nullable: false}) - expect (() => newCol ('date }')).toThrow () - expect (() => newCol ('date {}')).toThrow () + expect (() => newCol ('date ]')).toThrow () + expect (() => newCol ('date []')).toThrow () }) @@ -69,7 +69,7 @@ test ('dimension', () => { tst ('char(1)', {type: 'char', size: 1, nullable: true}) tst ('decimal (10, 2)', {type: 'decimal', size: 10, scale: 2, nullable: true}) - tst (' \t \t decimal (10, 2) = 0 { 0.00 .. 1000.00 } /00$/ // \t\t\t salary ', { + tst (' \t \t decimal (10, 2) = 0 [ 0.00 .. 1000.00 ] /00$/ // \t\t\t salary ', { type: 'decimal', size: 10, scale: 2, @@ -92,7 +92,7 @@ test ('nullable', () => { tst ('int!', {type: 'int', nullable: false}) tst ('int?=0', {type: 'int', default: '0', nullable: true}) - tst (' \t \t decimal (10, 2) ?= 0 { 0.00 .. 1000.00 } /00$/ // \t\t\t salary ', { + tst (' \t \t decimal (10, 2) ?= 0 [ 0.00 .. 1000.00 ] /00$/ // \t\t\t salary ', { type: 'decimal', size: 10, scale: 2, @@ -104,7 +104,7 @@ test ('nullable', () => { nullable: true, }) - tst (' \t \t decimal (10, 2) ! { 0.00 .. 1000.00 } /00$/ // \t\t\t salary ', { + tst (' \t \t decimal (10, 2) ! [ 0.00 .. 1000.00 ] /00$/ // \t\t\t salary ', { type: 'decimal', size: 10, scale: 2, diff --git a/lib/DbLang.js b/lib/DbLang.js index 86a52d8..9407984 100644 --- a/lib/DbLang.js +++ b/lib/DbLang.js @@ -15,7 +15,7 @@ const DbTypeDate = require ('./model/types/DbTypeDate.js') const DbTypeTimestamp = require ('./model/types/DbTypeTimestamp.js') const CH_ROUND_CLOSE = ')'.charCodeAt (0) -const CH_CURLY_CLOSE = '}'.charCodeAt (0) +const CH_SQUARE_CLOSE = ']'.charCodeAt (0) const CH_SLASH = '/'.charCodeAt (0) const CH_BACK_SLASH = '\\'.charCodeAt (0) const CH_QUESTION = '?'.charCodeAt (0) @@ -649,15 +649,15 @@ class DbLang { const {src} = o - if (src.charCodeAt (src.length - 1) !== CH_CURLY_CLOSE) return + if (src.charCodeAt (src.length - 1) !== CH_SQUARE_CLOSE) return - const begin = src.lastIndexOf ('{'); if (begin < 0) throw Error (`Cannot find the opening '{' for range`) + const begin = src.lastIndexOf ('['); if (begin < 0) throw Error (`Cannot find the opening '[' for range`) const range = src.slice (begin + 1, -1) o.src = src.substring (0, begin).trimEnd () - const pos = range.indexOf ('..'); if (pos < 0) throw Error (`Cannot find '..' between '{' and '}'`) + const pos = range.indexOf ('..'); if (pos < 0) throw Error (`Cannot find '..' between '[' and ']'`) const min = range.substring (0, pos).trim (); if (min.length !== 0) o.min = min From 077d37abecc1d95728a54372a9014800b49b6241 Mon Sep 17 00:00:00 2001 From: Dmitry Ovsyanko Date: Fri, 18 Apr 2025 16:33:33 +0300 Subject: [PATCH 5/5] DbLang +parseColumnExtra --- __tests__/DbColumn.js | 5 ++++- lib/DbLang.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/__tests__/DbColumn.js b/__tests__/DbColumn.js index 02c6a57..a767300 100644 --- a/__tests__/DbColumn.js +++ b/__tests__/DbColumn.js @@ -104,7 +104,7 @@ test ('nullable', () => { nullable: true, }) - tst (' \t \t decimal (10, 2) ! [ 0.00 .. 1000.00 ] /00$/ // \t\t\t salary ', { + tst (` \t \t decimal (10, 2) ! [ 0.00 .. 1000.00 ] /00$/ {compression: '{gz}', ttl: 600} // \t\t\t salary `, { type: 'decimal', size: 10, scale: 2, @@ -113,9 +113,12 @@ test ('nullable', () => { pattern: '00$', comment: 'salary', nullable: false, + compression: '{gz}', + ttl: 600, }) expect (() => newCol ('=0')).toThrow () + expect (() => newCol ('int=0}')).toThrow () }) diff --git a/lib/DbLang.js b/lib/DbLang.js index 9407984..ae16129 100644 --- a/lib/DbLang.js +++ b/lib/DbLang.js @@ -16,6 +16,8 @@ const DbTypeTimestamp = require ('./model/types/DbTypeTimestamp.js') const CH_ROUND_CLOSE = ')'.charCodeAt (0) const CH_SQUARE_CLOSE = ']'.charCodeAt (0) +const CH_CURLY_OPEN = '{'.charCodeAt (0) +const CH_CURLY_CLOSE = '}'.charCodeAt (0) const CH_SLASH = '/'.charCodeAt (0) const CH_BACK_SLASH = '\\'.charCodeAt (0) const CH_QUESTION = '?'.charCodeAt (0) @@ -588,6 +590,7 @@ class DbLang { try { this.parseColumnComment (o) + this.parseColumnExtra (o) this.parseColumnPattern (o) this.parseColumnRange (o) this.parseColumnDefault (o) @@ -625,6 +628,36 @@ class DbLang { } + parseColumnExtra (o) { + + const {src} = o + + let pos = src.length - 1; if (src.charCodeAt (pos) !== CH_CURLY_CLOSE) return + + let level = 1; while (level !== 0) { + + if (-- pos < 0) throw Error ('Unbalanced "}"') + + switch (src.charCodeAt (pos)) { + + case CH_CURLY_CLOSE: + level ++ + break + + case CH_CURLY_OPEN: + level -- + break + + } + + } + + for (const [k, v] of Object.entries (new Function ('return ' + src.substring (pos)) ())) o [k] = v + + o.src = src.substring (0, pos).trimEnd () + + } + parseColumnPattern (o) { const {src} = o