effect-qb 0.15.0 → 0.16.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/dist/mysql.js +362 -70
- package/dist/postgres/metadata.js +557 -27
- package/dist/postgres.js +5028 -4732
- package/package.json +2 -2
- package/src/internal/column-state.ts +7 -0
- package/src/internal/column.ts +22 -0
- package/src/internal/dialect.ts +12 -1
- package/src/internal/executor.ts +15 -4
- package/src/internal/predicate/analysis.ts +103 -1
- package/src/internal/predicate/atom.ts +7 -0
- package/src/internal/predicate/context.ts +156 -16
- package/src/internal/predicate/key.ts +46 -1
- package/src/internal/predicate/normalize.ts +115 -34
- package/src/internal/predicate/runtime.ts +118 -11
- package/src/internal/query.ts +328 -90
- package/src/internal/renderer.ts +4 -0
- package/src/internal/runtime/driver-value-mapping.ts +186 -0
- package/src/internal/scalar.ts +11 -0
- package/src/mysql/column.ts +1 -0
- package/src/mysql/executor.ts +20 -5
- package/src/mysql/internal/dialect.ts +12 -6
- package/src/mysql/internal/dsl.ts +268 -54
- package/src/mysql/internal/renderer.ts +11 -2
- package/src/mysql/internal/sql-expression-renderer.ts +54 -8
- package/src/mysql/renderer.ts +7 -2
- package/src/postgres/cast.ts +22 -7
- package/src/postgres/column.ts +1 -0
- package/src/postgres/executor.ts +20 -5
- package/src/postgres/internal/dialect.ts +12 -6
- package/src/postgres/internal/dsl.ts +285 -58
- package/src/postgres/internal/renderer.ts +11 -2
- package/src/postgres/internal/sql-expression-renderer.ts +60 -8
- package/src/postgres/renderer.ts +7 -2
- package/src/postgres/type.ts +4 -0
package/src/internal/renderer.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as Query from "./query.js"
|
|
2
|
+
import type * as Expression from "./scalar.js"
|
|
2
3
|
import { type Projection, validateProjections } from "./projections.js"
|
|
3
4
|
|
|
4
5
|
/** Symbol used to attach rendered-query phantom row metadata. */
|
|
@@ -21,6 +22,7 @@ export interface RenderedQuery<Row, Dialect extends string = string> {
|
|
|
21
22
|
readonly params: readonly unknown[]
|
|
22
23
|
readonly dialect: Dialect
|
|
23
24
|
readonly projections: readonly Projection[]
|
|
25
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
24
26
|
readonly [TypeId]: {
|
|
25
27
|
readonly row: Row
|
|
26
28
|
readonly dialect: Dialect
|
|
@@ -51,6 +53,7 @@ type CustomRender<Dialect extends string> = <PlanValue extends Query.Plan.Any>(
|
|
|
51
53
|
readonly sql: string
|
|
52
54
|
readonly params?: readonly unknown[]
|
|
53
55
|
readonly projections?: readonly Projection[]
|
|
56
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
/**
|
|
@@ -77,6 +80,7 @@ export function make<Dialect extends string>(
|
|
|
77
80
|
sql: rendered.sql,
|
|
78
81
|
params: rendered.params ?? [],
|
|
79
82
|
projections,
|
|
83
|
+
valueMappings: rendered.valueMappings,
|
|
80
84
|
dialect,
|
|
81
85
|
[TypeId]: {
|
|
82
86
|
row: undefined as any,
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema"
|
|
2
|
+
|
|
3
|
+
import type * as Expression from "../scalar.js"
|
|
4
|
+
import { normalizeDbValue } from "./normalize.js"
|
|
5
|
+
|
|
6
|
+
export type DriverValueMapping = Expression.DriverValueMapping
|
|
7
|
+
export type DriverValueMappings = Expression.DriverValueMappings
|
|
8
|
+
|
|
9
|
+
export interface DriverValueContext {
|
|
10
|
+
readonly dialect?: string
|
|
11
|
+
readonly dbType?: Expression.DbType.Any
|
|
12
|
+
readonly runtimeSchema?: Schema.Schema.Any
|
|
13
|
+
readonly driverValueMapping?: DriverValueMapping
|
|
14
|
+
readonly valueMappings?: DriverValueMappings
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type MappingKey =
|
|
18
|
+
| "fromDriver"
|
|
19
|
+
| "toDriver"
|
|
20
|
+
| "selectSql"
|
|
21
|
+
| "jsonSelectSql"
|
|
22
|
+
|
|
23
|
+
const runtimeTagOfDbType = (
|
|
24
|
+
dbType: Expression.DbType.Any | undefined
|
|
25
|
+
): string | undefined => {
|
|
26
|
+
if (dbType === undefined) {
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
if ("base" in dbType) {
|
|
30
|
+
return runtimeTagOfDbType(dbType.base)
|
|
31
|
+
}
|
|
32
|
+
if ("element" in dbType) {
|
|
33
|
+
return "array"
|
|
34
|
+
}
|
|
35
|
+
if ("fields" in dbType) {
|
|
36
|
+
return "record"
|
|
37
|
+
}
|
|
38
|
+
if ("variant" in dbType && dbType.variant === "json") {
|
|
39
|
+
return "json"
|
|
40
|
+
}
|
|
41
|
+
if ("variant" in dbType && (dbType.variant === "enum" || dbType.variant === "set")) {
|
|
42
|
+
return "string"
|
|
43
|
+
}
|
|
44
|
+
return dbType.runtime
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const familyOfDbType = (
|
|
48
|
+
dbType: Expression.DbType.Any | undefined
|
|
49
|
+
): string | undefined => {
|
|
50
|
+
if (dbType === undefined) {
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
if ("base" in dbType) {
|
|
54
|
+
return familyOfDbType(dbType.base)
|
|
55
|
+
}
|
|
56
|
+
return dbType.family
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const mappingCandidates = (
|
|
60
|
+
context: DriverValueContext
|
|
61
|
+
): readonly (DriverValueMapping | undefined)[] => {
|
|
62
|
+
const dbType = context.dbType
|
|
63
|
+
const runtimeTag = runtimeTagOfDbType(dbType)
|
|
64
|
+
const family = familyOfDbType(dbType)
|
|
65
|
+
return [
|
|
66
|
+
context.driverValueMapping,
|
|
67
|
+
dbType?.driverValueMapping,
|
|
68
|
+
dbType === undefined ? undefined : context.valueMappings?.[dbType.kind],
|
|
69
|
+
family === undefined ? undefined : context.valueMappings?.[family],
|
|
70
|
+
runtimeTag === undefined ? undefined : context.valueMappings?.[runtimeTag]
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const findMapping = <Key extends MappingKey>(
|
|
75
|
+
context: DriverValueContext,
|
|
76
|
+
key: Key
|
|
77
|
+
): NonNullable<DriverValueMapping[Key]> | undefined => {
|
|
78
|
+
for (const candidate of mappingCandidates(context)) {
|
|
79
|
+
const value = candidate?.[key]
|
|
80
|
+
if (value !== undefined) {
|
|
81
|
+
return value as NonNullable<DriverValueMapping[Key]>
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const encodeWithSchema = (
|
|
88
|
+
schema: Schema.Schema.Any | undefined,
|
|
89
|
+
value: unknown
|
|
90
|
+
): { readonly value: unknown; readonly encoded: boolean } => {
|
|
91
|
+
if (schema === undefined) {
|
|
92
|
+
return { value, encoded: false }
|
|
93
|
+
}
|
|
94
|
+
if (!(Schema.is(schema) as (value: unknown) => boolean)(value)) {
|
|
95
|
+
return { value, encoded: false }
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
value: (Schema.encodeUnknownSync as any)(schema)(value),
|
|
99
|
+
encoded: true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const toDriverValue = (
|
|
104
|
+
value: unknown,
|
|
105
|
+
context: DriverValueContext
|
|
106
|
+
): unknown => {
|
|
107
|
+
if (value === null) {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
const dbType = context.dbType
|
|
111
|
+
const encoded = encodeWithSchema(context.runtimeSchema, value)
|
|
112
|
+
let current = encoded.value
|
|
113
|
+
const custom = findMapping(context, "toDriver")
|
|
114
|
+
if (custom !== undefined && dbType !== undefined) {
|
|
115
|
+
return custom(current, dbType)
|
|
116
|
+
}
|
|
117
|
+
return dbType === undefined || !encoded.encoded
|
|
118
|
+
? current
|
|
119
|
+
: normalizeDbValue(dbType, current)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const fromDriverValue = (
|
|
123
|
+
value: unknown,
|
|
124
|
+
context: DriverValueContext
|
|
125
|
+
): unknown => {
|
|
126
|
+
if (value === null) {
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
const dbType = context.dbType
|
|
130
|
+
const custom = findMapping(context, "fromDriver")
|
|
131
|
+
if (custom !== undefined && dbType !== undefined) {
|
|
132
|
+
return custom(value, dbType)
|
|
133
|
+
}
|
|
134
|
+
return dbType === undefined
|
|
135
|
+
? value
|
|
136
|
+
: normalizeDbValue(dbType, value)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const textCast = (sql: string): string => `(${sql})::text`
|
|
140
|
+
|
|
141
|
+
const postgresJsonSql = (
|
|
142
|
+
sql: string,
|
|
143
|
+
dbType: Expression.DbType.Any
|
|
144
|
+
): string => {
|
|
145
|
+
const runtimeTag = runtimeTagOfDbType(dbType)
|
|
146
|
+
switch (runtimeTag) {
|
|
147
|
+
case "bigintString":
|
|
148
|
+
case "decimalString":
|
|
149
|
+
case "localDate":
|
|
150
|
+
case "localTime":
|
|
151
|
+
case "offsetTime":
|
|
152
|
+
case "localDateTime":
|
|
153
|
+
case "instant":
|
|
154
|
+
case "year":
|
|
155
|
+
return textCast(sql)
|
|
156
|
+
case "bytes":
|
|
157
|
+
return `encode(${sql}, 'base64')`
|
|
158
|
+
default:
|
|
159
|
+
return sql
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const renderSelectSql = (
|
|
164
|
+
sql: string,
|
|
165
|
+
context: DriverValueContext
|
|
166
|
+
): string => {
|
|
167
|
+
const dbType = context.dbType
|
|
168
|
+
const custom = findMapping(context, "selectSql")
|
|
169
|
+
return custom !== undefined && dbType !== undefined
|
|
170
|
+
? custom(sql, dbType)
|
|
171
|
+
: sql
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const renderJsonSelectSql = (
|
|
175
|
+
sql: string,
|
|
176
|
+
context: DriverValueContext
|
|
177
|
+
): string => {
|
|
178
|
+
const dbType = context.dbType
|
|
179
|
+
const custom = findMapping(context, "jsonSelectSql")
|
|
180
|
+
if (custom !== undefined && dbType !== undefined) {
|
|
181
|
+
return custom(sql, dbType)
|
|
182
|
+
}
|
|
183
|
+
return context.dialect === "postgres" && dbType !== undefined
|
|
184
|
+
? postgresJsonSql(sql, dbType)
|
|
185
|
+
: sql
|
|
186
|
+
}
|
package/src/internal/scalar.ts
CHANGED
|
@@ -51,6 +51,7 @@ export declare namespace DbType {
|
|
|
51
51
|
readonly compareGroup?: string
|
|
52
52
|
readonly castTargets?: readonly string[]
|
|
53
53
|
readonly traits?: DatatypeTraits
|
|
54
|
+
readonly driverValueMapping?: DriverValueMapping
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
/** JSON-like database type. */
|
|
@@ -135,6 +136,15 @@ export declare namespace DbType {
|
|
|
135
136
|
| Set<string, string>
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
export interface DriverValueMapping {
|
|
140
|
+
readonly fromDriver?: (value: unknown, dbType: DbType.Any) => unknown
|
|
141
|
+
readonly toDriver?: (value: unknown, dbType: DbType.Any) => unknown
|
|
142
|
+
readonly selectSql?: (sql: string, dbType: DbType.Any) => string
|
|
143
|
+
readonly jsonSelectSql?: (sql: string, dbType: DbType.Any) => string
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type DriverValueMappings = Readonly<Record<string, DriverValueMapping | undefined>>
|
|
147
|
+
|
|
138
148
|
/** Canonical static metadata stored on an expression. */
|
|
139
149
|
export interface State<
|
|
140
150
|
Runtime,
|
|
@@ -147,6 +157,7 @@ export interface State<
|
|
|
147
157
|
readonly runtime: Runtime
|
|
148
158
|
readonly dbType: Db
|
|
149
159
|
readonly runtimeSchema?: Schema.Schema.Any
|
|
160
|
+
readonly driverValueMapping?: DriverValueMapping
|
|
150
161
|
readonly nullability: Nullable
|
|
151
162
|
readonly dialect: Dialect
|
|
152
163
|
readonly kind: Kind
|
package/src/mysql/column.ts
CHANGED
|
@@ -100,6 +100,7 @@ export const primaryKey = BaseColumn.primaryKey
|
|
|
100
100
|
export const unique = BaseColumn.unique
|
|
101
101
|
const default_ = BaseColumn.default_
|
|
102
102
|
export const generated = BaseColumn.generated
|
|
103
|
+
export const driverValueMapping = BaseColumn.driverValueMapping
|
|
103
104
|
export const references = BaseColumn.references
|
|
104
105
|
export const schema = BaseColumn.schema
|
|
105
106
|
export { default_ as default }
|
package/src/mysql/executor.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as Stream from "effect/Stream"
|
|
|
5
5
|
import * as CoreExecutor from "../internal/executor.js"
|
|
6
6
|
import * as CoreQuery from "../internal/query.js"
|
|
7
7
|
import * as CoreRenderer from "../internal/renderer.js"
|
|
8
|
+
import type * as Expression from "../internal/scalar.js"
|
|
8
9
|
import { renderMysqlPlan } from "./internal/renderer.js"
|
|
9
10
|
import {
|
|
10
11
|
narrowMysqlDriverErrorForReadQuery,
|
|
@@ -28,6 +29,7 @@ export interface MakeOptions<Error = never, Context = never> {
|
|
|
28
29
|
readonly renderer?: Renderer
|
|
29
30
|
readonly driver?: Driver<Error, Context>
|
|
30
31
|
readonly driverMode?: CoreExecutor.DriverMode
|
|
32
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
31
33
|
}
|
|
32
34
|
/** Standard composed error shape for MySQL executors. */
|
|
33
35
|
export type MysqlExecutorError = MysqlDriverError | RowDecodeError
|
|
@@ -101,7 +103,8 @@ const fromDriver = <
|
|
|
101
103
|
>(
|
|
102
104
|
renderer: Renderer,
|
|
103
105
|
sqlDriver: Driver<Error, Context>,
|
|
104
|
-
driverMode: CoreExecutor.DriverMode = "raw"
|
|
106
|
+
driverMode: CoreExecutor.DriverMode = "raw",
|
|
107
|
+
valueMappings?: Expression.DriverValueMappings
|
|
105
108
|
): QueryExecutor<Context> => ({
|
|
106
109
|
dialect: "mysql",
|
|
107
110
|
execute(plan) {
|
|
@@ -110,7 +113,7 @@ const fromDriver = <
|
|
|
110
113
|
Effect.flatMap(
|
|
111
114
|
sqlDriver.execute(rendered),
|
|
112
115
|
(rows) => Effect.try({
|
|
113
|
-
try: () => CoreExecutor.decodeRows(rendered, plan, rows, { driverMode }),
|
|
116
|
+
try: () => CoreExecutor.decodeRows(rendered, plan, rows, { driverMode, valueMappings }),
|
|
114
117
|
catch: (error) => error as RowDecodeError
|
|
115
118
|
})
|
|
116
119
|
),
|
|
@@ -131,7 +134,7 @@ const fromDriver = <
|
|
|
131
134
|
Stream.mapChunksEffect(
|
|
132
135
|
sqlDriver.stream(rendered),
|
|
133
136
|
(rows) => Effect.try({
|
|
134
|
-
try: () => CoreExecutor.decodeChunk(rendered, plan, rows, { driverMode }),
|
|
137
|
+
try: () => CoreExecutor.decodeChunk(rendered, plan, rows, { driverMode, valueMappings }),
|
|
135
138
|
catch: (error) => error as RowDecodeError
|
|
136
139
|
})
|
|
137
140
|
),
|
|
@@ -169,6 +172,7 @@ export function make(
|
|
|
169
172
|
options: {
|
|
170
173
|
readonly renderer?: Renderer
|
|
171
174
|
readonly driverMode?: CoreExecutor.DriverMode
|
|
175
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
172
176
|
}
|
|
173
177
|
): QueryExecutor<SqlClient.SqlClient>
|
|
174
178
|
export function make<Error = never, Context = never>(
|
|
@@ -176,15 +180,26 @@ export function make<Error = never, Context = never>(
|
|
|
176
180
|
readonly renderer?: Renderer
|
|
177
181
|
readonly driver: Driver<Error, Context>
|
|
178
182
|
readonly driverMode?: CoreExecutor.DriverMode
|
|
183
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
179
184
|
}
|
|
180
185
|
): QueryExecutor<Context>
|
|
181
186
|
export function make<Error = never, Context = never>(
|
|
182
187
|
options: MakeOptions<Error, Context> = {}
|
|
183
188
|
): QueryExecutor<any> {
|
|
184
189
|
if (options.driver) {
|
|
185
|
-
return fromDriver(
|
|
190
|
+
return fromDriver(
|
|
191
|
+
options.renderer ?? CoreRenderer.make("mysql", (plan) => renderMysqlPlan(plan, { valueMappings: options.valueMappings })),
|
|
192
|
+
options.driver,
|
|
193
|
+
options.driverMode,
|
|
194
|
+
options.valueMappings
|
|
195
|
+
)
|
|
186
196
|
}
|
|
187
|
-
return fromDriver(
|
|
197
|
+
return fromDriver(
|
|
198
|
+
options.renderer ?? CoreRenderer.make("mysql", (plan) => renderMysqlPlan(plan, { valueMappings: options.valueMappings })),
|
|
199
|
+
sqlClientDriver(),
|
|
200
|
+
options.driverMode,
|
|
201
|
+
options.valueMappings
|
|
202
|
+
)
|
|
188
203
|
}
|
|
189
204
|
|
|
190
205
|
/** Creates a MySQL-specialized executor from a typed implementation callback. */
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import type { RenderState, SqlDialect } from "../../internal/dialect.js"
|
|
1
|
+
import type { RenderState, RenderValueContext, SqlDialect } from "../../internal/dialect.js"
|
|
2
|
+
import { toDriverValue } from "../../internal/runtime/driver-value-mapping.js"
|
|
2
3
|
|
|
3
4
|
const quoteIdentifier = (value: string): string => `\`${value.replaceAll("`", "``")}\``
|
|
4
5
|
|
|
5
|
-
const renderLiteral = (value: unknown, state: RenderState): string => {
|
|
6
|
-
|
|
6
|
+
const renderLiteral = (value: unknown, state: RenderState, context: RenderValueContext = {}): string => {
|
|
7
|
+
const driverValue = toDriverValue(value, {
|
|
8
|
+
dialect: "mysql",
|
|
9
|
+
valueMappings: state.valueMappings,
|
|
10
|
+
...context
|
|
11
|
+
})
|
|
12
|
+
if (driverValue === null) {
|
|
7
13
|
return "null"
|
|
8
14
|
}
|
|
9
|
-
if (typeof
|
|
10
|
-
return
|
|
15
|
+
if (typeof driverValue === "boolean") {
|
|
16
|
+
return driverValue ? "true" : "false"
|
|
11
17
|
}
|
|
12
|
-
state.params.push(
|
|
18
|
+
state.params.push(driverValue)
|
|
13
19
|
return "?"
|
|
14
20
|
}
|
|
15
21
|
|