Skip to content

Multi-Column Database Indexes are Always Dropped on Autoupdate #464

Closed
@ong-james

Description

@ong-james

Steps to reproduce

  1. Create a multi-column index on a database model's schema. E.g. for a model, person, in model's definition JSON file:
...
"indexes" {
  "unique_name_index": {
    "keys": { "firstName": 1, "lastName": 1 },
    "options": { "unique": true }
  }
},
...
  1. Execute ds.autoupdate() where ds is the database data source
  2. Verify unique_name_index exists on the person table
  3. Execute ds.autoupdate() a second time

Current Behavior

Despite the fact that the index definition hasn't changed, loopback-connector-postgresql will still DROP the index for unique_name_index and recreate it. Obviously this is not great for performance as the table's data needs to be re-indexed even though the index hasn't changed.

Expected Behavior

loopback-connector-postgresql detects that unique_name_index exists and it's definition (i.e. keys, type, and unique-ness properties) have not changed, so the connector doesn't DROP the index.

Given that the index is recreated during the autoupdate() call, it's not easy to detect that the index is first dropped and then re-created. We discovered the issue when we externally added a partial index (since loopback-connector-postgresql doesn't support partial indexes) and attempted to have Loopback "skip" dropping and re-creating the index by defining an index on the model's schema in Loopback with the same name, keys, type, and uniqueness. We found that even though our index matched all the properties loopback-connector-postgresql uses to determine if an existing index in the database matches the model schema's index definition, the index was still dropped and then recreated using the model schema's definition (i.e. it dropped our partial index and then created a new index based-on the model schema's definition.) We recognise this is not a supported Loopback method of adding indexes; however, the underlying issue appears to affect all multi-column indexes.

See below for more details for debugging the issue.

Additional information

linux x64 12.17.0

+─ [email protected]
+─ [email protected]
+─ [email protected]
+─ [email protected] (github:luminlife/loopback-component-migrate#e96bc2ea144b2d6221d82303c0edda96584a9469)
+─ [email protected]
+─ [email protected]
+─ [email protected]
+─ [email protected] (github:luminlife/loopback-jwt#5724ef89725aeb1a9b9704ce7d4ba023bc575913)

The code causing the issue appears to be here: https://github.com/strongloop/loopback-connector-postgresql/blob/ffb08f4ca60a711d544ce13996afebcf09b86712/lib/migration.js#L826

si.keys is an array of arrays built in the normalizeIndexDefintion() method of migration.js (e.g. [["firstName", "ASC"],["lastName", "ASC"]]), so, given how Object.keys operates on arrays, Object.keys(si.keys) will be an array of the position indexes of each of the array property pairs (e.g. siKeys = [0,1]); however, when the comparison for matching existing database indexes is performed (on line 612 of migration.js):

siKeys.forEach(function(propName, iter) {                                
  identical = identical && self.column(model, propName) === i.keys[iter];
});                                                                      

you'll notice the elements of siKeys are expected to be the property names (propName) of the keys (e.g. instead of the array of position index values [0,1], siKeys is expected to be ["firstName", "lastName"]), so the database index being evaluated will never match to any definition on the Loopback model and, therefore, the index will be dropped and recreated.

It looks like the correct code for defining siKeys should be something like:

const siKeys = si.keys.map((key) => {
   return key[0]; // get the property name described by "key"
});

this will create the correct array of property names and allow them to be matched to the column names on the model.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions