From 6ea60012fc790764fe19d2d3d961298a370e62b3 Mon Sep 17 00:00:00 2001 From: Ilya Shmygol Date: Thu, 21 Nov 2019 13:02:27 +0100 Subject: [PATCH 1/4] Improve parsing DynamoDB begins_with expression A query fails if it has a space between `begins_with` and `(`, for example: ```begins_with (#1, :1)``` Fix #1996 --- moto/dynamodb2/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index 0e39a1da15d6..c6dd094ae8d4 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -481,7 +481,7 @@ def query(self): ] elif "begins_with" in range_key_expression: range_comparison = "BEGINS_WITH" - range_values = [value_alias_map[range_key_expression_components[1]]] + range_values = [value_alias_map[range_key_expression_components[-1]]] else: range_values = [value_alias_map[range_key_expression_components[2]]] else: From 1d0de934e510963090a7690d0385047c4d94dde5 Mon Sep 17 00:00:00 2001 From: Ilya Shmygol Date: Wed, 11 Dec 2019 15:06:49 +0100 Subject: [PATCH 2/4] Slightly reformat the solution --- moto/dynamodb2/responses.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index c6dd094ae8d4..46cfaa239e9d 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -481,7 +481,9 @@ def query(self): ] elif "begins_with" in range_key_expression: range_comparison = "BEGINS_WITH" - range_values = [value_alias_map[range_key_expression_components[-1]]] + range_values = [ + value_alias_map[range_key_expression_components[-1]] + ] else: range_values = [value_alias_map[range_key_expression_components[2]]] else: From be5986c0b5b56cf0fc1955981394663b063fa834 Mon Sep 17 00:00:00 2001 From: Ilya Shmygol Date: Wed, 11 Dec 2019 15:08:45 +0100 Subject: [PATCH 3/4] Test query by non exists index --- tests/test_dynamodb2/test_dynamodb.py | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 2b0833d98022..da2b7c0942f4 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -2630,6 +2630,43 @@ def test_scan_by_non_exists_index(): ) +@mock_dynamodb2 +def test_query_by_non_exists_index(): + dynamodb = boto3.client("dynamodb", region_name="us-east-1") + + dynamodb.create_table( + TableName="test", + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "gsi_col", "AttributeType": "S"}, + ], + ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1}, + GlobalSecondaryIndexes=[ + { + "IndexName": "test_gsi", + "KeySchema": [{"AttributeName": "gsi_col", "KeyType": "HASH"}], + "Projection": {"ProjectionType": "ALL"}, + "ProvisionedThroughput": { + "ReadCapacityUnits": 1, + "WriteCapacityUnits": 1, + }, + } + ], + ) + + with assert_raises(ValueError) as ex: + dynamodb.query( + TableName="test", + IndexName="non_exists_index", + KeyConditionExpression="CarModel=M", + ) + + str(ex.exception).should.equal( + "Invalid index: non_exists_index for table: test. Available indexes are: test_gsi" + ) + + @mock_dynamodb2 def test_batch_items_returns_all(): dynamodb = _create_user_table() From 704a12146b482c3ed0bf3c4a8ccf64a860015b0b Mon Sep 17 00:00:00 2001 From: Ilya Shmygol Date: Thu, 12 Dec 2019 10:49:07 +0100 Subject: [PATCH 4/4] Improve error reporting for missing index --- moto/dynamodb2/responses.py | 9 ++++++--- tests/test_dynamodb2/test_dynamodb.py | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index 46cfaa239e9d..c9f3529a959e 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -438,9 +438,12 @@ def query(self): all_indexes = (table.global_indexes or []) + (table.indexes or []) indexes_by_name = dict((i["IndexName"], i) for i in all_indexes) if index_name not in indexes_by_name: - raise ValueError( - "Invalid index: %s for table: %s. Available indexes are: %s" - % (index_name, name, ", ".join(indexes_by_name.keys())) + er = "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException" + return self.error( + er, + "Invalid index: {} for table: {}. Available indexes are: {}".format( + index_name, name, ", ".join(indexes_by_name.keys()) + ), ) index = indexes_by_name[index_name]["KeySchema"] diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 698353a20d85..1a8a7061519c 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -2655,14 +2655,15 @@ def test_query_by_non_exists_index(): ], ) - with assert_raises(ValueError) as ex: + with assert_raises(ClientError) as ex: dynamodb.query( TableName="test", IndexName="non_exists_index", KeyConditionExpression="CarModel=M", ) - str(ex.exception).should.equal( + ex.exception.response["Error"]["Code"].should.equal("ResourceNotFoundException") + ex.exception.response["Error"]["Message"].should.equal( "Invalid index: non_exists_index for table: test. Available indexes are: test_gsi" )