@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.
- package/LICENSE +7 -0
- package/README.md +9 -0
- package/package.json +52 -0
- package/src/api/DatabaseTable.ts +51 -0
- package/src/api/DatabaseView.ts +22 -0
- package/src/api/api.types.ts +200 -0
- package/src/api/createDatabase.ts +24 -0
- package/src/api/index.ts +4 -0
- package/src/bun/bun.ts +27 -0
- package/src/bun/index.ts +1 -0
- package/src/cloudflare/cloudflare.ts +18 -0
- package/src/cloudflare/index.ts +1 -0
- package/src/console.ts +14 -0
- package/src/cowboyMigration/Compare.ts +97 -0
- package/src/cowboyMigration/CowboyConnection.ts +57 -0
- package/src/cowboyMigration/CowboyMigrator.ts +437 -0
- package/src/cowboyMigration/CowboySeeder.ts +245 -0
- package/src/cowboyMigration/cowboyMigration.types.ts +29 -0
- package/src/cowboyMigration/createSchemaHacker.ts +28 -0
- package/src/cowboyMigration/index.ts +3 -0
- package/src/crypto.d.ts +9 -0
- package/src/framework/Format.ts +31 -0
- package/src/framework/definitions.d.ts +4 -0
- package/src/framework/index.ts +1 -0
- package/src/index.ts +10 -0
- package/src/queryBuilder/DeleteBuilder.ts +40 -0
- package/src/queryBuilder/InsertBuilder.ts +31 -0
- package/src/queryBuilder/Mappings.ts +55 -0
- package/src/queryBuilder/SelectBuilder.ts +39 -0
- package/src/queryBuilder/SelectQueryBuilder.ts +93 -0
- package/src/queryBuilder/UpdateBuilder.ts +44 -0
- package/src/queryBuilder/addWhereClause.ts +35 -0
- package/src/queryBuilder/index.ts +7 -0
- package/src/queryBuilder/operators.ts +35 -0
- package/src/queryBuilder/queryBuilders.types.ts +105 -0
- package/src/queryGenerator/Clause.ts +79 -0
- package/src/queryGenerator/generateDelete.ts +18 -0
- package/src/queryGenerator/generateInsert.ts +29 -0
- package/src/queryGenerator/generateSelect.ts +89 -0
- package/src/queryGenerator/generateUpdate.ts +29 -0
- package/src/queryGenerator/index.ts +4 -0
- package/src/reflection/Reflector.ts +47 -0
- package/src/reflection/index.ts +2 -0
- package/src/reflection/reflection.types.ts +12 -0
- package/src/schemaBuilder/Mapping.ts +44 -0
- package/src/schemaBuilder/columnBuilders.ts +67 -0
- package/src/schemaBuilder/createFixture.ts +112 -0
- package/src/schemaBuilder/createIndex.ts +33 -0
- package/src/schemaBuilder/createSchema.ts +23 -0
- package/src/schemaBuilder/createTable.ts +80 -0
- package/src/schemaBuilder/createView.ts +21 -0
- package/src/schemaBuilder/index.ts +9 -0
- package/src/schemaBuilder/metadata.ts +73 -0
- package/src/schemaBuilder/schemaBuilder.types.ts +139 -0
- package/src/schemaGenerator/generateCreateIndex.ts +18 -0
- package/src/schemaGenerator/generateCreateTable.ts +68 -0
- package/src/schemaGenerator/generateCreateView.ts +13 -0
- 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
|
+
}
|
package/src/crypto.d.ts
ADDED
|
@@ -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 @@
|
|
|
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,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
|
+
}
|