effect-qb 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/mysql.js +1661 -591
  2. package/dist/postgres/metadata.js +1930 -135
  3. package/dist/postgres.js +7808 -6718
  4. package/dist/sqlite.js +8360 -0
  5. package/package.json +6 -1
  6. package/src/internal/derived-table.ts +29 -3
  7. package/src/internal/dialect.ts +2 -0
  8. package/src/internal/dsl-mutation-runtime.ts +173 -4
  9. package/src/internal/dsl-plan-runtime.ts +165 -20
  10. package/src/internal/dsl-query-runtime.ts +60 -6
  11. package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
  12. package/src/internal/executor.ts +47 -9
  13. package/src/internal/expression-ast.ts +3 -2
  14. package/src/internal/grouping-key.ts +141 -1
  15. package/src/internal/implication-runtime.ts +2 -1
  16. package/src/internal/json/types.ts +155 -40
  17. package/src/internal/predicate/context.ts +14 -1
  18. package/src/internal/predicate/key.ts +19 -2
  19. package/src/internal/predicate/runtime.ts +27 -3
  20. package/src/internal/query.ts +252 -30
  21. package/src/internal/renderer.ts +35 -2
  22. package/src/internal/runtime/driver-value-mapping.ts +58 -0
  23. package/src/internal/runtime/normalize.ts +62 -38
  24. package/src/internal/runtime/schema.ts +5 -3
  25. package/src/internal/runtime/value.ts +153 -30
  26. package/src/internal/table-options.ts +108 -1
  27. package/src/internal/table.ts +87 -29
  28. package/src/mysql/column.ts +18 -2
  29. package/src/mysql/datatypes/index.ts +21 -0
  30. package/src/mysql/errors/catalog.ts +5 -5
  31. package/src/mysql/errors/normalize.ts +2 -2
  32. package/src/mysql/internal/dsl.ts +736 -218
  33. package/src/mysql/internal/renderer.ts +2 -1
  34. package/src/mysql/internal/sql-expression-renderer.ts +486 -130
  35. package/src/mysql/query.ts +9 -2
  36. package/src/mysql/table.ts +38 -12
  37. package/src/postgres/column.ts +4 -2
  38. package/src/postgres/errors/normalize.ts +2 -2
  39. package/src/postgres/executor.ts +48 -5
  40. package/src/postgres/function/core.ts +19 -1
  41. package/src/postgres/internal/dsl.ts +683 -240
  42. package/src/postgres/internal/renderer.ts +2 -1
  43. package/src/postgres/internal/schema-ddl.ts +2 -1
  44. package/src/postgres/internal/schema-model.ts +6 -3
  45. package/src/postgres/internal/sql-expression-renderer.ts +420 -91
  46. package/src/postgres/json.ts +57 -17
  47. package/src/postgres/query.ts +9 -2
  48. package/src/postgres/schema-management.ts +91 -4
  49. package/src/postgres/schema.ts +1 -1
  50. package/src/postgres/table.ts +189 -53
  51. package/src/sqlite/column.ts +128 -0
  52. package/src/sqlite/datatypes/index.ts +79 -0
  53. package/src/sqlite/datatypes/spec.ts +98 -0
  54. package/src/sqlite/errors/catalog.ts +103 -0
  55. package/src/sqlite/errors/fields.ts +19 -0
  56. package/src/sqlite/errors/index.ts +19 -0
  57. package/src/sqlite/errors/normalize.ts +229 -0
  58. package/src/sqlite/errors/requirements.ts +71 -0
  59. package/src/sqlite/errors/types.ts +29 -0
  60. package/src/sqlite/executor.ts +227 -0
  61. package/src/sqlite/function/aggregate.ts +2 -0
  62. package/src/sqlite/function/core.ts +2 -0
  63. package/src/sqlite/function/index.ts +19 -0
  64. package/src/sqlite/function/string.ts +2 -0
  65. package/src/sqlite/function/temporal.ts +100 -0
  66. package/src/sqlite/function/window.ts +2 -0
  67. package/src/sqlite/internal/dialect.ts +37 -0
  68. package/src/sqlite/internal/dsl.ts +6926 -0
  69. package/src/sqlite/internal/renderer.ts +47 -0
  70. package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
  71. package/src/sqlite/json.ts +2 -0
  72. package/src/sqlite/query.ts +196 -0
  73. package/src/sqlite/renderer.ts +24 -0
  74. package/src/sqlite/table.ts +183 -0
  75. package/src/sqlite.ts +22 -0
