Skip to content

Commit

Permalink
aggregate with distinct
Browse files Browse the repository at this point in the history
  • Loading branch information
LinboLen committed Jul 12, 2024
1 parent 097acff commit 7673674
Show file tree
Hide file tree
Showing 18 changed files with 117 additions and 61 deletions.
4 changes: 2 additions & 2 deletions .run/All Tests.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<working-dir value="$PROJECT_DIR$/" />
<jest-options value="--config libs/fedaco/jest.config.ts" />
<envs>
<env name="DB_USER" value="admin" />
<env name="DB_PASSWORD" value="admin" />
<env name="DB_PASSWORD" value="root" />
<env name="DB_USER" value="123456" />
</envs>
<scope-kind value="ALL" />
<method v="2" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="database query builder test.test aggregate functions with distinct" type="JavaScriptTestRunnerJest" nameIsGenerated="true">
<node-interpreter value="project" />
<jest-package value="$PROJECT_DIR$/node_modules/jest" />
<working-dir value="$PROJECT_DIR$" />
<jest-options value="--config libs/fedaco/jest.config.ts" />
<envs />
<scope-kind value="TEST" />
<test-file value="$PROJECT_DIR$/libs/fedaco/test/query-builder.spec.ts" />
<test-names>
<test-name value="database query builder test" />
<test-name value="test aggregate functions with distinct" />
</test-names>
<method v="2" />
</configuration>
</component>
2 changes: 1 addition & 1 deletion libs/fedaco/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export * from './query/ast/assignment-set-clause';
export * from './query/ast/query-expression';
export * from './query/ast/fragment/order/reject-order-element-expression';
export * from './query/ast/fragment/union-fragment';
export * from './query/ast/fragment/aggregate-fragment';
export * from './query/ast/fragment/aggregate-function-call-fragment';
export * from './query/ast/fragment/nested-expression';
export * from './query/ast/fragment/raw-fragment';
export * from './query/ast/fragment/expression/nested-predicate-expression';
Expand Down
4 changes: 2 additions & 2 deletions libs/fedaco/src/query-builder/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import type { ColumnReferenceExpression } from '../query/ast/column-reference-expression';
import type { RawBindingExpression } from '../query/ast/expression/raw-binding-expression';
import type { RawExpression } from '../query/ast/expression/raw-expression';
import type { AggregateFragment } from '../query/ast/fragment/aggregate-fragment';
import type { AggregateFunctionCallFragment } from '../query/ast/fragment/aggregate-function-call-fragment';
import type { JoinFragment } from '../query/ast/fragment/join-fragment';
import type { UnionFragment } from '../query/ast/fragment/union-fragment';
import type { FromTable } from '../query/ast/from-table';
Expand Down Expand Up @@ -74,7 +74,7 @@ export abstract class Builder extends mixinJoin(
'insert' : []
};
/*An aggregate function and column to be run.*/
_aggregate: AggregateFragment;
_aggregate: AggregateFunctionCallFragment;
/*The columns that should be returned.*/
_columns: Array<ColumnReferenceExpression | RawBindingExpression | RawExpression> = [];
/*Indicates if the query returns distinct results.
Expand Down
4 changes: 3 additions & 1 deletion libs/fedaco/src/query-builder/grammar.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export interface GrammarInterface<T extends Builder = Builder> {

compilePredicateFuncName(funcName: string): string;

distinct(builder: T, distinct: boolean | any[]): string;
distinct(distinct: boolean | any[]): string;

distinctInAggregateFunctionCall(distinct: boolean | any[]): string;

getOperators(): string[];

Expand Down
12 changes: 4 additions & 8 deletions libs/fedaco/src/query-builder/grammar/postgres-query-grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,10 @@ export class PostgresQueryGrammar extends QueryGrammar implements GrammarInterfa
}


distinct(query: QueryBuilder, distinct: boolean | any[]): string {
if (! isBlank(query._aggregate)) {
return '';
}

if(isArray(distinct)) {
return `DISTINCT ON (${this.columnize(distinct)})`
}else if (distinct === true) {
distinct(distinct: boolean | any[]): string {
if (isArray(distinct)) {
return `DISTINCT ON (${this.columnize(distinct)})`;
} else if (distinct === true) {
return 'DISTINCT';
} else {
return '';
Expand Down
31 changes: 18 additions & 13 deletions libs/fedaco/src/query-builder/grammar/query-grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DeleteSpecification } from '../../query/ast/delete-specification';
import { ConditionExpression } from '../../query/ast/expression/condition-expression';
import { FunctionCallExpression } from '../../query/ast/expression/function-call-expression';
import { ParenthesizedExpression } from '../../query/ast/expression/parenthesized-expression';
import { AggregateFunctionCallFragment } from '../../query/ast/fragment/aggregate-function-call-fragment';
import type { NestedExpression } from '../../query/ast/fragment/nested-expression';
import { FromClause } from '../../query/ast/from-clause';
import { FromTable } from '../../query/ast/from-table';
Expand All @@ -37,7 +38,7 @@ import { WhereClause } from '../../query/ast/where-clause';
import { SqlParser } from '../../query/parser/sql-parser';
import type { SqlNode } from '../../query/sql-node';
import type { SqlVisitor } from '../../query/sql-visitor';
import { bindingVariable, createIdentifier, raw } from '../ast-factory';
import { bindingVariable, createColumnReferenceExpression, createIdentifier, raw } from '../ast-factory';
import type { Builder } from '../builder';
import type { GrammarInterface } from '../grammar.interface';
import type { JoinClauseBuilder, QueryBuilder } from '../query-builder';
Expand Down Expand Up @@ -327,14 +328,18 @@ export abstract class QueryGrammar extends BaseGrammar implements GrammarInterfa
return funcName;
}

distinct(query: QueryBuilder, distinct: boolean | any[]): string {
// If the query is actually performing an aggregating select, we will let that
// compiler handle the building of the select clauses, as it will need some
// more syntax that is best handled by that function to keep things neat.
if (!isBlank(query._aggregate)) {
distinct(distinct: boolean | any[]): string {
if (distinct !== false) {
return 'DISTINCT';
} else {
return '';
}
if (distinct !== false) {
}

distinctInAggregateFunctionCall(distinct: boolean | any[]): string {
if (isArray(distinct)) {
return `DISTINCT ${this.columnize(distinct)}`;
} else if (distinct !== false) {
return 'DISTINCT';
} else {
return '';
Expand Down Expand Up @@ -417,17 +422,17 @@ export abstract class QueryGrammar extends BaseGrammar implements GrammarInterfa
}

if (builder._aggregate && builder._unions.length === 0) {
selectClause = new SelectClause(
builder._aggregate.distinct = isArray(builder._distinct) ?
builder._distinct.map(it => createColumnReferenceExpression(it)) :
builder._distinct;
selectClause = new SelectClause(
[
new SelectScalarExpression(
new FunctionCallExpression(
builder._aggregate.aggregateFunctionName,
builder._aggregate.aggregateColumns
),
builder._aggregate,
createIdentifier('aggregate')
)
],
builder._distinct
false
);
} else {
selectClause = new SelectClause(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ export class SqliteQueryGrammar extends QueryGrammar implements GrammarInterface
return ast.accept(visitor);
}

distinct(query: QueryBuilder, distinct: boolean | any[]): string {
if (! isBlank(query._aggregate)) {
return '';
}
distinct(distinct: boolean | any[]): string {
if (distinct !== false) {
return 'DISTINCT';
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@ export class SqlserverQueryGrammar extends QueryGrammar implements GrammarInterf
return snakeCase(funcName);
}

distinct(query: QueryBuilder, distinct: boolean | any[]): string {
if (! isBlank(query._aggregate)) {
return '';
}
distinct(distinct: boolean | any[]): string {
if (distinct !== false) {
return 'DISTINCT';
} else {
Expand Down
4 changes: 2 additions & 2 deletions libs/fedaco/src/query-builder/mixins/aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { isObject } from '@gradii/nanofn';
import type { Constructor } from '../../helper/constructor';
import { ColumnReferenceExpression } from '../../query/ast/column-reference-expression';
import { AggregateFragment } from '../../query/ast/fragment/aggregate-fragment';
import { AggregateFunctionCallFragment } from '../../query/ast/fragment/aggregate-function-call-fragment';
import { PathExpression } from '../../query/ast/path-expression';
import { SqlParser } from '../../query/parser/sql-parser';
import { createColumnReferenceExpression, createIdentifier, rawSqlBindings } from '../ast-factory';
Expand Down Expand Up @@ -42,7 +42,7 @@ export function mixinAggregate<T extends Constructor<any>>(base: T): QueryBuilde
/*Set the aggregate property without running the query.*/
_setAggregate(this: QueryBuilder & _Self, func: string,
columns: Array<string | ColumnReferenceExpression>) {
this._aggregate = new AggregateFragment(
this._aggregate = new AggregateFunctionCallFragment(
createIdentifier(func),
columns.map(it => createColumnReferenceExpression(it))
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class QueryBuilderResolverVisitor implements SqlVisitor {
throw new Error('Method not implemented.');
}

visitAggregateFragment(node: SqlNode): string {
visitAggregateFunctionCallFragment(node: SqlNode): string {
throw new Error('Method not implemented.');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Use of this source code is governed by an MIT-style license
*/

import { isArray } from '@gradii/nanofn';
import type { DeleteSpecification } from '../../query/ast/delete-specification';
import type { FunctionCallExpression } from '../../query/ast/expression/function-call-expression';
import type { UpdateSpecification } from '../../query/ast/update-specification';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

import { isString } from '@gradii/nanofn';
import type { ColumnReferenceExpression } from '../../query/ast/column-reference-expression';
import type {
ComparisonPredicateExpression
} from '../../query/ast/expression/comparison-predicate-expression';
import type { ComparisonPredicateExpression } from '../../query/ast/expression/comparison-predicate-expression';
import type { FunctionCallExpression } from '../../query/ast/expression/function-call-expression';
import type { JsonPathExpression } from '../../query/ast/json-path-expression';
import type { LockClause } from '../../query/ast/lock-clause';
Expand Down
27 changes: 17 additions & 10 deletions libs/fedaco/src/query-builder/visitor/query-builder-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import type { ParenthesizedExpression } from '../../query/ast/expression/parenth
import type { RawBindingExpression } from '../../query/ast/expression/raw-binding-expression';
import { RawExpression } from '../../query/ast/expression/raw-expression';
import type { StringLiteralExpression } from '../../query/ast/expression/string-literal-expression';
import type { AggregateFragment } from '../../query/ast/fragment/aggregate-fragment';
import type { AggregateFunctionCallFragment } from '../../query/ast/fragment/aggregate-function-call-fragment';
import type {
NestedPredicateExpression
} from '../../query/ast/fragment/expression/nested-predicate-expression';
Expand Down Expand Up @@ -101,13 +101,20 @@ export class QueryBuilderVisitor implements SqlVisitor {
return 'hello';
}

visitAggregateFragment(node: AggregateFragment): string {
throw new Error('not implement yet');
// todo
// return this._grammar.compileAggregateFragment(
// node.aggregateFunctionName,
// node.aggregateColumns
// );
visitAggregateFunctionCallFragment(node: AggregateFunctionCallFragment): string {
let funcName = node.aggregateFunctionName.accept(this);
funcName = this._grammar.compilePredicateFuncName(funcName);
let columns;
if (isArray(node.distinct)) {
const list = node.distinct.map((it: SqlNode) => it.accept(this));
columns = this._grammar.distinctInAggregateFunctionCall(list);
} else {
columns = node.aggregateColumns.map(it => it.accept(this)).join(', ');
if (node.distinct === true && columns !== '*') {
columns = 'DISTINCT ' + columns;
}
}
return `${funcName}(${columns})`;
}

visitAsExpression(node: AsExpression): string {
Expand Down Expand Up @@ -586,9 +593,9 @@ export class QueryBuilderVisitor implements SqlVisitor {
return expression.accept(this);
});
return `SELECT${node.distinct ? ` ${
this._grammar.distinct(this._queryBuilder, node.distinct)} ` : ' '}${selectExpressions.join(', ')}`;
this._grammar.distinct(node.distinct)} ` : ' '}${selectExpressions.join(', ')}`;
} else {
return `SELECT${node.distinct ? ` ${this._grammar.distinct(this._queryBuilder, node.distinct)} ` : ' '}*`;
return `SELECT${node.distinct ? ` ${this._grammar.distinct(node.distinct)} ` : ' '}*`;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,10 @@ export class SqlserverQueryBuilderVisitor extends QueryBuilderVisitor {
const selectExpressions = node.selectExpressions.map(expression => {
return expression.accept(this);
});
return `SELECT${node.distinct ? ` ${this._grammar.distinct(
this._queryBuilder,
node.distinct)} ` : ' '}${topSql}${selectExpressions.join(
return `SELECT${node.distinct ? ` ${this._grammar.distinct(node.distinct)} ` : ' '}${topSql}${selectExpressions.join(
', ')}`;
} else {
return `SELECT${node.distinct ? ` ${this._grammar.distinct(
this._queryBuilder,
node.distinct)} ` : ' '}${topSql}*`;
return `SELECT${node.distinct ? ` ${this._grammar.distinct(node.distinct)} ` : ' '}${topSql}*`;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import type { SqlVisitor } from '../../sql-visitor';
import type { Identifier } from '../identifier';


export class AggregateFragment extends SqlNode {
export class AggregateFunctionCallFragment extends SqlNode {
constructor(
public aggregateFunctionName: Identifier,
public aggregateColumns: SqlNode[]
public aggregateColumns: SqlNode[],
public distinct: SqlNode[] | boolean = false
) {
super();
}

public accept(visitor: SqlVisitor) {
return visitor.visitAggregateFragment(this);
return visitor.visitAggregateFunctionCallFragment(this);
}
}
2 changes: 1 addition & 1 deletion libs/fedaco/src/query/sql-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface SqlVisitor {
// visit(node: SqlNode) {
// }

visitAggregateFragment(node: SqlNode): string;
visitAggregateFunctionCallFragment(node: SqlNode): string;

visitAsExpression(node: SqlNode): string;

Expand Down
40 changes: 40 additions & 0 deletions libs/fedaco/test/query-builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2415,6 +2415,46 @@ describe('database query builder test', () => {
expect(results).toBe(1);
});

it('test aggregate functions with distinct', async () => {
let spySelect, spyProcessSelect, results
builder = getBuilder();
spySelect = jest.spyOn(builder._connection, 'select').mockReturnValue(Promise.resolve([
{
'aggregate': 1
}
]));

spyProcessSelect = jest.spyOn(builder._processor, 'processSelect').mockImplementation(
(builder, results) => {
return results;
});

results = await builder.distinct('foo').from('users').count();
expect(spySelect).toBeCalledWith('SELECT count(DISTINCT `foo`) AS aggregate FROM `users`', [], true);
expect(results).toBe(1);

spySelect = jest.spyOn(builder._connection, 'select').mockReturnValue(Promise.resolve([
{
'aggregate': 2
}
]));

spyProcessSelect = jest.spyOn(builder._processor, 'processSelect').mockImplementation(
(builder, results) => {
return results;
});

results = await builder.distinct().from('users').count('*');
expect(spySelect).toBeCalledWith('SELECT count(*) AS aggregate FROM `users`', [], true);
expect(results).toBe(2);

results = await builder.distinct().from('users').count('bar');
expect(spySelect).toBeCalledWith('SELECT count(DISTINCT `bar`) AS aggregate FROM `users`', [], true);

});



/*
it('test sql server exists', () => {
builder = getSqlServerBuilder();
Expand Down

0 comments on commit 7673674

Please sign in to comment.