turbine-orm 0.90.2 → 0.91.0-next.32b6789
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/README.md +23 -24
- package/dist/cjs/dialect.d.ts +26 -1
- package/dist/cjs/dialect.js +4 -0
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/mssql.js +2 -0
- package/dist/cjs/mysql.js +3 -0
- package/dist/cjs/powdb.js +2 -0
- package/dist/cjs/powql.d.ts +5 -2
- package/dist/cjs/powql.js +49 -28
- package/dist/cjs/prisma-compat.d.ts +2 -2
- package/dist/cjs/prisma-compat.js +8 -5
- package/dist/cjs/query/aggregates.js +74 -4
- package/dist/cjs/query/builder.d.ts +20 -7
- package/dist/cjs/query/builder.js +31 -22
- package/dist/cjs/query/expr.d.ts +47 -0
- package/dist/cjs/query/expr.js +129 -3
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/option-surface.js +3 -0
- package/dist/cjs/query/types.d.ts +107 -7
- package/dist/cjs/query/writes.d.ts +14 -2
- package/dist/cjs/query/writes.js +65 -18
- package/dist/cjs/sqlite.js +6 -0
- package/dist/dialect.d.ts +26 -1
- package/dist/dialect.js +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/mssql.js +2 -0
- package/dist/mysql.js +3 -0
- package/dist/powdb.js +2 -0
- package/dist/powql.d.ts +5 -2
- package/dist/powql.js +49 -28
- package/dist/prisma-compat.d.ts +2 -2
- package/dist/prisma-compat.js +8 -5
- package/dist/query/aggregates.js +74 -4
- package/dist/query/builder.d.ts +20 -7
- package/dist/query/builder.js +31 -22
- package/dist/query/expr.d.ts +47 -0
- package/dist/query/expr.js +128 -3
- package/dist/query/index.d.ts +1 -1
- package/dist/query/option-surface.js +3 -0
- package/dist/query/types.d.ts +107 -7
- package/dist/query/writes.d.ts +14 -2
- package/dist/query/writes.js +63 -18
- package/dist/sqlite.js +6 -0
- package/package.json +1 -1
- package/skills/turbine-orm/SKILL.md +8 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { PgCompatPool } from '../pg-types.js';
|
|
14
14
|
import type { SchemaMetadata } from '../schema.js';
|
|
15
|
-
import type { AggregateArgs, AggregateResultOf, CountArgs, CreateArgs, CreateManyArgs, DefaultInputTypes, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, GroupByResult, QueryResult, TableInputTypes, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs, WithClause } from './types.js';
|
|
15
|
+
import type { AggregateArgs, AggregateResultOf, CountArgs, CreateArgs, CreateManyArgs, CreateManyResult, DefaultInputTypes, DeleteArgs, DeleteManyArgs, FieldPercentiles, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, GroupByResult, PercentileKeysChecked, QueryResult, TableInputTypes, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs, WithClause } from './types.js';
|
|
16
16
|
/**
|
|
17
17
|
* Marginal cost of keeping a to-one relation on the JOIN plan, per parent row.
|
|
18
18
|
*
|
|
@@ -1180,7 +1180,12 @@ export declare class QueryInterface<T extends object, R extends object = {}, M e
|
|
|
1180
1180
|
* writes.ts because relations.ts imports writes.ts.
|
|
1181
1181
|
*/
|
|
1182
1182
|
private resolveWriteProjection;
|
|
1183
|
-
buildCreateMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateManyArgs<T, M['createMany'], S, O>): DeferredQuery<FieldResult<T, S, O
|
|
1183
|
+
buildCreateMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined, C extends boolean | undefined = undefined>(args: CreateManyArgs<T, M['createMany'], S, O, C>): DeferredQuery<CreateManyResult<FieldResult<T, S, O>, C>>;
|
|
1184
|
+
/**
|
|
1185
|
+
* `createMany`'s projection: the caller's `select` / `omit`, or
|
|
1186
|
+
* {@link writesMod.COUNT_ONLY} for `returning: false`, which takes neither.
|
|
1187
|
+
*/
|
|
1188
|
+
private createManyProjection;
|
|
1184
1189
|
buildUpdate<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpdateArgs<T, R, S, O, M['update']>): DeferredQuery<FieldResult<T, S, O>>;
|
|
1185
1190
|
buildDelete<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: DeleteArgs<T, R, S, O>): DeferredQuery<FieldResult<T, S, O>>;
|
|
1186
1191
|
buildUpsert<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpsertArgs<T, R, S, O, M['whereUnique'], M['createMany'], M['updateMany']>): DeferredQuery<FieldResult<T, S, O>>;
|
|
@@ -1416,7 +1421,7 @@ export declare class QueryInterface<T extends object, R extends object = {}, M e
|
|
|
1416
1421
|
findUniqueOrThrow<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: FindUniqueArgs<T, R, W, S, O, M['whereUnique']>): Promise<QueryResult<T, R, W, S, O>>;
|
|
1417
1422
|
buildFindUniqueOrThrow<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: FindUniqueArgs<T, R, W, S, O, M['whereUnique']>): DeferredQuery<QueryResult<T, R, W, S, O>>;
|
|
1418
1423
|
create<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateArgs<T, R, S, O, M['create']>): Promise<FieldResult<T, S, O>>;
|
|
1419
|
-
createMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateManyArgs<T, M['createMany'], S, O>): Promise<FieldResult<T, S, O
|
|
1424
|
+
createMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined, C extends boolean | undefined = undefined>(args: CreateManyArgs<T, M['createMany'], S, O, C>): Promise<CreateManyResult<FieldResult<T, S, O>, C>>;
|
|
1420
1425
|
update<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpdateArgs<T, R, S, O, M['update']>): Promise<FieldResult<T, S, O>>;
|
|
1421
1426
|
private nestedCreate;
|
|
1422
1427
|
private nestedUpdate;
|
|
@@ -1477,10 +1482,18 @@ export declare class QueryInterface<T extends object, R extends object = {}, M e
|
|
|
1477
1482
|
* {@link GroupByResult}). Grouping by a JSON-path key yields a runtime alias
|
|
1478
1483
|
* that cannot be typed, so those columns are not projected onto the row type.
|
|
1479
1484
|
*/
|
|
1480
|
-
groupBy<A extends GroupByArgs<T, R>>(args: A): Promise<GroupByResult<T, A>[]>;
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1485
|
+
groupBy<A extends GroupByArgs<T, R>>(args: A & PercentileKeysChecked<T, A>): Promise<GroupByResult<T, A>[]>;
|
|
1486
|
+
/** Args of a generic type: field percentiles only (see {@link FieldPercentiles}). */
|
|
1487
|
+
groupBy<A extends Omit<GroupByArgs<T, R>, '_percentile'> & FieldPercentiles<T>>(args: A): Promise<GroupByResult<T, A>[]>;
|
|
1488
|
+
buildGroupBy<A extends GroupByArgs<T, R>>(args: A & PercentileKeysChecked<T, A>): DeferredQuery<GroupByResult<T, A>[]>;
|
|
1489
|
+
/** Args of a generic type: field percentiles only (see {@link FieldPercentiles}). */
|
|
1490
|
+
buildGroupBy<A extends Omit<GroupByArgs<T, R>, '_percentile'> & FieldPercentiles<T>>(args: A): DeferredQuery<GroupByResult<T, A>[]>;
|
|
1491
|
+
buildAggregate<A extends AggregateArgs<T, R>>(args: A & PercentileKeysChecked<T, A>): DeferredQuery<AggregateResultOf<T, A>>;
|
|
1492
|
+
/** Args of a generic type: field percentiles only (see {@link FieldPercentiles}). */
|
|
1493
|
+
buildAggregate<A extends Omit<AggregateArgs<T, R>, '_percentile'> & FieldPercentiles<T>>(args: A): DeferredQuery<AggregateResultOf<T, A>>;
|
|
1494
|
+
aggregate<A extends AggregateArgs<T, R>>(args: A & PercentileKeysChecked<T, A>): Promise<AggregateResultOf<T, A>>;
|
|
1495
|
+
/** Args of a generic type: field percentiles only (see {@link FieldPercentiles}). */
|
|
1496
|
+
aggregate<A extends Omit<AggregateArgs<T, R>, '_percentile'> & FieldPercentiles<T>>(args: A): Promise<AggregateResultOf<T, A>>;
|
|
1484
1497
|
private resolveColumns;
|
|
1485
1498
|
withFingerprint(withClause: WithClause | undefined, table?: string, depth?: number): string;
|
|
1486
1499
|
private collectWithParams;
|
|
@@ -3048,7 +3048,24 @@ class QueryInterface {
|
|
|
3048
3048
|
}
|
|
3049
3049
|
buildCreateMany(args) {
|
|
3050
3050
|
args = this.normalizeArgs(args);
|
|
3051
|
-
return writesMod.buildCreateMany(this.ctx, args, 'refuse', this.
|
|
3051
|
+
return writesMod.buildCreateMany(this.ctx, args, 'refuse', this.createManyProjection(args));
|
|
3052
|
+
}
|
|
3053
|
+
/**
|
|
3054
|
+
* `createMany`'s projection: the caller's `select` / `omit`, or
|
|
3055
|
+
* {@link writesMod.COUNT_ONLY} for `returning: false`, which takes neither.
|
|
3056
|
+
*/
|
|
3057
|
+
createManyProjection(args) {
|
|
3058
|
+
const { returning } = args;
|
|
3059
|
+
if (returning === undefined || returning === true)
|
|
3060
|
+
return this.resolveWriteProjection(args);
|
|
3061
|
+
if (returning !== false) {
|
|
3062
|
+
throw new errors_js_1.ValidationError(`createMany on "${this.table}": returning must be true or false, received ${typeof returning}.`);
|
|
3063
|
+
}
|
|
3064
|
+
if (args.select !== undefined || args.omit !== undefined) {
|
|
3065
|
+
throw new errors_js_1.ValidationError(`createMany on "${this.table}": returning: false returns { count } and no rows, so it takes no ` +
|
|
3066
|
+
`\`${args.select !== undefined ? 'select' : 'omit'}\`. Drop one of them.`);
|
|
3067
|
+
}
|
|
3068
|
+
return writesMod.COUNT_ONLY;
|
|
3052
3069
|
}
|
|
3053
3070
|
buildUpdate(args) {
|
|
3054
3071
|
args = this.normalizeArgs(args);
|
|
@@ -4441,13 +4458,17 @@ class QueryInterface {
|
|
|
4441
4458
|
async createMany(args) {
|
|
4442
4459
|
args = this.normalizeArgs(args);
|
|
4443
4460
|
return this.executeWithMiddleware('createMany', args, async () => {
|
|
4444
|
-
const projection = this.
|
|
4461
|
+
const projection = this.createManyProjection(args);
|
|
4462
|
+
const countOnly = writesMod.isCountOnly(projection);
|
|
4445
4463
|
const base = args;
|
|
4446
4464
|
// One statement when the rows fit the engine's per-statement limits,
|
|
4447
4465
|
// else chunks that do, all in one transaction so the rows land together
|
|
4448
4466
|
// or not at all. (`buildCreateMany`, one statement for a pipeline,
|
|
4449
4467
|
// refuses instead.)
|
|
4450
|
-
|
|
4468
|
+
// Splitting a call that fits one statement needs a transaction, which a
|
|
4469
|
+
// pool without connect() cannot open outside one.
|
|
4470
|
+
const canSplit = this.txScoped || typeof this.pool.connect === 'function';
|
|
4471
|
+
const { chunks, rowsPerChunk } = writesMod.planCreateMany(this.ctx, base, projection, canSplit);
|
|
4451
4472
|
const run = async () => {
|
|
4452
4473
|
// An engine that applies two rows whose onConflict keys are equal only
|
|
4453
4474
|
// under the column's collation (SQLite's NOCASE) as an insert and an
|
|
@@ -4463,13 +4484,18 @@ class QueryInterface {
|
|
|
4463
4484
|
const reselects = this.dialect.resultStrategy === 'reselect';
|
|
4464
4485
|
return this.inOneTransaction(async (exec) => {
|
|
4465
4486
|
const rows = [];
|
|
4487
|
+
let count = 0;
|
|
4466
4488
|
for (const chunk of chunks) {
|
|
4467
4489
|
const result = reselects && chunk.reselect
|
|
4468
4490
|
? await chunk.reselect(exec)
|
|
4469
4491
|
: await exec(chunk.sql, chunk.params, chunk.preparedName);
|
|
4470
|
-
|
|
4492
|
+
const out = chunk.transform(result);
|
|
4493
|
+
if (countOnly)
|
|
4494
|
+
count += out.count;
|
|
4495
|
+
else
|
|
4496
|
+
rows.push(...out);
|
|
4471
4497
|
}
|
|
4472
|
-
return rows;
|
|
4498
|
+
return (countOnly ? { count } : rows);
|
|
4473
4499
|
}, args.timeout, `createMany of ${base.data.length} rows`, `Insert at most ${rowsPerChunk} rows per call, which fits one statement, or pass a pool that can check ` +
|
|
4474
4500
|
'out a connection.');
|
|
4475
4501
|
};
|
|
@@ -4759,17 +4785,6 @@ class QueryInterface {
|
|
|
4759
4785
|
preparedName: entry.name,
|
|
4760
4786
|
};
|
|
4761
4787
|
}
|
|
4762
|
-
// -------------------------------------------------------------------------
|
|
4763
|
-
// groupBy (with aggregate functions)
|
|
4764
|
-
// -------------------------------------------------------------------------
|
|
4765
|
-
/**
|
|
4766
|
-
* Group rows and compute per-group aggregates (Prisma-style). The result row
|
|
4767
|
-
* type is INFERRED from the args: each `by` field carries its entity field
|
|
4768
|
-
* type, `_count` is always a number, and each requested `_sum` / `_avg` /
|
|
4769
|
-
* `_min` / `_max` block maps its fields to properly typed values (see
|
|
4770
|
-
* {@link GroupByResult}). Grouping by a JSON-path key yields a runtime alias
|
|
4771
|
-
* that cannot be typed, so those columns are not projected onto the row type.
|
|
4772
|
-
*/
|
|
4773
4788
|
async groupBy(args) {
|
|
4774
4789
|
args = this.normalizeArgs(args);
|
|
4775
4790
|
return this.executeWithMiddleware('groupBy', args, async () => {
|
|
@@ -4778,9 +4793,6 @@ class QueryInterface {
|
|
|
4778
4793
|
return deferred.transform(result);
|
|
4779
4794
|
});
|
|
4780
4795
|
}
|
|
4781
|
-
// ---------------------------------------------------------------------------
|
|
4782
|
-
// Aggregate / groupBy compilation (extracted to aggregates.ts).
|
|
4783
|
-
// ---------------------------------------------------------------------------
|
|
4784
4796
|
buildGroupBy(args) {
|
|
4785
4797
|
const normalized = this.normalizeArgs(args);
|
|
4786
4798
|
return this.withPreparedName(() => aggMod.buildGroupBy(this.ctx, normalized));
|
|
@@ -4789,9 +4801,6 @@ class QueryInterface {
|
|
|
4789
4801
|
const normalized = this.normalizeArgs(args);
|
|
4790
4802
|
return this.withPreparedName(() => aggMod.buildAggregate(this.ctx, normalized));
|
|
4791
4803
|
}
|
|
4792
|
-
// -------------------------------------------------------------------------
|
|
4793
|
-
// aggregate, standalone aggregation without groupBy
|
|
4794
|
-
// -------------------------------------------------------------------------
|
|
4795
4804
|
async aggregate(args) {
|
|
4796
4805
|
args = this.normalizeArgs(args);
|
|
4797
4806
|
return this.executeWithMiddleware('aggregate', args, async () => {
|
package/dist/cjs/query/expr.d.ts
CHANGED
|
@@ -124,6 +124,19 @@ export declare const expr: Readonly<{
|
|
|
124
124
|
* value). Refused anywhere else.
|
|
125
125
|
*/
|
|
126
126
|
excluded<F extends string>(field: F): ExcludedRef<F>;
|
|
127
|
+
/**
|
|
128
|
+
* The seconds from `from` to `to`, two timestamp columns of the row:
|
|
129
|
+
* `to - from`, fractional, negative when `to` is earlier, NULL when either is.
|
|
130
|
+
* Both columns must be timestamptz, or both zone-less timestamp. Accepted in
|
|
131
|
+
* `_percentile`'s `of` (PostgreSQL); refused in update data in this release.
|
|
132
|
+
*
|
|
133
|
+
* ```ts
|
|
134
|
+
* await db.jobs.aggregate({
|
|
135
|
+
* _percentile: { wait: { of: expr.secondsBetween(col('requestedAt'), col('readyAt')), p: [0.5, 0.95] } },
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
secondsBetween(from: ColumnRef, to: ColumnRef): SqlExpr<number>;
|
|
127
140
|
}>;
|
|
128
141
|
/** True only for a node built by {@link expr} (any loaded copy of the package). */
|
|
129
142
|
export declare function isSqlExpr(value: unknown): value is SqlExpr;
|
|
@@ -167,6 +180,12 @@ export interface ExprCompileEnv {
|
|
|
167
180
|
readonly insertColumns?: ReadonlySet<string> | undefined;
|
|
168
181
|
/** Compile a `case` arm's predicate (already pruned and validated) against the target row. */
|
|
169
182
|
predicate(when: Record<string, unknown>): string;
|
|
183
|
+
/**
|
|
184
|
+
* The expression is READ by a query (`_percentile`'s `of`), not assigned:
|
|
185
|
+
* there is no target column, `targetKey` is the result's name, and
|
|
186
|
+
* `expr.secondsBetween` is accepted while `expr.now` is not.
|
|
187
|
+
*/
|
|
188
|
+
readonly read?: true | undefined;
|
|
170
189
|
}
|
|
171
190
|
/** One compiled expression. */
|
|
172
191
|
export interface CompiledSetExpr {
|
|
@@ -197,6 +216,34 @@ export interface CompiledSetExpr {
|
|
|
197
216
|
* cache key does not capture.
|
|
198
217
|
*/
|
|
199
218
|
export declare function compileSetExpr(env: ExprCompileEnv, value: SqlExpr | ColumnRef): CompiledSetExpr;
|
|
219
|
+
/** What {@link compileReadExpr} needs from its caller (the aggregate module). */
|
|
220
|
+
export interface ReadExprEnv {
|
|
221
|
+
readonly qi: BuilderCtx;
|
|
222
|
+
/** The result's name, for messages. */
|
|
223
|
+
readonly name: string;
|
|
224
|
+
/** For messages: `aggregate _percentile`, `groupBy _percentile`. */
|
|
225
|
+
readonly operation: string;
|
|
226
|
+
/** Bind one number, bigint or null and return its placeholder, cast to its class's type. */
|
|
227
|
+
bind(value: number | bigint | null, cls: 'int' | 'dec'): string;
|
|
228
|
+
/** Compile a `case` arm's predicate (already pruned and validated) against the row. */
|
|
229
|
+
predicate(when: Record<string, unknown>): string;
|
|
230
|
+
}
|
|
231
|
+
/** One compiled read expression. */
|
|
232
|
+
export interface CompiledReadExpr {
|
|
233
|
+
readonly sql: string;
|
|
234
|
+
/** Value-free shape, as {@link CompiledSetExpr.fingerprint}. */
|
|
235
|
+
readonly fingerprint: string;
|
|
236
|
+
/** Columns the expression reads as a value (columns only a case arm's `when` reads are not). */
|
|
237
|
+
readonly values: ReadonlySet<string>;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Compile a numeric expression a query READS (`_percentile`'s `of`): the walk
|
|
241
|
+
* {@link compileSetExpr} runs, with no target column. Bound values must be
|
|
242
|
+
* numbers or bigints and are cast by their own class, the result must be
|
|
243
|
+
* numeric, and `col()` alone is refused because a field key already says it.
|
|
244
|
+
* Not gated on `updateExpressions`: the caller's own engine check applies.
|
|
245
|
+
*/
|
|
246
|
+
export declare function compileReadExpr(read: ReadExprEnv, value: unknown): CompiledReadExpr;
|
|
200
247
|
/**
|
|
201
248
|
* Rewrite every `expr.excluded(f)` in a single-row upsert's `update` to the
|
|
202
249
|
* value `create` supplies for `f`. The incoming row of a one-row upsert IS
|
package/dist/cjs/query/expr.js
CHANGED
|
@@ -32,6 +32,7 @@ exports.plainExprShapeMessage = plainExprShapeMessage;
|
|
|
32
32
|
exports.exprNotAllowedMessage = exprNotAllowedMessage;
|
|
33
33
|
exports.assertExprTarget = assertExprTarget;
|
|
34
34
|
exports.compileSetExpr = compileSetExpr;
|
|
35
|
+
exports.compileReadExpr = compileReadExpr;
|
|
35
36
|
exports.resolveExcludedFromCreate = resolveExcludedFromCreate;
|
|
36
37
|
const errors_js_1 = require("../errors.js");
|
|
37
38
|
const column_ref_js_1 = require("./column-ref.js");
|
|
@@ -55,6 +56,7 @@ const EXPR_KINDS = new Set([
|
|
|
55
56
|
'case',
|
|
56
57
|
'now',
|
|
57
58
|
'excluded',
|
|
59
|
+
'secondsBetween',
|
|
58
60
|
]);
|
|
59
61
|
/** Operand count bounds for coalesce / greatest / least. */
|
|
60
62
|
const MIN_VARIADIC = 2;
|
|
@@ -172,6 +174,20 @@ function caseNode(arms, otherwise) {
|
|
|
172
174
|
assertOperand('case', arms.length, otherwise);
|
|
173
175
|
return makeNode('case', frozen, otherwise === undefined ? undefined : { else: otherwise });
|
|
174
176
|
}
|
|
177
|
+
function secondsBetweenNode(from, to) {
|
|
178
|
+
[from, to].forEach((operand, i) => {
|
|
179
|
+
if ((0, column_ref_js_1.isColumnRef)(operand))
|
|
180
|
+
return;
|
|
181
|
+
const at = `expr.secondsBetween(): operand ${i + 1}`;
|
|
182
|
+
if ((0, column_ref_js_1.isPlainColumnRefShape)(operand)) {
|
|
183
|
+
const name = JSON.stringify(operand.col);
|
|
184
|
+
throw new errors_js_1.ValidationError(`${at} is a plain { col: ${name} } object, not a column reference. Build one with col(${name}).`);
|
|
185
|
+
}
|
|
186
|
+
throw new errors_js_1.ValidationError(`${at} is ${operand === undefined ? 'undefined' : describeOperand(operand)}. Both operands are col() of a ` +
|
|
187
|
+
'timestamp column of the row.');
|
|
188
|
+
});
|
|
189
|
+
return makeNode('secondsBetween', [from, to]);
|
|
190
|
+
}
|
|
175
191
|
/**
|
|
176
192
|
* Builders for SQL expressions in update data. Each returns a frozen, branded
|
|
177
193
|
* node; the SQL is generated per engine when the update compiles.
|
|
@@ -257,6 +273,21 @@ exports.expr = Object.freeze({
|
|
|
257
273
|
Object.defineProperty(node, exports.EXCLUDED_REF, { value: true, enumerable: false });
|
|
258
274
|
return Object.freeze(node);
|
|
259
275
|
},
|
|
276
|
+
/**
|
|
277
|
+
* The seconds from `from` to `to`, two timestamp columns of the row:
|
|
278
|
+
* `to - from`, fractional, negative when `to` is earlier, NULL when either is.
|
|
279
|
+
* Both columns must be timestamptz, or both zone-less timestamp. Accepted in
|
|
280
|
+
* `_percentile`'s `of` (PostgreSQL); refused in update data in this release.
|
|
281
|
+
*
|
|
282
|
+
* ```ts
|
|
283
|
+
* await db.jobs.aggregate({
|
|
284
|
+
* _percentile: { wait: { of: expr.secondsBetween(col('requestedAt'), col('readyAt')), p: [0.5, 0.95] } },
|
|
285
|
+
* });
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
secondsBetween(from, to) {
|
|
289
|
+
return secondsBetweenNode(from, to);
|
|
290
|
+
},
|
|
260
291
|
});
|
|
261
292
|
/** True only for a node built by {@link expr} (any loaded copy of the package). */
|
|
262
293
|
function isSqlExpr(value) {
|
|
@@ -448,6 +479,12 @@ function isBareStructuredCopy(qi, column, value) {
|
|
|
448
479
|
const type = columnType(qi, column);
|
|
449
480
|
return source !== undefined && type !== undefined && columnType(qi, source) === type;
|
|
450
481
|
}
|
|
482
|
+
/** Where an expression sits, for messages. */
|
|
483
|
+
function site(env) {
|
|
484
|
+
return env.read
|
|
485
|
+
? `${env.operation} "${env.targetKey}" on "${env.qi.table}"`
|
|
486
|
+
: `${env.operation} data on "${env.qi.table}.${env.targetKey}"`;
|
|
487
|
+
}
|
|
451
488
|
/**
|
|
452
489
|
* Compile one expression (or `col()` reference) assigned to `env.targetKey`.
|
|
453
490
|
* The single traversal: SQL, params (through `env.bind`, in emission order)
|
|
@@ -468,7 +505,7 @@ function compileSetExpr(env, value) {
|
|
|
468
505
|
const { qi } = env;
|
|
469
506
|
assertEngine(qi);
|
|
470
507
|
const column = assertExprTarget(qi, env.targetKey, value, env.operation);
|
|
471
|
-
const state = { env, targetType: columnType(qi, column), reads: new Set() };
|
|
508
|
+
const state = { env, targetType: columnType(qi, column), reads: new Set(), values: new Set() };
|
|
472
509
|
const part = walkOperand(state, value, false, 0);
|
|
473
510
|
if (part.cls === 'dec' && numClassOfType(state.targetType) === 'int') {
|
|
474
511
|
const what = (0, column_ref_js_1.isColumnRef)(value) ? `col(${JSON.stringify(value.col)})` : `expr.${value.kind}()`;
|
|
@@ -480,6 +517,48 @@ function compileSetExpr(env, value) {
|
|
|
480
517
|
}
|
|
481
518
|
return { sql: part.sql, fingerprint: part.fp, reads: state.reads };
|
|
482
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Compile a numeric expression a query READS (`_percentile`'s `of`): the walk
|
|
522
|
+
* {@link compileSetExpr} runs, with no target column. Bound values must be
|
|
523
|
+
* numbers or bigints and are cast by their own class, the result must be
|
|
524
|
+
* numeric, and `col()` alone is refused because a field key already says it.
|
|
525
|
+
* Not gated on `updateExpressions`: the caller's own engine check applies.
|
|
526
|
+
*/
|
|
527
|
+
function compileReadExpr(read, value) {
|
|
528
|
+
const { qi, name, operation } = read;
|
|
529
|
+
const at = `${operation} "${name}" on "${qi.table}"`;
|
|
530
|
+
if ((0, column_ref_js_1.isColumnRef)(value)) {
|
|
531
|
+
throw new errors_js_1.ValidationError(`${at}: \`of\` is col(${JSON.stringify(value.col)}) alone. Use the field as the key instead: ` +
|
|
532
|
+
`{ ${JSON.stringify(value.col)}: { p } }.`);
|
|
533
|
+
}
|
|
534
|
+
if (!isSqlExpr(value)) {
|
|
535
|
+
throw new errors_js_1.ValidationError(`${at}: \`of\` is ${value === undefined ? 'undefined' : describeOperand(value)}. Build it with expr ` +
|
|
536
|
+
"(import { col, expr } from 'turbine-orm'); a plain object is never an expression.");
|
|
537
|
+
}
|
|
538
|
+
const env = {
|
|
539
|
+
qi,
|
|
540
|
+
targetKey: name,
|
|
541
|
+
operation,
|
|
542
|
+
read: true,
|
|
543
|
+
bind: (leaf, arithmetic) => {
|
|
544
|
+
if (leaf !== null && typeof leaf !== 'number' && typeof leaf !== 'bigint') {
|
|
545
|
+
throw new errors_js_1.ValidationError(`${at}: a bound value is ${leaf instanceof Date ? 'a Date' : leaf instanceof Uint8Array ? 'a Uint8Array' : `a ${typeof leaf}`}. ` +
|
|
546
|
+
'An expression a query reads binds numbers and bigints only.');
|
|
547
|
+
}
|
|
548
|
+
const integer = (arithmetic ?? numClassOfValue(leaf)) !== 'dec' && (typeof leaf !== 'number' || Number.isSafeInteger(leaf));
|
|
549
|
+
return read.bind(leaf, integer ? 'int' : 'dec');
|
|
550
|
+
},
|
|
551
|
+
column: (column) => qi.q(column),
|
|
552
|
+
predicate: (when) => read.predicate(when),
|
|
553
|
+
};
|
|
554
|
+
const state = { env, targetType: undefined, reads: new Set(), values: new Set() };
|
|
555
|
+
const part = walkOperand(state, value, false, 0);
|
|
556
|
+
if (part.cls === 'other' || part.cls === 'null') {
|
|
557
|
+
throw new errors_js_1.ValidationError(`${at}: \`of\` is ${part.cls === 'null' ? 'NULL for every row' : 'not numeric (it reads a non-numeric column)'}. ` +
|
|
558
|
+
'A percentile orders numbers: numeric columns, bound numbers, and expr.secondsBetween() of two timestamps.');
|
|
559
|
+
}
|
|
560
|
+
return { sql: part.sql, fingerprint: part.fp, values: state.values };
|
|
561
|
+
}
|
|
483
562
|
function assertEngine(qi) {
|
|
484
563
|
if (qi.dialect.capabilities.updateExpressions)
|
|
485
564
|
return;
|
|
@@ -492,6 +571,7 @@ function walkOperand(state, value, inArithmetic, depth) {
|
|
|
492
571
|
if ((0, column_ref_js_1.isColumnRef)(value)) {
|
|
493
572
|
const column = resolveOperandColumn(state, value.col, 'col', inArithmetic);
|
|
494
573
|
state.reads.add(column);
|
|
574
|
+
state.values.add(column);
|
|
495
575
|
return { sql: state.env.column(column), fp: `c:${column}`, cls: numClassOfType(columnType(state.env.qi, column)) };
|
|
496
576
|
}
|
|
497
577
|
if (isSqlExpr(value))
|
|
@@ -503,7 +583,7 @@ function walkOperand(state, value, inArithmetic, depth) {
|
|
|
503
583
|
// greatest, least or a case arm (`expr.add(expr.coalesce('x', 1), 2)`).
|
|
504
584
|
if (inArithmetic &&
|
|
505
585
|
(typeof value === 'string' || typeof value === 'boolean' || value instanceof Date || value instanceof Uint8Array)) {
|
|
506
|
-
throw new errors_js_1.ValidationError(`An arithmetic operand in ${state.env
|
|
586
|
+
throw new errors_js_1.ValidationError(`An arithmetic operand in ${site(state.env)} is a ` +
|
|
507
587
|
`${value instanceof Date ? 'Date' : value instanceof Uint8Array ? 'Uint8Array' : typeof value}. ` +
|
|
508
588
|
'Arithmetic takes numbers, bigints, col() of a numeric column, or numeric expressions.');
|
|
509
589
|
}
|
|
@@ -520,7 +600,7 @@ function resolveOperandColumn(state, field, how, inArithmetic) {
|
|
|
520
600
|
const call = how === 'col' ? `col(${JSON.stringify(field)})` : `expr.excluded(${JSON.stringify(field)})`;
|
|
521
601
|
const column = (0, utils_js_1.resolveColumnName)(qi.tableMeta, field);
|
|
522
602
|
if (column === undefined) {
|
|
523
|
-
throw new errors_js_1.ValidationError(`${call} in ${state.env
|
|
603
|
+
throw new errors_js_1.ValidationError(`${call} in ${site(state.env)} names no column. ` +
|
|
524
604
|
`Known fields: ${Object.keys(qi.tableMeta.columnMap).join(', ') || '(none)'}.`);
|
|
525
605
|
}
|
|
526
606
|
const cls = columnClass(columnType(qi, column));
|
|
@@ -580,6 +660,9 @@ function walkNode(state, node, inArithmetic, depth) {
|
|
|
580
660
|
case 'case':
|
|
581
661
|
return walkCase(state, node, inArithmetic, depth);
|
|
582
662
|
case 'now': {
|
|
663
|
+
if (env.read) {
|
|
664
|
+
throw new errors_js_1.ValidationError(`expr.now() in ${site(env)}: an expression a query reads takes the row's columns and bound numbers only.`);
|
|
665
|
+
}
|
|
583
666
|
if (inArithmetic) {
|
|
584
667
|
throw new errors_js_1.ValidationError('expr.now() cannot be an arithmetic operand: date arithmetic is not supported.');
|
|
585
668
|
}
|
|
@@ -603,6 +686,8 @@ function walkNode(state, node, inArithmetic, depth) {
|
|
|
603
686
|
}
|
|
604
687
|
return { sql: env.excluded(column), fp: `x:${column}`, cls: numClassOfType(columnType(qi, column)) };
|
|
605
688
|
}
|
|
689
|
+
case 'secondsBetween':
|
|
690
|
+
return walkSecondsBetween(state, node);
|
|
606
691
|
default:
|
|
607
692
|
throw new errors_js_1.ValidationError(`Unknown expression kind "${String(node.kind)}".`);
|
|
608
693
|
}
|
|
@@ -634,6 +719,47 @@ function walkCase(state, node, inArithmetic, depth) {
|
|
|
634
719
|
cls: combineClasses(classes),
|
|
635
720
|
};
|
|
636
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* `expr.secondsBetween(from, to)`: `EXTRACT(EPOCH FROM (to - from))`, the
|
|
724
|
+
* PostgreSQL spelling. It is read only by `_percentile`, which only an engine
|
|
725
|
+
* with `supportsPercentileAggregates` runs, and that SQL (`percentile_cont ...
|
|
726
|
+
* WITHIN GROUP`) is PostgreSQL's too. Both columns must have one temporal
|
|
727
|
+
* kind: subtracting a timestamptz from a zone-less timestamp converts through
|
|
728
|
+
* the session time zone.
|
|
729
|
+
*/
|
|
730
|
+
function walkSecondsBetween(state, node) {
|
|
731
|
+
const { env } = state;
|
|
732
|
+
const { qi } = env;
|
|
733
|
+
if (!env.read) {
|
|
734
|
+
throw new errors_js_1.ValidationError(`expr.secondsBetween() in ${site(env)}: it is accepted in _percentile's \`of\` in this release, not in ` +
|
|
735
|
+
'update data. Compute the value in JavaScript and write it as a plain value.');
|
|
736
|
+
}
|
|
737
|
+
const kinds = [];
|
|
738
|
+
const columns = node.args.map((arg) => {
|
|
739
|
+
const field = arg.col;
|
|
740
|
+
const column = resolveOperandColumn(state, field, 'col', false);
|
|
741
|
+
const type = columnType(qi, column);
|
|
742
|
+
const kind = temporalKind(type);
|
|
743
|
+
if (kind !== 'tz' && kind !== 'timestamp') {
|
|
744
|
+
throw new errors_js_1.ValidationError(`expr.secondsBetween() in ${site(env)}: col(${JSON.stringify(field)}) is ${type === undefined ? 'a column of unknown type' : `a ${type} column`}. ` +
|
|
745
|
+
'Both operands must be timestamp (or both timestamptz) columns.');
|
|
746
|
+
}
|
|
747
|
+
kinds.push(kind);
|
|
748
|
+
state.reads.add(column);
|
|
749
|
+
state.values.add(column);
|
|
750
|
+
return column;
|
|
751
|
+
});
|
|
752
|
+
if (kinds[0] !== kinds[1]) {
|
|
753
|
+
throw new errors_js_1.ValidationError(`expr.secondsBetween() in ${site(env)} mixes a timestamptz column with a zone-less timestamp column, ` +
|
|
754
|
+
'whose difference depends on the session time zone. Use two columns of one kind.');
|
|
755
|
+
}
|
|
756
|
+
const [from, to] = columns;
|
|
757
|
+
return {
|
|
758
|
+
sql: `EXTRACT(EPOCH FROM (${env.column(to)} - ${env.column(from)}))`,
|
|
759
|
+
fp: `secs(${from},${to})`,
|
|
760
|
+
cls: 'dec',
|
|
761
|
+
};
|
|
762
|
+
}
|
|
637
763
|
/**
|
|
638
764
|
* Prune and validate one arm's `when`: `undefined` keys drop out (the C1 rule
|
|
639
765
|
* for every `where` position), an arm left with no predicate is refused, and
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `import { … } from './query/index.js'` is a drop-in replacement for the
|
|
6
6
|
* former monolithic `import { … } from './query.js'`.
|
|
7
7
|
*/
|
|
8
|
-
export type { AggregateArgs, AggregateResult, ArrayFilter, ColumnRef, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByAggregateSpec, GroupByArgs, GroupByDistinctOn, GroupByResult, HavingClause, JsonEncoding, JsonFilter, JsonPathAggregateTarget, JsonPathGroupKey, JsonPathOrderBy, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderByObject, OrderBySpec, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, RelationOrderBy, RelationOrderByChain, RelationPickBy, RelationPickOrderBy, ResolvedSkipGlobalFilters, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithOrderByObject, WithResult, } from './types.js';
|
|
8
|
+
export type { AggregateArgs, AggregateResult, ArrayFilter, ColumnRef, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, CreateManyCount, CreateManyResult, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByAggregateSpec, GroupByArgs, GroupByDistinctOn, GroupByResult, HavingClause, JsonEncoding, JsonFilter, JsonPathAggregateTarget, JsonPathGroupKey, JsonPathOrderBy, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderByObject, OrderBySpec, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, RelationOrderBy, RelationOrderByChain, RelationPickBy, RelationPickOrderBy, ResolvedSkipGlobalFilters, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithOrderByObject, WithResult, } from './types.js';
|
|
9
9
|
export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, InsertStatementInput, UpsertStatementInput, } from '../dialect.js';
|
|
10
10
|
export { postgresDialect } from '../dialect.js';
|
|
11
11
|
export { col } from './column-ref.js';
|
|
@@ -142,6 +142,9 @@ exports.CREATE_MANY_OPTIONS = {
|
|
|
142
142
|
onConflict: 'internal',
|
|
143
143
|
select: 'internal',
|
|
144
144
|
omit: 'internal',
|
|
145
|
+
// Compat's createMany already resolves to Prisma's { count } and sets this
|
|
146
|
+
// itself; a caller-supplied value would change the shape it relies on.
|
|
147
|
+
returning: 'internal',
|
|
145
148
|
timeout: 'native',
|
|
146
149
|
skipGlobalFilters: 'native',
|
|
147
150
|
};
|
|
@@ -1034,7 +1034,7 @@ export interface CreateArgs<T, R extends object = {}, S extends Record<string, b
|
|
|
1034
1034
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
1035
1035
|
timeout?: number | undefined;
|
|
1036
1036
|
}
|
|
1037
|
-
export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
|
|
1037
|
+
export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined, C extends boolean | undefined = true | undefined> {
|
|
1038
1038
|
/** The rows. A generated client passes its `XCreate` as `D`. */
|
|
1039
1039
|
data: D[];
|
|
1040
1040
|
/** Skip rows that conflict on ANY unique constraint (ON CONFLICT DO NOTHING). */
|
|
@@ -1052,6 +1052,22 @@ export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string
|
|
|
1052
1052
|
select?: FlagsArg<T, S>;
|
|
1053
1053
|
/** Return the written rows without these fields. Same rules as `select`. */
|
|
1054
1054
|
omit?: FlagsArg<T, O>;
|
|
1055
|
+
/**
|
|
1056
|
+
* `false` resolves to `{ count }`, the number of rows written, instead of the
|
|
1057
|
+
* rows. A plain insert then sends no `RETURNING` / `OUTPUT` at all; an
|
|
1058
|
+
* `onConflict` call returns only the key columns it needs to refuse two input
|
|
1059
|
+
* rows that write one table row. The count is the number of rows the call
|
|
1060
|
+
* would otherwise have returned: inserted plus updated, never a row
|
|
1061
|
+
* `skipDuplicates` or `ignore` skipped. Cannot be combined with `select` or
|
|
1062
|
+
* `omit` (E003). On MySQL, `skipDuplicates` with `false` is E017: a skipped
|
|
1063
|
+
* row there counts as written.
|
|
1064
|
+
*
|
|
1065
|
+
* Typed by `C`, which defaults to the rows form, so args typed ahead of the
|
|
1066
|
+
* call (`const args: CreateManyArgs<User> = ...`) still resolve to rows.
|
|
1067
|
+
* Pass `false` as `C` to type count-only args ahead of the call, or
|
|
1068
|
+
* `boolean` for a flag known only at run time (the result is then either).
|
|
1069
|
+
*/
|
|
1070
|
+
returning?: C;
|
|
1055
1071
|
/**
|
|
1056
1072
|
* Opt out of configured {@link GlobalFilters} on the conflict-UPDATE of
|
|
1057
1073
|
* `onConflict` (inserts are never filtered). See {@link SkipGlobalFilters}.
|
|
@@ -1060,6 +1076,16 @@ export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string
|
|
|
1060
1076
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
1061
1077
|
timeout?: number | undefined;
|
|
1062
1078
|
}
|
|
1079
|
+
/** What `createMany({ returning: false })` resolves to. */
|
|
1080
|
+
export interface CreateManyCount {
|
|
1081
|
+
/** Rows written: inserted plus updated. */
|
|
1082
|
+
count: number;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* `createMany`'s result for a `returning` flag `C`: the rows for `true` or no
|
|
1086
|
+
* flag, `{ count }` for `false`, either for a `boolean` known only at run time.
|
|
1087
|
+
*/
|
|
1088
|
+
export type CreateManyResult<Row, C> = [C] extends [false] ? CreateManyCount : [C] extends [true | undefined] ? Row[] : Row[] | CreateManyCount;
|
|
1063
1089
|
/**
|
|
1064
1090
|
* The columns of the primary key, or of one non-partial unique constraint or
|
|
1065
1091
|
* index, in any order. A `createMany` conflict target.
|
|
@@ -1098,7 +1124,7 @@ export type ConflictUpdateData<T> = {
|
|
|
1098
1124
|
[K in keyof T]?: T[K] | UpdateOperatorInput<T[K]> | SqlExpr<T[K]> | ColumnRef | ExcludedRef | undefined;
|
|
1099
1125
|
};
|
|
1100
1126
|
/** The node kinds an update expression is built from (see {@link SqlExpr}). */
|
|
1101
|
-
export type SqlExprKind = 'add' | 'sub' | 'mul' | 'div' | 'coalesce' | 'greatest' | 'least' | 'case' | 'now' | 'excluded';
|
|
1127
|
+
export type SqlExprKind = 'add' | 'sub' | 'mul' | 'div' | 'coalesce' | 'greatest' | 'least' | 'case' | 'now' | 'excluded' | 'secondsBetween';
|
|
1102
1128
|
/** Carries the phantom result type of a {@link SqlExpr}. Type-level only. */
|
|
1103
1129
|
declare const SQL_EXPR_RESULT: unique symbol;
|
|
1104
1130
|
/**
|
|
@@ -1622,8 +1648,11 @@ export interface GroupByArgs<T, R extends object = {}> {
|
|
|
1622
1648
|
_min?: GroupByAggregateSpec<T> | undefined;
|
|
1623
1649
|
/** Maximum value of fields (or JSON paths) in each group */
|
|
1624
1650
|
_max?: GroupByAggregateSpec<T> | undefined;
|
|
1625
|
-
/**
|
|
1626
|
-
|
|
1651
|
+
/**
|
|
1652
|
+
* Percentiles of fields, or of expressions, in each group (PostgreSQL only).
|
|
1653
|
+
* See {@link PercentileSpec} and {@link PercentileExprSpec}.
|
|
1654
|
+
*/
|
|
1655
|
+
_percentile?: PercentileArgs<T> | undefined;
|
|
1627
1656
|
/** Filter whole groups by their aggregate values (SQL HAVING). JSON-path aggregates key by their alias. */
|
|
1628
1657
|
having?: HavingClause<T> | undefined;
|
|
1629
1658
|
/**
|
|
@@ -1769,9 +1798,19 @@ export interface AggregateArgs<T, R extends object = {}> {
|
|
|
1769
1798
|
* s._percentile.statusCode; // number | null
|
|
1770
1799
|
* ```
|
|
1771
1800
|
*
|
|
1772
|
-
*
|
|
1801
|
+
* A key that names no field computes the percentile of an expression,
|
|
1802
|
+
* given as `of`:
|
|
1803
|
+
*
|
|
1804
|
+
* ```ts
|
|
1805
|
+
* const s = await db.jobs.aggregate({
|
|
1806
|
+
* _percentile: { wait: { of: expr.secondsBetween(col('requestedAt'), col('readyAt')), p: 0.95 } },
|
|
1807
|
+
* });
|
|
1808
|
+
* s._percentile.wait; // number | null, in seconds
|
|
1809
|
+
* ```
|
|
1810
|
+
*
|
|
1811
|
+
* See {@link PercentileSpec} and {@link PercentileExprSpec}.
|
|
1773
1812
|
*/
|
|
1774
|
-
_percentile?:
|
|
1813
|
+
_percentile?: PercentileArgs<T> | undefined;
|
|
1775
1814
|
/**
|
|
1776
1815
|
* Opt in to aggregating PII-tagged (`defineSchema` `pii: true`) columns.
|
|
1777
1816
|
* `_min` / `_max` / `_percentile` return a STORED VALUE (or one that equals a
|
|
@@ -1836,11 +1875,72 @@ export type AggregateResultOf<T, A> = AggregateCountPart<A> & GroupBySumAvgPart<
|
|
|
1836
1875
|
export interface PercentileSpec {
|
|
1837
1876
|
p: number | readonly number[];
|
|
1838
1877
|
method?: PercentileMethod | undefined;
|
|
1878
|
+
/** Only on a key that names no field: see {@link PercentileExprSpec}. */
|
|
1879
|
+
of?: undefined;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* One `_percentile` entry over an expression, under a key that names no field
|
|
1883
|
+
* (the key is only the result's name and never reaches the SQL).
|
|
1884
|
+
*
|
|
1885
|
+
* - `of`: a numeric expression over the row's own columns, built with
|
|
1886
|
+
* {@link expr}: arithmetic (`expr.add` / `sub` / `mul` / `div`),
|
|
1887
|
+
* `expr.coalesce` / `greatest` / `least` / `case`, and
|
|
1888
|
+
* `expr.secondsBetween(col('a'), col('b'))` for the seconds from one
|
|
1889
|
+
* timestamp column to another. Bound values must be numbers or bigints.
|
|
1890
|
+
* `col('x')` alone is refused: use the field key `x` instead.
|
|
1891
|
+
* - `p` and `method` as in {@link PercentileSpec}. The expression is ordered as
|
|
1892
|
+
* double precision, so the result is a `number` for both methods (an integer
|
|
1893
|
+
* beyond 2^53 loses exactness).
|
|
1894
|
+
*
|
|
1895
|
+
* Every column the expression reads as a value needs `includePii: UNSAFE`
|
|
1896
|
+
* when it is PII-tagged; columns read only by an `expr.case` arm's `when` do
|
|
1897
|
+
* not. Over zero rows the result is `null`.
|
|
1898
|
+
*/
|
|
1899
|
+
export interface PercentileExprSpec {
|
|
1900
|
+
of: SqlExpr<number | bigint | string>;
|
|
1901
|
+
p: number | readonly number[];
|
|
1902
|
+
method?: PercentileMethod | undefined;
|
|
1839
1903
|
}
|
|
1904
|
+
/**
|
|
1905
|
+
* The `_percentile` argument: field keys take a {@link PercentileSpec}; any
|
|
1906
|
+
* other key names an expression's result and takes a {@link PercentileExprSpec}.
|
|
1907
|
+
* On an untyped table (`keyof T` is `string`) any key takes either: the
|
|
1908
|
+
* `string` key is dropped from the field half, which would otherwise narrow
|
|
1909
|
+
* every key to a field spec.
|
|
1910
|
+
*/
|
|
1911
|
+
export type PercentileArgs<T> = {
|
|
1912
|
+
[K in keyof T & string as string extends K ? never : K]?: PercentileSpec | undefined;
|
|
1913
|
+
} & {
|
|
1914
|
+
[name: string]: PercentileSpec | PercentileExprSpec | undefined;
|
|
1915
|
+
};
|
|
1916
|
+
/**
|
|
1917
|
+
* `_percentile` naming fields only. `aggregate` / `groupBy` take args of this
|
|
1918
|
+
* shape when their type is generic, where {@link PercentileKeysChecked} cannot
|
|
1919
|
+
* read the keys, so a wrapper generic over its args still compiles.
|
|
1920
|
+
*/
|
|
1921
|
+
export type FieldPercentiles<T> = {
|
|
1922
|
+
_percentile?: {
|
|
1923
|
+
[K in keyof T & string]?: PercentileSpec | undefined;
|
|
1924
|
+
} | undefined;
|
|
1925
|
+
};
|
|
1926
|
+
/**
|
|
1927
|
+
* Applied to `aggregate` / `groupBy` arguments: every `_percentile` key that
|
|
1928
|
+
* names no field must carry `of`, so a misspelled field name is a compile
|
|
1929
|
+
* error rather than an expression percentile with no expression.
|
|
1930
|
+
*/
|
|
1931
|
+
export type PercentileKeysChecked<T, A> = A extends {
|
|
1932
|
+
_percentile: infer S;
|
|
1933
|
+
} ? [S] extends [object] ? {
|
|
1934
|
+
_percentile: {
|
|
1935
|
+
[K in keyof S]: K extends keyof T ? S[K] : PercentileExprSpec | undefined;
|
|
1936
|
+
};
|
|
1937
|
+
} : unknown : unknown;
|
|
1840
1938
|
/** `'cont'` interpolates (`percentile_cont`); `'disc'` returns a stored value (`percentile_disc`). */
|
|
1841
1939
|
export type PercentileMethod = 'cont' | 'disc';
|
|
1842
1940
|
/** One percentile value: the column's own type for `disc`; for `cont` a number, or an interval as the column reads. */
|
|
1843
|
-
type PercentileScalar<T, K, Spec> =
|
|
1941
|
+
type PercentileScalar<T, K, Spec> = Spec extends {
|
|
1942
|
+
of: object;
|
|
1943
|
+
} ? number : K extends keyof T ? Spec extends {
|
|
1844
1944
|
method: 'disc';
|
|
1845
1945
|
} ? NonNullable<T[K]> : [NonNullable<T[K]>] extends [number] ? number : number | NonNullable<T[K]> : unknown;
|
|
1846
1946
|
/** A scalar `p` yields one value, an array `p` yields an array, `null` over zero rows. */
|