effect-qb 0.20.0 → 0.22.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/index.js +663 -261
- package/dist/mysql.js +2840 -492
- package/dist/postgres/metadata.js +113 -14
- package/dist/postgres.js +2596 -285
- package/dist/sqlite.js +2783 -450
- package/dist/standard.js +663 -261
- package/package.json +2 -3
- package/src/internal/analytics.ts +264 -0
- package/src/internal/coercion/errors.d.ts +1 -1
- package/src/internal/coercion/errors.ts +1 -1
- package/src/internal/custom-sql-renderer.ts +20 -0
- package/src/internal/dialect-numeric.ts +332 -0
- package/src/internal/dialect-renderers/mysql.ts +68 -13
- package/src/internal/dialect-renderers/postgres.ts +83 -13
- package/src/internal/dialect-renderers/sqlite.ts +57 -13
- package/src/internal/dynamic.ts +131 -0
- package/src/internal/executor.ts +198 -7
- package/src/internal/expression-ast.ts +62 -1
- package/src/internal/fragment.ts +120 -0
- package/src/internal/function-constraints.ts +105 -0
- package/src/internal/grouping-key.ts +26 -0
- package/src/internal/numeric.ts +204 -0
- package/src/internal/query.ts +13 -1
- package/src/internal/runtime/normalize.ts +5 -2
- package/src/internal/runtime/schema.ts +2 -1
- package/src/internal/standard-dsl.ts +43 -20
- package/src/internal/window-frame.ts +30 -0
- package/src/internal/window-renderer.ts +25 -0
- package/src/mysql/executor.ts +126 -45
- package/src/mysql/function/aggregate.ts +87 -1
- package/src/mysql/function/index.ts +5 -2
- package/src/mysql/function/numeric.ts +185 -0
- package/src/mysql/function/temporal.ts +6 -6
- package/src/mysql/function/window.ts +12 -0
- package/src/mysql/internal/dsl.ts +38 -20
- package/src/mysql.ts +2 -0
- package/src/postgres/executor.ts +111 -45
- package/src/postgres/function/aggregate.ts +124 -1
- package/src/postgres/function/core.ts +2 -1
- package/src/postgres/function/index.ts +19 -1
- package/src/postgres/function/numeric.ts +246 -0
- package/src/postgres/function/window.ts +12 -0
- package/src/postgres/internal/dsl.ts +38 -20
- package/src/sqlite/executor.ts +96 -45
- package/src/sqlite/function/aggregate.ts +90 -1
- package/src/sqlite/function/index.ts +5 -2
- package/src/sqlite/function/numeric.ts +139 -0
- package/src/sqlite/function/window.ts +12 -0
- package/src/sqlite/internal/dsl.ts +38 -20
- package/src/sqlite.ts +2 -0
- package/src/standard/function/core.ts +8 -1
- package/src/standard/function/index.ts +18 -11
- package/src/standard/function/string.ts +1 -1
- package/src/standard/function/window.ts +10 -1
- package/src/standard/query.ts +19 -7
- package/src/standard/type.ts +16 -0
- package/src/standard.ts +4 -0
- package/src/standard/function/temporal.ts +0 -78
package/src/mysql/executor.ts
CHANGED
|
@@ -26,6 +26,16 @@ export type Executor<Error = never, Context = never> = CoreExecutor.Executor<"my
|
|
|
26
26
|
/** MySQL-specialized renderer contract. */
|
|
27
27
|
export type Renderer = CoreRenderer.Renderer<"mysql">
|
|
28
28
|
export type ValueMappings = Expression.DriverValueMappingsFor<MysqlDatatypeKind | "uuid", MysqlDatatypeFamily | "uuid">
|
|
29
|
+
/** MySQL EXPLAIN options. ANALYZE always uses TREE output. */
|
|
30
|
+
export type ExplainOptions =
|
|
31
|
+
| {
|
|
32
|
+
readonly analyze?: false
|
|
33
|
+
readonly format?: "text" | "json"
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
readonly analyze: true
|
|
37
|
+
readonly format?: "text"
|
|
38
|
+
}
|
|
29
39
|
/** Optional renderer / driver overrides for the standard MySQL executor pipeline. */
|
|
30
40
|
export interface MakeOptions<Error = never, Context = never> {
|
|
31
41
|
readonly renderer?: Renderer
|
|
@@ -39,20 +49,37 @@ export type MysqlExecutorError = MysqlDriverError | RowDecodeError
|
|
|
39
49
|
export type MysqlQueryError<PlanValue extends CoreQuery.QueryPlan<any, any, any, any, any, any, any, any, any, any>> =
|
|
40
50
|
Exclude<CoreQuery.CapabilitiesOfPlan<PlanValue>, "read"> extends never ? MysqlReadQueryError : MysqlExecutorError
|
|
41
51
|
|
|
52
|
+
/** Pipeable execution cardinality helpers. */
|
|
53
|
+
export const atMostOne = CoreExecutor.atMostOne
|
|
54
|
+
export const exactlyOne = CoreExecutor.exactlyOne
|
|
55
|
+
export const nonEmpty = CoreExecutor.nonEmpty
|
|
56
|
+
|
|
42
57
|
/** Runs an effect within the ambient MySQL SQL transaction service. */
|
|
43
58
|
export const withTransaction = CoreExecutor.withTransaction
|
|
44
59
|
|
|
45
60
|
/** MySQL executor whose error channel narrows based on the query plan. */
|
|
46
|
-
export interface QueryExecutor<Context = never> {
|
|
61
|
+
export interface QueryExecutor<Context = never> extends CoreExecutor.Executor<"mysql", MysqlQueryError<any>, Context> {
|
|
47
62
|
readonly dialect: "mysql"
|
|
48
63
|
execute<PlanValue extends CoreQuery.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
49
64
|
plan: CoreQuery.DialectCompatiblePlan<PlanValue, "mysql">
|
|
50
65
|
): Effect.Effect<CoreQuery.ResultRows<PlanValue>, MysqlQueryError<PlanValue>, Context>
|
|
66
|
+
executeResult<PlanValue extends CoreQuery.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
67
|
+
plan: CoreQuery.DialectCompatiblePlan<PlanValue, "mysql">
|
|
68
|
+
): Effect.Effect<CoreExecutor.ExecutionResult<CoreQuery.ResultRow<PlanValue>>, MysqlQueryError<PlanValue>, Context>
|
|
69
|
+
prepare<PlanValue extends CoreQuery.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
70
|
+
plan: CoreQuery.DialectCompatiblePlan<PlanValue, "mysql">
|
|
71
|
+
): CoreExecutor.PreparedQuery<CoreQuery.ResultRow<PlanValue>, MysqlQueryError<PlanValue>, Context>
|
|
51
72
|
stream<PlanValue extends CoreQuery.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
52
73
|
plan: Exclude<CoreQuery.CapabilitiesOfPlan<PlanValue>, "read" | "locking"> extends never
|
|
53
74
|
? CoreQuery.DialectCompatiblePlan<PlanValue, "mysql">
|
|
54
75
|
: never
|
|
55
76
|
): Stream.Stream<CoreQuery.ResultRow<PlanValue>, MysqlQueryError<PlanValue>, Context>
|
|
77
|
+
explain<PlanValue extends CoreQuery.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
78
|
+
plan: Exclude<CoreQuery.CapabilitiesOfPlan<PlanValue>, "read" | "locking"> extends never
|
|
79
|
+
? CoreQuery.DialectCompatiblePlan<PlanValue, "mysql">
|
|
80
|
+
: never,
|
|
81
|
+
options?: ExplainOptions
|
|
82
|
+
): Effect.Effect<ReadonlyArray<FlatRow>, MysqlQueryError<PlanValue>, Context>
|
|
56
83
|
}
|
|
57
84
|
|
|
58
85
|
/** Constructs a MySQL-specialized SQL driver. */
|
|
@@ -72,6 +99,9 @@ export function driver<
|
|
|
72
99
|
readonly execute: <Row>(
|
|
73
100
|
query: CoreRenderer.RenderedQuery<Row, "mysql">
|
|
74
101
|
) => Effect.Effect<ReadonlyArray<FlatRow>, Error, Context>
|
|
102
|
+
readonly executeResult?: <Row>(
|
|
103
|
+
query: CoreRenderer.RenderedQuery<Row, "mysql">
|
|
104
|
+
) => Effect.Effect<CoreExecutor.DriverResult, Error, Context>
|
|
75
105
|
readonly stream: <Row>(
|
|
76
106
|
query: CoreRenderer.RenderedQuery<Row, "mysql">
|
|
77
107
|
) => Stream.Stream<FlatRow, Error, Context>
|
|
@@ -89,6 +119,9 @@ export function driver<
|
|
|
89
119
|
readonly execute: <Row>(
|
|
90
120
|
query: CoreRenderer.RenderedQuery<Row, "mysql">
|
|
91
121
|
) => Effect.Effect<ReadonlyArray<FlatRow>, Error, Context>
|
|
122
|
+
readonly executeResult?: <Row>(
|
|
123
|
+
query: CoreRenderer.RenderedQuery<Row, "mysql">
|
|
124
|
+
) => Effect.Effect<CoreExecutor.DriverResult, Error, Context>
|
|
92
125
|
readonly stream: <Row>(
|
|
93
126
|
query: CoreRenderer.RenderedQuery<Row, "mysql">
|
|
94
127
|
) => Stream.Stream<FlatRow, Error, Context>
|
|
@@ -105,57 +138,105 @@ const fromDriver = <
|
|
|
105
138
|
sqlDriver: Driver<Error, Context>,
|
|
106
139
|
driverMode: CoreExecutor.DriverMode = "raw",
|
|
107
140
|
valueMappings?: Expression.DriverValueMappings
|
|
108
|
-
): QueryExecutor<Context> =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
stream(plan) {
|
|
132
|
-
const rendered = renderer.render(plan)
|
|
133
|
-
return Stream.mapError(
|
|
134
|
-
Stream.mapArrayEffect(
|
|
135
|
-
sqlDriver.stream(rendered),
|
|
136
|
-
(rows) => Effect.try({
|
|
137
|
-
try: () => CoreExecutor.decodeRows(rendered, plan, rows, { driverMode, valueMappings }) as never,
|
|
138
|
-
catch: (error) => error as RowDecodeError
|
|
139
|
-
})
|
|
140
|
-
),
|
|
141
|
-
(error) => {
|
|
142
|
-
if (typeof error === "object" && error !== null && "_tag" in error && error._tag === "RowDecodeError") {
|
|
143
|
-
return error as RowDecodeError
|
|
144
|
-
}
|
|
145
|
-
const normalized = normalizeMysqlDriverError(error, rendered)
|
|
146
|
-
return CoreExecutor.hasWriteCapability(plan)
|
|
147
|
-
? normalized
|
|
148
|
-
: narrowMysqlDriverErrorForReadQuery(normalized)
|
|
149
|
-
}
|
|
150
|
-
) as Stream.Stream<any, any, Context>
|
|
141
|
+
): QueryExecutor<Context> => {
|
|
142
|
+
const renderedCache = new WeakMap<object, CoreRenderer.RenderedQuery<any, "mysql">>()
|
|
143
|
+
const render = (plan: CoreQuery.Plan.Any) => {
|
|
144
|
+
const cached = renderedCache.get(plan)
|
|
145
|
+
if (cached !== undefined) {
|
|
146
|
+
return cached
|
|
147
|
+
}
|
|
148
|
+
const rendered = renderer.render(plan as any)
|
|
149
|
+
renderedCache.set(plan, rendered)
|
|
150
|
+
return rendered
|
|
151
|
+
}
|
|
152
|
+
const mapExecutionError = (
|
|
153
|
+
error: unknown,
|
|
154
|
+
rendered: CoreRenderer.RenderedQuery<any, "mysql">,
|
|
155
|
+
plan: CoreQuery.Plan.Any
|
|
156
|
+
) => {
|
|
157
|
+
if (typeof error === "object" && error !== null && "_tag" in error && error._tag === "RowDecodeError") {
|
|
158
|
+
return error as RowDecodeError
|
|
159
|
+
}
|
|
160
|
+
const normalized = normalizeMysqlDriverError(error, rendered)
|
|
161
|
+
return CoreExecutor.hasWriteCapability(plan)
|
|
162
|
+
? normalized
|
|
163
|
+
: narrowMysqlDriverErrorForReadQuery(normalized)
|
|
151
164
|
}
|
|
152
|
-
|
|
165
|
+
return CoreExecutor.withResultContracts({
|
|
166
|
+
dialect: "mysql",
|
|
167
|
+
execute(plan) {
|
|
168
|
+
const rendered = render(plan)
|
|
169
|
+
return Effect.mapError(
|
|
170
|
+
Effect.flatMap(
|
|
171
|
+
sqlDriver.execute(rendered),
|
|
172
|
+
(rows) => Effect.try({
|
|
173
|
+
try: () => CoreExecutor.decodeRows(rendered, plan, rows, { driverMode, valueMappings }),
|
|
174
|
+
catch: (error) => error as RowDecodeError
|
|
175
|
+
})
|
|
176
|
+
),
|
|
177
|
+
(error) => mapExecutionError(error, rendered, plan)
|
|
178
|
+
) as Effect.Effect<any, any, Context>
|
|
179
|
+
},
|
|
180
|
+
executeResult(plan) {
|
|
181
|
+
const rendered = render(plan)
|
|
182
|
+
const result = sqlDriver.executeResult
|
|
183
|
+
? sqlDriver.executeResult(rendered)
|
|
184
|
+
: Effect.map(sqlDriver.execute(rendered), (rows) => ({ rows }))
|
|
185
|
+
return Effect.mapError(
|
|
186
|
+
Effect.flatMap(result, ({ rows, ...metadata }) => Effect.try({
|
|
187
|
+
try: () => ({
|
|
188
|
+
...metadata,
|
|
189
|
+
rows: CoreExecutor.decodeRows(rendered, plan, rows, { driverMode, valueMappings })
|
|
190
|
+
}),
|
|
191
|
+
catch: (error) => error as RowDecodeError
|
|
192
|
+
})),
|
|
193
|
+
(error) => mapExecutionError(error, rendered, plan)
|
|
194
|
+
) as Effect.Effect<any, any, Context>
|
|
195
|
+
},
|
|
196
|
+
stream(plan) {
|
|
197
|
+
const rendered = render(plan)
|
|
198
|
+
return Stream.mapError(
|
|
199
|
+
Stream.mapArrayEffect(
|
|
200
|
+
sqlDriver.stream(rendered),
|
|
201
|
+
(rows) => Effect.try({
|
|
202
|
+
try: () => CoreExecutor.decodeRows(rendered, plan, rows, { driverMode, valueMappings }) as never,
|
|
203
|
+
catch: (error) => error as RowDecodeError
|
|
204
|
+
})
|
|
205
|
+
),
|
|
206
|
+
(error) => mapExecutionError(error, rendered, plan)
|
|
207
|
+
) as Stream.Stream<any, any, Context>
|
|
208
|
+
},
|
|
209
|
+
explain(plan, options) {
|
|
210
|
+
const rendered = CoreExecutor.explainQuery(render(plan), options)
|
|
211
|
+
return Effect.mapError(
|
|
212
|
+
sqlDriver.execute(rendered),
|
|
213
|
+
(error) => mapExecutionError(error, rendered, plan)
|
|
214
|
+
) as Effect.Effect<any, any, Context>
|
|
215
|
+
}
|
|
216
|
+
}) as QueryExecutor<Context>
|
|
217
|
+
}
|
|
153
218
|
|
|
154
219
|
const sqlClientDriver = (): Driver<any, SqlClient.SqlClient> =>
|
|
155
220
|
driver({
|
|
156
221
|
execute: (query) =>
|
|
157
222
|
Effect.flatMap(SqlClient.SqlClient, (sql) =>
|
|
158
223
|
sql.unsafe<FlatRow>(query.sql, [...query.params])),
|
|
224
|
+
executeResult: (query) =>
|
|
225
|
+
Effect.flatMap(SqlClient.SqlClient, (sql) =>
|
|
226
|
+
Effect.map(sql.unsafe<FlatRow>(query.sql, [...query.params]).raw, (raw) => {
|
|
227
|
+
if (Array.isArray(raw)) {
|
|
228
|
+
return { rows: raw as ReadonlyArray<FlatRow> }
|
|
229
|
+
}
|
|
230
|
+
const header = raw as {
|
|
231
|
+
readonly affectedRows?: number
|
|
232
|
+
readonly insertId?: number | string
|
|
233
|
+
} | null
|
|
234
|
+
return {
|
|
235
|
+
rows: [],
|
|
236
|
+
...(typeof header?.affectedRows === "number" ? { affectedRows: header.affectedRows } : {}),
|
|
237
|
+
...(header?.insertId === undefined ? {} : { insertId: header.insertId })
|
|
238
|
+
}
|
|
239
|
+
})),
|
|
159
240
|
stream: (query) =>
|
|
160
241
|
CoreExecutor.streamFromSqlClient(query)
|
|
161
242
|
})
|
|
@@ -1,2 +1,88 @@
|
|
|
1
|
-
|
|
1
|
+
import * as Numeric from "../../internal/dialect-numeric.js"
|
|
2
|
+
import * as Expression from "../../internal/scalar.js"
|
|
3
|
+
import { mysqlDatatypes } from "../datatypes/index.js"
|
|
4
|
+
|
|
2
5
|
export { count, max, min } from "../internal/dsl.js"
|
|
6
|
+
|
|
7
|
+
type MyDecimal = ReturnType<typeof mysqlDatatypes.decimal>
|
|
8
|
+
type MyDouble = ReturnType<typeof mysqlDatatypes.double>
|
|
9
|
+
|
|
10
|
+
type IsAny<Value> = 0 extends (1 & Value) ? true : false
|
|
11
|
+
|
|
12
|
+
type BaseDb<Db extends Expression.DbType.Any> =
|
|
13
|
+
Db extends Expression.DbType.Domain<any, infer Base extends Expression.DbType.Any, any>
|
|
14
|
+
? BaseDb<Base>
|
|
15
|
+
: Db
|
|
16
|
+
|
|
17
|
+
type MyAggregateCategory<Db extends Expression.DbType.Any> =
|
|
18
|
+
BaseDb<Db> extends infer Base extends Expression.DbType.Any
|
|
19
|
+
? Base["dialect"] extends "standard"
|
|
20
|
+
? Base["kind"] extends "int" | "integer" | "bigint" | "numeric" | "decimal"
|
|
21
|
+
? "exact"
|
|
22
|
+
: Base["kind"] extends "real" ? "approximate" : never
|
|
23
|
+
: Base["dialect"] extends "mysql"
|
|
24
|
+
? Base["kind"] extends "tinyint" | "smallint" | "mediumint" | "int" | "integer" | "bigint" |
|
|
25
|
+
"decimal" | "dec" | "numeric" | "fixed"
|
|
26
|
+
? "exact"
|
|
27
|
+
: Base["kind"] extends "float" | "double" | "real" ? "approximate" : never
|
|
28
|
+
: never
|
|
29
|
+
: never
|
|
30
|
+
|
|
31
|
+
type AggregateInputError<
|
|
32
|
+
Operation extends "sum" | "avg",
|
|
33
|
+
Db extends Expression.DbType.Any
|
|
34
|
+
> = {
|
|
35
|
+
readonly __effect_qb_error__: "effect-qb: unsupported mysql aggregate input"
|
|
36
|
+
readonly __effect_qb_operation__: Operation
|
|
37
|
+
readonly __effect_qb_db_type__: Db
|
|
38
|
+
readonly __effect_qb_expected__: "an integer, decimal, float, double, or real database type"
|
|
39
|
+
readonly __effect_qb_hint__: "Use Cast.to(...) with a supported Type or My.Type witness"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type MyAggregateConstraint<
|
|
43
|
+
Value extends Numeric.Input,
|
|
44
|
+
Operation extends "sum" | "avg"
|
|
45
|
+
> = IsAny<Value> extends true ? unknown
|
|
46
|
+
: Numeric.DialectConstraint<Value, MyDouble, "mysql"> & (
|
|
47
|
+
MyAggregateCategory<Numeric.DbTypeOfInput<Value, MyDouble, "mysql">> extends never
|
|
48
|
+
? AggregateInputError<
|
|
49
|
+
Operation,
|
|
50
|
+
Numeric.DbTypeOfInput<Value, MyDouble, "mysql">
|
|
51
|
+
>
|
|
52
|
+
: unknown
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
type MyAggregateResultDb<Value extends Numeric.Input> =
|
|
56
|
+
MyAggregateCategory<Numeric.DbTypeOfInput<Value, MyDouble, "mysql">> extends "exact"
|
|
57
|
+
? MyDecimal
|
|
58
|
+
: MyDouble
|
|
59
|
+
|
|
60
|
+
const baseDb = (db: Expression.DbType.Any): Expression.DbType.Any =>
|
|
61
|
+
"base" in db ? baseDb(db.base) : db
|
|
62
|
+
|
|
63
|
+
const resultDb = (value: Numeric.Input): MyDecimal | MyDouble => {
|
|
64
|
+
if (typeof value === "number") return mysqlDatatypes.double()
|
|
65
|
+
const db = baseDb(value[Expression.TypeId].dbType)
|
|
66
|
+
const approximate = db.kind === "real" || db.kind === "float" || db.kind === "double"
|
|
67
|
+
return approximate
|
|
68
|
+
? mysqlDatatypes.double()
|
|
69
|
+
: mysqlDatatypes.decimal()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const sum = <Value extends Numeric.Input>(
|
|
73
|
+
value: Value & MyAggregateConstraint<NoInfer<Value>, "sum">
|
|
74
|
+
): Numeric.AggregateResult<"sum", Value, MyDouble, MyAggregateResultDb<Value>, "mysql"> =>
|
|
75
|
+
(Numeric.aggregate as any)("sum", value, {
|
|
76
|
+
dialect: "mysql",
|
|
77
|
+
literalDb: mysqlDatatypes.double(),
|
|
78
|
+
resultDb: resultDb(value)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
export const avg = <Value extends Numeric.Input>(
|
|
82
|
+
value: Value & MyAggregateConstraint<NoInfer<Value>, "avg">
|
|
83
|
+
): Numeric.AggregateResult<"avg", Value, MyDouble, MyAggregateResultDb<Value>, "mysql"> =>
|
|
84
|
+
(Numeric.aggregate as any)("avg", value, {
|
|
85
|
+
dialect: "mysql",
|
|
86
|
+
literalDb: mysqlDatatypes.double(),
|
|
87
|
+
resultDb: resultDb(value)
|
|
88
|
+
})
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
export * as core from "./core.js"
|
|
2
2
|
export * as string from "./string.js"
|
|
3
3
|
export * as aggregate from "./aggregate.js"
|
|
4
|
+
export * as numeric from "./numeric.js"
|
|
4
5
|
export * as window from "./window.js"
|
|
5
6
|
export * as temporal from "./temporal.js"
|
|
6
7
|
|
|
7
8
|
export { coalesce } from "./core.js"
|
|
8
9
|
export { call } from "./core.js"
|
|
9
10
|
export { lower, upper, concat } from "./string.js"
|
|
10
|
-
export { count, max, min } from "./aggregate.js"
|
|
11
|
-
export {
|
|
11
|
+
export { avg, count, max, min, sum } from "./aggregate.js"
|
|
12
|
+
export { modulo, round } from "./numeric.js"
|
|
13
|
+
export { firstValue, lastValue, over, rowNumber, rank, denseRank } from "./window.js"
|
|
14
|
+
export type { WindowSpec } from "./window.js"
|
|
12
15
|
export {
|
|
13
16
|
currentDate,
|
|
14
17
|
currentTime,
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import * as Numeric from "../../internal/dialect-numeric.js"
|
|
2
|
+
import * as Expression from "../../internal/scalar.js"
|
|
3
|
+
import { mysqlDatatypes } from "../datatypes/index.js"
|
|
4
|
+
import { mysqlDatatypeFamilies } from "../datatypes/spec.js"
|
|
5
|
+
|
|
6
|
+
type MyNumericDb<
|
|
7
|
+
Kind extends "bigint" | "decimal" | "double",
|
|
8
|
+
Runtime extends "number" | "bigintString" | "decimalString"
|
|
9
|
+
> = Expression.DbType.Base<"mysql", Kind> & {
|
|
10
|
+
readonly family: "numeric"
|
|
11
|
+
readonly runtime: Runtime
|
|
12
|
+
readonly compareGroup: "numeric"
|
|
13
|
+
readonly castTargets: typeof mysqlDatatypeFamilies.numeric.castTargets
|
|
14
|
+
readonly implicitTargets: readonly []
|
|
15
|
+
readonly traits: { readonly ordered: true }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type MyBigInt = MyNumericDb<"bigint", "bigintString">
|
|
19
|
+
type MyDecimal = MyNumericDb<"decimal", "decimalString">
|
|
20
|
+
type MyDouble = MyNumericDb<"double", "number">
|
|
21
|
+
|
|
22
|
+
type IsAny<Value> = 0 extends (1 & Value) ? true : false
|
|
23
|
+
|
|
24
|
+
type BaseDb<Db extends Expression.DbType.Any> =
|
|
25
|
+
Db extends Expression.DbType.Domain<any, infer Base extends Expression.DbType.Any, any>
|
|
26
|
+
? BaseDb<Base>
|
|
27
|
+
: Db
|
|
28
|
+
|
|
29
|
+
type MyNumericCategory<Db extends Expression.DbType.Any> =
|
|
30
|
+
BaseDb<Db> extends infer Base extends Expression.DbType.Any
|
|
31
|
+
? Base["dialect"] extends "standard"
|
|
32
|
+
? Base["kind"] extends "int" | "integer" | "bigint" ? "integer"
|
|
33
|
+
: Base["kind"] extends "numeric" | "decimal" ? "exact"
|
|
34
|
+
: Base["kind"] extends "real" ? "approximate"
|
|
35
|
+
: never
|
|
36
|
+
: Base["dialect"] extends "mysql"
|
|
37
|
+
? Base["kind"] extends "tinyint" | "smallint" | "mediumint" | "int" | "integer" | "bigint"
|
|
38
|
+
? "integer"
|
|
39
|
+
: Base["kind"] extends "decimal" | "dec" | "numeric" | "fixed"
|
|
40
|
+
? "exact"
|
|
41
|
+
: Base["kind"] extends "float" | "double" | "real"
|
|
42
|
+
? "approximate"
|
|
43
|
+
: never
|
|
44
|
+
: never
|
|
45
|
+
: never
|
|
46
|
+
|
|
47
|
+
type NumericInputError<
|
|
48
|
+
Operation extends "modulo" | "round",
|
|
49
|
+
Db extends Expression.DbType.Any
|
|
50
|
+
> = {
|
|
51
|
+
readonly __effect_qb_error__: "effect-qb: unsupported mysql numeric input"
|
|
52
|
+
readonly __effect_qb_operation__: Operation
|
|
53
|
+
readonly __effect_qb_db_type__: Db
|
|
54
|
+
readonly __effect_qb_expected__: "an integer, decimal, float, double, or real database type"
|
|
55
|
+
readonly __effect_qb_hint__: "Use Cast.to(...) with a supported Type or My.Type witness"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type MyNumericConstraint<
|
|
59
|
+
Value extends Numeric.Input,
|
|
60
|
+
Operation extends "modulo" | "round"
|
|
61
|
+
> =
|
|
62
|
+
IsAny<Value> extends true ? unknown
|
|
63
|
+
: Numeric.DialectConstraint<Value, MyDouble, "mysql"> & (
|
|
64
|
+
MyNumericCategory<Numeric.DbTypeOfInput<Value, MyDouble, "mysql">> extends never
|
|
65
|
+
? NumericInputError<Operation, Numeric.DbTypeOfInput<Value, MyDouble, "mysql">>
|
|
66
|
+
: unknown
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
type MyResultCategory<
|
|
70
|
+
Left extends Numeric.Input,
|
|
71
|
+
Right extends Numeric.Input
|
|
72
|
+
> =
|
|
73
|
+
MyNumericCategory<Numeric.DbTypeOfInput<Left, MyDouble, "mysql">> extends infer LeftCategory
|
|
74
|
+
? MyNumericCategory<Numeric.DbTypeOfInput<Right, MyDouble, "mysql">> extends infer RightCategory
|
|
75
|
+
? "approximate" extends LeftCategory | RightCategory ? "approximate"
|
|
76
|
+
: "exact" extends LeftCategory | RightCategory ? "exact"
|
|
77
|
+
: "integer"
|
|
78
|
+
: never
|
|
79
|
+
: never
|
|
80
|
+
|
|
81
|
+
type MyResultDbForCategory<Category> =
|
|
82
|
+
Category extends "approximate" ? MyDouble
|
|
83
|
+
: Category extends "exact" ? MyDecimal
|
|
84
|
+
: MyBigInt
|
|
85
|
+
|
|
86
|
+
type MyModuloResultDb<
|
|
87
|
+
Left extends Numeric.Input,
|
|
88
|
+
Right extends Numeric.Input
|
|
89
|
+
> = MyResultDbForCategory<MyResultCategory<Left, Right>>
|
|
90
|
+
|
|
91
|
+
type MyRoundResultDb<Value extends Numeric.Input> =
|
|
92
|
+
MyResultDbForCategory<MyNumericCategory<Numeric.DbTypeOfInput<Value, MyDouble, "mysql">>>
|
|
93
|
+
|
|
94
|
+
type MyModuloResult<
|
|
95
|
+
Left extends Numeric.Input,
|
|
96
|
+
Right extends Numeric.Input
|
|
97
|
+
> = Numeric.BinaryResult<
|
|
98
|
+
Left,
|
|
99
|
+
Right,
|
|
100
|
+
MyDouble,
|
|
101
|
+
MyModuloResultDb<Left, Right>,
|
|
102
|
+
Numeric.ZeroDivisorNullability<Left, Right, MyDouble, "mysql">,
|
|
103
|
+
"mysql"
|
|
104
|
+
>
|
|
105
|
+
|
|
106
|
+
type MyRoundResult<
|
|
107
|
+
Value extends Numeric.Input,
|
|
108
|
+
WithScale extends boolean
|
|
109
|
+
> = Numeric.RoundResult<
|
|
110
|
+
Value,
|
|
111
|
+
MyDouble,
|
|
112
|
+
MyRoundResultDb<Value>,
|
|
113
|
+
Value extends Expression.Any ? Expression.NullabilityOf<Value> : "never",
|
|
114
|
+
"mysql",
|
|
115
|
+
WithScale
|
|
116
|
+
>
|
|
117
|
+
|
|
118
|
+
const baseDb = (db: Expression.DbType.Any): Expression.DbType.Any =>
|
|
119
|
+
"base" in db ? baseDb(db.base) : db
|
|
120
|
+
|
|
121
|
+
const category = (value: Numeric.Input): "integer" | "exact" | "approximate" => {
|
|
122
|
+
if (typeof value === "number") return "approximate"
|
|
123
|
+
const db = baseDb(value[Expression.TypeId].dbType)
|
|
124
|
+
if (db.kind === "int" || db.kind === "integer" || db.kind === "bigint" ||
|
|
125
|
+
db.kind === "tinyint" || db.kind === "smallint" || db.kind === "mediumint") {
|
|
126
|
+
return "integer"
|
|
127
|
+
}
|
|
128
|
+
if (db.kind === "numeric" || db.kind === "decimal" || db.kind === "dec" || db.kind === "fixed") {
|
|
129
|
+
return "exact"
|
|
130
|
+
}
|
|
131
|
+
return "approximate"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const resultDb = (categoryValue: "integer" | "exact" | "approximate") => {
|
|
135
|
+
if (categoryValue === "integer") return mysqlDatatypes.bigint()
|
|
136
|
+
if (categoryValue === "exact") return mysqlDatatypes.decimal()
|
|
137
|
+
return mysqlDatatypes.double()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const mergedCategory = (
|
|
141
|
+
left: Numeric.Input,
|
|
142
|
+
right: Numeric.Input
|
|
143
|
+
): "integer" | "exact" | "approximate" => {
|
|
144
|
+
const categories = [category(left), category(right)]
|
|
145
|
+
return categories.includes("approximate")
|
|
146
|
+
? "approximate"
|
|
147
|
+
: categories.includes("exact") ? "exact" : "integer"
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const modulo = <
|
|
151
|
+
Left extends Numeric.Input,
|
|
152
|
+
Right extends Numeric.Input
|
|
153
|
+
>(
|
|
154
|
+
left: Left & MyNumericConstraint<NoInfer<Left>, "modulo">,
|
|
155
|
+
right: Right & MyNumericConstraint<NoInfer<Right>, "modulo">
|
|
156
|
+
): MyModuloResult<Left, Right> =>
|
|
157
|
+
(Numeric.modulo as any)(left, right, {
|
|
158
|
+
dialect: "mysql",
|
|
159
|
+
literalDb: mysqlDatatypes.double(),
|
|
160
|
+
resultDb: resultDb(mergedCategory(left, right)) as MyModuloResultDb<Left, Right>,
|
|
161
|
+
nullability: Numeric.nullableForZeroDivisor(left, right) as Numeric.ZeroDivisorNullability<Left, Right, MyDouble, "mysql">
|
|
162
|
+
}) as MyModuloResult<Left, Right>
|
|
163
|
+
|
|
164
|
+
const roundRuntime = (
|
|
165
|
+
value: Numeric.Input,
|
|
166
|
+
scale?: number
|
|
167
|
+
): Expression.Any => {
|
|
168
|
+
return Numeric.round(value, scale, {
|
|
169
|
+
dialect: "mysql",
|
|
170
|
+
literalDb: mysqlDatatypes.double(),
|
|
171
|
+
scaleDb: mysqlDatatypes.bigint(),
|
|
172
|
+
resultDb: resultDb(category(value)),
|
|
173
|
+
nullability: typeof value === "number" ? "never" : value[Expression.TypeId].nullability
|
|
174
|
+
}) as Expression.Any
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const round: {
|
|
178
|
+
<Value extends Numeric.Input>(
|
|
179
|
+
value: Value & MyNumericConstraint<NoInfer<Value>, "round">
|
|
180
|
+
): MyRoundResult<Value, false>
|
|
181
|
+
<Value extends Numeric.Input>(
|
|
182
|
+
value: Value & MyNumericConstraint<NoInfer<Value>, "round">,
|
|
183
|
+
scale: number
|
|
184
|
+
): MyRoundResult<Value, true>
|
|
185
|
+
} = roundRuntime as any
|
|
@@ -71,23 +71,23 @@ export const currentTime = () =>
|
|
|
71
71
|
export const currentTimestamp = () =>
|
|
72
72
|
makeTemporal(
|
|
73
73
|
"current_timestamp",
|
|
74
|
-
mysqlDatatypes.
|
|
74
|
+
mysqlDatatypes.datetime(),
|
|
75
75
|
LocalDateTimeStringSchema
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
-
/** MySQL local time. */
|
|
78
|
+
/** MySQL LOCALTIME, a synonym for NOW that returns a local date and time. */
|
|
79
79
|
export const localTime = () =>
|
|
80
80
|
makeTemporal(
|
|
81
81
|
"localtime",
|
|
82
|
-
mysqlDatatypes.
|
|
83
|
-
|
|
82
|
+
mysqlDatatypes.datetime(),
|
|
83
|
+
LocalDateTimeStringSchema
|
|
84
84
|
)
|
|
85
85
|
|
|
86
86
|
/** MySQL local timestamp. */
|
|
87
87
|
export const localTimestamp = () =>
|
|
88
88
|
makeTemporal(
|
|
89
89
|
"localtimestamp",
|
|
90
|
-
mysqlDatatypes.
|
|
90
|
+
mysqlDatatypes.datetime(),
|
|
91
91
|
LocalDateTimeStringSchema
|
|
92
92
|
)
|
|
93
93
|
|
|
@@ -95,6 +95,6 @@ export const localTimestamp = () =>
|
|
|
95
95
|
export const now = () =>
|
|
96
96
|
makeTemporal(
|
|
97
97
|
"now",
|
|
98
|
-
mysqlDatatypes.
|
|
98
|
+
mysqlDatatypes.datetime(),
|
|
99
99
|
LocalDateTimeStringSchema
|
|
100
100
|
)
|
|
@@ -1,2 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
makeDialectFirstValue,
|
|
3
|
+
makeDialectLastValue
|
|
4
|
+
} from "../../internal/analytics.js"
|
|
5
|
+
export type { WindowSpec } from "../../internal/analytics.js"
|
|
6
|
+
|
|
1
7
|
/** MySQL window functions. */
|
|
2
8
|
export { over, rowNumber, rank, denseRank } from "../internal/dsl.js"
|
|
9
|
+
|
|
10
|
+
/** First value in a MySQL window frame. */
|
|
11
|
+
export const firstValue = makeDialectFirstValue("mysql")
|
|
12
|
+
|
|
13
|
+
/** Last value in a MySQL window frame. */
|
|
14
|
+
export const lastValue = makeDialectLastValue("mysql")
|