Experimental SQLite support
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
117
src/orm/connection/SQLiteConnection.ts
Normal file
117
src/orm/connection/SQLiteConnection.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user