@sigitex/outlaw 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +9 -0
  3. package/package.json +52 -0
  4. package/src/api/DatabaseTable.ts +51 -0
  5. package/src/api/DatabaseView.ts +22 -0
  6. package/src/api/api.types.ts +200 -0
  7. package/src/api/createDatabase.ts +24 -0
  8. package/src/api/index.ts +4 -0
  9. package/src/bun/bun.ts +27 -0
  10. package/src/bun/index.ts +1 -0
  11. package/src/cloudflare/cloudflare.ts +18 -0
  12. package/src/cloudflare/index.ts +1 -0
  13. package/src/console.ts +14 -0
  14. package/src/cowboyMigration/Compare.ts +97 -0
  15. package/src/cowboyMigration/CowboyConnection.ts +57 -0
  16. package/src/cowboyMigration/CowboyMigrator.ts +437 -0
  17. package/src/cowboyMigration/CowboySeeder.ts +245 -0
  18. package/src/cowboyMigration/cowboyMigration.types.ts +29 -0
  19. package/src/cowboyMigration/createSchemaHacker.ts +28 -0
  20. package/src/cowboyMigration/index.ts +3 -0
  21. package/src/crypto.d.ts +9 -0
  22. package/src/framework/Format.ts +31 -0
  23. package/src/framework/definitions.d.ts +4 -0
  24. package/src/framework/index.ts +1 -0
  25. package/src/index.ts +10 -0
  26. package/src/queryBuilder/DeleteBuilder.ts +40 -0
  27. package/src/queryBuilder/InsertBuilder.ts +31 -0
  28. package/src/queryBuilder/Mappings.ts +55 -0
  29. package/src/queryBuilder/SelectBuilder.ts +39 -0
  30. package/src/queryBuilder/SelectQueryBuilder.ts +93 -0
  31. package/src/queryBuilder/UpdateBuilder.ts +44 -0
  32. package/src/queryBuilder/addWhereClause.ts +35 -0
  33. package/src/queryBuilder/index.ts +7 -0
  34. package/src/queryBuilder/operators.ts +35 -0
  35. package/src/queryBuilder/queryBuilders.types.ts +105 -0
  36. package/src/queryGenerator/Clause.ts +79 -0
  37. package/src/queryGenerator/generateDelete.ts +18 -0
  38. package/src/queryGenerator/generateInsert.ts +29 -0
  39. package/src/queryGenerator/generateSelect.ts +89 -0
  40. package/src/queryGenerator/generateUpdate.ts +29 -0
  41. package/src/queryGenerator/index.ts +4 -0
  42. package/src/reflection/Reflector.ts +47 -0
  43. package/src/reflection/index.ts +2 -0
  44. package/src/reflection/reflection.types.ts +12 -0
  45. package/src/schemaBuilder/Mapping.ts +44 -0
  46. package/src/schemaBuilder/columnBuilders.ts +67 -0
  47. package/src/schemaBuilder/createFixture.ts +112 -0
  48. package/src/schemaBuilder/createIndex.ts +33 -0
  49. package/src/schemaBuilder/createSchema.ts +23 -0
  50. package/src/schemaBuilder/createTable.ts +80 -0
  51. package/src/schemaBuilder/createView.ts +21 -0
  52. package/src/schemaBuilder/index.ts +9 -0
  53. package/src/schemaBuilder/metadata.ts +73 -0
  54. package/src/schemaBuilder/schemaBuilder.types.ts +139 -0
  55. package/src/schemaGenerator/generateCreateIndex.ts +18 -0
  56. package/src/schemaGenerator/generateCreateTable.ts +68 -0
  57. package/src/schemaGenerator/generateCreateView.ts +13 -0
  58. package/src/schemaGenerator/index.ts +3 -0
