66 * Abstract helpers for all NoSqlProvider DbProviders that are based on SQL backings.
77 */
88
9+ import assert = require( 'assert' ) ;
910import _ = require( 'lodash' ) ;
1011import SyncTasks = require( 'synctasks' ) ;
1112
@@ -63,6 +64,12 @@ function indexUsesSeparateTable(indexSchema: NoSqlProvider.IndexSchema, supports
6364 return indexSchema . multiEntry || ( ! ! indexSchema . fullText && supportsFTS3 ) ;
6465}
6566
67+ function generateParamPlaceholder ( count : number ) : string {
68+ assert . ok ( count >= 1 , 'Must provide at least one parameter to SQL statement' ) ;
69+ // Generate correct count of ?'s and slice off trailing comma
70+ return _ . repeat ( '?,' , count ) . slice ( 0 , - 1 ) ;
71+ }
72+
6673const FakeFTSJoinToken = '^$^' ;
6774
6875// Limit LIMIT numbers to a reasonable size to not break queries.
@@ -209,10 +216,7 @@ export abstract class SqlProviderBase extends NoSqlProvider.DbProvider {
209216 }
210217
211218 // Generate as many '?' as there are params
212- let placeholder = '?' ;
213- for ( let i = 1 ; i < metasToDelete . length ; i ++ ) {
214- placeholder += ',?' ;
215- }
219+ const placeholder = generateParamPlaceholder ( metasToDelete . length ) ;
216220
217221 return trans . runQuery ( 'DELETE FROM metadata WHERE name IN (' + placeholder + ')' ,
218222 _ . map ( metasToDelete , meta => meta . key ) ) ;
@@ -788,10 +792,8 @@ class SqlStore implements NoSqlProvider.DbStore {
788792 startTime = Date . now ( ) ;
789793 }
790794
791- const qmarks = _ . map ( joinedKeys ! ! ! , k => '?' ) ;
792-
793795 let promise = this . _trans . internal_getResultsFromQuery ( 'SELECT nsp_data FROM ' + this . _schema . name + ' WHERE nsp_pk IN (' +
794- qmarks . join ( ',' ) + ')' , joinedKeys ! ! ! ) ;
796+ generateParamPlaceholder ( joinedKeys . length ) + ')' , joinedKeys ) ;
795797 if ( this . _verbose ) {
796798 promise = promise . finally ( ( ) => {
797799 console . log ( 'SqlStore (' + this . _schema . name + ') getMultiple: (' + ( Date . now ( ) - startTime ) + 'ms): Count: ' +
@@ -870,6 +872,9 @@ class SqlStore implements NoSqlProvider.DbStore {
870872
871873 // Also prepare mulltiEntry and FullText indexes
872874 if ( _ . some ( this . _schema . indexes , index => indexUsesSeparateTable ( index , this . _supportsFTS3 ) ) ) {
875+ const keysToDeleteByIndex : { [ indexIndex : number ] : string [ ] } = { } ;
876+ const dataToInsertByIndex : { [ indexIndex : number ] : string [ ] } = { } ;
877+
873878 _ . each ( items , ( item , itemIndex ) => {
874879 const key = _ . attempt ( ( ) => {
875880 return NoSqlProviderUtils . getSerializedKeyForKeypath ( item , this . _schema . primaryKeyPath ) ! ! ! ;
@@ -879,50 +884,91 @@ class SqlStore implements NoSqlProvider.DbStore {
879884 return ;
880885 }
881886
882- _ . each ( this . _schema . indexes , index => {
887+ _ . each ( this . _schema . indexes , ( index , indexIndex ) => {
883888 let serializedKeys : string [ ] ;
884889
885890 if ( index . fullText && this . _supportsFTS3 ) {
886891 // FTS3 terms go in a separate virtual table...
887- serializedKeys = [ FullTextSearchHelpers . getFullTextIndexWordsForItem ( < string > index . keyPath , item ) . join ( ' ' ) ] ;
892+ serializedKeys = [ FullTextSearchHelpers . getFullTextIndexWordsForItem ( < string > index . keyPath , item ) . join ( ' ' ) ] ;
888893 } else if ( index . multiEntry ) {
889894 // Have to extract the multiple entries into the alternate table...
890895 const valsRaw = NoSqlProviderUtils . getValueForSingleKeypath ( item , < string > index . keyPath ) ;
891896 if ( valsRaw ) {
892- const err = _ . attempt ( ( ) => {
893- serializedKeys = _ . map ( NoSqlProviderUtils . arrayify ( valsRaw ) , val =>
897+ const serializedKeysOrErr = _ . attempt ( ( ) => {
898+ return _ . map ( NoSqlProviderUtils . arrayify ( valsRaw ) , val =>
894899 NoSqlProviderUtils . serializeKeyToString ( val , < string > index . keyPath ) ) ;
895900 } ) ;
896- if ( err ) {
897- queries . push ( SyncTasks . Rejected < void > ( err ) ) ;
901+ if ( _ . isError ( serializedKeysOrErr ) ) {
902+ queries . push ( SyncTasks . Rejected < void > ( serializedKeysOrErr ) ) ;
898903 return ;
899904 }
905+ serializedKeys = serializedKeysOrErr ;
906+ } else {
907+ serializedKeys = [ ] ;
900908 }
901909 } else {
902910 return ;
903911 }
904912
905- let valArgs : string [ ] = [ ] , insertArgs : string [ ] = [ ] ;
906- _ . each ( serializedKeys ! ! ! , val => {
907- valArgs . push ( index . includeDataInIndex ? '(?, ?, ?)' : '(?, ?)' ) ;
908- insertArgs . push ( val ) ;
909- insertArgs . push ( key ) ;
910- if ( index . includeDataInIndex ) {
911- insertArgs . push ( datas [ itemIndex ] ) ;
913+ // Capture insert data
914+ if ( serializedKeys . length > 0 ) {
915+ if ( ! dataToInsertByIndex [ indexIndex ] ) {
916+ dataToInsertByIndex [ indexIndex ] = [ ] ;
912917 }
913- } ) ;
914- queries . push ( this . _trans . internal_nonQuery ( 'DELETE FROM ' + this . _schema . name + '_' + index . name +
915- ' WHERE nsp_refpk = ?' , [ key ] )
916- . then ( ( ) => {
917- if ( valArgs . length > 0 ) {
918- return this . _trans . internal_nonQuery ( 'INSERT INTO ' + this . _schema . name + '_' + index . name +
919- ' (nsp_key, nsp_refpk' + ( index . includeDataInIndex ? ', nsp_data' : '' ) + ') VALUES ' +
920- valArgs . join ( ',' ) , insertArgs ) ;
921- }
922- return undefined ;
923- } ) ) ;
918+ const dataToInsert = dataToInsertByIndex [ indexIndex ] ;
919+ _ . each ( serializedKeys , val => {
920+ dataToInsert . push ( val ) ;
921+ dataToInsert . push ( key ) ;
922+ if ( index . includeDataInIndex ) {
923+ dataToInsert . push ( datas [ itemIndex ] ) ;
924+ }
925+ } ) ;
926+ }
927+
928+ // Capture delete keys
929+ if ( ! keysToDeleteByIndex [ indexIndex ] ) {
930+ keysToDeleteByIndex [ indexIndex ] = [ ] ;
931+ }
932+
933+ keysToDeleteByIndex [ indexIndex ] . push ( key ) ;
924934 } ) ;
925935 } ) ;
936+
937+ const deleteQueries : SyncTasks . Promise < void > [ ] = [ ] ;
938+
939+ _ . each ( keysToDeleteByIndex , ( keysToDelete , indedIndex ) => {
940+ // We know indexes are defined if we have data to insert for them
941+ // _.each spits dictionary keys out as string, needs to turn into a number
942+ const index = this . _schema . indexes ! ! ! [ Number ( indedIndex ) ] ;
943+ const itemPageSize = this . _trans . internal_getMaxVariables ( ) ;
944+ for ( let i = 0 ; i < keysToDelete . length ; i += itemPageSize ) {
945+ const thisPageCount = Math . min ( itemPageSize , keysToDelete . length - i ) ;
946+ deleteQueries . push ( this . _trans . internal_nonQuery ( 'DELETE FROM ' + this . _schema . name + '_' + index . name +
947+ ' WHERE nsp_refpk IN (' + generateParamPlaceholder ( thisPageCount ) + ')' , keysToDelete . splice ( 0 , thisPageCount ) ) ) ;
948+ }
949+ } ) ;
950+
951+ // Delete and insert tracking - cannot insert until delete is completed
952+ queries . push ( SyncTasks . all ( deleteQueries ) . then ( ( ) => {
953+ const insertQueries : SyncTasks . Promise < void > [ ] = [ ] ;
954+ _ . each ( dataToInsertByIndex , ( data , indexIndex ) => {
955+ // We know indexes are defined if we have data to insert for them
956+ // _.each spits dictionary keys out as string, needs to turn into a number
957+ const index = this . _schema . indexes ! ! ! [ Number ( indexIndex ) ] ;
958+ const insertParamCount = index . includeDataInIndex ? 3 : 2 ;
959+ const itemPageSize = Math . floor ( this . _trans . internal_getMaxVariables ( ) / insertParamCount ) ;
960+ // data contains all the input parameters
961+ for ( let i = 0 ; i < ( data . length / insertParamCount ) ; i += itemPageSize ) {
962+ const thisPageCount = Math . min ( itemPageSize , ( data . length / insertParamCount ) ) - i ;
963+ const qmarksValues = _ . fill ( new Array ( thisPageCount ) , generateParamPlaceholder ( insertParamCount ) ) ;
964+ insertQueries . push ( this . _trans . internal_nonQuery ( 'INSERT INTO ' +
965+ this . _schema . name + '_' + index . name + ' (nsp_key, nsp_refpk' + ( index . includeDataInIndex ? ', nsp_data' : '' ) +
966+ ') VALUES ' + '(' + qmarksValues . join ( '),(' ) + ')' , data . splice ( 0 , thisPageCount * insertParamCount ) ) ) ;
967+ }
968+ } ) ;
969+ return SyncTasks . all ( insertQueries ) . then ( _ . noop ) ;
970+ } ) ) ;
971+
926972 }
927973
928974 let promise = SyncTasks . all ( queries ) ;
@@ -981,10 +1027,7 @@ class SqlStore implements NoSqlProvider.DbStore {
9811027 }
9821028
9831029 // Generate as many '?' as there are params
984- let placeholder = '?' ;
985- for ( let i = 1 ; i < params . length ; i ++ ) {
986- placeholder += ',?' ;
987- }
1030+ const placeholder = generateParamPlaceholder ( params . length ) ;
9881031
9891032 _ . each ( this . _schema . indexes , index => {
9901033 if ( indexUsesSeparateTable ( index , this . _supportsFTS3 ) ) {
0 commit comments