Repository: dynamoosejs/dynamoose
Pull request #744 (2020-03-21 01:58:36)

Document retriever improvements
- Better support for single object Scan's and Query's
- Finding index better for single object Query's


Patch:
diff --git a/lib/DocumentRetriever.js b/lib/DocumentRetriever.js
index b4673f76..a9a2816c 100644
--- a/lib/DocumentRetriever.js
+++ b/lib/DocumentRetriever.js
@@ -26,14 +26,15 @@ function main(documentRetrieverTypeString) {
 
 				if (typeof object === "object") {
 					Object.keys(object).forEach((key) => {
-						const type = Object.keys(object[key])[0];
+						const value = object[key];
+						const type = typeof value === "object" && Object.keys(value).length > 0 ? Object.keys(value)[0] : "eq";
 						const filterType = filterTypes.find((item) => item.name === type);
 
 						if (!filterType) {
 							throw new Error.InvalidFilterComparison(`The type: ${type} is invalid for the ${documentRetrieverType.type} operation.`);
 						}
 
-						this.settings.filters[key] = {"type": filterType.typeName, "value": object[key][type]};
+						this.settings.filters[key] = {"type": filterType.typeName, "value": typeof value[type] !== "undefined" && value[type] !== null ? value[type] : value};
 					});
 				} else if (object) {
 					this.settings.pending.key = object;
@@ -51,6 +52,16 @@ function main(documentRetrieverTypeString) {
 			};
 			if (documentRetrieverType.type === "query") {
 				object.KeyConditionExpression = "#qha = :qhv";
+
+				if (!Object.values(this.settings.filters).find((filter) => filter.queryCondition === "hash")) {
+					const indexes = (await model.schema.getIndexAttributes()).map((v) => v.attribute);
+					Object.keys(this.settings.filters).find((key) => {
+						if (indexes.includes(key) && this.settings.filters[key].type === "EQ") {
+							this.settings.filters[key].queryCondition = "hash";
+							return true;
+						}
+					});
+				}
 			}
 
 			Object.keys(this.settings.filters).forEach((key, index) => {


Test patch:
diff --git a/test/Query.js b/test/Query.js
index 73704cbe..113b5a02 100644
--- a/test/Query.js
+++ b/test/Query.js
@@ -151,6 +151,81 @@ describe("Query", () => {
 					});
 				});
 
+				it("Should send correct request on query.exec for one object passed in", async () => {
+					queryPromiseResolver = () => ({"Items": []});
+					await callType.func(Model.query({"name": "Charlie"}).exec).bind(Model.query({"name": "Charlie"}))();
+					expect(queryParams).to.eql({
+						"TableName": "Cat",
+						"IndexName": "nameGlobalIndex",
+						"ExpressionAttributeNames": {
+							"#qha": "name"
+						},
+						"ExpressionAttributeValues": {
+							":qhv": {"S": "Charlie"}
+						},
+						"KeyConditionExpression": "#qha = :qhv"
+					});
+				});
+
+				it("Should send correct request on query.exec for one object passed in", async () => {
+					queryPromiseResolver = () => ({"Items": []});
+					await callType.func(Model.query({"id": {"le": 5}, "name": {"eq": "Charlie"}}).exec).bind(Model.query({"id": {"le": 5}, "name": {"eq": "Charlie"}}))();
+					expect(queryParams).to.eql({
+						"TableName": "Cat",
+						"IndexName": "nameGlobalIndex",
+						"ExpressionAttributeNames": {
+							"#qha": "name",
+							"#a0": "id"
+						},
+						"ExpressionAttributeValues": {
+							":qhv": {"S": "Charlie"},
+							":v0": {"N": "5"}
+						},
+						"KeyConditionExpression": "#qha = :qhv",
+						"FilterExpression": "#a0 <= :v0"
+					});
+				});
+
+				it("Should send correct request on query.exec for one object passed in and multiple indexes but only one equals", async () => {
+					Model = new dynamoose.Model("Cat", {"id": Number, "age": {"type": Number, "index": {"global": true}}, "name": {"type": String, "index": {"global": true}}});
+					queryPromiseResolver = () => ({"Items": []});
+					await callType.func(Model.query({"age": {"le": 5}, "name": {"eq": "Charlie"}}).exec).bind(Model.query({"age": {"le": 5}, "name": {"eq": "Charlie"}}))();
+					expect(queryParams).to.eql({
+						"TableName": "Cat",
+						"IndexName": "nameGlobalIndex",
+						"ExpressionAttributeNames": {
+							"#qha": "name",
+							"#a0": "age"
+						},
+						"ExpressionAttributeValues": {
+							":qhv": {"S": "Charlie"},
+							":v0": {"N": "5"}
+						},
+						"KeyConditionExpression": "#qha = :qhv",
+						"FilterExpression": "#a0 <= :v0"
+					});
+				});
+
+				it("Should send correct request on query.exec for one object passed in and multiple indexes and multiple equals", async () => {
+					Model = new dynamoose.Model("Cat", {"id": Number, "age": {"type": Number, "index": {"global": true}}, "name": {"type": String, "index": {"global": true}}});
+					queryPromiseResolver = () => ({"Items": []});
+					await callType.func(Model.query({"age": {"eq": 5}, "name": {"eq": "Charlie"}}).exec).bind(Model.query({"age": {"eq": 5}, "name": {"eq": "Charlie"}}))();
+					expect(queryParams).to.eql({
+						"TableName": "Cat",
+						"IndexName": "ageGlobalIndex",
+						"ExpressionAttributeNames": {
+							"#qha": "age",
+							"#a1": "name"
+						},
+						"ExpressionAttributeValues": {
+							":qhv": {"N": "5"},
+							":v1": {"S": "Charlie"}
+						},
+						"KeyConditionExpression": "#qha = :qhv",
+						"FilterExpression": "#a1 = :v1"
+					});
+				});
+
 				it("Should send correct request on query.exec with filters", async () => {
 					queryPromiseResolver = () => ({"Items": []});
 					const query = Model.query("name").eq("Charlie").filter("id").eq("test");
diff --git a/test/Scan.js b/test/Scan.js
index cebfeccd..d7d35eda 100644
--- a/test/Scan.js
+++ b/test/Scan.js
@@ -133,6 +133,38 @@ describe("Scan", () => {
 					expect(scanParams).to.eql({"TableName": "Cat"});
 				});
 
+				it("Should send correct request on query.exec for one object passed in", async () => {
+					scanPromiseResolver = () => ({"Items": []});
+					await callType.func(Model.scan({"name": "Charlie"}).exec).bind(Model.scan({"name": "Charlie"}))();
+					expect(scanParams).to.eql({
+						"TableName": "Cat",
+						"ExpressionAttributeNames": {
+							"#a0": "name"
+						},
+						"ExpressionAttributeValues": {
+							":v0": {"S": "Charlie"}
+						},
+						"FilterExpression": "#a0 = :v0"
+					});
+				});
+
+				it("Should send correct request on query.exec for one object passed in", async () => {
+					scanPromiseResolver = () => ({"Items": []});
+					await callType.func(Model.scan({"id": {"le": 5}, "name": {"eq": "Charlie"}}).exec).bind(Model.scan({"id": {"le": 5}, "name": {"eq": "Charlie"}}))();
+					expect(scanParams).to.eql({
+						"TableName": "Cat",
+						"ExpressionAttributeNames": {
+							"#a0": "id",
+							"#a1": "name"
+						},
+						"ExpressionAttributeValues": {
+							":v0": {"N": "5"},
+							":v1": {"S": "Charlie"}
+						},
+						"FilterExpression": "#a0 <= :v0 AND #a1 = :v1"
+					});
+				});
+
 				it("Should send correct request on scan.exec with filters", async () => {
 					scanPromiseResolver = () => ({"Items": []});
 					const scan = Model.scan().filter("id").eq("test");