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.
Files changed (58) hide show
  1. package/dist/index.js +663 -261
  2. package/dist/mysql.js +2840 -492
  3. package/dist/postgres/metadata.js +113 -14
  4. package/dist/postgres.js +2596 -285
  5. package/dist/sqlite.js +2783 -450
  6. package/dist/standard.js +663 -261
  7. package/package.json +2 -3
  8. package/src/internal/analytics.ts +264 -0
  9. package/src/internal/coercion/errors.d.ts +1 -1
  10. package/src/internal/coercion/errors.ts +1 -1
  11. package/src/internal/custom-sql-renderer.ts +20 -0
  12. package/src/internal/dialect-numeric.ts +332 -0
  13. package/src/internal/dialect-renderers/mysql.ts +68 -13
  14. package/src/internal/dialect-renderers/postgres.ts +83 -13
  15. package/src/internal/dialect-renderers/sqlite.ts +57 -13
  16. package/src/internal/dynamic.ts +131 -0
  17. package/src/internal/executor.ts +198 -7
  18. package/src/internal/expression-ast.ts +62 -1
  19. package/src/internal/fragment.ts +120 -0
  20. package/src/internal/function-constraints.ts +105 -0
  21. package/src/internal/grouping-key.ts +26 -0
  22. package/src/internal/numeric.ts +204 -0
  23. package/src/internal/query.ts +13 -1
  24. package/src/internal/runtime/normalize.ts +5 -2
  25. package/src/internal/runtime/schema.ts +2 -1
  26. package/src/internal/standard-dsl.ts +43 -20
  27. package/src/internal/window-frame.ts +30 -0
  28. package/src/internal/window-renderer.ts +25 -0
  29. package/src/mysql/executor.ts +126 -45
  30. package/src/mysql/function/aggregate.ts +87 -1
  31. package/src/mysql/function/index.ts +5 -2
  32. package/src/mysql/function/numeric.ts +185 -0
  33. package/src/mysql/function/temporal.ts +6 -6
  34. package/src/mysql/function/window.ts +12 -0
  35. package/src/mysql/internal/dsl.ts +38 -20
  36. package/src/mysql.ts +2 -0
  37. package/src/postgres/executor.ts +111 -45
  38. package/src/postgres/function/aggregate.ts +124 -1
  39. package/src/postgres/function/core.ts +2 -1
  40. package/src/postgres/function/index.ts +19 -1
  41. package/src/postgres/function/numeric.ts +246 -0
  42. package/src/postgres/function/window.ts +12 -0
  43. package/src/postgres/internal/dsl.ts +38 -20
  44. package/src/sqlite/executor.ts +96 -45
  45. package/src/sqlite/function/aggregate.ts +90 -1
  46. package/src/sqlite/function/index.ts +5 -2
  47. package/src/sqlite/function/numeric.ts +139 -0
  48. package/src/sqlite/function/window.ts +12 -0
  49. package/src/sqlite/internal/dsl.ts +38 -20
  50. package/src/sqlite.ts +2 -0
  51. package/src/standard/function/core.ts +8 -1
  52. package/src/standard/function/index.ts +18 -11
  53. package/src/standard/function/string.ts +1 -1
  54. package/src/standard/function/window.ts +10 -1
  55. package/src/standard/query.ts +19 -7
  56. package/src/standard/type.ts +16 -0
  57. package/src/standard.ts +4 -0
  58. package/src/standard/function/temporal.ts +0 -78
@@ -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
- dialect: "mysql",
110
- execute(plan) {
111
- const rendered = renderer.render(plan)
112
- return Effect.mapError(
113
- Effect.flatMap(
114
- sqlDriver.execute(rendered),
115
- (rows) => Effect.try({
116
- try: () => CoreExecutor.decodeRows(rendered, plan, rows, { driverMode, valueMappings }),
117
- catch: (error) => error as RowDecodeError
118
- })
119
- ),
120
- (error) => {
121
- if (typeof error === "object" && error !== null && "_tag" in error && error._tag === "RowDecodeError") {
122
- return error as RowDecodeError
123
- }
124
- const normalized = normalizeMysqlDriverError(error, rendered)
125
- return CoreExecutor.hasWriteCapability(plan)
126
- ? normalized
127
- : narrowMysqlDriverErrorForReadQuery(normalized)
128
- }
129
- ) as Effect.Effect<any, any, Context>
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
- /** MySQL aggregate functions. */
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 { over, rowNumber, rank, denseRank } from "./window.js"
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.timestamp(),
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.time(),
83
- LocalTimeStringSchema
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.timestamp(),
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.timestamp(),
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")