@@ -6,6 +6,7 @@ import * as RowSet from "./row-set.js"
6
6
  import * as Table from "./table.js"
7
7
  import * as ExpressionAst from "./expression-ast.js"
8
8
  import * as QueryAst from "./query-ast.js"
9
+ import type * as ProjectionAlias from "./projection-alias.js"
9
10
  import type { JsonNode } from "./json/ast.js"
10
11
  import type * as JsonPath from "./json/path.js"
11
12
  import type { QueryCapability } from "./query-requirements.js"
@@ -24,7 +25,7 @@ import type {
24
25
  } from "./predicate/analysis.js"
25
26
  import type { AssumeFactsFalse, AssumeFactsTrue, PredicateContext } from "./predicate/context.js"
26
27
  import type { FormulaOfPredicate } from "./predicate/normalize.js"
27
- import type { ColumnKeyOfAst, ColumnKeyOfExpression, PredicateKeyOfAst } from "./predicate/key.js"
28
+ import type { ColumnKey, ColumnKeyOfAst, ColumnKeyOfExpression, PredicateKeyOfAst } from "./predicate/key.js"
28
29
  import type { PredicateFormula, TrueFormula } from "./predicate/formula.js"
29
30
  import { trueFormula } from "./predicate/runtime.js"
30
31
 
@@ -281,8 +282,57 @@ type JoinGroupingKeys<Keys extends readonly string[]> = Keys extends readonly []
281
282
  ? `${Head},${JoinGroupingKeys<Tail>}`
282
283
  : string
283
284
 
285
+ type EscapeGroupingBackslashes<Value extends string> = string extends Value
286
+ ? string
287
+ : Value extends `${infer Head}\\${infer Tail}`
288
+ ? `${Head}\\\\${EscapeGroupingBackslashes<Tail>}`
289
+ : Value
290
+
291
+ type EscapeGroupingCommas<Value extends string> = string extends Value
292
+ ? string
293
+ : Value extends `${infer Head},${infer Tail}`
294
+ ? `${Head}\\,${EscapeGroupingCommas<Tail>}`
295
+ : Value
296
+
297
+ type EscapeGroupingPipes<Value extends string> = string extends Value
298
+ ? string
299
+ : Value extends `${infer Head}|${infer Tail}`
300
+ ? `${Head}\\|${EscapeGroupingPipes<Tail>}`
301
+ : Value
302
+
303
+ type EscapeGroupingEquals<Value extends string> = string extends Value
304
+ ? string
305
+ : Value extends `${infer Head}=${infer Tail}`
306
+ ? `${Head}\\=${EscapeGroupingEquals<Tail>}`
307
+ : Value
308
+
309
+ type EscapeGroupingGreaterThan<Value extends string> = string extends Value
310
+ ? string
311
+ : Value extends `${infer Head}>${infer Tail}`
312
+ ? `${Head}\\>${EscapeGroupingGreaterThan<Tail>}`
313
+ : Value
314
+
315
+ type EscapeGroupingText<Value extends string> =
316
+ EscapeGroupingGreaterThan<
317
+ EscapeGroupingEquals<
318
+ EscapeGroupingPipes<
319
+ EscapeGroupingCommas<
320
+ EscapeGroupingBackslashes<Value>
321
+ >
322
+ >
323
+ >
324
+ >
325
+
326
+ type JsonStringKeysGroupingKey<Keys extends readonly string[]> = Keys extends readonly []
327
+ ? ""
328
+ : Keys extends readonly [infer Head extends string]
329
+ ? EscapeGroupingText<Head>
330
+ : Keys extends readonly [infer Head extends string, ...infer Tail extends readonly string[]]
331
+ ? `${EscapeGroupingText<Head>},${JsonStringKeysGroupingKey<Tail>}`
332
+ : string
333
+
284
334
  type JsonSegmentGroupingKey<Segment> =
285
- Segment extends JsonPath.KeySegment<infer Key extends string> ? `key:${Key}` :
335
+ Segment extends JsonPath.KeySegment<infer Key extends string> ? `key:${EscapeGroupingText<Key>}` :
286
336
  Segment extends JsonPath.IndexSegment<infer Index extends number> ? `index:${Index}` :
287
337
  Segment extends JsonPath.WildcardSegment ? "wildcard" :
288
338
  Segment extends JsonPath.SliceSegment<infer Start extends number | undefined, infer End extends number | undefined>
@@ -290,7 +340,7 @@ type JsonSegmentGroupingKey<Segment> =
290
340
  : Segment extends JsonPath.DescendSegment
291
341
  ? "descend"
