First Working Version #1

Merged
jusax23 merged 14 commits from dev into main 2023-02-14 23:33:55 +01:00
7 changed files with 300 additions and 108 deletions
Showing only changes of commit 37385589d6 - Show all commits

View file

@ -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/*

View file

@ -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';

View file

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

View file

@ -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;
}
}

View file

@ -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";
@ -62,3 +62,18 @@ 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 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
*/

View file

@ -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<data.length)this.list.push([false, comma]);
}
}
append(qb: QueryBuilder) {
this.list.push(...qb.list);
}
}
export class selectQuery {
attr: selectElements[] = [];
from: selectFromElements;
@ -41,12 +70,13 @@ export class selectQuery {
return this;
}
serialize(handler : Handler): serializeReturn {
return handler.querys.select(handler,this);
serialize(handler: Handler): QueryBuilder {
return handler.querys.select(handler, this);
}
query(db: DB) {
const s = this.serialize(db.getHandler());
return db.query(new Query(s[0], s[1]));
const handler = db.getHandler();
const s = handler.builders.query(this.serialize(handler));
return db.query(new Query(s));
}
}

View file

@ -1,5 +1,5 @@
import { Attribute, Table } from "./db";
import { Aggregation, Joins, Modifier } from "./dbStructure";
import { Aggregation, BooleanModifier, Datatype, Joins, Modifier } from "./dbStructure";
import { selectQuery } from "./query";
export type primaryData = string | number | boolean | null;
@ -20,8 +20,23 @@ export type attributeSettings = {
link: Attribute,
onDelete?: onAction,
onUpdate?: onAction
}
},
check?: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
}
export enum onAction {
cascade,