First Working Version #1

Merged
jusax23 merged 14 commits from dev into main 2023-02-14 23:33:55 +01:00
6 changed files with 304 additions and 86 deletions
Showing only changes of commit 78d894c415 - Show all commits

View file

@ -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)

View file

@ -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;
this.ops = ops;
if(ops.check != null){
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);
}

View file

@ -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.");

View file

@ -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"),

View file

@ -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+1<data.length)this.list.push([false, comma]);
}
}
addInjection(data: primaryData) {
this.list.push([true, data]);
}
@ -39,6 +47,16 @@ export class QueryBuilder {
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){
}*/
}
@ -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);
}
}

View file

@ -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 {