Skip to content

Commit 7e921bb

Browse files
feat(gsi): Add Composite Key Support to Global Secondary Indexes
1 parent dd0ccc2 commit 7e921bb

File tree

3 files changed

+120
-3
lines changed

3 files changed

+120
-3
lines changed

src/index.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,18 @@ export default class {
5656
}
5757
delete params.projectionType;
5858
}
59-
newIndex.IndexName = params.value + '-index';
60-
newIndex.KeySchema.push(this.generateKey(params));
59+
// Check for composite key
60+
if (params.values) {
61+
newIndex.IndexName = '';
62+
params.values.forEach(obj => {
63+
newIndex.IndexName += obj.value + '-';
64+
newIndex.KeySchema.push(this.generateKey(obj));
65+
});
66+
newIndex.IndexName += 'index';
67+
} else {
68+
newIndex.IndexName = params.value + '-index';
69+
newIndex.KeySchema.push(this.generateKey(params));
70+
}
6171
return newIndex;
6272
}
6373

@@ -143,7 +153,16 @@ export default class {
143153
let newTable = _.clone(tables.table, true);
144154
newTable.Table.TableName = this.schemas[version].tableName;
145155
_.each(this.schemas[version].indexes, (row) => {
146-
newTable.Table.AttributeDefinitions.push(this.generateDefinition(row));
156+
// Check for composite key
157+
if (row.values) {
158+
row.values.forEach(obj => {
159+
newTable.Table.AttributeDefinitions.push(this.generateDefinition(obj));
160+
});
161+
} else {
162+
newTable.Table.AttributeDefinitions.push(this.generateDefinition(row));
163+
}
164+
// Remove duplicates in AttributeDefinitions on AttributeName
165+
newTable.Table.AttributeDefinitions = _.uniq(newTable.Table.AttributeDefinitions, 'AttributeName');
147166
if (row.keytype === 'hash' || row.keytype === 'range') {
148167
newTable.Table.KeySchema.push(this.generateKey(row));
149168
} else if (row.keytype === 'secondary') {

test/src/index.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const failAdapter = new DynamoAdapter(dynamoConfig);
1919
const nogsiAdapter = new DynamoAdapter(dynamoConfig);
2020
const projectionAdapter = new DynamoAdapter(dynamoConfig);
2121
const includeAdapter = new DynamoAdapter(dynamoConfig);
22+
const compositeAdapter = new DynamoAdapter(dynamoConfig);
2223

2324
// Set the schemas
2425
numeric.setSchema(dbData.testNumericModel, '1');
@@ -27,6 +28,7 @@ failAdapter.setSchema(dbData.badModel, '1');
2728
nogsiAdapter.setSchema(dbData.nogsiModel, '1');
2829
projectionAdapter.setSchema(dbData.testProjectionModel, '1');
2930
includeAdapter.setSchema(dbData.testIncludeModel, '1');
31+
compositeAdapter.setSchema(dbData.testCompositeModel, '1');
3032

3133
const validate = (body) => {
3234
return new Promise((resolve, reject) => {
@@ -58,6 +60,19 @@ failAdapter.sanitize = sanitize;
5860
nogsiAdapter.sanitize = sanitize;
5961
projectionAdapter.sanitize = sanitize;
6062
includeAdapter.sanitize = sanitize;
63+
compositeAdapter.sanitize = sanitize;
64+
65+
describe('Composite Keys', () => {
66+
after(() => compositeAdapter.deleteTable({TableName: dbData.testCompositeModel.tableName}));
67+
it('creates a table with a composite key global secondary index', () => {
68+
return compositeAdapter.createTableFromModel().then((data) => {
69+
data.TableDescription.CreationDateTime = '';
70+
data.TableDescription.ProvisionedThroughput.LastDecreaseDateTime = '';
71+
data.TableDescription.ProvisionedThroughput.LastIncreaseDateTime = '';
72+
expect(data).to.deep.equal(dbData.testCompositeModelResult);
73+
});
74+
});
75+
});
6176

6277
describe('Non standard projection types', () => {
6378
it('Creates the KEYS_ONLY only table', (done) => {

test/src/test-data.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,86 @@ dbData.nogsiModel = {
166166
permissions: {}
167167
}
168168
};
169+
170+
dbData.testCompositeModel = {
171+
name: 'usercomposite',
172+
version: 1,
173+
autoCreate: false,
174+
indexes: [
175+
{ keytype: 'hash', value: 'id', type: 'N'},
176+
{ keytype: 'range', value: 'createdAt', type: 'S'},
177+
{ keytype: 'secondary', values: [{keytype: 'hash', value: 'login', type: 'S'}, {keytype: 'range', value: 'createdAt', type: 'S'}]}
178+
],
179+
tableName: 'usercomposites',
180+
schema: {
181+
permissions: {}
182+
}
183+
};
184+
185+
dbData.testCompositeModelResult = {
186+
'TableDescription': {
187+
'CreationDateTime': '',
188+
'AttributeDefinitions': [
189+
{
190+
'AttributeName': 'id',
191+
'AttributeType': 'N'
192+
},
193+
{
194+
'AttributeName': 'createdAt',
195+
'AttributeType': 'S'
196+
},
197+
{
198+
'AttributeName': 'login',
199+
'AttributeType': 'S'
200+
}
201+
],
202+
'TableArn': 'arn:aws:dynamodb:ddblocal:000000000000:table/usercomposites',
203+
'TableName': 'usercomposites',
204+
'TableSizeBytes': 0,
205+
'TableStatus': 'ACTIVE',
206+
'ItemCount': 0,
207+
'KeySchema': [
208+
{
209+
'AttributeName': 'id',
210+
'KeyType': 'HASH'
211+
},
212+
{
213+
'AttributeName': 'createdAt',
214+
'KeyType': 'RANGE'
215+
}
216+
],
217+
'ProvisionedThroughput': {
218+
'LastDecreaseDateTime': '',
219+
'LastIncreaseDateTime': '',
220+
'NumberOfDecreasesToday': 0,
221+
'ReadCapacityUnits': 1,
222+
'WriteCapacityUnits': 1
223+
},
224+
'GlobalSecondaryIndexes': [
225+
{
226+
'IndexArn': 'arn:aws:dynamodb:ddblocal:000000000000:table/usercomposites/index/login-createdAt-index',
227+
'IndexName': 'login-createdAt-index',
228+
'IndexSizeBytes': 0,
229+
'IndexStatus': 'ACTIVE',
230+
'ItemCount': 0,
231+
'KeySchema': [
232+
{
233+
'AttributeName': 'login',
234+
'KeyType': 'HASH'
235+
},
236+
{
237+
'AttributeName': 'createdAt',
238+
'KeyType': 'RANGE'
239+
}
240+
],
241+
'Projection': {
242+
'ProjectionType': 'ALL'
243+
},
244+
'ProvisionedThroughput': {
245+
'ReadCapacityUnits': 1,
246+
'WriteCapacityUnits': 1
247+
}
248+
}
249+
]
250+
}
251+
};

0 commit comments

Comments
 (0)