effect-qb 0.12.3 → 0.13.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/CHANGELOG.md +134 -0
- package/README.md +372 -224
- package/dist/mysql.js +5037 -4962
- package/dist/postgres.js +4978 -4903
- package/package.json +8 -1
- package/src/internal/column-state.ts +9 -1
- package/src/internal/column.ts +30 -17
- package/src/internal/expression-ast.ts +11 -0
- package/src/internal/expression.ts +2 -0
- package/src/internal/query-factory.ts +50 -63
- package/src/internal/query.ts +16 -2
- package/src/internal/sql-expression-renderer.ts +33 -9
- package/src/internal/table-options.ts +2 -1
- package/src/internal/table.ts +4 -3
- package/src/mysql/column.ts +2 -1
- package/src/mysql/executor.ts +20 -17
- package/src/mysql/function/aggregate.ts +6 -0
- package/src/mysql/function/core.ts +4 -0
- package/src/mysql/function/index.ts +19 -0
- package/src/mysql/function/json.ts +4 -0
- package/src/mysql/function/string.ts +6 -0
- package/src/mysql/function/temporal.ts +103 -0
- package/src/mysql/function/window.ts +7 -0
- package/src/mysql/private/query.ts +13 -0
- package/src/mysql/query.ts +1 -26
- package/src/mysql.ts +2 -0
- package/src/postgres/column.ts +1 -1
- package/src/postgres/executor.ts +19 -17
- package/src/postgres/function/aggregate.ts +6 -0
- package/src/postgres/function/core.ts +4 -0
- package/src/postgres/function/index.ts +19 -0
- package/src/postgres/function/json.ts +4 -0
- package/src/postgres/function/string.ts +6 -0
- package/src/postgres/function/temporal.ts +107 -0
- package/src/postgres/function/window.ts +7 -0
- package/src/postgres/private/query.ts +13 -0
- package/src/postgres/query.ts +1 -26
- package/src/postgres.ts +2 -0
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-qb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
7
7
|
"src",
|
|
8
|
+
"CHANGELOG.md",
|
|
8
9
|
"README.md"
|
|
9
10
|
],
|
|
10
11
|
"exports": {
|
|
@@ -18,11 +19,17 @@
|
|
|
18
19
|
},
|
|
19
20
|
"./package.json": "./package.json"
|
|
20
21
|
},
|
|
22
|
+
"imports": {
|
|
23
|
+
"#postgres": "./src/postgres.ts",
|
|
24
|
+
"#mysql": "./src/mysql.ts",
|
|
25
|
+
"#internal/*": "./src/internal/*"
|
|
26
|
+
},
|
|
21
27
|
"publishConfig": {
|
|
22
28
|
"access": "public"
|
|
23
29
|
},
|
|
24
30
|
"scripts": {
|
|
25
31
|
"build": "bun scripts/build-package.ts",
|
|
32
|
+
"release": "bun scripts/release.ts",
|
|
26
33
|
"test": "bun test",
|
|
27
34
|
"test:types": "bunx tsgo -p tsconfig.type-tests.json",
|
|
28
35
|
"test:integration": "bun scripts/test-integration.ts",
|
|
@@ -42,6 +42,8 @@ export interface ColumnState<
|
|
|
42
42
|
readonly primaryKey: PrimaryKey
|
|
43
43
|
readonly unique: Unique
|
|
44
44
|
readonly references: Ref
|
|
45
|
+
readonly defaultValue?: Expression.Any
|
|
46
|
+
readonly generatedValue?: Expression.Any
|
|
45
47
|
readonly source: Source
|
|
46
48
|
readonly dependencies: Dependencies
|
|
47
49
|
}
|
|
@@ -93,6 +95,8 @@ export interface ColumnDefinition<
|
|
|
93
95
|
readonly primaryKey: PrimaryKey
|
|
94
96
|
readonly unique: Unique
|
|
95
97
|
readonly references: Ref
|
|
98
|
+
readonly defaultValue?: Expression.Any
|
|
99
|
+
readonly generatedValue?: Expression.Any
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
|
|
@@ -249,6 +253,8 @@ export const makeColumnDefinition = <
|
|
|
249
253
|
primaryKey: metadata.primaryKey,
|
|
250
254
|
unique: metadata.unique,
|
|
251
255
|
references: metadata.references,
|
|
256
|
+
defaultValue: metadata.defaultValue,
|
|
257
|
+
generatedValue: metadata.generatedValue,
|
|
252
258
|
source: undefined as Source,
|
|
253
259
|
dependencies: {} as Dependencies
|
|
254
260
|
}
|
|
@@ -338,7 +344,9 @@ export const remapColumnDefinition = <
|
|
|
338
344
|
generated: metadata.generated,
|
|
339
345
|
primaryKey: metadata.primaryKey,
|
|
340
346
|
unique: metadata.unique,
|
|
341
|
-
references: metadata.references
|
|
347
|
+
references: metadata.references,
|
|
348
|
+
defaultValue: metadata.defaultValue,
|
|
349
|
+
generatedValue: metadata.generatedValue
|
|
342
350
|
}
|
|
343
351
|
if (ExpressionAst.TypeId in column) {
|
|
344
352
|
next[ExpressionAst.TypeId] = (column as unknown as {
|
package/src/internal/column.ts
CHANGED
|
@@ -105,6 +105,11 @@ type GeneratedColumn<Column extends AnyColumnDefinition> = ColumnDefinition<
|
|
|
105
105
|
ReferencesOf<Column>
|
|
106
106
|
>
|
|
107
107
|
|
|
108
|
+
type CompatibleColumnExpression<
|
|
109
|
+
Column extends AnyColumnDefinition,
|
|
110
|
+
Value extends Expression.Any
|
|
111
|
+
> = [Expression.RuntimeOf<Value>] extends [SelectType<Column>] ? Column : never
|
|
112
|
+
|
|
108
113
|
type ReferencingColumn<
|
|
109
114
|
Column extends AnyColumnDefinition,
|
|
110
115
|
Target extends AnyBoundColumn
|
|
@@ -378,24 +383,30 @@ export const unique = <Column extends AnyColumnDefinition>(
|
|
|
378
383
|
unique: true
|
|
379
384
|
})
|
|
380
385
|
|
|
381
|
-
/** Marks a column as having a
|
|
382
|
-
export const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
386
|
+
/** Marks a column as having a database default expression and therefore optional on insert. */
|
|
387
|
+
export const default_ = <Value extends Expression.Any>(value: Value) =>
|
|
388
|
+
<Column extends AnyColumnDefinition>(
|
|
389
|
+
column: Column[typeof ColumnTypeId]["generated"] extends true ? never : CompatibleColumnExpression<Column, Value>
|
|
390
|
+
): HasDefaultColumn<Column> =>
|
|
391
|
+
mapColumn(column, {
|
|
392
|
+
...column.metadata,
|
|
393
|
+
hasDefault: true,
|
|
394
|
+
defaultValue: value,
|
|
395
|
+
generatedValue: undefined
|
|
396
|
+
})
|
|
389
397
|
|
|
390
|
-
/** Marks a column as generated by the database and omitted from insert/update. */
|
|
391
|
-
export const generated = <
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
398
|
+
/** Marks a column as generated by the database expression and omitted from insert/update. */
|
|
399
|
+
export const generated = <Value extends Expression.Any>(value: Value) =>
|
|
400
|
+
<Column extends AnyColumnDefinition>(
|
|
401
|
+
column: Column[typeof ColumnTypeId]["hasDefault"] extends true ? never : CompatibleColumnExpression<Column, Value>
|
|
402
|
+
): GeneratedColumn<Column> =>
|
|
403
|
+
mapColumn(column, {
|
|
404
|
+
...column.metadata,
|
|
405
|
+
generated: true,
|
|
406
|
+
hasDefault: false,
|
|
407
|
+
defaultValue: undefined,
|
|
408
|
+
generatedValue: value
|
|
409
|
+
})
|
|
399
410
|
|
|
400
411
|
/**
|
|
401
412
|
* Attaches a lazy foreign-key reference to another bound column.
|
|
@@ -415,3 +426,5 @@ export const references = <Target extends AnyBoundColumn>(target: () => Target)
|
|
|
415
426
|
export type Any = AnyColumnDefinition
|
|
416
427
|
/** Convenience alias for any bound column. */
|
|
417
428
|
export type AnyBound = BoundColumn<any, any, any, any, any, any, any, any, any, any, any, any>
|
|
429
|
+
|
|
430
|
+
export { default_ as default }
|
|
@@ -34,6 +34,16 @@ export interface CastNode<
|
|
|
34
34
|
readonly target: Target
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** General SQL function call captured by the internal expression AST. */
|
|
38
|
+
export interface FunctionCallNode<
|
|
39
|
+
Name extends string = string,
|
|
40
|
+
Args extends readonly Expression.Any[] = readonly Expression.Any[]
|
|
41
|
+
> {
|
|
42
|
+
readonly kind: "function"
|
|
43
|
+
readonly name: Name
|
|
44
|
+
readonly args: Args
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
/** `excluded.column` reference used inside insert conflict handlers. */
|
|
38
48
|
export interface ExcludedNode<
|
|
39
49
|
ColumnName extends string = string
|
|
@@ -325,6 +335,7 @@ export type Any =
|
|
|
325
335
|
| ColumnNode
|
|
326
336
|
| LiteralNode
|
|
327
337
|
| CastNode
|
|
338
|
+
| FunctionCallNode
|
|
328
339
|
| ExcludedNode
|
|
329
340
|
| UnaryNode
|
|
330
341
|
| BinaryNode
|
|
@@ -168,6 +168,8 @@ export declare namespace DbType {
|
|
|
168
168
|
export type PgDate = Base<"postgres", "date">
|
|
169
169
|
export type PgTime = Base<"postgres", "time">
|
|
170
170
|
export type PgTimestamp = Base<"postgres", "timestamp">
|
|
171
|
+
export type PgTimetz = Base<"postgres", "timetz">
|
|
172
|
+
export type PgTimestamptz = Base<"postgres", "timestamptz">
|
|
171
173
|
export type PgInterval = Base<"postgres", "interval">
|
|
172
174
|
export type PgBytea = Base<"postgres", "bytea">
|
|
173
175
|
export type PgJsonb = Base<"postgres", "jsonb">
|
|
@@ -3802,7 +3802,7 @@ export function makeDialectQuery<
|
|
|
3802
3802
|
const firstRow = rows[0]
|
|
3803
3803
|
const firstColumns = Object.keys(firstRow)
|
|
3804
3804
|
if (firstColumns.length === 0) {
|
|
3805
|
-
throw new Error("values(...) rows must specify at least one column; use
|
|
3805
|
+
throw new Error("values(...) rows must specify at least one column; use insert(target) for default-only inserts instead")
|
|
3806
3806
|
}
|
|
3807
3807
|
const columns = firstColumns as [string, ...string[]]
|
|
3808
3808
|
const normalizedRows = rows.map((row) => {
|
|
@@ -4072,12 +4072,6 @@ type InsertPlanSelectionShapeError<
|
|
|
4072
4072
|
readonly __effect_qb_hint__: "Project a flat object like select({ id: ..., email: ... }) with column-name keys"
|
|
4073
4073
|
}
|
|
4074
4074
|
|
|
4075
|
-
type DefaultValuesError<Target extends MutationTargetLike> = Target & {
|
|
4076
|
-
readonly __effect_qb_error__: "effect-qb: defaultValues(...) requires every insert column to be optional or generated"
|
|
4077
|
-
readonly __effect_qb_required_columns__: RequiredKeys<Table.InsertOf<Target>>
|
|
4078
|
-
readonly __effect_qb_hint__: "Use insert(...) for required columns, or mark columns nullable/hasDefault/generated"
|
|
4079
|
-
}
|
|
4080
|
-
|
|
4081
4075
|
type MysqlConflictTargetError<Target> = Target & {
|
|
4082
4076
|
readonly __effect_qb_error__: "effect-qb: mysql does not support named or predicate-scoped conflict targets"
|
|
4083
4077
|
readonly __effect_qb_hint__: "Use a column tuple target, or rely on MySQL duplicate-key resolution without target predicates"
|
|
@@ -4558,34 +4552,77 @@ type AsCurriedResult<
|
|
|
4558
4552
|
return makeDerivedSource(value as CompletePlan<QueryPlan<any, any, any, any, any, any, any, any, any, any>>, resolvedAlias)
|
|
4559
4553
|
}
|
|
4560
4554
|
|
|
4555
|
+
function with_<
|
|
4556
|
+
Alias extends string
|
|
4557
|
+
>(
|
|
4558
|
+
alias: Alias
|
|
4559
|
+
): <PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
4560
|
+
value: CompletePlan<PlanValue>
|
|
4561
|
+
) => import("./query.js").CteSource<PlanValue, Alias>
|
|
4561
4562
|
function with_<
|
|
4562
4563
|
PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>,
|
|
4563
4564
|
Alias extends string
|
|
4564
4565
|
>(
|
|
4565
4566
|
value: CompletePlan<PlanValue>,
|
|
4566
4567
|
alias: Alias
|
|
4567
|
-
): import("./query.js").CteSource<PlanValue, Alias>
|
|
4568
|
-
|
|
4568
|
+
): import("./query.js").CteSource<PlanValue, Alias>
|
|
4569
|
+
function with_(valueOrAlias: unknown, alias?: string): unknown {
|
|
4570
|
+
if (alias === undefined) {
|
|
4571
|
+
return (value: unknown) => with_(value as any, valueOrAlias as string)
|
|
4572
|
+
}
|
|
4573
|
+
return makeCteSource(
|
|
4574
|
+
valueOrAlias as CompletePlan<QueryPlan<any, any, any, any, any, any, any, any, any, any>>,
|
|
4575
|
+
alias
|
|
4576
|
+
)
|
|
4569
4577
|
}
|
|
4570
4578
|
|
|
4579
|
+
function withRecursive_<
|
|
4580
|
+
Alias extends string
|
|
4581
|
+
>(
|
|
4582
|
+
alias: Alias
|
|
4583
|
+
): <PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
4584
|
+
value: CompletePlan<PlanValue>
|
|
4585
|
+
) => import("./query.js").CteSource<PlanValue, Alias>
|
|
4571
4586
|
function withRecursive_<
|
|
4572
4587
|
PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>,
|
|
4573
4588
|
Alias extends string
|
|
4574
4589
|
>(
|
|
4575
4590
|
value: CompletePlan<PlanValue>,
|
|
4576
4591
|
alias: Alias
|
|
4577
|
-
): import("./query.js").CteSource<PlanValue, Alias>
|
|
4578
|
-
|
|
4592
|
+
): import("./query.js").CteSource<PlanValue, Alias>
|
|
4593
|
+
function withRecursive_(valueOrAlias: unknown, alias?: string): unknown {
|
|
4594
|
+
if (alias === undefined) {
|
|
4595
|
+
return (value: unknown) => withRecursive_(value as any, valueOrAlias as string)
|
|
4596
|
+
}
|
|
4597
|
+
return makeCteSource(
|
|
4598
|
+
valueOrAlias as CompletePlan<QueryPlan<any, any, any, any, any, any, any, any, any, any>>,
|
|
4599
|
+
alias,
|
|
4600
|
+
true
|
|
4601
|
+
)
|
|
4579
4602
|
}
|
|
4580
4603
|
|
|
4604
|
+
function lateral<
|
|
4605
|
+
Alias extends string
|
|
4606
|
+
>(
|
|
4607
|
+
alias: Alias
|
|
4608
|
+
): <PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
4609
|
+
value: PlanValue
|
|
4610
|
+
) => import("./query.js").LateralSource<PlanValue, Alias>
|
|
4581
4611
|
function lateral<
|
|
4582
4612
|
PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>,
|
|
4583
4613
|
Alias extends string
|
|
4584
4614
|
>(
|
|
4585
4615
|
value: PlanValue,
|
|
4586
4616
|
alias: Alias
|
|
4587
|
-
): import("./query.js").LateralSource<PlanValue, Alias>
|
|
4588
|
-
|
|
4617
|
+
): import("./query.js").LateralSource<PlanValue, Alias>
|
|
4618
|
+
function lateral(valueOrAlias: unknown, alias?: string): unknown {
|
|
4619
|
+
if (alias === undefined) {
|
|
4620
|
+
return (value: unknown) => lateral(value as any, valueOrAlias as string)
|
|
4621
|
+
}
|
|
4622
|
+
return makeLateralSource(
|
|
4623
|
+
valueOrAlias as QueryPlan<any, any, any, any, any, any, any, any, any, any>,
|
|
4624
|
+
alias
|
|
4625
|
+
)
|
|
4589
4626
|
}
|
|
4590
4627
|
|
|
4591
4628
|
const values = <
|
|
@@ -5714,55 +5751,6 @@ type AsCurriedResult<
|
|
|
5714
5751
|
}, undefined as unknown as TrueFormula, "write", "insert", target, insertState)
|
|
5715
5752
|
}
|
|
5716
5753
|
|
|
5717
|
-
const defaultValues = <
|
|
5718
|
-
Target extends MutationTargetLike
|
|
5719
|
-
>(
|
|
5720
|
-
target: RequiredKeys<Table.InsertOf<Target>> extends never ? Target : DefaultValuesError<Target>
|
|
5721
|
-
): QueryPlan<
|
|
5722
|
-
{},
|
|
5723
|
-
never,
|
|
5724
|
-
AddAvailable<{}, SourceNameOf<Target>>,
|
|
5725
|
-
TableDialectOf<Target>,
|
|
5726
|
-
never,
|
|
5727
|
-
SourceNameOf<Target>,
|
|
5728
|
-
never,
|
|
5729
|
-
TrueFormula,
|
|
5730
|
-
"write",
|
|
5731
|
-
"insert",
|
|
5732
|
-
Target,
|
|
5733
|
-
"ready"
|
|
5734
|
-
> => {
|
|
5735
|
-
const { sourceName, sourceBaseName } = targetSourceDetails(target as Target)
|
|
5736
|
-
return makePlan({
|
|
5737
|
-
selection: {},
|
|
5738
|
-
required: [] as never,
|
|
5739
|
-
available: {
|
|
5740
|
-
[sourceName]: {
|
|
5741
|
-
name: sourceName,
|
|
5742
|
-
mode: "required",
|
|
5743
|
-
baseName: sourceBaseName
|
|
5744
|
-
}
|
|
5745
|
-
} as AddAvailable<{}, SourceNameOf<Target>>,
|
|
5746
|
-
dialect: (target as Target)[Plan.TypeId].dialect as TableDialectOf<Target>
|
|
5747
|
-
}, {
|
|
5748
|
-
kind: "insert",
|
|
5749
|
-
select: {},
|
|
5750
|
-
into: {
|
|
5751
|
-
kind: "from",
|
|
5752
|
-
tableName: sourceName,
|
|
5753
|
-
baseTableName: sourceBaseName,
|
|
5754
|
-
source: target as Target
|
|
5755
|
-
},
|
|
5756
|
-
values: [],
|
|
5757
|
-
conflict: undefined,
|
|
5758
|
-
where: [],
|
|
5759
|
-
having: [],
|
|
5760
|
-
joins: [],
|
|
5761
|
-
groupBy: [],
|
|
5762
|
-
orderBy: []
|
|
5763
|
-
}, undefined as unknown as TrueFormula, "write", "insert", target as Target, "ready")
|
|
5764
|
-
}
|
|
5765
|
-
|
|
5766
5754
|
const attachInsertSource = (
|
|
5767
5755
|
plan: QueryPlan<any, any, any, any, any, any, any, any, any, "insert", MutationTargetLike, "missing">,
|
|
5768
5756
|
source: AnyValuesInput | AnyValuesSource | AnyUnnestSource | CompletePlan<QueryPlan<any, any, any, any, any, any, any, any, any, any>>
|
|
@@ -6711,7 +6699,6 @@ type AsCurriedResult<
|
|
|
6711
6699
|
unnest,
|
|
6712
6700
|
generateSeries,
|
|
6713
6701
|
returning,
|
|
6714
|
-
defaultValues,
|
|
6715
6702
|
onConflict,
|
|
6716
6703
|
insert,
|
|
6717
6704
|
update,
|
package/src/internal/query.ts
CHANGED
|
@@ -1206,9 +1206,21 @@ type InsertSourceCompletenessError<
|
|
|
1206
1206
|
PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
|
|
1207
1207
|
> = PlanValue & {
|
|
1208
1208
|
readonly __effect_qb_error__: "effect-qb: insert plan is missing inline values or a source"
|
|
1209
|
-
readonly __effect_qb_hint__: "Pass values directly to insert(...),
|
|
1209
|
+
readonly __effect_qb_hint__: "Pass values directly to insert(...), or pipe the insert plan into from(...)"
|
|
1210
1210
|
}
|
|
1211
1211
|
|
|
1212
|
+
type RequiredKeys<Shape> = Extract<{
|
|
1213
|
+
[K in keyof Shape]-?: {} extends Pick<Shape, K> ? never : K
|
|
1214
|
+
}[keyof Shape], string>
|
|
1215
|
+
|
|
1216
|
+
type InsertHasOptionalDefaults<
|
|
1217
|
+
PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
|
|
1218
|
+
> = MutationTargetOfPlan<PlanValue> extends infer Target extends MutationTargetLike
|
|
1219
|
+
? RequiredKeys<Table.InsertOf<Target>> extends never
|
|
1220
|
+
? true
|
|
1221
|
+
: false
|
|
1222
|
+
: false
|
|
1223
|
+
|
|
1212
1224
|
/** Narrows a query plan to aggregate-compatible selections. */
|
|
1213
1225
|
export type AggregationCompatiblePlan<
|
|
1214
1226
|
PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
|
|
@@ -1221,7 +1233,9 @@ export type CompletePlan<PlanValue extends QueryPlan<any, any, any, any, any, an
|
|
|
1221
1233
|
PlanValue extends QueryPlan<infer Selection, infer Required, any, any, infer Grouped, any, any, any, any, infer Statement, any, infer InsertState>
|
|
1222
1234
|
? Statement extends "insert"
|
|
1223
1235
|
? InsertState extends "missing"
|
|
1224
|
-
?
|
|
1236
|
+
? InsertHasOptionalDefaults<PlanValue> extends true
|
|
1237
|
+
? PlanValue
|
|
1238
|
+
: InsertSourceCompletenessError<PlanValue>
|
|
1225
1239
|
: HasKnownOutstanding<Required> extends true
|
|
1226
1240
|
? SourceCompletenessError<PlanValue, Extract<Required, string>>
|
|
1227
1241
|
: IsAggregationCompatibleSelection<Selection, Grouped> extends true ? PlanValue : AggregationCompatibilityError<PlanValue>
|
|
@@ -46,6 +46,7 @@ const renderCastType = (
|
|
|
46
46
|
|
|
47
47
|
const renderColumnDefinition = (
|
|
48
48
|
dialect: SqlDialect,
|
|
49
|
+
state: RenderState,
|
|
49
50
|
columnName: string,
|
|
50
51
|
column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
|
|
51
52
|
): string => {
|
|
@@ -53,6 +54,11 @@ const renderColumnDefinition = (
|
|
|
53
54
|
dialect.quoteIdentifier(columnName),
|
|
54
55
|
renderDbType(dialect, column.metadata.dbType)
|
|
55
56
|
]
|
|
57
|
+
if (column.metadata.generatedValue) {
|
|
58
|
+
clauses.push(`generated always as (${renderExpression(column.metadata.generatedValue, state, dialect)}) stored`)
|
|
59
|
+
} else if (column.metadata.defaultValue) {
|
|
60
|
+
clauses.push(`default ${renderExpression(column.metadata.defaultValue, state, dialect)}`)
|
|
61
|
+
}
|
|
56
62
|
if (!column.metadata.nullable) {
|
|
57
63
|
clauses.push("not null")
|
|
58
64
|
}
|
|
@@ -60,17 +66,11 @@ const renderColumnDefinition = (
|
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
const renderCheckPredicate = (
|
|
63
|
-
predicate:
|
|
69
|
+
predicate: Expression.Any,
|
|
64
70
|
state: RenderState,
|
|
65
71
|
dialect: SqlDialect
|
|
66
72
|
): string => {
|
|
67
|
-
|
|
68
|
-
return predicate
|
|
69
|
-
}
|
|
70
|
-
if (predicate !== null && typeof predicate === "object" && Expression.TypeId in predicate) {
|
|
71
|
-
return renderExpression(predicate as Expression.Any, state, dialect)
|
|
72
|
-
}
|
|
73
|
-
throw new Error("Unsupported check constraint predicate for DDL rendering")
|
|
73
|
+
return renderExpression(predicate, state, dialect)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const renderCreateTableSql = (
|
|
@@ -82,7 +82,7 @@ const renderCreateTableSql = (
|
|
|
82
82
|
const table = targetSource.source as Table.AnyTable
|
|
83
83
|
const fields = table[Table.TypeId].fields
|
|
84
84
|
const definitions = Object.entries(fields).map(([columnName, column]) =>
|
|
85
|
-
renderColumnDefinition(dialect, columnName, column)
|
|
85
|
+
renderColumnDefinition(dialect, state, columnName, column)
|
|
86
86
|
)
|
|
87
87
|
for (const option of table[Table.OptionsSymbol]) {
|
|
88
88
|
switch (option.kind) {
|
|
@@ -305,6 +305,28 @@ const renderJsonOpaquePath = (
|
|
|
305
305
|
throw new Error("Unsupported SQL/JSON path input")
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
const renderFunctionCall = (
|
|
309
|
+
name: string,
|
|
310
|
+
args: readonly Expression.Any[],
|
|
311
|
+
state: RenderState,
|
|
312
|
+
dialect: SqlDialect
|
|
313
|
+
): string => {
|
|
314
|
+
const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
315
|
+
if (args.length === 0) {
|
|
316
|
+
switch (name) {
|
|
317
|
+
case "current_date":
|
|
318
|
+
case "current_time":
|
|
319
|
+
case "current_timestamp":
|
|
320
|
+
case "localtime":
|
|
321
|
+
case "localtimestamp":
|
|
322
|
+
return name
|
|
323
|
+
default:
|
|
324
|
+
return `${name}()`
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return `${name}(${renderedArgs})`
|
|
328
|
+
}
|
|
329
|
+
|
|
308
330
|
const renderJsonExpression = (
|
|
309
331
|
ast: Record<string, unknown>,
|
|
310
332
|
state: RenderState,
|
|
@@ -1190,6 +1212,8 @@ export const renderExpression = (
|
|
|
1190
1212
|
: `excluded.${dialect.quoteIdentifier(ast.columnName)}`
|
|
1191
1213
|
case "cast":
|
|
1192
1214
|
return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1215
|
+
case "function":
|
|
1216
|
+
return renderFunctionCall(ast.name, Array.isArray(ast.args) ? ast.args : [], state, dialect)
|
|
1193
1217
|
case "eq":
|
|
1194
1218
|
return `(${renderExpression(ast.left, state, dialect)} = ${renderExpression(ast.right, state, dialect)})`
|
|
1195
1219
|
case "neq":
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type AnyColumnDefinition,
|
|
5
5
|
type IsNullable
|
|
6
6
|
} from "./column-state.js"
|
|
7
|
+
import type { Any as AnyExpression } from "./expression.js"
|
|
7
8
|
import type { TableFieldMap } from "./schema-derivation.js"
|
|
8
9
|
|
|
9
10
|
/** Non-empty list of column names. */
|
|
@@ -36,7 +37,7 @@ export type TableOptionSpec =
|
|
|
36
37
|
| {
|
|
37
38
|
readonly kind: "check"
|
|
38
39
|
readonly name: string
|
|
39
|
-
readonly predicate:
|
|
40
|
+
readonly predicate: AnyExpression
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
/** Thin wrapper used by the public `Table.*` option builders. */
|
package/src/internal/table.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { pipeArguments, type Pipeable } from "effect/Pipeable"
|
|
|
2
2
|
import * as Schema from "effect/Schema"
|
|
3
3
|
|
|
4
4
|
import * as Plan from "./plan.js"
|
|
5
|
+
import type { Any as AnyExpression } from "./expression.js"
|
|
5
6
|
import type { BoundColumnFrom } from "./column-state.js"
|
|
6
7
|
import { bindColumn, type AnyColumnDefinition } from "./column-state.js"
|
|
7
8
|
import {
|
|
@@ -646,14 +647,14 @@ export const foreignKey = <
|
|
|
646
647
|
})
|
|
647
648
|
})
|
|
648
649
|
|
|
649
|
-
/** Declares a
|
|
650
|
+
/** Declares a check constraint expression. */
|
|
650
651
|
export const check = <Name extends string>(
|
|
651
652
|
name: Name,
|
|
652
|
-
predicate:
|
|
653
|
+
predicate: AnyExpression
|
|
653
654
|
): TableOption<{
|
|
654
655
|
readonly kind: "check"
|
|
655
656
|
readonly name: Name
|
|
656
|
-
readonly predicate:
|
|
657
|
+
readonly predicate: AnyExpression
|
|
657
658
|
}> => makeOption({
|
|
658
659
|
kind: "check",
|
|
659
660
|
name,
|
package/src/mysql/column.ts
CHANGED
|
@@ -21,10 +21,11 @@ export const custom = BaseColumn.mysql.custom
|
|
|
21
21
|
export const nullable = BaseColumn.nullable
|
|
22
22
|
export const primaryKey = BaseColumn.primaryKey
|
|
23
23
|
export const unique = BaseColumn.unique
|
|
24
|
-
|
|
24
|
+
const default_ = BaseColumn.default_
|
|
25
25
|
export const generated = BaseColumn.generated
|
|
26
26
|
export const references = BaseColumn.references
|
|
27
27
|
export const schema = BaseColumn.schema
|
|
28
|
+
export { default_ as default }
|
|
28
29
|
|
|
29
30
|
export type Any = BaseColumn.Any
|
|
30
31
|
export type AnyBound = BaseColumn.AnyBound
|
package/src/mysql/executor.ts
CHANGED
|
@@ -2,8 +2,9 @@ import * as Effect from "effect/Effect"
|
|
|
2
2
|
import * as SqlClient from "@effect/sql/SqlClient"
|
|
3
3
|
|
|
4
4
|
import * as CoreExecutor from "../internal/executor.js"
|
|
5
|
-
import * as
|
|
6
|
-
import * as
|
|
5
|
+
import * as CoreQuery from "../internal/query.js"
|
|
6
|
+
import * as CoreRenderer from "../internal/renderer.js"
|
|
7
|
+
import { renderMysqlPlan } from "../internal/mysql-renderer.js"
|
|
7
8
|
import {
|
|
8
9
|
narrowMysqlDriverErrorForReadQuery,
|
|
9
10
|
normalizeMysqlDriverError,
|
|
@@ -19,17 +20,19 @@ export type RowDecodeError = CoreExecutor.RowDecodeError
|
|
|
19
20
|
export type Driver<Error = never, Context = never> = CoreExecutor.Driver<"mysql", Error, Context>
|
|
20
21
|
/** MySQL-specialized executor contract. */
|
|
21
22
|
export type Executor<Error = never, Context = never> = CoreExecutor.Executor<"mysql", Error, Context>
|
|
23
|
+
/** MySQL-specialized renderer contract. */
|
|
24
|
+
export type Renderer = CoreRenderer.Renderer<"mysql">
|
|
22
25
|
/** Optional renderer / driver overrides for the standard MySQL executor pipeline. */
|
|
23
26
|
export interface MakeOptions<Error = never, Context = never> {
|
|
24
|
-
readonly renderer?: Renderer
|
|
27
|
+
readonly renderer?: Renderer
|
|
25
28
|
readonly driver?: Driver<Error, Context>
|
|
26
29
|
readonly driverMode?: CoreExecutor.DriverMode
|
|
27
30
|
}
|
|
28
31
|
/** Standard composed error shape for MySQL executors. */
|
|
29
32
|
export type MysqlExecutorError = MysqlDriverError | RowDecodeError
|
|
30
33
|
/** Read-query error surface emitted by built-in MySQL executors. */
|
|
31
|
-
export type MysqlQueryError<PlanValue extends
|
|
32
|
-
Exclude<
|
|
34
|
+
export type MysqlQueryError<PlanValue extends CoreQuery.QueryPlan<any, any, any, any, any, any, any, any, any, any>> =
|
|
35
|
+
Exclude<CoreQuery.CapabilitiesOfPlan<PlanValue>, "read"> extends never ? MysqlReadQueryError : MysqlExecutorError
|
|
33
36
|
|
|
34
37
|
/** Runs an effect within the ambient MySQL SQL transaction service. */
|
|
35
38
|
export const withTransaction = CoreExecutor.withTransaction
|
|
@@ -39,9 +42,9 @@ export const withSavepoint = CoreExecutor.withSavepoint
|
|
|
39
42
|
/** MySQL executor whose error channel narrows based on the query plan. */
|
|
40
43
|
export interface QueryExecutor<Context = never> {
|
|
41
44
|
readonly dialect: "mysql"
|
|
42
|
-
execute<PlanValue extends
|
|
43
|
-
plan:
|
|
44
|
-
): Effect.Effect<
|
|
45
|
+
execute<PlanValue extends CoreQuery.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
46
|
+
plan: CoreQuery.DialectCompatiblePlan<PlanValue, "mysql">
|
|
47
|
+
): Effect.Effect<CoreQuery.ResultRows<PlanValue>, MysqlQueryError<PlanValue>, Context>
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
/** Constructs a MySQL-specialized SQL driver. */
|
|
@@ -50,7 +53,7 @@ export const driver = <
|
|
|
50
53
|
Context = never
|
|
51
54
|
>(
|
|
52
55
|
execute: <Row>(
|
|
53
|
-
query:
|
|
56
|
+
query: CoreRenderer.RenderedQuery<Row, "mysql">
|
|
54
57
|
) => Effect.Effect<ReadonlyArray<FlatRow>, Error, Context>
|
|
55
58
|
): Driver<Error, Context> =>
|
|
56
59
|
CoreExecutor.driver("mysql", execute)
|
|
@@ -59,7 +62,7 @@ const fromDriver = <
|
|
|
59
62
|
Error = never,
|
|
60
63
|
Context = never
|
|
61
64
|
>(
|
|
62
|
-
renderer: Renderer
|
|
65
|
+
renderer: Renderer,
|
|
63
66
|
sqlDriver: Driver<Error, Context>,
|
|
64
67
|
driverMode: CoreExecutor.DriverMode = "raw"
|
|
65
68
|
): QueryExecutor<Context> => ({
|
|
@@ -102,13 +105,13 @@ const sqlClientDriver = (): Driver<any, SqlClient.SqlClient> =>
|
|
|
102
105
|
export function make(): QueryExecutor<SqlClient.SqlClient>
|
|
103
106
|
export function make(
|
|
104
107
|
options: {
|
|
105
|
-
readonly renderer?: Renderer
|
|
108
|
+
readonly renderer?: Renderer
|
|
106
109
|
readonly driverMode?: CoreExecutor.DriverMode
|
|
107
110
|
}
|
|
108
111
|
): QueryExecutor<SqlClient.SqlClient>
|
|
109
112
|
export function make<Error = never, Context = never>(
|
|
110
113
|
options: {
|
|
111
|
-
readonly renderer?: Renderer
|
|
114
|
+
readonly renderer?: Renderer
|
|
112
115
|
readonly driver: Driver<Error, Context>
|
|
113
116
|
readonly driverMode?: CoreExecutor.DriverMode
|
|
114
117
|
}
|
|
@@ -117,9 +120,9 @@ export function make<Error = never, Context = never>(
|
|
|
117
120
|
options: MakeOptions<Error, Context> = {}
|
|
118
121
|
): QueryExecutor<any> {
|
|
119
122
|
if (options.driver) {
|
|
120
|
-
return fromDriver(options.renderer ??
|
|
123
|
+
return fromDriver(options.renderer ?? CoreRenderer.make("mysql", renderMysqlPlan), options.driver, options.driverMode)
|
|
121
124
|
}
|
|
122
|
-
return fromDriver(options.renderer ??
|
|
125
|
+
return fromDriver(options.renderer ?? CoreRenderer.make("mysql", renderMysqlPlan), sqlClientDriver(), options.driverMode)
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
/** Creates a MySQL-specialized executor from a typed implementation callback. */
|
|
@@ -127,8 +130,8 @@ export const custom = <
|
|
|
127
130
|
Error = never,
|
|
128
131
|
Context = never
|
|
129
132
|
>(
|
|
130
|
-
execute: <PlanValue extends
|
|
131
|
-
plan:
|
|
132
|
-
) => Effect.Effect<
|
|
133
|
+
execute: <PlanValue extends CoreQuery.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
134
|
+
plan: CoreQuery.DialectCompatiblePlan<PlanValue, "mysql">
|
|
135
|
+
) => Effect.Effect<CoreQuery.ResultRows<PlanValue>, Error, Context>
|
|
133
136
|
): Executor<Error, Context> =>
|
|
134
137
|
CoreExecutor.make("mysql", execute as any) as Executor<Error, Context>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * as core from "./core.js"
|
|
2
|
+
export * as string from "./string.js"
|
|
3
|
+
export * as aggregate from "./aggregate.js"
|
|
4
|
+
export * as window from "./window.js"
|
|
5
|
+
export { json } from "./json.js"
|
|
6
|
+
export * as temporal from "./temporal.js"
|
|
7
|
+
|
|
8
|
+
export { coalesce } from "./core.js"
|
|
9
|
+
export { lower, upper, concat } from "./string.js"
|
|
10
|
+
export { count, max, min } from "./aggregate.js"
|
|
11
|
+
export { over, rowNumber, rank, denseRank } from "./window.js"
|
|
12
|
+
export {
|
|
13
|
+
currentDate,
|
|
14
|
+
currentTime,
|
|
15
|
+
currentTimestamp,
|
|
16
|
+
localTime,
|
|
17
|
+
localTimestamp,
|
|
18
|
+
now
|
|
19
|
+
} from "./temporal.js"
|