Skip to content

Commit

Permalink
feat: add support for better-sqlite3
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Mar 2, 2022
1 parent b991e82 commit 5d006b8
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 126 deletions.
11 changes: 9 additions & 2 deletions adonis-typings/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ declare module '@ioc:Adonis/Lucid/Database' {
* Dialect specific methods
*/
export interface DialectContract {
readonly name: 'mssql' | 'mysql' | 'oracledb' | 'postgres' | 'redshift' | 'sqlite3'
readonly name:
| 'mssql'
| 'mysql'
| 'oracledb'
| 'postgres'
| 'redshift'
| 'sqlite3'
| 'better-sqlite3'
readonly version?: string
readonly supportsAdvisoryLocks: boolean
readonly dateTimeFormat: string
Expand Down Expand Up @@ -355,7 +362,7 @@ declare module '@ioc:Adonis/Lucid/Database' {
* free to define them (let us know, in case any options are missing)
*/
export type SqliteConfig = SharedConfigNode & {
client: 'sqlite' | 'sqlite3'
client: 'sqlite' | 'sqlite3' | 'better-sqlite3'
connection: {
filename: string
flags?: string[]
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
"scripts": {
"mrm": "mrm --preset=@adonisjs/mrm-preset",
"pretest": "npm run lint",
"test:better_sqlite": "cross-env DB=better_sqlite FORCE_COLOR=true node ./.bin/test.js",
"test:sqlite": "cross-env DB=sqlite FORCE_COLOR=true node ./.bin/test.js",
"test:mysql": "cross-env DB=mysql FORCE_COLOR=true node ./.bin/test.js",
"test:mysql_legacy": "cross-env DB=mysql_legacy FORCE_COLOR=true node ./.bin/test.js",
"test:mssql": "cross-env DB=mssql FORCE_COLOR=true node ./.bin/test.js",
"test:pg": "cross-env DB=pg FORCE_COLOR=true node ./.bin/test.js",
"test:docker": "npm run test:mysql && npm run test:mysql_legacy && npm run test:pg && npm run test:mssql",
"test": "docker-compose -f docker-compose.yml -f docker-compose-test.yml build && docker-compose -f docker-compose.yml -f docker-compose-test.yml run --rm test && npm run test:sqlite",
"test": "docker-compose -f docker-compose.yml -f docker-compose-test.yml build && docker-compose -f docker-compose.yml -f docker-compose-test.yml run --rm test && npm run test:sqlite && npm run test:better_sqlite",
"lint": "eslint . --ext=.ts",
"clean": "del-cli build",
"compile": "npm run lint && npm run clean && tsc && npm run copyfiles",
Expand Down Expand Up @@ -52,7 +53,7 @@
"fast-deep-equal": "^3.1.3",
"igniculus": "^1.5.0",
"knex": "^1.0.3",
"knex-dynamic-connection": "^2.1.2",
"knex-dynamic-connection": "^2.1.3",
"luxon": "^2.3.1",
"macroable": "^6.0.1",
"pretty-hrtime": "^1.0.3",
Expand All @@ -74,6 +75,7 @@
"@types/pluralize": "0.0.29",
"@types/qs": "^6.9.7",
"@vscode/sqlite3": "^5.0.7",
"better-sqlite3": "^7.5.0",
"chance": "^1.1.8",
"commitizen": "^4.2.4",
"copyfiles": "^2.4.1",
Expand All @@ -96,7 +98,7 @@
"prettier": "^2.5.1",
"reflect-metadata": "^0.1.13",
"tedious": "^14.3.0",
"typescript": "^4.5.5"
"typescript": "^4.6.2"
},
"publishConfig": {
"tag": "latest",
Expand Down
17 changes: 17 additions & 0 deletions src/Dialects/BetterSqlite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/// <reference path="../../adonis-typings/index.ts" />

import { DialectContract } from '@ioc:Adonis/Lucid/Database'
import { BaseSqliteDialect } from './SqliteBase'

export class BetterSqliteDialect extends BaseSqliteDialect implements DialectContract {
public readonly name = 'better-sqlite3'
}
108 changes: 3 additions & 105 deletions src/Dialects/Sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,111 +9,9 @@

/// <reference path="../../adonis-typings/index.ts" />

import { DialectContract, QueryClientContract } from '@ioc:Adonis/Lucid/Database'
import { DialectContract } from '@ioc:Adonis/Lucid/Database'
import { BaseSqliteDialect } from './SqliteBase'

export class SqliteDialect implements DialectContract {
export class SqliteDialect extends BaseSqliteDialect implements DialectContract {
public readonly name = 'sqlite3'
public readonly supportsAdvisoryLocks = false

/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
public readonly version = this.client.getReadClient()['context']['client'].version

/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
public readonly dateTimeFormat = 'yyyy-MM-dd HH:mm:ss'

constructor(private client: QueryClientContract) {}

/**
* Returns an array of table names
*/
public async getAllTables() {
const tables = await this.client
.query()
.from('sqlite_master')
.select('name as table_name')
.where('type', 'table')
.whereNot('name', 'like', 'sqlite_%')
.orderBy('name', 'asc')

return tables.map(({ table_name }) => table_name)
}

/**
* Returns an array of all views names
*/
public async getAllViews(): Promise<string[]> {
const tables = await this.client
.query()
.from('sqlite_master')
.select('name as table_name')
.where('type', 'view')
.whereNot('name', 'like', 'sqlite_%')
.orderBy('name', 'asc')

return tables.map(({ table_name }) => table_name)
}

/**
* Returns an array of all types names
*/
public async getAllTypes(): Promise<string[]> {
throw new Error("Sqlite doesn't support types")
}

/**
* Truncate SQLITE tables
*/
public async truncate(table: string, _: boolean) {
return this.client.knexQuery().table(table).truncate()
}

/**
* Drop all tables inside the database
*/
public async dropAllTables() {
await this.client.rawQuery('PRAGMA writable_schema = 1;')
await this.client.rawQuery(
`delete from sqlite_master where type in ('table', 'index', 'trigger');`
)
await this.client.rawQuery('PRAGMA writable_schema = 0;')
await this.client.rawQuery('VACUUM;')
}

/**
* Drop all views inside the database
*/
public async dropAllViews(): Promise<void> {
await this.client.rawQuery('PRAGMA writable_schema = 1;')
await this.client.rawQuery(`delete from sqlite_schema where type = 'view';`)
await this.client.rawQuery('PRAGMA writable_schema = 0;')
await this.client.rawQuery('VACUUM;')
}

/**
* Drop all custom types inside the database
*/
public async dropAllTypes(): Promise<void> {
throw new Error("Sqlite doesn't support types")
}

/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
public getAdvisoryLock(): Promise<boolean> {
throw new Error("Sqlite doesn't support advisory locks")
}

/**
* Releases the advisory lock
*/
public releaseAdvisoryLock(): Promise<boolean> {
throw new Error("Sqlite doesn't support advisory locks")
}
}
119 changes: 119 additions & 0 deletions src/Dialects/SqliteBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/// <reference path="../../adonis-typings/index.ts" />

import { DialectContract, QueryClientContract } from '@ioc:Adonis/Lucid/Database'

export abstract class BaseSqliteDialect implements DialectContract {
public abstract readonly name: 'sqlite3' | 'better-sqlite3'
public readonly supportsAdvisoryLocks = false

/**
* Reference to the database version. Knex.js fetches the version after
* the first database query, so it will be set to undefined initially
*/
public readonly version = this.client.getReadClient()['context']['client'].version

/**
* The default format for datetime column. The date formats is
* valid for luxon date parsing library
*/
public readonly dateTimeFormat = 'yyyy-MM-dd HH:mm:ss'

constructor(private client: QueryClientContract) {}

/**
* Returns an array of table names
*/
public async getAllTables() {
const tables = await this.client
.query()
.from('sqlite_master')
.select('name as table_name')
.where('type', 'table')
.whereNot('name', 'like', 'sqlite_%')
.orderBy('name', 'asc')

return tables.map(({ table_name }) => table_name)
}

/**
* Returns an array of all views names
*/
public async getAllViews(): Promise<string[]> {
const tables = await this.client
.query()
.from('sqlite_master')
.select('name as table_name')
.where('type', 'view')
.whereNot('name', 'like', 'sqlite_%')
.orderBy('name', 'asc')

return tables.map(({ table_name }) => table_name)
}

/**
* Returns an array of all types names
*/
public async getAllTypes(): Promise<string[]> {
throw new Error("Sqlite doesn't support types")
}

/**
* Truncate SQLITE tables
*/
public async truncate(table: string) {
return this.client.knexQuery().table(table).truncate()
}

/**
* Drop all tables inside the database
*/
public async dropAllTables() {
await this.client.rawQuery('PRAGMA writable_schema = 1;')
await this.client.rawQuery(
`delete from sqlite_master where type in ('table', 'index', 'trigger');`
)
await this.client.rawQuery('PRAGMA writable_schema = 0;')
await this.client.rawQuery('VACUUM;')
}

/**
* Drop all views inside the database
*/
public async dropAllViews(): Promise<void> {
await this.client.rawQuery('PRAGMA writable_schema = 1;')
await this.client.rawQuery(`delete from sqlite_schema where type = 'view';`)
await this.client.rawQuery('PRAGMA writable_schema = 0;')
await this.client.rawQuery('VACUUM;')
}

/**
* Drop all custom types inside the database
*/
public async dropAllTypes(): Promise<void> {
throw new Error("Sqlite doesn't support types")
}

/**
* Attempts to add advisory lock to the database and
* returns it's status.
*/
public getAdvisoryLock(): Promise<boolean> {
throw new Error("Sqlite doesn't support advisory locks")
}

/**
* Releases the advisory lock
*/
public releaseAdvisoryLock(): Promise<boolean> {
throw new Error("Sqlite doesn't support advisory locks")
}
}
16 changes: 9 additions & 7 deletions src/Dialects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import { MssqlDialect } from './Mssql'
import { SqliteDialect } from './Sqlite'
import { OracleDialect } from './Oracle'
import { RedshiftDialect } from './Redshift'
import { BetterSqliteDialect } from './BetterSqlite'

export const dialects = {
mssql: MssqlDialect,
mysql: MysqlDialect,
mysql2: MysqlDialect,
oracledb: OracleDialect,
postgres: PgDialect,
redshift: RedshiftDialect,
sqlite3: SqliteDialect,
'mssql': MssqlDialect,
'mysql': MysqlDialect,
'mysql2': MysqlDialect,
'oracledb': OracleDialect,
'postgres': PgDialect,
'redshift': RedshiftDialect,
'sqlite3': SqliteDialect,
'better-sqlite3': BetterSqliteDialect,
}
22 changes: 21 additions & 1 deletion test-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { FactoryModel } from '../src/Factory/FactoryModel'
import { RawQueryBuilder } from '../src/Database/QueryBuilder/Raw'
import { InsertQueryBuilder } from '../src/Database/QueryBuilder/Insert'
import { DatabaseQueryBuilder } from '../src/Database/QueryBuilder/Database'
import { time } from 'console'

export const fs = new Filesystem(join(__dirname, 'tmp'))
dotenv.config()
Expand All @@ -65,6 +66,21 @@ export function getConfig(): ConnectionConfig {
useNullAsDefault: true,
debug: !!process.env.DEBUG,
}
case 'better_sqlite':
return {
client: 'better-sqlite3',
connection: {
filename: join(fs.basePath, 'better-sqlite-db.sqlite'),
},
useNullAsDefault: true,
debug: !!process.env.DEBUG,
pool: {
afterCreate(connection, done) {
connection.unsafeMode(true)
done()
},
},
}
case 'mysql':
return {
client: 'mysql',
Expand Down Expand Up @@ -131,7 +147,7 @@ export function getConfig(): ConnectionConfig {
* Does base setup by creating databases
*/
export async function setup(destroyDb: boolean = true) {
if (process.env.DB === 'sqlite') {
if (['sqlite', 'better_sqlite'].includes(process.env.DB!)) {
await fs.ensureRoot()
}

Expand Down Expand Up @@ -648,3 +664,7 @@ export async function setupReplicaDb(connection: Knex, datatoInsert: { username:
export async function cleanupReplicaDb(connection: Knex) {
await connection.schema.dropTable('replica_users')
}

export function sleep(timeout: number) {
return new Promise((resolve) => setTimeout(resolve, timeout))
}
Loading

0 comments on commit 5d006b8

Please sign in to comment.