diff --git a/.run/All Tests.run.xml b/.run/All Tests.run.xml
index 7a1550c..dabda1f 100644
--- a/.run/All Tests.run.xml
+++ b/.run/All Tests.run.xml
@@ -5,8 +5,8 @@
-
-
+
+
diff --git a/.run/database query builder test.test aggregate functions with distinct.run.xml b/.run/database query builder test.test aggregate functions with distinct.run.xml
new file mode 100644
index 0000000..23999f5
--- /dev/null
+++ b/.run/database query builder test.test aggregate functions with distinct.run.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/fedaco/src/index.ts b/libs/fedaco/src/index.ts
index b3f29c8..296f33c 100644
--- a/libs/fedaco/src/index.ts
+++ b/libs/fedaco/src/index.ts
@@ -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';
diff --git a/libs/fedaco/src/query-builder/builder.ts b/libs/fedaco/src/query-builder/builder.ts
index 93dcefc..748345a 100644
--- a/libs/fedaco/src/query-builder/builder.ts
+++ b/libs/fedaco/src/query-builder/builder.ts
@@ -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';
@@ -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 = [];
/*Indicates if the query returns distinct results.
diff --git a/libs/fedaco/src/query-builder/grammar.interface.ts b/libs/fedaco/src/query-builder/grammar.interface.ts
index 2716676..99ca930 100644
--- a/libs/fedaco/src/query-builder/grammar.interface.ts
+++ b/libs/fedaco/src/query-builder/grammar.interface.ts
@@ -38,7 +38,9 @@ export interface GrammarInterface {
compilePredicateFuncName(funcName: string): string;
- distinct(builder: T, distinct: boolean | any[]): string;
+ distinct(distinct: boolean | any[]): string;
+
+ distinctInAggregateFunctionCall(distinct: boolean | any[]): string;
getOperators(): string[];
diff --git a/libs/fedaco/src/query-builder/grammar/postgres-query-grammar.ts b/libs/fedaco/src/query-builder/grammar/postgres-query-grammar.ts
index f13bee1..abf6d42 100644
--- a/libs/fedaco/src/query-builder/grammar/postgres-query-grammar.ts
+++ b/libs/fedaco/src/query-builder/grammar/postgres-query-grammar.ts
@@ -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 '';
diff --git a/libs/fedaco/src/query-builder/grammar/query-grammar.ts b/libs/fedaco/src/query-builder/grammar/query-grammar.ts
index 97227ab..464037d 100644
--- a/libs/fedaco/src/query-builder/grammar/query-grammar.ts
+++ b/libs/fedaco/src/query-builder/grammar/query-grammar.ts
@@ -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';
@@ -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';
@@ -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 '';
@@ -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(
diff --git a/libs/fedaco/src/query-builder/grammar/sqlite-query-grammar.ts b/libs/fedaco/src/query-builder/grammar/sqlite-query-grammar.ts
index 81af477..7f196b5 100644
--- a/libs/fedaco/src/query-builder/grammar/sqlite-query-grammar.ts
+++ b/libs/fedaco/src/query-builder/grammar/sqlite-query-grammar.ts
@@ -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 {
diff --git a/libs/fedaco/src/query-builder/grammar/sqlserver-query-grammar.ts b/libs/fedaco/src/query-builder/grammar/sqlserver-query-grammar.ts
index 6b3c446..6e9c842 100644
--- a/libs/fedaco/src/query-builder/grammar/sqlserver-query-grammar.ts
+++ b/libs/fedaco/src/query-builder/grammar/sqlserver-query-grammar.ts
@@ -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 {
diff --git a/libs/fedaco/src/query-builder/mixins/aggregate.ts b/libs/fedaco/src/query-builder/mixins/aggregate.ts
index 11897d4..dec4158 100644
--- a/libs/fedaco/src/query-builder/mixins/aggregate.ts
+++ b/libs/fedaco/src/query-builder/mixins/aggregate.ts
@@ -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';
@@ -42,7 +42,7 @@ export function mixinAggregate>(base: T): QueryBuilde
/*Set the aggregate property without running the query.*/
_setAggregate(this: QueryBuilder & _Self, func: string,
columns: Array) {
- this._aggregate = new AggregateFragment(
+ this._aggregate = new AggregateFunctionCallFragment(
createIdentifier(func),
columns.map(it => createColumnReferenceExpression(it))
);
diff --git a/libs/fedaco/src/query-builder/query-builder-resolver-visitor.ts b/libs/fedaco/src/query-builder/query-builder-resolver-visitor.ts
index 8834474..bf62b3f 100644
--- a/libs/fedaco/src/query-builder/query-builder-resolver-visitor.ts
+++ b/libs/fedaco/src/query-builder/query-builder-resolver-visitor.ts
@@ -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.');
}
diff --git a/libs/fedaco/src/query-builder/visitor/mysql-query-builder-visitor.ts b/libs/fedaco/src/query-builder/visitor/mysql-query-builder-visitor.ts
index 86f155c..76141a9 100644
--- a/libs/fedaco/src/query-builder/visitor/mysql-query-builder-visitor.ts
+++ b/libs/fedaco/src/query-builder/visitor/mysql-query-builder-visitor.ts
@@ -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';
diff --git a/libs/fedaco/src/query-builder/visitor/postgres-query-builder-visitor.ts b/libs/fedaco/src/query-builder/visitor/postgres-query-builder-visitor.ts
index e1889b0..db44fa5 100644
--- a/libs/fedaco/src/query-builder/visitor/postgres-query-builder-visitor.ts
+++ b/libs/fedaco/src/query-builder/visitor/postgres-query-builder-visitor.ts
@@ -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';
diff --git a/libs/fedaco/src/query-builder/visitor/query-builder-visitor.ts b/libs/fedaco/src/query-builder/visitor/query-builder-visitor.ts
index 5a58c34..6645671 100644
--- a/libs/fedaco/src/query-builder/visitor/query-builder-visitor.ts
+++ b/libs/fedaco/src/query-builder/visitor/query-builder-visitor.ts
@@ -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';
@@ -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 {
@@ -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)} ` : ' '}*`;
}
}
diff --git a/libs/fedaco/src/query-builder/visitor/sqlserver-query-builder-visitor.ts b/libs/fedaco/src/query-builder/visitor/sqlserver-query-builder-visitor.ts
index 6ce493b..bd600b0 100644
--- a/libs/fedaco/src/query-builder/visitor/sqlserver-query-builder-visitor.ts
+++ b/libs/fedaco/src/query-builder/visitor/sqlserver-query-builder-visitor.ts
@@ -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}*`;
}
}
diff --git a/libs/fedaco/src/query/ast/fragment/aggregate-fragment.ts b/libs/fedaco/src/query/ast/fragment/aggregate-function-call-fragment.ts
similarity index 63%
rename from libs/fedaco/src/query/ast/fragment/aggregate-fragment.ts
rename to libs/fedaco/src/query/ast/fragment/aggregate-function-call-fragment.ts
index 64c75d2..f820740 100644
--- a/libs/fedaco/src/query/ast/fragment/aggregate-fragment.ts
+++ b/libs/fedaco/src/query/ast/fragment/aggregate-function-call-fragment.ts
@@ -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);
}
}
diff --git a/libs/fedaco/src/query/sql-visitor.ts b/libs/fedaco/src/query/sql-visitor.ts
index d60a9ff..8484dfb 100644
--- a/libs/fedaco/src/query/sql-visitor.ts
+++ b/libs/fedaco/src/query/sql-visitor.ts
@@ -13,7 +13,7 @@ export interface SqlVisitor {
// visit(node: SqlNode) {
// }
- visitAggregateFragment(node: SqlNode): string;
+ visitAggregateFunctionCallFragment(node: SqlNode): string;
visitAsExpression(node: SqlNode): string;
diff --git a/libs/fedaco/test/query-builder.spec.ts b/libs/fedaco/test/query-builder.spec.ts
index 2c3d8f0..b493cc3 100644
--- a/libs/fedaco/test/query-builder.spec.ts
+++ b/libs/fedaco/test/query-builder.spec.ts
@@ -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();