Skip to content

Commit 6ce1929

Browse files
committed
feat: add aggregate helpers
1 parent fc51883 commit 6ce1929

File tree

20 files changed

+781
-67
lines changed

20 files changed

+781
-67
lines changed

index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ export * from './src/define_config.js'
1111
export { Connection } from './src/connection/connection.js'
1212
export { QueryClient } from './src/database_clients/query_client.js'
1313
export { DatabaseClient } from './src/database_clients/abstract_client.js'
14-
export { SelectQueryBuilder } from './src/query_builders/select_query_builder.js'

package.json

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"!build/tests"
1313
],
1414
"exports": {
15-
".": "./build/index.js"
15+
".": "./build/index.js",
16+
"./types": "./build/src/types/main.js",
17+
"./query_builder": "./build/src/query_builders/main.js"
1618
},
1719
"scripts": {
1820
"pretest": "npm run lint && rm -rf coverage/tmp && mkdir -p coverage/tmp",
@@ -48,6 +50,7 @@
4850
"@japa/file-system": "^2.3.2",
4951
"@japa/runner": "^4.2.0",
5052
"@japa/snapshot": "^2.0.8",
53+
"@libsql/sqlite3": "^0.3.1",
5154
"@release-it/conventional-changelog": "^10.0.0",
5255
"@swc/core": "1.10.7",
5356
"@types/better-sqlite3": "^7.6.12",
@@ -78,7 +81,37 @@
7881
"tarn": "3.0.2"
7982
},
8083
"peerDependencies": {
81-
"@adonisjs/core": "^6.17.2"
84+
"@adonisjs/core": "^6.17.2",
85+
"tedious": "^18.6.1",
86+
"better-sqlite3": "^11.9.1",
87+
"mysql2": "^3.14.0",
88+
"pg": "^8.14.1",
89+
"@types/pg": "^8.11.11",
90+
"@types/better-sqlite3": "^7.6.12",
91+
"@libsql/sqlite3": "^0.3.1"
92+
},
93+
"peerDependenciesMeta": {
94+
"tedious": {
95+
"optional": true
96+
},
97+
"better-sqlite3": {
98+
"optional": true
99+
},
100+
"mysql2": {
101+
"optional": true
102+
},
103+
"pg": {
104+
"optional": true
105+
},
106+
"@types/pg": {
107+
"optional": true
108+
},
109+
"@types/better-sqlite3": {
110+
"optional": true
111+
},
112+
"@libsql/sqlite3": {
113+
"optional": true
114+
}
82115
},
83116
"homepage": "https://github.com/adonisjs/lucid#readme",
84117
"repository": {

src/connection/connection.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,18 @@ import { RuntimeException } from '@poppinss/exception'
1414

1515
import { debug } from '../debug.js'
1616
import * as errors from '../errors.js'
17+
import { NOOP_EMITTER } from '../helpers.js'
1718
import { createKnexLogger } from './logger.js'
1819
import { dialects } from '../dialects/main.js'
1920
import { QueryClient } from '../database_clients/query_client.js'
21+
2022
import type { DialectContract } from '../types/dialect.js'
2123
import type {
2224
ConnectionConfig,
2325
ConnectionLogger,
2426
DatabaseEmitter,
2527
SupportedDialectNames,
2628
} from '../types/connection.js'
27-
import { NOOP_EMITTER } from '../helpers.js'
2829

2930
/**
3031
* A connection represents a database connection created by instantiating
@@ -134,6 +135,34 @@ export class Connection {
134135
this.dialect = new dialects[config.dialectName](this)
135136
}
136137

138+
/**
139+
* Patches the knex client's "acquireRawConnection" method to add
140+
* support for BigInts when using the better-sqlite3 client and
141+
* "defaultSafeIntegers" is enabled.
142+
*/
143+
#patchSqliteConnectionToSupportBigInts(client: Knex) {
144+
/**
145+
* Return early when defaultSafeIntegers are disabled
146+
*/
147+
if (
148+
this.config.clientName !== 'better-sqlite3' ||
149+
'defaultSafeIntegers' in this.config === false ||
150+
!this.config.defaultSafeIntegers
151+
) {
152+
return
153+
}
154+
155+
const originalAcquireRawConnection = client.client.acquireRawConnection.bind(client.client)
156+
client.client.acquireRawConnection = function () {
157+
return originalAcquireRawConnection(...arguments).then((db: any) => {
158+
if ('defaultSafeIntegers' in db) {
159+
db.defaultSafeIntegers(true)
160+
}
161+
return db
162+
})
163+
}
164+
}
165+
137166
/**
138167
* Creates the fresh state for the connection
139168
*/
@@ -211,6 +240,7 @@ export class Connection {
211240

212241
debug('%s: creating write connection %O', this.identifier, writeConfig)
213242
this.client = knex.knex({ log: this.#knexLogger, ...writeConfig })
243+
this.#patchSqliteConnectionToSupportBigInts(this.client)
214244
patchKnex(this.client, (originalConfig) => originalConfig.connection as Knex.ConnectionConfig)
215245
}
216246

@@ -250,6 +280,7 @@ export class Connection {
250280

251281
debug('%s: creating read connection %O', this.identifier, initialConfig)
252282
this.readClient = knex({ log: this.#knexLogger, ...initialConfig })
283+
this.#patchSqliteConnectionToSupportBigInts(this.readClient)
253284

254285
/**
255286
* Creating the final config array of read replicas.
@@ -387,7 +418,9 @@ export class Connection {
387418
* @default: 'dual'
388419
*/
389420
getQueryClient(mode: 'dual' | 'write' | 'read' = 'dual') {
390-
return new QueryClient(this, mode, this.#emitter)
421+
const client = new QueryClient(this, mode, this.#emitter)
422+
client.debug = this.config.debug ?? false
423+
return client
391424
}
392425

393426
/**

src/database_clients/abstract_client.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { NOOP_EMITTER } from '../helpers.js'
1515
import { DatabaseEmitter } from '../types/connection.js'
1616
import type { DialectContract } from '../types/dialect.js'
1717
import type { Connection } from '../connection/connection.js'
18-
import { beginTransactionTracer, queryTracer } from '../tracing_channels.js'
18+
import { beginTransaction, dbQuery } from '../tracing_channels.js'
1919
import { SelectQueryBuilder } from '../query_builders/select_query_builder.js'
2020
import { InsertQueryBuilder } from '../query_builders/insert_query_builder.js'
2121
import { DeleteQueryBuilder } from '../query_builders/delete_query_builder.js'
@@ -33,6 +33,7 @@ import type {
3333
FromExpressionArguments,
3434
} from '../types/query.js'
3535
import { TransactionClient } from './transaction_client.js'
36+
import { FunctionExpressionBuilder } from '../expression_builders/function_expression_builder.js'
3637

3738
/**
3839
* DatabaseClient can be used to create different query builders and execute
@@ -77,6 +78,11 @@ export abstract class DatabaseClient implements DatabaseClientContract {
7778
*/
7879
isTransaction: boolean = false
7980

81+
/**
82+
* Helpers functions to express parts of a SQL query
83+
*/
84+
fn = new FunctionExpressionBuilder()
85+
8086
/**
8187
* The name of the connection from which the client
8288
* was originated.
@@ -140,7 +146,7 @@ export abstract class DatabaseClient implements DatabaseClientContract {
140146
const tracingData = { ...this.getContext(), isSavePoint: this.isTransaction }
141147
const event = this.createEvent('db:transaction:begin', this.debug)
142148

143-
return beginTransactionTracer.tracePromise(async () => {
149+
return beginTransaction.tracePromise(async () => {
144150
const trx = await this.getWriteClient().transaction(options)
145151
debug('begin transaction')
146152
event.emit(tracingData)
@@ -528,7 +534,7 @@ export abstract class DatabaseClient implements DatabaseClientContract {
528534
const tracingData = { ...query.getContext() } as DbQueryEventData
529535
const event = this.createEvent('db:query', query.debugging)
530536

531-
const result = (await queryTracer.tracePromise(async () => {
537+
const result = (await dbQuery.tracePromise(async () => {
532538
/**
533539
* This logic will mess up if one query instance is used to execute
534540
* multiple times, which in itself is incorrect usage of the

src/database_clients/transaction_client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { DatabaseClient } from './abstract_client.js'
1313
import type { IsolationLevels } from '../types/query.js'
1414
import type { Connection } from '../connection/connection.js'
1515
import type { DatabaseEmitter } from '../types/connection.js'
16-
import { commitTransactionTracer, rollbackTransactionTracer } from '../tracing_channels.js'
16+
import { commitTransaction, rollbackTransaction } from '../tracing_channels.js'
1717
import { debug } from '../debug.js'
1818

1919
export class TransactionClient extends DatabaseClient {
@@ -81,7 +81,7 @@ export class TransactionClient extends DatabaseClient {
8181
const tracingData = { ...this.getContext() }
8282
const event = this.createEvent('db:transaction:commit', this.debug)
8383

84-
return commitTransactionTracer.tracePromise(async () => {
84+
return commitTransaction.tracePromise(async () => {
8585
debug('committing transaction')
8686
await this.knexTransaction.commit()
8787
tracingData.duration = process.hrtime(this.#startedAt)
@@ -104,7 +104,7 @@ export class TransactionClient extends DatabaseClient {
104104
const tracingData = { ...this.getContext() }
105105
const event = this.createEvent('db:transaction:rollback', this.debug)
106106

107-
return rollbackTransactionTracer.tracePromise(async () => {
107+
return rollbackTransaction.tracePromise(async () => {
108108
debug('rolling back transaction')
109109
await this.knexTransaction.rollback()
110110
tracingData.duration = process.hrtime(this.#startedAt)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* @adonisjs/lucid
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { AGGREGATE_ARGUMENTS } from '../symbols.js'
11+
import type {
12+
AggregateMethods,
13+
AggregateExpression,
14+
AggregateExpressionArguments,
15+
} from '../types/query.js'
16+
17+
export class FunctionExpressionBuilder {
18+
/**
19+
* Creates an aggregate to be parsed by the select expression
20+
* builder
21+
*/
22+
#createAggregate(method: AggregateMethods, expression: AggregateExpressionArguments) {
23+
const aggregate = {
24+
[AGGREGATE_ARGUMENTS]: {
25+
method: method,
26+
expression,
27+
alias: undefined as string | undefined,
28+
},
29+
as(alias: string) {
30+
this[AGGREGATE_ARGUMENTS].alias = alias
31+
return this
32+
},
33+
}
34+
return aggregate satisfies AggregateExpression
35+
}
36+
37+
/**
38+
* Sum values of one or more columns
39+
*/
40+
sum(...expression: AggregateExpressionArguments) {
41+
return this.#createAggregate('sum', expression)
42+
}
43+
44+
/**
45+
* Sum values of one or more columns
46+
*/
47+
sumDistinct(...expression: AggregateExpressionArguments) {
48+
return this.#createAggregate('sumDistinct', expression)
49+
}
50+
51+
avg(...expression: AggregateExpressionArguments) {
52+
return this.#createAggregate('avg', expression)
53+
}
54+
55+
avgDistinct(...expression: AggregateExpressionArguments) {
56+
return this.#createAggregate('avgDistinct', expression)
57+
}
58+
59+
min(...expression: AggregateExpressionArguments) {
60+
return this.#createAggregate('min', expression)
61+
}
62+
63+
max(...expression: AggregateExpressionArguments) {
64+
return this.#createAggregate('max', expression)
65+
}
66+
67+
count(...expression: AggregateExpressionArguments) {
68+
return this.#createAggregate('count', expression)
69+
}
70+
71+
countDistinct(...expression: AggregateExpressionArguments) {
72+
return this.#createAggregate('countDistinct', expression)
73+
}
74+
}

0 commit comments

Comments
 (0)