292
342
  : Segment extends string
293
- ? `key:${Segment}`
343
+ ? `key:${EscapeGroupingText<Segment>}`
294
344
  : Segment extends number
295
345
  ? `index:${Segment}`
296
346
  : "unknown"
@@ -306,13 +356,13 @@ type JsonPathGroupingKey<Segments extends readonly any[]> = Segments extends rea
306
356
  type JsonOpaquePathGroupingKey<Value> =
307
357
  Value extends JsonPath.Path<infer Segments extends readonly JsonPath.CanonicalSegment[]>
308
358
  ? `jsonpath:${JsonPathGroupingKey<Segments>}` :
309
- Value extends string ? `jsonpath:${Value}` :
359
+ Value extends string ? `jsonpath:${EscapeGroupingText<Value>}` :
310
360
  Value extends Expression.Any ? `jsonpath:${GroupingKeyOfExpression<Value>}` :
311
361
  "jsonpath:unknown"
312
362
 
313
363
  type JsonEntryGroupingKey<Entry> =
314
364
  Entry extends { readonly key: infer Key extends string; readonly value: infer Value extends Expression.Any }
315
- ? `${Key}=>${GroupingKeyOfExpression<Value>}`
365
+ ? `${EscapeGroupingText<Key>}=>${GroupingKeyOfExpression<Value>}`
316
366
  : "entry:unknown"
317
367
 
318
368
  type JsonEntriesGroupingKey<Entries extends readonly { readonly key: string; readonly value: Expression.Any }[]> = Entries extends readonly []
@@ -338,13 +388,19 @@ type BranchGroupingKeys<
338
388
 
339
389
  type GroupingKeyOfAst<Ast extends ExpressionAst.Any> =
340
390
  Ast extends ExpressionAst.ColumnNode<infer TableName extends string, infer ColumnName extends string>
341
- ? `column:${TableName}.${ColumnName}`
391
+ ? `column:${ColumnKey<TableName, ColumnName>}`
342
392
  : Ast extends ExpressionAst.LiteralNode<infer Value>
343
393
  ? `literal:${LiteralGroupingKey<Value>}`
344
394
  : Ast extends ExpressionAst.ExcludedNode<infer ColumnName extends string>
345
395
  ? `excluded:${ColumnName}`
346
396
  : Ast extends ExpressionAst.CastNode<infer Value extends Expression.Any, infer Target extends Expression.DbType.Any>
347
397
  ? `cast(${GroupingKeyOfExpression<Value>} as ${Target["dialect"]}:${Target["kind"]})`
398
+ : Ast extends ExpressionAst.CollateNode<infer Value extends Expression.Any, infer Collation extends readonly [string, ...string[]]>
399
+ ? `collate(${GroupingKeyOfExpression<Value>},${JsonStringKeysGroupingKey<Collation>})`
400
+ : Ast extends ExpressionAst.FunctionCallNode<infer Name extends string, infer Args extends readonly Expression.Any[]>
401
+ ? `function(${EscapeGroupingText<Name>},${JoinGroupingKeys<{
402
+ readonly [K in keyof Args]: Args[K] extends Expression.Any ? GroupingKeyOfExpression<Args[K]> : never
403
+ } & readonly string[]>})`
348
404
  : Ast extends ExpressionAst.UnaryNode<infer Kind extends ExpressionAst.UnaryKind, infer Value extends Expression.Any>
349
405
  ? `${Kind}(${GroupingKeyOfExpression<Value>})`
350
406
  : Ast extends ExpressionAst.BinaryNode<infer Kind extends ExpressionAst.BinaryKind, infer Left extends Expression.Any, infer Right extends Expression.Any>
@@ -357,7 +413,7 @@ type GroupingKeyOfAst<Ast extends ExpressionAst.Any> =
357
413
  ? Kind extends "jsonGet" | "jsonPath" | "jsonAccess" | "jsonTraverse" | "jsonGetText" | "jsonPathText" | "jsonAccessText" | "jsonTraverseText"
358
414
  ? `json(${Kind},${GroupingKeyOfExpression<Extract<Ast["value"] | Ast["base"] | Ast["left"], Expression.Any>>},${JsonPathGroupingKey<Extract<Ast["segments"] | Ast["path"], readonly any[]>>})`
359
415
  : Kind extends "jsonHasKey" | "jsonKeyExists" | "jsonHasAnyKeys" | "jsonHasAllKeys"
