diff --git a/readme.md b/readme.md index ec357fd..2e2ee40 100644 --- a/readme.md +++ b/readme.md @@ -4,14 +4,14 @@ TypeScript Libary for relational Database Requests. Requests are written in js/ts and it supports automatic Schema evulution. -Fetures +Features: - [x] select Query - [x] insert Query - [x] update Query - [x] remove Query - [x] create Schema - [x] remove unused Schema -- [ ] joins +- [x] joins - [ ] change Schema - [ ] Key-Value Store Shortcut - [ ] Views @@ -93,7 +93,7 @@ See: [Modifiers](#modifier), [Aggregations](#aggregations) #### Select: ```javascript -import {select} from "dblang" +import { select } from "dblang" let res = await select([Table2.AttrName2], Table1) .where(eq(Table2.AttrName2, 5)) //optional @@ -101,12 +101,32 @@ let res = await select([Table2.AttrName2], Table1) .having(le(Table2.AttrName3, 5)) //optional .limit(10) //optional .query(db); - ``` +Joins: +```javascript +import { INT, onAction } from "dblang" + +const TableA = db.newTable("TableA"); +const TableB = db.newTable("TableB"); +TableA.addAttribute("A1", INT, { + primaryKey: true +}); +TableB.addAttribute("B1", INT, { + primaryKey: true, + foreignKey:{ + link: TableA.A1, + onDelete: onAction.cascade + } +}); +let res = await select([TableA.A1, TableB.B1], innerJoinOn(eq(TableA.A1, Tableb.B1))) + .query(db); +``` +See: [Joins](#joins) + #### Insert: ```javascript -import {select} from "dblang" +import { select } from "dblang" await insert(Table2.AttrName2, Table2.AttrName3) .add(1, 2) @@ -121,6 +141,8 @@ await insert(Table2.AttrName2, Table2.AttrName3) #### Update: ```javascript +import { update } from "dblang" + await update(Table2) .set(Table2.AttrName2, plus(Table2.AttrName2,1)) .set(Table2.AttrName3, minus(Table2.AttrName2,1)) @@ -130,6 +152,8 @@ await update(Table2) #### Remove: ```javascript +import { remove } from "dblang" + await remove(Table2) .where(geq(Table2.AttrName2, 5)) .query(db); @@ -205,7 +229,25 @@ StringModifier: (v\* = string, number, boolean, null, Modifier, Aggregation, select Query, Attribute) -# Aggregations +#### Joins + +- `crossJoin(t1, t2, ...)` +- `naturalJoin(t1, t2, ...)` +- `leftNaturalJoin(t1, t2, ...)` +- `rightNaturalJoin(t1, t2, ...)` +- `fullNaturalJoin(t1, t2, ...)` +- `innerJoinUsing(t1, t2, a1, a2, ...)` +- `leftJoinUsing(t1, t2, a1, a2, ...)` +- `rightJoinUsing(t1, t2, a1, a2, ...)` +- `fullJoinUsing(t1, t2, a1, a2, ...)` +- `innerJoinOn(t1, t2, b)` +- `leftJoinOn(t1, t2, b)` +- `rightJoinOn(t1, t2, b)` +- `fullJoinOn(t1, t2, b)` + +(t\* = Table or Join; a\* = Attributes; b = Boolean Modifier) + +#### Aggregations - `count(a)` - `sum(a)` - `avg(a)` diff --git a/src/dbStructure.ts b/src/dbStructure.ts index e61404c..1ca68d8 100644 --- a/src/dbStructure.ts +++ b/src/dbStructure.ts @@ -1,7 +1,7 @@ import { Attribute, Table } from "./db"; import { Handler } from "./defaultHandler"; import { QueryBuilder } from "./query"; -import { allModifierInput, onAction, primaryData, serializeReturn } from "./types"; +import { allModifierInput, joinElements, joinType, onAction, primaryData, serializeReturn } from "./types"; export class Datatype { type: string; @@ -42,11 +42,62 @@ export class Aggregation { return handler.aggregations[this.t as keyof typeof handler.aggregations](handler, this.a); } } -export class Joins { - +export interface Joins { + serialize(handler: Handler): QueryBuilder +} +export class joinNatural implements Joins{ + tables: joinElements[]; + type: joinType; + constructor(tables:joinElements[], type: joinType){ + this.tables = tables; + this.type = type; + } serialize(handler: Handler): QueryBuilder { - return new QueryBuilder(); + return handler.joins.natural(handler,this); } + +} +export class onJoin implements Joins{ + tableA: joinElements; + tableB: joinElements; + type: joinType; + on: BooleanModifier; + constructor(tableA: joinElements, tableB: joinElements, type: joinType, on: BooleanModifier){ + this.tableA = tableA; + this.tableB = tableB; + this.type = type; + this.on = on; + } + serialize(handler: Handler): QueryBuilder { + return handler.joins.on(handler,this); + } + +} +export class usingJoin implements Joins{ + tableA: joinElements; + tableB: joinElements; + type: joinType; + using: Attribute[]; + constructor(tableA: joinElements, tableB: joinElements, type: joinType, using: Attribute[]){ + this.tableA = tableA; + this.tableB = tableB; + this.type = type; + this.using = using; + } + serialize(handler: Handler): QueryBuilder { + return handler.joins.using(handler,this); + } + +} +export class joinCross implements Joins{ + tables: joinElements[]; + constructor(tables:joinElements[]){ + this.tables = tables; + } + serialize(handler: Handler): QueryBuilder { + return handler.joins.cross(handler,this); + } + } export interface Constraint { diff --git a/src/defaultHandler.ts b/src/defaultHandler.ts index 3765dfb..25bc8b1 100644 --- a/src/defaultHandler.ts +++ b/src/defaultHandler.ts @@ -1,12 +1,10 @@ import { Attribute, DB, Table } from "./db" -import { Aggregation, checkConstraint, Constraint, Datatype, foreignConstraint, Modifier, uniqueConstraint } from "./dbStructure" +import { Aggregation, checkConstraint, joinCross, Datatype, foreignConstraint, Modifier, joinNatural, onJoin, uniqueConstraint, usingJoin } from "./dbStructure" import { insertQuery, Query, QueryBuilder, removeQuery, selectQuery, updateQuery } from "./query" -import { allModifierInput, onAction, primaryData, serializeReturn } from "./types" +import { allModifierInput, joinType, onAction, primaryData } from "./types" export class Handler { 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); @@ -237,7 +235,14 @@ export class Handler { const builder = new QueryBuilder(); builder.addCode("select "); builder.append(joinArg(", ")(handler, q.attr)); - builder.addCode(` from ${q.from == null ? 'DUAL' : q.from.serialize(handler)}`); + builder.addCode(` from `); + if(q.from == null){ + builder.addCode(" DUAL"); + }else if (q.from instanceof Table){ + builder.addCode(q.from.serialize(handler)); + }else{ + builder.append(q.from.serialize(handler)); + } if (q.whereD) { builder.addCode(" where "); builder.append(q.whereD.serialize(handler)); @@ -305,7 +310,7 @@ export class Handler { } return qb; }, - remove: (handler: Handler, q: removeQuery): QueryBuilder =>{ + remove: (handler: Handler, q: removeQuery): QueryBuilder => { const qb = new QueryBuilder(); qb.addCode("DELETE FROM "); qb.addCode(q.table.serialize(handler)); @@ -527,6 +532,91 @@ export class Handler { max: (handler: Handler, a: Attribute): QueryBuilder => new QueryBuilder([{ data: "max(" + a.toString(handler) + ")" }]), } + joins = { + natural: (handler: Handler, j: joinNatural) => { + const qb = new QueryBuilder(); + for (let i = 0; i < j.tables.length; i++) { + const t = j.tables[i]; + if (t instanceof Table) qb.addCode(t.serialize(handler)); + else { + qb.addCode("("); + qb.append(t.serialize(handler)); + qb.addCode(")"); + } + if (i + 1 < j.tables.length) { + qb.addCode(" natural "); + if (j.type == joinType.left) qb.addCode("left"); + if (j.type == joinType.right) qb.addCode("right"); + if (j.type == joinType.full) qb.addCode("full"); + qb.addCode(" join "); + } + } + return qb; + }, + using: (handler: Handler, j: usingJoin) => { + const qb = new QueryBuilder(); + if (j.tableA instanceof Table) qb.addCode(j.tableA.serialize(handler)); + else { + qb.addCode("("); + qb.append(j.tableA.serialize(handler)); + qb.addCode(")"); + } + qb.addCode(" "); + if (j.type == joinType.left) qb.addCode("left "); + if (j.type == joinType.right) qb.addCode("right "); + if (j.type == joinType.full) qb.addCode("full "); + qb.addCode("join "); + if (j.tableA instanceof Table) qb.addCode(j.tableA.serialize(handler)); + else { + qb.addCode("("); + qb.append(j.tableA.serialize(handler)); + qb.addCode(")"); + } + qb.addCode(" using ("); + qb.addCodeCommaSeperated(j.using.map(t => t.serialize(handler))); + qb.addCode(")"); + return qb; + }, + on: (handler: Handler, j: onJoin) => { + const qb = new QueryBuilder(); + if (j.tableA instanceof Table) qb.addCode(j.tableA.serialize(handler)); + else { + qb.addCode("("); + qb.append(j.tableA.serialize(handler)); + qb.addCode(")"); + } + qb.addCode(" "); + if (j.type == joinType.left) qb.addCode("left "); + if (j.type == joinType.right) qb.addCode("right "); + if (j.type == joinType.full) qb.addCode("full "); + qb.addCode("join "); + if (j.tableA instanceof Table) qb.addCode(j.tableA.serialize(handler)); + else { + qb.addCode("("); + qb.append(j.tableA.serialize(handler)); + qb.addCode(")"); + } + qb.addCode(" on ("); + qb.append(j.on.serialize(handler)); + qb.addCode(")"); + return qb; + }, + cross: (handler: Handler, j: joinCross) => { + const qb = new QueryBuilder(); + for (let i = 0; i < j.tables.length; i++) { + const t = j.tables[i]; + if (t instanceof Table) qb.addCode(t.serialize(handler)); + else { + qb.addCode("("); + qb.append(t.serialize(handler)); + qb.addCode(")"); + } + if (i + 1 < j.tables.length) qb.addCode(" cross join "); + } + return qb; + } + } + modifiers = { and: joinArg("and"), or: joinArg("or"), diff --git a/src/funcs.ts b/src/funcs.ts index 77ad471..e5e5ac3 100644 --- a/src/funcs.ts +++ b/src/funcs.ts @@ -1,7 +1,7 @@ import { Attribute, Table } from "./db"; -import { Aggregation, BooleanModifier, checkConstraint, Datatype, foreignConstraint, NumberModifier, StringModifier, uniqueConstraint } from "./dbStructure"; +import { Aggregation, BooleanModifier, checkConstraint, Datatype, foreignConstraint, joinCross, joinNatural, usingJoin, onJoin, NumberModifier, StringModifier, uniqueConstraint } from "./dbStructure"; import { insertQuery, removeQuery, selectQuery, updateQuery } from "./query"; -import { allModifierInput, selectElements, selectFromElements } from "./types"; +import { allModifierInput, joinType, selectElements, selectFromElements } from "./types"; //modifiers export const and = (...args: BooleanModifier[]) => new BooleanModifier("and", args); @@ -28,6 +28,25 @@ 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); +//join + +export const crossJoin = (...tables: Table[]) => new joinCross(tables); + +export const naturalJoin = (...tables: Table[]) => new joinNatural(tables, joinType.inner); +export const leftNaturalJoin = (...tables: Table[]) => new joinNatural(tables, joinType.left); +export const rightNaturalJoin = (...tables: Table[]) => new joinNatural(tables, joinType.right); +export const fullNaturalJoin = (...tables: Table[]) => new joinNatural(tables, joinType.full); + +export const innerJoinUsing = (tableA: Table, tableB: Table, ...using: Attribute[]) => new usingJoin(tableA, tableB, joinType.inner, using); +export const leftJoinUsing = (tableA: Table, tableB: Table, ...using: Attribute[]) => new usingJoin(tableA, tableB, joinType.left, using); +export const rightJoinUsing = (tableA: Table, tableB: Table, ...using: Attribute[]) => new usingJoin(tableA, tableB, joinType.right, using); +export const fullJoinUsing = (tableA: Table, tableB: Table, ...using: Attribute[]) => new usingJoin(tableA, tableB, joinType.full, using); + +export const innerJoinOn = (tableA: Table, tableB: Table, on: BooleanModifier) => new onJoin(tableA, tableB, joinType.inner, on); +export const leftJoinOn = (tableA: Table, tableB: Table, on: BooleanModifier) => new onJoin(tableA, tableB, joinType.left, on); +export const rightJoinOn = (tableA: Table, tableB: Table, on: BooleanModifier) => new onJoin(tableA, tableB, joinType.right, on); +export const fullJoinOn = (tableA: Table, tableB: Table, on: BooleanModifier) => new onJoin(tableA, tableB, joinType.full, on); + //query export const select = (args: selectElements[], from: selectFromElements) => new selectQuery(args, from); export const insert = (...attrs: Attribute[]) => new insertQuery(attrs); diff --git a/src/types.ts b/src/types.ts index 026c038..5b26bfc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,6 +7,7 @@ export type allModifierInput = primaryData | Modifier | selectQuery | Attribute export type selectElements = primaryData | Attribute | Aggregation | selectQuery export type selectFromElements = Table | Joins | null; +export type joinElements = Table | Joins; export type serializeReturn = [string, primaryData[]]; @@ -48,3 +49,11 @@ export enum onAction { setNull, setDefault } + +export enum joinType { + inner, + left, + right, + full, + cross +} \ No newline at end of file