@prisma-next/sql-relational-core 0.12.0-dev.4 → 0.12.0-dev.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{codec-types-Cezo7k-i.d.mts → codec-types-Ey_MAEly.d.mts} +1 -1
- package/dist/{codec-types-Cezo7k-i.d.mts.map → codec-types-Ey_MAEly.d.mts.map} +1 -1
- package/dist/ddl-types-BV7GsGNt.d.mts +56 -0
- package/dist/ddl-types-BV7GsGNt.d.mts.map +1 -0
- package/dist/ddl-types-wqxUC57D.mjs +71 -0
- package/dist/ddl-types-wqxUC57D.mjs.map +1 -0
- package/dist/{errors-Bavu5wZA.d.mts → errors-CI1oIVmU.d.mts} +2 -2
- package/dist/{errors-Bavu5wZA.d.mts.map → errors-CI1oIVmU.d.mts.map} +1 -1
- package/dist/{errors-BF7W5uUd.mjs → errors-HvB_h2on.mjs} +1 -1
- package/dist/{errors-BF7W5uUd.mjs.map → errors-HvB_h2on.mjs.map} +1 -1
- package/dist/exports/ast.d.mts +5 -8
- package/dist/exports/ast.d.mts.map +1 -1
- package/dist/exports/ast.mjs +4 -3
- package/dist/exports/ast.mjs.map +1 -1
- package/dist/exports/codec-descriptor-registry.d.mts +2 -2
- package/dist/exports/contract-free.d.mts +149 -0
- package/dist/exports/contract-free.d.mts.map +1 -0
- package/dist/exports/contract-free.mjs +248 -0
- package/dist/exports/contract-free.mjs.map +1 -0
- package/dist/exports/errors.d.mts +1 -1
- package/dist/exports/errors.mjs +1 -1
- package/dist/exports/expression.d.mts +2 -2
- package/dist/exports/expression.mjs +1 -1
- package/dist/exports/middleware.d.mts +1 -1
- package/dist/exports/middleware.mjs +1 -1
- package/dist/exports/plan.d.mts +2 -2
- package/dist/exports/query-lane-context.d.mts +1 -1
- package/dist/exports/types.d.mts +3 -3
- package/dist/index.d.mts +12 -11
- package/dist/index.mjs +6 -5
- package/dist/{middleware-B0yz0pff.mjs → middleware-CbHPs9WK.mjs} +2 -2
- package/dist/{middleware-B0yz0pff.mjs.map → middleware-CbHPs9WK.mjs.map} +1 -1
- package/dist/{middleware-EOcO8Ogo.d.mts → middleware-DkAjjPxq.d.mts} +2 -2
- package/dist/{middleware-EOcO8Ogo.d.mts.map → middleware-DkAjjPxq.d.mts.map} +1 -1
- package/dist/{plan-CG3sy5kw.d.mts → plan-Ds9oN5QU.d.mts} +3 -3
- package/dist/{plan-CG3sy5kw.d.mts.map → plan-Ds9oN5QU.d.mts.map} +1 -1
- package/dist/{query-lane-context-Dw1Tz0kv.d.mts → query-lane-context-BUYT0tOb.d.mts} +3 -3
- package/dist/{query-lane-context-Dw1Tz0kv.d.mts.map → query-lane-context-BUYT0tOb.d.mts.map} +1 -1
- package/dist/{sql-execution-plan-D4OimCVH.d.mts → sql-execution-plan-Dopo3SDj.d.mts} +2 -2
- package/dist/{sql-execution-plan-D4OimCVH.d.mts.map → sql-execution-plan-Dopo3SDj.d.mts.map} +1 -1
- package/dist/{types-DoolOzqd.d.mts → types-CLirmH9F.d.mts} +8 -20
- package/dist/types-CLirmH9F.d.mts.map +1 -0
- package/dist/{types-C4EdOD-s.mjs → types-CMznS9Yh.mjs} +24 -5
- package/dist/types-CMznS9Yh.mjs.map +1 -0
- package/dist/{types-C_1ZqLwZ.d.mts → types-CXQF5KS8.d.mts} +19 -6
- package/dist/types-CXQF5KS8.d.mts.map +1 -0
- package/dist/{types-CkTDkFcK.d.mts → types-DRBn98H_.d.mts} +3 -3
- package/dist/{types-CkTDkFcK.d.mts.map → types-DRBn98H_.d.mts.map} +1 -1
- package/dist/{util-DWmhUCEO.mjs → util-DUXs0nZ9.mjs} +1 -1
- package/dist/{util-DWmhUCEO.mjs.map → util-DUXs0nZ9.mjs.map} +1 -1
- package/package.json +12 -11
- package/src/ast/adapter-types.ts +0 -5
- package/src/ast/ddl-types.ts +106 -0
- package/src/ast/types.ts +31 -6
- package/src/contract-free/column.ts +21 -0
- package/src/contract-free/dml.ts +17 -0
- package/src/contract-free/table.ts +390 -0
- package/src/exports/ast.ts +1 -0
- package/src/exports/contract-free.ts +18 -0
- package/src/types.ts +3 -23
- package/dist/types-C4EdOD-s.mjs.map +0 -1
- package/dist/types-C_1ZqLwZ.d.mts.map +0 -1
- package/dist/types-DoolOzqd.d.mts.map +0 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ColumnDefaultLiteralInputValue } from '@prisma-next/contract/types';
|
|
2
|
+
import { isColumnDefaultLiteralInputValue } from '@prisma-next/contract/types';
|
|
3
|
+
import type { AnyParamRef } from './types';
|
|
4
|
+
|
|
5
|
+
export interface DdlColumnDefaultVisitor<R> {
|
|
6
|
+
literal(node: LiteralColumnDefault): R;
|
|
7
|
+
function(node: FunctionColumnDefault): R;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export abstract class DdlColumnDefault {
|
|
11
|
+
abstract readonly kind: string;
|
|
12
|
+
abstract accept<R>(visitor: DdlColumnDefaultVisitor<R>): R;
|
|
13
|
+
|
|
14
|
+
protected freeze(): void {
|
|
15
|
+
Object.freeze(this);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class LiteralColumnDefault extends DdlColumnDefault {
|
|
20
|
+
readonly kind = 'literal' as const;
|
|
21
|
+
readonly value: ColumnDefaultLiteralInputValue;
|
|
22
|
+
|
|
23
|
+
constructor(value: ColumnDefaultLiteralInputValue) {
|
|
24
|
+
super();
|
|
25
|
+
if (!isColumnDefaultLiteralInputValue(value)) {
|
|
26
|
+
throw new Error('Invalid column default literal value');
|
|
27
|
+
}
|
|
28
|
+
this.value = value;
|
|
29
|
+
this.freeze();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override accept<R>(visitor: DdlColumnDefaultVisitor<R>): R {
|
|
33
|
+
return visitor.literal(this);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class FunctionColumnDefault extends DdlColumnDefault {
|
|
38
|
+
readonly kind = 'function' as const;
|
|
39
|
+
readonly expression: string;
|
|
40
|
+
|
|
41
|
+
constructor(expression: string) {
|
|
42
|
+
super();
|
|
43
|
+
this.expression = expression;
|
|
44
|
+
this.freeze();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override accept<R>(visitor: DdlColumnDefaultVisitor<R>): R {
|
|
48
|
+
return visitor.function(this);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type AnyDdlColumnDefault = LiteralColumnDefault | FunctionColumnDefault;
|
|
53
|
+
|
|
54
|
+
export class DdlColumn {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly type: string;
|
|
57
|
+
readonly notNull?: boolean | undefined;
|
|
58
|
+
readonly primaryKey?: boolean | undefined;
|
|
59
|
+
readonly default?: DdlColumnDefault | undefined;
|
|
60
|
+
|
|
61
|
+
constructor(options: {
|
|
62
|
+
readonly name: string;
|
|
63
|
+
readonly type: string;
|
|
64
|
+
readonly notNull?: boolean;
|
|
65
|
+
readonly primaryKey?: boolean;
|
|
66
|
+
readonly default?: DdlColumnDefault;
|
|
67
|
+
}) {
|
|
68
|
+
this.name = options.name;
|
|
69
|
+
this.type = options.type;
|
|
70
|
+
this.notNull = options.notNull;
|
|
71
|
+
this.primaryKey = options.primaryKey;
|
|
72
|
+
this.default = options.default;
|
|
73
|
+
Object.freeze(this);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export abstract class DdlNode {
|
|
78
|
+
abstract readonly kind: string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Structural brand: every DDL node answers `true`. Lets {@link isDdlNode}
|
|
82
|
+
* recognise any `DdlNode` subclass — including target-contributed kinds —
|
|
83
|
+
* without a central kind registry that subclasses would have to register
|
|
84
|
+
* into.
|
|
85
|
+
*/
|
|
86
|
+
isDdlNode(): true {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected freeze(): void {
|
|
91
|
+
Object.freeze(this);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
collectParamRefs(): AnyParamRef[] {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isDdlNode(value: unknown): value is DdlNode {
|
|
100
|
+
return (
|
|
101
|
+
typeof value === 'object' &&
|
|
102
|
+
value !== null &&
|
|
103
|
+
'isDdlNode' in value &&
|
|
104
|
+
typeof value.isDdlNode === 'function'
|
|
105
|
+
);
|
|
106
|
+
}
|
package/src/ast/types.ts
CHANGED
|
@@ -80,7 +80,7 @@ export interface ExpressionFolder<T> {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
export type ProjectionExpr = AnyExpression;
|
|
83
|
-
export type InsertValue = ColumnRef | ParamRef | PreparedParamRef | DefaultValueExpr;
|
|
83
|
+
export type InsertValue = ColumnRef | ParamRef | PreparedParamRef | DefaultValueExpr | RawExpr;
|
|
84
84
|
export type JoinOnExpr = EqColJoinOn | AnyExpression;
|
|
85
85
|
export type WhereArg = AnyExpression | ToWhereExpr;
|
|
86
86
|
export type JsonObjectEntry = {
|
|
@@ -205,6 +205,11 @@ function rewriteInsertValue(value: InsertValue, rewriter: AstRewriter): InsertVa
|
|
|
205
205
|
return rewriter.columnRef ? rewriteColumnRefForInsert(value, rewriter) : value;
|
|
206
206
|
case 'default-value':
|
|
207
207
|
return value;
|
|
208
|
+
// RawExpr insert values are opaque DB-side expressions (e.g. `now()` /
|
|
209
|
+
// `datetime('now')`) carried in value position; they are not a rewrite
|
|
210
|
+
// target on the insert path.
|
|
211
|
+
case 'raw-expr':
|
|
212
|
+
return value;
|
|
208
213
|
}
|
|
209
214
|
}
|
|
210
215
|
|
|
@@ -312,16 +317,25 @@ export class TableSource extends FromSource {
|
|
|
312
317
|
readonly kind = 'table-source' as const;
|
|
313
318
|
readonly name: string;
|
|
314
319
|
readonly alias: string | undefined;
|
|
320
|
+
/**
|
|
321
|
+
* Resolved storage namespace coordinate for this table, stamped when the
|
|
322
|
+
* table proxy constructs the AST. Renderers qualify via the namespace
|
|
323
|
+
* concretion's `qualifyTable()` using this id — never by re-resolving the
|
|
324
|
+
* bare table name at render time.
|
|
325
|
+
*/
|
|
326
|
+
readonly namespaceId: string | undefined;
|
|
315
327
|
|
|
316
|
-
constructor(name: string, alias?: string) {
|
|
328
|
+
protected constructor(name: string, alias?: string, namespaceId?: string) {
|
|
317
329
|
super();
|
|
318
330
|
this.name = name;
|
|
319
331
|
this.alias = alias;
|
|
320
|
-
this.
|
|
332
|
+
this.namespaceId = namespaceId;
|
|
321
333
|
}
|
|
322
334
|
|
|
323
|
-
static named(name: string, alias?: string): TableSource {
|
|
324
|
-
|
|
335
|
+
static named(name: string, alias?: string, namespaceId?: string): TableSource {
|
|
336
|
+
const source = new TableSource(name, alias, namespaceId);
|
|
337
|
+
source.freeze();
|
|
338
|
+
return source;
|
|
325
339
|
}
|
|
326
340
|
|
|
327
341
|
override rewrite(rewriter: AstRewriter): AnyFromSource {
|
|
@@ -857,6 +871,15 @@ export class OrderByItem extends AstNode {
|
|
|
857
871
|
rewrite(rewriter: ExpressionRewriter): OrderByItem {
|
|
858
872
|
return new OrderByItem(this.expr.rewrite(rewriter), this.dir);
|
|
859
873
|
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* A new frozen item with the sort direction flipped and `expr` unchanged.
|
|
877
|
+
* Integrations that own pagination (e.g. backward cursor pagination) use
|
|
878
|
+
* this to reverse a user's sort order without reaching into the AST.
|
|
879
|
+
*/
|
|
880
|
+
reverse(): OrderByItem {
|
|
881
|
+
return new OrderByItem(this.expr, this.dir === 'asc' ? 'desc' : 'asc');
|
|
882
|
+
}
|
|
860
883
|
}
|
|
861
884
|
|
|
862
885
|
export class JsonArrayAggExpr extends Expression {
|
|
@@ -1674,6 +1697,8 @@ export class InsertAst extends QueryAst {
|
|
|
1674
1697
|
for (const value of Object.values(row)) {
|
|
1675
1698
|
if (value.kind === 'param-ref' || value.kind === 'prepared-param-ref') {
|
|
1676
1699
|
refs.push(value);
|
|
1700
|
+
} else if (value.kind === 'raw-expr') {
|
|
1701
|
+
refs.push(...value.collectParamRefs());
|
|
1677
1702
|
}
|
|
1678
1703
|
}
|
|
1679
1704
|
}
|
|
@@ -1897,7 +1922,7 @@ export type AnyExpression =
|
|
|
1897
1922
|
| RawExpr;
|
|
1898
1923
|
export type AnyParamRef = ParamRef | PreparedParamRef;
|
|
1899
1924
|
export type AnyInsertOnConflictAction = DoNothingConflictAction | DoUpdateSetConflictAction;
|
|
1900
|
-
export type AnyInsertValue = ColumnRef | ParamRef | PreparedParamRef | DefaultValueExpr;
|
|
1925
|
+
export type AnyInsertValue = ColumnRef | ParamRef | PreparedParamRef | DefaultValueExpr | RawExpr;
|
|
1901
1926
|
export type AnyOperationArg = AnyExpression | ParamRef | PreparedParamRef | LiteralExpr;
|
|
1902
1927
|
|
|
1903
1928
|
export const queryAstKinds: ReadonlySet<string> = new Set<AnyQueryAst['kind']>([
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ColumnDefaultLiteralInputValue } from '@prisma-next/contract/types';
|
|
2
|
+
import type { DdlColumnDefault } from '../ast/ddl-types';
|
|
3
|
+
import { DdlColumn, FunctionColumnDefault, LiteralColumnDefault } from '../ast/ddl-types';
|
|
4
|
+
|
|
5
|
+
export interface DdlColumnOptions {
|
|
6
|
+
readonly notNull?: boolean;
|
|
7
|
+
readonly primaryKey?: boolean;
|
|
8
|
+
readonly default?: DdlColumnDefault;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function lit(value: ColumnDefaultLiteralInputValue): LiteralColumnDefault {
|
|
12
|
+
return new LiteralColumnDefault(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function fn(expression: string): FunctionColumnDefault {
|
|
16
|
+
return new FunctionColumnDefault(expression);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function col(name: string, type: string, options?: DdlColumnOptions): DdlColumn {
|
|
20
|
+
return new DdlColumn({ name, type, ...options });
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
CfConflictClause,
|
|
3
|
+
CfExpr,
|
|
4
|
+
CfInsertQuery,
|
|
5
|
+
CfSelectQuery,
|
|
6
|
+
CfUpdateQuery,
|
|
7
|
+
CfUpsertBuilder,
|
|
8
|
+
CfUpsertQuery,
|
|
9
|
+
type ColumnDescriptor,
|
|
10
|
+
type ColumnProxy,
|
|
11
|
+
type ColumnSchema,
|
|
12
|
+
type ExcludedProxy,
|
|
13
|
+
type TableHandle,
|
|
14
|
+
type TableInsertRow,
|
|
15
|
+
type TableSetValues,
|
|
16
|
+
table,
|
|
17
|
+
} from './table';
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
2
|
+
import {
|
|
3
|
+
AndExpr,
|
|
4
|
+
type AnyExpression,
|
|
5
|
+
BinaryExpr,
|
|
6
|
+
ColumnRef,
|
|
7
|
+
InsertAst,
|
|
8
|
+
InsertOnConflict,
|
|
9
|
+
type InsertValue,
|
|
10
|
+
NullCheckExpr,
|
|
11
|
+
OrderByItem,
|
|
12
|
+
OrExpr,
|
|
13
|
+
ParamRef,
|
|
14
|
+
ProjectionItem,
|
|
15
|
+
SelectAst,
|
|
16
|
+
type TableSource,
|
|
17
|
+
UpdateAst,
|
|
18
|
+
} from '../ast/types';
|
|
19
|
+
|
|
20
|
+
export interface ColumnDescriptor {
|
|
21
|
+
readonly codecId: string;
|
|
22
|
+
readonly nullable: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ColumnSchema = Record<string, ColumnDescriptor>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A composable WHERE / ON expression. Wraps an `AnyExpression` and exposes
|
|
29
|
+
* fluent boolean combinators, mirroring the spirit of `sql-builder`'s
|
|
30
|
+
* `Expression` interface without the contract-bound type machinery.
|
|
31
|
+
*/
|
|
32
|
+
export class CfExpr {
|
|
33
|
+
constructor(readonly ast: AnyExpression) {}
|
|
34
|
+
|
|
35
|
+
and(other: CfExpr): CfExpr {
|
|
36
|
+
return new CfExpr(AndExpr.of([this.ast, other.ast]));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
or(other: CfExpr): CfExpr {
|
|
40
|
+
return new CfExpr(OrExpr.of([this.ast, other.ast]));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
not(): CfExpr {
|
|
44
|
+
return new CfExpr(this.ast.not());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Per-column authoring handle exposed as a keyed property on a {@link TableHandle}.
|
|
50
|
+
* Carries codec metadata baked at declaration time so no codec ID, table name, or
|
|
51
|
+
* column name needs to be repeated at the call site.
|
|
52
|
+
*/
|
|
53
|
+
export interface ColumnProxy {
|
|
54
|
+
readonly codecId: string;
|
|
55
|
+
readonly nullable: boolean;
|
|
56
|
+
readonly columnName: string;
|
|
57
|
+
readonly tableName: string;
|
|
58
|
+
eq(value: unknown): CfExpr;
|
|
59
|
+
neq(value: unknown): CfExpr;
|
|
60
|
+
isNull(): CfExpr;
|
|
61
|
+
isNotNull(): CfExpr;
|
|
62
|
+
toRef(): ColumnRef;
|
|
63
|
+
toProjectionItem(alias?: string): ProjectionItem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Object passed to the {@link CfConflictClause.doUpdate} callback. Each key
|
|
68
|
+
* resolves to a `ColumnRef` for the corresponding `excluded.<column>`, so the
|
|
69
|
+
* upsert conflict-update branch can copy proposed values without re-binding
|
|
70
|
+
* parameters.
|
|
71
|
+
*/
|
|
72
|
+
export type ExcludedProxy<Schema extends ColumnSchema> = {
|
|
73
|
+
readonly [K in keyof Schema]: ColumnRef;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type TableInsertRow<Schema extends ColumnSchema> = {
|
|
77
|
+
readonly [K in keyof Schema]: unknown;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type TableSetValues<Schema extends ColumnSchema> = {
|
|
81
|
+
readonly [K in keyof Schema]?: unknown;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fluent authoring handle for a fixed control-plane table. Column proxies are
|
|
86
|
+
* exposed as keyed properties so `handle.columnName.eq(value)` composes without
|
|
87
|
+
* threading codec IDs, table names, or column refs at the call site.
|
|
88
|
+
*/
|
|
89
|
+
export type TableHandle<Schema extends ColumnSchema> = {
|
|
90
|
+
readonly source: TableSource;
|
|
91
|
+
insert(row: TableInsertRow<Schema>): CfInsertQuery<Schema>;
|
|
92
|
+
upsert(row: TableInsertRow<Schema>): CfUpsertBuilder<Schema>;
|
|
93
|
+
update(): CfUpdateQuery<Schema>;
|
|
94
|
+
select(...columns: ReadonlyArray<ColumnProxy>): CfSelectQuery;
|
|
95
|
+
} & {
|
|
96
|
+
readonly [K in keyof Schema]: ColumnProxy;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export class CfInsertQuery<Schema extends ColumnSchema> {
|
|
100
|
+
constructor(
|
|
101
|
+
private readonly src: TableSource,
|
|
102
|
+
private readonly schema: Schema,
|
|
103
|
+
private readonly rowValues: TableInsertRow<Schema>,
|
|
104
|
+
private readonly returningItems: ReadonlyArray<ProjectionItem> | undefined = undefined,
|
|
105
|
+
) {}
|
|
106
|
+
|
|
107
|
+
returning(...columns: ReadonlyArray<ColumnProxy>): CfInsertQuery<Schema> {
|
|
108
|
+
return new CfInsertQuery(
|
|
109
|
+
this.src,
|
|
110
|
+
this.schema,
|
|
111
|
+
this.rowValues,
|
|
112
|
+
columns.map((col) => col.toProjectionItem()),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
build(): InsertAst {
|
|
117
|
+
const row = buildInsertRow(this.schema, this.rowValues);
|
|
118
|
+
const ast = InsertAst.into(this.src).withRows([row]);
|
|
119
|
+
return this.returningItems ? ast.withReturning(this.returningItems) : ast;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export class CfUpsertBuilder<Schema extends ColumnSchema> {
|
|
124
|
+
constructor(
|
|
125
|
+
private readonly src: TableSource,
|
|
126
|
+
private readonly schema: Schema,
|
|
127
|
+
private readonly rowValues: TableInsertRow<Schema>,
|
|
128
|
+
) {}
|
|
129
|
+
|
|
130
|
+
onConflict(...columns: ReadonlyArray<ColumnProxy>): CfConflictClause<Schema> {
|
|
131
|
+
return new CfConflictClause(this.src, this.schema, this.rowValues, [...columns]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class CfConflictClause<Schema extends ColumnSchema> {
|
|
136
|
+
constructor(
|
|
137
|
+
private readonly src: TableSource,
|
|
138
|
+
private readonly schema: Schema,
|
|
139
|
+
private readonly rowValues: TableInsertRow<Schema>,
|
|
140
|
+
private readonly conflictCols: ReadonlyArray<ColumnProxy>,
|
|
141
|
+
) {}
|
|
142
|
+
|
|
143
|
+
doUpdate(
|
|
144
|
+
setOrCallback:
|
|
145
|
+
| TableSetValues<Schema>
|
|
146
|
+
| ((excluded: ExcludedProxy<Schema>) => TableSetValues<Schema>),
|
|
147
|
+
): CfUpsertQuery<Schema> {
|
|
148
|
+
const set =
|
|
149
|
+
typeof setOrCallback === 'function'
|
|
150
|
+
? setOrCallback(buildExcludedProxy(this.schema))
|
|
151
|
+
: setOrCallback;
|
|
152
|
+
return new CfUpsertQuery(this.src, this.schema, this.rowValues, this.conflictCols, set);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
doNothing(): CfUpsertQuery<Schema> {
|
|
156
|
+
return new CfUpsertQuery(this.src, this.schema, this.rowValues, this.conflictCols, undefined);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class CfUpsertQuery<Schema extends ColumnSchema> {
|
|
161
|
+
constructor(
|
|
162
|
+
private readonly src: TableSource,
|
|
163
|
+
private readonly schema: Schema,
|
|
164
|
+
private readonly rowValues: TableInsertRow<Schema>,
|
|
165
|
+
private readonly conflictCols: ReadonlyArray<ColumnProxy>,
|
|
166
|
+
private readonly updateSet: TableSetValues<Schema> | undefined,
|
|
167
|
+
) {}
|
|
168
|
+
|
|
169
|
+
build(): InsertAst {
|
|
170
|
+
const row = buildInsertRow(this.schema, this.rowValues);
|
|
171
|
+
const conflictRefs = this.conflictCols.map((col) => col.toRef());
|
|
172
|
+
const onConflict =
|
|
173
|
+
this.updateSet === undefined
|
|
174
|
+
? InsertOnConflict.on(conflictRefs).doNothing()
|
|
175
|
+
: InsertOnConflict.on(conflictRefs).doUpdateSet(buildSetMap(this.schema, this.updateSet));
|
|
176
|
+
return InsertAst.into(this.src).withRows([row]).withOnConflict(onConflict);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export class CfUpdateQuery<Schema extends ColumnSchema> {
|
|
181
|
+
constructor(
|
|
182
|
+
private readonly src: TableSource,
|
|
183
|
+
private readonly schema: Schema,
|
|
184
|
+
private readonly setValues: TableSetValues<Schema> | undefined = undefined,
|
|
185
|
+
private readonly whereExpr: CfExpr | undefined = undefined,
|
|
186
|
+
private readonly returningItems: ReadonlyArray<ProjectionItem> | undefined = undefined,
|
|
187
|
+
) {}
|
|
188
|
+
|
|
189
|
+
set(values: TableSetValues<Schema>): CfUpdateQuery<Schema> {
|
|
190
|
+
return new CfUpdateQuery(this.src, this.schema, values, this.whereExpr, this.returningItems);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
where(expr: CfExpr): CfUpdateQuery<Schema> {
|
|
194
|
+
return new CfUpdateQuery(this.src, this.schema, this.setValues, expr, this.returningItems);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
returning(...columns: ReadonlyArray<ColumnProxy>): CfUpdateQuery<Schema> {
|
|
198
|
+
return new CfUpdateQuery(
|
|
199
|
+
this.src,
|
|
200
|
+
this.schema,
|
|
201
|
+
this.setValues,
|
|
202
|
+
this.whereExpr,
|
|
203
|
+
columns.map((col) => col.toProjectionItem()),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
build(): UpdateAst {
|
|
208
|
+
const set = buildSetMap(this.schema, this.setValues);
|
|
209
|
+
const base = UpdateAst.table(this.src).withSet(set);
|
|
210
|
+
const withWhere = this.whereExpr ? base.withWhere(this.whereExpr.ast) : base;
|
|
211
|
+
return this.returningItems ? withWhere.withReturning(this.returningItems) : withWhere;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export class CfSelectQuery {
|
|
216
|
+
constructor(
|
|
217
|
+
private readonly src: TableSource,
|
|
218
|
+
private readonly projectionItems: ReadonlyArray<ProjectionItem>,
|
|
219
|
+
private readonly whereExpr: CfExpr | undefined = undefined,
|
|
220
|
+
private readonly orderByItems: ReadonlyArray<OrderByItem> = [],
|
|
221
|
+
) {}
|
|
222
|
+
|
|
223
|
+
where(expr: CfExpr): CfSelectQuery {
|
|
224
|
+
return new CfSelectQuery(this.src, this.projectionItems, expr, this.orderByItems);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
orderBy(column: ColumnProxy, dir: 'asc' | 'desc' = 'asc'): CfSelectQuery {
|
|
228
|
+
const item = dir === 'asc' ? OrderByItem.asc(column.toRef()) : OrderByItem.desc(column.toRef());
|
|
229
|
+
return new CfSelectQuery(this.src, this.projectionItems, this.whereExpr, [
|
|
230
|
+
...this.orderByItems,
|
|
231
|
+
item,
|
|
232
|
+
]);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
build(): SelectAst {
|
|
236
|
+
const base = SelectAst.from(this.src).withProjection(this.projectionItems);
|
|
237
|
+
const withWhere = this.whereExpr ? base.withWhere(this.whereExpr.ast) : base;
|
|
238
|
+
return this.orderByItems.length > 0 ? withWhere.withOrderBy(this.orderByItems) : withWhere;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Declare a control-plane table once, binding column codecs at declaration time.
|
|
244
|
+
* Returns a `TableHandle` whose column properties compose expressions directly
|
|
245
|
+
* without per-call-site codec or column-name threading.
|
|
246
|
+
*
|
|
247
|
+
* ```ts
|
|
248
|
+
* const marker = pgTable({ name: 'marker', schema: 'prisma_contract' }, {
|
|
249
|
+
* space: text(),
|
|
250
|
+
* core_hash: text(),
|
|
251
|
+
* updated_at: timestamptz(),
|
|
252
|
+
* });
|
|
253
|
+
*
|
|
254
|
+
* const query = marker.update()
|
|
255
|
+
* .set({ core_hash: newHash, updated_at: NOW })
|
|
256
|
+
* .where(marker.space.eq(space).and(marker.core_hash.eq(expectedFrom)))
|
|
257
|
+
* .returning(marker.space)
|
|
258
|
+
* .build();
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
export function table<Schema extends ColumnSchema>(
|
|
262
|
+
source: TableSource,
|
|
263
|
+
schema: Schema,
|
|
264
|
+
): TableHandle<Schema> {
|
|
265
|
+
const proxies: Record<string, ColumnProxy> = {};
|
|
266
|
+
for (const [col, desc] of Object.entries(schema)) {
|
|
267
|
+
proxies[col] = makeColumnProxy(source.alias ?? source.name, col, desc);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const handle = {
|
|
271
|
+
...proxies,
|
|
272
|
+
source,
|
|
273
|
+
insert: (row: TableInsertRow<Schema>) => new CfInsertQuery(source, schema, row),
|
|
274
|
+
upsert: (row: TableInsertRow<Schema>) => new CfUpsertBuilder(source, schema, row),
|
|
275
|
+
update: () => new CfUpdateQuery(source, schema),
|
|
276
|
+
select: (...cols: ReadonlyArray<ColumnProxy>) =>
|
|
277
|
+
new CfSelectQuery(
|
|
278
|
+
source,
|
|
279
|
+
cols.map((col) => col.toProjectionItem()),
|
|
280
|
+
),
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
return blindCast<
|
|
284
|
+
TableHandle<Schema>,
|
|
285
|
+
'Column proxies are dynamically built from Schema keys — TypeScript cannot verify the per-key ColumnProxy constraint at the spread call site. Construction is correct by construction: every key maps to makeColumnProxy(source.name, key, schema[key]).'
|
|
286
|
+
>(handle);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function makeColumnProxy(
|
|
290
|
+
tableName: string,
|
|
291
|
+
columnName: string,
|
|
292
|
+
desc: ColumnDescriptor,
|
|
293
|
+
): ColumnProxy {
|
|
294
|
+
const ref = ColumnRef.of(tableName, columnName);
|
|
295
|
+
return {
|
|
296
|
+
codecId: desc.codecId,
|
|
297
|
+
nullable: desc.nullable,
|
|
298
|
+
columnName,
|
|
299
|
+
tableName,
|
|
300
|
+
eq: (value) =>
|
|
301
|
+
value === null
|
|
302
|
+
? new CfExpr(NullCheckExpr.isNull(ref))
|
|
303
|
+
: new CfExpr(BinaryExpr.eq(ref, toSetExpression(value, desc))),
|
|
304
|
+
neq: (value) =>
|
|
305
|
+
value === null
|
|
306
|
+
? new CfExpr(NullCheckExpr.isNotNull(ref))
|
|
307
|
+
: new CfExpr(BinaryExpr.neq(ref, toSetExpression(value, desc))),
|
|
308
|
+
isNull: () => new CfExpr(NullCheckExpr.isNull(ref)),
|
|
309
|
+
isNotNull: () => new CfExpr(NullCheckExpr.isNotNull(ref)),
|
|
310
|
+
toRef: () => ref,
|
|
311
|
+
toProjectionItem: (alias = columnName) =>
|
|
312
|
+
ProjectionItem.of(alias, ref, { codecId: desc.codecId }),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function buildExcludedProxy<Schema extends ColumnSchema>(schema: Schema): ExcludedProxy<Schema> {
|
|
317
|
+
return blindCast<
|
|
318
|
+
ExcludedProxy<Schema>,
|
|
319
|
+
'Object.fromEntries cannot preserve per-key ColumnRef types — correct by construction: every key maps to ColumnRef.of("excluded", key).'
|
|
320
|
+
>(Object.fromEntries(Object.keys(schema).map((col) => [col, ColumnRef.of('excluded', col)])));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function isExpressionSource(value: unknown): value is { toExpr(): AnyExpression } {
|
|
324
|
+
return (
|
|
325
|
+
typeof value === 'object' &&
|
|
326
|
+
value !== null &&
|
|
327
|
+
'toExpr' in value &&
|
|
328
|
+
typeof value.toExpr === 'function'
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function toInsertValue(value: unknown, desc: ColumnDescriptor): InsertValue {
|
|
333
|
+
if (isExpressionSource(value)) {
|
|
334
|
+
const expr = value.toExpr();
|
|
335
|
+
if (
|
|
336
|
+
expr.kind === 'column-ref' ||
|
|
337
|
+
expr.kind === 'param-ref' ||
|
|
338
|
+
expr.kind === 'prepared-param-ref' ||
|
|
339
|
+
expr.kind === 'raw-expr'
|
|
340
|
+
) {
|
|
341
|
+
return expr;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return ParamRef.of(value, { codec: { codecId: desc.codecId } });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function toSetExpression(value: unknown, desc: ColumnDescriptor): AnyExpression {
|
|
348
|
+
if (isExpressionSource(value)) {
|
|
349
|
+
return value.toExpr();
|
|
350
|
+
}
|
|
351
|
+
return ParamRef.of(value, { codec: { codecId: desc.codecId } });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function buildInsertRow<Schema extends ColumnSchema>(
|
|
355
|
+
schema: Schema,
|
|
356
|
+
values: TableInsertRow<Schema>,
|
|
357
|
+
): Record<string, InsertValue> {
|
|
358
|
+
const row: Record<string, InsertValue> = {};
|
|
359
|
+
const rawValues = blindCast<
|
|
360
|
+
Record<string, unknown>,
|
|
361
|
+
'TableInsertRow<Schema> maps Schema keys to unknown; indexing by the same string keys is correct by construction'
|
|
362
|
+
>(values);
|
|
363
|
+
for (const [col, desc] of Object.entries(schema)) {
|
|
364
|
+
row[col] = toInsertValue(rawValues[col], desc);
|
|
365
|
+
}
|
|
366
|
+
return row;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function buildSetMap<Schema extends ColumnSchema>(
|
|
370
|
+
schema: Schema,
|
|
371
|
+
values: TableSetValues<Schema> | undefined,
|
|
372
|
+
): Record<string, AnyExpression> {
|
|
373
|
+
if (values === undefined) return {};
|
|
374
|
+
const set: Record<string, AnyExpression> = {};
|
|
375
|
+
const rawSchema = blindCast<
|
|
376
|
+
Record<string, ColumnDescriptor>,
|
|
377
|
+
'Schema extends ColumnSchema = Record<string, ColumnDescriptor>; runtime key access is correct by construction'
|
|
378
|
+
>(schema);
|
|
379
|
+
const rawValues = blindCast<
|
|
380
|
+
Record<string, unknown>,
|
|
381
|
+
'TableSetValues<Schema> maps Schema keys to unknown; iterating with Object.entries is correct by construction'
|
|
382
|
+
>(values);
|
|
383
|
+
for (const [col, value] of Object.entries(rawValues)) {
|
|
384
|
+
const desc = rawSchema[col];
|
|
385
|
+
if (desc !== undefined) {
|
|
386
|
+
set[col] = toSetExpression(value, desc);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return set;
|
|
390
|
+
}
|
package/src/exports/ast.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { col, type DdlColumnOptions, fn, lit } from '../contract-free/column';
|
|
2
|
+
export {
|
|
3
|
+
CfConflictClause,
|
|
4
|
+
CfExpr,
|
|
5
|
+
CfInsertQuery,
|
|
6
|
+
CfSelectQuery,
|
|
7
|
+
CfUpdateQuery,
|
|
8
|
+
CfUpsertBuilder,
|
|
9
|
+
CfUpsertQuery,
|
|
10
|
+
type ColumnDescriptor,
|
|
11
|
+
type ColumnProxy,
|
|
12
|
+
type ColumnSchema,
|
|
13
|
+
type ExcludedProxy,
|
|
14
|
+
type TableHandle,
|
|
15
|
+
type TableInsertRow,
|
|
16
|
+
type TableSetValues,
|
|
17
|
+
table,
|
|
18
|
+
} from '../contract-free/dml';
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Contract,
|
|
1
|
+
import type { Contract, ContractModelDefinitions } from '@prisma-next/contract/types';
|
|
2
2
|
import type { ParamSpec } from '@prisma-next/operations';
|
|
3
3
|
import type {
|
|
4
4
|
ExtractFieldOutputTypes,
|
|
@@ -17,7 +17,7 @@ export type Expr = ColumnRef | ParamRef;
|
|
|
17
17
|
* whose `storage.table` matches.
|
|
18
18
|
*/
|
|
19
19
|
type ExtractTableToModel<TContract extends Contract<SqlStorage>, TableName extends string> =
|
|
20
|
-
|
|
20
|
+
ContractModelDefinitions<TContract> extends infer Models extends Record<string, unknown>
|
|
21
21
|
? {
|
|
22
22
|
[M in keyof Models & string]: Models[M] extends {
|
|
23
23
|
readonly storage: { readonly table: TableName };
|
|
@@ -37,7 +37,7 @@ type ExtractColumnToField<
|
|
|
37
37
|
ColumnName extends string,
|
|
38
38
|
> =
|
|
39
39
|
ExtractTableToModel<TContract, TableName> extends infer ModelName extends string
|
|
40
|
-
?
|
|
40
|
+
? ContractModelDefinitions<TContract> extends infer Models extends Record<string, unknown>
|
|
41
41
|
? ModelName & keyof Models extends infer MKey extends string
|
|
42
42
|
? Models[MKey] extends {
|
|
43
43
|
readonly storage: { readonly fields: infer Fields extends Record<string, unknown> };
|
|
@@ -151,26 +151,6 @@ export type ComputeColumnJsType<
|
|
|
151
151
|
: never
|
|
152
152
|
: never;
|
|
153
153
|
|
|
154
|
-
/**
|
|
155
|
-
* Utility type to check if a contract has the required capabilities for includeMany.
|
|
156
|
-
* Requires both `lateral` and `jsonAgg` to be `true` in the contract's capabilities for the target.
|
|
157
|
-
* Capabilities are nested by target: `{ [target]: { lateral: true, jsonAgg: true } }`
|
|
158
|
-
*/
|
|
159
|
-
export type HasIncludeManyCapabilities<TContract extends Contract<SqlStorage>> = TContract extends {
|
|
160
|
-
capabilities: infer C;
|
|
161
|
-
target: infer T;
|
|
162
|
-
}
|
|
163
|
-
? T extends string
|
|
164
|
-
? C extends Record<string, Record<string, boolean>>
|
|
165
|
-
? C extends { [K in T]: infer TargetCaps }
|
|
166
|
-
? TargetCaps extends { lateral: true; jsonAgg: true }
|
|
167
|
-
? true
|
|
168
|
-
: false
|
|
169
|
-
: false
|
|
170
|
-
: false
|
|
171
|
-
: false
|
|
172
|
-
: false;
|
|
173
|
-
|
|
174
154
|
/**
|
|
175
155
|
* Alias for the SQL-domain executable plan, exposed under the legacy
|
|
176
156
|
* `SqlPlan` name for compatibility with SQL builder/utility call sites.
|