Skip to content

Commit

Permalink
Interim
Browse files Browse the repository at this point in the history
  • Loading branch information
benmerckx committed Apr 26, 2024
1 parent 2dd6c35 commit b969d3e
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 36 deletions.
55 changes: 52 additions & 3 deletions src/core/Builder.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import {getData, internalData, type HasSql, type HasTable} from './Internal.ts'
import {
getData,
getQuery,
getSelection,
internalData,
internalQuery,
internalTarget,
type HasQuery,
type HasSql,
type HasTable,
type HasTarget
} from './Internal.ts'
import type {IsPostgres, QueryMeta} from './MetaData.ts'
import type {QueryData} from './Query.ts'
import type {SelectionInput} from './Selection.ts'
import {sql} from './Sql.ts'
import type {Table, TableDefinition} from './Table.ts'
import {Create} from './query/Create.ts'
import {DeleteFrom} from './query/Delete.ts'
import {Drop} from './query/Drop.ts'
import {InsertInto} from './query/Insert.ts'
import type {WithSelection, WithoutSelection} from './query/Select.ts'
import type {
SelectBase,
WithSelection,
WithoutSelection
} from './query/Select.ts'
import {Select} from './query/Select.ts'
import {UpdateTable} from './query/Update.ts'

export class Builder<Meta extends QueryMeta> {
class BuilderBase<Meta extends QueryMeta> {
readonly [internalData]: QueryData<Meta>

constructor(data: QueryData<Meta>) {
Expand Down Expand Up @@ -93,3 +109,36 @@ export class Builder<Meta extends QueryMeta> {
return new DeleteFrom<Meta>({...getData(this), from})
}
}

export type CTE<Input = unknown> = Input & HasTarget & HasQuery

export class Builder<Meta extends QueryMeta> extends BuilderBase<Meta> {
$with(cteName: string) {
return {
as<Input extends SelectionInput>(
query: SelectBase<Input, Meta>
): CTE<Input> {
const fields = getSelection(query).makeVirtual(cteName)
return Object.assign(<any>fields, {
[internalTarget]: sql.identifier(cteName),
[internalQuery]: getQuery(query)
})
}
}
}

with(...cte: Array<CTE>) {
return new BuilderBase<Meta>({
...getData(this),
cte
})
}

create<Definition extends TableDefinition>(table: Table<Definition>) {
return new Create<Meta>({...getData(this), table})
}

drop(table: HasTable) {
return new Drop<Meta>({...getData(this), table})
}
}
37 changes: 33 additions & 4 deletions src/core/Emitter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import type {ColumnData} from './Column.ts'
import type {FieldData} from './Field.ts'
import {getData, getQuery, getSelection, getTable} from './Internal.ts'
import {
getData,
getQuery,
getSelection,
getTable,
getTarget,
type HasQuery,
type HasTarget
} from './Internal.ts'
import {ValueParam, type Param} from './Param.ts'
import {sql} from './Sql.ts'
import type {Create} from './query/Create.ts'
Expand Down Expand Up @@ -81,7 +89,8 @@ export abstract class Emitter {
}

emitDelete(deleteOp: Delete<unknown>): void {
const {from, where, returning} = getData(deleteOp)
const {cte, from, where, returning} = getData(deleteOp)
if (cte) this.emitWith(cte)
const table = getTable(from)
sql
.query({
Expand All @@ -93,7 +102,8 @@ export abstract class Emitter {
}

emitInsert(insert: Insert<unknown>): void {
const {into, values, onConflict, returning} = getData(insert)
const {cte, into, values, onConflict, returning} = getData(insert)
if (cte) this.emitWith(cte)
const table = getTable(into)
const tableName = sql.identifier(table.name)
sql
Expand All @@ -109,6 +119,7 @@ export abstract class Emitter {

emitSelect(select: Select<unknown>): void {
const {
cte,
from,
distinct,
distinctOn,
Expand All @@ -119,6 +130,7 @@ export abstract class Emitter {
limit,
offset
} = getData(select)
if (cte) this.emitWith(cte)
const selected = getSelection(select)
const prefix = distinctOn
? sql`distinct on (${sql.join(distinctOn, sql`, `)})`
Expand All @@ -143,8 +155,9 @@ export abstract class Emitter {
}

emitUpdate(update: Update<unknown>): void {
const {table, set, where, returning} = getData(update)
const {cte, table, set, where, returning} = getData(update)
const tableApi = getTable(table)
if (cte) this.emitWith(cte)
sql
.query({
update: sql.identifier(tableApi.name),
Expand All @@ -157,4 +170,20 @@ export abstract class Emitter {
}

abstract emitIdColumn(): void

emitWith(cte: Array<HasQuery & HasTarget>): void {
sql
.query({
with: sql.join(
cte.map(cte => {
const query = getQuery(cte)
const target = getTarget(cte)
return sql`${target} as (${query})`
}),
sql`, `
)
})
.add(sql` `)
.emit(this)
}
}
4 changes: 2 additions & 2 deletions src/core/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export interface FieldData {
}

export class Field<Value, Table extends string> implements HasSql<Value> {
private declare keep?: [Table];
readonly [internalField]: FieldData;
private declare keep?: [Table]
readonly [internalField]: FieldData
readonly [internalSql]: Sql<Value>
constructor(
targetName: string,
Expand Down
7 changes: 7 additions & 0 deletions src/core/Internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {TableApi, TableDefinition} from './Table.ts'
export const internalData = Symbol()
export const internalSql = Symbol()
export const internalSelection = Symbol()
export const internalTarget = Symbol()
export const internalQuery = Symbol()
export const internalTable = Symbol()
export const internalColumn = Symbol()
Expand All @@ -24,6 +25,9 @@ export declare class HasSql<Value = unknown> {
export declare class HasSelection {
get [internalSelection](): Selection
}
export declare class HasTarget {
get [internalTarget](): Sql
}
export declare class HasQuery {
get [internalQuery](): Sql
}
Expand Down Expand Up @@ -52,6 +56,9 @@ export const getSql = <Value>(obj: HasSql<Value>) => obj[internalSql]
export const hasSelection = (obj: object): obj is HasSelection =>
internalSelection in obj
export const getSelection = (obj: HasSelection) => obj[internalSelection]
export const hasTarget = (obj: object): obj is HasTarget =>
internalTarget in obj
export const getTarget = (obj: HasTarget) => obj[internalTarget]
export const hasQuery = (obj: object): obj is HasQuery => internalQuery in obj
export const getQuery = (obj: HasQuery) => obj[internalQuery]
export const hasTable = (obj: object): obj is HasTable => internalTable in obj
Expand Down
3 changes: 3 additions & 0 deletions src/core/Query.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {
type HasTarget,
getData,
getResolver,
hasResolver,
internalData,
internalQuery,
type HasQuery,
type HasResolver
} from './Internal.ts'
import type {Async, QueryMeta, Sync} from './MetaData.ts'
Expand All @@ -12,6 +14,7 @@ import type {Sql} from './Sql.ts'

export class QueryData<Meta extends QueryMeta> {
resolver?: Resolver<Meta>
cte?: Array<HasQuery & HasTarget>
}

export abstract class Query<Result, Meta extends QueryMeta>
Expand Down
26 changes: 15 additions & 11 deletions src/core/Selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import {sql, type Sql} from './Sql.ts'
import type {Table, TableRow} from './Table.ts'
import type {Expand} from './Types.ts'
import {virtual} from './Virtual.ts'

declare const nullable: unique symbol
export interface SelectionRecord extends Record<string, SelectionInput> {}
Expand All @@ -21,16 +22,15 @@ export type RowOfRecord<Input> = Expand<{
Input[Key]
>
}>
export type SelectionRow<Input> =
Input extends HasSql<infer Value>
? Value
: Input extends IsNullable
? RowOfRecord<Input> | null
: Input extends SelectionRecord
? RowOfRecord<Input>
: Input extends Table<infer Definition>
? TableRow<Definition>
: never
export type SelectionRow<Input> = Input extends HasSql<infer Value>
? Value
: Input extends IsNullable
? RowOfRecord<Input> | null
: Input extends SelectionRecord
? RowOfRecord<Input>
: Input extends Table<infer Definition>
? TableRow<Definition>
: never

export class Selection implements HasSql {
#input: SelectionInput
Expand All @@ -41,6 +41,10 @@ export class Selection implements HasSql {
this.#nullable = nullable
}

makeVirtual(name: string) {
return virtual(name, this.#input)
}

mapRow = (values: Array<unknown>) => {
return this.#mapResult(this.#input, values)
}
Expand Down Expand Up @@ -81,7 +85,7 @@ export class Selection implements HasSql {
): Sql {
const expr = this.#exprOf(input)
if (expr) {
let exprName = expr.alias ?? name
let exprName = name ?? expr.alias
if (exprName) {
// The bun:sqlite driver cannot handle multiple columns by the same name
while (names.has(exprName)) exprName = `${exprName}_`
Expand Down
6 changes: 5 additions & 1 deletion src/core/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import type {Column, JsonColumn, RequiredColumn} from './Column.ts'
import {jsonExpr, type Input, type JsonExpr} from './Expr.ts'
import {Field} from './Field.ts'
import {
type HasTarget,
getColumn,
getTable,
internalTable,
internalTarget,
type HasSql,
type HasTable
} from './Internal.ts'
Expand Down Expand Up @@ -77,7 +79,7 @@ export class TableApi<
export type Table<
Definition extends TableDefinition = TableDefinition,
Name extends string = string
> = TableFields<Definition, Name> & HasTable<Definition, Name>
> = TableFields<Definition, Name> & HasTable<Definition, Name> & HasTarget

export type TableFields<
Definition extends TableDefinition,
Expand Down Expand Up @@ -127,6 +129,7 @@ export function table<Definition extends TableDefinition, Name extends string>(
const api = assign(new TableApi(), {name, columns})
return <Table<Definition, Name>>{
[internalTable]: api,
[internalTarget]: api.from(),
...api.fields()
}
}
Expand All @@ -138,6 +141,7 @@ export function alias<Definition extends TableDefinition, Alias extends string>(
const api = assign(new TableApi(), {...getTable(table), alias})
return <Table<Definition, Alias>>{
[internalTable]: api,
[internalTarget]: api.from(),
...api.fields()
}
}
27 changes: 13 additions & 14 deletions src/core/query/Select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import {
getQuery,
getSelection,
getTable,
hasQuery,
getTarget,
hasTable,
internalData,
internalQuery,
internalSelection,
type HasQuery,
internalTarget,
type HasSelection,
type HasSql,
type HasTable
type HasTable,
type HasTarget
} from '../Internal.ts'
import type {IsMysql, IsPostgres, QueryMeta} from '../MetaData.ts'
import {Query, QueryData} from '../Query.ts'
Expand All @@ -28,7 +29,6 @@ import {
import {sql} from '../Sql.ts'
import type {Table, TableDefinition, TableFields} from '../Table.ts'
import type {Expand} from '../Types.ts'
import {virtual} from '../Virtual.ts'
import {Union} from './Union.ts'

export type SelectionType = 'selection' | 'allFrom' | 'joinTables'
Expand Down Expand Up @@ -63,19 +63,18 @@ export class Select<Input, Meta extends QueryMeta = QueryMeta>
this[internalData] = data
}

as(name: string): SubQuery<Input> {
const {select} = getData(this)
if (!select.input) throw new Error('No selection defined')
return Object.assign(virtual(name, <SelectionInput>select.input) as any, {
[internalQuery]: sql`(${getQuery(this)}) as ${sql.identifier(
name
as(alias: string): SubQuery<Input> {
const fields = getSelection(this).makeVirtual(alias)
return Object.assign(<any>fields, {
[internalTarget]: sql`(${getQuery(this)}) as ${sql.identifier(
alias
)}`.inlineFields(true)
})
}

from(target: HasQuery | Table): Select<Input, Meta> {
from(target: HasTarget): Select<Input, Meta> {
const {select: current} = getData(this)
const from = hasQuery(target) ? getQuery(target) : getTable(target).from()
const from = getTarget(target)
const isTable = hasTable(target)
const selectionInput = current.input ?? (isTable ? target : sql`*`)
return new Select({
Expand Down Expand Up @@ -252,9 +251,9 @@ export class Select<Input, Meta extends QueryMeta = QueryMeta>
}
}

export type SubQuery<Input> = Input & HasQuery
export type SubQuery<Input> = Input & HasTarget

export interface SelectBase<Input, Meta extends QueryMeta>
export interface SelectBase<Input, Meta extends QueryMeta = QueryMeta>
extends Query<SelectionRow<Input>, Meta>,
HasSelection {
where(where: HasSql<boolean>): Select<Input, Meta>
Expand Down
2 changes: 1 addition & 1 deletion test/core/Selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {emit} from '../TestUtils.ts'

Test.describe('Selection', () => {
Test.it('alias', () => {
const aliased = selection({id: sql.value(1).as('name')})
const aliased = selection(sql.value(1).as('name'))
Assert.isEqual(emit(aliased), '1 as "name"')
})
})
Loading

0 comments on commit b969d3e

Please sign in to comment.