diff --git a/readme.md b/readme.md index 197a59e..d023b05 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ # DBlang -sql Querys with js ot ts Syntax. +sql Querys with js or ts Syntax. [![status-badge](https://ci.jusax.de/api/badges/jusax23/dblang/status.svg)](https://ci.jusax.de/jusax23/dblang) \ No newline at end of file diff --git a/src/db.ts b/src/db.ts index b7187fd..5566360 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,37 +1,38 @@ import mariadb from 'mariadb'; import { checkConstraint, Constraint, Datatype, uniqueConstraint } from './dbStructure'; import { Handler } from './defaultHandler'; -import { Query, selectQuery } from './query'; -import { attributeSettings, extendedAttributeSettings, onAction, primaryData, serializeReturn } from './types'; +import { Query } from './query'; +import { attributeSettings, extendedAttributeSettings, onAction } from './types'; export class DB { - tables:Table[] = []; + tables: Table[] = []; handler: Handler; name: string; - //pool:mariadb.Pool; - constructor(/*{ host, user, password, database, connectionLimit = 5 }*/) { - //this.pool = mariadb.createPool({ host, user, password, database, connectionLimit, multipleStatements: true }); + pool: mariadb.Pool; + constructor({ host, user, password, database, connectionLimit = 5 }: { host: string, user: string, password: string, database: string, connectionLimit: number }) { + this.pool = mariadb.createPool({ host, user, password, database, connectionLimit, multipleStatements: true }); this.handler = new Handler(); - this.name = "notimplemented" + this.name = database; } async query(query: Query) { console.log(query); - //return this.pool.query(query); + return await this.pool.query(query); } getHandler() { return this.handler; } - sync(){ + async sync() { let handler = this.getHandler(); - this.tables.forEach(t=>{ - console.log(new Query(handler.builders.query(handler.querys.create(handler,t)))); - }) + await handler.syncDB(this, handler); + //this.tables.forEach(t=>{ + // console.log(handler.builders.query(handler.querys.create(handler,t))); + //}) } newTable(name: string) { - let tabel = new Table(name,this); + let tabel = new Table(name, this); this.tables.push(tabel); return tabel; } @@ -42,39 +43,40 @@ export class Attribute { table: Table; ops: attributeSettings; type: Datatype; - constructor(name: string, table:Table, type: Datatype, ops: attributeSettings) { + constructor(name: string, table: Table, type: Datatype, ops: attributeSettings) { this.name = name; - this.ops = ops; this.type = type; this.table = table; - - if(ops.check != null){ + this.ops = ops; + + if (ops.check != null) { + if(typeof ops.check == "function")ops.check = ops.check(this); table.addConstraint(new checkConstraint( - table.dbLangDatabaseInstance.name+"_"+table.dbLangTableName+"_"+name+"_check_constraint", + table.dbLangDatabaseInstance.name + "_" + table.dbLangTableName + "_" + name + "_check_constraint", ops.check )) } - if(ops.unique != null){ + if (ops.unique != null) { table.addConstraint(new uniqueConstraint( - table.dbLangDatabaseInstance.name+"_"+table.dbLangTableName+" "+name+"_unique_constraint", + table.dbLangDatabaseInstance.name + "_" + table.dbLangTableName + " " + name + "_unique_constraint", [this] )) } } - serializeDatatype(handler : Handler){ + serializeDatatype(handler: Handler) { return this.type.serialize(handler); } - serializeSettings(handler : Handler){ - return handler.builders.attributeSettings(handler,this); + serializeSettings(handler: Handler) { + return handler.builders.attributeSettings(handler, this); } - serialize(handler : Handler) { + serialize(handler: Handler) { return handler.builders.escapeID(this.name); } - toString(handler : Handler = this.table.dbLangDatabaseInstance.getHandler()) { - return this.table.serialize(handler)+"."+this.serialize(handler); + toString(handler: Handler = this.table.dbLangDatabaseInstance.getHandler()) { + return this.table.serialize(handler) + "." + this.serialize(handler); } - toStringFunc(handler : Handler){ - return this.table.serialize(handler)+"("+this.serialize(handler)+")"; + toStringFunc(handler: Handler) { + return this.table.serialize(handler) + "(" + this.serialize(handler) + ")"; } } @@ -84,34 +86,34 @@ export class Table { dbLangTableAttributes: { [key: string]: Attribute; } = {}; dbLangDatabaseInstance: DB; dbLangConstrains: Constraint[] = []; - [key: string]: Attribute | any - constructor(name: string, db:DB) { + [key: string]: Attribute | any; + constructor(name: string, db: DB) { this.dbLangTableName = name; this.dbLangDatabaseInstance = db; } - serialize(handler : Handler) { + serialize(handler: Handler) { return this.toString(handler); } - toString(handler : Handler = this.dbLangDatabaseInstance.getHandler()) { + toString(handler: Handler = this.dbLangDatabaseInstance.getHandler()) { return handler.builders.escapeID(this.dbLangTableName); } addAttribute(name: string, type: Datatype, ops: attributeSettings = {}, noErrorOnNameConflict = false) { - if(this.dbLangTableAttributes[name] != null) throw new Error("You are tring to create an Attribute twise!"); - let attr = new Attribute(name,this,type,ops); + if (this.dbLangTableAttributes[name] != null) throw new Error("You are tring to create an Attribute twise!"); + let attr = new Attribute(name, this, type, ops); this.dbLangTableAttributes[name] = attr; - if (["serialize", "toString", "addAttribute", "dbLangTableName", "dbLangTableAttributes", "dbLangDatabaseInstance","addConstraint","addAttributes"].includes(name)) { + if (["serialize", "toString", "addAttribute", "dbLangTableName", "dbLangTableAttributes", "dbLangDatabaseInstance", "addConstraint", "addAttributes"].includes(name)) { if (!noErrorOnNameConflict) throw new Error("You cannot name Attribute like Methode of this Table!"); } else { this[name] = attr; } return attr; } - addAttributes(list:{[key: string]:extendedAttributeSettings}):{[key: string]:Attribute} { - return Object.fromEntries(Object.entries(list).map(([k,a])=>{ - return [k,this.addAttribute(k,a.type,a)]; + addAttributes(list: { [key: string]: (extendedAttributeSettings) }): { [key: string]: Attribute } { + return Object.fromEntries(Object.entries(list).map(([k, a]) => { + return [k, this.addAttribute(k, a.type, a)]; })); } - addConstraint(c:Constraint){ + addConstraint(c: Constraint) { c.check(this); this.dbLangConstrains.push(c); } diff --git a/src/dbStructure.ts b/src/dbStructure.ts index d3ecde2..b5fe4fd 100644 --- a/src/dbStructure.ts +++ b/src/dbStructure.ts @@ -64,7 +64,7 @@ export class checkConstraint implements Constraint{ this.checkQuery = check; } check(attr: Table): boolean { - throw new Error("Method not implemented."); + return true; } uses(attr: Attribute): boolean { throw new Error("Method not implemented."); @@ -76,19 +76,19 @@ export class checkConstraint implements Constraint{ export class uniqueConstraint implements Constraint{ name: string; - attr: Attribute[]; - constructor(name:string, attr:Attribute[]){ + attrs: Attribute[]; + constructor(name:string, attrs:Attribute[]){ this.name = name; - this.attr = attr; + this.attrs = attrs; } check(table: Table): boolean|string { - for(let i = 0; i < this.attr.length; i++){ - if(this.attr[i].ops.primaryKey) return "Can not combine unique Constraint and primary key"; + for(let i = 0; i < this.attrs.length; i++){ + if(this.attrs[i].ops.primaryKey) return "Can not combine unique Constraint and primary key"; } return false; } uses(attr: Attribute): boolean { - throw new Error("Method not implemented."); + return this.attrs.includes(attr); } serialize(handler: Handler): QueryBuilder { throw new Error("Method not implemented."); diff --git a/src/defaultHandler.ts b/src/defaultHandler.ts index 156ea49..9aa48af 100644 --- a/src/defaultHandler.ts +++ b/src/defaultHandler.ts @@ -1,12 +1,115 @@ import { Attribute, DB, Table } from "./db" -import { Aggregation, Modifier } from "./dbStructure" -import { QueryBuilder, selectQuery } from "./query" +import { Aggregation, checkConstraint, Constraint, Datatype, Modifier, uniqueConstraint } from "./dbStructure" +import { Query, QueryBuilder, selectQuery } from "./query" import { allModifierInput, primaryData, serializeReturn } from "./types" export class Handler { - /*syncDB(db : DB){ + async syncDB(db: DB, handler: Handler, deleteInDB: boolean = false) { + console.log("start sync"); - }*/ + let gd = new QueryBuilder(); + gd.addCode(`SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where CONSTRAINT_SCHEMA = `); + gd.addInjection(db.name); + gd.addCode(` or TABLE_SCHEMA = `); + gd.addInjection(db.name); + gd.addCode(`;select * from information_schema.table_constraints where CONSTRAINT_SCHEMA = `); + gd.addInjection(db.name) + gd.addCode(` or TABLE_SCHEMA = `); + gd.addInjection(db.name); + + let [key, constraints] = await db.query(handler.builders.query(gd)); + + + let tableData = []; + for (let i = 0; i < db.tables.length; i++) { + const table = db.tables[i]; + try { + const tableDataBuilder = new QueryBuilder(); + tableDataBuilder.addCode("DESCRIBE "); + tableDataBuilder.addCode(table.serialize(handler)); + tableData[i] = Object.fromEntries((await db.query(handler.builders.query(tableDataBuilder))).map((d: any) => [d.Field, d])); + } catch (_) { + tableData[i] = null; + } + } + + const del = new QueryBuilder(); + const create = new QueryBuilder(); + + for (let i = 0; i < constraints.length; i++) { + const c = constraints[i]; + if (c.CONSTRAINT_TYPE == "CHECK") { + del.appendEnding(handler.querys.removeCheck(handler, c.TABLE_NAME, c.CONSTRAINT_NAME)); + } + } + // TODO: delete old constraints/tables + + function freeForUpdate(a: Attribute) { + for (let i = 0; i < key.length; i++) { + const k = key[i]; + if ( + k.REFERENCED_TABLE_NAME == a.table.dbLangTableName + && k.REFERENCED_TABLE_NAME == a.name + ) { + del.appendEnding(handler.querys.removeForeignKey(handler, k.TABLE_NAME, k.CONSTRAINT_NAME)); + } + } + } + + //create tables + for (let i = 0; i < db.tables.length; i++) { + const table = db.tables[i]; + const tableD = tableData[i]; + let changePrimary = false; + if (tableD == null) { + create.appendEnding(handler.querys.create(handler, table)); + changePrimary = true; + } else { + let keys = Object.keys(table.dbLangTableAttributes); + for (let j = 0; j < keys.length; j++) { + const a = table.dbLangTableAttributes[keys[j]]; + const attrData = tableD[keys[j]]; + if (attrData == null) { + create.appendEnding(handler.querys.addColumn(handler, a)); + changePrimary = true; + } else if ( + !handler.builders.compareDatatypes(handler, a.type, attrData.Type) || + a.ops.default != attrData.Default || + (!!a.ops.notNull || !!a.ops.autoIncrement || !!a.ops.primaryKey) != (attrData.Null == "NO") || + (!!a.ops.autoIncrement) != (attrData.Extra == "auto_increment") + ) { + console.log(!handler.builders.compareDatatypes(handler, a.type, attrData.Type), "|", + a.ops.default, attrData.Default, "|", + (!!a.ops.notNull || !!a.ops.autoIncrement || !!a.ops.primaryKey), (attrData.Null == "NO"), "|", + (!!a.ops.autoIncrement), (attrData.Extra == "auto_increment")); + freeForUpdate(a); + create.appendEnding(handler.querys.changeColumn(handler, a)); + } + if (attrData == null) { + changePrimary = true; + } else { + if ((attrData.Key == "PRI") != (!!a.ops.primaryKey)) { + freeForUpdate(a); + changePrimary = true; + } + } + } + } + if (changePrimary) { + create.appendEnding(handler.querys.removePrimaryKey(handler, table)); + create.appendEnding(handler.querys.addPrimaryKey(handler, table)); + } + for (let j = 0; j < table.dbLangConstrains.length; j++) { + const c = table.dbLangConstrains[j]; + if (c instanceof checkConstraint) { + create.appendEnding(handler.querys.addCheck(handler, table, c)); + } + } + // TODO: unique + } + if (!create.isEmpty()) del.append(create); + if (!del.isEmpty()) await db.query(handler.builders.query(del)); + } querys = { select: (handler: Handler, q: selectQuery): QueryBuilder => { @@ -26,11 +129,15 @@ export class Handler { builder.append(q.havingD.serialize(handler)); } } - if (q.havingD) { builder.addCode(" limit "); builder.addInjection(q.limitD); } + + /*builder.setHandler((json)=>{ + return json; + });*/ + return builder; }, create: (handler: Handler, table: Table): QueryBuilder => { @@ -46,16 +153,102 @@ export class Handler { builder.addCode(")"); return builder; }, + addColumn: (handler: Handler, attr: Attribute): QueryBuilder => { + const builder = new QueryBuilder(); + builder.addCode(`alter table `); + builder.addCode(attr.table.toString(handler)); + builder.addCode(` add if not exists `); + builder.addCode(attr.serialize(handler) + " "); + builder.append(attr.serializeSettings(handler)); + return builder; + }, + changeColumn: (handler: Handler, attr: Attribute): QueryBuilder => { + const builder = new QueryBuilder(); + builder.addCode(`alter table `); + builder.addCode(attr.table.toString(handler)); + builder.addCode(` change `); + builder.addCode(attr.serialize(handler) + " "); + builder.addCode(attr.serialize(handler) + " "); + builder.append(attr.serializeSettings(handler)); + return builder; + }, + addForeignKey: (handler: Handler, table: Table) => { + const builder = new QueryBuilder(); - } + return builder; + }, + removeForeignKey: (handler: Handler, tablenName: string, name: string) => { + const builder = new QueryBuilder(); + builder.addCode("ALTER TABLE "); + builder.addCode(handler.builders.escapeID(tablenName)); + builder.addCode(" DROP FOREIGN KEY IF EXISTS "); + builder.addCode(handler.builders.escapeID(name)); + builder.addCode(";ALTER TABLE "); + builder.addCode(handler.builders.escapeID(tablenName)); + builder.addCode(" DROP INDEX IF EXISTS "); + builder.addCode(handler.builders.escapeID(name)); - constraints = { - // check constraints - listPrimaryKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), - listForeignKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), - listUniqueKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), - listChecks: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), - // add constraints + return builder; + }, + addPrimaryKey: (handler: Handler, table: Table) => { + const qb = new QueryBuilder(); + qb.addCode("ALTER TABLE "); + qb.addCode(table.serialize(handler)); + qb.addCode(" add PRIMARY KEY if not exists ("); + qb.addCodeCommaSeperated( + Object.entries(table.dbLangTableAttributes) + .filter(([n, attr]) => !!attr.ops.primaryKey) + .map(([n, attr]) => handler.builders.escapeID(n)) + ); + qb.addCode(")"); + return qb; + }, + removePrimaryKey: (handler: Handler, table: Table) => { + const qb = new QueryBuilder(); + qb.addCode("ALTER TABLE "); + qb.addCode(table.serialize(handler)); + qb.addCode(" DROP INDEX IF EXISTS `PRIMARY`"); + return qb; + }, + removeCheck: (handler: Handler, table: string, name: string) => { + const qb = new QueryBuilder(); + qb.addCode("ALTER TABLE "); + qb.addCode(handler.builders.escapeID(table)); + qb.addCode(" DROP CONSTRAINT IF EXISTS "); + qb.addCode(handler.builders.escapeID(name)); + return qb; + }, + addCheck: (handler: Handler, table: Table, c: checkConstraint) => { + const qb = new QueryBuilder(); + qb.addCode(`ALTER TABLE `); + qb.addCode(table.serialize(handler)); + qb.addCode(` ADD CONSTRAINT `); + qb.addCode(handler.builders.escapeID(c.name)); + qb.addCode(` CHECK (`); + qb.append(c.checkQuery.serialize(handler)); + qb.addCode(")"); + return qb; + }, + removeUnique: (handler: Handler, table: string, name: string) => { + const qb = new QueryBuilder(); + qb.addCode("ALTER TABLE "); + qb.addCode(handler.builders.escapeID(table)); + qb.addCode(" DROP INDEX IF EXISTS "); + qb.addCode(handler.builders.escapeID(name)); + return qb; + }, + addUnique: (handler: Handler, table: Table, u: uniqueConstraint) => { + const qb = new QueryBuilder(); + qb.addCode("ALTER TABLE "); + qb.addCode(table.serialize(handler)); + qb.addCode(" ADD CONSTRAINT "); + qb.addCode(handler.builders.escapeID(u.name)); + qb.addCode(" UNIQUE ("); + qb.addCodeCommaSeperated(u.attrs.map(a => a.serialize(handler))); + qb.addCode(")"); + return qb; + }, + /*// add constraints appPrimaryKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), appForeignKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), addUniqueKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), @@ -65,10 +258,15 @@ export class Handler { dropForeignKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), dropUniqueKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), dropChecks: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + // check constraints + listPrimaryKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + listForeignKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + listUniqueKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + listChecks: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(),*/ } builders = { - query: (qb: QueryBuilder): serializeReturn => { + query: (qb: QueryBuilder): Query => { let args: primaryData[] = []; let sql = ""; for (let i = 0; i < qb.list.length; i++) { @@ -80,7 +278,7 @@ export class Handler { sql += data; } } - return [sql, args]; + return new Query([sql, args]); }, attributeSettings: (handler: Handler, a: Attribute): QueryBuilder => { @@ -89,38 +287,34 @@ export class Handler { if (a.ops.autoIncrement) { builder.addCode(" auto_increment"); } - /*if (a.ops.primaryKey) { - sql += " primary key"; - }*/ if (a.ops.default != null) { if (a.ops.autoIncrement || a.ops.primaryKey || a.ops.notNull) throw new Error(`Can not set default when autoIncrement, primaryKey or notNull ist set on Attribute: ${a.toStringFunc(handler)}`); builder.addCode(" default "); builder.addInjection(a.ops.default); } - - /*if (a.ops.unique != null) { - if (!a.ops.autoIncrement && !a.ops.primaryKey){ - sql += " unique"; - } - }*/ if (a.ops.notNull != null) { if (!a.ops.autoIncrement && !a.ops.primaryKey && a.ops.default == null) { builder.addCode(" not null"); - } - } - - /*if (a.ops.foreginKey != null) { - sql += ` foreign key references (${a.ops.foreginKey.link.toStringFunc(handler)})` - }*/ - + } else builder.addCode(" null"); + } else builder.addCode(" null"); return builder; }, escapeID: (key: string): string => { if (!key || key === "" || key.includes('\u0000')) throw new Error("Can not escape empty key or with null unicode!"); if (key.match(/^`.+`$/g)) return key; return `\`${key.replace(/`/g, '``')}\``; - } + }, + compareDatatypes: (handler: Handler, attr: Datatype, curr: String) => { + let qb = attr.serialize(handler); + let sql = ""; + for (let i = 0; i < qb.list.length; i++) { + const [inject, data] = qb.list[i]; + sql += data; + } + + return curr.split(" ").join("").startsWith(sql.split(" ").join("")); + }, } @@ -200,7 +394,7 @@ export class Handler { double: dataTypeDblNum("double"), decimal: dataTypeDblNum("decimal"), - date: dataTypeNoArg("data"), + date: dataTypeNoArg("date"), datetime: dataTypeNoArg("datatime"), timestamp: dataTypeNoArg("timestamp"), time: dataTypeNoArg("time"), diff --git a/src/query.ts b/src/query.ts index 4cf4e16..3debfff 100644 --- a/src/query.ts +++ b/src/query.ts @@ -16,6 +16,7 @@ export class Query { export class QueryBuilder { //injekt and data list: ([boolean, primaryData])[] = []; + constructor(l?: ({ inject?: boolean, data: primaryData })[]) { if (Array.isArray(l)) for (let i = 0; i < l.length; i++) { @@ -26,6 +27,13 @@ export class QueryBuilder { addCode(text: string) { this.list.push([false, text]); } + addCodeCommaSeperated(data: string[], comma = ", ") { + for (let i = 0; i < data.length; i++) { + const e = data[i]; + this.list.push([false, e]); + if(i+1any){ + + }*/ } @@ -74,9 +92,10 @@ export class selectQuery { return handler.querys.select(handler, this); } - query(db: DB) { + async query(db: DB) { const handler = db.getHandler(); - const s = handler.builders.query(this.serialize(handler)); - return db.query(new Query(s)); + const builder = this.serialize(handler); + const s = handler.builders.query(builder); + return await db.query(s); } } \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index e0f6ea7..c7bc431 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ import { Attribute, Table } from "./db"; import { Aggregation, BooleanModifier, Datatype, Joins, Modifier } from "./dbStructure"; -import { selectQuery } from "./query"; +import { QueryBuilder, selectQuery } from "./query"; export type primaryData = string | number | boolean | null; export type allModifierInput = primaryData | Modifier | selectQuery | Attribute | Aggregation; @@ -10,6 +10,8 @@ export type selectFromElements = Table | Joins | null; export type serializeReturn = [string, primaryData[]]; +export type DatatypeBuild = [QueryBuilder, number]; + export type attributeSettings = { unique?: boolean, autoIncrement?: boolean, @@ -21,8 +23,9 @@ export type attributeSettings = { onDelete?: onAction, onUpdate?: onAction }, - check?:BooleanModifier + check?: BooleanModifier | ((a: Attribute) => BooleanModifier) }; + export type extendedAttributeSettings = { type: Datatype, unique?: boolean, @@ -35,7 +38,7 @@ export type extendedAttributeSettings = { onDelete?: onAction, onUpdate?: onAction }, - check?:BooleanModifier + check?: BooleanModifier | ((a: Attribute) => BooleanModifier) } export enum onAction {