import * as fs from "fs" ; import * as path from "path" ; import { Database , OPEN_CREATE , OPEN_READWRITE } from "sqlite3" ; import { DbConnectionInfo } from "../.." ; import { SqLiteBaseAdapter } from "../adapter-sqlite-base/sqlite-base-adapter" ; import { DbLocalContext } from "../db-local-context" ; import { IQueryIterator , QueryIterator , SqlQueryBuilder , SqlQueryOptions , SqlQueryResultSet , } from "../db-operations" ; /**
 * An SQLite 3 adapter for Node.js that uses the sqlite3 npm module.
 *
 * This adapter supports SQL operations, transactions, and local
 * store operations exposed by a local database context (of class `DbLocalContext`).
 * For the `ìquery` operation the `fetchSize` option is ignored.
 *
 * The SQLite database file path is specified as connection string in the
 * connection info object. If you specify an absolute or relative file path
 * ensure that the path exists and is accessible by the process, otherwise
 * the database file cannot be created or opened. The database file name should
 * include the extension, if desired, e.g. `"db/myservice.db"`.
 *
 * No further connection options are supported.
 *
 * As a prerequisite you need to install the npm module `sqlite3` as explained
 * here: [sqlite3](https://www.npmjs.com/package/sqlite3)
 */ export class SqLiteNodeAdapter extends SqLiteBaseAdapter { private _dbFile : string ; private _db : Database ; private _transactionDb : Database ; constructor ( connectionInfo ) { super ( connectionInfo ) ; this . _dbFile = path . resolve ( connectionInfo . connectionString ) ; this . registerExtension ( "deleteDatabase" ) ; } query ( sql ) { return this . _getDatabase ( ) . then ( db => new Promise < SqlQueryResultSet > ( ( resolve , reject ) => { const [ text , params ] = this . getSqlQuery ( sql ) ; db . all ( text , params , ( err , rows ) => err ? reject ( err ) : resolve ( { rows : rows } ) ) ; } ) ) ; } iquery ( sql , queryOptions ? ) { return new Promise < IQueryIterator < any > > ( ( resolve , reject ) => { const iterator = new QueryIterator < any > ( ) ; const donePromise = new Promise < [ number , boolean ] > ( ( resolveDone , rejectDone ) => { iterator . setStarter ( ( ) => { this . _getDatabase ( ) . then ( db => { const [ text , params ] = this . getSqlQuery ( sql ) ; let resultIndex = 0 ; let currentBatch = [ ] ; let isBreakInvoked = false ; let iterationError ; db . each ( text , params , ( err , row ) => { if ( iterationError || isBreakInvoked ) { return ; } if ( err ) { iterationError = err ; rejectDone ( err ) ; return ; } let dontBreak = true ; try { if ( iterator . forEachAction ) { dontBreak = iterator . forEachAction ( row , resultIndex ) ; } if ( iterator . forBatchAction ) { currentBatch . push ( row ) ; if ( iterator . batchSize > 0 && currentBatch . length === iterator . batchSize ) { dontBreak = iterator . forBatchAction ( currentBatch ) ; currentBatch = [ ] ; } } } catch ( error ) { iterationError = error ; rejectDone ( error ) ; return ; } resultIndex ++ ; if ( dontBreak === false ) { isBreakInvoked = true ; } } , ( err , count ) => { if ( err ) { rejectDone ( err ) ; return ; } let batchError ; if ( currentBatch . length > 0 || resultIndex === 0 ) { try { if ( iterator . forBatchAction ( currentBatch ) === false ) { isBreakInvoked = true ; } } catch ( error ) { batchError = error ; } } if ( batchError ) { rejectDone ( batchError ) ; } else { resolveDone ( [ resultIndex , isBreakInvoked ] ) ; } } ) ; } ) . catch ( error => rejectDone ( error ) ) ; } ) ; } ) ; iterator . setDonePromise ( donePromise ) ; resolve ( iterator ) ; } ) ; } transaction ( action ) { return new Promise < void > ( ( resolve , reject ) => { const transact = ( db ) => { Promise . resolve ( ) . then ( ( ) => { const ctx = new DbLocalContext ( this . connectionInfo ) ; ( ctx . adapter as SqLiteNodeAdapter ) . _transactionDb = db ; return action ( ctx ) . then ( result => { ( ctx . adapter as SqLiteNodeAdapter ) . _transactionDb = undefined ; return result ; } ) . catch ( err => { ( ctx . adapter as SqLiteNodeAdapter ) . _transactionDb = undefined ; return Promise . reject ( err ) ; } ) ; } ) . then ( ( ) => { this . _commitTransaction ( db , ( ) => resolve ( ) , error => reject ( error ) ) ; } ) . catch ( err => { this . _rollbackTransaction ( db , err , error => reject ( error ) ) ; } ) ; } ; this . _getDatabase ( ) . then ( db => { this . _beginTransaction ( db , database => transact ( database ) , error => reject ( error ) ) ; } ) . catch ( error => reject ( error ) ) ; } ) ; } close ( ) { return new Promise < void > ( ( resolve , reject ) => { if ( ! this . _db ) { resolve ( ) ; } else { this . _db . close ( err => { if ( err ) { reject ( err ) ; } else { this . _db = undefined ; resolve ( ) ; } } ) ; } } ) ; } param name deleteDatabase ( file ) { return new Promise < void > ( ( resolve , reject ) => { const filePath = path . resolve ( file ) ; if ( ! fs . existsSync ( filePath ) ) { resolve ( ) ; return ; } fs . unlink ( filePath , err => { err ? reject ( err ) : resolve ( ) ; } ) ; } ) ; } private _beginTransaction ( db , action , errorAction ) { if ( this . _transactionDb !== undefined ) { throw new Error ( "Nested transactions (savepoints) are not supported." ) ; } db . run ( "BEGIN" , err => { if ( err ) { this . _rollbackTransaction ( db , err , errorAction ) ; } else { action ( db ) ; } } ) ; } private _commitTransaction ( db , action , errorAction ) { db . run ( "COMMIT" , err => { if ( err ) { errorAction ( err ) ; } else { action ( ) ; } } ) ; } private _rollbackTransaction ( db , error , errorAction ) { db . run ( "ROLLBACK" , err => { errorAction ( error ) ; } ) ; } private _getDatabase ( ) { return new Promise < Database > ( ( resolve , reject ) => { if ( this . _transactionDb ) { resolve ( this . _transactionDb ) ; return ; } if ( this . _db ) { resolve ( this . _db ) ; return ; } const db = new Database ( this . _dbFile , OPEN_READWRITE | OPEN_CREATE ) ; db . once ( "open" , ( ) => { this . _db = db ; this . query ( this . getStoreQuery ( "CREATE" ) ) . then ( ( ) => resolve ( db ) ) . catch ( error => reject ( error ) ) ; } ) . once ( "error" , err => { reject ( err ) ; } ) ; } ) ; } }