Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.

Commit 3a2184a

Browse files
committed
refactor(rawSql): address inline review comments
Renames + cleanup driven by inline REVIEW comments: - Drop 5 raw-SQL tests from the postgres/sqlite extension packages — they duplicated coverage that already lives in `@prisma-next/sql-relational-core` (factory shape, AST surface, type-level interpolation contract). The extension-level coverage was end-to-end via the integration test, not unit-level repetition. - `Db<C>` in sql-builder: collapse the explicit `TableProxy<C, Name, Name, DefaultScope<...>, ContractToQC<C, Name>>` instantiation to `TableProxy<C, Name>`, since every argument matches TableProxy's default generics. - `RawExpr` visitor / rewriter / folder arms renamed from `rawSql` to `rawExpr` to match the AST kind discriminant (`'raw-expr'`). Updates `RawExpr.accept` / `.rewrite` / `.fold` plus the two call sites in `@prisma-next/sql-orm-client` (`where-binding.ts`, `query-plan-aggregate.ts`) and the relevant test arm names. - `RawCodecInferer` JSDoc rewritten as public-API documentation — explains the contract implementers fulfil and how the runtime consumes it, without linking to internal symbols. - `RawSqlLiteral` JSDoc no longer calls out Date specifically; the rule is generic: anything outside the literal union is routed through `param(value, { codecId })`. - `create-raw-sql.test.ts`: `stubAdapter` → `stubInferer`; test titles + comments use "inferer" terminology to match the renamed interface; "defence-in-depth" describe renamed to plain language. - Drop a tautological test in `raw-expr.test.ts` that asserted `RawExpr.baseColumnRef` shares an error message with `AggregateExpr.count()` — the wording is not load-bearing. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
1 parent a165da2 commit 3a2184a

12 files changed

Lines changed: 52 additions & 924 deletions

File tree

packages/2-sql/4-lanes/relational-core/src/ast/types.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export type AggregateFn = AggregateCountFn | AggregateOpFn;
2020
*/
2121
export type WindowFn = 'row_number' | 'rank' | 'dense_rank';
2222

23-
/** Scalar JS values that map directly to a SQL wire type. `Date` is excluded — it must be routed through `param(date, { codecId })` to select the target codec (timestamp vs. timestamptz, precision, timezone semantics). */
23+
/** Scalar JS values that map directly to a SQL wire type. Values outside this set must be routed through `param(value, { codecId })` to declare the target codec explicitly. */
2424
export type RawSqlLiteral = number | bigint | string | boolean | Uint8Array;
2525