360
- ? `json(${Kind},${GroupingKeyOfExpression<Extract<Ast["value"] | Ast["base"] | Ast["left"], Expression.Any>>},${JoinGroupingKeys<Extract<Ast["keys"], readonly string[]> & readonly string[]>})`
416
+ ? `json(${Kind},${GroupingKeyOfExpression<Extract<Ast["value"] | Ast["base"] | Ast["left"], Expression.Any>>},${JsonStringKeysGroupingKey<Extract<Ast["keys"], readonly string[]> & readonly string[]>})`
361
417
  : Kind extends "jsonConcat" | "jsonMerge" | "jsonDelete" | "jsonDeletePath" | "jsonRemove" | "jsonSet" | "jsonInsert"
362
418
  ? `json(${Kind},${GroupingKeyOfExpression<Extract<Ast["left"] | Ast["base"] | Ast["value"], Expression.Any>>},${GroupingKeyOfExpression<Extract<Ast["right"] | Ast["newValue"] | Ast["insert"], Expression.Any>>},${JsonPathGroupingKey<Extract<Ast["segments"] | Ast["path"], readonly any[]>>})`
363
419
  : Kind extends "jsonPathExists" | "jsonPathMatch"
@@ -1000,6 +1056,138 @@ type JoinPath<Segments extends readonly string[]> = Segments extends readonly []
1000
1056
  ? `${Head}__${JoinPath<Tail>}`
1001
1057
  : string
1002
1058
 
