Skip to content

Commit

Permalink
Add Operation API
Browse files Browse the repository at this point in the history
  • Loading branch information
etienne-dldc committed Dec 29, 2022
1 parent 0fe1cd1 commit 3ea0d94
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 130 deletions.
29 changes: 29 additions & 0 deletions src/Operation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface IDeleteOperation {
kind: 'Delete';
sql: string;
params: Record<string, any> | null;
}

export interface IUpdateOperation {
kind: 'Update';
sql: string;
params: Record<string, any> | null;
}

export interface IInsertOperation<Inserted> {
kind: 'Insert';
sql: string;
params: Array<any>;
parse: () => Inserted;
}

export interface IQueryOperation<Result> {
kind: 'Query';
sql: string;
params: Record<string, any> | null;
parse: (raw: Array<Record<string, any>>) => Result;
}

export type IOperation = IDeleteOperation | IUpdateOperation | IInsertOperation<any> | IQueryOperation<any>;

export type IOperationKind = IOperation['kind'];
65 changes: 16 additions & 49 deletions src/Table.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { builder as b, printNode } from 'zensqlite';
import { IDeleteOperation, IInsertOperation, IUpdateOperation } from './Operation';
import { Infer, ISchemaAny } from './Schema';
import { ISchemaColumnAny, SchemaColumn } from './SchemaColumn';
import { InferSchemaTableInput, InferSchemaTableResult, ISchemaTableAny } from './SchemaTable';
Expand All @@ -11,34 +12,18 @@ import { PRIV } from './Utils';

export type DeleteOptions = { limit?: number };

export type DeleteResolved = {
query: string;
params: Record<string, any> | null;
};

export type UpdateOptions<SchemaTable extends ISchemaTableAny> = {
limit?: number;
where?: WhereBase<SchemaTable>;
};

export type UpdateResolved = {
query: string;
params: Record<string, any> | null;
};

export interface InsertResolved<SchemaTable extends ISchemaTableAny> {
query: string;
params: Array<any>;
inserted: InferSchemaTableResult<SchemaTable>;
}

export interface ITable<Schema extends ISchemaAny, TableName extends keyof Schema['tables'], SchemaTable extends ISchemaTableAny> {
query(): IQueryBuilder<Schema, TableName, ExtractTable<Schema, TableName>, null, null>;
insert(data: InferSchemaTableInput<SchemaTable>): InsertResolved<SchemaTable>;
delete(condition: WhereBase<SchemaTable>, options?: DeleteOptions): DeleteResolved;
deleteOne(condition: WhereBase<SchemaTable>): DeleteResolved;
update(data: Partial<Infer<SchemaTable>>, options?: UpdateOptions<SchemaTable>): UpdateResolved;
updateOne(data: Partial<Infer<SchemaTable>>, where?: WhereBase<SchemaTable>): UpdateResolved;
insert(data: InferSchemaTableInput<SchemaTable>): IInsertOperation<InferSchemaTableResult<SchemaTable>>;
delete(condition: WhereBase<SchemaTable>, options?: DeleteOptions): IDeleteOperation;
deleteOne(condition: WhereBase<SchemaTable>): IDeleteOperation;
update(data: Partial<Infer<SchemaTable>>, options?: UpdateOptions<SchemaTable>): IUpdateOperation;
updateOne(data: Partial<Infer<SchemaTable>>, where?: WhereBase<SchemaTable>): IUpdateOperation;
}

