Skip to content

Commit

Permalink
Join working
Browse files Browse the repository at this point in the history
  • Loading branch information
etienne-dldc committed Feb 6, 2023
1 parent 326a1a0 commit e8b39e2
Show file tree
Hide file tree
Showing 14 changed files with 859 additions and 688 deletions.
28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,28 @@
"typecheck:watch": "tsc --noEmit --watch"
},
"dependencies": {
"zensqlite": "^2.0.0-2"
"zensqlite": "^2.0.0-3"
},
"devDependencies": {
"@swc/core": "^1.3.24",
"@swc/core": "^1.3.32",
"@types/better-sqlite3": "^7.6.3",
"@types/dedent": "^0.7.0",
"@types/jest": "^29.2.4",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"@typescript-eslint/parser": "^5.47.1",
"@types/jest": "^29.4.0",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"better-sqlite3": "^8.0.1",
"dedent": "^0.7.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"jest": "^29.3.1",
"np": "^7.6.2",
"prettier": "^2.8.1",
"rimraf": "^3.0.2",
"eslint": "^8.33.0",
"eslint-config-prettier": "^8.6.0",
"jest": "^29.4.1",
"np": "^7.6.3",
"prettier": "^2.8.3",
"rimraf": "^4.1.2",
"sql-formatter": "^12.1.0",
"ts-jest": "^29.0.3",
"tslib": "^2.4.1",
"ts-jest": "^29.0.5",
"tslib": "^2.5.0",
"tsup": "^6.5.0",
"typescript": "^4.9.4"
"typescript": "^4.9.5"
},
"publishConfig": {
"access": "public",
Expand Down
111 changes: 67 additions & 44 deletions src/Datatype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,78 @@ export type SqliteDatatype = 'INTEGER' | 'TEXT' | 'REAL' | 'BLOB';

export type DatatypeAny = Datatype<any, any>;

export type Datatype<External, Internal> = {
export type Datatype<External, Internal = any> = {
[TYPES]: External;
name: string;
parse: (value: Internal) => External;
serialize: (value: External) => Internal;
type: SqliteDatatype;
};

function createDatatype<External, Internal>(dt: Omit<Datatype<External, Internal>, TYPES>): Datatype<External, Internal> {
return dt as any;
}
export const Datatype = (() => {
return {
create: createDatatype,
fromLiteral,
// preset
boolean: createDatatype<boolean, number>({
name: 'boolean',
parse: (value: number) => value === 1,
serialize: (value: boolean) => (value ? 1 : 0),
type: 'INTEGER',
}),
integer: createDatatype<number, number>({
name: 'integer',
parse: (value: number) => value,
serialize: (value: number) => value,
type: 'INTEGER',
}),
number: createDatatype<number, number>({
name: 'number',
parse: (value: number) => value,
serialize: (value: number) => value,
type: 'REAL',
}),
text: createDatatype<string, string>({
name: 'text',
parse: (value: string) => value,
serialize: (value: string) => value,
type: 'TEXT',
}),
date: createDatatype<Date, number>({
name: 'date',
parse: (value: number) => new Date(value),
serialize: (value: Date) => value.getTime(),
type: 'REAL',
}),
json: createDatatype<any, string>({
name: 'json',
parse: (value: string) => JSON.parse(value),
serialize: (value: any) => JSON.stringify(value),
type: 'TEXT',
}),
null: createDatatype<null, null>({
name: 'null',
parse: () => null,
serialize: () => null,
type: 'TEXT',
}),
any: createDatatype<any, any>({
name: 'any',
parse: (value: any) => value,
serialize: (value: any) => value,
type: 'TEXT',
}),
};

export const Datatype = {
create: createDatatype,
// preset
boolean: createDatatype<boolean, number>({
name: 'boolean',
parse: (value: number) => value === 1,
serialize: (value: boolean) => (value ? 1 : 0),
type: 'INTEGER',
}),
integer: createDatatype<number, number>({
name: 'integer',
parse: (value: number) => value,
serialize: (value: number) => value,
type: 'INTEGER',
}),
number: createDatatype<number, number>({
name: 'number',
parse: (value: number) => value,
serialize: (value: number) => value,
type: 'REAL',
}),
text: createDatatype<string, string>({
name: 'text',
parse: (value: string) => value,
serialize: (value: string) => value,
type: 'TEXT',
}),
date: createDatatype<Date, number>({
name: 'date',
parse: (value: number) => new Date(value),
serialize: (value: Date) => value.getTime(),
type: 'REAL',
}),
json: createDatatype<any, string>({
name: 'json',
parse: (value: string) => JSON.parse(value),
serialize: (value: any) => JSON.stringify(value),
type: 'TEXT',
}),
};
function fromLiteral<Val extends string | number | boolean | null>(val: Val): Datatype<Val> {
if (val === null) return Datatype.null as any;
if (typeof val === 'string') return Datatype.text as any;
if (typeof val === 'number') return Datatype.number as any;
if (typeof val === 'boolean') return Datatype.boolean as any;
throw new Error('Invalid literal');
}

function createDatatype<External, Internal>(dt: Omit<Datatype<External, Internal>, TYPES>): Datatype<External, Internal> {
return dt as any;
}
})();
99 changes: 46 additions & 53 deletions src/Expr.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,86 @@
import { Ast, builder } from 'zensqlite';
import { Datatype } from './Datatype';
import { Random } from './Random';
import { PRIV, TYPES } from './utils/constants';
import { mapObject } from './utils/utils';

export type IExprInternal_Param = { readonly name?: string; readonly value: any };

export interface IExprInternal {
// json option is set to true when the expression was already JSON parsed
export type ExprParser<Val> = (raw: any, json: boolean) => Val;

export interface IExprInternal<Val> {
readonly parse: ExprParser<Val>;
// used by Expr.external
readonly param?: IExprInternal_Param;
readonly populated?: any;
}

type InnerExpr = Ast.Expr;

export type IExpr<Val> = InnerExpr & { readonly [TYPES]: Val; readonly [PRIV]?: IExprInternal };

export type IColumnRef<Val> = Ast.Node<'Column'> & { readonly [TYPES]: Val; readonly [PRIV]?: IExprInternal };

export interface IJson<Value> {
readonly [TYPES]: Value;
}

// type ExprBuilder = {
// functionInvocation: (name: string, ...params: IExpr<any>[]) => IExpr<any>;
// literal: <Val extends string | number | boolean | null>(val: Val) => IExpr<Val>;
// add: <Val extends number>(left: IExpr<Val>, right: IExpr<Val>) => IExpr<Val>;
// equal: (left: IExpr<any>, right: IExpr<any>) => IExpr<boolean>;
// different: (left: IExpr<any>, right: IExpr<any>) => IExpr<boolean>;
// like: (left: IExpr<any>, right: IExpr<any>) => IExpr<boolean>;
// or: (left: IExpr<any>, right: IExpr<any>) => IExpr<boolean>;
// and: (left: IExpr<any>, right: IExpr<any>) => IExpr<boolean>;
// notNull: (expr: IExpr<any>) => IExpr<boolean>;
// concatenate: (left: IExpr<string>, right: IExpr<string>) => IExpr<string>;
// AggregateFunctions: {
// json_group_array: <Val>(expr: IExpr<Val>) => IExpr<IJson<Array<Val>>>;
// count: (expr: IExpr<any>) => IExpr<number>;
// };
// ScalarFunctions: {
// json_array_length: (expr: IExpr<IJson<Array<any>>>) => IExpr<number>;
// json_object: <Items extends Record<string, IExpr<any>>>(items: Items) => IExpr<IJson<{ [K in keyof Items]: Items[K][TYPES] }>>;
// };
// };
export type IExpr<Val> = InnerExpr & { readonly [TYPES]: Val; readonly [PRIV]: IExprInternal<Val> };

export const Expr = (() => {
return {
functionInvocation: (name: string, ...params: IExpr<any>[]): IExpr<any> => create(builder.Expr.functionInvocation(name, ...params)),
literal: <Val extends string | number | boolean | null>(val: Val): IExpr<Val> => create(builder.Expr.literal(val)),
add: <Val extends number>(left: IExpr<Val>, right: IExpr<Val>): IExpr<Val> => create(builder.Expr.add(left, right)),
equal: (left: IExpr<any>, right: IExpr<any>): IExpr<boolean> => create(builder.Expr.equal(left, right)),
different: (left: IExpr<any>, right: IExpr<any>): IExpr<boolean> => create(builder.Expr.different(left, right)),
like: (left: IExpr<any>, right: IExpr<any>): IExpr<boolean> => create(builder.Expr.like(left, right)),
or: (left: IExpr<any>, right: IExpr<any>): IExpr<boolean> => create(builder.Expr.or(left, right)),
and: (left: IExpr<any>, right: IExpr<any>): IExpr<boolean> => create(builder.Expr.and(left, right)),
notNull: (expr: IExpr<any>): IExpr<boolean> => create(builder.Expr.notNull(expr)),
concatenate: (left: IExpr<string>, right: IExpr<string>): IExpr<string> => create(builder.Expr.concatenate(left, right)),
functionInvocation: <Val>(name: string, parse: ExprParser<Val>, ...params: IExpr<any>[]) =>
create<Val>(builder.Expr.functionInvocation(name, ...params), parse),
literal: <Val extends string | number | boolean | null>(val: Val) => createLiteral<Val>(val),
add: (left: IExpr<number>, right: IExpr<number>) => create<number>(builder.Expr.add(left, right), Datatype.number.parse),
equal: (left: IExpr<any>, right: IExpr<any>) => create(builder.Expr.equal(left, right), Datatype.boolean.parse),
different: (left: IExpr<any>, right: IExpr<any>) => create(builder.Expr.different(left, right), Datatype.boolean.parse),
like: (left: IExpr<any>, right: IExpr<any>) => create(builder.Expr.like(left, right), Datatype.boolean.parse),
or: (left: IExpr<any>, right: IExpr<any>) => create(builder.Expr.or(left, right), Datatype.boolean.parse),
and: (left: IExpr<any>, right: IExpr<any>) => create(builder.Expr.and(left, right), Datatype.boolean.parse),
notNull: (expr: IExpr<any>) => create(builder.Expr.notNull(expr), Datatype.boolean.parse),
concatenate: (left: IExpr<string>, right: IExpr<string>): IExpr<string> =>
create(builder.Expr.concatenate(left, right), Datatype.text.parse),

external: <Val extends string | number | boolean | null>(val: Val, name?: string): IExpr<Val> => {
const paramName = (name ?? '') + '_' + Random.createId();
return create(builder.Expr.BindParameter.colonNamed(paramName), { name: paramName, value: val });
return create(builder.Expr.BindParameter.colonNamed(paramName), Datatype.fromLiteral(val).parse, { name: paramName, value: val });
},

column: <Val>(table: Ast.Identifier, column: string): IColumnRef<Val> => {
return create<Val>(builder.Expr.column({ column, table: { table } })) as IColumnRef<Val>;
column: <Val>(table: Ast.Identifier, column: string, parse: ExprParser<Val>) => {
return create<Val>(builder.Expr.column({ column, table: { table } }), parse);
},

AggregateFunctions: {
json_group_array: <Val>(expr: IExpr<Val>): IExpr<IJson<Array<Val>>> =>
create(builder.Expr.AggregateFunctions.json_group_array({ params: expr })),
count: (expr: IExpr<any>): IExpr<number> => create(builder.Expr.AggregateFunctions.count({ params: expr })),
avg: (expr: IExpr<any>): IExpr<number> => create(builder.Expr.AggregateFunctions.avg({ params: expr })),
json_group_array: <Val>(expr: IExpr<Val>): IExpr<Array<Val>> =>
create(builder.Expr.AggregateFunctions.json_group_array({ params: expr }), (raw, json) => {
const arr = json ? raw : JSON.parse(raw);
return arr.map((item: any) => parseExprVal(expr, item, true));
}),
count: (expr: IExpr<any>): IExpr<number> => create(builder.Expr.AggregateFunctions.count({ params: expr }), Datatype.number.parse),
avg: (expr: IExpr<number>): IExpr<number> => create(builder.Expr.AggregateFunctions.avg({ params: expr }), Datatype.number.parse),
},

ScalarFunctions: {
json_object: <Items extends Record<string, IExpr<any>>>(items: Items): IExpr<IJson<{ [K in keyof Items]: Items[K][TYPES] }>> =>
json_object: <Items extends Record<string, IExpr<any>>>(items: Items): IExpr<{ [K in keyof Items]: Items[K][TYPES] }> =>
create(
builder.Expr.ScalarFunctions.json_object(
...Object.entries(items)
.map(([name, value]) => [builder.Expr.literal(name), value])
.flat()
)
),
(raw, json) => {
const obj = json ? raw : JSON.parse(raw);
return mapObject(items, (name, expr) => parseExprVal(expr, obj[name], true));
}
),
// json_array_length: (expr: IExpr<IJson<Array<any>>>): IExpr<number> => create(builder.Expr.ScalarFunctions.json_array_length({ params: expr })),
},
};

function create<Val>(expr: InnerExpr, param?: IExprInternal_Param): IExpr<Val> {
if (!param) {
return expr as IExpr<Val>;
}
function createLiteral<Val extends string | number | boolean | null>(val: Val): IExpr<Val> {
return create<Val>(builder.Expr.literal(val), Datatype.fromLiteral(val).parse);
}

const internal: IExprInternal = { param };
function create<Val>(expr: InnerExpr, parse: ExprParser<Val>, param?: IExprInternal_Param): IExpr<Val> {
const internal: IExprInternal<Val> = { parse, param };
return Object.assign(expr, { [PRIV]: internal }) as IExpr<Val>;
}

function parseExprVal<Val>(expr: IExpr<Val>, raw: any, json: boolean): Val {
return expr[PRIV].parse(raw, json);
}
})();
12 changes: 6 additions & 6 deletions src/Populate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IColumnRef, IExpr, IJson } from './Expr';
import { IExpr } from './Expr';
import { ITableQuery } from './TableQuery';
import { TYPES } from './utils/constants';
import { ColumnsRef } from './utils/types';
Expand All @@ -17,7 +17,7 @@ export const Populate = (() => {
_table: RT,
_rightCol: keyof RT[TYPES],
_select: (cols: ColumnsRef<RT[TYPES]>) => IExpr<ResultColumn>
): IColumnRef<IJson<Array<ResultColumn>>> {
): IExpr<Array<ResultColumn>> {
// const subTable = table.groupBy()
throw new Error('Not implemented');
}
Expand All @@ -27,7 +27,7 @@ export const Populate = (() => {
_table: RT,
_rightCol: keyof RT[TYPES],
_select: (cols: ColumnsRef<RT[TYPES]>) => IExpr<ResultColumn>
): IColumnRef<IJson<ResultColumn>> {
): IExpr<ResultColumn> {
throw new Error('Not implemented');
}

Expand All @@ -36,7 +36,7 @@ export const Populate = (() => {
_table: RT,
_rightCol: keyof RT[TYPES],
_select: (cols: ColumnsRef<RT[TYPES]>) => IExpr<ResultColumn>
): IColumnRef<IJson<ResultColumn>> {
): IExpr<ResultColumn> {
throw new Error('Not implemented');
}

Expand All @@ -45,7 +45,7 @@ export const Populate = (() => {
_table: RT,
_rightCol: keyof RT[TYPES],
_select: (cols: ColumnsRef<RT[TYPES]>) => IExpr<ResultColumn>
): IColumnRef<IJson<ResultColumn | null>> {
): IExpr<ResultColumn | null> {
throw new Error('Not implemented');
}

Expand All @@ -54,7 +54,7 @@ export const Populate = (() => {
_table: RT,
_rightCol: keyof RT[TYPES],
_select: (cols: ColumnsRef<RT[TYPES]>) => IExpr<ResultColumn>
): IColumnRef<IJson<ResultColumn | null>> {
): IExpr<ResultColumn | null> {
throw new Error('Not implemented');
}
})();
4 changes: 2 additions & 2 deletions src/Random.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { nanoid } from './utils/utils';
import { createNanoid } from './utils/utils';

export const Random = (() => {
let createId = () => nanoid(12);
let createId = createNanoid('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 10);

return {
// used for testing
Expand Down
6 changes: 3 additions & 3 deletions src/Table.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Ast, builder as b, printNode } from 'zensqlite';
import { ColumnDef, IColumnDefAny } from './ColumnDef';
import { Expr, IColumnRef } from './Expr';
import { Expr, IExpr } from './Expr';
import { ICreateTableOperation, IDeleteOperation, IInsertOperation, IUpdateOperation } from './Operation';
import { ITableQuery, TableQuery } from './TableQuery';
import { PRIV } from './utils/constants';
Expand Down Expand Up @@ -49,8 +49,8 @@ export const Table = (() => {

function getTableInfos<ColumnsDefs extends ColumnsDefsBase>(table: string, columns: ColumnsDefs): TableInfos<ITableResult<ColumnsDefs>> {
const tableIdentifier = b.Expr.identifier(table);
const columnsRefs = mapObject(columns, (key, _colDef): IColumnRef<any> => {
return Expr.column(tableIdentifier, key);
const columnsRefs = mapObject(columns, (key, colDef): IExpr<any> => {
return Expr.column(tableIdentifier, key, colDef[PRIV].datatype.parse);
});
return { table: tableIdentifier, columnsRefs };
}
Expand Down
Loading

0 comments on commit e8b39e2

Please sign in to comment.