Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1f944c9
spec(ast): add kind-discriminant migration spec for TML-2096
wmadden Mar 25, 2026
947625c
plan(ast): add execution plan for kind-discriminant migration
wmadden Mar 25, 2026
6e7a1a4
add kind discriminant tags to all AST node classes
wmadden Mar 25, 2026
5dab01e
migrate AST internals and guards from instanceof to kind dispatch
wmadden Mar 25, 2026
bd01359
migrate postgres adapter from instanceof to kind dispatch
wmadden Mar 25, 2026
e86fc3d
migrate sql-runtime lints from instanceof to kind dispatch
wmadden Mar 25, 2026
9c4b253
migrate ORM client from instanceof to kind dispatch
wmadden Mar 25, 2026
2018719
migrate Kysely lane from instanceof to kind dispatch
wmadden Mar 25, 2026
4020602
migrate sql-lane from instanceof to kind dispatch
wmadden Mar 25, 2026
a2505ae
migrate test assertions from instanceof to kind-based dispatch
wmadden Mar 25, 2026
1775c86
migrate budgets plugin from instanceof to kind dispatch
wmadden Mar 25, 2026
1378d55
narrow kind on intermediate abstract classes for compile-time safety
wmadden Mar 25, 2026
51d1596
chore: reformat plan markdown tables and task lists
wmadden Mar 25, 2026
6f89648
remove dead branch in JoinAst.rewrite
wmadden Mar 25, 2026
ee6869a
migrate all toBeInstanceOf assertions to kind-based checks
wmadden Mar 25, 2026
f664f15
remove redundant kind assertions that test compile-time constants
wmadden Mar 25, 2026
a166dcf
export canonical kind sets from relational-core
wmadden Mar 25, 2026
e7d3be1
export isQueryAst and isWhereExpr guards from relational-core
wmadden Mar 25, 2026
f88d415
fix typecheck errors from abstract class kind narrowing
wmadden Mar 25, 2026
ea84c2f
strengthen test assertions: use kind discriminants instead of toBeDef…
wmadden Mar 26, 2026
ceda6cf
prevent shorthand filters from being misclassified as AST nodes
wmadden Mar 26, 2026
fcb3ae7
fix(budgets): address review findings — explain sig, concurrency, dea…
wmadden Mar 26, 2026
ed06563
fix(budgets): latency shouldBlock uses mode only, not severity
wmadden Mar 26, 2026
c957135
make all kind-dispatch switches exhaustive with never checks
wmadden Mar 26, 2026
7aedb4b
replace abstract class types with discriminated unions in AST
wmadden Mar 27, 2026
8ee4f5a
propagate union types to sql-lane, kysely-lane, and sql-runtime
wmadden Mar 27, 2026
248e7a8
propagate union types to postgres adapter
wmadden Mar 27, 2026
3c91436
propagate union types to sql-orm-client
wmadden Mar 27, 2026
e130fa4
add spec and plan for AST union-typed fields
wmadden Mar 27, 2026
a9d18ea
add explanatory comment on Expression.toExpr() double cast
wmadden Mar 27, 2026
c1cb7e9
address code review feedback on union-typed fields
wmadden Mar 27, 2026
bb7a447
$(cat <<'EOF'
wmadden Mar 27, 2026
8b7ae37
refactor: remove some redundant type assertions
aqrln Mar 27, 2026
7e71428
refactor: simplify exhaustiveness checks
aqrln Mar 27, 2026
6e210df
refactor: type safe conversions from base classes to unions
aqrln Mar 27, 2026
f008607
style: fix formatting issues
aqrln Mar 27, 2026
9fd5b10
refactor: simplify more exhaustiveness checks
aqrln Mar 27, 2026
d736dbd
refactor: remove extractRefsFromAst function
aqrln Mar 27, 2026
a46c196
refactor: simplify all exhaustiveness checks
aqrln Mar 27, 2026
6678a48
style: remove unused import
aqrln Mar 27, 2026
95ae31b
refactor: remove unnecessary SelectAst casts
aqrln Mar 27, 2026
2dcb590
refactor: remove unreachable check
aqrln Mar 27, 2026
994d39d
refactor: remove more obsolete type assertions
aqrln Mar 27, 2026
059521a
fix: resolve naming collision with a duck-typed ToWhereExpr interface
aqrln Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
InsertAst,
InsertOnConflict,
type InsertValue,
ParamRef,
type ParamRef,
UpdateAst,
} from '@prisma-next/sql-relational-core/ast';
import {
Expand Down Expand Up @@ -37,7 +37,7 @@ import { expandSelectAll } from './transform-select';
import { resolveColumnRef, transformTableRef, validateColumn } from './transform-validate';

function assertParamRef(value: ReturnType<typeof transformValue>): ParamRef {
if (!(value instanceof ParamRef)) {
if (value.kind !== 'param-ref') {
throw new KyselyTransformError(
'Only parameterized VALUES are supported in Kysely transform lane',
KYSELY_TRANSFORM_ERROR_CODES.UNSUPPORTED_NODE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import type { ParamDescriptor } from '@prisma-next/contract/types';
import type { BinaryOp, JoinOnExpr, WhereExpr } from '@prisma-next/sql-relational-core/ast';
import type {
AnyWhereExpr,
BinaryOp,
JoinOnExpr,
WhereExpr,
} from '@prisma-next/sql-relational-core/ast';
import {
AndExpr,
BinaryExpr,
ColumnRef,
type ColumnRef,
EqColJoinOn,
ListLiteralExpr,
LiteralExpr,
Expand Down Expand Up @@ -251,13 +256,14 @@ export function transformJoinOn(
);
}

const narrowed = expr as AnyWhereExpr;

@aqrln aqrln Mar 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ideal. If AnyWhereExpr is expected here, then transformWhereExpr should return AnyWhereExpr.

if (
expr instanceof BinaryExpr &&
expr.op === 'eq' &&
expr.left instanceof ColumnRef &&
expr.right instanceof ColumnRef
narrowed.kind === 'binary' &&
narrowed.op === 'eq' &&
narrowed.left.kind === 'column-ref' &&
narrowed.right.kind === 'column-ref'
) {
return EqColJoinOn.of(expr.left, expr.right);
return EqColJoinOn.of(narrowed.left as ColumnRef, narrowed.right as ColumnRef);
}

return expr;
Expand Down
56 changes: 30 additions & 26 deletions packages/2-sql/4-lanes/kysely-lane/src/transform/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
import type { PlanRefs } from '@prisma-next/contract/types';
import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
import { ColumnRef, type QueryAst, SelectAst } from '@prisma-next/sql-relational-core/ast';
import type { AnyQueryAst, ColumnRef } from '@prisma-next/sql-relational-core/ast';
import { ifDefined } from '@prisma-next/utils/defined';
import { DeleteQueryNode, InsertQueryNode, SelectQueryNode, UpdateQueryNode } from 'kysely';
import { KYSELY_TRANSFORM_ERROR_CODES, KyselyTransformError } from './errors';
Expand All @@ -21,7 +21,7 @@ import { transformSelect } from './transform-select';

export type { TransformResult };

function extractRefsFromAst(ast: QueryAst): PlanRefs {
function extractRefsFromAst(ast: AnyQueryAst): PlanRefs {
return ast.collectRefs();
}

Expand Down Expand Up @@ -49,7 +49,7 @@ export function transformKyselyToPnAst(

const ctx = createContext(contract, parameters);

let ast: QueryAst;
let ast: AnyQueryAst;
if (SelectQueryNode.is(query)) {
ast = transformSelect(query, ctx);
} else if (InsertQueryNode.is(query)) {
Expand Down Expand Up @@ -77,19 +77,21 @@ export function transformKyselyToPnAst(

let projection: Record<string, string> | undefined;
let projectionTypes: Record<string, string> | undefined;
if (ast instanceof SelectAst) {
const select = ast.kind === 'select' ? ast : undefined;
if (select) {
projection = Object.fromEntries(
ast.projection.map((projected) => [
projected.alias,
projected.expr instanceof ColumnRef ? projected.expr.column : projected.alias,
]),
select.projection.map((projected) => {
const col =
projected.expr.kind === 'column-ref' ? (projected.expr as ColumnRef) : undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
projected.expr.kind === 'column-ref' ? (projected.expr as ColumnRef) : undefined;
projected.expr.kind === 'column-ref' ? projected.expr : undefined;

return [projected.alias, col?.column ?? projected.alias];
}),
);

projectionTypes = {};
for (const projected of ast.projection) {
if (projected.expr instanceof ColumnRef) {
const column =
ctx.contract.storage.tables[projected.expr.table]?.columns[projected.expr.column];
for (const projected of select.projection) {
const col = projected.expr.kind === 'column-ref' ? (projected.expr as ColumnRef) : undefined;
Comment thread
aqrln marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const col = projected.expr.kind === 'column-ref' ? (projected.expr as ColumnRef) : undefined;
const col = projected.expr.kind === 'column-ref' ? projected.expr : undefined;

if (col) {
const column = ctx.contract.storage.tables[col.table]?.columns[col.column];
if (column) {
projectionTypes[projected.alias] = column.codecId;
}
Expand All @@ -105,8 +107,8 @@ export function transformKyselyToPnAst(
'projectionTypes',
projectionTypes && Object.keys(projectionTypes).length > 0 ? projectionTypes : undefined,
),
...ifDefined('selectAllIntent', ast instanceof SelectAst ? ast.selectAllIntent : undefined),
...ifDefined('limit', ast instanceof SelectAst ? ast.limit : undefined),
...ifDefined('selectAllIntent', select?.selectAllIntent),
...ifDefined('limit', select?.limit),
};

return { ast, metaAdditions };
Expand All @@ -131,7 +133,7 @@ export function transformKyselyToPnAstCollectingParams(

const ctx = createContext(contract);

let ast: QueryAst;
let ast: AnyQueryAst;
if (SelectQueryNode.is(query)) {
ast = transformSelect(query, ctx);
} else if (InsertQueryNode.is(query)) {
Expand Down Expand Up @@ -159,19 +161,21 @@ export function transformKyselyToPnAstCollectingParams(

let projection: Record<string, string> | undefined;
let projectionTypes: Record<string, string> | undefined;
if (ast instanceof SelectAst) {
const select = ast.kind === 'select' ? ast : undefined;
if (select) {
projection = Object.fromEntries(
ast.projection.map((projected) => [
projected.alias,
projected.expr instanceof ColumnRef ? projected.expr.column : projected.alias,
]),
select.projection.map((projected) => {
const col =
projected.expr.kind === 'column-ref' ? (projected.expr as ColumnRef) : undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
projected.expr.kind === 'column-ref' ? (projected.expr as ColumnRef) : undefined;
projected.expr.kind === 'column-ref' ? projected.expr : undefined;

return [projected.alias, col?.column ?? projected.alias];
}),
);

projectionTypes = {};
for (const projected of ast.projection) {
if (projected.expr instanceof ColumnRef) {
const column =
ctx.contract.storage.tables[projected.expr.table]?.columns[projected.expr.column];
for (const projected of select.projection) {
const col = projected.expr.kind === 'column-ref' ? (projected.expr as ColumnRef) : undefined;
if (col) {
const column = ctx.contract.storage.tables[col.table]?.columns[col.column];
if (column) {
projectionTypes[projected.alias] = column.codecId;
}
Expand All @@ -187,8 +191,8 @@ export function transformKyselyToPnAstCollectingParams(
'projectionTypes',
projectionTypes && Object.keys(projectionTypes).length > 0 ? projectionTypes : undefined,
),
...ifDefined('selectAllIntent', ast instanceof SelectAst ? ast.selectAllIntent : undefined),
...ifDefined('limit', ast instanceof SelectAst ? ast.limit : undefined),
...ifDefined('selectAllIntent', select?.selectAllIntent),
...ifDefined('limit', select?.limit),
};

return { ast, params: ctx.params, metaAdditions };
Expand Down
17 changes: 10 additions & 7 deletions packages/2-sql/4-lanes/kysely-lane/src/where-expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
import {
type BoundWhereExpr,
ListLiteralExpr,
ParamRef,
SelectAst,
type SelectAst,
type ToWhereExpr,
} from '@prisma-next/sql-relational-core/ast';
import type { CompiledQuery } from 'kysely';
Expand All @@ -29,21 +28,25 @@ export function buildKyselyWhereExpr<Row>(
options: BuildKyselyPlanOptions = {},
): ToWhereExpr {
const plan = buildKyselyPlan(contract, compiledQuery, options);
if (!(plan.ast instanceof SelectAst) || !plan.ast.where) {
if (plan.ast.kind !== 'select') {
throw new Error('whereExpr(...) requires a select query with a where clause');
}
const selectAst = plan.ast as SelectAst;
if (!selectAst.where) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (plan.ast.kind !== 'select') {
throw new Error('whereExpr(...) requires a select query with a where clause');
}
const selectAst = plan.ast as SelectAst;
if (!selectAst.where) {
if (plan.ast.kind !== 'select' || !plan.ast.where) {
throw new Error('whereExpr(...) requires a select query with a where clause');
}

throw new Error('whereExpr(...) requires a select query with a where clause');
}

const indexes = [...new Set(collectParamIndexes(plan.ast.where))].sort((a, b) => a - b);
const indexes = [...new Set(collectParamIndexes(selectAst.where))].sort((a, b) => a - b);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

plan.ast instead of selectAst here and below after applying the suggestion above

if (indexes.length === 0) {
return new LaneWhereExpr({
expr: plan.ast.where,
expr: selectAst.where,
params: [],
paramDescriptors: [],
});
}

const remap = new Map<number, number>(indexes.map((index, i) => [index, i + 1]));
const remappedExpr = remapParamIndexes(plan.ast.where, remap);
const remappedExpr = remapParamIndexes(selectAst.where, remap);
const params = indexes.map((index) => {
if (index <= 0 || index > plan.params.length) {
throw new Error(`whereExpr(...) payload is invalid: missing param value for index ${index}`);
Expand Down Expand Up @@ -97,7 +100,7 @@ function remapParamIndexes(
listLiteral: (list) =>
new ListLiteralExpr(
list.values.map((value) => {
if (!(value instanceof ParamRef)) {
if (value.kind !== 'param-ref') {
return value;
}
const newIndex = remap.get(value.index);
Expand Down
Loading
Loading