1059
+ type ProjectionAliasOf<
1060
+ Value extends Expression.Any,
1061
+ Path extends readonly string[]
1062
+ > = Value extends {
1063
+ readonly [ProjectionAlias.TypeId]: ProjectionAlias.State<infer Alias extends string>
1064
+ } ? Alias : JoinPath<Path>
1065
+
1066
+ type SelectionProjectionEntry<
1067
+ Alias extends string,
1068
+ Path extends readonly string[],
1069
+ ExpectedAlias extends string
1070
+ > = {
1071
+ readonly alias: Alias
1072
+ readonly path: Path
1073
+ readonly expectedAlias: ExpectedAlias
1074
+ }
1075
+
1076
+ type SelectionProjectionEntries<
1077
+ Selection,
1078
+ Path extends readonly string[] = []
1079
+ > = Selection extends Expression.Any
1080
+ ? SelectionProjectionEntry<ProjectionAliasOf<Selection, Path>, Path, JoinPath<Path>>
1081
+ : Selection extends Record<string, any>
1082
+ ? {
1083
+ readonly [K in Extract<keyof Selection, string>]:
1084
+ SelectionProjectionEntries<Selection[K], [...Path, K]>
1085
+ }[Extract<keyof Selection, string>]
1086
+ : never
1087
+
1088
+ type SameProjectionPath<
1089
+ Left extends readonly string[],
1090
+ Right extends readonly string[]
1091
+ > = Left extends readonly []
1092
+ ? Right extends readonly [] ? true : false
1093
+ : Left extends readonly [infer LeftHead extends string, ...infer LeftTail extends readonly string[]]
1094
+ ? Right extends readonly [infer RightHead extends string, ...infer RightTail extends readonly string[]]
1095
+ ? [LeftHead] extends [RightHead]
1096
+ ? [RightHead] extends [LeftHead]
1097
+ ? SameProjectionPath<LeftTail, RightTail>
1098
+ : false
1099
+ : false
1100
+ : false
1101
+ : false
1102
+
1103
+ type SameProjectionAlias<
1104
+ Left extends string,
1105
+ Right extends string
1106
+ > = [Left] extends [Right] ? [Right] extends [Left] ? true : false : false
1107
+
1108
+ type HasDifferentPathForAlias<
1109
+ Entries,
1110
+ Alias extends string,
1111
+ Path extends readonly string[]
1112
+ > = Entries extends SelectionProjectionEntry<infer EntryAlias, infer EntryPath, any>
1113
+ ? SameProjectionAlias<EntryAlias, Alias> extends true
1114
+ ? SameProjectionPath<EntryPath, Path> extends true ? never : true
1115
+ : never
1116
+ : never
1117
+
1118
+ type DuplicateProjectionAliases<
1119
+ Entries,
1120
+ AllEntries = Entries
1121
+ > = Entries extends SelectionProjectionEntry<infer Alias, infer Path, any>
1122
+ ? string extends Alias ? never
1123
+ : true extends HasDifferentPathForAlias<AllEntries, Alias, Path> ? Alias : never
1124
+ : never
1125
+
1126
+ type ProjectionAliasMismatches<Entries> =
1127
+ Entries extends SelectionProjectionEntry<infer Alias, any, infer ExpectedAlias>
1128
+ ? string extends Alias ? never
1129
+ : string extends ExpectedAlias ? never
1130
+ : SameProjectionAlias<Alias, ExpectedAlias> extends true ? never : Alias
1131
+ : never
1132
+
1133
+ type DerivedProjectionDuplicateAliases<Selection> =
1134
+ DuplicateProjectionAliases<SelectionProjectionEntries<Selection>>
1135
+
1136
+ type DerivedProjectionAliasMismatches<Selection> =
1137
+ ProjectionAliasMismatches<SelectionProjectionEntries<Selection>>
1138
+
1139
+ type IsAny<Value> = 0 extends (1 & Value) ? true : false
1140
+
1141
+ type IsBroadSelection<Selection> =
1142
+ IsAny<Selection> extends true ? true :
1143
+ unknown extends Selection ? true : false
1144
+
1145
+ type DerivedProjectionIssues<Selection> =
1146
+ IsBroadSelection<Selection> extends true
1147
+ ? never
1148
+ : | DerivedProjectionDuplicateAliases<Selection>
1149
+ | DerivedProjectionAliasMismatches<Selection>
1150
+
1151
+ export type DerivedSourceProjectionCompatibilityError<
1152
+ PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
1153
+ > = PlanValue & {
1154
+ readonly __effect_qb_error__: "effect-qb: derived subqueries require unique path-based projection aliases"
1155
+ readonly __effect_qb_duplicate_projection_aliases__: DerivedProjectionDuplicateAliases<SelectionOfPlan<PlanValue>>
1156
+ readonly __effect_qb_alias_mismatches__: DerivedProjectionAliasMismatches<SelectionOfPlan<PlanValue>>
1157
+ readonly __effect_qb_hint__: "Use unique nested selection paths and do not override projection aliases inside derived subqueries"
1158
+ }
1159
+
1160
+ export type DerivedProjectionCompatiblePlan<
1161
+ PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>,
1162
+ ValidPlan = PlanValue
1163
+ > = [DerivedProjectionIssues<SelectionOfPlan<PlanValue>>] extends [never]
1164
+ ? ValidPlan
1165
+ : ValidPlan & DerivedSourceProjectionCompatibilityError<PlanValue>
1166
+
1167
+ export type DerivedSourceCompatiblePlan<
1168
+ PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
1169
+ > = DerivedProjectionCompatiblePlan<PlanValue, CompletePlan<PlanValue>>
1170
+
1171
+ type InlineSourceStatementError<
1172
+ PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
1173
+ > = PlanValue & {
1174
+ readonly __effect_qb_error__: "effect-qb: inline derived sources only accept select-like query plans"
1175
+ readonly __effect_qb_statement__: StatementOfPlan<PlanValue>
1176
+ readonly __effect_qb_hint__: "Use select(...) or a set operator for as(...) and lateral(...); use with(...) for data-modifying CTEs where supported"
1177
+ }
1178
+
1179
+ export type DerivedTableCompatiblePlan<
1180
+ PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
1181
+ > = StatementOfPlan<PlanValue> extends SelectLikeStatement
1182
+ ? DerivedSourceCompatiblePlan<PlanValue>
1183
+ : InlineSourceStatementError<PlanValue>
1184
+
1185
+ export type LateralSourceCompatiblePlan<
1186
+ PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
1187
+ > = StatementOfPlan<PlanValue> extends SelectLikeStatement
1188
+ ? DerivedProjectionCompatiblePlan<PlanValue>
1189
+ : InlineSourceStatementError<PlanValue>
1190
+
1003
1191
  type DerivedLeafExpression<
1004
1192
  Value extends Expression.Any,
1005
1193
  Alias extends string,
@@ -1085,6 +1273,12 @@ export type AssumptionsOfPlan<
1085
1273
  export type FactsOfPlan<
1086
1274
  PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any, any, any>
1087
1275
  > = QueryPlanState<PlanValue>["facts"]
1276
+ export type CommonSetFacts<
1277
+ Left extends QueryPlan<any, any, any, any, any, any, any, any, any, any>,
1278
+ Right extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
1279
+ > = [FactsOfPlan<Left>] extends [FactsOfPlan<Right>]
1280
+ ? [FactsOfPlan<Right>] extends [FactsOfPlan<Left>] ? FactsOfPlan<Left> : EmptyFacts
1281
+ : EmptyFacts
1088
1282
  export type PredicateStateOfPlan<
1089
1283
  PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any, any, any>
1090
1284
  > = PredicateState<AssumptionsOfPlan<PlanValue>, FactsOfPlan<PlanValue>>
