Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
169 changes: 77 additions & 92 deletions packages/runtime/src/client/crud/dialects/base.ts

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions packages/runtime/src/client/crud/dialects/postgresql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,14 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
// simple select by default
let result = eb.selectFrom(`${relationModel} as ${joinTableName}`);

const joinBases: string[] = [];

// however if there're filter/orderBy/take/skip,
// we need to build a subquery to handle them before aggregation
result = eb.selectFrom(() => {
let subQuery = eb.selectFrom(relationModel);
let subQuery = this.buildSelectModel(eb, relationModel);
subQuery = this.buildSelectAllFields(
relationModel,
subQuery,
typeof payload === 'object' ? payload?.omit : undefined,
joinBases,
);

if (payload && typeof payload === 'object') {
Expand Down
4 changes: 1 addition & 3 deletions packages/runtime/src/client/crud/dialects/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,12 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
const subQueryName = `${parentName}$${relationField}`;

let tbl = eb.selectFrom(() => {
let subQuery = eb.selectFrom(relationModel);
let subQuery = this.buildSelectModel(eb, relationModel);

const joinBases: string[] = [];
subQuery = this.buildSelectAllFields(
relationModel,
subQuery,
typeof payload === 'object' ? payload?.omit : undefined,
joinBases,
);

if (payload && typeof payload === 'object') {
Expand Down
38 changes: 19 additions & 19 deletions packages/runtime/src/client/crud/operations/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
args: FindArgs<Schema, GetModels<Schema>, true> | undefined,
): Promise<any[]> {
// table
let query = kysely.selectFrom(model);
let query = this.dialect.buildSelectModel(expressionBuilder(), model);

// where
if (args?.where) {
Expand Down Expand Up @@ -182,22 +182,19 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
}
}

// for deduplicating base joins
const joinedBases: string[] = [];

// select
if (args && 'select' in args && args.select) {
// select is mutually exclusive with omit
query = this.buildFieldSelection(model, query, args.select, model, joinedBases);
query = this.buildFieldSelection(model, query, args.select, model);
} else {
// include all scalar fields except those in omit
query = this.dialect.buildSelectAllFields(model, query, (args as any)?.omit, joinedBases);
query = this.dialect.buildSelectAllFields(model, query, (args as any)?.omit);
}

// include
if (args && 'include' in args && args.include) {
// note that 'omit' is handled above already
query = this.buildFieldSelection(model, query, args.include, model, joinedBases);
query = this.buildFieldSelection(model, query, args.include, model);
}

if (args?.cursor) {
Expand All @@ -207,13 +204,15 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
query = query.modifyEnd(this.makeContextComment({ model, operation: 'read' }));

let result: any[] = [];
const queryId = { queryId: `zenstack-${createId()}` };
const compiled = kysely.getExecutor().compileQuery(query.toOperationNode(), queryId);
try {
result = await query.execute();
const r = await kysely.getExecutor().executeQuery(compiled, queryId);
result = r.rows;
} catch (err) {
const { sql, parameters } = query.compile();
let message = `Failed to execute query: ${err}, sql: ${sql}`;
let message = `Failed to execute query: ${err}, sql: ${compiled.sql}`;
if (this.options.debug) {
message += `, parameters: \n${parameters.map((p) => inspect(p)).join('\n')}`;
message += `, parameters: \n${compiled.parameters.map((p) => inspect(p)).join('\n')}`;
}
throw new QueryError(message, err);
}
Expand Down Expand Up @@ -248,7 +247,6 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
query: SelectQueryBuilder<any, any, any>,
selectOrInclude: Record<string, any>,
parentAlias: string,
joinedBases: string[],
) {
let result = query;

Expand All @@ -265,17 +263,12 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
const fieldDef = this.requireField(model, field);
if (!fieldDef.relation) {
// scalar field
result = this.dialect.buildSelectField(result, model, parentAlias, field, joinedBases);
result = this.dialect.buildSelectField(result, model, parentAlias, field);
} else {
if (!fieldDef.array && !fieldDef.optional && payload.where) {
throw new QueryError(`Field "${field}" doesn't support filtering`);
}
if (fieldDef.originModel) {
// relation is inherited from a delegate base model, need to build a join
if (!joinedBases.includes(fieldDef.originModel)) {
joinedBases.push(fieldDef.originModel);
result = this.dialect.buildDelegateJoin(parentAlias, fieldDef.originModel, result);
}
result = this.dialect.buildRelationSelection(
result,
fieldDef.originModel,
Expand Down Expand Up @@ -399,8 +392,15 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
model: GetModels<Schema>,
data: any,
fromRelation?: FromRelationContext<Schema>,
creatingForDelegate = false,
): Promise<unknown> {
const modelDef = this.requireModel(model);

// additional validations
if (modelDef.isDelegate && !creatingForDelegate) {
throw new QueryError(`Model "${this.model}" is a delegate and cannot be created directly.`);
}

let createFields: any = {};
let parentUpdateTask: ((entity: any) => Promise<unknown>) | undefined = undefined;

Expand Down Expand Up @@ -573,7 +573,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
thisCreateFields[discriminatorField] = forModel;

// create base model entity
const createResult = await this.create(kysely, model as GetModels<Schema>, thisCreateFields);
const createResult = await this.create(kysely, model as GetModels<Schema>, thisCreateFields, undefined, true);

// copy over id fields from base model
const idValues = extractIdFields(createResult, this.schema, model);
Expand Down
6 changes: 0 additions & 6 deletions packages/runtime/src/client/crud/operations/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,9 @@ import type { GetModels, SchemaDef } from '../../../schema';
import type { CreateArgs, CreateManyAndReturnArgs, CreateManyArgs, WhereInput } from '../../crud-types';
import { getIdValues } from '../../query-utils';
import { BaseOperationHandler } from './base';
import { QueryError } from '../../errors';

export class CreateOperationHandler<Schema extends SchemaDef> extends BaseOperationHandler<Schema> {
async handle(operation: 'create' | 'createMany' | 'createManyAndReturn', args: unknown | undefined) {
const modelDef = this.requireModel(this.model);
if (modelDef.isDelegate) {
throw new QueryError(`Model "${this.model}" is a delegate and cannot be created directly.`);
}

// normalize args to strip `undefined` fields
const normalizedArgs = this.normalizeArgs(args);

Expand Down
Loading