From 87f2afded8ad6e7d9fd0451d19ba561a8c67aacd Mon Sep 17 00:00:00 2001 From: jlenon7 Date: Sat, 4 Nov 2023 21:12:55 +0000 Subject: [PATCH] feat(driver): implement mysql driver --- src/database/builders/QueryBuilder.ts | 25 +- src/drivers/MySqlDriver.ts | 1229 +++++++ src/drivers/PostgresDriver.ts | 6 +- src/factories/DriverFactory.ts | 14 +- tests/fixtures/config/database.ts | 11 + ...08_000000_create_uuid_function_postgres.ts | 13 + ...2_10_08_000001_create_users_mysql_table.ts | 23 + ...0_08_000002_create_products_mysql_table.ts | 25 + ...0003_create_product_details_mysql_table.ts | 24 + ...0_08_000003_create_users_postgres_table.ts | 23 + ...8_000004_create_products_postgres_table.ts | 25 + ...022_10_08_000005_create_countries_table.ts | 16 + ..._create_products_details_postgres_table.ts | 24 + ...2022_10_08_000006_create_capitals_table.ts | 18 + ...2022_10_08_000007_create_students_table.ts | 20 + .../2022_10_08_000008_create_courses_table.ts | 20 + ...08_000009_create_students_courses_table.ts | 22 + .../migrations/OrdersDockerMigration.ts | 29 - tests/fixtures/migrations/OrdersMigration.ts | 29 - .../fixtures/migrations/ProductsMigration.ts | 25 - tests/fixtures/migrations/UsersMigration.ts | 25 - .../migrations/MigrationSourceTest.ts | 18 +- tests/unit/drivers/MySqlDriver.ts | 2884 +++++++++++++++++ tests/unit/drivers/PostgresDriverTest.ts | 40 +- tests/unit/factories/DriverFactoryTest.ts | 2 +- 25 files changed, 4424 insertions(+), 166 deletions(-) create mode 100644 src/drivers/MySqlDriver.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000000_create_uuid_function_postgres.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000001_create_users_mysql_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000002_create_products_mysql_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000003_create_product_details_mysql_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000003_create_users_postgres_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000004_create_products_postgres_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000005_create_countries_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000005_create_products_details_postgres_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000006_create_capitals_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000007_create_students_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000008_create_courses_table.ts create mode 100644 tests/fixtures/migrations/2022_10_08_000009_create_students_courses_table.ts delete mode 100644 tests/fixtures/migrations/OrdersDockerMigration.ts delete mode 100644 tests/fixtures/migrations/OrdersMigration.ts delete mode 100644 tests/fixtures/migrations/ProductsMigration.ts delete mode 100644 tests/fixtures/migrations/UsersMigration.ts create mode 100644 tests/unit/drivers/MySqlDriver.ts diff --git a/src/database/builders/QueryBuilder.ts b/src/database/builders/QueryBuilder.ts index 09da397..d922dba 100644 --- a/src/database/builders/QueryBuilder.ts +++ b/src/database/builders/QueryBuilder.ts @@ -614,10 +614,7 @@ export class QueryBuilder { /** * Set a where not statement in your query. */ - public whereNot( - statement: string | Record, - value?: any - ): QueryBuilder { + public whereNot(statement: any, value?: any): QueryBuilder { this.driver.whereNot(statement, value) return this @@ -666,10 +663,7 @@ export class QueryBuilder { /** * Set a where ILike statement in your query. */ - public whereILike( - statement: string | Record, - value?: any - ): QueryBuilder { + public whereILike(statement: any, value?: any): QueryBuilder { this.driver.whereILike(statement, value) return this @@ -741,10 +735,7 @@ export class QueryBuilder { /** * Set an or where not statement in your query. */ - public orWhereNot( - statement: string | Record, - value?: any - ): QueryBuilder { + public orWhereNot(statement: any, value?: any): QueryBuilder { this.driver.orWhereNot(statement, value) return this @@ -784,10 +775,7 @@ export class QueryBuilder { /** * Set an or where like statement in your query. */ - public orWhereLike( - statement: string | Record, - value?: any - ): QueryBuilder { + public orWhereLike(statement: any, value?: any): QueryBuilder { this.driver.orWhereLike(statement, value) return this @@ -796,10 +784,7 @@ export class QueryBuilder { /** * Set an or where ILike statement in your query. */ - public orWhereILike( - statement: string | Record, - value?: any - ): QueryBuilder { + public orWhereILike(statement: any, value?: any): QueryBuilder { this.driver.orWhereILike(statement, value) return this diff --git a/src/drivers/MySqlDriver.ts b/src/drivers/MySqlDriver.ts new file mode 100644 index 0000000..ce55f8c --- /dev/null +++ b/src/drivers/MySqlDriver.ts @@ -0,0 +1,1229 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/** + * @athenna/database + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { + Exec, + Is, + Json, + Options, + type PaginatedResponse +} from '@athenna/common' + +import type { Knex } from 'knex' +import { Driver } from '#src/drivers/Driver' +import { DriverFactory } from '#src/factories/DriverFactory' +import type { ConnectionOptions, Direction } from '#src/types' +import { Transaction } from '#src/database/transactions/Transaction' +import { MigrationSource } from '#src/database/migrations/MigrationSource' +import { WrongMethodException } from '#src/exceptions/WrongMethodException' +import { PROTECTED_QUERY_METHODS } from '#src/constants/ProtectedQueryMethods' +import { NotConnectedDatabaseException } from '#src/exceptions/NotConnectedDatabaseException' + +export class MySqlDriver extends Driver { + /** + * Connect to database. + */ + public connect(options: ConnectionOptions = {}): void { + options = Options.create(options, { + force: false, + saveOnFactory: true, + connect: true + }) + + if (!options.connect) { + return + } + + if (this.isConnected && !options.force) { + return + } + + this.client = DriverFactory.createConnection( + this.connection, + Json.pick(options, ['saveOnFactory']) + ) + + this.isConnected = true + this.isSavedOnFactory = options.saveOnFactory + + this.qb = this.query() + } + + /** + * Close the connection with database in this instance. + */ + public async close(): Promise { + if (!this.isConnected) { + return + } + + if (this.isSavedOnFactory) { + await DriverFactory.closeConnection(this.connection) + } else { + await this.client.destroy() + } + + this.qb = null + this.tableName = null + this.client = null + this.isConnected = false + } + + /** + * Creates a new instance of query builder. + */ + public query(): Knex.QueryBuilder { + if (!this.isConnected) { + throw new NotConnectedDatabaseException() + } + + const query = this.useSetQB + ? this.qb.table(this.tableName) + : this.client.queryBuilder().table(this.tableName) + + const handler = { + get: (target: Knex.QueryBuilder, propertyKey: string) => { + if (PROTECTED_QUERY_METHODS.includes(propertyKey)) { + this.qb = this.query() + } + + return target[propertyKey] + } + } + + return new Proxy(query, handler) + } + + /** + * Create a new transaction. + */ + public async startTransaction(): Promise< + Transaction + > { + const trx = await this.client.transaction() + + return new Transaction(this.clone().setClient(trx)) + } + + /** + * Commit the transaction. + */ + public async commitTransaction(): Promise { + const client = this.client as Knex.Transaction + + await client.commit() + + this.tableName = null + this.client = null + this.isConnected = false + } + + /** + * Rollback the transaction. + */ + public async rollbackTransaction(): Promise { + const client = this.client as Knex.Transaction + + await client.rollback() + + this.tableName = null + this.client = null + this.isConnected = false + } + + /** + * Run database migrations. + */ + public async runMigrations(): Promise { + await this.client.migrate.latest({ + migrationSource: new MigrationSource(this.connection) + }) + } + + /** + * Revert database migrations. + */ + public async revertMigrations(): Promise { + await this.client.migrate.rollback({ + migrationSource: new MigrationSource(this.connection) + }) + } + + /** + * List all databases available. + */ + public async getDatabases(): Promise { + const [databases] = await this.raw('SHOW DATABASES') + + return databases.map(database => database.Database) + } + + /** + * Get the current database name. + */ + public async getCurrentDatabase(): Promise { + return this.client.client.database() + } + + /** + * Verify if database exists. + */ + public async hasDatabase(database: string): Promise { + const databases = await this.getDatabases() + + return databases.includes(database) + } + + /** + * Create a new database. + */ + public async createDatabase(database: string): Promise { + await this.raw('CREATE DATABASE IF NOT EXISTS ??', database) + } + + /** + * Drop some database. + */ + public async dropDatabase(database: string): Promise { + await this.raw('DROP DATABASE IF EXISTS ??', database) + } + + /** + * List all tables available. + */ + public async getTables(): Promise { + const [tables] = await this.raw( + 'SELECT table_name FROM information_schema.tables WHERE table_schema = ?', + await this.getCurrentDatabase() + ) + + return tables.map(table => table.TABLE_NAME) + } + + /** + * Verify if table exists. + */ + public async hasTable(table: string): Promise { + return this.client.schema.hasTable(table) + } + + /** + * Create a new table in database. + */ + public async createTable( + table: string, + closure: (builder: Knex.TableBuilder) => void | Promise + ): Promise { + await this.client.schema.createTable(table, closure) + } + + /** + * Drop a table in database. + */ + public async dropTable(table: string): Promise { + await this.client.schema.dropTableIfExists(table) + } + + /** + * Remove all data inside some database table + * and restart the identity of the table. + */ + public async truncate(table: string): Promise { + try { + await this.raw('SET FOREIGN_KEY_CHECKS = 0') + await this.raw('TRUNCATE TABLE ??', table) + } finally { + await this.raw('SET FOREIGN_KEY_CHECKS = 1') + } + } + + /** + * Make a raw query in database. + */ + public raw(sql: string, bindings?: any): T { + return this.client.raw(sql, bindings) as any + } + + /** + * Calculate the average of a given column. + */ + public async avg(column: string): Promise { + const [{ avg }] = await this.qb.avg({ avg: column }) + + return avg + } + + /** + * Calculate the average of a given column using distinct. + */ + public async avgDistinct(column: string): Promise { + const [{ avg }] = await this.qb.avgDistinct({ avg: column }) + + return avg + } + + /** + * Get the max number of a given column. + */ + public async max(column: string): Promise { + const [{ max }] = await this.qb.max({ max: column }) + + return max + } + + /** + * Get the min number of a given column. + */ + public async min(column: string): Promise { + const [{ min }] = await this.qb.min({ min: column }) + + return min + } + + /** + * Sum all numbers of a given column. + */ + public async sum(column: string): Promise { + const [{ sum }] = await this.qb.sum({ sum: column }) + + return sum + } + + /** + * Sum all numbers of a given column in distinct mode. + */ + public async sumDistinct(column: string): Promise { + const [{ sum }] = await this.qb.sumDistinct({ sum: column }) + + return sum + } + + /** + * Increment a value of a given column. + */ + public async increment(column: string): Promise { + await this.qb.increment(column) + } + + /** + * Decrement a value of a given column. + */ + public async decrement(column: string): Promise { + await this.qb.decrement(column) + } + + /** + * Calculate the average of a given column using distinct. + */ + public async count(column: string = '*'): Promise { + const [{ count }] = await this.qb.count({ count: column }) + + return `${count}` + } + + /** + * Calculate the average of a given column using distinct. + */ + public async countDistinct(column: string): Promise { + const [{ count }] = await this.qb.countDistinct({ count: column }) + + return `${count}` + } + + /** + * Find a value in database. + */ + public async find(): Promise { + return this.qb.first() + } + + /** + * Find many values in database. + */ + public async findMany(): Promise { + const data = await this.qb + + this.qb = this.query() + + return data + } + + /** + * Find many values in database and return as paginated response. + */ + public async paginate( + page = 0, + limit = 10, + resourceUrl = '/' + ): Promise> { + const [{ count }] = await this.qb + .clone() + .clearOrder() + .clearSelect() + .count({ count: '*' }) + + const data = await this.offset(page).limit(limit).findMany() + + return Exec.pagination(data, parseInt(count), { page, limit, resourceUrl }) + } + + /** + * Create a value in database. + */ + public async create( + data: Partial = {}, + primaryKey: string = 'id' + ): Promise { + if (Is.Array(data)) { + throw new WrongMethodException('create', 'createMany') + } + + const created = await this.createMany([data], primaryKey) + + return created[0] + } + + /** + * Create many values in database. + */ + public async createMany( + data: Partial[] = [], + primaryKey: string = 'id' + ): Promise { + if (!Is.Array(data)) { + throw new WrongMethodException('createMany', 'create') + } + + const ids = [] + + const promises = data.map(data => { + return this.qb + .clone() + .insert(data) + .then(([id]) => ids.push(data[primaryKey] || id)) + }) + + await Promise.all(promises) + + return this.whereIn(primaryKey, ids).findMany() + } + + /** + * Create data or update if already exists. + */ + public async createOrUpdate( + data: Partial = {}, + primaryKey: string = 'id' + ): Promise { + const query = this.qb.clone() + const hasValue = await query.first() + + if (hasValue) { + await this.qb.where(primaryKey, hasValue[primaryKey]).update(data) + + return this.where(primaryKey, hasValue[primaryKey]).find() + } + + return this.create(data, primaryKey) + } + + /** + * Update a value in database. + */ + public async update(data: Partial): Promise { + await this.qb.clone().update(data) + + const result = await this.findMany() + + if (result.length === 1) { + return result[0] + } + + return result + } + + /** + * Delete one value in database. + */ + public async delete(): Promise { + await this.qb.delete() + } + + /** + * Set the table that this query will be executed. + */ + public table(table: string): this { + if (!this.isConnected) { + throw new NotConnectedDatabaseException() + } + + this.tableName = table + this.qb = this.query() + + return this + } + + /** + * Log in console the actual query built. + */ + public dump(): this { + console.log(this.qb.toSQL().toNative()) + + return this + } + + /** + * Set the columns that should be selected on query. + */ + public select(...columns: string[]): this { + this.qb.select(...columns) + + return this + } + + /** + * Set the columns that should be selected on query raw. + */ + public selectRaw(sql: string, bindings?: any): this { + return this.select(this.raw(sql, bindings) as any) + } + + /** + * Set the table that should be used on query. + * Different from `table()` method, this method + * doesn't change the driver table. + */ + public from(table: string): this { + this.qb.from(table) + + return this + } + + /** + * Set the table that should be used on query raw. + * Different from `table()` method, this method + * doesn't change the driver table. + */ + public fromRaw(sql: string, bindings?: any): this { + return this.from(this.raw(sql, bindings) as any) + } + + /** + * Set a join statement in your query. + */ + public join(table: any, column1?: any, operation?: any, column2?: any): this { + return this.joinByType('join', table, column1, operation, column2) + } + + /** + * Set a left join statement in your query. + */ + public leftJoin( + table: any, + column1?: any, + operation?: any, + column2?: any + ): this { + return this.joinByType('leftJoin', table, column1, operation, column2) + } + + /** + * Set a right join statement in your query. + */ + public rightJoin( + table: any, + column1?: any, + operation?: any, + column2?: any + ): this { + return this.joinByType('rightJoin', table, column1, operation, column2) + } + + /** + * Set a cross join statement in your query. + */ + public crossJoin( + table: any, + column1?: any, + operation?: any, + column2?: any + ): this { + return this.joinByType('crossJoin', table, column1, operation, column2) + } + + /** + * Set a full outer join statement in your query. + */ + public fullOuterJoin( + table: any, + column1?: any, + operation?: any, + column2?: any + ): this { + // TODO https://github.com/knex/knex/issues/3949 + return this.joinByType('leftJoin', table, column1, operation, column2) + } + + /** + * Set a left outer join statement in your query. + */ + public leftOuterJoin( + table: any, + column1?: any, + operation?: any, + column2?: any + ): this { + return this.joinByType('leftOuterJoin', table, column1, operation, column2) + } + + /** + * Set a right outer join statement in your query. + */ + public rightOuterJoin( + table: any, + column1?: any, + operation?: any, + column2?: any + ): this { + return this.joinByType('rightOuterJoin', table, column1, operation, column2) + } + + /** + * Set a join raw statement in your query. + */ + public joinRaw(sql: string, bindings?: any): this { + this.qb.joinRaw(sql, bindings) + + return this + } + + /** + * Set a group by statement in your query. + */ + public groupBy(...columns: string[]): this { + this.qb.groupBy(...columns) + + return this + } + + /** + * Set a group by raw statement in your query. + */ + public groupByRaw(sql: string, bindings?: any): this { + this.qb.groupByRaw(sql, bindings) + + return this + } + + /** + * Set a having statement in your query. + */ + public having(column: any, operation?: any, value?: any): this { + if (operation === undefined) { + this.qb.having(column) + + return this + } + + if (value === undefined) { + this.qb.having(column, '=', operation) + + return this + } + + this.qb.having(column, operation, value) + + return this + } + + /** + * Set a having raw statement in your query. + */ + public havingRaw(sql: string, bindings?: any): this { + this.qb.havingRaw(sql, bindings) + + return this + } + + /** + * Set a having exists statement in your query. + */ + public havingExists(closure: (query: MySqlDriver) => void): this { + const driver = this.clone() as MySqlDriver + + // @ts-ignore + this.qb.havingExists(function () { + closure(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + /** + * Set a having not exists statement in your query. + */ + public havingNotExists(closure: (query: MySqlDriver) => void): this { + const driver = this.clone() as MySqlDriver + + // @ts-ignore + this.qb.havingNotExists(function () { + closure(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + /** + * Set a having in statement in your query. + */ + public havingIn(column: string, values: any[]): this { + this.qb.havingIn(column, values) + + return this + } + + /** + * Set a having not in statement in your query. + */ + public havingNotIn(column: string, values: any[]): this { + this.qb.havingNotIn(column, values) + + return this + } + + /** + * Set a having between statement in your query. + */ + public havingBetween(column: string, values: [any, any]): this { + this.qb.havingBetween(column, values) + + return this + } + + /** + * Set a having not between statement in your query. + */ + public havingNotBetween(column: string, values: [any, any]): this { + this.qb.havingNotBetween(column, values) + + return this + } + + /** + * Set a having null statement in your query. + */ + public havingNull(column: string): this { + this.qb.havingNull(column) + + return this + } + + /** + * Set a having not null statement in your query. + */ + public havingNotNull(column: string): this { + this.qb.havingNotNull(column) + + return this + } + + /** + * Set an or having statement in your query. + */ + public orHaving(column: any, operation?: any, value?: any): this { + if (operation === undefined) { + this.qb.orHaving(column) + + return this + } + + if (value === undefined) { + this.qb.orHaving(column, '=', operation) + + return this + } + + this.qb.orHaving(column, operation, value) + + return this + } + + /** + * Set an or having raw statement in your query. + */ + public orHavingRaw(sql: string, bindings?: any): this { + this.qb.orHavingRaw(sql, bindings) + + return this + } + + /** + * Set an or having exists statement in your query. + */ + public orHavingExists(closure: (query: MySqlDriver) => void): this { + const driver = this.clone() as MySqlDriver + + // @ts-ignore + this.qb.orHavingExists(function () { + closure(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + /** + * Set an or having not exists statement in your query. + */ + public orHavingNotExists(closure: (query: MySqlDriver) => void): this { + const driver = this.clone() as MySqlDriver + + // @ts-ignore + this.qb.orHavingNotExists(function () { + closure(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + /** + * Set an or having in statement in your query. + */ + public orHavingIn(column: string, values: any[]): this { + // @ts-ignore + this.qb.orHavingIn(column, values) + + return this + } + + /** + * Set an or having not in statement in your query. + */ + public orHavingNotIn(column: string, values: any[]): this { + this.qb.orHavingNotIn(column, values) + + return this + } + + /** + * Set an or having between statement in your query. + */ + public orHavingBetween(column: string, values: [any, any]): this { + this.qb.orHavingBetween(column, values) + + return this + } + + /** + * Set an or having not between statement in your query. + */ + public orHavingNotBetween(column: string, values: [any, any]): this { + this.qb.orHavingNotBetween(column, values) + + return this + } + + /** + * Set an or having null statement in your query. + */ + public orHavingNull(column: string): this { + // @ts-ignore + this.qb.orHavingNull(column) + + return this + } + + /** + * Set an or having not null statement in your query. + */ + public orHavingNotNull(column: string): this { + // @ts-ignore + this.qb.orHavingNotNull(column) + + return this + } + + /** + * Set a where statement in your query. + */ + public where(statement: any, operation?: any, value?: any): this { + if (Is.Function(statement)) { + const driver = this.clone() + + this.qb.where(function () { + statement(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + if (operation === undefined) { + this.qb.where(statement) + + return this + } + + if (value === undefined) { + this.qb.where(statement, operation) + + return this + } + + this.qb.where(statement, operation, value) + + return this + } + + /** + * Set a where not statement in your query. + */ + public whereNot(statement: any, value?: any): this { + if (Is.Function(statement)) { + const driver = this.clone() + + this.qb.whereNot(function () { + statement(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + if (value === undefined) { + this.qb.whereNot(statement) + + return this + } + + this.qb.whereNot(statement, value) + + return this + } + + /** + * Set a where raw statement in your query. + */ + public whereRaw(sql: string, bindings?: any): this { + this.qb.whereRaw(sql, bindings) + + return this + } + + /** + * Set a where exists statement in your query. + */ + public whereExists(closure: (query: MySqlDriver) => void): this { + const driver = this.clone() as MySqlDriver + + this.qb.whereExists(function () { + closure(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + /** + * Set a where not exists statement in your query. + */ + public whereNotExists(closure: (query: MySqlDriver) => void): this { + const driver = this.clone() as MySqlDriver + + this.qb.whereNotExists(function () { + closure(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + /** + * Set a where like statement in your query. + */ + public whereLike(column: string, value: any): this { + this.qb.whereLike(column, value) + + return this + } + + /** + * Set a where ILike statement in your query. + */ + public whereILike(column: string, value: any): this { + this.qb.whereILike(column, value) + + return this + } + + /** + * Set a where in statement in your query. + */ + public whereIn(column: string, values: any[]): this { + this.qb.whereIn(column, values) + + return this + } + + /** + * Set a where not in statement in your query. + */ + public whereNotIn(column: string, values: any[]): this { + this.qb.whereNotIn(column, values) + + return this + } + + /** + * Set a where between statement in your query. + */ + public whereBetween(column: string, values: [any, any]): this { + this.qb.whereBetween(column, values) + + return this + } + + /** + * Set a where not between statement in your query. + */ + public whereNotBetween(column: string, values: [any, any]): this { + this.qb.whereNotBetween(column, values) + + return this + } + + /** + * Set a where null statement in your query. + */ + public whereNull(column: string): this { + this.qb.whereNull(column) + + return this + } + + /** + * Set a where not null statement in your query. + */ + public whereNotNull(column: string): this { + this.qb.whereNotNull(column) + + return this + } + + /** + * Set a or where statement in your query. + */ + public orWhere(statement: any, operation?: any, value?: any): this { + if (Is.Function(statement)) { + const driver = this.clone() + + this.qb.orWhere(function () { + statement(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + if (operation === undefined) { + this.qb.orWhere(statement) + + return this + } + + if (value === undefined) { + this.qb.orWhere(statement, operation) + + return this + } + + this.qb.orWhere(statement, operation, value) + + return this + } + + /** + * Set an or where not statement in your query. + */ + public orWhereNot(statement: any, value?: any): this { + if (Is.Function(statement)) { + const driver = this.clone() + + this.qb.orWhereNot(function () { + statement(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + if (value === undefined) { + this.qb.orWhereNot(statement) + + return this + } + + this.qb.orWhereNot(statement, value) + + return this + } + + /** + * Set a or where raw statement in your query. + */ + public orWhereRaw(sql: string, bindings?: any): this { + this.qb.orWhereRaw(sql, bindings) + + return this + } + + /** + * Set an or where exists statement in your query. + */ + public orWhereExists(closure: (query: MySqlDriver) => void): this { + const driver = this.clone() as MySqlDriver + + this.qb.orWhereExists(function () { + closure(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + /** + * Set an or where not exists statement in your query. + */ + public orWhereNotExists(closure: (query: MySqlDriver) => void): this { + const driver = this.clone() as MySqlDriver + + this.qb.orWhereNotExists(function () { + closure(driver.setQueryBuilder(this, { useSetQB: true })) + }) + + return this + } + + /** + * Set an or where like statement in your query. + */ + public orWhereLike(column: any, value: any): this { + this.qb.orWhereLike(column, value) + + return this + } + + /** + * Set an or where ILike statement in your query. + */ + public orWhereILike(column: any, value: any): this { + this.qb.orWhereILike(column, value) + + return this + } + + /** + * Set an or where in statement in your query. + */ + public orWhereIn(column: string, values: any[]): this { + this.qb.orWhereIn(column, values) + + return this + } + + /** + * Set an or where not in statement in your query. + */ + public orWhereNotIn(column: string, values: any[]): this { + this.qb.orWhereNotIn(column, values) + + return this + } + + /** + * Set an or where between statement in your query. + */ + public orWhereBetween(column: string, values: [any, any]): this { + this.qb.orWhereBetween(column, values) + + return this + } + + /** + * Set an or where not between statement in your query. + */ + public orWhereNotBetween(column: string, values: [any, any]): this { + this.qb.orWhereNotBetween(column, values) + + return this + } + + /** + * Set an or where null statement in your query. + */ + public orWhereNull(column: string): this { + this.qb.orWhereNull(column) + + return this + } + + /** + * Set an or where not null statement in your query. + */ + public orWhereNotNull(column: string): this { + this.qb.orWhereNotNull(column) + + return this + } + + /** + * Set an order by statement in your query. + */ + public orderBy(column: string, direction: Direction = 'ASC'): this { + this.qb.orderBy(column, direction.toUpperCase()) + + return this + } + + /** + * Set an order by raw statement in your query. + */ + public orderByRaw(sql: string, bindings?: any): this { + this.qb.orderByRaw(sql, bindings) + + return this + } + + /** + * Order the results easily by the latest date. By default, the result will + * be ordered by the table's "createdAt" column. + */ + public latest(column: string = 'createdAt'): this { + return this.orderBy(column, 'DESC') + } + + /** + * Order the results easily by the oldest date. By default, the result will + * be ordered by the table's "createdAt" column. + */ + public oldest(column: string = 'createdAt'): this { + return this.orderBy(column, 'ASC') + } + + /** + * Set the skip number in your query. + */ + public offset(number: number): this { + this.qb.offset(number) + + return this + } + + /** + * Set the limit number in your query. + */ + public limit(number: number): this { + this.qb.limit(number) + + return this + } +} diff --git a/src/drivers/PostgresDriver.ts b/src/drivers/PostgresDriver.ts index 87fbbb6..244a554 100644 --- a/src/drivers/PostgresDriver.ts +++ b/src/drivers/PostgresDriver.ts @@ -107,9 +107,9 @@ export class PostgresDriver extends Driver { public async startTransaction(): Promise< Transaction > { - return new Transaction( - new PostgresDriver(this.connection, await this.client.transaction()) - ) + const trx = await this.client.transaction() + + return new Transaction(this.clone().setClient(trx)) } /** diff --git a/src/factories/DriverFactory.ts b/src/factories/DriverFactory.ts index 4a4a614..9c49c6e 100644 --- a/src/factories/DriverFactory.ts +++ b/src/factories/DriverFactory.ts @@ -13,6 +13,7 @@ import { Config } from '@athenna/config' import { Exec, Options } from '@athenna/common' import type { Driver } from '#src/drivers/Driver' import type { DriverKey } from '#src/types/DriverKey' +import { MySqlDriver } from '#src/drivers/MySqlDriver' import { PostgresDriver } from '#src/drivers/PostgresDriver' import type { CreateConOptions } from '#src/types/CreateConOptions' import { ConnectionFactory } from '#src/factories/ConnectionFactory' @@ -24,10 +25,15 @@ export class DriverFactory { /** * All athenna drivers connection configuration. */ - public static drivers: Map = new Map().set('postgres', { - Driver: PostgresDriver, - client: null - }) + public static drivers: Map = new Map() + .set('mysql', { + Driver: MySqlDriver, + client: null + }) + .set('postgres', { + Driver: PostgresDriver, + client: null + }) /** * Return all available drivers. diff --git a/tests/fixtures/config/database.ts b/tests/fixtures/config/database.ts index 7fd840c..0494a59 100644 --- a/tests/fixtures/config/database.ts +++ b/tests/fixtures/config/database.ts @@ -27,6 +27,17 @@ export default { 'not-found-driver': { driver: 'not-found' }, + 'mysql-docker': { + driver: 'mysql', + connection: { + host: 'localhost', + port: 3307, + user: 'root', + password: '12345', + database: 'athenna' + }, + debug: false + }, 'postgres-docker': { driver: 'postgres', connection: { diff --git a/tests/fixtures/migrations/2022_10_08_000000_create_uuid_function_postgres.ts b/tests/fixtures/migrations/2022_10_08_000000_create_uuid_function_postgres.ts new file mode 100644 index 0000000..6764e66 --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000000_create_uuid_function_postgres.ts @@ -0,0 +1,13 @@ +import { DatabaseImpl, Migration } from '#src' + +export class UuidFunctionMigration extends Migration { + public static connection() { + return 'postgres-docker' + } + + public async up(db: DatabaseImpl) { + return db.raw('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"') + } + + public async down() {} +} diff --git a/tests/fixtures/migrations/2022_10_08_000001_create_users_mysql_table.ts b/tests/fixtures/migrations/2022_10_08_000001_create_users_mysql_table.ts new file mode 100644 index 0000000..16a068d --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000001_create_users_mysql_table.ts @@ -0,0 +1,23 @@ +import { DatabaseImpl, Migration } from '#src' + +export class UserMigration extends Migration { + public static connection() { + return 'mysql-docker' + } + + public tableName = 'users' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.increments('id') + table.string('name') + table.string('email').unique() + table.timestamps(true, true, true) + table.dateTime('deletedAt').nullable().defaultTo(null) + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000002_create_products_mysql_table.ts b/tests/fixtures/migrations/2022_10_08_000002_create_products_mysql_table.ts new file mode 100644 index 0000000..4740f98 --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000002_create_products_mysql_table.ts @@ -0,0 +1,25 @@ +import { DatabaseImpl, Migration } from '#src' + +export class ProductMigration extends Migration { + public static connection() { + return 'mysql-docker' + } + + public tableName = 'products' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.increments('id') + table.string('name', 255) + table.integer('price').defaultTo(0) + table.integer('userId').unsigned().index().references('id').inTable('users') + + table.timestamps(true, true, true) + table.dateTime('deletedAt').nullable().defaultTo(null) + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000003_create_product_details_mysql_table.ts b/tests/fixtures/migrations/2022_10_08_000003_create_product_details_mysql_table.ts new file mode 100644 index 0000000..60dab3f --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000003_create_product_details_mysql_table.ts @@ -0,0 +1,24 @@ +import { DatabaseImpl, Migration } from '#src' + +export class ProductDetailsMigration extends Migration { + public static connection() { + return 'mysql-docker' + } + + public tableName = 'product_details' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.increments('id') + table.string('content', 255) + table.integer('productId').unsigned().index().references('id').inTable('products') + + table.timestamps(true, true, true) + table.dateTime('deletedAt').nullable().defaultTo(null) + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000003_create_users_postgres_table.ts b/tests/fixtures/migrations/2022_10_08_000003_create_users_postgres_table.ts new file mode 100644 index 0000000..6c95ee2 --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000003_create_users_postgres_table.ts @@ -0,0 +1,23 @@ +import { DatabaseImpl, Migration } from '#src' + +export class UserMigration extends Migration { + public static connection() { + return 'postgres-docker' + } + + public tableName = 'users' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.increments('id') + table.string('name') + table.string('email').unique() + table.timestamps(true, true, true) + table.dateTime('deletedAt').nullable().defaultTo(null) + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000004_create_products_postgres_table.ts b/tests/fixtures/migrations/2022_10_08_000004_create_products_postgres_table.ts new file mode 100644 index 0000000..5346542 --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000004_create_products_postgres_table.ts @@ -0,0 +1,25 @@ +import { DatabaseImpl, Migration } from '#src' + +export class ProductMigration extends Migration { + public static connection() { + return 'postgres-docker' + } + + public tableName = 'products' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.increments('id') + table.string('name', 255) + table.integer('price').defaultTo(0) + table.integer('userId').unsigned().index().references('id').inTable('users') + + table.timestamps(true, true, true) + table.dateTime('deletedAt').nullable().defaultTo(null) + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000005_create_countries_table.ts b/tests/fixtures/migrations/2022_10_08_000005_create_countries_table.ts new file mode 100644 index 0000000..a10bf09 --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000005_create_countries_table.ts @@ -0,0 +1,16 @@ +import { DatabaseImpl, Migration } from '#src' + +export class CountryMigration extends Migration { + public tableName = 'countries' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.uuid('id').primary().defaultTo(db.raw('uuid_generate_v4()')) + table.string('name') + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000005_create_products_details_postgres_table.ts b/tests/fixtures/migrations/2022_10_08_000005_create_products_details_postgres_table.ts new file mode 100644 index 0000000..4dbca04 --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000005_create_products_details_postgres_table.ts @@ -0,0 +1,24 @@ +import { DatabaseImpl, Migration } from '#src' + +export class ProductDetailsMigration extends Migration { + public static connection() { + return 'postgres-docker' + } + + public tableName = 'product_details' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.increments('id') + table.string('content', 255) + table.integer('productId').unsigned().index().references('id').inTable('products') + + table.timestamps(true, true, true) + table.dateTime('deletedAt').nullable().defaultTo(null) + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000006_create_capitals_table.ts b/tests/fixtures/migrations/2022_10_08_000006_create_capitals_table.ts new file mode 100644 index 0000000..981a980 --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000006_create_capitals_table.ts @@ -0,0 +1,18 @@ +import { DatabaseImpl, Migration } from '#src' + +export class CapitalMigration extends Migration { + public tableName = 'capitals' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.uuid('id').primary().defaultTo(db.raw('uuid_generate_v4()')) + table.string('name') + + table.uuid('countryId').unique().unsigned().index().references('id').inTable('countries') + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000007_create_students_table.ts b/tests/fixtures/migrations/2022_10_08_000007_create_students_table.ts new file mode 100644 index 0000000..1af68d5 --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000007_create_students_table.ts @@ -0,0 +1,20 @@ +import { DatabaseImpl, Migration } from '#src' + +export class StudentMigration extends Migration { + public static connection() { + return 'mysql-docker' + } + + public tableName = 'students' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.uuid('id').primary().defaultTo(db.raw('(UUID())')) + table.string('name') + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000008_create_courses_table.ts b/tests/fixtures/migrations/2022_10_08_000008_create_courses_table.ts new file mode 100644 index 0000000..8fe8d1d --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000008_create_courses_table.ts @@ -0,0 +1,20 @@ +import { DatabaseImpl, Migration } from '#src' + +export class CourseMigration extends Migration { + public static connection() { + return 'mysql-docker' + } + + public tableName = 'courses' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.uuid('id').primary().defaultTo(db.raw('(UUID())')) + table.string('name') + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/2022_10_08_000009_create_students_courses_table.ts b/tests/fixtures/migrations/2022_10_08_000009_create_students_courses_table.ts new file mode 100644 index 0000000..e469cc4 --- /dev/null +++ b/tests/fixtures/migrations/2022_10_08_000009_create_students_courses_table.ts @@ -0,0 +1,22 @@ +import { DatabaseImpl, Migration } from '#src' + +export class StudentCourseMigration extends Migration { + public static connection() { + return 'mysql-docker' + } + + public tableName = 'students_courses' + + public async up(db: DatabaseImpl) { + return db.createTable(this.tableName, table => { + table.uuid('id').primary().defaultTo(db.raw('(UUID())')) + + table.uuid('courseId').references('id').inTable('courses') + table.uuid('studentId').references('id').inTable('students') + }) + } + + public async down(db: DatabaseImpl) { + return db.dropTable(this.tableName) + } +} diff --git a/tests/fixtures/migrations/OrdersDockerMigration.ts b/tests/fixtures/migrations/OrdersDockerMigration.ts deleted file mode 100644 index 4cc34cc..0000000 --- a/tests/fixtures/migrations/OrdersDockerMigration.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @athenna/database - * - * (c) João Lenon - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Migration } from '#src/database/migrations/Migration' -import type { DatabaseImpl } from '#src/database/DatabaseImpl' - -export class OrdersDockerMigration extends Migration { - public static connection() { - return 'postgres-docker' - } - - public tableName = 'orders' - - public async up(db: DatabaseImpl): Promise { - await db.createTable(this.tableName, builder => { - builder.string('id').primary() - }) - } - - public async down(db: DatabaseImpl): Promise { - await db.dropTable(this.tableName) - } -} diff --git a/tests/fixtures/migrations/OrdersMigration.ts b/tests/fixtures/migrations/OrdersMigration.ts deleted file mode 100644 index 947ee0a..0000000 --- a/tests/fixtures/migrations/OrdersMigration.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @athenna/database - * - * (c) João Lenon - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Migration } from '#src/database/migrations/Migration' -import type { DatabaseImpl } from '#src/database/DatabaseImpl' - -export class OrdersMigration extends Migration { - public static connection() { - return 'postgres' - } - - public tableName = 'orders' - - public async up(db: DatabaseImpl): Promise { - await db.createTable(this.tableName, builder => { - builder.string('id').primary() - }) - } - - public async down(db: DatabaseImpl): Promise { - await db.dropTable(this.tableName) - } -} diff --git a/tests/fixtures/migrations/ProductsMigration.ts b/tests/fixtures/migrations/ProductsMigration.ts deleted file mode 100644 index 91b04f2..0000000 --- a/tests/fixtures/migrations/ProductsMigration.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @athenna/database - * - * (c) João Lenon - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Migration } from '#src/database/migrations/Migration' -import type { DatabaseImpl } from '#src/database/DatabaseImpl' - -export class ProductsMigration extends Migration { - public tableName = 'products' - - public async up(db: DatabaseImpl): Promise { - await db.createTable(this.tableName, builder => { - builder.string('id').primary() - }) - } - - public async down(db: DatabaseImpl): Promise { - await db.dropTable(this.tableName) - } -} diff --git a/tests/fixtures/migrations/UsersMigration.ts b/tests/fixtures/migrations/UsersMigration.ts deleted file mode 100644 index 034747a..0000000 --- a/tests/fixtures/migrations/UsersMigration.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @athenna/database - * - * (c) João Lenon - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Migration } from '#src/database/migrations/Migration' -import type { DatabaseImpl } from '#src/database/DatabaseImpl' - -export class UsersMigration extends Migration { - public tableName = 'users' - - public async up(db: DatabaseImpl): Promise { - await db.createTable(this.tableName, builder => { - builder.string('id').primary() - }) - } - - public async down(db: DatabaseImpl): Promise { - await db.dropTable(this.tableName) - } -} diff --git a/tests/unit/database/migrations/MigrationSourceTest.ts b/tests/unit/database/migrations/MigrationSourceTest.ts index 43f6145..b50f459 100644 --- a/tests/unit/database/migrations/MigrationSourceTest.ts +++ b/tests/unit/database/migrations/MigrationSourceTest.ts @@ -8,11 +8,8 @@ */ import { FakeDriver } from '#tests/fixtures/drivers/FakeDriver' -import { UsersMigration } from '#tests/fixtures/migrations/UsersMigration' import { MigrationSource } from '#src/database/migrations/MigrationSource' -import { OrdersMigration } from '#tests/fixtures/migrations/OrdersMigration' import { Test, Mock, AfterEach, type Context, BeforeEach } from '@athenna/test' -import { ProductsMigration } from '#tests/fixtures/migrations/ProductsMigration' export default class MigrationSourceTest { @BeforeEach() @@ -31,21 +28,18 @@ export default class MigrationSourceTest { const migrations = await new MigrationSource('fake').getMigrations() - assert.deepEqual(migrations[0].name, 'ProductsMigration.ts') - assert.deepEqual(migrations[0].Migration, ProductsMigration) - assert.deepEqual(migrations[1].name, 'UsersMigration.ts') - assert.deepEqual(migrations[1].Migration, UsersMigration) + assert.deepEqual(migrations[0].name, '2022_10_08_000005_create_countries_table.ts') + assert.deepEqual(migrations[1].name, '2022_10_08_000006_create_capitals_table.ts') } @Test() public async shouldBeAbleToGetAllMigrationsFromMigrationsDirAndCheckTheOnesThatShouldRun({ assert }: Context) { Mock.when(Path, 'migrations').return(Path.fixtures('migrations')) - const migrations = await new MigrationSource('postgres').getMigrations() + const migrations = await new MigrationSource('postgres-docker').getMigrations() - assert.lengthOf(migrations, 1) - assert.deepEqual(migrations[0].name, 'OrdersMigration.ts') - assert.deepEqual(migrations[0].Migration, OrdersMigration) + assert.lengthOf(migrations, 4) + assert.deepEqual(migrations[0].name, '2022_10_08_000000_create_uuid_function_postgres.ts') } @Test() @@ -56,7 +50,7 @@ export default class MigrationSourceTest { const migrations = await migrationSource.getMigrations() const name = migrationSource.getMigrationName(migrations[0]) - assert.deepEqual(name, 'ProductsMigration.ts') + assert.deepEqual(name, '2022_10_08_000005_create_countries_table.ts') } @Test() diff --git a/tests/unit/drivers/MySqlDriver.ts b/tests/unit/drivers/MySqlDriver.ts new file mode 100644 index 0000000..c3b4a0b --- /dev/null +++ b/tests/unit/drivers/MySqlDriver.ts @@ -0,0 +1,2884 @@ +/** + * @athenna/database + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Config } from '@athenna/config' +import { Collection, Exec, Path } from '@athenna/common' +import { MySqlDriver } from '#src/drivers/MySqlDriver' +import { DriverFactory } from '#src/factories/DriverFactory' +import { WrongMethodException } from '#src/exceptions/WrongMethodException' +import { NotFoundDataException } from '#src/exceptions/NotFoundDataException' +import { Test, Mock, AfterEach, BeforeEach, type Context, Cleanup, Skip } from '@athenna/test' +import { NotConnectedDatabaseException } from '#src/exceptions/NotConnectedDatabaseException' + +export default class MySqlDriverTest { + public driver = new MySqlDriver('mysql-docker') + + @BeforeEach() + public async beforeEach() { + await Config.loadAll(Path.fixtures('config')) + this.driver.connect() + await this.driver.dropDatabase('trx') + await this.driver.dropTable('trx') + await this.driver.dropTable('rents') + await this.driver.dropTable('students_courses') + await this.driver.dropTable('students') + await this.driver.dropTable('courses') + await this.driver.dropTable('product_details') + await this.driver.dropTable('products') + await this.driver.dropTable('users') + await this.driver.dropTable('orders') + await this.driver.dropTable('migrations') + await this.driver.dropTable('migrations_lock') + + await this.driver.createTable('orders', builder => { + builder.string('id').primary() + }) + + await this.driver.createTable('products', builder => { + builder.string('id').primary() + builder.integer('quantity').defaultTo(0) + }) + + await this.driver.createTable('users', builder => { + builder.string('id').primary() + builder.string('name') + builder.timestamp('created_at').defaultTo(this.driver.getClient().fn.now()) + }) + + await this.driver.createTable('rents', builder => { + builder.string('id').primary() + builder.string('user_id').references('id').inTable('users') + }) + } + + @AfterEach() + public async afterEach() { + Mock.restoreAll() + + await this.driver.close() + + Config.clear() + } + + @Test() + public async shouldBeAbleToCloneTheDriverInstance({ assert }: Context) { + const result = this.driver.clone() + + assert.notDeepEqual(result, this.driver) + assert.instanceOf(result, MySqlDriver) + } + + @Test() + public async shouldBeAbleToGetTheClientOfTheDriver({ assert }: Context) { + const result = this.driver.getClient() + + assert.isDefined(result.select) + assert.isDefined(result.where) + } + + @Test() + public async shouldBeAbleToGetTheQueryBuilderOfTheDriver({ assert }: Context) { + const result = this.driver.getQueryBuilder() + + assert.isDefined(result.select) + assert.isDefined(result.where) + } + + @Test() + public async shouldBeAbleToSetDifferentQueryBuilderToDriver({ assert }: Context) { + const query: any = {} + + this.driver.setQueryBuilder(query) + + assert.deepEqual(this.driver.getQueryBuilder(), query) + } + + @Test() + @Cleanup(() => DriverFactory.closeConnection('mysql-docker')) + public async shouldBeAbleToConnectToDatabaseUsingMySqlDriver({ assert }: Context) { + const driver = new MySqlDriver('mysql-docker') + + assert.isFalse(driver.isConnected) + + driver.connect() + + assert.isTrue(driver.isConnected) + } + + @Test() + public async shouldBeAbleToConnectToDatabaseWithoutSavingConnectionInFactory({ assert }: Context) { + await DriverFactory.closeAllConnections() + + const driver = new MySqlDriver('mysql-docker') + + assert.isFalse(driver.isConnected) + + driver.connect({ saveOnFactory: false }) + + assert.isTrue(driver.isConnected) + assert.isFalse(DriverFactory.availableDrivers({ onlyConnected: true }).includes('mysql')) + } + + @Test() + public async shouldBeAbleToCallConnectMethodButWithoutConnectingToDatabase({ assert }: Context) { + await DriverFactory.closeAllConnections() + + const driver = new MySqlDriver('mysql-docker') + + assert.isFalse(driver.isConnected) + + driver.connect({ connect: false }) + + assert.isFalse(driver.isConnected) + } + + @Test() + public async shouldNotReconnectToDatabaseIfIsAlreadyConnected({ assert }: Context) { + Mock.spy(DriverFactory, 'createConnection') + + await DriverFactory.closeAllConnections() + + const driver = new MySqlDriver('mysql-docker') + + assert.isFalse(driver.isConnected) + + driver.connect() + assert.isTrue(driver.isConnected) + + driver.connect() + + assert.calledOnce(DriverFactory.createConnection) + } + + @Test() + @Cleanup(() => DriverFactory.closeAllConnections()) + public async shouldReconnectToDatabaseEvenIfIsAlreadyConnectedWhenForceIsSet({ assert }: Context) { + Mock.spy(DriverFactory, 'createConnection') + + await DriverFactory.closeAllConnections() + + const driver = new MySqlDriver('mysql-docker') + + assert.isFalse(driver.isConnected) + + driver.connect() + assert.isTrue(driver.isConnected) + + driver.connect({ force: true }) + + assert.calledTimes(DriverFactory.createConnection, 2) + } + + @Test() + @Cleanup(() => DriverFactory.closeAllConnections()) + public async shouldBeAbleToCloseTheConnectionWithDriver({ assert }: Context) { + Mock.spy(DriverFactory, 'closeConnection') + + await DriverFactory.closeAllConnections() + + const driver = new MySqlDriver('mysql-docker') + + assert.isFalse(driver.isConnected) + + driver.connect() + driver.close() + + assert.calledOnce(DriverFactory.closeConnection) + } + + @Test() + public async shouldNotTryToCloseConnectionWithDriverIfConnectionIsClosed({ assert }: Context) { + Mock.spy(DriverFactory, 'closeConnection') + + await DriverFactory.closeAllConnections() + + const driver = new MySqlDriver('mysql-docker') + + assert.isFalse(driver.isConnected) + + driver.close() + assert.notCalled(DriverFactory.closeConnection) + } + + @Test() + public async shouldBeAbleToCloseConnectionsThatAreNotSavedInTheDriverFactory({ assert }: Context) { + await DriverFactory.closeAllConnections() + + const driver = new MySqlDriver('mysql-docker') + + assert.isFalse(driver.isConnected) + + driver.connect({ saveOnFactory: false }) + Mock.spy(driver.getClient(), 'destroy') + + driver.close() + + assert.calledOnce(driver.getClient().destroy) + } + + @Test() + public async shouldBeAbleToCreateQueryUsingDriverQueryBuilder({ assert }: Context) { + const query = this.driver.query() + + assert.isDefined(query) + assert.isDefined(query.select) + assert.isDefined(query.where) + } + + @Test() + public async shouldThrowNotConnectedDatabaseExceptionIfTryingToCreateQueryWithConnectionClosed({ assert }: Context) { + await this.driver.close() + + assert.throws(() => this.driver.query(), NotConnectedDatabaseException) + } + + @Test() + public async shouldBeAbleToCreateAndRollbackDatabaseTransactionsFromDriver({ assert }: Context) { + const trx = await this.driver.startTransaction() + const query = trx.table('users') + const data = await query.create({ id: '1', name: 'Lenon' }) + + assert.deepEqual(data, await query.where('id', '1').find()) + + await trx.rollbackTransaction() + + assert.isUndefined(await this.driver.table('users').where('id', '1').find()) + } + + @Test() + public async shouldBeAbleToCreateAndCommitDatabaseTransactionsFromDriver({ assert }: Context) { + const trx = await this.driver.startTransaction() + const query = trx.table('users') + const data = await query.create({ id: '1', name: 'Lenon' }) + + assert.deepEqual(data, await query.where('id', '1').find()) + + await trx.commitTransaction() + + assert.isDefined(await this.driver.table('users').where('id', '1').find()) + } + + @Test() + public async shouldBeAbleToRunMigrationsUsingDriver({ assert }: Context) { + await this.driver.dropTable('rents') + await this.driver.dropTable('products') + await this.driver.dropTable('users') + + Mock.when(Path, 'migrations').return(Path.fixtures('migrations')) + + await this.driver.runMigrations() + + assert.isTrue(await this.driver.hasTable('users')) + } + + @Test() + public async shouldBeAbleToRollbackMigrationsUsingDriver({ assert }: Context) { + await this.driver.dropTable('rents') + await this.driver.dropTable('products') + await this.driver.dropTable('users') + + Mock.when(Path, 'migrations').return(Path.fixtures('migrations')) + + await this.driver.runMigrations() + + assert.isTrue(await this.driver.hasTable('users')) + + await this.driver.revertMigrations() + + assert.isFalse(await this.driver.hasTable('users')) + } + + @Test() + public async shouldBeAbleToGetTheDatabasesOfDriver({ assert }: Context) { + const databases = await this.driver.getDatabases() + + assert.deepEqual(databases, ['athenna', 'information_schema', 'mysql', 'performance_schema', 'sys']) + } + + @Test() + public async shouldBeAbleToGetTheCurrentDatabaseNameThatIsBeingUsed({ assert }: Context) { + const database = await this.driver.getCurrentDatabase() + + assert.deepEqual(database, 'athenna') + } + + @Test() + public async shouldBeAbleToValidateThatDatabaseExists({ assert }: Context) { + const exists = await this.driver.hasDatabase('athenna') + + assert.isTrue(exists) + } + + @Test() + public async shouldBeAbleToValidateThatDatabaseDoesNotExist({ assert }: Context) { + const exists = await this.driver.hasDatabase('not-found') + + assert.isFalse(exists) + } + + @Test() + public async shouldBeAbleToCreateDatabaseUsingDriver({ assert }: Context) { + await this.driver.createDatabase('trx') + + assert.isTrue(await this.driver.hasDatabase('trx')) + } + + @Test() + public async shouldNotThrowErrorsWhenCreatingADatabaseThatAlreadyExists({ assert }: Context) { + await this.driver.createDatabase('trx') + + await assert.doesNotRejects(() => this.driver.createDatabase('trx')) + assert.isTrue(await this.driver.hasDatabase('trx')) + } + + @Test() + public async shouldBeAbleToDropDatabaseUsingDriver({ assert }: Context) { + await this.driver.createDatabase('trx') + await this.driver.dropDatabase('trx') + + assert.isFalse(await this.driver.hasDatabase('trx')) + } + + @Test() + public async shouldNotThrowErrorsWhenDroppingADatabaseThatDoesNotExists({ assert }: Context) { + await this.driver.dropDatabase('trx') + + await assert.doesNotRejects(() => this.driver.dropDatabase('trx')) + assert.isFalse(await this.driver.hasDatabase('trx')) + } + + @Test() + public async shouldBeAbleToGetAllTheTablesFromDatabase({ assert }: Context) { + const tables = await this.driver.getTables() + + assert.isTrue(tables.includes('orders')) + assert.isTrue(tables.includes('products')) + assert.isTrue(tables.includes('users')) + } + + @Test() + public async shouldBeAbleToGetAllTablesEvenRecentlyCreatedFromDatabase({ assert }: Context) { + await this.driver.createTable('migrations', builder => { + builder.string('id').primary() + }) + + const tables = await this.driver.getTables() + + assert.isTrue(tables.includes('migrations')) + assert.isTrue(tables.includes('products')) + assert.isTrue(tables.includes('orders')) + assert.isTrue(tables.includes('users')) + } + + @Test() + public async shouldBeAbleToValidateThatATableExists({ assert }: Context) { + await this.driver.createTable('migrations', builder => { + builder.string('id').primary() + }) + + const exists = await this.driver.hasTable('migrations') + + assert.isTrue(exists) + } + + @Test() + public async shouldBeAbleToValidateThatATableDoesNotExists({ assert }: Context) { + const exists = await this.driver.hasTable('migrations') + + assert.isFalse(exists) + } + + @Test() + public async shouldBeAbleToCreateTablesUsingDriver({ assert }: Context) { + const exists = await this.driver.hasTable('orders') + + assert.isTrue(exists) + } + + @Test() + public async shouldBeAbleToDropTablesUsingDriver({ assert }: Context) { + assert.isTrue(await this.driver.hasTable('orders')) + + await this.driver.dropTable('orders') + + assert.isFalse(await this.driver.hasTable('orders')) + } + + @Test() + public async shouldBeAbleToTruncateTheTableLeavingItClean({ assert }: Context) { + const data = [{ id: '1' }, { id: '2' }, { id: '3' }] + + await this.driver.table('orders').createMany(data) + const orders = await this.driver.table('orders').findMany() + + assert.deepEqual(orders, data) + + await this.driver.truncate('orders') + + assert.deepEqual(await this.driver.table('orders').findMany(), []) + } + + @Test() + public async shouldBeAbleToExecuteRawSQLQueriesWithDriver({ assert }: Context) { + const data = [{ id: '1' }, { id: '2' }, { id: '3' }] + + await this.driver.table('orders').createMany(data) + const result = await this.driver.raw('SELECT * FROM orders') + + assert.deepEqual(data, result[0]) + } + + @Test() + public async shouldBeAbleToGetTheAvgOfAGivenColumnWhenTableGotValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 10 } + ]) + + const result = await this.driver.table('products').avg('quantity') + + assert.equal(result, 10) + } + + @Test() + public async shouldReturnNullWhenAvgCantFindAnyValue({ assert }: Context) { + const result = await this.driver.table('products').avg('quantity') + + assert.isNull(result) + } + + @Test() + public async shouldBeAbleToGetTheAvgDistinctOfAGivenColumnWhenTableGotValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 10 } + ]) + + const result = await this.driver.table('products').avgDistinct('quantity') + + assert.equal(result, 10) + } + + @Test() + public async shouldReturnNullWhenAvgDistinctCantFindAnyValue({ assert }: Context) { + const result = await this.driver.table('products').avgDistinct('quantity') + + assert.isNull(result) + } + + @Test() + public async shouldBeAbleToGetTheMaxNumberOfAGivenColumnWhenTableGotValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 20 } + ]) + + const result = await this.driver.table('products').max('quantity') + + assert.equal(result, 20) + } + + @Test() + public async shouldReturnNullWhenMaxCantFindAnyValue({ assert }: Context) { + const result = await this.driver.table('products').max('quantity') + + assert.isNull(result) + } + + @Test() + public async shouldBeAbleToGetTheMinNumberOfAGivenColumnWhenTableGotValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 20 } + ]) + + const result = await this.driver.table('products').min('quantity') + + assert.equal(result, 10) + } + + @Test() + public async shouldReturnNullWhenMinCantFindAnyValue({ assert }: Context) { + const result = await this.driver.table('products').min('quantity') + + assert.isNull(result) + } + + @Test() + public async shouldBeAbleToSumTheNumberOfAGivenColumnWhenTableGotValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 10 } + ]) + + const result = await this.driver.table('products').sum('quantity') + + assert.equal(result, 20) + } + + @Test() + public async shouldReturnNullWhenSumCantFindAnyValue({ assert }: Context) { + const result = await this.driver.table('products').sum('quantity') + + assert.isNull(result) + } + + @Test() + public async shouldBeAbleToGetTheSumDistinctOfAGivenColumnWhenTableGotValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 10 } + ]) + + const result = await this.driver.table('products').sumDistinct('quantity') + + assert.equal(result, 10) + } + + @Test() + public async shouldReturnNullWhenSumDistinctCantFindAnyValue({ assert }: Context) { + const result = await this.driver.table('products').sumDistinct('quantity') + + assert.isNull(result) + } + + @Test() + public async shouldBeAbleToIncrementTheNumberOfAGivenColumnWhenTableGotValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 10 } + ]) + + await this.driver.table('products').increment('quantity') + const avg = await this.driver.table('products').avg('quantity') + + assert.equal(avg, 11) + } + + @Test() + public async shouldBeAbleToDecrementTheNumberOfAGivenColumnWhenTableGotValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 10 } + ]) + + await this.driver.table('products').decrement('quantity') + const avg = await this.driver.table('products').avg('quantity') + + assert.equal(avg, 9) + } + + @Test() + public async shouldBeAbleToCountRecords({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 10 } + ]) + + const result = await this.driver.table('products').count() + + assert.equal(result, 2) + } + + @Test() + public async shouldBeAbleToCountColumnsValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 10 } + ]) + + const result = await this.driver.table('products').count('quantity') + + assert.equal(result, '2') + } + + @Test() + public async shouldBeAbleToCountDistinctColumnsValues({ assert }: Context) { + await this.driver.table('products').createMany([ + { id: '1', quantity: 10 }, + { id: '2', quantity: 10 } + ]) + + const result = await this.driver.table('products').countDistinct('quantity') + + assert.equal(result, '1') + } + + @Test() + public async shouldBeAbleToFindDataUsingFindOrFail({ assert }: Context) { + const data = { id: '1', name: 'John Doe' } + await this.driver.table('users').create(data) + + const result = await this.driver.table('users').findOrFail() + + assert.containsSubset(result, data) + } + + @Test() + public async shouldThrowNotFoundDataExceptionWhenFindOrFailFail({ assert }: Context) { + await assert.rejects(() => this.driver.table('users').findOrFail(), NotFoundDataException) + } + + @Test() + public async shouldBeAbleToUseFindOrMethodToFindData({ assert }: Context) { + const data = { id: '1', name: 'John Doe' } + await this.driver.table('users').create(data) + + const result = await this.driver.table('users').findOr(() => { + return { id: '1', name: 'Marie Curie' } + }) + + assert.containsSubset(result, data) + } + + @Test() + public async shouldBeAbleToReturnDataFromCallbackWhenFindOrFail({ assert }: Context) { + const result = await this.driver.table('users').findOr(() => { + return { id: '1', name: 'Marie Curie' } + }) + + assert.deepEqual(result, { id: '1', name: 'Marie Curie' }) + } + + @Test() + public async shouldBeAbleToExecuteSomeClosureWhenCriteriaIsTrue({ assert }: Context) { + const data = { id: '1', name: 'Marie Curie' } + await this.driver.table('users').create(data) + + const result = await this.driver + .when(true, query => { + query.select('name') + }) + .find() + + assert.deepEqual(result, { name: 'Marie Curie' }) + } + + @Test() + public async shouldNotExecuteSomeClosureWhenCriteriaIsFalse({ assert }: Context) { + const data = { id: '1', name: 'Marie Curie' } + await this.driver.table('users').create(data) + + const result = await this.driver + .when(false, query => { + query.select('*') + }) + .find() + + assert.containsSubset(result, { id: '1', name: 'Marie Curie' }) + } + + @Test() + public async shouldBeAbleToFindDataUsingDriver({ assert }: Context) { + const data = { id: '1', name: 'Charles Babbage' } + await this.driver.table('users').create(data) + + const result = await this.driver.table('users').find() + + assert.containsSubset(result, data) + } + + @Test() + public async shouldReturnUndefinedWhenFindMethodCantFindNothing({ assert }: Context) { + const result = await this.driver.table('users').find() + + assert.isUndefined(result) + } + + @Test() + public async shouldBeAbleToFindManyDataUsingDriver({ assert }: Context) { + const data = [{ id: '1', name: 'Charles Babbage' }] + await this.driver.table('users').createMany(data) + + const result = await this.driver.table('users').findMany() + + assert.containsSubset(result, data) + } + + @Test() + public async shouldReturnEmptyArrayWhenFindManyMethodCantFindNothing({ assert }: Context) { + const result = await this.driver.table('users').findMany() + + assert.isEmpty(result) + } + + @Test() + public async shouldBeAbleToFindManyDataAndReturnAsCollectionUsingDriver({ assert }: Context) { + const data = [{ id: '1', name: 'Alan Turing' }] + await this.driver.table('users').createMany(data) + + const result = await this.driver.table('users').findMany() + + assert.containsSubset(result, data) + } + + @Test() + public async shouldReturnEmptyCollectionWhenCollectionMethodCantFindNothing({ assert }: Context) { + const result = await this.driver.table('users').collection() + + assert.instanceOf(result, Collection) + } + + @Test() + public async shouldBeAbleToFindManyDataAndReturnPaginatedUsingDriver({ assert }: Context) { + const data = [{ id: '1', name: 'Alan Turing' }] + await this.driver.table('users').createMany(data) + + const result = await this.driver.table('users').paginate() + + assert.containsSubset(result.data, data) + assert.deepEqual(result.meta, { + currentPage: 0, + itemCount: 1, + itemsPerPage: 10, + totalItems: 1, + totalPages: 1 + }) + assert.deepEqual(result.links, { + first: '/?limit=10', + last: '/?page=1&limit=10', + next: '/?page=1&limit=10', + previous: '/?page=0&limit=10' + }) + } + + @Test() + public async shouldBeAbleToSetDifferentUrlToFindManyDataAndReturnPaginatedUsingDriver({ assert }: Context) { + const data = [{ id: '1', name: 'Alan Turing' }] + await this.driver.table('users').createMany(data) + + const result = await this.driver.table('users').paginate(0, 10, '/users') + + assert.containsSubset(result.data, data) + assert.deepEqual(result.meta, { + currentPage: 0, + itemCount: 1, + itemsPerPage: 10, + totalItems: 1, + totalPages: 1 + }) + assert.deepEqual(result.links, { + first: '/users?limit=10', + last: '/users?page=1&limit=10', + next: '/users?page=1&limit=10', + previous: '/users?page=0&limit=10' + }) + } + + @Test() + public async shouldReturnEmptyDataWhenPaginateMethodCantFindNothing({ assert }: Context) { + const result = await this.driver.table('users').paginate() + + assert.isEmpty(result.data) + } + + @Test() + public async shouldBeAbleToCreateDataUsingDriver({ assert }: Context) { + const data = { id: '1', name: 'Robert Kiyosaki' } + + const result = await this.driver.table('users').create(data) + + assert.containsSubset(result, data) + } + + @Test() + public async shouldThrowWrongMethodExceptionWhenCallingCreateWithObject({ assert }: Context) { + await assert.rejects( + () => this.driver.table('users').create([{ id: '1', name: 'Robert Kiyosaki' }] as any), + WrongMethodException + ) + } + + @Test() + public async shouldBeAbleToCreateManyDataUsingDriver({ assert }: Context) { + const data = [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ] + + const result = await this.driver.table('users').createMany(data) + + assert.containsSubset(result, data) + } + + @Test() + public async shouldThrowWrongMethodExceptionWhenCallingCreateManyWithObject({ assert }: Context) { + await assert.rejects( + () => this.driver.table('users').createMany({ id: '1', name: 'Robert Kiyosaki' } as any), + WrongMethodException + ) + } + + @Test() + public async shouldBeAbleToCreateDataUsingCreateOrUpdateMethod({ assert }: Context) { + const data = { id: '1', name: 'Robert Kiyosaki' } + + const result = await this.driver.table('users').createOrUpdate(data) + + assert.containsSubset(result, data) + } + + @Test() + public async shouldBeAbleToUpdateDataUsingCreateOrUpdateMethod({ assert }: Context) { + const data = { id: '1', name: 'Robert Kiyosaki' } + + await this.driver.table('users').create(data) + const result = await this.driver.table('users').createOrUpdate({ ...data, name: 'Robert Kiyosaki Millennials' }) + + assert.containsSubset(result, { ...data, name: 'Robert Kiyosaki Millennials' }) + } + + @Test() + public async shouldBeAbleToUpdateSingleDataAndReturnSingleObject({ assert }: Context) { + const data = { id: '1', name: 'Robert Kiyosaki' } + + await this.driver.table('users').create(data) + const result = await this.driver.table('users').update({ ...data, name: 'Robert Kiyosaki Millennials' }) + + assert.containsSubset(result, { ...data, name: 'Robert Kiyosaki Millennials' }) + } + + @Test() + public async shouldBeAbleToUpdateMultipleDataAndReturnAnArrayOfObjects({ assert }: Context) { + const data = [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ] + + await this.driver.table('users').createMany(data) + const result = await this.driver + .table('users') + .whereIn('id', ['1', '2']) + .update({ name: 'Robert Kiyosaki Millennials' }) + + assert.containsSubset(result, [ + { id: '1', name: 'Robert Kiyosaki Millennials' }, + { id: '2', name: 'Robert Kiyosaki Millennials' } + ]) + } + + @Test() + public async shouldBeAbleToDeleteDataUsingDeleteMethod({ assert }: Context) { + const data = { id: '1', name: 'Robert Kiyosaki' } + + await this.driver.table('users').create(data) + await this.driver.table('users').where('id', '1').delete() + + assert.isUndefined(await this.driver.table('users').where('id', '1').find()) + } + + @Test() + public async shouldBeAbleToChangeInWhichTableTheDriverWillPerformTheOperations({ assert }: Context) { + const data = { id: '1', name: 'Robert Kiyosaki' } + + await this.driver.table('users').create(data) + + assert.deepEqual(await this.driver.table('users').count(), '1') + } + + @Test() + public async shouldThrowNotConnectedDatabaseExceptionWhenTryingToChangeTable({ assert }: Context) { + await this.driver.close() + + await assert.rejects(() => this.driver.table('users'), NotConnectedDatabaseException) + } + + @Test() + public async shouldBeAbleToDumpTheSQLQuery({ assert }: Context) { + Mock.when(console, 'log').return(undefined) + + this.driver.table('users').select('*').dump() + + assert.calledWith(console.log, { bindings: [], sql: 'select * from `users`' }) + } + + @Test() + public async shouldAllowSelectingSpecificColumnsFromTable({ assert }: Context) { + await this.driver.table('users').create({ id: '1', name: 'Alan Turing' }) + + const data = await this.driver.table('users').select('name').where('id', '1').findMany() + + assert.deepEqual(data, [{ name: 'Alan Turing' }]) + } + + @Test() + public async shouldAllowSelectingAllColumnsFromTable({ assert }: Context) { + await this.driver.table('users').create({ id: '1', name: 'Alan Turing' }) + + const data = await this.driver.table('users').select('*').where('id', '1').findMany() + + assert.containsSubset(data, [{ id: '1', name: 'Alan Turing' }]) + } + + @Test() + public async shouldAllowRawSqlSelectionForSpecializedQueries({ assert }: Context) { + await this.driver.table('users').create({ id: '1', name: 'Alan Turing' }) + + const data = await this.driver.table('users').selectRaw('COUNT(*) as user_count').find() + + assert.deepEqual(data, { user_count: 1 }) + } + + @Test() + public async shouldAllowSelectingAllColumnsFromTableUsingFrom({ assert }: Context) { + await this.driver.table('users').create({ id: '1', name: 'Alan Turing' }) + + const data = await this.driver.select('*').from('users').where('id', '1').findMany() + + assert.containsSubset(data, [{ id: '1', name: 'Alan Turing' }]) + } + + @Test() + public async shouldAllowRawSqlSelectionForSpecializedQueriesUsingFromRaw({ assert }: Context) { + await this.driver.table('users').create({ id: '1', name: 'Alan Turing' }) + + const data = await this.driver.selectRaw('COUNT(*) as user_count').fromRaw('users').find() + + assert.deepEqual(data, { user_count: 1 }) + } + + @Test() + public async shouldBeAbleToJoinAnotherTableBasedOnSpecifiedColumns({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .join('rents', 'users.id', 'rents.user_id') + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldBeAbleToJoinAnotherTableBasedOnSpecifiedColumnsAndOperation({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .join('rents', 'users.id', '=', 'rents.user_id') + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldBeAbleToJoinAnotherTableBasedOnSpecifiedFunction({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .join('rents', function () { + this.on('users.id', '=', 'rents.user_id') + }) + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldBeAbleToLeftJoinAnotherTableBasedOnSpecifiedColumnsAndOperation({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .leftJoin('rents', 'users.id', '=', 'rents.user_id') + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldBeAbleToRightJoinAnotherTableBasedOnSpecifiedColumnsAndOperation({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .rightJoin('rents', 'users.id', '=', 'rents.user_id') + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldBeAbleToCrossJoinAnotherTableBasedOnSpecifiedColumnsAndOperation({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .crossJoin('rents') + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldBeAbleToFullOuterJoinAnotherTableBasedOnSpecifiedColumnsAndOperation({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .fullOuterJoin('rents', 'users.id', '=', 'rents.user_id') + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldBeAbleToLeftOuterJoinAnotherTableBasedOnSpecifiedColumnsAndOperation({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .leftOuterJoin('rents', 'users.id', '=', 'rents.user_id') + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldBeAbleToRightOuterJoinAnotherTableBasedOnSpecifiedColumnsAndOperation({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .rightOuterJoin('rents', 'users.id', '=', 'rents.user_id') + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldApplyJoinRawForGivenTableAndConditions({ assert }: Context) { + await this.driver.table('users').createMany([{ id: '1', name: 'Robert Kiyosaki' }]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' } + ]) + + const data = await this.driver + .table('users') + .select('users.id as user_id') + .select('users.name as user_name') + .select('rents.id as rent_id') + .joinRaw('INNER JOIN rents') + .findMany() + + assert.deepEqual(data, [ + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '1' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '2' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '3' }, + { user_id: '1', user_name: 'Robert Kiyosaki', rent_id: '4' } + ]) + } + + @Test() + public async shouldBeAbleToGroupBySpecifiedColumnsUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('rents').select('user_id').groupBy('user_id').findMany() + + assert.deepEqual(data, [{ user_id: '1' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToGroupByRawSpecifiedColumnsUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('rents').select('user_id').groupByRaw('user_id ORDER BY user_id').findMany() + + assert.deepEqual(data, [{ user_id: '1' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAHavingClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .groupBy('user_id') + .having('user_id', '<=', '2') + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '1' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAHavingClauseWithDefaultEqualOpToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .groupBy('user_id') + .having('user_id', '2') + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAHavingClauseAsRawToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .groupBy('user_id') + .having(this.driver.raw("user_id <= '2'")) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '1' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAHavingRawClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .groupBy('user_id') + .havingRaw("user_id <= '2' ORDER BY user_id") + .findMany() + + assert.deepEqual(data, [{ user_id: '1' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAHavingExistsClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .groupBy('id', 'user_id') + .havingExists(query => { + query.select(query.raw('1')).from('users').whereRaw('users.id = rents.user_id') + }) + .orderBy('id') + .findMany() + + assert.deepEqual(data, [ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + } + + @Test() + public async shouldBeAbleToAddAHavingNotExistsClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .groupBy('id', 'name') + .havingNotExists(query => { + query.select(query.raw('1')).from('rents').whereRaw('users.id = rents.user_id') + }) + .findMany() + + assert.deepEqual(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAHavingInClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .groupBy('id', 'name') + .havingIn('name', ['Alan Turing']) + .findMany() + + assert.deepEqual(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAHavingNotInClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').groupBy('id', 'name').havingNotIn('id', ['1', '2']).findMany() + + assert.containsSubset(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAHavingBetweenClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .groupBy('id', 'name') + .havingBetween('created_at', [new Date('12/09/2001'), new Date('12/09/2050')]) + .findMany() + + assert.containsSubset(data, [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + } + + @Test() + public async shouldBeAbleToAddAHavingNotBetweenClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .groupBy('id', 'name') + .havingNotBetween('created_at', [new Date('12/09/2001'), new Date('12/09/2050')]) + .findMany() + + assert.isEmpty(data) + } + + @Test() + public async shouldBeAbleToAddAHavingNullClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' }, + { id: '4', name: null } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .groupBy('id', 'name') + .havingNull('name') + .findMany() + + assert.deepEqual(data, [{ id: '4', name: null }]) + } + + @Test() + public async shouldBeAbleToAddAHavingNotNullClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' }, + { id: '4', name: null } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .groupBy('id', 'name') + .havingNotNull('name') + .orderBy('id') + .findMany() + + assert.deepEqual(data, [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .groupBy('user_id') + .orHaving('user_id', '<=', '2') + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '1' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingClauseWithDefaultEqualOpToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .groupBy('user_id') + .orHaving('user_id', '2') + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingClauseAsRawToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .groupBy('user_id') + .orHaving(this.driver.raw("user_id <= '2'")) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '1' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingRawClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .groupBy('user_id') + .orHavingRaw("user_id <= '2' ORDER BY user_id") + .findMany() + + assert.deepEqual(data, [{ user_id: '1' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingExistsClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .groupBy('id', 'user_id') + .orHavingExists(query => { + query.select(query.raw('1')).from('users').whereRaw('users.id = rents.user_id') + }) + .orderBy('id') + .findMany() + + assert.deepEqual(data, [ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingNotExistsClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .groupBy('id', 'name') + .orHavingNotExists(query => { + query.select(query.raw('1')).from('rents').whereRaw('users.id = rents.user_id') + }) + .findMany() + + assert.deepEqual(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingInClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .groupBy('id', 'name') + .orHavingIn('name', ['Alan Turing']) + .findMany() + + assert.deepEqual(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingNotInClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').groupBy('id', 'name').orHavingNotIn('id', ['1', '2']).findMany() + + assert.containsSubset(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingBetweenClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .groupBy('id', 'name') + .orHavingBetween('created_at', [new Date('12/09/2001'), new Date('12/09/2050')]) + .findMany() + + assert.containsSubset(data, [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingNotBetweenClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .groupBy('id', 'name') + .orHavingNotBetween('created_at', [new Date('12/09/2001'), new Date('12/09/2050')]) + .findMany() + + assert.isEmpty(data) + } + + @Test() + public async shouldBeAbleToAddAOrHavingNullClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' }, + { id: '4', name: null } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .groupBy('id', 'name') + .orHavingNull('name') + .findMany() + + assert.deepEqual(data, [{ id: '4', name: null }]) + } + + @Test() + public async shouldBeAbleToAddAOrHavingNotNullClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' }, + { id: '4', name: null } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .groupBy('id', 'name') + .orHavingNotNull('name') + .orderBy('id') + .findMany() + + assert.deepEqual(data, [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + } + + @Test() + public async shouldBeAbleToAddAWhereClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .where('user_id', '=', '2') + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereClauseWithDefaultEqualOpToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('rents').select('user_id').where('user_id', '2').orderBy('user_id').findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereClauseAsRawToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .where(this.driver.raw("user_id = '2'")) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereClauseAsClosureToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .where(query => { + query.whereIn('user_id', [2]) + }) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereRawClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('rents').select('user_id').whereRaw("user_id = '2'").findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereNotClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .whereNot('user_id', '1') + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereNotClauseAsRawToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .whereNot(this.driver.raw("user_id = '1'")) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereNotClauseAsFunctionToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .whereNot(query => { + query.whereIn('user_id', ['1']) + }) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + @Skip("COLLATION 'utf8_bin' is not valid for CHARACTER SET 'utf8mb4'") + public async shouldBeAbleToAddAWhereLikeClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .whereLike('name', '%Warren Buffet%') + .orderBy('id') + .findMany() + + assert.deepEqual(data, [{ id: '2', name: 'Warren Buffet' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereILikeClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .whereILike('name', '%Warren Buffet%') + .orderBy('id') + .findMany() + + assert.deepEqual(data, [{ id: '2', name: 'Warren Buffet' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereExistsClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .whereExists(query => { + query.select(query.raw('1')).from('users').whereRaw('users.id = rents.user_id') + }) + .findMany() + + assert.deepEqual(data, [ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + } + + @Test() + public async shouldBeAbleToAddAWhereNotExistsClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .whereNotExists(query => { + query.select(query.raw('1')).from('rents').whereRaw('users.id = rents.user_id') + }) + .findMany() + + assert.deepEqual(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereInClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').select('id', 'name').whereIn('name', ['Alan Turing']).findMany() + + assert.deepEqual(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereNotInClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').whereNotIn('id', ['1', '2']).findMany() + + assert.containsSubset(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAWhereBetweenClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .whereBetween('created_at', [new Date('12/09/2001'), new Date('12/09/2050')]) + .findMany() + + assert.containsSubset(data, [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + } + + @Test() + public async shouldBeAbleToAddAWhereNotBetweenClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .whereNotBetween('created_at', [new Date('12/09/2001'), new Date('12/09/2050')]) + .findMany() + + assert.isEmpty(data) + } + + @Test() + public async shouldBeAbleToAddAWhereNullClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' }, + { id: '4', name: null } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').select('id', 'name').whereNull('name').findMany() + + assert.deepEqual(data, [{ id: '4', name: null }]) + } + + @Test() + public async shouldBeAbleToAddAWhereNotNullClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' }, + { id: '4', name: null } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').select('id', 'name').whereNotNull('name').orderBy('id').findMany() + + assert.deepEqual(data, [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .orWhere('user_id', '=', '2') + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereClauseWithDefaultEqualOpToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .orWhere('user_id', '2') + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereClauseAsRawToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .orWhere(this.driver.raw("user_id = '2'")) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereClauseAsClosureToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .orWhere(query => { + query.whereIn('user_id', [2]) + }) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereRawClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .orWhereRaw("user_id = '2' ORDER BY user_id") + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereNotClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .orWhereNot('user_id', '1') + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereNotClauseAsRawToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .orWhereNot(this.driver.raw("user_id = '1'")) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereNotClauseAsFunctionToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .select('user_id') + .orWhereNot(query => { + query.whereIn('user_id', ['1']) + }) + .orderBy('user_id') + .findMany() + + assert.deepEqual(data, [{ user_id: '2' }, { user_id: '2' }]) + } + + @Test() + @Skip("COLLATION 'utf8_bin' is not valid for CHARACTER SET 'utf8mb4'") + public async shouldBeAbleToAddAOrWhereLikeClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .orWhereLike('name', '%Warren Buffet%') + .orderBy('id') + .findMany() + + assert.deepEqual(data, [{ id: '2', name: 'Warren Buffet' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereILikeClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .orWhereILike('name', '%Warren Buffet%') + .orderBy('id') + .findMany() + + assert.deepEqual(data, [{ id: '2', name: 'Warren Buffet' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereExistsClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('rents') + .orWhereExists(query => { + query.select(query.raw('1')).from('users').whereRaw('users.id = rents.user_id') + }) + .findMany() + + assert.deepEqual(data, [ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereNotExistsClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .select('id', 'name') + .orWhereNotExists(query => { + query.select(query.raw('1')).from('rents').whereRaw('users.id = rents.user_id') + }) + .findMany() + + assert.deepEqual(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereInClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').select('id', 'name').orWhereIn('name', ['Alan Turing']).findMany() + + assert.deepEqual(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereNotInClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').orWhereNotIn('id', ['1', '2']).findMany() + + assert.containsSubset(data, [{ id: '3', name: 'Alan Turing' }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereBetweenClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .orWhereBetween('created_at', [new Date('12/09/2001'), new Date('12/09/2050')]) + .findMany() + + assert.containsSubset(data, [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereNotBetweenClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver + .table('users') + .orWhereNotBetween('created_at', [new Date('12/09/2001'), new Date('12/09/2050')]) + .findMany() + + assert.isEmpty(data) + } + + @Test() + public async shouldBeAbleToAddAOrWhereNullClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' }, + { id: '4', name: null } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').select('id', 'name').orWhereNull('name').findMany() + + assert.deepEqual(data, [{ id: '4', name: null }]) + } + + @Test() + public async shouldBeAbleToAddAOrWhereNotNullClauseToTheQueryUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' }, + { id: '4', name: null } + ]) + await this.driver.table('rents').createMany([ + { id: '1', user_id: '1' }, + { id: '2', user_id: '1' }, + { id: '3', user_id: '1' }, + { id: '4', user_id: '1' }, + { id: '5', user_id: '2' }, + { id: '6', user_id: '2' } + ]) + + const data = await this.driver.table('users').select('id', 'name').orWhereNotNull('name').orderBy('id').findMany() + + assert.deepEqual(data, [ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + } + + @Test() + public async shouldOrderBySpecifiedColumnInASCUpperCaseDirectionUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + + const data = await this.driver.select('name').orderBy('name', 'ASC').findMany() + + assert.deepEqual(data, [{ name: 'Alan Turing' }, { name: 'Robert Kiyosaki' }, { name: 'Warren Buffet' }]) + } + + @Test() + public async shouldOrderBySpecifiedColumnInASCLowerCaseDirectionUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + + const data = await this.driver.select('name').orderBy('name', 'asc').findMany() + + assert.deepEqual(data, [{ name: 'Alan Turing' }, { name: 'Robert Kiyosaki' }, { name: 'Warren Buffet' }]) + } + + @Test() + public async shouldOrderBySpecifiedColumnInDESCUpperCaseDirectionUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + + const data = await this.driver.select('name').orderBy('name', 'DESC').findMany() + + assert.deepEqual(data, [{ name: 'Warren Buffet' }, { name: 'Robert Kiyosaki' }, { name: 'Alan Turing' }]) + } + + @Test() + public async shouldOrderBySpecifiedColumnInDESCLowerCaseDirectionUsingDriver({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + + const data = await this.driver.select('name').orderBy('name', 'desc').findMany() + + assert.deepEqual(data, [{ name: 'Warren Buffet' }, { name: 'Robert Kiyosaki' }, { name: 'Alan Turing' }]) + } + + @Test() + public async shouldOrderBySpecifiedColumnInGivenDirectionUsingRawSQL({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' }, + { id: '4', name: null } + ]) + + const data = await this.driver.table('users').select('name').orderByRaw('name DESC').findMany() + + assert.deepEqual(data, [ + { name: 'Warren Buffet' }, + { name: 'Robert Kiyosaki' }, + { name: 'Alan Turing' }, + { name: null } + ]) + } + + @Test() + public async shouldBeAbleToAutomaticallyOrderTheDataByDatesUsingLatest({ assert }: Context) { + await this.driver.table('users').create({ id: '1', name: 'Robert Kiyosaki' }) + await Exec.sleep(2000) + const latest = await this.driver.table('users').create({ id: '3', name: 'Alan Turing' }) + + const data = await this.driver.table('users').latest('created_at').find() + + assert.deepEqual(latest, data) + } + + @Test() + public async shouldBeAbleToAutomaticallyOrderTheDataByDatesUsingOldest({ assert }: Context) { + const oldest = await this.driver.table('users').create({ id: '1', name: 'Robert Kiyosaki' }) + await this.driver.table('users').create({ id: '3', name: 'Alan Turing' }) + + const data = await this.driver.table('users').oldest('created_at').find() + + assert.deepEqual(oldest, data) + } + + @Test() + public async shouldOffsetTheResultsByGivenValue({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + + const data = await this.driver.table('users').select('name').offset(1).findMany() + + assert.deepEqual(data, [{ name: 'Warren Buffet' }, { name: 'Alan Turing' }]) + } + + @Test() + public async shouldLimitTheResultsByGivenValue({ assert }: Context) { + await this.driver.table('users').createMany([ + { id: '1', name: 'Robert Kiyosaki' }, + { id: '2', name: 'Warren Buffet' }, + { id: '3', name: 'Alan Turing' } + ]) + + const data = await this.driver.table('users').select('name').limit(1).findMany() + + assert.deepEqual(data, [{ name: 'Robert Kiyosaki' }]) + } +} diff --git a/tests/unit/drivers/PostgresDriverTest.ts b/tests/unit/drivers/PostgresDriverTest.ts index abd0601..c0aa732 100644 --- a/tests/unit/drivers/PostgresDriverTest.ts +++ b/tests/unit/drivers/PostgresDriverTest.ts @@ -26,9 +26,13 @@ export default class PostgresDriverTest { await this.driver.dropDatabase('trx') await this.driver.dropTable('trx') await this.driver.dropTable('rents') + await this.driver.dropTable('students_courses') + await this.driver.dropTable('students') + await this.driver.dropTable('courses') + await this.driver.dropTable('product_details') + await this.driver.dropTable('products') await this.driver.dropTable('users') await this.driver.dropTable('orders') - await this.driver.dropTable('products') await this.driver.dropTable('migrations') await this.driver.dropTable('migrations_lock') @@ -237,57 +241,57 @@ export default class PostgresDriverTest { @Test() public async shouldBeAbleToCreateAndRollbackDatabaseTransactionsFromDriver({ assert }: Context) { const trx = await this.driver.startTransaction() + const query = trx.table('users') + const data = await query.create({ id: '1', name: 'Lenon' }) - await trx.createTable('trx', builder => { - builder.string('id').primary() - }) - - assert.isTrue(await trx.hasTable('trx')) + assert.deepEqual(data, await query.where('id', '1').find()) await trx.rollbackTransaction() - assert.isFalse(await this.driver.hasTable('trx')) + assert.isUndefined(await this.driver.table('users').where('id', '1').find()) } @Test() public async shouldBeAbleToCreateAndCommitDatabaseTransactionsFromDriver({ assert }: Context) { const trx = await this.driver.startTransaction() + const query = trx.table('users') + const data = await query.create({ id: '1', name: 'Lenon' }) - await trx.createTable('trx', builder => { - builder.string('id').primary() - }) - - assert.isTrue(await trx.hasTable('trx')) + assert.deepEqual(data, await query.where('id', '1').find()) await trx.commitTransaction() - assert.isTrue(await this.driver.hasTable('trx')) + assert.isDefined(await this.driver.table('users').where('id', '1').find()) } @Test() public async shouldBeAbleToRunMigrationsUsingDriver({ assert }: Context) { - await this.driver.dropTable('orders') + await this.driver.dropTable('rents') + await this.driver.dropTable('products') + await this.driver.dropTable('users') Mock.when(Path, 'migrations').return(Path.fixtures('migrations')) await this.driver.runMigrations() - assert.isTrue(await this.driver.hasTable('orders')) + assert.isTrue(await this.driver.hasTable('users')) } @Test() public async shouldBeAbleToRollbackMigrationsUsingDriver({ assert }: Context) { - await this.driver.dropTable('orders') + await this.driver.dropTable('rents') + await this.driver.dropTable('products') + await this.driver.dropTable('users') Mock.when(Path, 'migrations').return(Path.fixtures('migrations')) await this.driver.runMigrations() - assert.isTrue(await this.driver.hasTable('orders')) + assert.isTrue(await this.driver.hasTable('users')) await this.driver.revertMigrations() - assert.isFalse(await this.driver.hasTable('orders')) + assert.isFalse(await this.driver.hasTable('users')) } @Test() diff --git a/tests/unit/factories/DriverFactoryTest.ts b/tests/unit/factories/DriverFactoryTest.ts index ed91535..daffe54 100644 --- a/tests/unit/factories/DriverFactoryTest.ts +++ b/tests/unit/factories/DriverFactoryTest.ts @@ -40,7 +40,7 @@ export default class DriverFactoryTest { public shouldBeAbleToListAllAvailableDrivers({ assert }: Context) { const drivers = DriverFactory.availableDrivers() - assert.deepEqual(drivers, ['postgres', 'fake']) + assert.deepEqual(drivers, ['mysql', 'postgres', 'fake']) } @Test()