@@ -1217,9 +1411,10 @@ export type AddJoinRequired<
1217
1411
  Available extends Record<string, RowSet.AnySource>,
1218
1412
  JoinedName extends string,
1219
1413
  Predicate extends PredicateInput | never,
1220
- Kind extends QueryAst.JoinKind = "inner"
1414
+ Kind extends QueryAst.JoinKind = "inner",
1415
+ SourceRequired extends string = never
1221
1416
  > = Exclude<
1222
- Required | (Predicate extends never ? never : RequiredFromInput<Predicate>),
1417
+ Required | SourceRequired | (Predicate extends never ? never : RequiredFromInput<Predicate>),
1223
1418
  AvailableNames<AvailableAfterJoin<Available, JoinedName, Kind>>
1224
1419
  >
1225
1420
 
@@ -1744,30 +1939,45 @@ type JsonLiteralSetsForColumn<
1744
1939
  ? Paths
1745
1940
  : {}
1746
1941
 
1942
+ type JsonPathHead<
1943
+ Path extends string,
1944
+ Current extends string = ""
1945
+ > = Path extends `\\.${infer Rest}`
1946
+ ? JsonPathHead<Rest, `${Current}.`>
1947
+ : Path extends `\\\\${infer Rest}`
1948
+ ? JsonPathHead<Rest, `${Current}\\`>
1949
+ : Path extends `.${infer Tail}`
1950
+ ? readonly [Current, Tail]
1951
+ : Path extends `${infer Character}${infer Rest}`
1952
+ ? JsonPathHead<Rest, `${Current}${Character}`>
1953
+ : readonly [Current, ""]
1954
+
1747
1955
  type RefineJsonRuntimeAtPath<
1748
1956
  Runtime,
1749
1957
  Path extends string,
1750
1958
  Values
1751
1959
  > = Runtime extends unknown
1752
- ? Path extends `${infer Head}.${infer Tail}`
1753
- ? Runtime extends object
1754
- ? Head extends keyof Runtime
1755
- ? RefineJsonRuntimeAtPath<NonNullable<Runtime[Head]>, Tail, Values> extends infer Refined
1756
- ? [Refined] extends [never]
1757
- ? never
1758
- : Omit<Runtime, Head> & { readonly [K in Head]: Refined }
1960
+ ? JsonPathHead<Path> extends readonly [infer Head extends string, infer Tail extends string]
1961
+ ? Tail extends ""
1962
+ ? Runtime extends object
1963
+ ? Head extends keyof Runtime
1964
+ ? RefineRuntimeByAllowedLiterals<NonNullable<Runtime[Head]>, Values> extends infer Refined
1965
+ ? [Refined] extends [never]
1966
+ ? never
1967
+ : Omit<Runtime, Head> & { readonly [K in Head]: Refined }
1968
+ : never
1759
1969
  : never
1760
- : never
1761
- : never
1762
- : Runtime extends object
1763
- ? Path extends keyof Runtime
1764
- ? RefineRuntimeByAllowedLiterals<NonNullable<Runtime[Path]>, Values> extends infer Refined
1765
- ? [Refined] extends [never]
1766
- ? never
1767
- : Omit<Runtime, Path> & { readonly [K in Path]: Refined }
1970
+ : RefineRuntimeByAllowedLiterals<NonNullable<Runtime>, Values>
1971
+ : Runtime extends object
1972
+ ? Head extends keyof Runtime
1973
+ ? RefineJsonRuntimeAtPath<NonNullable<Runtime[Head]>, Tail, Values> extends infer Refined
1974
+ ? [Refined] extends [never]
1975
+ ? never
1976
+ : Omit<Runtime, Head> & { readonly [K in Head]: Refined }
1977
+ : never
1768
1978
  : never
1769
1979
  : never
1770
- : RefineRuntimeByAllowedLiterals<NonNullable<Runtime>, Values>
1980
+ : never
1771
1981
  : never
1772
1982
 
1773
1983
  type RefineJsonRuntimeWithConstraints<
@@ -1965,9 +2175,9 @@ type IsDialectCompatible<
1965
2175
  EngineDialect extends string
1966
2176
  > = [PlanDialect] extends [never]
1967
2177
  ? true
1968
- : Extract<PlanDialect, EngineDialect> extends never
1969
- ? false
1970
- : true
2178
+ : Exclude<PlanDialect, EngineDialect> extends never
2179
+ ? true
2180
+ : false
1971
2181
 