export const Table = (() => {
Expand Down Expand Up @@ -66,7 +51,7 @@ export const Table = (() => {
return builder<Schema, TableName>(schema, _tableName);
}

function insert(data: InferSchemaTableInput<SchemaTable>): InsertResolved<SchemaTable> {
function insert(data: InferSchemaTableInput<SchemaTable>): IInsertOperation<InferSchemaTableResult<SchemaTable>> {
const resolvedData: Record<string, any> = {};
const parsedData: Record<string, any> = {};
columns.forEach(([name, column]) => {
Expand All @@ -77,28 +62,29 @@ export const Table = (() => {
});
const columnsArgs = columns.map(([name]) => resolvedData[name]);
return {
query: getInsertStatement(),
kind: 'Insert',
sql: getInsertStatement(),
params: columnsArgs,
inserted: parsedData as any,
parse: () => parsedData as any,
};
}

function deleteFn(condition: WhereBase<SchemaTable>, options: DeleteOptions = {}): DeleteResolved {
function deleteFn(condition: WhereBase<SchemaTable>, options: DeleteOptions = {}): IDeleteOperation {
const paramsMap = new Map<any, string>();
const queryNode = b.DeleteStmt(tableName, {
where: createWhere(paramsMap, schemaTable, condition, tableName),
limit: options.limit,
});
const queryText = printNode(queryNode);
const params = paramsFromMap(paramsMap);
return { query: queryText, params };
return { kind: 'Delete', sql: queryText, params };
}

function deleteOne(condition: WhereBase<SchemaTable>): DeleteResolved {
function deleteOne(condition: WhereBase<SchemaTable>): IDeleteOperation {
return deleteFn(condition, { limit: 1 });
}

function update(data: Partial<Infer<SchemaTable>>, { where, limit }: UpdateOptions<SchemaTable> = {}): UpdateResolved {
function update(data: Partial<Infer<SchemaTable>>, { where, limit }: UpdateOptions<SchemaTable> = {}): IUpdateOperation {
const paramsMap = new Map<any, string>();
// const table = this.schemaTable;
const queryNode = b.UpdateStmt(tableName, {
Expand All @@ -108,10 +94,10 @@ export const Table = (() => {
});
const queryText = printNode(queryNode);
const params = paramsFromMap(paramsMap);
return { query: queryText, params };
return { kind: 'Update', sql: queryText, params };
}

function updateOne(data: Partial<Infer<SchemaTable>>, where?: WhereBase<SchemaTable>): UpdateResolved {
function updateOne(data: Partial<Infer<SchemaTable>>, where?: WhereBase<SchemaTable>): IUpdateOperation {
return update(data, { where, limit: 1 });
}

Expand All @@ -128,23 +114,4 @@ export const Table = (() => {
return insertStatement;
}
}

// function insert<
// Schema extends SchemaAny,
// TableName extends keyof Schema['tables'],
// SchemaTable extends SchemaTableAny>(schema: Schema, name: TableName, data: InferSchemaTableInput<SchemaTable>): IsertResolved {

// }

// function getInsertStatement(schema: SchemaAny, name: string): {
// const schemaTable = (schema.tables as any)[name];
// const columns = Object.entries(schemaTable[PRIV].columns);
// const params = this.columns.map(() => b.Expr.BindParameter.Indexed());
// const columns = this.columns.map(([col]) => b.Identifier(col));
// const queryNode = b.InsertStmt(this.name as string, {
// columnNames: columns,
// data: b.InsertStmtData.Values([params]),
// });
// return (printNode(queryNode));
// }
})();
79 changes: 74 additions & 5 deletions src/Table/builder.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Expr } from '../Expr';
import { IQueryOperation } from '../Operation';
import { ISchemaAny } from '../Schema';
import { SchemaColumnOutputValue } from '../SchemaColumn';
import { ISchemaTableAny } from '../SchemaTable';
import { PRIV } from '../Utils';
import { groupRows } from './groupRows';
import { resolve } from './resolve';
import { ExtractTable, QueryResolved } from './types';
import { ExtractTable, Result } from './types';

export type JoinKind = 'many' | 'one' | 'maybeOne' | 'first' | 'maybeFirst';

Expand Down Expand Up @@ -81,7 +83,6 @@ export interface IQueryBuilder<
Selection extends SelectionBase<SchemaTable> | null,
Parent extends null | QueryParentBase<Schema>
> {
// readonly resolved: Resolved | null;
readonly [PRIV]: DatabaseTableQueryInternal<Schema, TableName, SchemaTable, Selection, Parent>;

select<Selection extends SelectionBase<SchemaTable>>(
Expand Down Expand Up @@ -161,7 +162,16 @@ export interface IQueryBuilder<
QueryParent<Schema, 'maybeFirst', TableName, SchemaTable, Selection, Parent>
>;

resolve(): QueryResolved<Schema, TableName, SchemaTable, Selection, Parent>;
// Returns an Array
all(): IQueryOperation<Array<Result<Schema, TableName, Selection, Parent>>>;
// Throw if result count is not === 1
one(): IQueryOperation<Result<Schema, TableName, Selection, Parent>>;
// Throw if result count is > 1
maybeOne(): IQueryOperation<Result<Schema, TableName, Selection, Parent> | null>;
// Throw if result count is === 0
first(): IQueryOperation<Result<Schema, TableName, Selection, Parent>>;
// Never throws
maybeFirst(): IQueryOperation<Result<Schema, TableName, Selection, Parent> | null>;
}

export type IQueryBuilderAny = IQueryBuilder<ISchemaAny, any, any, any, any>;
Expand Down Expand Up @@ -216,8 +226,67 @@ function createBuilder<
joinMaybeFirst(currentCol, table, joinCol) {
return joinInternal('maybeFirst', currentCol, table, joinCol);
},
resolve() {
return resolve(internal);

all() {
const { sql, params, schema, resolvedJoins } = resolve(internal);
return { kind: 'Query', sql, params, parse: (data) => groupRows(schema, resolvedJoins, data) };
},
one() {
const { sql, params, schema, resolvedJoins } = resolve(internal);
return {
kind: 'Query',
sql,
params,
parse: (data) => {
const results = groupRows(schema, resolvedJoins, data);
if (results.length !== 1) {
throw new Error(`Expected 1 result, got ${results.length}`);
}
return results[0];
},
};
},
maybeOne() {
const { sql, params, schema, resolvedJoins } = resolve(internal);
return {
kind: 'Query',
sql,
params,
parse: (data) => {
const results = groupRows(schema, resolvedJoins, data);
if (results.length > 1) {
throw new Error(`Expected maybe 1 result, got ${results.length}`);
}
return results[0] ?? null;
},
};
},
first() {
const { sql, params, schema, resolvedJoins } = resolve(internal);
return {
kind: 'Query',
sql,
params,
parse: (data) => {
const results = groupRows(schema, resolvedJoins, data);
if (results.length === 0) {
throw new Error('Expected at least 1 result, got 0');
}
return results[0];
},
};
},
maybeFirst() {
const { sql, params, schema, resolvedJoins } = resolve(internal);
return {
kind: 'Query',
sql,
params,
parse: (data) => {
const results = groupRows(schema, resolvedJoins, data);
return results[0] ?? null;
},
};
},
};

Expand Down
11 changes: 3 additions & 8 deletions src/Table/groupRows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,11 @@ import { ISchemaAny } from '../Schema';
import { SchemaColumn } from '../SchemaColumn';
import { arrayEqual } from '../Utils';
import { getColumnSchema } from './getColumnSchema';
import { ResolvedJoinItem, ResolvedQuery } from './resolveQuery';
import { ResolvedJoins } from './resolveQuery';
import { transformJoin } from './transformJoin';
import { dotCol } from './utils';

export function groupRows(
schema: ISchemaAny,
query: ResolvedQuery,
joins: Array<ResolvedJoinItem>,
rows: Array<Record<string, unknown>>
): Array<any> {
export function groupRows(schema: ISchemaAny, [query, joins]: ResolvedJoins, rows: Array<Record<string, unknown>>): Array<any> {
const colsKey = query.primaryColumns.map((col) => dotCol(query.tableAlias, col));
const groups: Array<{ keys: Array<any>; rows: Array<Record<string, unknown>> }> = [];
rows.forEach((row) => {
Expand Down Expand Up @@ -44,7 +39,7 @@ export function groupRows(
return result;
}
const joinName = join.query.table;
const joinResult = groupRows(schema, join.query, nextJoins, group.rows);
const joinResult = groupRows(schema, [join.query, nextJoins], group.rows);
const joinContent = transformJoin(joinResult, join.join.kind);
if (query.columns === null) {
return joinContent;
Expand Down
50 changes: 14 additions & 36 deletions src/Table/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,29 @@ import { Node, printNode } from 'zensqlite';
import { ISchemaAny } from '../Schema';
import { ISchemaTableAny } from '../SchemaTable';
import { DatabaseTableQueryInternal, QueryParentBase, SelectionBase } from './builder';
import { groupRows } from './groupRows';
import { resolvedQueryToSelect } from './resolvedQueryToSelect';
import { resolveQuery } from './resolveQuery';
import { QueryResolved } from './types';
import { ResolvedJoins, resolveQuery } from './resolveQuery';
import { paramsFromMap } from './utils';

export interface ResolveResult {
schema: ISchemaAny;
sql: string;
params: Record<string, any> | null;
resolvedJoins: ResolvedJoins;
}

export function resolve<
Schema extends ISchemaAny,
TableName extends keyof Schema['tables'],
SchemaTable extends ISchemaTableAny,
Selection extends SelectionBase<SchemaTable> | null,
Parent extends null | QueryParentBase<Schema>
>(
builder: DatabaseTableQueryInternal<Schema, TableName, SchemaTable, Selection, Parent>
): QueryResolved<Schema, TableName, SchemaTable, Selection, Parent> {
>(builder: DatabaseTableQueryInternal<Schema, TableName, SchemaTable, Selection, Parent>): ResolveResult {
const schema = builder.schema;
// map values to params names
const paramsMap = new Map<any, string>();
const [baseQuery, joins] = resolveQuery(schema, builder, null, 0);
const resolvedJoins = resolveQuery(schema, builder, null, 0);
const [baseQuery, joins] = resolvedJoins;
const tables = builder.schema.tables;
let prevQuery = baseQuery;
let queryNode: Node<'SelectStmt'> = resolvedQueryToSelect(paramsMap, tables[baseQuery.table], baseQuery, null);
Expand All @@ -35,35 +39,9 @@ export function resolve<
const queryText = printNode(queryNode);
const params = paramsFromMap(paramsMap);
return {
query: queryText,
sql: queryText,
params,
parseAll(data) {
return groupRows(schema, baseQuery, joins, data);
},
parseOne(data) {
const results = groupRows(schema, baseQuery, joins, data);
if (results.length !== 1) {
throw new Error(`Expected 1 result, got ${results.length}`);
}
return results[0];
},
parseMaybeOne(data) {
const results = groupRows(schema, baseQuery, joins, data);
if (results.length > 1) {
throw new Error(`Expected maybe 1 result, got ${results.length}`);
}
return results[0] ?? null;
},
parseFirst(data) {
const results = groupRows(schema, baseQuery, joins, data);
if (results.length === 0) {
throw new Error('Expected at least 1 result, got 0');
}
return results[0];
},
parseMaybeFirst(data) {
const results = groupRows(schema, baseQuery, joins, data);
return results[0] ?? null;
},
schema,
resolvedJoins,
};
}
22 changes: 0 additions & 22 deletions src/Table/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,25 +72,3 @@ export type Result<
Selection extends SelectionBase<ExtractTable<Schema, TableName>> | null,
Parent extends null | QueryParentBase<Schema>
> = WrapInParent<Schema, TableName, ResultSelf<Schema, TableName, Selection>, Parent>;

export interface QueryResolved<
Schema extends ISchemaAny,
TableName extends keyof Schema['tables'],
SchemaTable extends ISchemaTableAny,
Selection extends SelectionBase<SchemaTable> | null,
Parent extends null | QueryParentBase<Schema>
> {
readonly query: string;
readonly params: Record<string, any> | null;

// Returns an Array
parseAll(data: Array<Record<string, any>>): Array<Result<Schema, TableName, Selection, Parent>>;
// Throw if result count is not === 1
parseOne(data: Array<Record<string, any>>): Result<Schema, TableName, Selection, Parent>;
// Throw if result count is > 1
parseMaybeOne(data: Array<Record<string, any>>): Result<Schema, TableName, Selection, Parent> | null;
// Throw if result count is === 0
parseFirst(data: Array<Record<string, any>>): Result<Schema, TableName, Selection, Parent>;
// Never throws
parseMaybeFirst(data: Array<Record<string, any>>): Result<Schema, TableName, Selection, Parent> | null;
}
Loading

0 comments on commit 3ea0d94

Please sign in to comment.