7
7
var SG = require ( 'strong-globalize' ) ;
8
8
var g = SG ( ) ;
9
9
var async = require ( 'async' ) ;
10
+ var debug = require ( 'debug' ) ( 'loopback:connector:postgresql:migration' ) ;
10
11
11
12
module . exports = mixinMigration ;
12
13
@@ -83,7 +84,7 @@ function mixinMigration(PostgreSQL) {
83
84
84
85
var applyPending = function ( actions , cb , err , result ) {
85
86
var action = actions . shift ( ) ;
86
- var pendingChanges = action && action . call ( self , model , actualFields ) || [ ] ;
87
+ var pendingChanges = action && action ( ) || [ ] ;
87
88
if ( pendingChanges . length ) {
88
89
self . applySqlChanges ( model , pendingChanges , function ( err , result ) {
89
90
if ( ! err ) {
@@ -99,15 +100,37 @@ function mixinMigration(PostgreSQL) {
99
100
}
100
101
} ;
101
102
102
- async . series ( [
103
- function ( cb ) {
104
- applyPending ( [ self . getAddModifyColumns , self . getDropColumns ] , cb ) ;
105
- } ,
106
- function ( cb ) {
107
- self . addIndexes ( model , actualIndexes , cb ) ;
108
- } ,
109
- ] , function ( err , result ) {
110
- cb ( err , result [ 0 ] ) ;
103
+ self . discoverForeignKeys ( self . table ( model ) , { } , function ( err , actualFks ) {
104
+ if ( err ) {
105
+ debug ( 'Failed to discover "%s" foreign keys %s' , self . table ( model ) , err ) ;
106
+ cb ( err ) ;
107
+ return ;
108
+ }
109
+
110
+ // actualFks is a list of EXISTING fkeys here,
111
+ // so you don't need to recreate them again
112
+ // prepare fkSQL for new foreign keys
113
+ var fkSQL = self . getForeignKeySQL ( model ,
114
+ self . getModelDefinition ( model ) . settings . foreignKeys ,
115
+ actualFks ) ;
116
+
117
+ async . series ( [
118
+ function ( cb ) {
119
+ applyPending ( [
120
+ self . getAddModifyColumns . bind ( self , model , actualFields ) ,
121
+ self . getDropColumns . bind ( self , model , actualFields ) ,
122
+ self . getDropForeignKeys . bind ( self , model , actualFks ) ,
123
+ ] , cb ) ;
124
+ } ,
125
+ function ( cb ) {
126
+ self . addIndexes ( model , actualIndexes , cb ) ;
127
+ } ,
128
+ function ( cb ) {
129
+ self . addForeignKeys ( model , fkSQL , cb ) ;
130
+ } ,
131
+ ] , function ( err , result ) {
132
+ cb ( err , result [ 0 ] ) ;
133
+ } ) ;
111
134
} ) ;
112
135
} ;
113
136
@@ -302,7 +325,14 @@ function mixinMigration(PostgreSQL) {
302
325
if ( err ) {
303
326
return cb ( err , info ) ;
304
327
}
305
- self . addIndexes ( model , undefined , cb ) ;
328
+ self . addIndexes ( model , undefined , function ( err ) {
329
+ if ( err ) {
330
+ return cb ( err ) ;
331
+ }
332
+ self . addForeignKeys ( model , function ( err , result ) {
333
+ cb ( err ) ;
334
+ } ) ;
335
+ } ) ;
306
336
}
307
337
) ;
308
338
} ) ;
@@ -443,6 +473,112 @@ function mixinMigration(PostgreSQL) {
443
473
}
444
474
} ;
445
475
476
+ PostgreSQL . prototype . addForeignKeys = function ( model , fkSQL , cb ) {
477
+ var self = this ;
478
+ var m = this . getModelDefinition ( model ) ;
479
+
480
+ if ( ( ! cb ) && ( 'function' === typeof fkSQL ) ) {
481
+ cb = fkSQL ;
482
+ fkSQL = undefined ;
483
+ }
484
+
485
+ if ( ! fkSQL || fkSQL . length === 0 ) {
486
+ var newFks = m . settings . foreignKeys ;
487
+ if ( newFks )
488
+ fkSQL = self . getForeignKeySQL ( model , newFks ) ;
489
+ }
490
+
491
+ if ( fkSQL && fkSQL . length ) {
492
+ self . applySqlChanges ( model , [ fkSQL . toString ( ) ] , function ( err , result ) {
493
+ if ( err ) cb ( err ) ;
494
+ else
495
+ cb ( null , result ) ;
496
+ } ) ;
497
+ } else cb ( null , { } ) ;
498
+ } ;
499
+
500
+ PostgreSQL . prototype . getDropForeignKeys = function ( model , actualFks ) {
501
+ var self = this ;
502
+ var m = this . getModelDefinition ( model ) ;
503
+
504
+ var fks = actualFks ;
505
+ var sql = [ ] ;
506
+ var correctFks = m . settings . foreignKeys || { } ;
507
+
508
+ // drop foreign keys for removed fields
509
+ if ( fks && fks . length ) {
510
+ var removedFks = [ ] ;
511
+ fks . forEach ( function ( fk ) {
512
+ var needsToDrop = false ;
513
+ var newFk = correctFks [ fk . fkName ] ;
514
+ if ( newFk ) {
515
+ var fkCol = newFk . foreignKey ;
516
+ var fkRefKey = newFk . entityKey ;
517
+ var fkEntityName = ( typeof newFk . entity === 'object' ) ? newFk . entity . name : newFk . entity ;
518
+ var fkRefTable = self . table ( fkEntityName ) ;
519
+ needsToDrop = fkCol != fk . fkColumnName ||
520
+ fkRefKey != fk . pkColumnName ||
521
+ fkRefTable != fk . pkTableName ;
522
+ } else {
523
+ needsToDrop = true ;
524
+ }
525
+
526
+ if ( needsToDrop ) {
527
+ sql . push ( 'DROP CONSTRAINT ' + self . escapeName ( fk . fkName ) ) ;
528
+ removedFks . push ( fk ) ; // keep track that we removed these
529
+ }
530
+ } ) ;
531
+
532
+ // update out list of existing keys by removing dropped keys
533
+ removedFks . forEach ( function ( k ) {
534
+ var index = actualFks . indexOf ( k ) ;
535
+ if ( index !== - 1 ) actualFks . splice ( index , 1 ) ;
536
+ } ) ;
537
+ }
538
+ return sql ;
539
+ } ;
540
+
541
+ PostgreSQL . prototype . getForeignKeySQL = function getForeignKeySQL ( model , actualFks , existingFks ) {
542
+ var self = this ;
543
+ var addFksSql = [ ] ;
544
+ existingFks = existingFks || [ ] ;
545
+
546
+ if ( actualFks ) {
547
+ var keys = Object . keys ( actualFks ) ;
548
+ for ( var i = 0 ; i < keys . length ; i ++ ) {
549
+ // all existing fks are already checked in PostgreSQL.prototype.dropForeignKeys
550
+ // so we need check only names - skip if found
551
+ if ( existingFks . filter ( function ( fk ) {
552
+ return fk . fkName === keys [ i ] ;
553
+ } ) . length > 0 ) continue ;
554
+ var constraint = self . buildForeignKeyDefinition ( model , keys [ i ] ) ;
555
+
556
+ if ( constraint ) {
557
+ addFksSql . push ( 'ADD ' + constraint ) ;
558
+ }
559
+ }
560
+ }
561
+ return addFksSql ;
562
+ } ;
563
+
564
+ PostgreSQL . prototype . buildForeignKeyDefinition = function buildForeignKeyDefinition ( model , keyName ) {
565
+ var definition = this . getModelDefinition ( model ) ;
566
+
567
+ var fk = definition . settings . foreignKeys [ keyName ] ;
568
+ if ( fk ) {
569
+ // get the definition of the referenced object
570
+ var fkEntityName = ( typeof fk . entity === 'object' ) ? fk . entity . name : fk . entity ;
571
+
572
+ // verify that the other model in the same DB
573
+ if ( this . _models [ fkEntityName ] ) {
574
+ return 'CONSTRAINT ' + this . escapeName ( fk . name ) + ' ' +
575
+ 'FOREIGN KEY (' + fk . foreignKey + ') ' +
576
+ 'REFERENCES ' + this . tableEscaped ( fkEntityName ) + '(' + fk . entityKey + ')' ;
577
+ }
578
+ }
579
+ return '' ;
580
+ } ;
581
+
446
582
/*!
447
583
* Map postgresql data types to json types
448
584
* @param {String } postgresqlType
0 commit comments