1972
2182
  /** Narrows a complete plan to those compatible with a target engine dialect. */
1973
2183
  export type DialectCompatiblePlan<
@@ -1977,15 +2187,27 @@ export type DialectCompatiblePlan<
1977
2187
  ? CompletePlan<PlanValue>
1978
2188
  : DialectCompatibilityError<PlanValue, EngineDialect>
1979
2189
 
2190
+ type SelectLikeStatement = "select" | "set"
2191
+
2192
+ type NestedPlanStatementError<
2193
+ PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>
2194
+ > = PlanValue & {
2195
+ readonly __effect_qb_error__: "effect-qb: subquery expressions only accept select-like query plans"
2196
+ readonly __effect_qb_statement__: StatementOfPlan<PlanValue>
2197
+ readonly __effect_qb_hint__: "Use select(...) or a set operator as the nested subquery expression"
2198
+ }
2199
+
1980
2200
  /** Nested-plan compatibility used by subquery expressions such as `exists(...)`. */
1981
2201
  export type DialectCompatibleNestedPlan<
1982
2202
  PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>,
1983
2203
  EngineDialect extends string
1984
2204
  > = IsDialectCompatible<PlanValue[typeof RowSet.TypeId]["dialect"], EngineDialect> extends true
1985
- ? AggregationCompatiblePlan<PlanValue>
2205
+ ? StatementOfPlan<PlanValue> extends SelectLikeStatement
2206
+ ? AggregationCompatiblePlan<PlanValue>
2207
+ : NestedPlanStatementError<PlanValue>
1986
2208
  : DialectCompatibilityError<PlanValue, EngineDialect>
1987
2209
 
1988
- type SetOperandStatement = "select" | "set"
2210
+ type SetOperandStatement = SelectLikeStatement
1989
2211
  type IsUnion<Value, All = Value> = Value extends any ? ([All] extends [Value] ? false : true) : never
1990
2212
 
1991
2213
  type SingleSelectedExpressionError<
@@ -1,6 +1,7 @@
1
1
  import * as Query from "./query.js"
2
2
  import type * as Expression from "./scalar.js"
3
- import { type Projection, validateProjections } from "./projections.js"
3
+ import { flattenSelection, type Projection, validateProjections } from "./projections.js"
4
+ import * as Plan from "./row-set.js"
4
5
 
5
6
  /** Symbol used to attach rendered-query phantom row metadata. */
6
7
  export const TypeId: unique symbol = Symbol.for("effect-qb/Renderer")
@@ -44,7 +45,7 @@ export interface Renderer<Dialect extends string = string> {
44
45
  readonly dialect: Dialect
45
46
  render<PlanValue extends Query.Plan.Any>(
46
47
  plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
47
- ): RenderedQuery<any, Dialect>
48
+ ): RenderedQuery<Query.ResultRow<PlanValue>, Dialect>
48
49
  }
49
50
 
