dblang/src/defaultHandler.ts

453 lines
19 KiB
TypeScript
Raw Normal View History

2023-01-24 20:34:03 +01:00
import { Attribute, DB, Table } from "./db"
2023-02-13 23:44:08 +01:00
import { Aggregation, checkConstraint, Constraint, Datatype, Modifier, uniqueConstraint } from "./dbStructure"
import { Query, QueryBuilder, selectQuery } from "./query"
2023-01-23 21:19:18 +01:00
import { allModifierInput, primaryData, serializeReturn } from "./types"
export class Handler {
2023-02-13 23:44:08 +01:00
async syncDB(db: DB, handler: Handler, deleteInDB: boolean = false) {
console.log("start sync");
2023-01-24 20:34:03 +01:00
2023-02-13 23:44:08 +01:00
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));
}
2023-01-24 20:34:03 +01:00
querys = {
2023-01-29 22:11:24 +01:00
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)}`);
2023-01-23 21:19:18 +01:00
if (q.whereD) {
2023-01-29 22:11:24 +01:00
builder.addCode(" where ");
builder.append(q.whereD.serialize(handler));
2023-01-23 21:19:18 +01:00
}
2023-01-23 22:10:33 +01:00
if (q.groupByD.length > 0) {
2023-01-29 22:11:24 +01:00
builder.addCode(" group by ");
builder.append(joinArg(",")(handler, q.groupByD));
2023-01-23 21:19:18 +01:00
if (q.havingD) {
2023-01-29 22:11:24 +01:00
builder.addCode(" having ");
builder.append(q.havingD.serialize(handler));
2023-01-23 21:19:18 +01:00
}
}
2023-01-29 22:11:24 +01:00
if (q.havingD) {
builder.addCode(" limit ");
builder.addInjection(q.limitD);
}
2023-02-13 23:44:08 +01:00
/*builder.setHandler((json)=>{
return json;
});*/
2023-01-29 22:11:24 +01:00
return builder;
2023-01-24 20:34:03 +01:00
},
2023-01-29 22:11:24 +01:00
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;
},
2023-02-13 23:44:08 +01:00
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();
2023-01-29 22:11:24 +01:00
2023-02-13 23:44:08 +01:00
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));
2023-01-24 20:34:03 +01:00
2023-02-13 23:44:08 +01:00
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
2023-01-29 22:11:24 +01:00
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(),
2023-02-13 23:44:08 +01:00
// 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(),*/
2023-01-29 22:11:24 +01:00
}
2023-01-24 20:34:03 +01:00
builders = {
2023-02-13 23:44:08 +01:00
query: (qb: QueryBuilder): Query => {
2023-01-29 22:11:24 +01:00
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;
}
}
2023-02-13 23:44:08 +01:00
return new Query([sql, args]);
2023-01-29 22:11:24 +01:00
},
attributeSettings: (handler: Handler, a: Attribute): QueryBuilder => {
const builder = new QueryBuilder();
builder.append(a.type.serialize(handler));
2023-01-24 20:34:03 +01:00
if (a.ops.autoIncrement) {
2023-01-29 22:11:24 +01:00
builder.addCode(" auto_increment");
2023-01-24 20:34:03 +01:00
}
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)}`);
2023-01-29 22:11:24 +01:00
builder.addCode(" default ");
builder.addInjection(a.ops.default);
2023-01-24 20:34:03 +01:00
}
if (a.ops.notNull != null) {
2023-01-29 22:11:24 +01:00
if (!a.ops.autoIncrement && !a.ops.primaryKey && a.ops.default == null) {
builder.addCode(" not null");
2023-02-13 23:44:08 +01:00
} else builder.addCode(" null");
} else builder.addCode(" null");
2023-01-29 22:11:24 +01:00
return builder;
2023-01-24 20:34:03 +01:00
},
2023-01-29 22:11:24 +01:00
escapeID: (key: string): string => {
if (!key || key === "" || key.includes('\u0000')) throw new Error("Can not escape empty key or with null unicode!");
2023-01-24 20:34:03 +01:00
if (key.match(/^`.+`$/g)) return key;
return `\`${key.replace(/`/g, '``')}\``;
2023-02-13 23:44:08 +01:00
},
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(""));
},
2023-01-23 21:19:18 +01:00
}
2023-01-24 20:34:03 +01:00
aggregations = {
2023-01-29 22:11:24 +01:00
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) + ")" }]),
2023-01-23 21:19:18 +01:00
}
2023-01-24 20:34:03 +01:00
modifiers = {
and: joinArg("and"),
or: joinArg("or"),
le: joinArg("<"),
leq: joinArg("<="),
eq: joinArg("="),
geq: joinArg(">="),
ge: joinArg(">"),
plus: joinArg("+"),
minus: joinArg("-"),
2023-01-29 22:11:24 +01:00
not: (handler: Handler, a: allModifierInput[]): QueryBuilder => {
2023-01-23 21:19:18 +01:00
let e = a[0];
2023-01-29 22:11:24 +01:00
if (e instanceof Attribute) return new QueryBuilder([{ data: "not (" + e.toString(handler) + ")" }])
2023-01-23 21:19:18 +01:00
if (e instanceof Modifier || e instanceof selectQuery || e instanceof Aggregation) {
2023-01-29 22:11:24 +01:00
const builder = new QueryBuilder();
builder.addCode("not (");
builder.append(e.serialize(handler));
builder.addCode(")");
return builder;
2023-01-23 21:19:18 +01:00
}
2023-01-29 22:11:24 +01:00
return new QueryBuilder([
{ data: "not(" },
{ inject: true, data: e },
{ data: ")" }
])
2023-01-23 21:19:18 +01:00
}
}
2023-01-24 20:34:03 +01:00
datatypes = {
2023-01-24 10:40:16 +01:00
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"),
2023-01-29 22:11:24 +01:00
enum: (a: primaryData[]): QueryBuilder => {
const builder = new QueryBuilder();
builder.addCode("enum(");
builder.addInjectionCommaSeperated(a);
builder.addCode(")");
return builder;
2023-01-24 10:40:16 +01:00
},
2023-01-29 22:11:24 +01:00
set: (a: primaryData[]): QueryBuilder => {
const builder = new QueryBuilder();
builder.addCode("set(");
builder.addInjectionCommaSeperated(a);
builder.addCode(")");
return builder;
2023-01-24 10:40:16 +01:00
},
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"),
2023-02-13 23:44:08 +01:00
date: dataTypeNoArg("date"),
2023-01-24 20:34:03 +01:00
datetime: dataTypeNoArg("datatime"),
2023-01-24 10:40:16 +01:00
timestamp: dataTypeNoArg("timestamp"),
time: dataTypeNoArg("time"),
year: dataTypeNoArg("year"),
}
2023-01-23 21:19:18 +01:00
};
2023-01-24 20:34:03 +01:00
function dataTypeNoArg(type: string) {
2023-01-29 22:11:24 +01:00
return (a: primaryData[]): QueryBuilder => {
return new QueryBuilder([{
inject: false,
data: type
}]);
2023-01-24 10:40:16 +01:00
}
}
2023-01-24 20:34:03 +01:00
function dataTypeSingleNum(type: string) {
2023-01-29 22:11:24 +01:00
return (a: primaryData[]): QueryBuilder => {
return new QueryBuilder([
{ data: type + "(" },
{ inject: true, data: a[0] },
{ data: ")" }
]);
2023-01-24 10:40:16 +01:00
}
}
2023-01-24 20:34:03 +01:00
function dataTypeDblNum(type: string) {
2023-01-29 22:11:24 +01:00
return (a: primaryData[]): QueryBuilder => {
return new QueryBuilder([
{ data: type + "(" },
{ inject: true, data: a[0] },
{ data: ", " },
{ inject: true, data: a[1] },
{ data: ")" }
]);
2023-01-24 10:40:16 +01:00
}
}
2023-01-24 20:34:03 +01:00
function joinArg(type: string) {
2023-01-29 22:11:24 +01:00
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);
2023-01-23 21:19:18 +01:00
}
2023-01-29 22:11:24 +01:00
if (i + 1 < a.length) builder.addCode(" " + type + " ");
}
return builder;
2023-01-23 21:19:18 +01:00
}
}