effect-qb 0.13.0 → 0.14.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 +6 -1431
- package/dist/mysql.js +1678 -355
- package/dist/postgres/metadata.js +2724 -0
- package/dist/postgres.js +7197 -5433
- package/package.json +8 -10
- package/src/internal/column-state.ts +84 -10
- package/src/internal/column.ts +556 -34
- package/src/internal/datatypes/define.ts +0 -30
- package/src/internal/executor.ts +45 -11
- package/src/internal/expression-ast.ts +4 -0
- package/src/internal/expression.ts +1 -1
- package/src/internal/implication-runtime.ts +171 -0
- package/src/internal/mysql-query.ts +7173 -0
- package/src/internal/mysql-renderer.ts +2 -2
- package/src/internal/plan.ts +14 -4
- package/src/internal/{query-factory.ts → postgres-query.ts} +619 -167
- package/src/internal/postgres-renderer.ts +2 -2
- package/src/internal/postgres-schema-model.ts +144 -0
- package/src/internal/predicate-analysis.ts +10 -0
- package/src/internal/predicate-context.ts +112 -36
- package/src/internal/predicate-formula.ts +31 -19
- package/src/internal/predicate-normalize.ts +177 -106
- package/src/internal/predicate-runtime.ts +676 -0
- package/src/internal/query.ts +455 -39
- package/src/internal/renderer.ts +2 -2
- package/src/internal/runtime-schema.ts +74 -20
- package/src/internal/schema-ddl.ts +55 -0
- package/src/internal/schema-derivation.ts +93 -21
- package/src/internal/schema-expression.ts +44 -0
- package/src/internal/sql-expression-renderer.ts +95 -31
- package/src/internal/table-options.ts +87 -7
- package/src/internal/table.ts +104 -41
- package/src/mysql/column.ts +1 -0
- package/src/mysql/datatypes/index.ts +17 -2
- package/src/mysql/function/core.ts +1 -0
- package/src/mysql/function/index.ts +1 -0
- package/src/mysql/private/query.ts +1 -13
- package/src/mysql/query.ts +5 -0
- package/src/postgres/cast.ts +31 -0
- package/src/postgres/column.ts +26 -0
- package/src/postgres/datatypes/index.ts +40 -5
- package/src/postgres/function/core.ts +12 -0
- package/src/postgres/function/index.ts +2 -1
- package/src/postgres/function/json.ts +499 -2
- package/src/postgres/metadata.ts +31 -0
- package/src/postgres/private/query.ts +1 -13
- package/src/postgres/query.ts +5 -2
- package/src/postgres/schema-expression.ts +16 -0
- package/src/postgres/schema-management.ts +204 -0
- package/src/postgres/schema.ts +35 -0
- package/src/postgres/table.ts +307 -41
- package/src/postgres/type.ts +4 -0
- package/src/postgres.ts +14 -0
- package/CHANGELOG.md +0 -134
|
@@ -12,33 +12,3 @@ export type DatatypeModule<
|
|
|
12
12
|
} & {
|
|
13
13
|
readonly [Alias in keyof Aliases]: () => Expression.DbType.Base<Dialect, Aliases[Alias] & string>
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
export const makeDatatypeModule = <
|
|
17
|
-
Dialect extends string,
|
|
18
|
-
Kinds extends Record<string, DatatypeKindSpec>,
|
|
19
|
-
Aliases extends Record<string, string> = Record<never, never>
|
|
20
|
-
>(
|
|
21
|
-
dialect: Dialect,
|
|
22
|
-
kinds: Kinds,
|
|
23
|
-
aliases?: Aliases
|
|
24
|
-
): DatatypeModule<Dialect, Kinds, Aliases> => {
|
|
25
|
-
const module: Record<string, (...args: readonly any[]) => Expression.DbType.Base<Dialect, string>> = {
|
|
26
|
-
custom: (kind: string) => ({
|
|
27
|
-
dialect,
|
|
28
|
-
kind
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
for (const kind of Object.keys(kinds)) {
|
|
32
|
-
module[kind] = () => ({
|
|
33
|
-
dialect,
|
|
34
|
-
kind
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
for (const [alias, kind] of Object.entries(aliases ?? {})) {
|
|
38
|
-
module[alias] = () => ({
|
|
39
|
-
dialect,
|
|
40
|
-
kind
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
return module as DatatypeModule<Dialect, Kinds, Aliases>
|
|
44
|
-
}
|
package/src/internal/executor.ts
CHANGED
|
@@ -4,6 +4,8 @@ import * as SqlClient from "@effect/sql/SqlClient"
|
|
|
4
4
|
import * as SqlError from "@effect/sql/SqlError"
|
|
5
5
|
|
|
6
6
|
import * as Expression from "./expression.js"
|
|
7
|
+
import * as ExpressionAst from "./expression-ast.js"
|
|
8
|
+
import { resolveImplicationScope, type ImplicationScope } from "./implication-runtime.js"
|
|
7
9
|
import { normalizeDbValue } from "./runtime-normalize.js"
|
|
8
10
|
import { expressionRuntimeSchema } from "./runtime-schema.js"
|
|
9
11
|
import { flattenSelection } from "./projections.js"
|
|
@@ -16,6 +18,10 @@ import * as Plan from "./plan.js"
|
|
|
16
18
|
export type FlatRow = Readonly<Record<string, unknown>>
|
|
17
19
|
export type DriverMode = "raw" | "normalized"
|
|
18
20
|
|
|
21
|
+
type AstBackedExpression = Expression.Any & {
|
|
22
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
export interface RowDecodeError {
|
|
20
26
|
readonly _tag: "RowDecodeError"
|
|
21
27
|
readonly message: string
|
|
@@ -67,7 +73,7 @@ export interface Executor<
|
|
|
67
73
|
Context = never
|
|
68
74
|
> {
|
|
69
75
|
readonly dialect: Dialect
|
|
70
|
-
execute<PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any>>(
|
|
76
|
+
execute<PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
71
77
|
plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
|
|
72
78
|
): Effect.Effect<Query.ResultRows<PlanValue>, Error, Context>
|
|
73
79
|
}
|
|
@@ -189,24 +195,39 @@ const makeRowDecodeError = (
|
|
|
189
195
|
|
|
190
196
|
const hasOptionalSourceDependency = (
|
|
191
197
|
expression: Expression.Any,
|
|
192
|
-
|
|
198
|
+
scope: ImplicationScope
|
|
193
199
|
): boolean => {
|
|
194
200
|
const state = expression[Expression.TypeId]
|
|
195
201
|
if (state.sourceNullability === "resolved") {
|
|
196
202
|
return false
|
|
197
203
|
}
|
|
198
|
-
return Object.keys(state.dependencies).some((sourceName) =>
|
|
204
|
+
return Object.keys(state.dependencies).some((sourceName) =>
|
|
205
|
+
!scope.absentSourceNames.has(sourceName) && scope.sourceModes.get(sourceName) === "optional")
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
const effectiveRuntimeNullability = (
|
|
202
209
|
expression: Expression.Any,
|
|
203
|
-
|
|
210
|
+
scope: ImplicationScope
|
|
204
211
|
): Expression.Nullability => {
|
|
205
212
|
const nullability = expression[Expression.TypeId].nullability
|
|
213
|
+
const ast = (expression as AstBackedExpression)[ExpressionAst.TypeId]
|
|
206
214
|
if (nullability === "always") {
|
|
207
215
|
return "always"
|
|
208
216
|
}
|
|
209
|
-
|
|
217
|
+
if (ast.kind === "column") {
|
|
218
|
+
const key = `${ast.tableName}.${ast.columnName}`
|
|
219
|
+
if (scope.absentSourceNames.has(ast.tableName) || scope.nullKeys.has(key)) {
|
|
220
|
+
return "always"
|
|
221
|
+
}
|
|
222
|
+
if (scope.nonNullKeys.has(key)) {
|
|
223
|
+
return "never"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (expression[Expression.TypeId].sourceNullability !== "resolved" &&
|
|
227
|
+
Object.keys(expression[Expression.TypeId].dependencies).some((sourceName) => scope.absentSourceNames.has(sourceName))) {
|
|
228
|
+
return "always"
|
|
229
|
+
}
|
|
230
|
+
return hasOptionalSourceDependency(expression, scope)
|
|
210
231
|
? "maybe"
|
|
211
232
|
: nullability
|
|
212
233
|
}
|
|
@@ -216,7 +237,7 @@ const decodeProjectionValue = (
|
|
|
216
237
|
projection: Renderer.RenderedQuery<any, any>["projections"][number],
|
|
217
238
|
expression: Expression.Any,
|
|
218
239
|
raw: unknown,
|
|
219
|
-
|
|
240
|
+
scope: ImplicationScope,
|
|
220
241
|
driverMode: DriverMode
|
|
221
242
|
): unknown => {
|
|
222
243
|
let normalized = raw
|
|
@@ -228,8 +249,9 @@ const decodeProjectionValue = (
|
|
|
228
249
|
}
|
|
229
250
|
}
|
|
230
251
|
|
|
252
|
+
const nullability = effectiveRuntimeNullability(expression, scope)
|
|
231
253
|
if (normalized === null) {
|
|
232
|
-
if (
|
|
254
|
+
if (nullability === "never") {
|
|
233
255
|
throw makeRowDecodeError(
|
|
234
256
|
rendered,
|
|
235
257
|
projection,
|
|
@@ -243,7 +265,19 @@ const decodeProjectionValue = (
|
|
|
243
265
|
return null
|
|
244
266
|
}
|
|
245
267
|
|
|
246
|
-
|
|
268
|
+
if (nullability === "always") {
|
|
269
|
+
throw makeRowDecodeError(
|
|
270
|
+
rendered,
|
|
271
|
+
projection,
|
|
272
|
+
expression,
|
|
273
|
+
raw,
|
|
274
|
+
"schema",
|
|
275
|
+
new Error("Received non-null for an always-null projection"),
|
|
276
|
+
normalized
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const schema = expressionRuntimeSchema(expression, { assumptions: scope.assumptions })
|
|
247
281
|
if (schema === undefined) {
|
|
248
282
|
return normalized
|
|
249
283
|
}
|
|
@@ -274,7 +308,7 @@ export const decodeRows = (
|
|
|
274
308
|
projections.map((projection) => [projection.alias, projection.expression] as const)
|
|
275
309
|
)
|
|
276
310
|
const driverMode = options.driverMode ?? "raw"
|
|
277
|
-
const
|
|
311
|
+
const scope = resolveImplicationScope(plan[Plan.TypeId].available, Query.getQueryState(plan).assumptions)
|
|
278
312
|
return rows.map((row) => {
|
|
279
313
|
const decoded: Record<string, unknown> = {}
|
|
280
314
|
for (const projection of rendered.projections) {
|
|
@@ -288,7 +322,7 @@ export const decodeRows = (
|
|
|
288
322
|
setPath(
|
|
289
323
|
decoded,
|
|
290
324
|
projection.path,
|
|
291
|
-
decodeProjectionValue(rendered, projection, expression, row[projection.alias],
|
|
325
|
+
decodeProjectionValue(rendered, projection, expression, row[projection.alias], scope, driverMode)
|
|
292
326
|
)
|
|
293
327
|
}
|
|
294
328
|
return decoded
|
|
@@ -304,7 +338,7 @@ export const make = <
|
|
|
304
338
|
Context = never
|
|
305
339
|
>(
|
|
306
340
|
dialect: Dialect,
|
|
307
|
-
execute: <PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any>>(
|
|
341
|
+
execute: <PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
308
342
|
plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
|
|
309
343
|
) => Effect.Effect<Query.ResultRows<PlanValue>, Error, Context>
|
|
310
344
|
): Executor<Dialect, Error, Context> => ({
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import * as Expression from "./expression.js"
|
|
2
|
+
import * as ExpressionAst from "./expression-ast.js"
|
|
3
|
+
import * as Plan from "./plan.js"
|
|
4
|
+
import * as Table from "./table.js"
|
|
5
|
+
import type { PredicateFormula } from "./predicate-formula.js"
|
|
6
|
+
import {
|
|
7
|
+
assumeFormulaTrue,
|
|
8
|
+
contradictsFormula,
|
|
9
|
+
guaranteedNonNullKeys,
|
|
10
|
+
guaranteedNullKeys,
|
|
11
|
+
guaranteedSourceNames,
|
|
12
|
+
trueFormula
|
|
13
|
+
} from "./predicate-runtime.js"
|
|
14
|
+
import type { SourceLike } from "./query.js"
|
|
15
|
+
|
|
16
|
+
export interface ImplicationScope {
|
|
17
|
+
readonly assumptions: PredicateFormula
|
|
18
|
+
readonly nonNullKeys: ReadonlySet<string>
|
|
19
|
+
readonly nullKeys: ReadonlySet<string>
|
|
20
|
+
readonly requiredSourceNames: ReadonlySet<string>
|
|
21
|
+
readonly absentSourceNames: ReadonlySet<string>
|
|
22
|
+
readonly sourceModes: ReadonlyMap<string, Plan.SourceMode>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type AstBackedExpression = Expression.Any & {
|
|
26
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const presentFormulaOfSource = (source: Plan.Source): PredicateFormula =>
|
|
30
|
+
source._presentFormula ?? trueFormula()
|
|
31
|
+
|
|
32
|
+
export const presenceWitnessesOfSource = (source: Plan.Source): ReadonlySet<string> =>
|
|
33
|
+
new Set(source._presenceWitnesses ?? [])
|
|
34
|
+
|
|
35
|
+
const collectPresenceWitnesses = (
|
|
36
|
+
selection: unknown,
|
|
37
|
+
output: Set<string>
|
|
38
|
+
): void => {
|
|
39
|
+
if (typeof selection !== "object" || selection === null) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
if (Expression.TypeId in selection && ExpressionAst.TypeId in selection) {
|
|
43
|
+
const expression = selection as unknown as AstBackedExpression
|
|
44
|
+
const ast = expression[ExpressionAst.TypeId]
|
|
45
|
+
if (ast.kind === "column" && expression[Expression.TypeId].nullability === "never") {
|
|
46
|
+
output.add(`${ast.tableName}.${ast.columnName}`)
|
|
47
|
+
}
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
for (const value of Object.values(selection as Record<string, unknown>)) {
|
|
51
|
+
collectPresenceWitnesses(value, output)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const presenceWitnessesOfSourceLike = (source: SourceLike): readonly string[] => {
|
|
56
|
+
const output = new Set<string>()
|
|
57
|
+
if (typeof source !== "object" || source === null) {
|
|
58
|
+
return []
|
|
59
|
+
}
|
|
60
|
+
if (Table.TypeId in source) {
|
|
61
|
+
collectPresenceWitnesses((source as Plan.Any)[Plan.TypeId].selection, output)
|
|
62
|
+
return [...output]
|
|
63
|
+
}
|
|
64
|
+
if ("columns" in source) {
|
|
65
|
+
collectPresenceWitnesses((source as { readonly columns: unknown }).columns, output)
|
|
66
|
+
}
|
|
67
|
+
return [...output]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const directAbsentSourceNames = (
|
|
71
|
+
available: Readonly<Record<string, Plan.Source>>,
|
|
72
|
+
assumptions: PredicateFormula
|
|
73
|
+
): Set<string> => {
|
|
74
|
+
const nullKeys = guaranteedNullKeys(assumptions)
|
|
75
|
+
const absent = new Set<string>()
|
|
76
|
+
for (const [name, source] of Object.entries(available)) {
|
|
77
|
+
if (source._presenceWitnesses?.some((key) => nullKeys.has(key))) {
|
|
78
|
+
absent.add(name)
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
if (contradictsFormula(assumptions, presentFormulaOfSource(source))) {
|
|
82
|
+
absent.add(name)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return absent
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const propagateAbsentSourceNames = (
|
|
89
|
+
available: Readonly<Record<string, Plan.Source>>,
|
|
90
|
+
seed: ReadonlySet<string>
|
|
91
|
+
): Set<string> => {
|
|
92
|
+
const absent = new Set(seed)
|
|
93
|
+
let changed = true
|
|
94
|
+
while (changed) {
|
|
95
|
+
changed = false
|
|
96
|
+
for (const [name, source] of Object.entries(available)) {
|
|
97
|
+
if (absent.has(name)) {
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
const required = guaranteedSourceNames(presentFormulaOfSource(source))
|
|
101
|
+
if (Array.from(required).some((dependency) => absent.has(dependency))) {
|
|
102
|
+
absent.add(name)
|
|
103
|
+
changed = true
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return absent
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const resolveImplicationScope = (
|
|
111
|
+
available: Readonly<Record<string, Plan.Source>>,
|
|
112
|
+
initialAssumptions: PredicateFormula
|
|
113
|
+
): ImplicationScope => {
|
|
114
|
+
let assumptions = initialAssumptions
|
|
115
|
+
const required = new Set<string>(
|
|
116
|
+
Object.entries(available)
|
|
117
|
+
.filter(([, source]) => source.mode === "required")
|
|
118
|
+
.map(([name]) => name)
|
|
119
|
+
)
|
|
120
|
+
const appliedRequired = new Set<string>()
|
|
121
|
+
let absent = new Set<string>()
|
|
122
|
+
|
|
123
|
+
let changed = true
|
|
124
|
+
while (changed) {
|
|
125
|
+
changed = false
|
|
126
|
+
|
|
127
|
+
for (const name of guaranteedSourceNames(assumptions)) {
|
|
128
|
+
if (!required.has(name)) {
|
|
129
|
+
required.add(name)
|
|
130
|
+
changed = true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const name of required) {
|
|
135
|
+
if (absent.has(name) || appliedRequired.has(name)) {
|
|
136
|
+
continue
|
|
137
|
+
}
|
|
138
|
+
const source = available[name]
|
|
139
|
+
if (source === undefined) {
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
assumptions = assumeFormulaTrue(assumptions, presentFormulaOfSource(source))
|
|
143
|
+
appliedRequired.add(name)
|
|
144
|
+
changed = true
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const nextAbsent = propagateAbsentSourceNames(available, directAbsentSourceNames(available, assumptions))
|
|
148
|
+
if (nextAbsent.size !== absent.size || Array.from(nextAbsent).some((name) => !absent.has(name))) {
|
|
149
|
+
absent = nextAbsent
|
|
150
|
+
changed = true
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const name of absent) {
|
|
155
|
+
required.delete(name)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const sourceModes = new Map<string, Plan.SourceMode>()
|
|
159
|
+
for (const [name, source] of Object.entries(available)) {
|
|
160
|
+
sourceModes.set(name, required.has(name) ? "required" : source.mode)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
assumptions,
|
|
165
|
+
nonNullKeys: guaranteedNonNullKeys(assumptions),
|
|
166
|
+
nullKeys: guaranteedNullKeys(assumptions),
|
|
167
|
+
requiredSourceNames: required,
|
|
168
|
+
absentSourceNames: absent,
|
|
169
|
+
sourceModes
|
|
170
|
+
}
|
|
171
|
+
}
|