diff --git a/.woodpecker.yml b/.woodpecker.yml index 8d3ec32..133f850 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -12,7 +12,6 @@ pipeline: - mkdir dblang - mkdir upload - npm run prepublish - - ls dist - cp dist/* dblang - zip -r upload/DBlang.zip dblang/* - tar -czvf upload/DBlang.tar.gz dblang/* diff --git a/src/db.ts b/src/db.ts index 31767fc..b7187fd 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,17 +1,19 @@ import mariadb from 'mariadb'; -import { Datatype } from './dbStructure'; +import { checkConstraint, Constraint, Datatype, uniqueConstraint } from './dbStructure'; import { Handler } from './defaultHandler'; import { Query, selectQuery } from './query'; -import { attributeSettings, onAction, primaryData, serializeReturn } from './types'; +import { attributeSettings, extendedAttributeSettings, onAction, primaryData, serializeReturn } from './types'; export class DB { 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 }); this.handler = new Handler(); + this.name = "notimplemented" } async query(query: Query) { console.log(query); @@ -24,7 +26,7 @@ export class DB { sync(){ let handler = this.getHandler(); this.tables.forEach(t=>{ - console.log(handler.querys.create(handler,t)); + console.log(new Query(handler.builders.query(handler.querys.create(handler,t)))); }) } @@ -45,6 +47,19 @@ export class Attribute { this.ops = ops; this.type = type; this.table = table; + + if(ops.check != null){ + table.addConstraint(new checkConstraint( + table.dbLangDatabaseInstance.name+"_"+table.dbLangTableName+"_"+name+"_check_constraint", + ops.check + )) + } + if(ops.unique != null){ + table.addConstraint(new uniqueConstraint( + table.dbLangDatabaseInstance.name+"_"+table.dbLangTableName+" "+name+"_unique_constraint", + [this] + )) + } } serializeDatatype(handler : Handler){ return this.type.serialize(handler); @@ -68,10 +83,11 @@ export class Table { dbLangTableName: string; dbLangTableAttributes: { [key: string]: Attribute; } = {}; dbLangDatabaseInstance: DB; + dbLangConstrains: Constraint[] = []; [key: string]: Attribute | any - constructor(name: string, table:DB) { + constructor(name: string, db:DB) { this.dbLangTableName = name; - this.dbLangDatabaseInstance = table; + this.dbLangDatabaseInstance = db; } serialize(handler : Handler) { return this.toString(handler); @@ -80,15 +96,25 @@ export class Table { 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); this.dbLangTableAttributes[name] = attr; - if (["serialize", "toString", "addAttribute", "dbLangTableName", "dbLangTableAttributes", "dbLangDatabaseInstance"].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)]; + })); + } + addConstraint(c:Constraint){ + c.check(this); + this.dbLangConstrains.push(c); + } } export * from './funcs'; diff --git a/src/dbStructure.ts b/src/dbStructure.ts index 0dee547..d3ecde2 100644 --- a/src/dbStructure.ts +++ b/src/dbStructure.ts @@ -1,5 +1,6 @@ -import { Attribute } from "./db"; +import { Attribute, Table } from "./db"; import { Handler } from "./defaultHandler"; +import { QueryBuilder } from "./query"; import { allModifierInput, primaryData, serializeReturn } from "./types"; export class Datatype{ @@ -9,7 +10,7 @@ export class Datatype{ this.type = type; this.args = args; } - serialize(handler : Handler): serializeReturn{ + serialize(handler : Handler): QueryBuilder{ return handler.datatypes[this.type as keyof typeof handler.datatypes](this.args); } } @@ -21,7 +22,7 @@ export abstract class Modifier { this.t = type; this.a = args; } - serialize(handler : Handler): serializeReturn { + serialize(handler : Handler): QueryBuilder { return handler.modifiers[this.t as keyof typeof handler.modifiers](handler,this.a); } } @@ -37,13 +38,60 @@ export class Aggregation { this.t = type; this.a = args; } - serialize(handler : Handler): serializeReturn { + serialize(handler : Handler): QueryBuilder { return handler.aggregations[this.t as keyof typeof handler.aggregations](handler,this.a); } } export class Joins { - serialize(handler : Handler): serializeReturn { - return ["", []]; + serialize(handler : Handler): QueryBuilder { + return new QueryBuilder(); } +} + +export interface Constraint{ + name:string; + serialize( handler: Handler) : QueryBuilder; + uses(attr:Attribute): boolean; + check(attr:Table): boolean | string; +} + +export class checkConstraint implements Constraint{ + checkQuery:BooleanModifier; + name: string; + constructor(name:string, check: BooleanModifier){ + this.name = name; + this.checkQuery = check; + } + check(attr: Table): boolean { + throw new Error("Method not implemented."); + } + uses(attr: Attribute): boolean { + throw new Error("Method not implemented."); + } + serialize(handler: Handler): QueryBuilder { + throw new Error("Method not implemented."); + } +} + +export class uniqueConstraint implements Constraint{ + name: string; + attr: Attribute[]; + constructor(name:string, attr:Attribute[]){ + this.name = name; + this.attr = attr; + } + 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"; + } + return false; + } + uses(attr: Attribute): boolean { + throw new Error("Method not implemented."); + } + serialize(handler: Handler): QueryBuilder { + throw new Error("Method not implemented."); + } + } \ No newline at end of file diff --git a/src/defaultHandler.ts b/src/defaultHandler.ts index 635de1e..156ea49 100644 --- a/src/defaultHandler.ts +++ b/src/defaultHandler.ts @@ -1,92 +1,123 @@ import { Attribute, DB, Table } from "./db" import { Aggregation, Modifier } from "./dbStructure" -import { selectQuery } from "./query" +import { QueryBuilder, selectQuery } from "./query" import { allModifierInput, primaryData, serializeReturn } from "./types" export class Handler { - syncDB(db : DB){ + /*syncDB(db : DB){ - } + }*/ querys = { - select: (handler:Handler,q: selectQuery): serializeReturn => { - let args: primaryData[] = []; - let w = joinArg(", ")(handler,q.attr); - args.push(...w[1]); - let sql = `select ${w[0]} from ${q.from == null ? 'DUAL' : q.from.serialize(handler)}`; + select: (handler: Handler, q: selectQuery): QueryBuilder => { + const builder = new QueryBuilder(); + builder.addCode("select "); + builder.append(joinArg(", ")(handler, q.attr)); + builder.addCode(` from ${q.from == null ? 'DUAL' : q.from.serialize(handler)}`); if (q.whereD) { - let whereS = q.whereD.serialize(handler); - args.push(...whereS[1]); - sql += " where " + whereS[0]; + builder.addCode(" where "); + builder.append(q.whereD.serialize(handler)); } if (q.groupByD.length > 0) { - let groupByS = joinArg(",")(handler,q.groupByD); - args.push(...groupByS[1]); - sql += " group by " + groupByS[0]; + builder.addCode(" group by "); + builder.append(joinArg(",")(handler, q.groupByD)); if (q.havingD) { - let havingS = q.havingD.serialize(handler); - args.push(...havingS[1]); - sql += " having " + havingS[0]; + builder.addCode(" having "); + builder.append(q.havingD.serialize(handler)); } } - if (q.limitD != null) { - sql += " limit ?"; - args.push(q.limitD); - } - return [sql, args]; + if (q.havingD) { + builder.addCode(" limit "); + builder.addInjection(q.limitD); + } + return builder; }, - create:(handler:Handler,table: Table): serializeReturn=>{ - let args:primaryData[] = []; - let sql = `create table if not exists ${table.toString(handler)}( -${Object.entries(table.dbLangTableAttributes).map(([_,a])=>{ - let atype = a.serializeSettings(handler); - args.push(...(atype[1])); - return a.serialize(handler)+" "+atype[0]; - }).join(",\n")} -)`; - return [sql,args]; - } + create: (handler: Handler, table: Table): QueryBuilder => { + const builder = new QueryBuilder(); + builder.addCode(`create table if not exists ${table.toString(handler)}(`); + let keys = Object.keys(table.dbLangTableAttributes); + for (let i = 0; i < keys.length; i++) { + const a = table.dbLangTableAttributes[keys[i]]; + builder.addCode(a.serialize(handler) + " "); + builder.append(a.serializeSettings(handler)); + if (i + 1 < keys.length) builder.addCode(", "); + } + builder.addCode(")"); + return builder; + }, + } + 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 + appPrimaryKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + appForeignKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + addUniqueKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + addChecks: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + //should drop all keys to be able to recreate them + dropPrimaryKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + dropForeignKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + dropUniqueKeys: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + dropChecks: (handler: Handler, table: Table): QueryBuilder => new QueryBuilder(), + } builders = { - attributeSettings: (handler:Handler,a: Attribute): serializeReturn => { - let dtype = a.type.serialize(handler); - let sql = ""+dtype[0]; - let args:primaryData[] = [...dtype[1]]; + query: (qb: QueryBuilder): serializeReturn => { + let args: primaryData[] = []; + let sql = ""; + for (let i = 0; i < qb.list.length; i++) { + const [inject, data] = qb.list[i]; + if (inject) { + sql += "?"; + args.push(data); + } else { + sql += data; + } + } + return [sql, args]; + }, + + attributeSettings: (handler: Handler, a: Attribute): QueryBuilder => { + const builder = new QueryBuilder(); + builder.append(a.type.serialize(handler)); if (a.ops.autoIncrement) { - sql += " auto_increment"; + builder.addCode(" auto_increment"); } - if (a.ops.primaryKey) { + /*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)}`); - sql += " default ?"; - args.push(a.ops.default); + builder.addCode(" default "); + builder.addInjection(a.ops.default); } - if (a.ops.unique != null) { + /*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){ - sql += " not null"; + if (!a.ops.autoIncrement && !a.ops.primaryKey && a.ops.default == null) { + builder.addCode(" not null"); } } - if (a.ops.foreginKey != null) { + /*if (a.ops.foreginKey != null) { sql += ` foreign key references (${a.ops.foreginKey.link.toStringFunc(handler)})` - } + }*/ - return [sql, args]; + return builder; }, - escapeID: (key:string) :string =>{ - if(!key || key === "" || key.includes('\u0000')) throw new Error("Can not escape empty key or with null unicode!"); + 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, '``')}\``; } @@ -94,11 +125,11 @@ ${Object.entries(table.dbLangTableAttributes).map(([_,a])=>{ aggregations = { - count: (handler:Handler,a: Attribute): serializeReturn => ["count(" + a.toString(handler) + ")", []], - sum: (handler:Handler,a: Attribute): serializeReturn => ["sum(" + a.toString(handler) + ")", []], - avg: (handler:Handler,a: Attribute): serializeReturn => ["avg(" + a.toString(handler) + ")", []], - min: (handler:Handler,a: Attribute): serializeReturn => ["min(" + a.toString(handler) + ")", []], - max: (handler:Handler,a: Attribute): serializeReturn => ["max(" + a.toString(handler) + ")", []], + count: (handler: Handler, a: Attribute): QueryBuilder => new QueryBuilder([{ data: "count(" + a.toString(handler) + ")" }]), + sum: (handler: Handler, a: Attribute): QueryBuilder => new QueryBuilder([{ data: "sum(" + a.toString(handler) + ")" }]), + avg: (handler: Handler, a: Attribute): QueryBuilder => new QueryBuilder([{ data: "avg(" + a.toString(handler) + ")" }]), + min: (handler: Handler, a: Attribute): QueryBuilder => new QueryBuilder([{ data: "min(" + a.toString(handler) + ")" }]), + max: (handler: Handler, a: Attribute): QueryBuilder => new QueryBuilder([{ data: "max(" + a.toString(handler) + ")" }]), } modifiers = { @@ -111,14 +142,21 @@ ${Object.entries(table.dbLangTableAttributes).map(([_,a])=>{ ge: joinArg(">"), plus: joinArg("+"), minus: joinArg("-"), - not: (handler:Handler,a: allModifierInput[]): serializeReturn => { + not: (handler: Handler, a: allModifierInput[]): QueryBuilder => { let e = a[0]; - if (e instanceof Attribute) return ["not (" + e.toString(handler) + ")", []]; + if (e instanceof Attribute) return new QueryBuilder([{ data: "not (" + e.toString(handler) + ")" }]) if (e instanceof Modifier || e instanceof selectQuery || e instanceof Aggregation) { - let [sqli, argsi] = e.serialize(handler); - return ["not (" + sqli + ")", argsi] + const builder = new QueryBuilder(); + builder.addCode("not ("); + builder.append(e.serialize(handler)); + builder.addCode(")"); + return builder; } - return ["not (?)", [e]]; + return new QueryBuilder([ + { data: "not(" }, + { inject: true, data: e }, + { data: ")" } + ]) } } datatypes = { @@ -136,12 +174,19 @@ ${Object.entries(table.dbLangTableAttributes).map(([_,a])=>{ mediumtext: dataTypeNoArg("mediumtext"), longtext: dataTypeNoArg("longtext"), - enum: (a: primaryData[]): serializeReturn => { - console.log(a); - return ["enum(" + a.map(() => "?").join(", ") + ")", a]; + enum: (a: primaryData[]): QueryBuilder => { + const builder = new QueryBuilder(); + builder.addCode("enum("); + builder.addInjectionCommaSeperated(a); + builder.addCode(")"); + return builder; }, - set: (a: primaryData[]): serializeReturn => { - return ["set(" + a.map(() => "?").join(", ") + ")", a]; + set: (a: primaryData[]): QueryBuilder => { + const builder = new QueryBuilder(); + builder.addCode("set("); + builder.addInjectionCommaSeperated(a); + builder.addCode(")"); + return builder; }, bool: dataTypeNoArg("bool"), bit: dataTypeNoArg("bit"), @@ -165,36 +210,50 @@ ${Object.entries(table.dbLangTableAttributes).map(([_,a])=>{ }; function dataTypeNoArg(type: string) { - return (a: primaryData[]): serializeReturn => { - return [type, []]; + return (a: primaryData[]): QueryBuilder => { + return new QueryBuilder([{ + inject: false, + data: type + }]); } } function dataTypeSingleNum(type: string) { - return (a: primaryData[]): serializeReturn => { - return [type + "(?)", [a[0]]]; + return (a: primaryData[]): QueryBuilder => { + return new QueryBuilder([ + { data: type + "(" }, + { inject: true, data: a[0] }, + { data: ")" } + ]); } } function dataTypeDblNum(type: string) { - return (a: primaryData[]): serializeReturn => { - return [type + "(?,?)", [a[0], a[1]]]; + return (a: primaryData[]): QueryBuilder => { + return new QueryBuilder([ + { data: type + "(" }, + { inject: true, data: a[0] }, + { data: ", " }, + { inject: true, data: a[1] }, + { data: ")" } + ]); } } function joinArg(type: string) { - return (handler:Handler,a: (allModifierInput)[]): serializeReturn => { - let args: primaryData[] = []; - let sql = a.map(d => { - if (d instanceof Attribute) return d.toString(handler); - if (d instanceof Modifier || d instanceof selectQuery || d instanceof Aggregation) { - let [sqli, argsi] = d.serialize(handler); - args.push(...(argsi.flat(Infinity))); - return "(" + sqli + ")"; + return (handler: Handler, a: (allModifierInput)[]): QueryBuilder => { + const builder = new QueryBuilder(); + for (let i = 0; i < a.length; i++) { + const d = a[i]; + if (d instanceof Attribute) builder.addCode(d.toString(handler)); + else if (d instanceof Modifier || d instanceof selectQuery || d instanceof Aggregation) { + builder.addCode("("); + builder.append(d.serialize(handler)); + builder.addCode(")"); + } else { + builder.addInjection(d); } - args.push(d); - return "?"; - }).join(" " + type + " "); - return [sql, args] - + if (i + 1 < a.length) builder.addCode(" " + type + " "); + } + return builder; } } \ No newline at end of file diff --git a/src/funcs.ts b/src/funcs.ts index 1092389..a135fd7 100644 --- a/src/funcs.ts +++ b/src/funcs.ts @@ -1,5 +1,5 @@ import { Attribute } from "./db"; -import { Aggregation, BooleanModifier, Datatype, NumberModifier } from "./dbStructure"; +import { Aggregation, BooleanModifier, checkConstraint, Datatype, NumberModifier } from "./dbStructure"; import { selectQuery } from "./query"; import { allModifierInput, primaryData, selectElements, selectFromElements } from "./types"; @@ -61,4 +61,19 @@ export const DATE = new Datatype("date",[]); export const DATETIME = new Datatype("datetime",[]); export const TIMESTAMP = new Datatype("timestamp",[]); export const TIME = new Datatype("time",[]); -export const YEAR = new Datatype("year",[]); \ No newline at end of file +export const YEAR = new Datatype("year",[]); + +// Constraints +//TODO: +//primary key +export const foreginKey = (attrs:Attribute[],target:Attribute[])=>{}; +export const uniqueKey = (attr:Attribute[])=>{}; +export const check = (name:string,mod:BooleanModifier)=>new checkConstraint(name,mod); + + +/** + * primary key: kein richtiger Constraint -> renew = drop + * foreign key: ? + * unique: richtiger Constraint -> achtung manchmal nicht entfernabr + * check: richtiger Constraint + */ \ No newline at end of file diff --git a/src/query.ts b/src/query.ts index 4567a83..4cf4e16 100644 --- a/src/query.ts +++ b/src/query.ts @@ -7,12 +7,41 @@ import { primaryData, selectElements, selectFromElements, serializeReturn } from export class Query { sql: string; values: primaryData[]; - constructor(sql: string, values: primaryData[]) { + constructor([sql, values]:serializeReturn) { this.sql = sql; this.values = values; } } +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++) { + const e = l[i]; + this.list.push([e.inject ? true : false, e.data]); + } + } + addCode(text: string) { + this.list.push([false, text]); + } + addInjection(data: primaryData) { + this.list.push([true, data]); + } + addInjectionCommaSeperated(data: primaryData[], comma = ", ") { + for (let i = 0; i < data.length; i++) { + const e = data[i]; + this.list.push([true, e]); + if(i+1