From 9c9acc6a4dbaf90b178397d3ceafdc22d3cb2b46 Mon Sep 17 00:00:00 2001 From: Naor Peled Date: Sat, 26 Nov 2022 10:29:26 +0200 Subject: [PATCH] feat(select/distinctOn): add RawBuilder support (#239) closes #230 --- src/operation-node/select-query-node.ts | 5 ++-- src/parser/expression-parser.ts | 9 +++++--- src/parser/reference-parser.ts | 4 ++-- src/query-builder/select-query-builder.ts | 18 +++++++++------ src/query-compiler/default-query-compiler.ts | 5 +++- test/node/src/select.test.ts | 24 ++++++++++++++++++++ 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/operation-node/select-query-node.ts b/src/operation-node/select-query-node.ts index 7431a053a..cbc1c9b19 100644 --- a/src/operation-node/select-query-node.ts +++ b/src/operation-node/select-query-node.ts @@ -15,12 +15,13 @@ import { WithNode } from './with-node.js' import { SelectModifierNode } from './select-modifier-node.js' import { ExplainNode } from './explain-node.js' import { SetOperationNode } from './set-operation-node.js' +import { SimpleReferenceExpressionNode } from './simple-reference-expression-node.js' export interface SelectQueryNode extends OperationNode { readonly kind: 'SelectQueryNode' readonly from: FromNode readonly selections?: ReadonlyArray - readonly distinctOnSelections?: ReadonlyArray + readonly distinctOnSelections?: ReadonlyArray readonly joins?: ReadonlyArray readonly groupBy?: GroupByNode readonly orderBy?: OrderByNode @@ -68,7 +69,7 @@ export const SelectQueryNode = freeze({ cloneWithDistinctOnSelections( select: SelectQueryNode, - selections: ReadonlyArray + selections: ReadonlyArray ): SelectQueryNode { return freeze({ ...select, diff --git a/src/parser/expression-parser.ts b/src/parser/expression-parser.ts index 89d4453aa..5eb794b1e 100644 --- a/src/parser/expression-parser.ts +++ b/src/parser/expression-parser.ts @@ -6,6 +6,7 @@ import { import { AliasNode } from '../operation-node/alias-node.js' import { isOperationNodeSource } from '../operation-node/operation-node-source.js' import { OperationNode } from '../operation-node/operation-node.js' +import { SimpleReferenceExpressionNode } from '../operation-node/simple-reference-expression-node.js' import { ExpressionBuilder } from '../query-builder/expression-builder.js' import { SelectQueryBuilder } from '../query-builder/select-query-builder.js' import { isFunction } from '../util/object-utils.js' @@ -29,11 +30,13 @@ export type AliasedExpressionOrFactory = export function parseExpression( exp: ExpressionOrFactory -): OperationNode { +): SimpleReferenceExpressionNode { if (isOperationNodeSource(exp)) { - return exp.toOperationNode() + return exp.toOperationNode() as SimpleReferenceExpressionNode } else if (isFunction(exp)) { - return exp(createExpressionBuilder()).toOperationNode() + return exp( + createExpressionBuilder() + ).toOperationNode() as SimpleReferenceExpressionNode } throw new Error(`invalid expression: ${JSON.stringify(exp)}`) diff --git a/src/parser/reference-parser.ts b/src/parser/reference-parser.ts index 3d33abe03..53f601687 100644 --- a/src/parser/reference-parser.ts +++ b/src/parser/reference-parser.ts @@ -88,7 +88,7 @@ export function parseSimpleReferenceExpression( export function parseReferenceExpressionOrList( arg: ReferenceExpressionOrList -): OperationNode[] { +): SimpleReferenceExpressionNode[] { if (isReadonlyArray(arg)) { return arg.map((it) => parseReferenceExpression(it)) } else { @@ -98,7 +98,7 @@ export function parseReferenceExpressionOrList( export function parseReferenceExpression( exp: ReferenceExpression -): OperationNode { +): SimpleReferenceExpressionNode { if (isExpressionOrFactory(exp)) { return parseExpression(exp) } diff --git a/src/query-builder/select-query-builder.ts b/src/query-builder/select-query-builder.ts index d8707148f..979fbdf59 100644 --- a/src/query-builder/select-query-builder.ts +++ b/src/query-builder/select-query-builder.ts @@ -15,7 +15,11 @@ import { SelectAllQueryBuilder, SelectExpressionOrList, } from '../parser/select-parser.js' -import { ReferenceExpression } from '../parser/reference-parser.js' +import { + parseReferenceExpressionOrList, + ReferenceExpression, + ReferenceExpressionOrList, +} from '../parser/reference-parser.js' import { SelectQueryNode } from '../operation-node/select-query-node.js' import { QueryNode } from '../operation-node/query-node.js' import { MergePartial, Nullable, SingleResultType } from '../util/type-utils.js' @@ -515,20 +519,20 @@ export class SelectQueryBuilder * where "pet"."name" = $1 * ``` */ - distinctOn>( - selections: ReadonlyArray + distinctOn>( + selections: ReadonlyArray ): SelectQueryBuilder - distinctOn>( - selection: SE + distinctOn>( + selection: RE ): SelectQueryBuilder - distinctOn(selection: SelectExpressionOrList): any { + distinctOn(selection: ReferenceExpressionOrList): any { return new SelectQueryBuilder({ ...this.#props, queryNode: SelectQueryNode.cloneWithDistinctOnSelections( this.#props.queryNode, - parseSelectExpressionOrList(selection) + parseReferenceExpressionOrList(selection) ), }) } diff --git a/src/query-compiler/default-query-compiler.ts b/src/query-compiler/default-query-compiler.ts index 127d62a31..7eb18fe56 100644 --- a/src/query-compiler/default-query-compiler.ts +++ b/src/query-compiler/default-query-compiler.ts @@ -93,6 +93,7 @@ import { PartitionByItemNode } from '../operation-node/partition-by-item-node.js import { SetOperationNode } from '../operation-node/set-operation-node.js' import { BinaryOperationNode } from '../operation-node/binary-operation-node.js' import { UnaryOperationNode } from '../operation-node/unary-operation-node.js' +import { SimpleReferenceExpressionNode } from '../operation-node/simple-reference-expression-node.js' export class DefaultQueryCompiler extends OperationNodeVisitor @@ -225,7 +226,9 @@ export class DefaultQueryCompiler this.visitNode(node.column) } - protected compileDistinctOn(selections: ReadonlyArray): void { + protected compileDistinctOn( + selections: ReadonlyArray + ): void { this.append('distinct on (') this.compileList(selections) this.append(')') diff --git a/test/node/src/select.test.ts b/test/node/src/select.test.ts index 41e416b14..44b9b5df9 100644 --- a/test/node/src/select.test.ts +++ b/test/node/src/select.test.ts @@ -534,6 +534,30 @@ for (const dialect of BUILT_IN_DIALECTS) { ]) }) + it('should select with distict on that uses a RawBuilder expression', async () => { + const query = ctx.db + .selectFrom('person') + .select(['first_name', 'last_name']) + .distinctOn(sql`gender::text`) + + testSql(query, dialect, { + postgres: { + sql: 'select distinct on (gender::text) "first_name", "last_name" from "person"', + parameters: [], + }, + mysql: NOT_SUPPORTED, + sqlite: NOT_SUPPORTED, + }) + + const persons = await query.execute() + + expect(persons).to.have.length(2) + expect(persons).to.eql([ + { first_name: 'Jennifer', last_name: 'Aniston' }, + { first_name: 'Arnold', last_name: 'Schwarzenegger' }, + ]) + }) + for (const [methods, sql] of [ [['forUpdate'], 'for update'], [['forShare'], 'for share'],