2626
export interface ExpressionSource {
@@ -35,7 +35,7 @@ export interface ExpressionRewriter {
3535
literal?(expr: LiteralExpr): LiteralExpr;
3636
list?(expr: ListExpression): ListExpression | LiteralExpr;
3737
select?(ast: SelectAst): SelectAst;
38-
rawSql?(expr: RawExpr): AnyExpression;
38+
rawExpr?(expr: RawExpr): AnyExpression;
3939
}
4040

4141
export interface AstRewriter extends ExpressionRewriter {
@@ -62,7 +62,7 @@ export interface ExprVisitor<R> {
6262
param(expr: ParamRef): R;
6363
preparedParam(expr: PreparedParamRef): R;
6464
list(expr: ListExpression): R;
65-
rawSql(expr: RawExpr): R;
65+
rawExpr(expr: RawExpr): R;
6666
}
6767

6868
export interface ExpressionFolder<T> {
@@ -76,7 +76,7 @@ export interface ExpressionFolder<T> {
7676
literal?(expr: LiteralExpr): T;
7777
list?(expr: ListExpression): T;
7878
select?(ast: SelectAst): T;
79-
rawSql?(expr: RawExpr): T;
79+
rawExpr?(expr: RawExpr): T;
8080
}
8181

8282
export type ProjectionExpr = AnyExpression;
@@ -639,16 +639,16 @@ export class RawExpr extends Expression {
639639
}
640640

641641
override accept<R>(visitor: ExprVisitor<R>): R {
642-
return visitor.rawSql(this);
642+
return visitor.rawExpr(this);
643643
}
644644

645645
override rewrite(rewriter: ExpressionRewriter): AnyExpression {
646-
return rewriter.rawSql ? rewriter.rawSql(this) : this;
646+
return rewriter.rawExpr ? rewriter.rawExpr(this) : this;
647647
}
648648

649649
override fold<T>(folder: ExpressionFolder<T>): T {
650-
if (folder.rawSql) {
651-
return folder.rawSql(this);
650+
if (folder.rawExpr) {
651+
return folder.rawExpr(this);
652652
}
653653
return combineAll(
654654
folder,

packages/2-sql/4-lanes/relational-core/src/expression.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,18 @@ export function buildOperation<R extends ScopeField>(spec: BuildOperationSpec<R>
160160
};
161161
}
162162

163-
/** Minimal contract a target must satisfy to construct a {@link RawSqlTag} via {@link createRawSql} — looks up the codec id for a bare-literal interpolation. */
163+
/**
164+
* Resolves a codec id for a bare JavaScript value interpolated into a raw-SQL
165+
* template — e.g. `` rawSql`SELECT ${42}` `` calls `inferCodec(42)` to pick
166+
* the codec id (`pg/int4`, `sqlite/integer@1`, etc.) that will encode the
167+
* value as a bound parameter.
168+
*
169+
* Targets implement this once per dialect: examine the JS value's runtime
170+
* shape (number, bigint, string, boolean, `Uint8Array`) and return a codec
171+
* id known to the target's codec registry. Throw when the value falls
172+
* outside the supported set — callers should wrap such values with
173+
* `param(value, { codecId })` to declare the codec explicitly.
174+
*/
164175
export interface RawCodecInferer {
165176
inferCodec(value: RawSqlLiteral): string;
166177
}

packages/2-sql/4-lanes/relational-core/test/ast/raw-expr.test.ts

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { describe, expect, it } from 'vitest';
2-
import {
3-
AggregateExpr,
4-
type AnyExpression,
5-
type ExprVisitor,
6-
ParamRef,
7-
RawExpr,
8-
} from '../../src/exports/ast';
2+
import { type AnyExpression, type ExprVisitor, ParamRef, RawExpr } from '../../src/exports/ast';
93
import { col, lit, param } from './test-helpers';
104

115
describe('ast/RawExpr', () => {
@@ -52,24 +46,24 @@ describe('ast/RawExpr', () => {
5246
preparedParam: () => 'preparedParam',
5347
list: () => 'list',
5448
windowFunc: () => 'windowFunc',
55-
rawSql: (e) => {
56-
visited.push(`rawSql:${e.parts.length}`);
57-
return 'rawSql';
49+
rawExpr: (e) => {
50+
visited.push(`rawExpr:${e.parts.length}`);
51+
return 'rawExpr';
5852
},
5953
};
6054

6155
const result = expr.accept(visitor);
62-
expect(result).toBe('rawSql');
63-
expect(visited).toEqual(['rawSql:2']);
56+
expect(result).toBe('rawExpr');
57+
expect(visited).toEqual(['rawExpr:2']);
6458
});
6559

66-
it('rewrites expression parts through the optional rawSql rewriter arm', () => {
60+
it('rewrites expression parts through the optional rawExpr rewriter arm', () => {
6761
const ref = param(1, 'x');
6862
const expr = new RawExpr({ parts: ['prefix ', ref, ' suffix'], returns: returnsSpec });
6963

7064
const newRef = param(99, 'x');
7165
const rewritten = expr.rewrite({
72-
rawSql: (e) =>
66+
rawExpr: (e) =>
7367
new RawExpr({
7468
parts: e.parts.map((p) => (p instanceof ParamRef ? newRef : p)) as ReadonlyArray<
7569
string | AnyExpression
@@ -82,26 +76,26 @@ describe('ast/RawExpr', () => {
8276
expect((rewritten as RawExpr).parts[1]).toBe(newRef);
8377
});
8478

85-
it('returns self from rewrite when no rawSql arm is provided', () => {
79+
it('returns self from rewrite when no rawExpr arm is provided', () => {
8680
const expr = new RawExpr({ parts: ['now()'], returns: returnsSpec });
8781
const rewritten = expr.rewrite({});
8882
expect(rewritten).toBe(expr);
8983
});
9084

91-
it('folds using the optional rawSql folder arm when provided', () => {
85+
it('folds using the optional rawExpr folder arm when provided', () => {
9286
const ref = param(1, 'x');
9387
const expr = new RawExpr({ parts: ['prefix ', ref], returns: returnsSpec });
9488

9589
const result = expr.fold<string>({
9690
empty: '',
9791
combine: (a, b) => `${a}+${b}`,
98-
rawSql: (e) => `raw:${e.parts.length}`,
92+
rawExpr: (e) => `raw:${e.parts.length}`,
9993
});
10094

10195
expect(result).toBe('raw:2');
10296
});
10397

104-
it('falls back to empty when no rawSql folder arm is provided', () => {
98+
it('falls back to empty when no rawExpr folder arm is provided', () => {
10599
const expr = new RawExpr({ parts: ['now()'], returns: returnsSpec });
106100

107101
const result = expr.fold<string[]>({
@@ -127,14 +121,6 @@ describe('ast/RawExpr', () => {
127121
expect(collected).toContain(ref3);
128122
});
129123

130-
it('baseColumnRef throws the same message as AggregateExpr.count()', () => {
131-
const rawExpr = new RawExpr({ parts: ['1'], returns: returnsSpec });
132-
const countExpr = AggregateExpr.count();
133-
134-
expect(() => rawExpr.baseColumnRef()).toThrow('does not expose a base column reference');
135-
expect(() => countExpr.baseColumnRef()).toThrow('does not expose a base column reference');
136-
});
137-
138124
it('preserves empty-string parts from back-to-back interpolations', () => {
139125
const a = lit(1);
140126
const b = lit(2);

packages/2-sql/4-lanes/relational-core/test/expression/create-raw-sql.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { describe, expect, it } from 'vitest';
22
import { ColumnRef, ParamRef, RawExpr, type RawSqlLiteral } from '../../src/exports/ast';
33
import { buildOperation, createRawSql, param } from '../../src/exports/expression';
44

5-
// Stub adapter used throughout — applies simple type-based codec resolution.
5+
// Stub inferer used throughout — applies simple type-based codec resolution.
66
// Safe-integer integers → 'test/int'; fractions / out-of-safe-int numbers → 'test/float';
77
// bigint → 'test/bigint'; string → 'test/str'; boolean → 'test/bool'; Uint8Array → 'test/bytes'.
8-
const stubAdapter = {
8+
const stubInferer = {
99
inferCodec(value: RawSqlLiteral): string {
1010
if (typeof value === 'bigint') return 'test/bigint';
1111
if (typeof value === 'string') return 'test/str';
@@ -18,7 +18,7 @@ const stubAdapter = {
1818
},
1919
};
2020

21-
const rawSql = createRawSql(stubAdapter);
21+
const rawSql = createRawSql(stubInferer);
2222

2323
describe('createRawSql factory', () => {
2424
describe('zero-interpolation template', () => {
@@ -47,8 +47,8 @@ describe('createRawSql factory', () => {
4747
});
4848
});
4949

50-
describe('bare RawSqlLiteral interpolation routes through adapter.inferCodec', () => {
51-
it('wraps a number (safe integer) as a ParamRef with codec from adapter', () => {
50+
describe('bare RawSqlLiteral interpolation routes through inferer.inferCodec', () => {
51+
it('wraps a number (safe integer) as a ParamRef with codec from inferer', () => {
5252
const expr = rawSql`${42}`.returns('test/int');
5353
const rawExpr = expr.buildAst() as RawExpr;
5454
const part = rawExpr.parts[1];
@@ -57,7 +57,7 @@ describe('createRawSql factory', () => {
5757
expect((part as ParamRef).codec?.codecId).toBe('test/int');
5858
});
5959

60-
it('wraps a bigint as a ParamRef with codec from adapter', () => {
60+
it('wraps a bigint as a ParamRef with codec from inferer', () => {
6161
const expr = rawSql`${9007199254740993n}`.returns('test/bigint');
6262
const rawExpr = expr.buildAst() as RawExpr;
6363
const part = rawExpr.parts[1];
@@ -66,7 +66,7 @@ describe('createRawSql factory', () => {
6666
expect((part as ParamRef).codec?.codecId).toBe('test/bigint');
6767
});
6868

69-
it('wraps a string as a ParamRef with codec from adapter', () => {
69+
it('wraps a string as a ParamRef with codec from inferer', () => {
7070
const expr = rawSql`${'hello'}`.returns('test/str');
7171
const rawExpr = expr.buildAst() as RawExpr;
7272
const part = rawExpr.parts[1];
@@ -75,7 +75,7 @@ describe('createRawSql factory', () => {
7575
expect((part as ParamRef).codec?.codecId).toBe('test/str');
7676
});
7777

78-
it('wraps a boolean as a ParamRef with codec from adapter', () => {
78+
it('wraps a boolean as a ParamRef with codec from inferer', () => {
7979
const expr = rawSql`${true}`.returns('test/bool');
8080
const rawExpr = expr.buildAst() as RawExpr;
8181
const part = rawExpr.parts[1];
@@ -84,7 +84,7 @@ describe('createRawSql factory', () => {
8484
expect((part as ParamRef).codec?.codecId).toBe('test/bool');
8585
});
8686

87-
it('wraps a Uint8Array as a ParamRef with codec from adapter', () => {
87+
it('wraps a Uint8Array as a ParamRef with codec from inferer', () => {
8888
const bytes = new Uint8Array([1, 2, 3]);
8989
const expr = rawSql`${bytes}`.returns('test/bytes');
9090
const rawExpr = expr.buildAst() as RawExpr;
@@ -123,35 +123,35 @@ describe('createRawSql factory', () => {
123123
});
124124
});
125125

126-
describe('number boundary cases routed through adapter', () => {
127-
it('routes a safe integer to test/int via stub adapter', () => {
126+
describe('number boundary cases routed through inferer', () => {
127+
it('routes a safe integer to test/int via stub inferer', () => {
128128
const expr = rawSql`${42}`.returns('test/int');
129129
const rawExpr = expr.buildAst() as RawExpr;
130130
expect((rawExpr.parts[1] as ParamRef).codec?.codecId).toBe('test/int');
131131
});
132132

133-
it('routes a fractional number to test/float via stub adapter', () => {
133+
it('routes a fractional number to test/float via stub inferer', () => {
134134
const expr = rawSql`${1.5}`.returns('test/float');
135135
const rawExpr = expr.buildAst() as RawExpr;
136136
expect((rawExpr.parts[1] as ParamRef).codec?.codecId).toBe('test/float');
137137
});
138138

139-
it('routes a number beyond safe-integer range to test/float via stub adapter', () => {
139+
it('routes a number beyond safe-integer range to test/float via stub inferer', () => {
140140
const beyondSafe = Number.MAX_SAFE_INTEGER + 1;
141141
const expr = rawSql`${beyondSafe}`.returns('test/float');
142142
const rawExpr = expr.buildAst() as RawExpr;
143143
expect((rawExpr.parts[1] as ParamRef).codec?.codecId).toBe('test/float');
144144
});
145145

146-
it('routes -0 to test/int via stub adapter (isSafeInteger(-0) is true)', () => {
146+
it('routes -0 to test/int via stub inferer (isSafeInteger(-0) is true)', () => {
147147
const expr = rawSql`${-0}`.returns('test/int');
148148
const rawExpr = expr.buildAst() as RawExpr;
149149
expect((rawExpr.parts[1] as ParamRef).codec?.codecId).toBe('test/int');
150150
});
151151
});
152152

153153
describe('param() override produces different codec than bare literal', () => {
154-
it('bare 42 uses adapter-inferred codec while param(42, { codecId }) uses the specified one', () => {
154+
it('bare 42 uses inferer-inferred codec while param(42, { codecId }) uses the specified one', () => {
155155
const withBare = rawSql`${42}`.returns('test/int').buildAst() as RawExpr;
156156
const withParam = rawSql`${param(42, { codecId: 'pg/int8' })}`
157157
.returns('test/int')
@@ -201,7 +201,7 @@ describe('createRawSql factory', () => {
201201
});
202202
});
203203

204-
describe('defence-in-depth throw for off-union values', () => {
204+
describe('runtime throw when interpolating an off-union value via type cast', () => {
205205
type AnyTemplate = (
206206
strings: TemplateStringsArray,
207207
...values: unknown[]

packages/2-sql/4-lanes/sql-builder/src/types/db.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { StorageTable } from '@prisma-next/sql-contract/types';
2-
import type { DefaultScope } from '../scope';
3-
import type { ContractToQC, TableProxy } from './table-proxy';
2+
import type { TableProxy } from './table-proxy';
43

54
export type CapabilitiesBase = Record<string, Record<string, boolean>>;
65

@@ -37,11 +36,5 @@ type TableInAnyNamespace<C extends TableProxyContract, Name extends string> = {
3736
}[keyof C['storage']['namespaces']];
3837

3938
export type Db<C extends TableProxyContract> = {
40-
[Name in TableNamesAcrossNamespaces<C>]: TableProxy<
41-
C,
42-
Name,
43-
Name,
44-
DefaultScope<Name, UnboundTables<C>[Name]>,
45-
ContractToQC<C, Name>
46-
>;
39+
[Name in TableNamesAcrossNamespaces<C>]: TableProxy<C, Name>;
4740
};

0 commit comments

Comments
 (0)