Fix dynamodb2 KEYS_ONLY Indexes (#3125)

KEYS_ONLY indexes include table keys.
This commit is contained in:
Mike Fogel 2020-07-14 09:42:13 -03:00 committed by GitHub
parent 890c3b4954
commit 9072153474
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 13 deletions

View File

@ -283,17 +283,18 @@ class SecondaryIndex(BaseModel):
if self.projection: if self.projection:
if self.projection.get("ProjectionType", None) == "KEYS_ONLY": if self.projection.get("ProjectionType", None) == "KEYS_ONLY":
allowed_attributes = ",".join( allowed_attributes = ",".join(
[key["AttributeName"] for key in self.schema] self.table_key_attrs + [key["AttributeName"] for key in self.schema]
) )
item.filter(allowed_attributes) item.filter(allowed_attributes)
return item return item
class LocalSecondaryIndex(SecondaryIndex): class LocalSecondaryIndex(SecondaryIndex):
def __init__(self, index_name, schema, projection): def __init__(self, index_name, schema, projection, table_key_attrs):
self.name = index_name self.name = index_name
self.schema = schema self.schema = schema
self.projection = projection self.projection = projection
self.table_key_attrs = table_key_attrs
def describe(self): def describe(self):
return { return {
@ -303,21 +304,29 @@ class LocalSecondaryIndex(SecondaryIndex):
} }
@staticmethod @staticmethod
def create(dct): def create(dct, table_key_attrs):
return LocalSecondaryIndex( return LocalSecondaryIndex(
index_name=dct["IndexName"], index_name=dct["IndexName"],
schema=dct["KeySchema"], schema=dct["KeySchema"],
projection=dct["Projection"], projection=dct["Projection"],
table_key_attrs=table_key_attrs,
) )
class GlobalSecondaryIndex(SecondaryIndex): class GlobalSecondaryIndex(SecondaryIndex):
def __init__( def __init__(
self, index_name, schema, projection, status="ACTIVE", throughput=None self,
index_name,
schema,
projection,
table_key_attrs,
status="ACTIVE",
throughput=None,
): ):
self.name = index_name self.name = index_name
self.schema = schema self.schema = schema
self.projection = projection self.projection = projection
self.table_key_attrs = table_key_attrs
self.status = status self.status = status
self.throughput = throughput or { self.throughput = throughput or {
"ReadCapacityUnits": 0, "ReadCapacityUnits": 0,
@ -334,11 +343,12 @@ class GlobalSecondaryIndex(SecondaryIndex):
} }
@staticmethod @staticmethod
def create(dct): def create(dct, table_key_attrs):
return GlobalSecondaryIndex( return GlobalSecondaryIndex(
index_name=dct["IndexName"], index_name=dct["IndexName"],
schema=dct["KeySchema"], schema=dct["KeySchema"],
projection=dct["Projection"], projection=dct["Projection"],
table_key_attrs=table_key_attrs,
throughput=dct.get("ProvisionedThroughput", None), throughput=dct.get("ProvisionedThroughput", None),
) )
@ -374,16 +384,20 @@ class Table(BaseModel):
else: else:
self.range_key_attr = elem["AttributeName"] self.range_key_attr = elem["AttributeName"]
self.range_key_type = elem["KeyType"] self.range_key_type = elem["KeyType"]
self.table_key_attrs = [
key for key in (self.hash_key_attr, self.range_key_attr) if key
]
if throughput is None: if throughput is None:
self.throughput = {"WriteCapacityUnits": 10, "ReadCapacityUnits": 10} self.throughput = {"WriteCapacityUnits": 10, "ReadCapacityUnits": 10}
else: else:
self.throughput = throughput self.throughput = throughput
self.throughput["NumberOfDecreasesToday"] = 0 self.throughput["NumberOfDecreasesToday"] = 0
self.indexes = [ self.indexes = [
LocalSecondaryIndex.create(i) for i in (indexes if indexes else []) LocalSecondaryIndex.create(i, self.table_key_attrs)
for i in (indexes if indexes else [])
] ]
self.global_indexes = [ self.global_indexes = [
GlobalSecondaryIndex.create(i) GlobalSecondaryIndex.create(i, self.table_key_attrs)
for i in (global_indexes if global_indexes else []) for i in (global_indexes if global_indexes else [])
] ]
self.created_at = datetime.datetime.utcnow() self.created_at = datetime.datetime.utcnow()
@ -1015,7 +1029,7 @@ class DynamoDBBackend(BaseBackend):
) )
gsis_by_name[gsi_to_create["IndexName"]] = GlobalSecondaryIndex.create( gsis_by_name[gsi_to_create["IndexName"]] = GlobalSecondaryIndex.create(
gsi_to_create gsi_to_create, table.table_key_attrs,
) )
# in python 3.6, dict.values() returns a dict_values object, but we expect it to be a list in other # in python 3.6, dict.values() returns a dict_values object, but we expect it to be a list in other