@@ -0,0 +1,28 @@
1
+ import type { SchemaHack } from "./cowboyMigration.types"
2
+
3
+ export type SchemaHacker = ReturnType<typeof createSchemaHacker>
4
+
5
+ export function createSchemaHacker() {
6
+ const hacks: SchemaHack[] = []
7
+ return {
8
+ get hacks() {
9
+ return hacks
10
+ },
11
+ renamed: {
12
+ table(fromTable: string, toTable: string) {
13
+ hacks.push({ type: "renamedTable", fromTable, toTable })
14
+ },
15
+ column(fromTable: string, fromColumn: string, toColumn: string) {
16
+ hacks.push({ type: "renamedColumn", fromTable, fromColumn, toColumn })
17
+ },
18
+ },
19
+ dropped: {
20
+ table(tableName: string) {
21
+ hacks.push({ type: "droppedTable", tableName })
22
+ },
23
+ column(tableName: string, columnName: string) {
24
+ hacks.push({ type: "droppedColumn", tableName, columnName })
25
+ },
26
+ },
27
+ }
28
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./cowboyMigration.types"
2
+ export * from "./CowboyConnection"
3
+ export * from "./createSchemaHacker"
@@ -0,0 +1,9 @@
1
+ type Hash = {
2
+ update(data: string): Hash
3
+ digest(encoding: "hex"): string
4
+ }
5
+
6
+ declare module "node:crypto" {
7
+ export function createHash(algorithm: string): Hash
8
+ export function randomUUID(): `${string}-${string}-${string}-${string}-${string}`
9
+ }
@@ -0,0 +1,31 @@
1
+ import * as SqlString from "sqlstring-sqlite"
2
+
3
+ export namespace Format {
4
+ export const NOW = "strftime('%Y-%m-%dT%H:%M:%S.%fZ', 'now')"
5
+
6
+ export function text(text: string) {
7
+ return SqlString.escape(text)
8
+ }
9
+
10
+ export function name(name: string | null | undefined) {
11
+ return SqlString.escapeId(name)
12
+ }
13
+
14
+ export function number(number: number) {
15
+ if (typeof number !== "number" && typeof number !== "bigint") {
16
+ throw new Error("Not a number.")
17
+ }
18
+ const s = number.toString()
19
+ return number < 0 ? `(${s})` : s
20
+ }
21
+
22
+ export function value(value: unknown): string {
23
+ if (value === null || value === undefined) {
24
+ return "null"
25
+ }
26
+ if (typeof value === "number" || typeof value === "bigint") {
27
+ return Format.number(Number(value))
28
+ }
29
+ return Format.text(String(value))
30
+ }
31
+ }
@@ -0,0 +1,4 @@
1
+ declare module "sqlstring-sqlite" {
2
+ export function escape(text: string | null | undefined): string
3
+ export function escapeId(text: string | null | undefined): string
4
+ }
@@ -0,0 +1 @@
1
+ export * from "./Format"
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ import "./console"
2
+
3
+ export * from "./api"
4
+ export * from "./cowboyMigration"
5
+ export * from "./framework"
6
+ export * from "./queryBuilder"
7
+ export * from "./queryGenerator"
8
+ export * from "./reflection"
9
+ export * from "./schemaBuilder"
10
+ export * from "./schemaGenerator"
@@ -0,0 +1,40 @@
1
+ // oxlint-disable typescript/no-explicit-any
2
+ import type { BinaryOperator, UnaryOperator, Delete, Connection } from "../api"
3
+ import type { TableData } from "../schemaBuilder"
4
+ import type { DeleteCommand } from "./queryBuilders.types"
5
+ import { generateDelete } from "../queryGenerator"
6
+ import { addWhereClause } from "./addWhereClause"
7
+ import { Mappings } from "./Mappings"
8
+
9
+ /** Constructs declarative `DELETE` statements. */
10
+ export class DeleteBuilder implements Delete<any, any> {
11
+ connection: Connection
12
+ table: TableData
13
+ command: DeleteCommand
14
+
15
+ constructor(connection: Connection, table: TableData) {
16
+ this.connection = connection
17
+ this.table = table
18
+ this.command = { table: table.name }
19
+ }
20
+
21
+ async execute(): Promise<any> {
22
+ const result = await this.connection.query(generateDelete(this.command))
23
+ return this.command.returning ? Mappings.results(this.table, result) : result
24
+ }
25
+
26
+ returning(all: "*"): Delete<any, any[]>
27
+ returning(columns: string[]): Delete<any, Pick<any, any>[]>
28
+ returning(columns: any): Delete<any, any[]> | Delete<any, Pick<any, any>[]> {
29
+ this.command.returning = columns === "*" ? ["*"] : columns
30
+ return this
31
+ }
32
+
33
+ where(column: string, value: any): this
34
+ where(column: string, operator: UnaryOperator): this
35
+ where(column: string, operator: BinaryOperator, value: any): this
36
+ where(column: any, operator: any, value?: any): this {
37
+ addWhereClause(column, this.table, this.command, operator, value)
38
+ return this
39
+ }
40
+ }
@@ -0,0 +1,31 @@
1
+ // oxlint-disable typescript/no-explicit-any
2
+ import type { Connection, Insert } from "../api"
3
+ import type { TableData } from "../schemaBuilder"
4
+ import type { InsertCommand } from "./queryBuilders.types"
5
+ import { generateInsert } from "../queryGenerator"
6
+ import { Mappings } from "./Mappings"
7
+
8
+ /** Constructs declarative `INSERT` statements. */
9
+ export class InsertBuilder implements Insert<any, any, any> {
10
+ readonly connection: Connection
11
+ readonly table: TableData
12
+ readonly command: InsertCommand
13
+
14
+ constructor(connection: Connection, table: TableData, columns: string[], rows: Record<string, unknown>[]) {
15
+ this.connection = connection
16
+ this.table = table
17
+ this.command = { table: table.name, columns, rows: rows.map((r) => Mappings.row(table, r)) }
18
+ }
19
+
20
+ async execute(): Promise<any> {
21
+ const result = await this.connection.query(generateInsert(this.command))
22
+ return this.command.returning ? Mappings.results(this.table, result) : result
23
+ }
24
+
25
+ returning(all: "*"): Insert<any, any, any[]>
26
+ returning(...columns: string[]): Insert<any, any, any[]>
27
+ returning(...columns: ("*" | string)[]) {
28
+ this.command.returning = columns[0] === "*" ? ["*"] : columns
29
+ return this
30
+ }
31
+ }
@@ -0,0 +1,55 @@
1
+ import type { MappingData, TableData } from "../schemaBuilder"
2
+
3
+ export namespace Mappings {
4
+ export function row(
5
+ table: TableData,
6
+ row: Record<string, unknown>,
7
+ ): Record<string, unknown> {
8
+ const mapped: Record<string, unknown> = {}
9
+ for (const key in row) {
10
+ const column = findColumn(table, key)
11
+ mapped[key] = column?.mapping ? column.mapping.to(row[key]) : row[key]
12
+ }
13
+ return mapped
14
+ }
15
+
16
+ export function conditionValue(
17
+ table: TableData,
18
+ column: string,
19
+ value: unknown,
20
+ ): unknown {
21
+ const col = findColumn(table, column)
22
+ return col?.mapping ? col.mapping.to(value) : value
23
+ }
24
+
25
+ export function results(
26
+ tables: TableData | TableData[],
27
+ rows: Record<string, unknown>[],
28
+ ): Record<string, unknown>[] {
29
+ const tableList = Array.isArray(tables) ? tables : [tables]
30
+ const mappings = new Map<string, MappingData>()
31
+ for (const table of tableList) {
32
+ for (const col of table.columns) {
33
+ if (col.mapping && !mappings.has(col.name)) {
34
+ mappings.set(col.name, col.mapping)
35
+ }
36
+ }
37
+ }
38
+ if (mappings.size === 0) {
39
+ return rows
40
+ }
41
+ return rows.map((row) => {
42
+ const mapped: Record<string, unknown> = { ...row }
43
+ for (const [name, mapping] of mappings) {
44
+ if (name in mapped) {
45
+ mapped[name] = mapping.from(mapped[name])
46
+ }
47
+ }
48
+ return mapped
49
+ })
50
+ }
51
+ }
52
+
53
+ function findColumn(table: TableData, name: string) {
54
+ return table.columns.find((c) => c.name === name)
55
+ }
@@ -0,0 +1,39 @@
1
+ // oxlint-disable typescript/no-explicit-any
2
+ import type { Connection, Select } from "../api"
3
+ import type { TableData } from "../schemaBuilder"
4
+ import { generateSelect } from "../queryGenerator"
5
+ import { Mappings } from "./Mappings"
6
+ import { SelectQueryBuilder } from "./SelectQueryBuilder"
7
+
8
+ /** Constructs declarative `SELECT` statements with connection-based execution. */
9
+ export class SelectBuilder extends SelectQueryBuilder implements Select<any, any> {
10
+ readonly connection: Connection
11
+
12
+ constructor(
13
+ connection: Connection,
14
+ table: TableData,
15
+ columns: "*" | string[],
16
+ ) {
17
+ super(table, columns)
18
+ this.connection = connection
19
+ }
20
+
21
+ async fetch(): Promise<any[]> {
22
+ const rows = await this.connection.query(generateSelect(this.query))
23
+ const tables = [
24
+ this.table,
25
+ ...(this.query.joins?.map((j) => j.target.tableData) ?? []),
26
+ ]
27
+ return Mappings.results(tables, rows)
28
+ }
29
+
30
+ async first(): Promise<any> {
31
+ const results = await this.fetch()
32
+ if (results[0] === undefined) {
33
+ throw new Error(
34
+ `Query did not return a result (table: "${this.table.name}").`,
35
+ )
36
+ }
37
+ return results[0]
38
+ }
39
+ }
@@ -0,0 +1,93 @@
1
+ // oxlint-disable typescript/no-explicit-any
2
+ import type { BinaryOperator, Select, UnaryOperator } from "../api"
3
+ import type { BuildTable, ColumnRef, TableData } from "../schemaBuilder"
4
+ import { addWhereClause } from "./addWhereClause"
5
+ import type { JoinTarget, JoinType, SelectQuery } from "./queryBuilders.types"
6
+
7
+ /** Base query builder for constructing declarative `SELECT` statements without a connection. */
8
+ export class SelectQueryBuilder {
9
+ readonly table: TableData
10
+ readonly query: SelectQuery
11
+
12
+ constructor(table: TableData, columns: "*" | string[]) {
13
+ this.table = table
14
+ this.query = { table: table.name }
15
+ if (Array.isArray(columns)) {
16
+ this.query.selected = columns
17
+ }
18
+ }
19
+
20
+ limit(n: number) {
21
+ this.query.limit = n
22
+ return this
23
+ }
24
+
25
+ offset(n: number) {
26
+ this.query.offset = n
27
+ return this
28
+ }
29
+
30
+ where(column: string, value: any): this
31
+ where(column: string, operator: UnaryOperator): this
32
+ where(column: string, operator: BinaryOperator, value: any): this
33
+ where(column: string, operator: any, value?: any) {
34
+ addWhereClause(column, this.table, this.query, operator, value)
35
+ return this
36
+ }
37
+
38
+ orderBy(sorts: [string, "asc" | "desc"][]): this {
39
+ this.query.orderBy = sorts.map(([column, direction]) => ({
40
+ column,
41
+ direction,
42
+ }))
43
+ return this
44
+ }
45
+
46
+ private addJoin(type: JoinType, target: JoinTarget): this {
47
+ if (!this.query.joins) {
48
+ this.query.joins = []
49
+ }
50
+ this.query.joins.push({ type, target, on: [] })
51
+ return this
52
+ }
53
+
54
+ join(target: BuildTable<any> | Select<any, any>): this {
55
+ return this.addJoin("join", resolveTarget(target))
56
+ }
57
+
58
+ leftJoin(target: BuildTable<any> | Select<any, any>): this {
59
+ return this.addJoin("left join", resolveTarget(target))
60
+ }
61
+
62
+ rightJoin(target: BuildTable<any> | Select<any, any>): this {
63
+ return this.addJoin("right join", resolveTarget(target))
64
+ }
65
+
66
+ crossJoin(target: BuildTable<any> | Select<any, any>): this {
67
+ return this.addJoin("cross join", resolveTarget(target))
68
+ }
69
+
70
+ on(left: ColumnRef, operator: BinaryOperator, right: ColumnRef): this {
71
+ const joins = this.query.joins
72
+ if (!joins?.length) {
73
+ throw new Error("on() called without a preceding join")
74
+ }
75
+ joins[joins.length - 1].on.push({ left, operator, right })
76
+ return this
77
+ }
78
+ }
79
+
80
+ export function resolveTarget(
81
+ target: BuildTable<any> | Select<any, any>,
82
+ ): JoinTarget {
83
+ if ("$meta" in target) {
84
+ return { kind: "table", name: target.$meta.name, tableData: target.$meta }
85
+ }
86
+ const builder = target as unknown as SelectQueryBuilder
87
+ return {
88
+ kind: "subquery",
89
+ table: builder.query.table,
90
+ query: { ...builder.query },
91
+ tableData: builder.table,
92
+ }
93
+ }
@@ -0,0 +1,44 @@
1
+ // oxlint-disable typescript/no-explicit-any
2
+ import type { BinaryOperator, Connection, UnaryOperator, Update } from "../api"
3
+ import { generateUpdate } from "../queryGenerator"
4
+ import type { TableData } from "../schemaBuilder"
5
+ import { addWhereClause } from "./addWhereClause"
6
+ import { Mappings } from "./Mappings"
7
+ import type { UpdateCommand } from "./queryBuilders.types"
8
+
9
+ /** Constructs declarative `UPDATE` statements. */
10
+ export class UpdateBuilder implements Update<any, any> {
11
+ readonly connection: Connection
12
+ readonly table: TableData
13
+ readonly command: UpdateCommand
14
+
15
+ constructor(
16
+ connection: Connection,
17
+ table: TableData,
18
+ row: Record<string, unknown>,
19
+ ) {
20
+ this.connection = connection
21
+ this.table = table
22
+ this.command = { table: table.name, assignments: Mappings.row(table, row) }
23
+ }
24
+
25
+ async execute(): Promise<any> {
26
+ const result = await this.connection.query(generateUpdate(this.command))
27
+ return this.command.returning ? Mappings.results(this.table, result) : result
28
+ }
29
+
30
+ returning(all: "*"): Update<any, any[]>
31
+ returning(columns: string[]): Update<any, Pick<any, any>[]>
32
+ returning(columns: any): Update<any, any[]> | Update<any, Pick<any, any>[]> {
33
+ this.command.returning = columns === "*" ? ["*"] : columns
34
+ return this
35
+ }
36
+
37
+ where(column: string, value: any): this
38
+ where(column: string, operator: UnaryOperator): this
39
+ where(column: string, operator: BinaryOperator, value: any): this
40
+ where(column: any, operator: any, value?: any): this {
41
+ addWhereClause(column, this.table, this.command, operator, value)
42
+ return this
43
+ }
44
+ }
@@ -0,0 +1,35 @@
1
+ // oxlint-disable typescript/no-explicit-any
2
+ import type { TableData } from "../schemaBuilder"
3
+ import type { Condition } from "./queryBuilders.types"
4
+ import { Mappings } from "./Mappings"
5
+ import { isBinaryOperator, isUnaryOperator } from "./operators"
6
+
7
+ /** Constructs `WHERE` clauses. */
8
+ export function addWhereClause(
9
+ column: string,
10
+ table: TableData,
11
+ query: { conditions?: Condition[] },
12
+ operator: any,
13
+ value?: any,
14
+ ) {
15
+ if (query.conditions === undefined) {
16
+ query.conditions = []
17
+ }
18
+ if (value !== undefined && isBinaryOperator(operator)) {
19
+ query.conditions.push({
20
+ column,
21
+ arity: 2,
22
+ operator,
23
+ value: Mappings.conditionValue(table, column, value),
24
+ })
25
+ } else if (isUnaryOperator(operator)) {
26
+ query.conditions.push({ column, arity: 1, operator })
27
+ } else {
28
+ query.conditions.push({
29
+ column,
30
+ arity: 2,
31
+ operator: "=",
32
+ value: Mappings.conditionValue(table, column, operator),
33
+ })
34
+ }
35
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./queryBuilders.types"
2
+ export * from "./operators"
3
+ export * from "./SelectQueryBuilder"
4
+ export * from "./SelectBuilder"
5
+ export * from "./InsertBuilder"
6
+ export * from "./UpdateBuilder"
7
+ export * from "./DeleteBuilder"
@@ -0,0 +1,35 @@
1
+ // oxlint-disable typescript/no-explicit-any
2
+
3
+ export const UNARY_OPERATORS = [
4
+ "is null",
5
+ "is not null",
6
+ ] as const
7
+
8
+ export const BINARY_OPERATORS = [
9
+ "=",
10
+ "!=",
11
+ ">",
12
+ "<",
13
+ ">=",
14
+ "<=",
15
+ "like",
16
+ "not like",
17
+ "glob",
18
+ "not glob",
19
+ "match",
20
+ "not match",
21
+ "regexp",
22
+ "not regexp",
23
+ "in",
24
+ "not in",
25
+ "is",
26
+ "is not",
27
+ ] as const
28
+
29
+ export function isUnaryOperator(o: string) {
30
+ return UNARY_OPERATORS.includes(o as any)
31
+ }
32
+
33
+ export function isBinaryOperator(o: string) {
34
+ return BINARY_OPERATORS.includes(o as any)
35
+ }
@@ -0,0 +1,105 @@
1
+ // oxlint-disable typescript/consistent-type-definitions -- review
2
+ import type { BinaryOperator, UnaryOperator } from "../api/api.types"
3
+ import type { ColumnRef, TableData } from "../schemaBuilder"
4
+
5
+ /** Represents a select query. */
6
+ export interface SelectQuery {
7
+ /** Table this query is performed on. */
8
+ table: string
9
+ /** Limit clause. */
10
+ limit?: number
11
+ /** Offset clause. */
12
+ offset?: number
13
+ /** Conditions of the SELECT. */
14
+ conditions?: Condition[]
15
+ /** Which columns are being selected in this query. */
16
+ selected?: string[]
17
+ /** Order By clause */
18
+ orderBy?: OrderBySort[]
19
+ /** Join clauses. */
20
+ joins?: JoinClause[]
21
+ }
22
+
23
+ export type JoinType = "join" | "left join" | "right join" | "cross join"
24
+
25
+ export type JoinTarget =
26
+ | { kind: "table"; name: string; tableData: TableData }
27
+ | {
28
+ kind: "subquery"
29
+ table: string
30
+ query: SelectQuery
31
+ tableData: TableData
32
+ }
33
+
34
+ export interface JoinClause {
35
+ /** The type of join. */
36
+ type: JoinType
37
+ /** The target being joined — a table or a subquery. */
38
+ target: JoinTarget
39
+ /** Join conditions (ON clause). */
40
+ on: JoinOn[]
41
+ }
42
+
43
+ export interface JoinOn {
44
+ /** Left side of the ON condition. */
45
+ left: ColumnRef
46
+ /** The comparison operator. */
47
+ operator: BinaryOperator
48
+ /** Right side of the ON condition. */
49
+ right: ColumnRef
50
+ }
51
+
52
+ /** Represents an update command. */
53
+ export interface UpdateCommand {
54
+ /** Table being UPDATEd. */
55
+ table: string
56
+ /** Column-value assignments. */
57
+ assignments: Record<string, unknown>
58
+ /** Conditions of the UPDATE. */
59
+ conditions?: Condition[]
60
+ /** Returning clause. */
61
+ returning?: string[]
62
+ }
63
+
64
+ /** Represents an insert command. */
65
+ export interface InsertCommand {
66
+ /** Table being INSERTed to. */
67
+ table: string
68
+ /** Column names. */
69
+ columns: string[]
70
+ /** Row values to insert. */
71
+ rows: Record<string, unknown>[]
72
+ /** Returning clause. */
73
+ returning?: string[]
74
+ }
75
+
76
+ /** Represents a delete command. */
77
+ export interface DeleteCommand {
78
+ table: string
79
+ conditions?: Condition[]
80
+ returning?: string[]
81
+ }
82
+
83
+ export interface OrderBySort {
84
+ column: string
85
+ direction: "asc" | "desc"
86
+ }
87
+
88
+ /* Represents a condition. */
89
+ export type Condition = UnaryCondition | BinaryCondition
90
+
91
+ /** A unary condition. */
92
+ export interface UnaryCondition {
93
+ column: string
94
+ arity: 1
95
+ operator: UnaryOperator
96
+ }
97
+
98
+ /** A binary condition. */
99
+ export interface BinaryCondition {
100
+ column: string
101
+ arity: 2
102
+ operator: BinaryOperator
103
+ // oxlint-disable-next-line typescript/no-explicit-any
104
+ value: any
105
+ }
@@ -0,0 +1,79 @@
1
+ import { indent, join, newline, type Node } from "@sigitex/print"
2
+ import { Format } from "../framework"
3
+ import type { Condition, JoinClause, JoinTarget } from "../queryBuilder"
4
+ import type { ColumnRef } from "../schemaBuilder"
5
+ import { generateSelectNode } from "./generateSelect"
6
+
7
+ export namespace Clause {
8
+ export function where(conditions: Condition[], baseTable?: string): Node[] {
9
+ return [
10
+ "where ",
11
+ indent(
12
+ conditions.map((condition, index) => [
13
+ index > 0 && " and ",
14
+ baseTable
15
+ ? qualifyColumn(baseTable, condition.column)
16
+ : Format.name(condition.column),
17
+ " ",
18
+ condition.operator,
19
+ condition.arity === 2 && [" ", Format.value(condition.value)],
20
+ newline,
21
+ ]),
22
+ ),
23
+ ]
24
+ }
25
+
26
+ export function returning(columns: string[]): Node {
27
+ if (columns.length === 1 && columns[0] === "*") {
28
+ return ["returning *", newline]
29
+ }
30
+ return ["returning ", join(", ", columns, Format.name), newline]
31
+ }
32
+
33
+ export function joins(
34
+ clauses: JoinClause[],
35
+ aliasMap: Map<JoinClause, string>,
36
+ ): Node {
37
+ return clauses.map((clause) => {
38
+ const { type, on } = clause
39
+ const alias = aliasMap.get(clause)!
40
+ return [
41
+ type,
42
+ " ",
43
+ formatTarget(clause.target, alias),
44
+ newline,
45
+ on.length > 0 &&
46
+ indent(
47
+ on.map(({ left, operator, right }, index) => [
48
+ index === 0 ? "on " : "and ",
49
+ formatRef(left),
50
+ ` ${operator} `,
51
+ formatRef(right),
52
+ newline,
53
+ ]),
54
+ ),
55
+ ]
56
+ })
57
+ }
58
+ }
59
+
60
+ function formatTarget(target: JoinTarget, alias: string): Node {
61
+ if (target.kind === "table") {
62
+ return Format.name(target.name)
63
+ }
64
+ return [
65
+ "(",
66
+ newline,
67
+ indent([generateSelectNode(target.query)]),
68
+ ") as ",
69
+ Format.name(alias),
70
+ ]
71
+ }
72
+
73
+ function formatRef(ref: ColumnRef): string {
74
+ return `${Format.name(ref.table)}.${Format.name(ref.column)}`
75
+ }
76
+
77
+ function qualifyColumn(baseTable: string, column: string): string {
78
+ return `${Format.name(baseTable)}.${Format.name(column)}`
79
+ }
@@ -0,0 +1,18 @@
1
+ import { newline, print } from "@sigitex/print"
2
+ import { Format } from "../framework"
3
+ import type { DeleteCommand } from "../queryBuilder"
4
+ import { Clause } from "./Clause"
5
+
6
+ export function generateDelete({
7
+ table,
8
+ conditions,
9
+ returning,
10
+ }: DeleteCommand) {
11
+ return print([
12
+ "delete from ",
13
+ Format.name(table),
14
+ newline,
15
+ conditions?.length && Clause.where(conditions),
16
+ returning && Clause.returning(returning),
17
+ ])
18
+ }