diff --git a/src/database/query_builder/chainable.ts b/src/database/query_builder/chainable.ts index d7ec061b..0caa9bcf 100644 --- a/src/database/query_builder/chainable.ts +++ b/src/database/query_builder/chainable.ts @@ -16,6 +16,7 @@ import { isObject } from '../../utils/index.js' import { RawQueryBuilder } from './raw.js' import { RawBuilder } from '../static_builder/raw.js' import { ReferenceBuilder } from '../static_builder/reference.js' +import type { DialectContract } from '../../types/database.js' /** * The chainable query builder to construct SQL queries for selecting, updating and @@ -105,6 +106,7 @@ export abstract class Chainable extends Macroable implements ChainableContract { constructor( public knexQuery: Knex.QueryBuilder, private queryCallback: DBQueryCallback, + private dialect: DialectContract, public keysResolver?: (columnName: string) => string ) { super() @@ -1701,6 +1703,27 @@ export abstract class Chainable extends Macroable implements ChainableContract { return this } + /** + * Order results by random value. + */ + orderByRandom(seed = '') { + switch (this.dialect.name) { + case 'sqlite3': + case 'better-sqlite3': + case 'postgres': + case 'redshift': + return this.orderByRaw('RANDOM()') + case 'mysql': + return this.orderByRaw(`RAND(${seed})`) + case 'mssql': + return this.orderByRaw('NEWID()') + case 'oracledb': + return this.orderByRaw('dbms_random.value') + default: + throw new Error(`Cannot order by random for the given dialect ${this.dialect.name}`) + } + } + /** * Define select offset */ diff --git a/src/database/query_builder/database.ts b/src/database/query_builder/database.ts index 926712b5..32fe35a4 100644 --- a/src/database/query_builder/database.ts +++ b/src/database/query_builder/database.ts @@ -70,7 +70,7 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil public client: QueryClientContract, public keysResolver?: (columnName: string) => string ) { - super(builder, queryCallback, keysResolver) + super(builder, queryCallback, client.dialect, keysResolver) this.debugQueries = this.client.debug } diff --git a/src/orm/query_builder/index.ts b/src/orm/query_builder/index.ts index e34dc9b3..135fa5fa 100644 --- a/src/orm/query_builder/index.ts +++ b/src/orm/query_builder/index.ts @@ -150,6 +150,7 @@ export class ModelQueryBuilder super( builder, customFn, + client.dialect, model.$keys.attributesToColumns.resolve.bind(model.$keys.attributesToColumns) ) diff --git a/src/types/querybuilder.ts b/src/types/querybuilder.ts index dd23c2cb..04cf1d5e 100644 --- a/src/types/querybuilder.ts +++ b/src/types/querybuilder.ts @@ -651,6 +651,7 @@ export interface ChainableContract { orderBy: OrderBy orderByRaw: RawQueryFn + orderByRandom: (seed?: string) => this union: Union unionAll: UnionAll diff --git a/test/database/query_builder.spec.ts b/test/database/query_builder.spec.ts index 4d6d5d40..9fbc9192 100644 --- a/test/database/query_builder.spec.ts +++ b/test/database/query_builder.spec.ts @@ -5439,6 +5439,55 @@ test.group('Query Builder | orderByRaw', (group) => { }) }) +test.group('Query Builder | orderByRandom', (group) => { + group.setup(async () => { + await setup() + }) + + group.teardown(async () => { + await cleanup() + }) + + group.each.teardown(async () => { + await resetTables() + }) + + test('define order by random value', async ({ assert }) => { + const connection = new Connection('primary', getConfig(), logger) + connection.connect() + + const db = getQueryBuilder(getQueryClient(connection)) + await getInsertBuilder(getQueryClient(connection)) + .table('users') + .multiInsert([ + { + username: 'virk', + email: 'virk@adonisjs.com', + }, + { + username: 'romain', + email: 'romain@adonisjs.com', + }, + { + username: 'nikk', + email: 'nikk@adonisjs.com', + }, + ]) + + const userResults: number[][] = [] + + for (let i = 0; i < 10; i++) { + const result = await db.from('users').orderByRandom() + + userResults.push(result.map((user) => user.id)) + } + + assert.isTrue(userResults.some((users) => userResults[0] !== users)) + + await connection.disconnect() + }) +}) + test.group('Query Builder | offset', (group) => { group.setup(async () => { await setup()