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
package/README.md
CHANGED
|
@@ -21,39 +21,38 @@ Six reasons, each with the mechanism that makes it true:
|
|
|
21
21
|
1. **One dependency.** `dependencies` is `{ "pg": "^8.15.0" }`. No engine binary, no WASM compiler, no adapter packages in lockstep. The optional engines (SQLite, MySQL, SQL Server, PowDB, all tiered Experimental in [STABILITY.md](STABILITY.md)) are peer dependencies or Node builtins you install only if you use them.
|
|
22
22
|
2. **Written from scratch.** Turbine is not a layer over Knex or a query-builder library. Query compilation is plain string building with an FNV-1a shape fingerprint into a bounded LRU of SQL templates, so there is no plan cache to size and no compiler running on your event loop.
|
|
23
23
|
3. **Nested relations in one statement.** A `with` clause compiles to correlated `json_agg` subqueries, so users with posts with comments is one round trip, typed end to end: `users[0].posts[0].comments[0].author.name` autocompletes with no annotation.
|
|
24
|
-
4. **
|
|
24
|
+
4. **Faster than Drizzle and Prisma.** In the 0.91.0 run Drizzle took 1.4x as long as Turbine and Prisma 2.0x, by geometric mean, on a dedicated benchmark machine. The table is below; the losses are stated with the wins.
|
|
25
25
|
5. **Agents get a skill and typed tools, not a SQL prompt.** `npx turbine skill` installs a query-writing skill whose every claim is executed against a live database before release; `npx turbine mcp` exposes eleven read-only MCP tools, including a relation graph and a join-path finder that returns the `with` clause to write. Every tool runs inside `BEGIN READ ONLY`, and PII-tagged columns are redacted before rows reach a model.
|
|
26
26
|
6. **The dangerous operations ask first.** Destructive migration statements refuse to run without typed consent. `update`/`delete` with an empty `where` throws. Columns tagged `pii: true` are excluded from the emitted SQL's projections. `turbine doctor` reports missing FK indexes offline, no account, no telemetry.
|
|
27
27
|
|
|
28
28
|
## Benchmarks
|
|
29
29
|
|
|
30
|
-
Measured **2026-
|
|
30
|
+
Measured **2026-10-15 against turbine-orm 0.91.0**, versus **Drizzle 0.45.3** (relational queries) and **Prisma 7.10.0** (`@prisma/adapter-pg`, `relationJoins` on), with a hand-written `pg` control arm. Run by the repository's [`benchmarks.yml`](.github/workflows/benchmarks.yml) workflow on a dedicated machine: AMD Ryzen 9 7950X host, 4-vCPU 16 GB Firecracker VM, client and PostgreSQL 17 (Docker) on the same VM over a Unix socket. PostgreSQL 17.11, Node v24.18.0, `pg` 8.23.1 under every arm. Same schema, same data (1K users, 10K posts, 50K comments), same pool config. Each figure is the median over 200 rounds with arm order rotated per round, taken as the median of 3 full runs.
|
|
31
31
|
|
|
32
|
-
| Scenario | Turbine 0.
|
|
32
|
+
| Scenario | Turbine 0.91.0 | Drizzle 0.45.3 | Prisma 7.10.0 | raw pg |
|
|
33
33
|
|---|---|---|---|---|
|
|
34
|
-
| findMany, 100 users
|
|
35
|
-
| findMany, 50 users + posts
|
|
36
|
-
| findMany, 10 users
|
|
37
|
-
| findUnique
|
|
38
|
-
| findUnique, user + posts + comments
|
|
39
|
-
| count, all users | **0.
|
|
40
|
-
| stream,
|
|
41
|
-
| atomic increment
|
|
42
|
-
| pipeline, 5-query batch | **0.
|
|
43
|
-
| hot findUnique,
|
|
34
|
+
| findMany, 100 users | **0.204 ms** | 0.307 ms | 0.349 ms | 0.198 ms |
|
|
35
|
+
| findMany, 50 users + posts *(tie)* | 1.470 ms | 1.488 ms | 2.930 ms | 0.791 ms |
|
|
36
|
+
| findMany, 10 users + posts + comments | **0.795 ms** | 0.899 ms | 2.303 ms | n/a |
|
|
37
|
+
| findUnique by primary key | **0.090 ms** | 0.151 ms | 0.179 ms | 0.074 ms |
|
|
38
|
+
| findUnique, user + posts + comments | **0.278 ms** | 0.467 ms | 0.599 ms | n/a |
|
|
39
|
+
| count, all users | **0.080 ms** | 0.115 ms | 0.170 ms | 0.077 ms |
|
|
40
|
+
| stream 50,000 comments, per row *(tie)* | 55.27 ms | 57.33 ms | 85.99 ms | 56.38 ms |
|
|
41
|
+
| atomic increment | **0.175 ms** | 0.185 ms | 0.274 ms | 0.143 ms |
|
|
42
|
+
| pipeline, 5-query batch | **0.333 ms** | 0.707 ms | 0.782 ms | 0.365 ms |
|
|
43
|
+
| hot findUnique, rotating ids | **0.078 ms** | 0.143 ms | 0.159 ms | 0.072 ms |
|
|
44
44
|
|
|
45
45
|
What that run says:
|
|
46
46
|
|
|
47
|
-
- **
|
|
48
|
-
-
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
-
|
|
52
|
-
- The sub-0.15 ms scenarios carry roughly one third uncertainty in their absolute values. Orderings held across runs; margins should not be quoted, and that includes L2's 2.5%.
|
|
47
|
+
- **Across all ten scenarios Drizzle took 1.40x as long as Turbine and Prisma 2.00x**, by geometric mean. Turbine won or tied every one against Drizzle (2 ties) and won all ten against Prisma.
|
|
48
|
+
- **Against hand-written `pg`, Turbine ran at 1.14x, Drizzle at 1.61x and Prisma at 2.17x** (geometric mean over the 8 scenarios with a control). Where a query returns nested rows, the control decodes them into objects with `Date` timestamps as the ORMs do. The 0.71.0 figure of 1.08x was adjusted by hand against a control that did more work than Turbine; this one is not adjusted.
|
|
49
|
+
- **Pipelining is the clearest win over a network**: five dashboard queries 25 ms from the database take 25 ms as one batch against 171 ms as a transaction.
|
|
50
|
+
- **Where Turbine loses:** deep `OFFSET` pagination unless `forceCustomPlan` is set, and per-row streaming against the Drizzle 1.0 release candidate (`findManyStreamBatches` ties it).
|
|
51
|
+
- Figures under 0.15 ms carry roughly one third uncertainty in their absolute values. Orderings held across runs; those margins should not be quoted.
|
|
53
52
|
|
|
54
|
-
Full method, the
|
|
53
|
+
Full method, the extra scenarios, streaming, latency and the losses: [`benchmarks/RESULTS-0.91.0.md`](https://github.com/zvndev/turbine-orm/blob/main/benchmarks/RESULTS-0.91.0.md), or the charts at [turbineorm.dev/benchmarks](https://turbineorm.dev/benchmarks).
|
|
55
54
|
|
|
56
|
-
Reproduce it
|
|
55
|
+
Reproduce it on a fresh machine with `gh workflow run benchmarks.yml`, or locally with `benchmarks/run-round.sh` (see [`benchmarks/README.md`](benchmarks/README.md)).
|
|
57
56
|
|
|
58
57
|
## Quick Start
|
|
59
58
|
|
|
@@ -272,7 +271,7 @@ Going deep on one database means the parts other ORMs push to raw SQL are typed
|
|
|
272
271
|
|
|
273
272
|
## Serverless and edge
|
|
274
273
|
|
|
275
|
-
The core is driver-agnostic: hand any pg-compatible pool to `turbineHttp()` and Turbine runs on Vercel Edge, Cloudflare Workers, Deno Deploy, or anywhere else without TCP. The main entry's import graph is held under **
|
|
274
|
+
The core is driver-agnostic: hand any pg-compatible pool to `turbineHttp()` and Turbine runs on Vercel Edge, Cloudflare Workers, Deno Deploy, or anywhere else without TCP. The main entry's import graph is held under **148 kB brotli** (edge entry under **116 kB**) with `pg` external, enforced by `size-limit` in CI at those exact numbers; run `npm run size` for the current figure.
|
|
276
275
|
|
|
277
276
|
```typescript
|
|
278
277
|
import { Pool } from '@neondatabase/serverless';
|
|
@@ -359,11 +358,11 @@ It is also built to be extended rather than wrapped. All SQL generation routes t
|
|
|
359
358
|
|---|---|---|---|---|---|
|
|
360
359
|
| **Engine / runtime** | No engine binary (`pg` only) | Client + TS/WASM query compiler | No engine | No engine | Kysely as query runner; SQL built by MikroORM (v7) |
|
|
361
360
|
| **Runtime deps** | 1 (`pg`) | `@prisma/client` + required driver adapter | 0 | 0 | `@mikro-orm/core` (0 deps) + a driver package |
|
|
362
|
-
| **Main bundle (brotli)** | under
|
|
361
|
+
| **Main bundle (brotli)** | under 148 kB import graph (CI-enforced), `pg` external | ~1.6 MB client (TS/WASM compiler) | ~7 KB core | small | |
|
|
363
362
|
| **Studio** | Read-only by default | Full CRUD, cloud-hosted | Full CRUD; [Gateway](https://gateway.drizzle.team/) self-hosted, free | None | AdminJS integration guide (CRUD panel) |
|
|
364
363
|
| **Error PII safety** | Keys only by default | Values in messages | Raw pg errors | Raw pg errors | |
|
|
365
364
|
| **Migrations** | SQL-first, SHA-256 checksums | DSL-generated, shadow DB | SQL or Drizzle Kit | None | Generated by diffing entities against a snapshot or the database; TS/JS classes |
|
|
366
|
-
| **Edge runtime** | One import swap, under
|
|
365
|
+
| **Edge runtime** | One import swap, under 116 kB brotli (CI-enforced) | Driver adapter + WASM compiler | Native | Native | Cloudflare Workers and Deno (v7) |
|
|
367
366
|
| **Pipeline batching** | Parse/Bind/Execute protocol | Sequential in txn | Sequential | Manual | `flush()` runs queued changes in one transaction |
|
|
368
367
|
| **Typed errors** | `isRetryable` discriminant | Error codes only | None | None | `DriverException` subclasses (`UniqueConstraintViolationException`, ...) |
|
|
369
368
|
| **Nested relations** | 1 query, deep type inference | 1 query per relation by default; single-query `relationJoins` is Preview | 1 query, `relations()` re-declaration | Manual (`jsonArrayFrom`) | `populate`; default `balanced`: joins to-one, one query per to-many relation |
|
package/dist/cjs/dialect.d.ts
CHANGED
|
@@ -501,6 +501,17 @@ export interface EngineCapabilities {
|
|
|
501
501
|
* not bound by it.
|
|
502
502
|
*/
|
|
503
503
|
readonly maxInsertRows: number;
|
|
504
|
+
/**
|
|
505
|
+
* The most bind parameters each statement of a `createMany` that runs as
|
|
506
|
+
* several statements carries: at most `maxBindParameters`, and lower where
|
|
507
|
+
* an engine slows faster than linearly with a statement's size. SQLite
|
|
508
|
+
* finds each named parameter by a linear search, at prepare and at bind, so
|
|
509
|
+
* 40,000 six-column rows took 141 s in statements of 32,766 parameters and
|
|
510
|
+
* 1 s in statements of 250. A call whose rows cannot be told apart
|
|
511
|
+
* across statements, or on a pool with no `connect()`, is not split below
|
|
512
|
+
* `maxBindParameters` (see `planCreateMany`).
|
|
513
|
+
*/
|
|
514
|
+
readonly chunkBindParameters: number;
|
|
504
515
|
/**
|
|
505
516
|
* What a single-statement upsert conflicts on. `'explicit'`: the conflict
|
|
506
517
|
* target it names. `'any-unique-key'`: whichever unique key collides first
|
|
@@ -531,6 +542,16 @@ export interface EngineCapabilities {
|
|
|
531
542
|
* it afterwards, so the statement runs in a transaction it can roll back.
|
|
532
543
|
*/
|
|
533
544
|
readonly bulkUpsertAppliesRepeatedKeys: boolean;
|
|
545
|
+
/**
|
|
546
|
+
* A `skipDuplicates` insert reports a skipped row as affected, so its row
|
|
547
|
+
* count cannot be the rows it inserted. MySQL: its skip is a no-op
|
|
548
|
+
* `ON DUPLICATE KEY UPDATE`, which under `CLIENT_FOUND_ROWS` (mysql2's
|
|
549
|
+
* default) counts a matched row, and a one-row statement's result carries no
|
|
550
|
+
* `info` to subtract the duplicates from. `createMany` refuses
|
|
551
|
+
* `skipDuplicates` with `returning: false` here (E017). False on an engine
|
|
552
|
+
* that never runs a skip.
|
|
553
|
+
*/
|
|
554
|
+
readonly skipDuplicatesCountsSkipped: boolean;
|
|
534
555
|
/** `$transaction({ readOnly: true })`: `READ ONLY` after the dialect's BEGIN. Else E017. */
|
|
535
556
|
readonly readOnlyTransactions: boolean;
|
|
536
557
|
/**
|
|
@@ -817,7 +838,11 @@ export interface Dialect {
|
|
|
817
838
|
readonly explainQuery?: {
|
|
818
839
|
prefix: string;
|
|
819
840
|
};
|
|
820
|
-
/**
|
|
841
|
+
/**
|
|
842
|
+
* Build a dialect-specific RETURNING clause. Return an empty string when
|
|
843
|
+
* unsupported, and for an empty column list (a write that returns no column,
|
|
844
|
+
* `createMany({ returning: false })`).
|
|
845
|
+
*/
|
|
821
846
|
buildReturningClause(selection?: ReturningSelection): string;
|
|
822
847
|
/** Build a single-row INSERT statement. Inputs are SQL-ready quoted fragments. */
|
|
823
848
|
buildInsertStatement(input: InsertStatementInput): string;
|
package/dist/cjs/dialect.js
CHANGED
|
@@ -108,12 +108,14 @@ exports.postgresDialect = {
|
|
|
108
108
|
// Counted in a 16-bit field.
|
|
109
109
|
maxBindParameters: 65535,
|
|
110
110
|
maxInsertRows: Number.POSITIVE_INFINITY,
|
|
111
|
+
chunkBindParameters: 65535,
|
|
111
112
|
upsertConflictTarget: 'explicit',
|
|
112
113
|
setClauseOrder: 'snapshot',
|
|
113
114
|
reselectLocksRows: false,
|
|
114
115
|
integerDivision: '/',
|
|
115
116
|
updateExpressions: true,
|
|
116
117
|
bulkUpsertAppliesRepeatedKeys: false,
|
|
118
|
+
skipDuplicatesCountsSkipped: false,
|
|
117
119
|
readOnlyTransactions: true,
|
|
118
120
|
connectionParameters: true,
|
|
119
121
|
backendCancel: true,
|
|
@@ -191,6 +193,8 @@ exports.postgresDialect = {
|
|
|
191
193
|
return `COALESCE((${subquery}), ${fallback})`;
|
|
192
194
|
},
|
|
193
195
|
buildReturningClause(selection = '*') {
|
|
196
|
+
if (selection !== '*' && selection.length === 0)
|
|
197
|
+
return '';
|
|
194
198
|
return ` RETURNING ${selection === '*' ? '*' : selection.join(', ')}`;
|
|
195
199
|
},
|
|
196
200
|
buildInsertStatement(input) {
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -47,9 +47,9 @@ export { HttpJsonSink, type HttpJsonSinkOptions, type MetricsFlushBatch, type Me
|
|
|
47
47
|
export { executePipeline, type PipelineOptions, type PipelineResults, pipelineSupported } from './pipeline.js';
|
|
48
48
|
export { fingerprintPrismaSchema } from './prisma-schema-fingerprint.js';
|
|
49
49
|
export { type CaseArm, type ExprOperand, expr, type NumericOperand } from './query/expr.js';
|
|
50
|
-
export { type AggregateArgs, type AggregateResult, type ArrayFilter, AUTO_ASSUMED_ROUND_TRIP_MS, AUTO_COUNT_BATCH_MIN_PARENT_ROWS, AUTO_JOIN_PENALTY_MS_PER_ROW, AUTO_TO_ONE_JOIN_MAX_ROWS, AUTO_TO_ONE_JOIN_ROWS_MAX, AUTO_TO_ONE_JOIN_ROWS_MIN, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, col, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type GroupByResult, type HavingClause, type JsonEncoding, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type JsonValue, jsonValue, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderByObject, type OrderBySpec, type OrderDirection, type PrivilegeOption, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationLoadStrategy, type RelationOrderBy, type RelationOrderByChain, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TemporalInfinityReading, type TextSearchFilter, type TypedWithClause, UNSAFE, type Unsafe, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithOrderByObject, type WithResult, } from './query/index.js';
|
|
50
|
+
export { type AggregateArgs, type AggregateResult, type ArrayFilter, AUTO_ASSUMED_ROUND_TRIP_MS, AUTO_COUNT_BATCH_MIN_PARENT_ROWS, AUTO_JOIN_PENALTY_MS_PER_ROW, AUTO_TO_ONE_JOIN_MAX_ROWS, AUTO_TO_ONE_JOIN_ROWS_MAX, AUTO_TO_ONE_JOIN_ROWS_MIN, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type CreateManyCount, type CreateManyResult, col, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type GroupByResult, type HavingClause, type JsonEncoding, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type JsonValue, jsonValue, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderByObject, type OrderBySpec, type OrderDirection, type PrivilegeOption, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationLoadStrategy, type RelationOrderBy, type RelationOrderByChain, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TemporalInfinityReading, type TextSearchFilter, type TypedWithClause, UNSAFE, type Unsafe, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithOrderByObject, type WithResult, } from './query/index.js';
|
|
51
51
|
export type { SqlCommentOptions } from './query/sql-comment.js';
|
|
52
|
-
export type { AggregateResultOf, ConflictTarget, ConflictUpdateData, CreateManyOnConflict, DefaultInputTypes, ExcludedRef, GlobalFilter, NestedToOneCreateOp, NestedToOneUpdateOp, OptionalInput, PercentileMethod, PercentileSpec, RelationKind, RowLock, RowLockMode, SqlExpr, SqlExprKind, TableInputTypes, } from './query/types.js';
|
|
52
|
+
export type { AggregateResultOf, ConflictTarget, ConflictUpdateData, CreateManyOnConflict, DefaultInputTypes, ExcludedRef, FieldPercentiles, GlobalFilter, NestedToOneCreateOp, NestedToOneUpdateOp, OptionalInput, PercentileArgs, PercentileExprSpec, PercentileKeysChecked, PercentileMethod, PercentileSpec, RelationKind, RowLock, RowLockMode, SqlExpr, SqlExprKind, TableInputTypes, } from './query/types.js';
|
|
53
53
|
export { MAX_QUERY_TAG_LENGTH, RAW_QUERY_MODEL } from './query-events.js';
|
|
54
54
|
export { type ActiveSubscription, type ListenOptions, type ListenReconnectOptions, type NotificationHandler, type Subscription, validateChannel, } from './realtime.js';
|
|
55
55
|
export type { CheckMetadata, ColumnMetadata, IndexMetadata, PrismaCompatMap, PrismaModelMap, PrismaRelationMap, PrismaSchemaSource, ReferentialAction, RelationDef, SchemaMetadata, TableMetadata, } from './schema.js';
|
package/dist/cjs/mssql.js
CHANGED
|
@@ -644,6 +644,7 @@ exports.mssqlDialect = {
|
|
|
644
644
|
// The limit the driver can actually send (see MSSQL_MAX_PARAMS), not 2100.
|
|
645
645
|
maxBindParameters: MSSQL_MAX_PARAMS,
|
|
646
646
|
maxInsertRows: MSSQL_MAX_INSERT_ROWS,
|
|
647
|
+
chunkBindParameters: MSSQL_MAX_PARAMS,
|
|
647
648
|
upsertConflictTarget: 'explicit',
|
|
648
649
|
setClauseOrder: 'snapshot',
|
|
649
650
|
reselectLocksRows: false,
|
|
@@ -651,6 +652,7 @@ exports.mssqlDialect = {
|
|
|
651
652
|
updateExpressions: true,
|
|
652
653
|
// MERGE refuses a target row matched twice (error 8672).
|
|
653
654
|
bulkUpsertAppliesRepeatedKeys: false,
|
|
655
|
+
skipDuplicatesCountsSkipped: false,
|
|
654
656
|
readOnlyTransactions: false,
|
|
655
657
|
connectionParameters: false,
|
|
656
658
|
backendCancel: false,
|
package/dist/cjs/mysql.js
CHANGED
|
@@ -709,6 +709,7 @@ exports.mysqlDialect = {
|
|
|
709
709
|
// Counted in a 16-bit field.
|
|
710
710
|
maxBindParameters: 65535,
|
|
711
711
|
maxInsertRows: Number.POSITIVE_INFINITY,
|
|
712
|
+
chunkBindParameters: 65535,
|
|
712
713
|
upsertConflictTarget: 'any-unique-key',
|
|
713
714
|
setClauseOrder: 'left-to-right',
|
|
714
715
|
reselectLocksRows: true,
|
|
@@ -716,6 +717,8 @@ exports.mysqlDialect = {
|
|
|
716
717
|
updateExpressions: true,
|
|
717
718
|
// Caught by the onConflict re-read, which runs in the reselect transaction.
|
|
718
719
|
bulkUpsertAppliesRepeatedKeys: true,
|
|
720
|
+
// A skipped row is a no-op ON DUPLICATE KEY UPDATE, counted under FOUND_ROWS.
|
|
721
|
+
skipDuplicatesCountsSkipped: true,
|
|
719
722
|
readOnlyTransactions: true,
|
|
720
723
|
connectionParameters: false,
|
|
721
724
|
backendCancel: false,
|
package/dist/cjs/powdb.js
CHANGED
|
@@ -147,12 +147,14 @@ exports.powdbDialect = {
|
|
|
147
147
|
structuredValueColumns: 'scalars',
|
|
148
148
|
maxBindParameters: Number.POSITIVE_INFINITY,
|
|
149
149
|
maxInsertRows: Number.POSITIVE_INFINITY,
|
|
150
|
+
chunkBindParameters: Number.POSITIVE_INFINITY,
|
|
150
151
|
upsertConflictTarget: 'explicit',
|
|
151
152
|
setClauseOrder: 'snapshot',
|
|
152
153
|
reselectLocksRows: false,
|
|
153
154
|
integerDivision: '/',
|
|
154
155
|
updateExpressions: false,
|
|
155
156
|
bulkUpsertAppliesRepeatedKeys: false,
|
|
157
|
+
skipDuplicatesCountsSkipped: false,
|
|
156
158
|
readOnlyTransactions: false,
|
|
157
159
|
connectionParameters: false,
|
|
158
160
|
backendCancel: false,
|
package/dist/cjs/powql.d.ts
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
import type { PowdbPool } from './powdb.js';
|
|
38
38
|
import type { MiddlewareFn, QueryInterfaceOptions } from './query/index.js';
|
|
39
39
|
import type { TableClient } from './query/table-client.js';
|
|
40
|
-
import type { AggregateArgs, AggregateResultOf, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindUniqueArgs, GroupByArgs, GroupByResult, OptionalInput, QueryResult, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs } from './query/types.js';
|
|
40
|
+
import type { AggregateArgs, AggregateResultOf, CountArgs, CreateArgs, CreateManyArgs, CreateManyResult, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindUniqueArgs, GroupByArgs, GroupByResult, OptionalInput, QueryResult, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs } from './query/types.js';
|
|
41
41
|
import { type SchemaMetadata } from './schema.js';
|
|
42
42
|
/**
|
|
43
43
|
* Max values one `in` / `notIn` on a PowDB-native `datetime` column may carry.
|
|
@@ -1146,7 +1146,10 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
|
|
|
1146
1146
|
* byte-identical PowQL.
|
|
1147
1147
|
*/
|
|
1148
1148
|
private assertUniformCreateManyRows;
|
|
1149
|
-
createMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateManyArgs<T, OptionalInput<T>, S, O>): Promise<FieldResult<T, S, O
|
|
1149
|
+
createMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined, C extends boolean | undefined = undefined>(args: CreateManyArgs<T, OptionalInput<T>, S, O, C>): Promise<CreateManyResult<FieldResult<T, S, O>, C>>;
|
|
1150
|
+
/** `returning: false` validated: a boolean, and not combined with a projection. */
|
|
1151
|
+
private createManyCountOnly;
|
|
1152
|
+
private insertMany;
|
|
1150
1153
|
update<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpdateArgs<T, NoRelations, S, O>): Promise<FieldResult<T, S, O>>;
|
|
1151
1154
|
updateMany(args: UpdateManyArgs<T>): Promise<{
|
|
1152
1155
|
count: number;
|
package/dist/cjs/powql.js
CHANGED
|
@@ -3860,35 +3860,56 @@ class PowqlInterface {
|
|
|
3860
3860
|
async createMany(args) {
|
|
3861
3861
|
args = this.normalizeArgs(args);
|
|
3862
3862
|
return this.withMiddleware('createMany', args, async () => {
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3863
|
+
// `returning: false` is checked as the SQL engines check it. PowQL's
|
|
3864
|
+
// insert still returns the rows, which are counted and dropped.
|
|
3865
|
+
const countOnly = this.createManyCountOnly(args);
|
|
3866
|
+
const rows = await this.insertMany(args);
|
|
3867
|
+
return countOnly ? { count: rows.length } : rows;
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
/** `returning: false` validated: a boolean, and not combined with a projection. */
|
|
3871
|
+
createManyCountOnly(args) {
|
|
3872
|
+
const { returning } = args;
|
|
3873
|
+
if (returning === undefined || returning === true)
|
|
3874
|
+
return false;
|
|
3875
|
+
if (returning !== false) {
|
|
3876
|
+
throw new errors_js_1.ValidationError(`createMany on "${this.table}": returning must be true or false, received ${typeof returning}.`);
|
|
3877
|
+
}
|
|
3878
|
+
if (args.select !== undefined || args.omit !== undefined) {
|
|
3879
|
+
throw new errors_js_1.ValidationError(`createMany on "${this.table}": returning: false returns { count } and no rows, so it takes no ` +
|
|
3880
|
+
`\`${args.select !== undefined ? 'select' : 'omit'}\`. Drop one of them.`);
|
|
3881
|
+
}
|
|
3882
|
+
return true;
|
|
3883
|
+
}
|
|
3884
|
+
async insertMany(args) {
|
|
3885
|
+
if (args.onConflict !== undefined) {
|
|
3886
|
+
throw new errors_js_1.UnsupportedFeatureError('createMany({ onConflict })', 'powdb', "PowQL's upsert statement takes one row. Call upsert() per row inside $transaction.");
|
|
3887
|
+
}
|
|
3888
|
+
// Validated as every engine validates it; it acts only on onConflict's
|
|
3889
|
+
// update, which is refused above.
|
|
3890
|
+
(0, types_js_1.resolveSkipGlobalFilters)(args.skipGlobalFilters);
|
|
3891
|
+
if (args.skipDuplicates) {
|
|
3892
|
+
// PowQL's `insert … returning` has no conflict clause, so there is no
|
|
3893
|
+
// faithful skip-duplicates form; refuse rather than silently insert.
|
|
3894
|
+
throw new errors_js_1.UnsupportedFeatureError('createMany({ skipDuplicates: true })', 'powdb', 'PowQL insert has no ON CONFLICT DO NOTHING equivalent, filter duplicates before inserting.');
|
|
3895
|
+
}
|
|
3896
|
+
// Resolved before the write, so a bad projection writes nothing.
|
|
3897
|
+
const plan = this.writeReturnPlan(args);
|
|
3898
|
+
const supplied = args.data.map((d) => this.applyPkDefault(d));
|
|
3899
|
+
if (!supplied.length)
|
|
3900
|
+
return [];
|
|
3901
|
+
// Uniformity is judged on what the caller supplied, as the SQL engines
|
|
3902
|
+
// judge it; defaults are then filled per row.
|
|
3903
|
+
this.assertUniformCreateManyRows(supplied);
|
|
3904
|
+
const inputs = supplied.map((d) => this.applyColumnDefaults(d));
|
|
3905
|
+
const params = [];
|
|
3906
|
+
const tuples = inputs.map((d) => {
|
|
3907
|
+
const assigns = this.scalarData(d);
|
|
3908
|
+
return `{ ${assigns.map((a) => `${(0, powdb_shared_js_1.quotePowqlIdent)(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
|
|
3891
3909
|
});
|
|
3910
|
+
// Multi-row insert with `returning` hands back every inserted row in one round-trip.
|
|
3911
|
+
const { rows, native } = await this.exec(`insert ${this.qt} ${tuples.join(', ')} returning`, params, args.timeout, 'createMany');
|
|
3912
|
+
return this.shape(rows, native).map((r) => this.shapeWriteRow(r, plan));
|
|
3892
3913
|
}
|
|
3893
3914
|
async update(args) {
|
|
3894
3915
|
args = this.normalizeArgs(args);
|
|
@@ -137,7 +137,7 @@ export interface CompatQueryInterface {
|
|
|
137
137
|
findFirstOrThrow(args?: Record<string, unknown>): Promise<unknown>;
|
|
138
138
|
findUniqueOrThrow(args: Record<string, unknown>): Promise<unknown>;
|
|
139
139
|
create(args: Record<string, unknown>): Promise<unknown>;
|
|
140
|
-
createMany(args: Record<string, unknown>): Promise<unknown
|
|
140
|
+
createMany(args: Record<string, unknown>): Promise<unknown>;
|
|
141
141
|
update(args: Record<string, unknown>): Promise<unknown>;
|
|
142
142
|
updateMany(args: Record<string, unknown>): Promise<{
|
|
143
143
|
count: number;
|
|
@@ -156,7 +156,7 @@ export interface CompatQueryInterface {
|
|
|
156
156
|
buildFindFirstOrThrow(args?: Record<string, unknown>): DeferredQuery<unknown>;
|
|
157
157
|
buildFindUniqueOrThrow(args: Record<string, unknown>): DeferredQuery<unknown>;
|
|
158
158
|
buildCreate(args: Record<string, unknown>): DeferredQuery<unknown>;
|
|
159
|
-
buildCreateMany(args: Record<string, unknown>): DeferredQuery<unknown
|
|
159
|
+
buildCreateMany(args: Record<string, unknown>): DeferredQuery<unknown>;
|
|
160
160
|
buildUpdate(args: Record<string, unknown>): DeferredQuery<unknown>;
|
|
161
161
|
buildUpdateMany(args: Record<string, unknown>): DeferredQuery<{
|
|
162
162
|
count: number;
|
|
@@ -1901,12 +1901,12 @@ function createManyRunsOf(t) {
|
|
|
1901
1901
|
async function createManyByRun(qi, t, runs) {
|
|
1902
1902
|
let count = 0;
|
|
1903
1903
|
for (const run of runs) {
|
|
1904
|
-
const args = { data: run };
|
|
1904
|
+
const args = { data: run, returning: false };
|
|
1905
1905
|
if (t.skipDuplicates)
|
|
1906
1906
|
args.skipDuplicates = true;
|
|
1907
1907
|
if (t.timeout !== undefined)
|
|
1908
1908
|
args.timeout = t.timeout;
|
|
1909
|
-
count += (await qi.createMany(args)).
|
|
1909
|
+
count += (await qi.createMany(args)).count;
|
|
1910
1910
|
}
|
|
1911
1911
|
return { count };
|
|
1912
1912
|
}
|
|
@@ -2002,7 +2002,10 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
2002
2002
|
`array of them, received ${describeValue(data)}.`);
|
|
2003
2003
|
}
|
|
2004
2004
|
const rows = list.map((d) => translateWriteData(ctx, mm, applyCreateDefaults(mm, d)));
|
|
2005
|
-
|
|
2005
|
+
// Prisma's createMany resolves to { count }, which core counts
|
|
2006
|
+
// without returning the rows (a plain MySQL insert returns none, so
|
|
2007
|
+
// counting rows reported 0 there).
|
|
2008
|
+
const t = { data: rows, returning: false };
|
|
2006
2009
|
(0, index_js_1.applyNativeOptions)(index_js_1.CREATE_MANY_OPTIONS, args, t);
|
|
2007
2010
|
if (args.skipDuplicates)
|
|
2008
2011
|
t.skipDuplicates = true;
|
|
@@ -2014,11 +2017,11 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
2014
2017
|
// stays all-or-nothing and the count is the total actually inserted.
|
|
2015
2018
|
const runs = createManyRunsOf(t);
|
|
2016
2019
|
if (runs.length <= 1)
|
|
2017
|
-
return qi.createMany(t)
|
|
2020
|
+
return qi.createMany(t);
|
|
2018
2021
|
return runInTx((table) => createManyByRun(table(mm.table), t, runs));
|
|
2019
2022
|
}, {
|
|
2020
2023
|
build: (qi, t) => qi.buildCreateMany(t),
|
|
2021
|
-
reshape: (raw) =>
|
|
2024
|
+
reshape: (raw) => raw,
|
|
2022
2025
|
// A mixed batch is more than one statement, so the array
|
|
2023
2026
|
// $transaction([...]) form runs the whole array sequentially in a tx.
|
|
2024
2027
|
nested: (t) => createManyRunsOf(t).length > 1,
|
|
@@ -54,6 +54,7 @@ exports.buildHavingNumericClauses = buildHavingNumericClauses;
|
|
|
54
54
|
exports.buildAggregate = buildAggregate;
|
|
55
55
|
const errors_js_1 = require("../errors.js");
|
|
56
56
|
const schema_js_1 = require("../schema.js");
|
|
57
|
+
const expr_js_1 = require("./expr.js");
|
|
57
58
|
const filters_js_1 = require("./filters.js");
|
|
58
59
|
const types_js_1 = require("./types.js");
|
|
59
60
|
const utils_js_1 = require("./utils.js");
|
|
@@ -325,7 +326,7 @@ function buildGroupBy(qi, args) {
|
|
|
325
326
|
buildAggregates('_avg', 'AVG', args._avg);
|
|
326
327
|
buildAggregates('_min', 'MIN', args._min);
|
|
327
328
|
buildAggregates('_max', 'MAX', args._max);
|
|
328
|
-
const percentiles = planPercentiles(qi, args._percentile, params, includePii, 'groupBy', claimResultKey);
|
|
329
|
+
const percentiles = planPercentiles(qi, args._percentile, params, includePii, 'groupBy', claimResultKey, (key) => key === '_count' || usedResultKeys.has(key));
|
|
329
330
|
selectExprs.push(...percentiles.map((plan) => plan.expr));
|
|
330
331
|
let sql = `SELECT ${selectExprs.join(', ')} FROM ${fromSql} GROUP BY ${groupExprs.join(', ')}`;
|
|
331
332
|
// HAVING, filter whole groups by their aggregate values.
|
|
@@ -1070,7 +1071,7 @@ const PERCENTILE_HINT = {
|
|
|
1070
1071
|
mssql: 'Percentile aggregates are not available on SQL Server in this release: PERCENTILE_CONT is a window ' +
|
|
1071
1072
|
'function there, not an aggregate.',
|
|
1072
1073
|
};
|
|
1073
|
-
const PERCENTILE_SPEC_KEYS = ['p', 'method'];
|
|
1074
|
+
const PERCENTILE_SPEC_KEYS = ['p', 'method', 'of'];
|
|
1074
1075
|
const PERCENTILE_METHODS = ['cont', 'disc'];
|
|
1075
1076
|
const PERCENTILE_NOT_ORDERABLE = 'ordering or filtering groups by _percentile is not supported yet.';
|
|
1076
1077
|
/**
|
|
@@ -1085,8 +1086,14 @@ const PERCENTILE_NOT_ORDERABLE = 'ordering or filtering groups by _percentile is
|
|
|
1085
1086
|
* form), a JSON-path target, and two keys naming one column. Both methods need
|
|
1086
1087
|
* `includePii: UNSAFE` on a PII column: `disc` returns a stored cell, and so
|
|
1087
1088
|
* does `cont` at p = 0 or 1 or over a one-row group.
|
|
1089
|
+
*
|
|
1090
|
+
* A key with `of` names an expression's result (see `PercentileExprSpec`): it
|
|
1091
|
+
* must not name a field, the expression compiles through `compileReadExpr`,
|
|
1092
|
+
* it is ordered as `float8`, and its SQL alias is `_percentile_of_<n>`, so the
|
|
1093
|
+
* caller's name never reaches the SQL. Every PII column it reads as a value
|
|
1094
|
+
* needs `includePii: UNSAFE`, as a column percentile does.
|
|
1088
1095
|
*/
|
|
1089
|
-
function planPercentiles(qi, spec, params, includePii, operation, claimResultKey) {
|
|
1096
|
+
function planPercentiles(qi, spec, params, includePii, operation, claimResultKey, resultKeyTaken) {
|
|
1090
1097
|
if (spec === undefined)
|
|
1091
1098
|
return [];
|
|
1092
1099
|
const where = `${operation} _percentile on table "${qi.table}"`;
|
|
@@ -1102,6 +1109,16 @@ function planPercentiles(qi, spec, params, includePii, operation, claimResultKey
|
|
|
1102
1109
|
const meta = qi.schema.tables[qi.table];
|
|
1103
1110
|
const plans = [];
|
|
1104
1111
|
const aliases = new Set();
|
|
1112
|
+
// Column aliases are `_percentile_<column>`; an expression's is the first
|
|
1113
|
+
// free `_percentile_of_<n>`, chosen after every column alias is known so a
|
|
1114
|
+
// column named `of_0` keeps its own.
|
|
1115
|
+
const columnAliases = new Set();
|
|
1116
|
+
for (const [key] of entries) {
|
|
1117
|
+
const column = (0, utils_js_1.resolveColumnName)(qi.tableMeta, key);
|
|
1118
|
+
if (column !== undefined)
|
|
1119
|
+
columnAliases.add(`_percentile_${column}`);
|
|
1120
|
+
}
|
|
1121
|
+
let exprCount = 0;
|
|
1105
1122
|
for (const [key, entry] of entries) {
|
|
1106
1123
|
const label = `${where}, field "${key}"`;
|
|
1107
1124
|
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
@@ -1116,7 +1133,7 @@ function planPercentiles(qi, spec, params, includePii, operation, claimResultKey
|
|
|
1116
1133
|
throw new errors_js_1.ValidationError(`${label}: unknown key "${k}".${hint ? ` Did you mean "${hint}"?` : ''} Expected { p, method? }.`);
|
|
1117
1134
|
}
|
|
1118
1135
|
}
|
|
1119
|
-
const { p, method = 'cont' } = entry;
|
|
1136
|
+
const { p, method = 'cont', of } = entry;
|
|
1120
1137
|
if (!PERCENTILE_METHODS.includes(method)) {
|
|
1121
1138
|
throw new errors_js_1.ValidationError(`${label}: method must be 'cont' or 'disc', received ${describeValue(method)}.`);
|
|
1122
1139
|
}
|
|
@@ -1129,6 +1146,10 @@ function planPercentiles(qi, spec, params, includePii, operation, claimResultKey
|
|
|
1129
1146
|
else {
|
|
1130
1147
|
assertFraction(p, label);
|
|
1131
1148
|
}
|
|
1149
|
+
if (of !== undefined) {
|
|
1150
|
+
plans.push(planExpressionPercentile(qi, key, of, p, method, params, includePii, operation, label, aliases, columnAliases, exprCount++, claimResultKey, resultKeyTaken));
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1132
1153
|
const col = qi.toColumn(key);
|
|
1133
1154
|
if (method === 'cont' && qi.tableMeta.dateColumns.has(col)) {
|
|
1134
1155
|
throw new errors_js_1.ValidationError(`${label}: method 'cont' interpolates and needs a numeric or interval column, and "${col}" is a ` +
|
|
@@ -1158,6 +1179,51 @@ function planPercentiles(qi, spec, params, includePii, operation, claimResultKey
|
|
|
1158
1179
|
}
|
|
1159
1180
|
return plans;
|
|
1160
1181
|
}
|
|
1182
|
+
/** One `_percentile` entry over an expression (`of`); see {@link planPercentiles}. */
|
|
1183
|
+
function planExpressionPercentile(qi, key, of, p, method, params, includePii, operation, label, aliases, columnAliases, index, claimResultKey, resultKeyTaken) {
|
|
1184
|
+
const column = (0, utils_js_1.resolveColumnName)(qi.tableMeta, key);
|
|
1185
|
+
if (column !== undefined) {
|
|
1186
|
+
throw new errors_js_1.ValidationError(`${label}: "${key}" is a field of the table, and \`of\` names an expression. Give the expression a name ` +
|
|
1187
|
+
`that is not a field (the name is only its result's key), or drop \`of\` for the percentile of "${key}".`);
|
|
1188
|
+
}
|
|
1189
|
+
const compiled = (0, expr_js_1.compileReadExpr)({
|
|
1190
|
+
qi,
|
|
1191
|
+
name: key,
|
|
1192
|
+
operation: `${operation} _percentile`,
|
|
1193
|
+
bind: (value, cls) => {
|
|
1194
|
+
params.push(value);
|
|
1195
|
+
return `${qi.p(params.length)}::${cls === 'int' ? 'int8' : 'numeric'}`;
|
|
1196
|
+
},
|
|
1197
|
+
predicate: (when) => {
|
|
1198
|
+
const sql = whereMod.buildWhereClause(qi, when, params);
|
|
1199
|
+
if (sql === null)
|
|
1200
|
+
throw new errors_js_1.ValidationError(`${label}: an expr.case() arm's when compiles to no predicate.`);
|
|
1201
|
+
return sql;
|
|
1202
|
+
},
|
|
1203
|
+
}, of);
|
|
1204
|
+
const meta = qi.schema.tables[qi.table];
|
|
1205
|
+
for (const col of compiled.values) {
|
|
1206
|
+
const field = qi.tableMeta.reverseColumnMap[col] ?? (0, schema_js_1.snakeToCamel)(col);
|
|
1207
|
+
assertAggregatePiiOptIn(qi.table, meta, field, col, `${operation} _percentile "${key}"`, includePii);
|
|
1208
|
+
}
|
|
1209
|
+
// The caller's key never names the alias, so it skips every name already
|
|
1210
|
+
// taken, a groupBy key included, rather than collide with one.
|
|
1211
|
+
let n = index;
|
|
1212
|
+
const taken = (alias) => aliases.has(alias) || columnAliases.has(alias) || resultKeyTaken?.(alias) === true;
|
|
1213
|
+
while (taken(`_percentile_of_${n}`))
|
|
1214
|
+
n++;
|
|
1215
|
+
const alias = `_percentile_of_${n}`;
|
|
1216
|
+
aliases.add(alias);
|
|
1217
|
+
claimResultKey?.(alias, `_percentile "${key}"`);
|
|
1218
|
+
params.push(Array.isArray(p) ? [...p] : p);
|
|
1219
|
+
const fractions = `${qi.p(params.length)}::float8${Array.isArray(p) ? '[]' : ''}`;
|
|
1220
|
+
return {
|
|
1221
|
+
expr: `percentile_${method}(${fractions}) WITHIN GROUP (ORDER BY (${compiled.sql})::float8) AS ${qi.q(alias)}`,
|
|
1222
|
+
alias,
|
|
1223
|
+
field: key,
|
|
1224
|
+
col: null,
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1161
1227
|
function assertFraction(value, label) {
|
|
1162
1228
|
if (typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1)
|
|
1163
1229
|
return;
|
|
@@ -1199,6 +1265,10 @@ function readPercentiles(qi, plans, row) {
|
|
|
1199
1265
|
function percentileElement(qi, col, value) {
|
|
1200
1266
|
if (value === null || value === undefined)
|
|
1201
1267
|
return null;
|
|
1268
|
+
// An expression is ordered as float8, which the driver reads as a number
|
|
1269
|
+
// (a supplied pool's own parser may hand back text).
|
|
1270
|
+
if (col === null)
|
|
1271
|
+
return typeof value === 'string' ? Number(value) : value;
|
|
1202
1272
|
if (typeof value === 'string' && isInt8Type(whereMod.getColumnPgType(qi, col))) {
|
|
1203
1273
|
const n = Number(value);
|
|
1204
1274
|
return Number.isSafeInteger(n) ? n : value;
|