diff --git a/.gitignore b/.gitignore index a34ff48..a3c17fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist node_modules -ltests \ No newline at end of file +ltests +sqlsave \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml index c946d1e..133f850 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -5,11 +5,15 @@ pipeline: build: image: node:18-alpine commands: - - apk add git + - apk add git zip tar - npm install - npm install git+https://jusax.de/git/jusax23/gitea-release.git - mkdir build + - mkdir dblang + - mkdir upload - npm run prepublish - - ls dist - - npx gitea-release "https://jusax.de/git/api/v1/" "$${GITEA_TOKEN}" "$${CI_REPO}" "$${CI_COMMIT_BRANCH}" "$${CI_COMMIT_TAG}" "dist" "$${CI_COMMIT_MESSAGE}" + - cp dist/* dblang + - zip -r upload/DBlang.zip dblang/* + - tar -czvf upload/DBlang.tar.gz dblang/* + - npx gitea-release "https://jusax.de/git/api/v1/" "$${GITEA_TOKEN}" "$${CI_REPO}" "$${CI_COMMIT_BRANCH}" "$${CI_COMMIT_TAG}" "upload" "$${CI_COMMIT_MESSAGE}" secrets: [ gitea_token ] \ No newline at end of file diff --git a/readme.md b/readme.md index 72fa516..ec357fd 100644 --- a/readme.md +++ b/readme.md @@ -1 +1,215 @@ -[![status-badge](https://ci.jusax.de/api/badges/jusax23/dblang/status.svg)](https://ci.jusax.de/jusax23/dblang) \ No newline at end of file +# DBlang + +[![status-badge](https://ci.jusax.de/api/badges/jusax23/dblang/status.svg)](https://ci.jusax.de/jusax23/dblang) + +TypeScript Libary for relational Database Requests. Requests are written in js/ts and it supports automatic Schema evulution. + +Fetures +- [x] select Query +- [x] insert Query +- [x] update Query +- [x] remove Query +- [x] create Schema +- [x] remove unused Schema +- [ ] joins +- [ ] change Schema +- [ ] Key-Value Store Shortcut +- [ ] Views + +Supported Databses: +- [x] mariadb +- [ ] mysql (not tested) +- [ ] postrges + +## Getting Started + +### Connect + +```javascript +import { DB } from "dblang"; +const db = new DB({ + host: "localhost", + user: "root", + password: "0123456789", + database: "databaseName" +}); + +``` + +### Schema + +Tables: +```javascript +const Table1 = db.newTable("Table1Name"); +const Table2 = db.newTable("Table2Name"); +``` + +Add Attributes: +```javascript +Table1.addAttribute("AttrName", TYPE, { + default: 5, + // other settings +}); + +Table2.addAttributes({ + "AttrName2":{ + type: TYPE, + default: 6 + // other settings + }, + "AttrName3":{ + type: TYPE, + default: 6 + // other settings + }, + // more Attributes +}); +``` + +TYPE: See [Types](#types) + +Other Settings: +- `unique: boolean`: (optional) Add Unique Constraint +- `autoIncrement: boolean`: (optional) Set auto Increment (must be primary key) +- `default: any`: (optional) Set default Property. +- `notNull: boolean`: (optional) Set not nullable. +- `primaryKey: boolean`: (optinal) mark as part of primary Key. +- `foreignKey: Object`: Add foreign Constraint for single Attribute. + - `link: Attribute`: Linked Attribute + - `onDelete: onAction`: Specify delete Action [onAction](#onaction) + - `onUpdate: onAction`: Specify update Action [onAction](#onaction) +- `check: BooleanModifier | ((a: Attribute) => BooleanModifier)`: Add check Constraint (A function must be used when check references its self.) + +Sync: +Create Tables, Attributes and Constraints +```javascript +await db.sync(); + +await db.sync(true); //delete unused Elements (May do a backup befor.) +``` + +### Querys +See: [Modifiers](#modifier), [Aggregations](#aggregations) + +#### Select: +```javascript +import {select} from "dblang" + +let res = await select([Table2.AttrName2], Table1) + .where(eq(Table2.AttrName2, 5)) //optional + .groupBy(Table2.AttrName3) //optional + .having(le(Table2.AttrName3, 5)) //optional + .limit(10) //optional + .query(db); + +``` + +#### Insert: +```javascript +import {select} from "dblang" + +await insert(Table2.AttrName2, Table2.AttrName3) + .add(1, 2) + .add(2, 3) + .addValues([4, 5], [3, 4]) + .query(db); + +await insert(Table2.AttrName2, Table2.AttrName3) + .setSelect(select(Table1.AttrName, 4)) + .query(db); +``` + +#### Update: +```javascript +await update(Table2) + .set(Table2.AttrName2, plus(Table2.AttrName2,1)) + .set(Table2.AttrName3, minus(Table2.AttrName2,1)) + .where(leq(Table2.AttrName2, 5)) + .query(db); +``` + +#### Remove: +```javascript +await remove(Table2) + .where(geq(Table2.AttrName2, 5)) + .query(db); +``` + +### Content List +#### Types + +- `CHAR(size)` +- `VARCHAR(size)` +- `BINARY(size)` +- `VARBINARY(size)` +- `TINYBLOB` +- `BLOB` +- `MEDIUMBLOB` +- `LONGBLOB` +- `TINYTEXT` +- `TEXT` +- `MEDIUMTEXT` +- `LONGTEXT` +- `ENUM(val1, val2, ...)` +- `SET(val1, val2, ...)` +- `BOOL` +- `BIT` +- `TINYINT` +- `SMALLINT` +- `MEDIUMINT` +- `INT` +- `BIGINT` +- `FLOAT(size, d)` +- `DOUBLE(size, d)` +- `DECIMAL(size, d)` +- `DATE` +- `DATETIME` +- `TIMESTAMP` +- `TIME` +- `YEAR` + +#### onAction + +Action for onDelete or onUpdate. + +Usage: +```javascript +import { onAction } from "dblang" + +onAction.cascade; +onAction.noAction; // dangerous +onAction.setNull; +onAction.setDefault; +``` + +#### Modifier +BooleanModifier: +- `and(v1, v2, ...)` +- `or(v1, v2, ...)` +- `ge(v1, v2, ...)` +- `geq(v1, v2, ...)` +- `eq(v1, v2, ...)` +- `leq(v1, v2, ...)` +- `le(v1, v2, ...)` +- `not(v1)` +- `like(v1, v2)` + +NumberModifier: +- `plus(v1, v2, ...)` +- `minus(v1, v2, ...)` +- `mult(v1, v2, ...)` +- `divide(v1, v2, ...)` + +StringModifier: +- `concat(v1, v2, ...)` + +(v\* = string, number, boolean, null, Modifier, Aggregation, select Query, Attribute) + +# Aggregations +- `count(a)` +- `sum(a)` +- `avg(a)` +- `min(a)` +- `max(a)` + +(a = Attribute) \ No newline at end of file diff --git a/src/db.ts b/src/db.ts index 1266a2a..bc3cb92 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,75 +1,142 @@ import mariadb from 'mariadb'; +import { checkConstraint, Constraint, Datatype, foreignConstraint, uniqueConstraint } from './dbStructure'; import { Handler } from './defaultHandler'; import { Query } from './query'; -import { onAction, primaryData } from './types'; +import { attributeSettings, extendedAttributeSettings, onAction } from './types'; export class DB { - //pool:mariadb.Pool; - constructor(/*{ host, user, password, database, connectionLimit = 5 }*/) { - //this.pool = mariadb.createPool({ host, user, password, database, connectionLimit, multipleStatements: true }); + tables: Table[] = []; + handler: Handler; + name: string; + 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 = database; } async query(query: Query) { - //return this.pool.query(query); + //console.log(query); + return await this.pool.query(query); } getHandler() { - return Handler; + return this.handler; } - newTable(name:string){ - return new Table(name); + async sync(force = false) { + let handler = this.getHandler(); + await handler.syncDB(this, handler, force); + //this.tables.forEach(t=>{ + // console.log(handler.builders.query(handler.querys.create(handler,t))); + //}) + } + + newTable(name: string) { + let tabel = new Table(name, this); + this.tables.push(tabel); + return tabel; + } + + getTable(name: string) { + for (let i = 0; i < this.tables.length; i++) if (this.tables[i].dbLangTableName == name) return this.tables[i]; + return null; + } + async close() { + if (this.pool) await this.pool.end(); } } export class Attribute { - name : string; - constructor(name: string){ - this.name = name; + name: string; + table: Table; + ops: attributeSettings; + type: Datatype; + constructor(name: string, table: Table, type: Datatype, ops: attributeSettings) { + this.name = name.toLowerCase(); + this.type = type; + this.table = table; + 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", + ops.check + )) + } + if (ops.unique != null) { + table.addConstraint(new uniqueConstraint( + table.dbLangDatabaseInstance.name + "_" + table.dbLangTableName + "_" + name + "_unique_constraint", + [this] + )) + } + if (ops.foreginKey != null) { + table.addConstraint(new foreignConstraint( + table.dbLangDatabaseInstance.name + "_" + table.dbLangTableName + "_" + name + "_foreign_constraint_to_" + ops.foreginKey.link.name, + [this], + [ops.foreginKey.link], + ops.foreginKey.onDelete, + ops.foreginKey.onUpdate + )); + } } - serialize(){ - return this.toString(); + serializeDatatype(handler: Handler) { + return this.type.serialize(handler); } - toString(){ - return this.name; + serializeSettings(handler: Handler) { + return handler.builders.attributeSettings(handler, this); + } + serialize(handler: Handler) { + return handler.builders.escapeID(this.name); + } + 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) + ")"; } } -export class Table{ - dbLangTableName : string; - dbLangTableAttributes:{ [key: string]: Attribute; } = {}; - [key:string]: Attribute | any - constructor(name: string){ +export class Table { + dbLangTableName: string; + dbLangTableAttributes: { [key: string]: Attribute; } = {}; + dbLangDatabaseInstance: DB; + dbLangConstrains: Constraint[] = []; + [key: string]: Attribute | any; + constructor(name: string, db: DB) { this.dbLangTableName = name; + this.dbLangDatabaseInstance = db; } - serialize(){ - return this.toString(); + serialize(handler: Handler) { + return this.toString(handler); } - toString(){ - return this.dbLangTableName; + toString(handler: Handler = this.dbLangDatabaseInstance.getHandler()) { + return handler.builders.escapeID(this.dbLangTableName); } - addAttribute(name:string,ops:{ - unique?: boolean, - A_I?: boolean, - default?: primaryData, - notNull?: boolean - primaryKey?: boolean, - foreginKey?: { - link: Attribute, - onDelete?: onAction, - onUpdate?: onAction - } - },noErrorOnNameConflict = false){ - let attr = new Attribute(name); - this.dbLangTableAttributes[name] = attr; - if(["serialize", "toString","addAttribute","dbLangTableName","dbLangTableAttributes"].includes(name)){ - if(!noErrorOnNameConflict) throw new Error("You cannot name Attribute like Methode of this Table!"); - }else{ + addAttribute(name: string, type: Datatype, ops: attributeSettings = {}, noErrorOnNameConflict = false) { + let lowName = name.toLowerCase(); + if (this.dbLangTableAttributes[lowName] != null) throw new Error("You are tring to create an Attribute twise!"); + let attr = new Attribute(lowName, this, type, ops); + this.dbLangTableAttributes[lowName] = attr; + if (["serialize", "toString", "addAttribute", "dbLangTableName", "dbLangTableAttributes", "dbLangDatabaseInstance", "addConstraint", "addAttributes"].includes(lowName)) { + if (!noErrorOnNameConflict) throw new Error("You cannot name Attribute like Methode of this Table!"); + } else { + this[lowName] = attr; 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'; -export {onAction}; \ No newline at end of file +export { onAction }; \ No newline at end of file diff --git a/src/dbStructure.ts b/src/dbStructure.ts index 186b08e..e61404c 100644 --- a/src/dbStructure.ts +++ b/src/dbStructure.ts @@ -1,39 +1,131 @@ -import { Attribute } from "./db"; +import { Attribute, Table } from "./db"; import { Handler } from "./defaultHandler"; -import { allModifierInput, primaryData, serializeReturn } from "./types"; - +import { QueryBuilder } from "./query"; +import { allModifierInput, onAction, primaryData, serializeReturn } from "./types"; +export class Datatype { + type: string; + args: primaryData[]; + constructor(type: string, args: primaryData[]) { + this.type = type; + this.args = args; + } + serialize(handler: Handler): QueryBuilder { + return handler.datatypes[this.type as keyof typeof handler.datatypes](this.args); + } +} export abstract class Modifier { - t : string; - a : Array; + t: string; + a: Array; constructor(type: string, args: (allModifierInput)[]) { this.t = type; this.a = args; } - serialize(handler = Handler):serializeReturn { - return handler.modifiers[this.t as keyof typeof handler.modifiers](this.a); + serialize(handler: Handler): QueryBuilder { + return handler.modifiers[this.t as keyof typeof handler.modifiers](handler, this.a); } } -export class BooleanModifier extends Modifier{} -export class NumberModifier extends Modifier{} -export class StringModifier extends Modifier{} +export class BooleanModifier extends Modifier { } +export class NumberModifier extends Modifier { } +export class StringModifier extends Modifier { } -export class Aggregation{ - t : string; - a : Attribute; +export class Aggregation { + t: string; + a: Attribute; constructor(type: string, args: Attribute) { this.t = type; this.a = args; } - serialize(handler = Handler):serializeReturn{ - return handler.aggregations[this.t as keyof typeof handler.aggregations](this.a); + serialize(handler: Handler): QueryBuilder { + return handler.aggregations[this.t as keyof typeof handler.aggregations](handler, this.a); } } -export class Joins{ +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(table: Table): boolean | string; +} + +export class checkConstraint implements Constraint { + checkQuery: BooleanModifier; + name: string; + constructor(name: string, check: BooleanModifier) { + this.name = name.toLowerCase(); + this.checkQuery = check; + } + check(attr: Table): boolean { + return true; + } + 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; + attrs: Attribute[]; + constructor(name: string, attrs: Attribute[]) { + this.name = name.toLowerCase(); + this.attrs = attrs; + } + check(table: Table): boolean | string { + for (let i = 0; i < this.attrs.length; i++) { + if (this.attrs[i].ops.primaryKey) return "Can not combine unique Constraint and primary key"; + if (this.attrs[i].table != table) return "Referencing Attributes must be in host Table."; + } + return false; + } + uses(attr: Attribute): boolean { + return this.attrs.includes(attr); + } + serialize(handler: Handler): QueryBuilder { + throw new Error("Method not implemented."); + } + +} + +export class foreignConstraint implements Constraint { + name: string; + fromAttrs: Attribute[]; + toAttrs: Attribute[]; + onUpdate: onAction; + onDelete: onAction; + constructor(name: string, from: Attribute[], to: Attribute[], onDelete: onAction = onAction.nothing, onUpdate: onAction = onAction.nothing) { + this.name = name.toLowerCase(); + this.fromAttrs = from; + this.toAttrs = to; + this.onUpdate = onUpdate; + this.onDelete = onDelete; + } + serialize(handler: Handler): QueryBuilder { + throw new Error("Method not implemented."); + } + uses(attr: Attribute): boolean { + throw new Error("Method not implemented."); + } + check(t: Table): string | boolean { + let table = this.toAttrs[0].table; + for (let i = 0; i < this.toAttrs.length; i++) { + if (table != this.toAttrs[i].table) return "Referenced Attributes must be in one Table."; + if (this.toAttrs[i].ops.primaryKey) return "Can not reference non primary keys."; + } + for (let i = 0; i < this.fromAttrs.length; i++) { + if (this.fromAttrs[i].table != t) return "Referencing Attributes must be in host Table."; + } + return false; + } + } \ No newline at end of file diff --git a/src/defaultHandler.ts b/src/defaultHandler.ts index 9299829..3765dfb 100644 --- a/src/defaultHandler.ts +++ b/src/defaultHandler.ts @@ -1,76 +1,690 @@ -import { Attribute } from "./db" -import { Aggregation, Modifier } from "./dbStructure" -import { selectQuery } from "./query" -import { allModifierInput, primaryData, serializeReturn } from "./types" +import { Attribute, DB, Table } from "./db" +import { Aggregation, checkConstraint, Constraint, Datatype, foreignConstraint, Modifier, uniqueConstraint } from "./dbStructure" +import { insertQuery, Query, QueryBuilder, removeQuery, selectQuery, updateQuery } from "./query" +import { allModifierInput, onAction, primaryData, serializeReturn } from "./types" export class Handler { - static querys = { - select: (q: selectQuery): serializeReturn => { - let args: primaryData[] = []; - let w = joinArg(", ", this)(q.attr); - args.push(...w[1]); - let sql = `select ${w[0]} from ${q.from == null ? 'DUAL' : q.from.serialize(this)}`; - if (q.whereD) { - let whereS = q.whereD.serialize(this); - args.push(...whereS[1]); - sql += " where "+whereS[0]; + 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)); + + //Table List + let allTables = (await db.query(handler.builders.query(handler.querys.listTables(handler, db)))).map((d: any) => d.TABLE_NAME); + + // gether Table Schematics + let tableData: { [key: string]: any; } = {}; + 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[table.dbLangTableName.toLowerCase()] = + Object.fromEntries((await db.query(handler.builders.query(tableDataBuilder))).map((d: any) => [d.Field.toLowerCase(), d])); + } catch (_) { + tableData[table.dbLangTableName.toLowerCase()] = null; } - if (q.groupByD.length>0) { - let groupByS = joinArg(",", this)(q.groupByD); - args.push(...groupByS[1]); - sql += " group by "+groupByS[0]; - if (q.havingD) { - let havingS = q.havingD.serialize(this); - args.push(...havingS[1]); - sql += " having "+havingS[0]; + } + + const del = new QueryBuilder(); + const create = new QueryBuilder(); + + function checkUniqueIsVaild(n: string, table: string) { + let t = db.getTable(table); + if (t == null) return false; + for (let i = 0; i < t.dbLangConstrains.length; i++) { + const c = t.dbLangConstrains[i]; + if (c.name == n.toLowerCase() && c instanceof uniqueConstraint) { + let attrs = c.attrs.map(a => a.name); + let found = 0; + for (let j = 0; j < key.length; j++) { + if ( + key[j].CONSTRAINT_NAME == n + && key[j].TABLE_NAME == table + ) { + if (attrs.includes(key[j].COLUMN_NAME.toLowerCase())) found++; + else return false; + } + } + return found == attrs.length; } } - - return [sql, args]; + return false; } - } - static aggregations = { - count: (a: Attribute): serializeReturn => ["count(" + a + ")", []], - sum: (a: Attribute): serializeReturn => ["sum(" + a + ")", []], - avg: (a: Attribute): serializeReturn => ["avg(" + a + ")", []], - min: (a: Attribute): serializeReturn => ["min(" + a + ")", []], - max: (a: Attribute): serializeReturn => ["max(" + a + ")", []], - } - - static modifiers = { - and: joinArg("and", this), - or: joinArg("or", this), - eq: joinArg("=", this), - plus: joinArg("+", this), - minus: joinArg("-", this), - not: (a: allModifierInput[]):serializeReturn => { - let e = a[0]; - if (e instanceof Attribute) return ["not (" + e + ")", []]; - if (e instanceof Modifier || e instanceof selectQuery || e instanceof Aggregation) { - let [sqli, argsi] = e.serialize(this); - return ["not (" + sqli + ")", argsi] + function checkForeignIsVaild(n: string, table: string) { + let t = db.getTable(table); + if (t == null) return false; + for (let i = 0; i < t.dbLangConstrains.length; i++) { + const c = t.dbLangConstrains[i]; + if (c.name == n.toLowerCase() && c instanceof foreignConstraint) { + let fromAttrs = c.fromAttrs.map(a => a.name); + let toAttrs = c.toAttrs.map(a => a.name); + let refTable = c.toAttrs[0].table.dbLangTableName; + let found = 0; + for (let j = 0; j < key.length; j++) { + if ( + key[j].CONSTRAINT_NAME == n + && key[j].TABLE_NAME == table + && key[j].REFERENCED_TABLE_NAME == refTable + ) { + let inF = fromAttrs.indexOf(key[j].COLUMN_NAME.toLowerCase()); + let inT = toAttrs.indexOf(key[j].REFERENCED_COLUMN_NAME.toLowerCase()); + if (inF != -1 && inT == inF) found++; + else return false; + } + } + return found == fromAttrs.length && found == toAttrs.length; + } } - return ["not (?)", [e]]; + return false; } + + function checkUniqueExists(tabel: Table, c: uniqueConstraint) { + let attrs = c.attrs.map(a => a.name); + let found = 0; + for (let j = 0; j < key.length; j++) { + if ( + key[j].CONSTRAINT_NAME.toLowerCase() == c.name + && key[j].TABLE_NAME == tabel.dbLangTableName + ) { + if (attrs.includes(key[j].COLUMN_NAME.toLowerCase())) found++; + else return false; + } + } + return found == attrs.length; + } + function checkForeignExists(tabel: Table, c: foreignConstraint) { + let fromAttrs = c.fromAttrs.map(a => a.name); + let toAttrs = c.toAttrs.map(a => a.name); + let refTable = c.toAttrs[0].table.dbLangTableName; + let found = 0; + for (let j = 0; j < key.length; j++) { + if ( + key[j].CONSTRAINT_NAME == c.name + && key[j].TABLE_NAME == tabel.dbLangTableName + && key[j].REFERENCED_TABLE_NAME == refTable + ) { + let inF = fromAttrs.indexOf(key[j].COLUMN_NAME.toLowerCase()); + let inT = toAttrs.indexOf(key[j].REFERENCED_COLUMN_NAME.toLowerCase()); + if (inF != -1 && inT == inF) found++; + else return false; + } + } + return found == fromAttrs.length && found == toAttrs.length; + } + + //delete check and unused/false(unique, foreign) constraints + 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)); + } else if (c.CONSTRAINT_TYPE == "UNIQUE") { + if ( + (typeof tableData[c.TABLE_NAME.toLowerCase()] == "undefined" && deleteInDB) + || !checkUniqueIsVaild(c.CONSTRAINT_NAME, c.TABLE_NAME) + ) del.appendEnding(handler.querys.removeUnique(handler, c.TABLE_NAME, c.CONSTRAINT_NAME)); + } else if (c.CONSTRAINT_TYPE == "FOREIGN KEY") { + if ( + (typeof tableData[c.TABLE_NAME.toLowerCase()] == "undefined" && deleteInDB) + || !checkForeignIsVaild(c.CONSTRAINT_NAME, c.TABLE_NAME) + ) del.appendEnding(handler.querys.removeForeignKey(handler, c.TABLE_NAME, c.CONSTRAINT_NAME)); + } + } + //delete unused Tables + if (deleteInDB) for (let i = 0; i < allTables.length; i++) { + if (typeof tableData[allTables[i].toLowerCase()] == "undefined") del.appendEnding(handler.querys.deleteTable(handler, allTables[i])); + } + + function freeForUpdate(attr: string, table: string) { + for (let i = 0; i < key.length; i++) { + const k = key[i]; + if ( + k.REFERENCED_TABLE_NAME == table + && k.REFERENCED_COLUMN_NAME.toLowerCase() == attr.toLowerCase() + ) { + 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[table.dbLangTableName.toLowerCase()]; + //delete unused Columns + if (deleteInDB && tableD != null) { + let keys = Object.keys(tableD); + for (let i = 0; i < keys.length; i++) { + if (table.dbLangTableAttributes[keys[i]] == null) { + freeForUpdate(keys[i].toLowerCase(), table.dbLangTableName); + create.appendEnding(handler.querys.removeColumn(handler, table, keys[i])); + } + } + } + //add/mofify columns + 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.name, a.table.dbLangTableName); + create.appendEnding(handler.querys.changeColumn(handler, a)); + } + if (attrData == null) { + changePrimary = true; + } else { + if ((attrData.Key == "PRI") != (!!a.ops.primaryKey)) { + freeForUpdate(a.name, a.table.dbLangTableName); + changePrimary = true; + } + } + } + } + if (changePrimary) { + create.appendEnding(handler.querys.removePrimaryKey(handler, table.dbLangTableName)); + 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)); + } else if (c instanceof uniqueConstraint) { + if (!checkUniqueExists(table, c)) { + create.appendEnding(handler.querys.addUnique(handler, table, c)); + } + } else if (c instanceof foreignConstraint) { + if (!checkForeignExists(table, c)) { + create.appendEnding(handler.querys.addForeignKey(handler, table, c)); + } + } + } + } + if (!create.isEmpty()) del.append(create); + if (!del.isEmpty()) await db.query(handler.builders.query(del)); + } + + querys = { + 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) { + builder.addCode(" where "); + builder.append(q.whereD.serialize(handler)); + } + if (q.groupByD.length > 0) { + builder.addCode(" group by "); + builder.append(joinArg(",")(handler, q.groupByD)); + if (q.havingD) { + builder.addCode(" having "); + builder.append(q.havingD.serialize(handler)); + } + } + if (q.havingD) { + builder.addCode(" limit "); + builder.addInjection(q.limitD); + } + + /*builder.setHandler((json)=>{ + return json; + });*/ + + return builder; + }, + insert: (hander: Handler, q: insertQuery): QueryBuilder => { + const qb = new QueryBuilder(); + qb.addCode("INSERT INTO "); + qb.addCode(q.attrs[0].table.serialize(hander)); + qb.addCode("("); + qb.addCodeCommaSeperated(q.attrs.map(a => a.serialize(hander))); + qb.addCode(") "); + if (q.select == null) { + qb.addCode("VALUES "); + for (let i = 0; i < q.values.length; i++) { + qb.addCode("("); + qb.addInjectionCommaSeperated(q.values[i]); + qb.addCode(")"); + if (i + 1 < q.values.length) qb.addCode(","); + } + } else { + qb.append(q.select.serialize(hander)); + } + return qb; + }, + update: (handler: Handler, q: updateQuery): QueryBuilder => { + const qb = new QueryBuilder(); + qb.addCode("UPDATE "); + qb.addCode(q.table.serialize(handler)); + qb.addCode(" SET "); + for (let i = 0; i < q.setD.length; i++) { + const s = q.setD[i]; + qb.addCode(s[0].serialize(handler)); + qb.addCode(" = ("); + if (s[1] instanceof Attribute) qb.addCode(s[1].serialize(handler)); + else if (s[1] instanceof Modifier || s[1] instanceof selectQuery || s[1] instanceof Aggregation) { + qb.append(s[1].serialize(handler)); + } else { + qb.addInjection(s[1]); + } + qb.addCode(")"); + if (i + 1 < q.setD.length) qb.addCode(", "); + } + if (q.whereD) { + qb.addCode(" where "); + qb.append(q.whereD.serialize(handler)); + } + return qb; + }, + remove: (handler: Handler, q: removeQuery): QueryBuilder =>{ + const qb = new QueryBuilder(); + qb.addCode("DELETE FROM "); + qb.addCode(q.table.serialize(handler)); + if (q.whereD) { + qb.addCode(" where "); + qb.append(q.whereD.serialize(handler)); + } + return qb; + }, + listTables: (handler: Handler, db: DB) => { + const qb = new QueryBuilder(); + qb.addCode(`SELECT * FROM information_schema.tables where TABLE_SCHEMA = `); + qb.addInjection(db.name); + qb.addCode(` and TABLE_TYPE = "BASE TABLE"`); + return qb; + }, + 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; + }, + deleteTable: (handler: Handler, table: string): QueryBuilder => { + const qb = new QueryBuilder(); + qb.addCode(`DROP TABLE `); + qb.addCode(handler.builders.escapeID(table)); + qb.addCode(` CASCADE`); + return qb; + }, + 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; + }, + removeColumn: (handler: Handler, tabel: Table, attr: string) => { + const builder = new QueryBuilder(); + builder.addCode("ALTER TABLE "); + builder.addCode(tabel.serialize(handler)); + builder.addCode(" DROP COLUMN "); + builder.addCode(handler.builders.escapeID(attr)); + return builder; + }, + addForeignKey: (handler: Handler, table: Table, c: foreignConstraint) => { + const builder = new QueryBuilder(); + builder.addCode("ALTER TABLE "); + builder.addCode(table.serialize(handler)); + builder.addCode(" ADD CONSTRAINT "); + builder.addCode(handler.builders.escapeID(c.name)); + builder.addCode(" FOREIGN KEY ("); + builder.addCodeCommaSeperated(c.fromAttrs.map(a => a.serialize(handler))); + builder.addCode(") REFERENCES "); + builder.addCode(c.toAttrs[0].table.serialize(handler)); + builder.addCode("("); + builder.addCodeCommaSeperated(c.toAttrs.map(a => a.serialize(handler))); + builder.addCode(")"); + + if (c.onUpdate == onAction.cascade) builder.addCode(" ON UPDATE CASCADE"); + if (c.onUpdate == onAction.noAction) builder.addCode(" ON UPDATE NO ACTION"); + if (c.onUpdate == onAction.setDefault) builder.addCode(" ON UPDATE SET DEFUALT"); + if (c.onUpdate == onAction.setNull) builder.addCode(" ON UPDATE SET NULL"); + + if (c.onDelete == onAction.cascade) builder.addCode(" ON DELETE CASCADE"); + if (c.onDelete == onAction.noAction) builder.addCode(" ON DELETE NO ACTION"); + if (c.onDelete == onAction.setDefault) builder.addCode(" ON DELETE SET DEFUALT"); + if (c.onDelete == onAction.setNull) builder.addCode(" ON DELETE SET NULL"); + 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)); + + 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: string) => { + const qb = new QueryBuilder(); + qb.addCode("ALTER TABLE "); + qb.addCode(handler.builders.escapeID(table)); + 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; + }, + } + + builders = { + query: (qb: QueryBuilder): Query => { + 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 new Query([sql, args]); + }, + + attributeSettings: (handler: Handler, a: Attribute): QueryBuilder => { + const builder = new QueryBuilder(); + builder.append(a.type.serialize(handler)); + if (a.ops.autoIncrement) { + builder.addCode(" auto_increment"); + } + 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.notNull != null) { + if (!a.ops.autoIncrement && !a.ops.primaryKey && a.ops.default == null) { + builder.addCode(" not null"); + } 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("")); + }, + } + + aggregations = { + 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 = { + and: joinArg("and"), + or: joinArg("or"), + le: joinArg("<"), + leq: joinArg("<="), + eq: joinArg("="), + geq: joinArg(">="), + ge: joinArg(">"), + plus: joinArg("+"), + minus: joinArg("-"), + mult: joinArg("*"), + divide: joinArg("-/"), + not: (handler: Handler, a: allModifierInput[]): QueryBuilder => { + let e = a[0]; + if (e instanceof Attribute) return new QueryBuilder([{ data: "not (" + e.toString(handler) + ")" }]) + if (e instanceof Modifier || e instanceof selectQuery || e instanceof Aggregation) { + const builder = new QueryBuilder(); + builder.addCode("not ("); + builder.append(e.serialize(handler)); + builder.addCode(")"); + return builder; + } + return new QueryBuilder([ + { data: "not(" }, + { inject: true, data: e }, + { data: ")" } + ]); + }, + like: (handler: Handler, a: allModifierInput[]): QueryBuilder => { + const builder = new QueryBuilder(); + if (a[0] instanceof Attribute) builder.addCode(a[0].toString()); + else if (a[0] instanceof Modifier || a[0] instanceof selectQuery || a[0] instanceof Aggregation) { + builder.append(a[0].serialize(handler)); + } else { + builder.addInjection(a[0]); + } + builder.addCode(" LIKE "); + if (a[1] instanceof Attribute) builder.addCode(a[1].toString()); + else if (a[1] instanceof Modifier || a[1] instanceof selectQuery || a[1] instanceof Aggregation) { + builder.append(a[1].serialize(handler)); + } else { + builder.addInjection(a[1]); + } + return builder; + }, + concat: (handler: Handler, a: allModifierInput[]): QueryBuilder => { + const builder = new QueryBuilder(); + builder.addCode("CONCAT("); + for (let i = 0; i < a.length; i++) { + const e = a[i]; + if (e instanceof Attribute) builder.addCode(e.toString()); + else if (e instanceof Modifier || e instanceof selectQuery || e instanceof Aggregation) { + builder.append(e.serialize(handler)); + } else { + builder.addInjection(e); + } + if (i < a.length - 1) builder.addCode(", "); + } + builder.addCode(")"); + return builder; + } + } + + datatypes = { + char: dataTypeSingleNum("char"), + varchar: dataTypeSingleNum("varchar"), + binary: dataTypeSingleNum("binary"), + varbinary: dataTypeSingleNum("varbinary"), + + tinyblob: dataTypeNoArg("tinyblob"), + blob: dataTypeNoArg("blob"), + mediumblob: dataTypeNoArg("mediumblob"), + longblob: dataTypeNoArg("longblob"), + tinytext: dataTypeNoArg("tinytext"), + text: dataTypeNoArg("text"), + mediumtext: dataTypeNoArg("mediumtext"), + longtext: dataTypeNoArg("longtext"), + + enum: (a: primaryData[]): QueryBuilder => { + const builder = new QueryBuilder(); + builder.addCode("enum("); + builder.addInjectionCommaSeperated(a); + builder.addCode(")"); + return builder; + }, + set: (a: primaryData[]): QueryBuilder => { + const builder = new QueryBuilder(); + builder.addCode("set("); + builder.addInjectionCommaSeperated(a); + builder.addCode(")"); + return builder; + }, + bool: dataTypeNoArg("bool"), + bit: dataTypeNoArg("bit"), + tinyint: dataTypeNoArg("tinyint"), + smallint: dataTypeNoArg("smallint"), + mediumint: dataTypeNoArg("mediumint"), + int: dataTypeNoArg("int"), + bigint: dataTypeNoArg("bigint"), + + float: dataTypeDblNum("float"), + double: dataTypeDblNum("double"), + decimal: dataTypeDblNum("decimal"), + + date: dataTypeNoArg("date"), + datetime: dataTypeNoArg("datatime"), + timestamp: dataTypeNoArg("timestamp"), + time: dataTypeNoArg("time"), + year: dataTypeNoArg("year"), } }; -function joinArg(type: string, s: any) { - return (a: (allModifierInput)[]): serializeReturn => { - let args: primaryData[] = []; - let sql = "(" + a.map(d => { - if (d instanceof Attribute) return d; - if (d instanceof Modifier || d instanceof selectQuery || d instanceof Aggregation) { - let [sqli, argsi] = d.serialize(s); - args.push(...(argsi.flat(Infinity))); - return sqli; - } - args.push(d); - return "?"; - }).join(" " + type + " ") + ")"; - return [sql, args] +function dataTypeNoArg(type: string) { + return (a: primaryData[]): QueryBuilder => { + return new QueryBuilder([{ + inject: false, + data: type + }]); + } +} +function dataTypeSingleNum(type: string) { + return (a: primaryData[]): QueryBuilder => { + return new QueryBuilder([ + { data: type + "(" }, + { inject: true, data: a[0] }, + { data: ")" } + ]); + } +} +function dataTypeDblNum(type: string) { + 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)[]): 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); + } + 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 700f537..77ad471 100644 --- a/src/funcs.ts +++ b/src/funcs.ts @@ -1,22 +1,93 @@ -import { Attribute } from "./db"; -import { Aggregation, BooleanModifier, NumberModifier } from "./dbStructure"; -import { selectQuery } from "./query"; +import { Attribute, Table } from "./db"; +import { Aggregation, BooleanModifier, checkConstraint, Datatype, foreignConstraint, NumberModifier, StringModifier, uniqueConstraint } from "./dbStructure"; +import { insertQuery, removeQuery, selectQuery, updateQuery } from "./query"; import { allModifierInput, selectElements, selectFromElements } from "./types"; //modifiers -export const and = (...args: (BooleanModifier)[]) => new BooleanModifier("and",args); -export const or = (...args: (BooleanModifier)[]) => new BooleanModifier("or",args); -export const eq = (...args: (allModifierInput)[]) => new BooleanModifier("eq",args); -export const plus = (...args: (allModifierInput)[]) => new NumberModifier("plus",args); -export const minus = (...args: (allModifierInput)[]) => new NumberModifier("minus",args); +export const and = (...args: BooleanModifier[]) => new BooleanModifier("and", args); +export const or = (...args: BooleanModifier[]) => new BooleanModifier("or", args); +export const ge = (...args: allModifierInput[]) => new BooleanModifier("ge", args); +export const geq = (...args: allModifierInput[]) => new BooleanModifier("geq", args); +export const eq = (...args: allModifierInput[]) => new BooleanModifier("eq", args); +export const leq = (...args: allModifierInput[]) => new BooleanModifier("leq", args); +export const le = (...args: allModifierInput[]) => new BooleanModifier("le", args); +export const plus = (...args: allModifierInput[]) => new NumberModifier("plus", args); +export const minus = (...args: allModifierInput[]) => new NumberModifier("minus", args); +export const mult = (...args: allModifierInput[]) => new NumberModifier("mult", args); +export const divide = (...args: allModifierInput[]) => new NumberModifier("divide", args); + +export const not = (arg: allModifierInput) => new BooleanModifier("not", [arg]); +export const like = (a: allModifierInput, b: allModifierInput) => new BooleanModifier("like", [a, b]); + +export const concat = (...args: allModifierInput[]) => new StringModifier("concat", args); //aggregations - -export const count = (a:Attribute) => new Aggregation("count",a); -export const sum = (a:Attribute) => new Aggregation("sum",a); -export const avg = (a:Attribute) => new Aggregation("avg",a); -export const min = (a:Attribute) => new Aggregation("min",a); -export const max = (a:Attribute) => new Aggregation("max",a); +export const count = (a: Attribute) => new Aggregation("count", a); +export const sum = (a: Attribute) => new Aggregation("sum", a); +export const avg = (a: Attribute) => new Aggregation("avg", a); +export const min = (a: Attribute) => new Aggregation("min", a); +export const max = (a: Attribute) => new Aggregation("max", a); //query -export const select = (args: selectElements[],from: selectFromElements) => new selectQuery(args,from); +export const select = (args: selectElements[], from: selectFromElements) => new selectQuery(args, from); +export const insert = (...attrs: Attribute[]) => new insertQuery(attrs); +export const update = (table: Table) => new updateQuery(table); +export const remove = (table: Table) => new removeQuery(table); + +//datatypes + +export const CHAR = (size: number) => new Datatype("char", [size]); +export const VARCHAR = (size: number) => new Datatype("varchar", [size]); +export const BINARY = (size: number) => new Datatype("binary", [size]); +export const VARBINARY = (size: number) => new Datatype("varbinary", [size]); + +export const TINYBLOB = new Datatype("tinyblob", []); +export const BLOB = new Datatype("blob", []); +export const MEDIUMBLOB = new Datatype("mediumblob", []); +export const LONGBLOB = new Datatype("longblob", []); + +export const TINYTEXT = new Datatype("tinytext", []); +export const TEXT = new Datatype("text", []); +export const MEDIUMTEXT = new Datatype("mediumtext", []); +export const LONGTEXT = new Datatype("longtext", []); + +export const ENUM = (...values: string[]) => new Datatype("enum", values); +export const SET = (...values: string[]) => new Datatype("set", values); + +export const BOOL = new Datatype("bool", []); +export const BIT = new Datatype("bit", []); +export const TINYINT = new Datatype("tinyint", []); +export const SMALLINT = new Datatype("smallint", []); +export const MEDIUMINT = new Datatype("mediumint", []); +export const INT = new Datatype("int", []); +export const BIGINT = new Datatype("bigint", []); + +export const FLOAT = (size: number, d: number) => new Datatype("float", [size, d]); +export const DOUBLE = (size: number, d: number) => new Datatype("double", [size, d]); +export const DECIMAL = (size: number, d: number) => new Datatype("decimal", [size, d]); + +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", []); + +// Constraints +//TODO: +//primary key +export const foreignKey = (attrs: Attribute[], target: Attribute[]) => new foreignConstraint( + "foreign_constraint_" + + attrs.map(a => a.name + "_" + a.table.dbLangTableName).join("_") + + "_to_" + + target.map(a => a.name + "_" + a.table.dbLangTableName).join("_"), + attrs, target); +export const uniqueKey = (attrs: Attribute[]) => new uniqueConstraint("unique_constraint_" + attrs.map(a => a.name + "_" + a.table.dbLangTableName).join("_"), attrs); +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 10e6252..9fd00d6 100644 --- a/src/query.ts +++ b/src/query.ts @@ -1,26 +1,73 @@ -import { Attribute, DB } from "./db"; +import { Attribute, DB, Table } from "./db"; import { BooleanModifier, Modifier } from "./dbStructure"; import { Handler } from "./defaultHandler"; -import { primaryData, selectElements, selectFromElements, serializeReturn } from "./types"; +import { allModifierInput, primaryData, selectElements, selectFromElements, serializeReturn } from "./types"; export class Query { - sql : string; - values : primaryData[]; - constructor(sql: string, values: primaryData[]) { + 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]); + } + addCodeCommaSeperated(data: string[], comma = ", ") { + for (let i = 0; i < data.length; i++) { + const e = data[i]; + this.list.push([false, e]); + if (i + 1 < data.length) this.list.push([false, comma]); + } + } + 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 < data.length) this.list.push([false, comma]); + } + } + append(qb: QueryBuilder) { + this.list.push(...qb.list); + } + appendEnding(qb: QueryBuilder) { + this.append(qb); + this.list.push([false, ";"]); + } + isEmpty() { + return this.list.length == 0; + } + /*setHandler(fun:(d:any)=>any){ + + }*/ +} + + export class selectQuery { - attr:selectElements[] = []; - from:selectFromElements; - constructor(a: selectElements[], from:selectFromElements) { + attr: selectElements[] = []; + from: selectFromElements; + constructor(a: selectElements[], from: selectFromElements) { this.attr.push(...a.flat(Infinity)); this.from = from ? from : null; } - whereD:BooleanModifier | null = null; + whereD: BooleanModifier | null = null; where(m: BooleanModifier) { this.whereD = m; return this; @@ -30,18 +77,111 @@ export class selectQuery { this.groupByD = a; return this; } - havingD:Modifier | null = null; - having(m: Modifier) { + havingD: BooleanModifier | null = null; + having(m: BooleanModifier) { this.havingD = m; return this; } - - serialize(handler = Handler) : serializeReturn { - return handler.querys.select(this); + limitD: number | null = null; + limit(i: number) { + this.limitD = i; + return this; } - query(db: DB) { - const s = this.serialize(db.getHandler()); - return new Query(s[0],s[1]); + serialize(handler: Handler): QueryBuilder { + return handler.querys.select(handler, this); + } + + async query(db: DB) { + const handler = db.getHandler(); + const builder = this.serialize(handler); + const s = handler.builders.query(builder); + return await db.query(s); + } +} + +export class insertQuery { + attrs: Attribute[]; + values: primaryData[][] = []; + select: selectQuery | null = null; + constructor(attrs: Attribute[]) { + if (attrs.length == 0) throw new Error("Insertion must be done in at least one Column."); + for (let i = 0; i < attrs.length; i++) { + if (attrs[i].table != attrs[0].table) throw new Error("Insertion Columns must be in one Table."); + } + this.attrs = attrs; + } + add(...data: primaryData[]) { + if (this.select != null) throw new Error("Can not add Values when using select!"); + this.values.push(data); + return this; + } + addValues(...data: primaryData[][]) { + if (this.select != null) throw new Error("Can not add Values when using select!"); + this.values.push(...data); + return this; + } + setSelect(state: selectQuery) { + if (this.values.length != 0) throw new Error("Can not add select when using values!"); + this.select = state; + return this; + } + serialize(handler: Handler): QueryBuilder { + return handler.querys.insert(handler, this); + } + async query(db: DB) { + const handler = db.getHandler(); + const builder = this.serialize(handler); + const s = handler.builders.query(builder); + return await db.query(s); + } +} + +export class updateQuery { + table: Table; + setD: ([Attribute, allModifierInput])[] = []; + whereD: BooleanModifier | null = null; + + constructor(table: Table) { + this.table = table; + } + set(attr: Attribute, value: allModifierInput) { + if (this.table != attr.table) throw new Error("Can only edit columns of the updated table!"); + this.setD.push([attr, value]); + return this; + } + where(w: BooleanModifier) { + this.whereD = w; + return this; + } + serialize(handler: Handler): QueryBuilder { + return handler.querys.update(handler, this); + } + async query(db: DB) { + const handler = db.getHandler(); + const builder = this.serialize(handler); + const s = handler.builders.query(builder); + return await db.query(s); + } +} + +export class removeQuery { + table: Table; + whereD: BooleanModifier | null = null; + constructor(table: Table) { + this.table = table; + } + where(w: BooleanModifier) { + this.whereD = w; + return this; + } + serialize(handler: Handler): QueryBuilder { + return handler.querys.remove(handler, this); + } + async query(db: DB) { + const handler = db.getHandler(); + 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 0ecfc4e..026c038 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ import { Attribute, Table } from "./db"; -import { Aggregation, Joins, Modifier } from "./dbStructure"; -import { selectQuery } from "./query"; +import { Aggregation, BooleanModifier, Datatype, Joins, Modifier } from "./dbStructure"; +import { QueryBuilder, selectQuery } from "./query"; export type primaryData = string | number | boolean | null; export type allModifierInput = primaryData | Modifier | selectQuery | Attribute | Aggregation; @@ -10,10 +10,41 @@ export type selectFromElements = Table | Joins | null; export type serializeReturn = [string, primaryData[]]; +export type DatatypeBuild = [QueryBuilder, number]; -export enum onAction{ +export type attributeSettings = { + unique?: boolean, + autoIncrement?: boolean, + default?: primaryData, + notNull?: boolean + primaryKey?: boolean, + foreginKey?: { + link: Attribute, + onDelete?: onAction, + onUpdate?: onAction + }, + check?: BooleanModifier | ((a: Attribute) => BooleanModifier) +}; + +export type extendedAttributeSettings = { + type: Datatype, + unique?: boolean, + autoIncrement?: boolean, + default?: primaryData, + notNull?: boolean + primaryKey?: boolean, + foreginKey?: { + link: Attribute, + onDelete?: onAction, + onUpdate?: onAction + }, + check?: BooleanModifier | ((a: Attribute) => BooleanModifier) +} + +export enum onAction { + nothing, cascade, noAction, setNull, setDefault -} \ No newline at end of file +}