Experimental SQLite support

This commit is contained in:
2022-09-30 11:42:13 -05:00
parent c0595f3ef9
commit 52762bd4a1
16 changed files with 1401 additions and 71 deletions

View File

@@ -1,11 +1,12 @@
import {Awaitable, ErrorWithContext} from '../../util'
import {QueryResult} from '../types'
import {Awaitable, Collection, ErrorWithContext} from '../../util'
import {QueryResult, QueryRow} from '../types'
import {SQLDialect} from '../dialect/SQLDialect'
import {AppClass} from '../../lifecycle/AppClass'
import {Inject, Injectable} from '../../di'
import {QueryExecutedEvent} from './event/QueryExecutedEvent'
import {Schema} from '../schema/Schema'
import {Bus} from '../../support/bus'
import {ModelField} from '../model/Field'
/**
* Error thrown when a connection is used before it is ready.
@@ -75,6 +76,17 @@ export abstract class Connection extends AppClass {
*/
public abstract asTransaction<T>(closure: () => Awaitable<T>): Awaitable<T>
/**
* Normalize a query row before it is used by the framework.
* This helps account for differences in return values from the dialects.
* @param row
* @param fields
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public normalizeRow(row: QueryRow, fields: Collection<ModelField>): QueryRow {
return row
}
/**
* Fire a QueryExecutedEvent for the given query string.
* @param query

View File

@@ -80,9 +80,21 @@ export class PostgresConnection extends Connection {
}
await this.client.query('BEGIN')
const result = await closure()
await this.client.query('COMMIT')
return result
try {
const result = await closure()
await this.client.query('COMMIT')
return result
} catch (e) {
await this.client.query('ROLLBACK')
if ( e instanceof Error ) {
throw this.app().errorWrapContext(e, {
connection: this.name,
})
}
throw e
}
}
public schema(name?: string): Schema {

View File

@@ -0,0 +1,117 @@
import {Connection, ConnectionNotReadyError} from './Connection'
import {Logging} from '../../service/Logging'
import {Inject} from '../../di'
import {open, Database} from 'sqlite'
import {FieldType, QueryResult, QueryRow} from '../types'
import {Schema} from '../schema/Schema'
import {Awaitable, collect, Collection, hasOwnProperty, UniversalPath} from '../../util'
import {SQLDialect} from '../dialect/SQLDialect'
import {SQLiteDialect} from '../dialect/SQLiteDialect'
import {SQLiteSchema} from '../schema/SQLiteSchema'
import * as sqlite3 from 'sqlite3'
import {ModelField} from '../model/Field'
export interface SQLiteConnectionConfig {
filename: string,
}
export class SQLiteConnection extends Connection {
@Inject()
protected readonly logging!: Logging
protected client?: Database
public dialect(): SQLDialect {
return this.make(SQLiteDialect)
}
public async init(): Promise<void> {
if ( this.config?.filename instanceof UniversalPath ) {
this.config.filename = this.config.filename.toLocal
}
this.logging.debug(`Opening SQLite connection ${this.name} (${this.config?.filename})...`)
this.client = await open({
...this.config,
driver: sqlite3.Database,
})
}
public async close(): Promise<void> {
this.logging.debug(`Closing SQLite connection ${this.name}...`)
if ( this.client ) {
await this.client.close()
}
}
public async query(query: string): Promise<QueryResult> {
if ( !this.client ) {
throw new ConnectionNotReadyError(this.name, {
config: this.config,
})
}
this.logging.verbose(`Executing query in connection ${this.name}: \n${query.split('\n').map(x => ' ' + x)
.join('\n')}`)
try {
const result = await this.client.all(query) // FIXME: this probably won't work for non-select statements?
await this.queryExecuted(query)
return {
rows: collect(result),
rowCount: result.length,
}
} catch (e) {
if ( e instanceof Error ) {
throw this.app().errorWrapContext(e, {
query,
connection: this.name,
})
}
throw e
}
}
public async asTransaction<T>(closure: () => Awaitable<T>): Promise<T> {
if ( !this.client ) {
throw new ConnectionNotReadyError(this.name, {
config: this.config,
})
}
// fixme: sqlite doesn't support tx's properly in node atm
await this.client.run('BEGIN')
try {
const result = await closure()
await this.client.run('COMMIT')
return result
} catch (e) {
await this.client.run('ROLLBACK')
if ( e instanceof Error ) {
throw this.app().errorWrapContext(e, {
connection: this.name,
})
}
throw e
}
}
public normalizeRow(row: QueryRow, fields: Collection<ModelField>): QueryRow {
fields.where('type', '=', FieldType.json)
.pluck('databaseKey')
.filter(key => hasOwnProperty(row, key))
.filter(key => typeof row[key] === 'string')
.each(key => row[key] = JSON.parse(row[key]))
return row
}
public schema(): Schema {
return new SQLiteSchema(this)
}
}