diff --git a/lib/discovery.js b/lib/discovery.js index 0393c524..63be505d 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -335,6 +335,32 @@ function mixinDiscovery(PostgreSQL) { return sql; }; + /** + * Discover unique keys for a given table + * @param {String} table The table name + * @param {Object} options The options for discovery + */ + + /*! + * Retrieves a list of column names that have unique key index + * @param schema + * @param table + * @returns {string} + */ + PostgreSQL.prototype.buildQueryUniqueKeys = function(schema, table) { + const sql = 'SELECT a.attname AS "columnName", n.nspname AS "owner", c.relname AS "tableName"' + + 'FROM pg_index i' + + 'JOIN pg_class c ON c.oid = i.indrelid' + + 'JOIN pg_namespace n ON n.oid = c.relnamespace' + + 'JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)' + + 'WHERE i.indisunique = true' + + 'AND i.indisprimary = false' + + 'AND n.nspname = $1' + + 'AND c.relname = $2' + + 'ORDER BY a.attnum'; + return {text: sql, values: [schema, table]}; + }; + /** * Discover foreign keys that reference to the primary key of this table * @param {String} table The table name diff --git a/package-lock.json b/package-lock.json index e43c5061..ca3e5710 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "eslint": "^8.0.0", "eslint-config-loopback": "^13.1.0", "lodash": "^4.17.4", - "loopback-datasource-juggler": "^5.0.0", + "loopback-datasource-juggler": "^5.1.5", "mocha": "^11.0.0", "rc": "^1.0.0", "should": "^13.2.3", @@ -849,7 +849,6 @@ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -863,7 +862,6 @@ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", "dev": true, - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" @@ -1353,7 +1351,6 @@ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -1411,7 +1408,6 @@ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -1421,17 +1417,15 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -1937,7 +1931,6 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1953,22 +1946,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dev": true, - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", + "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -1977,6 +1969,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -2116,7 +2121,6 @@ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2145,7 +2149,6 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2158,7 +2161,6 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, - "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -2255,11 +2257,10 @@ } }, "node_modules/inflection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-3.0.0.tgz", - "integrity": "sha512-1zEJU1l19SgJlmwqsEyFTbScw/tkMHFenUo//Y0i+XEP83gDFdMvPizAD/WGcE+l1ku12PcTVHQhO6g5E0UCMw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-3.0.2.tgz", + "integrity": "sha512-+Bg3+kg+J6JUWn8J6bzFmOWkTQ6L/NHfDRSYU+EVvuKHDxUDHAXgqixHfVlzuBQaPOTac8hn43aPhMNk6rMe3g==", "dev": true, - "license": "MIT", "engines": { "node": ">=18.0.0" } @@ -2713,42 +2714,40 @@ } }, "node_modules/loopback-connector": { - "version": "6.1.12", - "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.1.12.tgz", - "integrity": "sha512-WO3FVwpbabPfe1h7SmSyB6rBySgScN9mQ0M0GwN8TWbA+0HcCP9DWFiXYPHvS4v7QXOkey62sYVW+7M1l9zkNQ==", - "license": "MIT", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.2.1.tgz", + "integrity": "sha512-xsnpYly/EubWsvUYYAB6/H4yuzRUKqMMY8lZ+Woj7km6OsWGXvky2A4tPoX5C+JFR6pP+KOYFNPu437q1lTGcg==", "dependencies": { "async": "^3.2.6", "bluebird": "^3.7.2", - "debug": "^4.3.7", + "debug": "^4.4.0", "msgpack5": "^4.5.1", "strong-globalize": "^6.0.6", - "uuid": "^11.0.3" + "uuid": "^11.0.5" }, "engines": { "node": ">=18" } }, "node_modules/loopback-datasource-juggler": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/loopback-datasource-juggler/-/loopback-datasource-juggler-5.1.3.tgz", - "integrity": "sha512-r+fVHyziu1LhiuVDUOMF2C8vqBuHSTJvRlvGHGLMFZI/4XsjUciAFil7r1xSFepGAfBDWZEZUya67m0lb05wXQ==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/loopback-datasource-juggler/-/loopback-datasource-juggler-5.1.5.tgz", + "integrity": "sha512-qFTLIlDCy+aBfOmOOS2UBZJnIc3TEirOgBH+0agADwLaKHjiZJW1JBZRXSTPVVs36AY/982wnbms0kkxj0feSg==", "dev": true, - "license": "MIT", "dependencies": { "async": "^3.2.6", "change-case": "^4.1.2", - "debug": "^4.3.7", + "debug": "^4.4.0", "depd": "^2.0.0", - "inflection": "^3.0.0", + "inflection": "^3.0.2", "lodash": "^4.17.21", - "loopback-connector": "^6.1.12", + "loopback-connector": "^6.2.1", "minimatch": "^10.0.1", "nanoid": "^3.3.8", "neotraverse": "^0.6.18", - "qs": "^6.13.1", + "qs": "^6.14.0", "strong-globalize": "^6.0.6", - "uuid": "^11.0.3" + "uuid": "^11.0.5" }, "engines": { "node": ">=18" @@ -2814,7 +2813,6 @@ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3181,11 +3179,10 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3614,13 +3611,12 @@ } }, "node_modules/qs": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", - "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -3979,7 +3975,6 @@ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -3999,7 +3994,6 @@ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -4016,7 +4010,6 @@ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -4035,7 +4028,6 @@ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", diff --git a/package.json b/package.json index 6b018472..7663203b 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "eslint": "^8.0.0", "eslint-config-loopback": "^13.1.0", "lodash": "^4.17.4", - "loopback-datasource-juggler": "^5.0.0", + "loopback-datasource-juggler": "^5.1.5", "mocha": "^11.0.0", "rc": "^1.0.0", "should": "^13.2.3", diff --git a/test/postgresql.discover.test.js b/test/postgresql.discover.test.js index 74aadce0..9316fe93 100644 --- a/test/postgresql.discover.test.js +++ b/test/postgresql.discover.test.js @@ -394,6 +394,17 @@ describe('Discover LDL schema from a table', function() { }); }); +describe('Discover unique properties', function() { + it('should validate unique key for user', function(done) { + db.discoverSchema('user', {owner: 'strongloop'}, function(err, schema) { + console.log('This is our err: ', err); + console.log('This is our schema: ', schema); + assert(schema.properties.email.index.unique, true); + done(null, schema); + }); + }); +}); + describe('Discover and map correctly database types', function() { it('should handle character varying, date, timestamp with time zone, timestamp without time zone', function(done) { db.discoverSchema('customer', {owner: 'strongloop'}, function(err, schema) { diff --git a/test/schema.sql b/test/schema.sql index d6212895..0845bda9 100644 --- a/test/schema.sql +++ b/test/schema.sql @@ -276,6 +276,7 @@ CREATE TABLE "user" ( email character varying(100) ); +ALTER TABLE "user" ADD CONSTRAINT user_email_unique UNIQUE (email); -- -- Name: GeoPoint_id_seq; Type: SEQUENCE; Schema: strongloop; Owner: strongloop --