118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
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)
|
|
}
|
|
}
|