@sigitex/outlaw 1.2.0 → 2.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/README.md +170 -5
- package/package.json +1 -1
- package/src/api/DatabaseTable.ts +38 -24
- package/src/api/DatabaseView.ts +36 -14
- package/src/api/api.types.ts +31 -79
- package/src/framework/Format.ts +4 -0
- package/src/framework/definitions.d.ts +4 -1
- package/src/queryBuilder/Mappings.ts +1 -1
- package/src/queryBuilder/Projection.ts +83 -0
- package/src/queryBuilder/QueryScope.ts +272 -0
- package/src/queryBuilder/QuerySource.ts +231 -0
- package/src/queryBuilder/QuerySourceBuilder.ts +76 -0
- package/src/queryBuilder/SelectBuilder.ts +14 -20
- package/src/queryBuilder/SelectQueryBuilder.ts +61 -72
- package/src/queryBuilder/index.ts +2 -0
- package/src/queryBuilder/queryBuilders.types.ts +21 -16
- package/src/queryGenerator/Clause.ts +36 -35
- package/src/queryGenerator/generateSelect.ts +38 -73
- package/src/schemaBuilder/ColumnRef.ts +36 -0
- package/src/schemaBuilder/columnBuilders.ts +7 -2
- package/src/schemaBuilder/createTable.ts +33 -26
- package/src/schemaBuilder/createView.ts +24 -12
- package/src/schemaBuilder/index.ts +1 -0
- package/src/schemaBuilder/metadata.ts +12 -6
- package/src/schemaBuilder/schemaBuilder.types.ts +57 -30
|
@@ -1,93 +1,82 @@
|
|
|
1
|
-
|
|
2
|
-
import type {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
1
|
+
import type { BinaryOperator, UnaryOperator } from "../api/api.types"
|
|
2
|
+
import type { ColumnRef } from "../schemaBuilder/ColumnRef"
|
|
3
|
+
import { QuerySource } from "./QuerySource"
|
|
4
|
+
import type { SelectQuery } from "./queryBuilders.types"
|
|
5
|
+
import { isBinaryOperator, isUnaryOperator } from "./operators"
|
|
6
|
+
import { Projection } from "./Projection"
|
|
6
7
|
|
|
7
|
-
/** Base query builder for constructing declarative `SELECT` statements without a connection. */
|
|
8
8
|
export class SelectQueryBuilder {
|
|
9
|
-
readonly table: TableData
|
|
10
9
|
readonly query: SelectQuery
|
|
11
10
|
|
|
12
|
-
constructor(
|
|
13
|
-
this.
|
|
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
|
|
11
|
+
constructor(query: SelectQuery) {
|
|
12
|
+
this.query = query
|
|
23
13
|
}
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return this
|
|
15
|
+
as(name: string) {
|
|
16
|
+
return QuerySource.create({ ...QuerySource.target(this), alias: name })
|
|
28
17
|
}
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
|
|
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)
|
|
19
|
+
limit(count: number) {
|
|
20
|
+
this.query.limit = count
|
|
35
21
|
return this
|
|
36
22
|
}
|
|
37
23
|
|
|
38
|
-
|
|
39
|
-
this.query.
|
|
40
|
-
column,
|
|
41
|
-
direction,
|
|
42
|
-
}))
|
|
24
|
+
offset(count: number) {
|
|
25
|
+
this.query.offset = count
|
|
43
26
|
return this
|
|
44
27
|
}
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
29
|
+
where(column: string | ColumnRef, value: unknown): this
|
|
30
|
+
where(
|
|
31
|
+
column: string | ColumnRef,
|
|
32
|
+
operator: BinaryOperator | UnaryOperator,
|
|
33
|
+
value?: unknown,
|
|
34
|
+
): this
|
|
35
|
+
where(column: string | ColumnRef, operator: unknown, value?: unknown) {
|
|
36
|
+
const scope = QuerySource.scope(this.query)
|
|
37
|
+
const identifier = QuerySource.identifier(scope, column)
|
|
38
|
+
this.query.conditions ??= []
|
|
39
|
+
if (
|
|
40
|
+
typeof operator === "string" &&
|
|
41
|
+
isUnaryOperator(operator) &&
|
|
42
|
+
arguments.length === 2
|
|
43
|
+
) {
|
|
44
|
+
this.query.conditions.push({
|
|
45
|
+
column: identifier,
|
|
46
|
+
arity: 1,
|
|
47
|
+
operator: operator as UnaryOperator,
|
|
48
|
+
})
|
|
49
|
+
} else {
|
|
50
|
+
const binary =
|
|
51
|
+
arguments.length === 3 &&
|
|
52
|
+
typeof operator === "string" &&
|
|
53
|
+
isBinaryOperator(operator)
|
|
54
|
+
const operand = binary ? value : operator
|
|
55
|
+
const columns = QuerySource.resolve(scope, identifier)
|
|
56
|
+
const mapping = columns.length
|
|
57
|
+
? Projection.merge(columns).mapping
|
|
58
|
+
: undefined
|
|
59
|
+
this.query.conditions.push({
|
|
60
|
+
column: identifier,
|
|
61
|
+
arity: 2,
|
|
62
|
+
operator: binary ? (operator as BinaryOperator) : "=",
|
|
63
|
+
value: mapping && operand !== null ? mapping.to(operand) : operand,
|
|
64
|
+
})
|
|
49
65
|
}
|
|
50
|
-
this.query.joins.push({ type, target, on: [] })
|
|
51
66
|
return this
|
|
52
67
|
}
|
|
53
68
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 })
|
|
69
|
+
orderBy(sorts: readonly (readonly [string | ColumnRef, "asc" | "desc"])[]) {
|
|
70
|
+
const scope = QuerySource.scope(this.query)
|
|
71
|
+
const outputs = Projection.columns(this.query)
|
|
72
|
+
this.query.orderBy = sorts.map(([column, direction]) => ({
|
|
73
|
+
column:
|
|
74
|
+
typeof column === "string" &&
|
|
75
|
+
outputs.some((output) => output.name === column)
|
|
76
|
+
? { column }
|
|
77
|
+
: QuerySource.identifier(scope, column),
|
|
78
|
+
direction,
|
|
79
|
+
}))
|
|
76
80
|
return this
|
|
77
81
|
}
|
|
78
82
|
}
|
|
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
|
-
}
|
|
@@ -1,35 +1,39 @@
|
|
|
1
1
|
// oxlint-disable typescript/consistent-type-definitions -- review
|
|
2
2
|
import type { BinaryOperator, UnaryOperator } from "../api/api.types"
|
|
3
|
-
import type {
|
|
3
|
+
import type { QuerySource } from "./QuerySource"
|
|
4
|
+
import type { Projection } from "./Projection"
|
|
5
|
+
|
|
6
|
+
export type ColumnIdentifier = { table?: string; column: string }
|
|
7
|
+
export type SelectCondition =
|
|
8
|
+
| (Omit<UnaryCondition, "column"> & { column: ColumnIdentifier })
|
|
9
|
+
| (Omit<BinaryCondition, "column"> & { column: ColumnIdentifier })
|
|
4
10
|
|
|
5
11
|
/** Represents a select query. */
|
|
6
12
|
export interface SelectQuery {
|
|
7
13
|
/** Table this query is performed on. */
|
|
8
|
-
|
|
14
|
+
source: QuerySource.Data
|
|
9
15
|
/** Limit clause. */
|
|
10
16
|
limit?: number
|
|
11
17
|
/** Offset clause. */
|
|
12
18
|
offset?: number
|
|
13
19
|
/** Conditions of the SELECT. */
|
|
14
|
-
conditions?:
|
|
20
|
+
conditions?: SelectCondition[]
|
|
15
21
|
/** Which columns are being selected in this query. */
|
|
16
|
-
selected
|
|
22
|
+
selected: Projection[]
|
|
17
23
|
/** Order By clause */
|
|
18
|
-
orderBy?:
|
|
24
|
+
orderBy?: { column: ColumnIdentifier; direction: "asc" | "desc" }[]
|
|
19
25
|
/** Join clauses. */
|
|
20
26
|
joins?: JoinClause[]
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
export type JoinType =
|
|
29
|
+
export type JoinType =
|
|
30
|
+
| "join"
|
|
31
|
+
| "left join"
|
|
32
|
+
| "right join"
|
|
33
|
+
| "full join"
|
|
34
|
+
| "cross join"
|
|
24
35
|
|
|
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
|
-
}
|
|
36
|
+
export type JoinTarget = QuerySource.Data
|
|
33
37
|
|
|
34
38
|
export interface JoinClause {
|
|
35
39
|
/** The type of join. */
|
|
@@ -38,15 +42,16 @@ export interface JoinClause {
|
|
|
38
42
|
target: JoinTarget
|
|
39
43
|
/** Join conditions (ON clause). */
|
|
40
44
|
on: JoinOn[]
|
|
45
|
+
using?: string[]
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
export interface JoinOn {
|
|
44
49
|
/** Left side of the ON condition. */
|
|
45
|
-
left:
|
|
50
|
+
left: ColumnIdentifier
|
|
46
51
|
/** The comparison operator. */
|
|
47
52
|
operator: BinaryOperator
|
|
48
53
|
/** Right side of the ON condition. */
|
|
49
|
-
right:
|
|
54
|
+
right: ColumnIdentifier
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
/** Represents an update command. */
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { indent, join, newline, type Node } from "@sigitex/print"
|
|
2
2
|
import { Format } from "../framework"
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
Condition,
|
|
5
|
+
JoinClause,
|
|
6
|
+
JoinTarget,
|
|
7
|
+
ColumnIdentifier,
|
|
8
|
+
SelectCondition,
|
|
9
|
+
} from "../queryBuilder"
|
|
5
10
|
import { generateSelectNode } from "./generateSelect"
|
|
6
11
|
|
|
7
12
|
export namespace Clause {
|
|
8
|
-
export function where(conditions: Condition
|
|
13
|
+
export function where(conditions: (Condition | SelectCondition)[]): Node[] {
|
|
9
14
|
return [
|
|
10
15
|
"where ",
|
|
11
16
|
indent(
|
|
12
17
|
conditions.map((condition, index) => [
|
|
13
18
|
index > 0 && " and ",
|
|
14
|
-
|
|
15
|
-
?
|
|
16
|
-
:
|
|
19
|
+
typeof condition.column === "string"
|
|
20
|
+
? Format.name(condition.column)
|
|
21
|
+
: identifier(condition.column),
|
|
17
22
|
" ",
|
|
18
23
|
condition.operator,
|
|
19
24
|
condition.arity === 2 && [" ", Format.value(condition.value)],
|
|
@@ -30,50 +35,46 @@ export namespace Clause {
|
|
|
30
35
|
return ["returning ", join(", ", columns, Format.name), newline]
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
export function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
export function identifier(column: ColumnIdentifier): string {
|
|
39
|
+
return column.table === undefined
|
|
40
|
+
? Format.identifier(column.column)
|
|
41
|
+
: `${Format.identifier(column.table)}.${Format.identifier(column.column)}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function source(target: JoinTarget): Node {
|
|
45
|
+
return [
|
|
46
|
+
target.kind === "table"
|
|
47
|
+
? Format.identifier(target.name)
|
|
48
|
+
: ["(", newline, indent([generateSelectNode(target.query)]), ")"],
|
|
49
|
+
target.alias !== undefined && [" as ", Format.identifier(target.alias)],
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function joins(clauses: JoinClause[]): Node {
|
|
37
54
|
return clauses.map((clause) => {
|
|
38
55
|
const { type, on } = clause
|
|
39
|
-
const alias = aliasMap.get(clause)!
|
|
40
56
|
return [
|
|
41
57
|
type,
|
|
42
58
|
" ",
|
|
43
|
-
|
|
59
|
+
source(clause.target),
|
|
44
60
|
newline,
|
|
45
61
|
on.length > 0 &&
|
|
46
62
|
indent(
|
|
47
63
|
on.map(({ left, operator, right }, index) => [
|
|
48
64
|
index === 0 ? "on " : "and ",
|
|
49
|
-
|
|
65
|
+
identifier(left),
|
|
50
66
|
` ${operator} `,
|
|
51
|
-
|
|
67
|
+
identifier(right),
|
|
52
68
|
newline,
|
|
53
69
|
]),
|
|
54
70
|
),
|
|
71
|
+
clause.using?.length && [
|
|
72
|
+
"using (",
|
|
73
|
+
join(", ", clause.using, Format.identifier),
|
|
74
|
+
")",
|
|
75
|
+
newline,
|
|
76
|
+
],
|
|
55
77
|
]
|
|
56
78
|
})
|
|
57
79
|
}
|
|
58
80
|
}
|
|
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
|
-
}
|
|
@@ -1,89 +1,54 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join, newline, type Node, print } from "@sigitex/print"
|
|
2
2
|
import { Format } from "../framework"
|
|
3
|
-
import type {
|
|
3
|
+
import type { SelectQuery } from "../queryBuilder/queryBuilders.types"
|
|
4
4
|
import { Clause } from "./Clause"
|
|
5
5
|
|
|
6
6
|
export function generateSelect(query: SelectQuery) {
|
|
7
7
|
return print([generateSelectNode(query)])
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function generateSelectNode({
|
|
11
|
-
selected,
|
|
12
|
-
table,
|
|
13
|
-
conditions,
|
|
14
|
-
limit,
|
|
15
|
-
offset,
|
|
16
|
-
orderBy,
|
|
17
|
-
joins,
|
|
18
|
-
}: SelectQuery): Node {
|
|
19
|
-
const hasJoins = !!joins?.length
|
|
20
|
-
const aliasMap = hasJoins ? buildAliasMap(table, joins!) : undefined
|
|
10
|
+
export function generateSelectNode(query: SelectQuery): Node {
|
|
21
11
|
return [
|
|
22
12
|
"select ",
|
|
23
|
-
selected
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
join(", ", query.selected, (projection) => {
|
|
14
|
+
if (projection === "*") {
|
|
15
|
+
return "*"
|
|
16
|
+
}
|
|
17
|
+
if ("wildcard" in projection) {
|
|
18
|
+
return [Format.identifier(projection.table), ".*"]
|
|
19
|
+
}
|
|
20
|
+
return [
|
|
21
|
+
Clause.identifier(projection.column),
|
|
22
|
+
projection.alias !== undefined && [
|
|
23
|
+
" as ",
|
|
24
|
+
Format.identifier(projection.alias),
|
|
25
|
+
],
|
|
26
|
+
]
|
|
27
|
+
}),
|
|
29
28
|
newline,
|
|
30
29
|
"from ",
|
|
31
|
-
|
|
30
|
+
Clause.source(query.source),
|
|
32
31
|
newline,
|
|
33
|
-
|
|
34
|
-
conditions?.length &&
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function buildAliasMap(
|
|
43
|
-
baseTable: string,
|
|
44
|
-
joins: JoinClause[],
|
|
45
|
-
): Map<JoinClause, string> {
|
|
46
|
-
const map = new Map<JoinClause, string>()
|
|
47
|
-
const used = new Set([baseTable])
|
|
48
|
-
|
|
49
|
-
for (const clause of joins) {
|
|
50
|
-
const sourceName =
|
|
51
|
-
clause.target.kind === "table" ? clause.target.name : clause.target.table
|
|
52
|
-
let alias = sourceName
|
|
53
|
-
let i = 1
|
|
54
|
-
while (used.has(alias)) {
|
|
55
|
-
alias = `${sourceName}_${i++}`
|
|
56
|
-
}
|
|
57
|
-
used.add(alias)
|
|
58
|
-
map.set(clause, alias)
|
|
59
|
-
}
|
|
60
|
-
return map
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function qualifyColumn(baseTable: string, column: string): string {
|
|
64
|
-
return `${Format.name(baseTable)}.${Format.name(column)}`
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function allColumnsQualified(
|
|
68
|
-
table: string,
|
|
69
|
-
joins: JoinClause[],
|
|
70
|
-
aliasMap: Map<JoinClause, string>,
|
|
71
|
-
): Node {
|
|
72
|
-
const names = [table, ...joins.map((j) => aliasMap.get(j)!)]
|
|
73
|
-
return names.map((t, i) => [i > 0 && ", ", Format.name(t), ".*"])
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function orderByClause(orderBy: OrderBySort[]): Node {
|
|
77
|
-
return [
|
|
78
|
-
"order by ",
|
|
79
|
-
indent(
|
|
80
|
-
orderBy.map(({ column, direction }, index) => [
|
|
81
|
-
index > 0 && ", ",
|
|
82
|
-
Format.name(column),
|
|
32
|
+
query.joins?.length && Clause.joins(query.joins),
|
|
33
|
+
query.conditions?.length && Clause.where(query.conditions),
|
|
34
|
+
query.orderBy?.length && [
|
|
35
|
+
"order by ",
|
|
36
|
+
join(", ", query.orderBy, (sort) => [
|
|
37
|
+
Clause.identifier(sort.column),
|
|
83
38
|
" ",
|
|
84
|
-
direction,
|
|
85
|
-
newline,
|
|
39
|
+
sort.direction,
|
|
86
40
|
]),
|
|
87
|
-
|
|
41
|
+
newline,
|
|
42
|
+
],
|
|
43
|
+
query.limit !== undefined && [
|
|
44
|
+
"limit ",
|
|
45
|
+
Format.number(query.limit),
|
|
46
|
+
newline,
|
|
47
|
+
],
|
|
48
|
+
query.offset !== undefined && [
|
|
49
|
+
"offset ",
|
|
50
|
+
Format.number(query.offset),
|
|
51
|
+
newline,
|
|
52
|
+
],
|
|
88
53
|
]
|
|
89
54
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type ColumnRef<
|
|
2
|
+
Table extends string = string,
|
|
3
|
+
Column extends string = string,
|
|
4
|
+
> = {
|
|
5
|
+
readonly table: Table
|
|
6
|
+
readonly column: Column
|
|
7
|
+
as<Alias extends string>(
|
|
8
|
+
alias: Alias,
|
|
9
|
+
): ColumnRef.Aliased<Table, Column, Alias>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export namespace ColumnRef {
|
|
13
|
+
export type Aliased<
|
|
14
|
+
Table extends string,
|
|
15
|
+
Column extends string,
|
|
16
|
+
Alias extends string,
|
|
17
|
+
> = ColumnRef<Table, Column> & { readonly alias: Alias }
|
|
18
|
+
|
|
19
|
+
export type Wildcard<Table extends string = string> = {
|
|
20
|
+
readonly table: Table
|
|
21
|
+
readonly wildcard: true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function create<Table extends string, Column extends string>(
|
|
25
|
+
table: Table,
|
|
26
|
+
column: Column,
|
|
27
|
+
): ColumnRef<Table, Column> {
|
|
28
|
+
return {
|
|
29
|
+
table,
|
|
30
|
+
column,
|
|
31
|
+
as(alias) {
|
|
32
|
+
return { ...create(table, column), alias }
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// oxlint-disable typescript/no-explicit-any
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
CheckExpression,
|
|
4
|
+
ColumnData,
|
|
5
|
+
ColumnRef,
|
|
6
|
+
MappingData,
|
|
7
|
+
} from "./metadata"
|
|
3
8
|
import { Mapping } from "./Mapping"
|
|
4
9
|
import type {
|
|
5
10
|
BuildColumn,
|
|
@@ -49,7 +54,7 @@ function composeColumn(
|
|
|
49
54
|
references(ref: ColumnRef) {
|
|
50
55
|
return composeColumn({
|
|
51
56
|
...$meta,
|
|
52
|
-
foreignKey: ref,
|
|
57
|
+
foreignKey: { table: ref.table, column: ref.column },
|
|
53
58
|
})
|
|
54
59
|
},
|
|
55
60
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { ColumnData,
|
|
1
|
+
import type { ColumnData, TableData } from "./metadata"
|
|
2
2
|
import type {
|
|
3
3
|
BuildColumns,
|
|
4
4
|
BuildColumnInner,
|
|
5
5
|
BuildTable,
|
|
6
6
|
} from "./schemaBuilder.types"
|
|
7
|
-
import {
|
|
7
|
+
import { QuerySource } from "../queryBuilder/QuerySource"
|
|
8
8
|
|
|
9
|
-
export function createTable<Columns extends BuildColumns>(
|
|
10
|
-
name:
|
|
9
|
+
export function createTable<Columns extends BuildColumns, Name extends string>(
|
|
10
|
+
name: Name,
|
|
11
11
|
defineColumns: Columns,
|
|
12
|
-
): BuildTable<Columns> {
|
|
12
|
+
): BuildTable<Columns, Name> {
|
|
13
13
|
const columns = Object.entries(defineColumns).map<ColumnData>(
|
|
14
14
|
([name, define]) => {
|
|
15
15
|
const meta = (define as BuildColumnInner).$meta
|
|
@@ -31,27 +31,30 @@ export function createTable<Columns extends BuildColumns>(
|
|
|
31
31
|
columns,
|
|
32
32
|
constraints: [],
|
|
33
33
|
}
|
|
34
|
-
const by = new Proxy(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
const by = new Proxy(
|
|
35
|
+
{},
|
|
36
|
+
{
|
|
37
|
+
get(_, col: string) {
|
|
38
|
+
return (value: unknown) => ({
|
|
39
|
+
_tag: "RefBy" as const,
|
|
40
|
+
table: defineTable,
|
|
41
|
+
column: col,
|
|
42
|
+
value,
|
|
43
|
+
})
|
|
44
|
+
},
|
|
42
45
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
)
|
|
47
|
+
const defineTable = Object.assign(
|
|
48
|
+
QuerySource.create({ kind: "table", name, tableData: $meta }),
|
|
49
|
+
{
|
|
50
|
+
$kind: "table" as const,
|
|
51
|
+
$meta,
|
|
52
|
+
by,
|
|
53
|
+
primaryKey,
|
|
54
|
+
unique,
|
|
55
|
+
check,
|
|
56
|
+
},
|
|
57
|
+
) as unknown as BuildTable<Columns, Name>
|
|
55
58
|
return defineTable
|
|
56
59
|
|
|
57
60
|
function primaryKey(...columns: (keyof Columns)[]) {
|
|
@@ -70,7 +73,11 @@ export function createTable<Columns extends BuildColumns>(
|
|
|
70
73
|
return defineTable
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
function check(
|
|
76
|
+
function check(
|
|
77
|
+
expression:
|
|
78
|
+
| string
|
|
79
|
+
| ((columns: Record<string, string>, table: TableData) => string),
|
|
80
|
+
) {
|
|
74
81
|
$meta.constraints.push({
|
|
75
82
|
type: "check",
|
|
76
83
|
expression,
|
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
// oxlint-disable typescript/no-explicit-any
|
|
2
1
|
import { print } from "@sigitex/print"
|
|
3
2
|
import { generateSelectNode } from "../queryGenerator/generateSelect"
|
|
4
3
|
import type { SelectQueryBuilder } from "../queryBuilder/SelectQueryBuilder"
|
|
4
|
+
import { Projection } from "../queryBuilder/Projection"
|
|
5
|
+
import type { QueryScope } from "../queryBuilder/QueryScope"
|
|
6
|
+
import { QuerySource } from "../queryBuilder/QuerySource"
|
|
5
7
|
import type { TableData, ViewData } from "./metadata"
|
|
6
|
-
import type { BuildView
|
|
8
|
+
import type { BuildView } from "./schemaBuilder.types"
|
|
7
9
|
|
|
8
|
-
export function createView<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
export function createView<
|
|
11
|
+
Name extends string,
|
|
12
|
+
Columns extends QueryScope.Columns,
|
|
13
|
+
>(
|
|
14
|
+
name: Name,
|
|
15
|
+
queryBuilder: { readonly $type: QueryScope.State<"", Columns> },
|
|
16
|
+
): BuildView<Columns, Name> {
|
|
12
17
|
const builder = queryBuilder as unknown as SelectQueryBuilder
|
|
13
18
|
const sql = print([generateSelectNode(builder.query)])
|
|
14
19
|
const $meta: ViewData = { kind: "view", name, sql }
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return
|
|
20
|
+
const $tableData: TableData = {
|
|
21
|
+
name,
|
|
22
|
+
columns: Projection.columns(builder.query),
|
|
23
|
+
constraints: [],
|
|
24
|
+
}
|
|
25
|
+
return Object.assign(
|
|
26
|
+
QuerySource.create({ kind: "table", name, tableData: $tableData }),
|
|
27
|
+
{
|
|
28
|
+
$kind: "view" as const,
|
|
29
|
+
$meta,
|
|
30
|
+
$tableData,
|
|
31
|
+
},
|
|
32
|
+
) as unknown as BuildView<Columns, Name>
|
|
21
33
|
}
|