View File

@ -5358,14 +5358,23 @@ def test_gsi_projection_type_keys_only():
IndexName="GSI-K1", IndexName="GSI-K1",
)["Items"] )["Items"]
items.should.have.length_of(1) items.should.have.length_of(1)
# Item should only include GSI Keys, as per the ProjectionType # Item should only include GSI Keys and Table Keys, as per the ProjectionType
items[0].should.equal({"gsiK1PartitionKey": "gsi-pk", "gsiK1SortKey": "gsi-sk"}) items[0].should.equal(
{
"gsiK1PartitionKey": "gsi-pk",
"gsiK1SortKey": "gsi-sk",
"partitionKey": "pk-1",
}
)
@mock_dynamodb2 @mock_dynamodb2
def test_lsi_projection_type_keys_only(): def test_lsi_projection_type_keys_only():
table_schema = { table_schema = {
"KeySchema": [{"AttributeName": "partitionKey", "KeyType": "HASH"}], "KeySchema": [
{"AttributeName": "partitionKey", "KeyType": "HASH"},
{"AttributeName": "sortKey", "KeyType": "RANGE"},
],
"LocalSecondaryIndexes": [ "LocalSecondaryIndexes": [
{ {
"IndexName": "LSI", "IndexName": "LSI",
@ -5378,12 +5387,14 @@ def test_lsi_projection_type_keys_only():
], ],
"AttributeDefinitions": [ "AttributeDefinitions": [
{"AttributeName": "partitionKey", "AttributeType": "S"}, {"AttributeName": "partitionKey", "AttributeType": "S"},
{"AttributeName": "sortKey", "AttributeType": "S"},
{"AttributeName": "lsiK1SortKey", "AttributeType": "S"}, {"AttributeName": "lsiK1SortKey", "AttributeType": "S"},
], ],
} }
item = { item = {
"partitionKey": "pk-1", "partitionKey": "pk-1",
"sortKey": "sk-1",
"lsiK1SortKey": "lsi-sk", "lsiK1SortKey": "lsi-sk",
"someAttribute": "lore ipsum", "someAttribute": "lore ipsum",
} }
@ -5399,5 +5410,7 @@ def test_lsi_projection_type_keys_only():
KeyConditionExpression=Key("partitionKey").eq("pk-1"), IndexName="LSI", KeyConditionExpression=Key("partitionKey").eq("pk-1"), IndexName="LSI",
)["Items"] )["Items"]
items.should.have.length_of(1) items.should.have.length_of(1)
# Item should only include GSI Keys, as per the ProjectionType # Item should only include GSI Keys and Table Keys, as per the ProjectionType
items[0].should.equal({"partitionKey": "pk-1", "lsiK1SortKey": "lsi-sk"}) items[0].should.equal(
{"partitionKey": "pk-1", "sortKey": "sk-1", "lsiK1SortKey": "lsi-sk"}
)