50
51
  type CustomRender<Dialect extends string> = <PlanValue extends Query.Plan.Any>(
@@ -56,6 +57,29 @@ type CustomRender<Dialect extends string> = <PlanValue extends Query.Plan.Any>(
56
57
  readonly valueMappings?: Expression.DriverValueMappings
57
58
  }
58
59
 
60
+ const projectionPathKey = (path: readonly string[]): string => JSON.stringify(path)
61
+
62
+ const formatProjectionPath = (path: readonly string[]): string => path.join(".")
63
+
64
+ const validateProjectionPathsMatchSelection = (
65
+ plan: Query.Plan.Any,
66
+ projections: readonly Projection[]
67
+ ): void => {
68
+ const expected = flattenSelection(Query.getAst(plan).select as Record<string, unknown>)
69
+ const expectedPaths = new Set(expected.map((projection) => projectionPathKey(projection.path)))
70
+ const actualPaths = new Set(projections.map((projection) => projectionPathKey(projection.path)))
71
+ for (const projection of projections) {
72
+ if (!expectedPaths.has(projectionPathKey(projection.path))) {
73
+ throw new Error(`Projection path ${formatProjectionPath(projection.path)} does not exist in the query selection`)
74
+ }
75
+ }
76
+ for (const projection of expected) {
77
+ if (!actualPaths.has(projectionPathKey(projection.path))) {
78
+ throw new Error(`Projection path ${formatProjectionPath(projection.path)} is missing from rendered projections`)
79
+ }
80
+ }
81
+ }
82
+
59
83
  /**
60
84
  * Constructs a renderer from a dialect and implementation callback.
61
85
  */
@@ -73,9 +97,18 @@ export function make<Dialect extends string>(
73
97
  return {
74
98
  dialect,
75
99
  render(plan) {
100
+ const required = Query.currentRequiredList(plan[Plan.TypeId].required)
101
+ if (required.length > 0) {
102
+ throw new Error(`query references sources that are not yet in scope: ${required.join(", ")}`)
103
+ }
104
+ const planDialect = plan[Plan.TypeId].dialect
105
+ if (planDialect !== dialect) {
106
+ throw new Error("effect-qb: plan dialect is not compatible with the target renderer or executor")
107
+ }
76
108
  const rendered = render(plan)
77
109
  const projections = rendered.projections ?? []
78
110
  validateProjections(projections)
111
+ validateProjectionPathsMatchSelection(plan as Query.Plan.Any, projections)
79
112
  return {
80
113
  sql: rendered.sql,
81
114
  params: rendered.params ?? [],
@@ -84,6 +84,26 @@ const findMapping = <Key extends MappingKey>(
84
84
  return undefined
85
85
  }
86
86
 
87
+ const isJsonDbType = (dbType: Expression.DbType.Any | undefined): boolean => {
88
+ if (dbType === undefined) {
89
+ return false
90
+ }
91
+ if ("base" in dbType) {
92
+ return isJsonDbType(dbType.base)
93
+ }
94
+ if (!("variant" in dbType)) {
95
+ return false
96
+ }
97
+ const variant = dbType.variant as string
98
+ return variant === "json" || variant === "jsonb"
99
+ }
100
+
101
+ const schemaAccepts = (
102
+ schema: Schema.Schema.Any | undefined,
103
+ value: unknown
104
+ ): boolean =>
105
+ schema !== undefined && (Schema.is(schema) as (candidate: unknown) => boolean)(value)
106
+
87
107
  const encodeWithSchema = (
88
108
  schema: Schema.Schema.Any | undefined,
89
109
  value: unknown
@@ -100,6 +120,32 @@ const encodeWithSchema = (
100
120
  }
101
121
  }
102
122
 
123
+ const normalizeJsonDriverString = (
124
+ value: string,
125
+ context: DriverValueContext
126
+ ): unknown | undefined => {
127
+ if (!isJsonDbType(context.dbType) || context.runtimeSchema === undefined) {
128
+ return undefined
129
+ }
130
+ try {
131
+ const parsed = JSON.parse(value)
132
+ if (value.trimStart().startsWith("\"") && schemaAccepts(context.runtimeSchema, parsed)) {
133
+ return parsed
134
+ }
135
+ if (schemaAccepts(context.runtimeSchema, value) && !schemaAccepts(context.runtimeSchema, parsed)) {
136
+ return value
137
+ }
138
+ } catch (error) {
139
+ if (error instanceof SyntaxError && schemaAccepts(context.runtimeSchema, value)) {
140
+ return value
141
+ }
142
+ if (!(error instanceof SyntaxError)) {
143
+ throw error
144
+ }
145
+ }
146
+ return undefined
147
+ }
148
+
103
149
  export const toDriverValue = (
104
150
  value: unknown,
105
151
  context: DriverValueContext
@@ -107,6 +153,9 @@ export const toDriverValue = (
107
153
  if (value === null) {
108
154
  return null
109
155
  }
156
+ if (value instanceof Date && Number.isNaN(value.getTime())) {
157
+ throw new Error("Expected a valid Date value")
158
+ }
110
159
  const dbType = context.dbType
111
160
  const encoded = encodeWithSchema(context.runtimeSchema, value)
112
161
  let current = encoded.value
@@ -114,6 +163,9 @@ export const toDriverValue = (
114
163
  if (custom !== undefined && dbType !== undefined) {
115
164
  return custom(current, dbType)
116
165
  }
166
+ if (encoded.encoded && typeof current === "string" && isJsonDbType(dbType)) {
167
+ return current
168
+ }
117
169
  return dbType === undefined || !encoded.encoded
118
170
  ? current
119
171
  : normalizeDbValue(dbType, current)
@@ -131,6 +183,12 @@ export const fromDriverValue = (
131
183
  if (custom !== undefined && dbType !== undefined) {
132
184
  return custom(value, dbType)
133
185
  }
186
+ if (typeof value === "string") {
187
+ const normalizedJsonString = normalizeJsonDriverString(value, context)
188
+ if (normalizedJsonString !== undefined) {
189
+ return normalizedJsonString
190
+ }
191
+ }
134
192
  return dbType === undefined
135
193
  ? value
136
194
  : normalizeDbValue(dbType, value)