Comparing version 0.4.2 to 0.5.0
@@ -28,2 +28,6 @@ 'use strict'; | ||
internals.isUsingGlobalIndex = function (query) { | ||
return query.request.IndexName && query.table.schema.globalIndexes[query.request.IndexName]; | ||
}; | ||
var Query = module.exports = function (hashKey, table, serializer) { | ||
@@ -141,2 +145,6 @@ this.hashKey = hashKey; | ||
if(internals.isUsingGlobalIndex(this)) { | ||
key = this.table.schema.globalIndexes[this.request.IndexName].hashKey; | ||
} | ||
return this.buildAttributeValue(key, this.hashKey, 'EQ'); | ||
@@ -143,0 +151,0 @@ }; |
@@ -49,2 +49,3 @@ 'use strict'; | ||
this.secondaryIndexes = []; | ||
this.globalIndexes = {}; | ||
}; | ||
@@ -137,2 +138,8 @@ | ||
Schema.prototype.globalIndex = function (name, keys) { | ||
var schema = this; | ||
schema.globalIndexes[name] = keys; | ||
}; | ||
Schema.prototype.defaults = function () { | ||
@@ -139,0 +146,0 @@ return _.reduce(this.attrs, function (result, attr, key) { |
@@ -290,2 +290,16 @@ 'use strict'; | ||
internals.globalIndex = function (indexName, params) { | ||
var projection = params.Projection || { ProjectionType : 'ALL' }; | ||
return { | ||
IndexName : indexName, | ||
KeySchema : internals.keySchema(params.hashKey, params.rangeKey), | ||
Projection : projection, | ||
ProvisionedThroughput: { | ||
ReadCapacityUnits: params.readCapacity || 1, | ||
WriteCapacityUnits: params.writeCapacity || 1 | ||
} | ||
}; | ||
}; | ||
Table.prototype.createTable = function (options, callback) { | ||
@@ -313,2 +327,17 @@ var self = this; | ||
var globalSecondaryIndexes = []; | ||
_.forEach(self.schema.globalIndexes, function (params, indexName) { | ||
if(!_.find(attributeDefinitions, { 'AttributeName': params.hashKey })) { | ||
attributeDefinitions.push(internals.attributeDefinition(self.schema, params.hashKey)); | ||
} | ||
if(params.rangeKey && !_.find(attributeDefinitions, { 'AttributeName': params.rangeKey })) { | ||
attributeDefinitions.push(internals.attributeDefinition(self.schema, params.rangeKey)); | ||
} | ||
globalSecondaryIndexes.push(internals.globalIndex(indexName, params)); | ||
}); | ||
var keySchema = internals.keySchema(self.schema.hashKey, self.schema.rangeKey); | ||
@@ -330,2 +359,6 @@ | ||
if(globalSecondaryIndexes.length >= 1) { | ||
params.GlobalSecondaryIndexes = globalSecondaryIndexes; | ||
} | ||
self.dynamodb.createTable(params, callback); | ||
@@ -332,0 +365,0 @@ }; |
{ | ||
"name": "vogels", | ||
"version": "0.4.2", | ||
"version": "0.5.0", | ||
"author": "Ryan Fitzgerald <ryan@codebrewstudios.com>", | ||
@@ -20,19 +20,19 @@ "description": "DynamoDB data mapper", | ||
"dependencies": { | ||
"aws-sdk": "~1.4.0", | ||
"lodash": "~1.3.1", | ||
"aws-sdk": "~2.0.0-rc3", | ||
"lodash": "~2.4.1", | ||
"joi": "~0.3.5", | ||
"node-uuid": "~1.4.0", | ||
"async": "~0.2.8", | ||
"readable-stream": "~1.0.2" | ||
"async": "~0.2.9", | ||
"readable-stream": "~1.1.9" | ||
}, | ||
"devDependencies": { | ||
"mocha": "~1.9.0", | ||
"chai": "~1.6.0", | ||
"mocha": "~1.15.0", | ||
"chai": "~1.8.0", | ||
"grunt-simple-mocha": "~0.4.0", | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-jshint": "~0.5.1", | ||
"grunt": "~0.4.2", | ||
"grunt-contrib-jshint": "~0.7.2", | ||
"grunt-regarde": "~0.1.1", | ||
"sinon": "~1.7.0", | ||
"grunt-cli": "~0.1.7" | ||
"grunt-cli": "~0.1.11" | ||
} | ||
} |
@@ -10,3 +10,4 @@ # vogels [](https://travis-ci.org/ryanfitz/vogels) | ||
* [Autogenerating UUIDs](#uuid) | ||
* [Secondary Indexes](#secondary-indexes) | ||
* [Global Secondary Indexes](#global-indexes) | ||
* [Local Secondary Indexes](#local-secondary-indexes) | ||
* [Parallel Scans](#parallel-scan) | ||
@@ -394,6 +395,66 @@ | ||
#### Secondary Indexes | ||
#### Global Indexes | ||
First, define a model using secondard indexes | ||
```js | ||
var BlogPost = vogels.define('GameScore', function (schema) { | ||
schema.String('userId', {hashKey: true}); | ||
schema.String('gameTitle', {rangeKey: true}); | ||
schema.Number('topScore'); | ||
schema.Date('topScoreDateTime'); | ||
schema.Number('wins'); | ||
schema.Number('losses'); | ||
schema.globalIndex('GameTitleIndex', { hashKey: 'gameTitle', rangeKey: 'topScore'}); | ||
}); | ||
``` | ||
Now we can query against the global index | ||
```js | ||
BlogPost | ||
.query('Galaxy Invaders') | ||
.usingIndex('GameTitleIndex') | ||
.descending() | ||
.exec(callback); | ||
``` | ||
When can also configure the attributes projected into the index. | ||
By default all attributes will be projected when no Projection pramater is | ||
present | ||
```js | ||
var GameScore = vogels.define('GameScore', function (schema) { | ||
schema.String('userId', {hashKey: true}); | ||
schema.String('gameTitle', {rangeKey: true}); | ||
schema.Number('topScore'); | ||
schema.Date('topScoreDateTime'); | ||
schema.Number('wins'); | ||
schema.Number('losses'); | ||
schema.globalIndex('GameTitleIndex', { | ||
hashKey: 'gameTitle', | ||
rangeKey: 'topScore', | ||
Projection: { NonKeyAttributes: [ 'wins' ], ProjectionType: 'INCLUDE' } //optional, defaults to ALL | ||
}); | ||
}); | ||
``` | ||
Filter items against the configured rangekey for the global index. | ||
```js | ||
`GameScore | ||
.query('Galaxy Invaders') | ||
.usingIndex('GameTitleIndex') | ||
.where('topScore').gt(1000) | ||
.descending() | ||
.exec(function (err, data) { | ||
console.log(_.map(data.Items, JSON.stringify)); | ||
}); | ||
``` | ||
#### Local Secondary Indexes | ||
First, define a model using a local secondary index | ||
```js | ||
var BlogPost = vogels.define('Account', function (schema) { | ||
@@ -400,0 +461,0 @@ schema.String('email', {hashKey: true}); |
@@ -31,4 +31,3 @@ 'use strict'; | ||
new Query('tim', table, serializer).exec(function (err, results) { | ||
console.log(err, results); | ||
results.should.eql({Items: [], Count: 0}); | ||
done(); | ||
@@ -65,2 +64,17 @@ }); | ||
it('should create key condition for global index hash key', function () { | ||
schema.String('name', {hashKey: true}); | ||
schema.String('email', {rangeKey: true}); | ||
schema.Number('age'); | ||
schema.globalIndex('UserAgeIndex', {hashKey: 'age'}); | ||
serializer.serializeItem.returns({age: {N: '18'}}); | ||
var query = new Query(18, table, serializer).usingIndex('UserAgeIndex'); | ||
query.buildRequest(); | ||
query.request.IndexName.should.equal('UserAgeIndex'); | ||
query.request.KeyConditions.age.should.eql({AttributeValueList: [{N: '18'}], ComparisonOperator: 'EQ'}); | ||
}); | ||
}); | ||
@@ -67,0 +81,0 @@ |
@@ -233,2 +233,16 @@ 'use strict'; | ||
}); | ||
describe('#globalIndex', function () { | ||
it('should set globalIndexes', function () { | ||
schema.String('userId', {hashKey: true}); | ||
schema.String('gameTitle', {rangeKey: true}); | ||
schema.Number('topScore'); | ||
schema.globalIndex('GameTitleIndex', {hashKey: 'gameTitle', rangeKey : 'topScore'}); | ||
schema.globalIndexes.should.include.keys('GameTitleIndex'); | ||
}); | ||
}); | ||
}); |
@@ -659,2 +659,98 @@ 'use strict'; | ||
it('should create table with global secondary index', function (done) { | ||
schema.String('userId', {hashKey: true}); | ||
schema.String('gameTitle', {rangeKey: true}); | ||
schema.Number('topScore'); | ||
schema.globalIndex('GameTitleIndex', {hashKey: 'gameTitle', rangeKey : 'topScore'}); | ||
table = new Table('gameScores', schema, serializer, dynamodb); | ||
var request = { | ||
TableName: 'gameScores', | ||
AttributeDefinitions : [ | ||
{ AttributeName: 'userId', AttributeType: 'S' }, | ||
{ AttributeName: 'gameTitle', AttributeType: 'S' }, | ||
{ AttributeName: 'topScore', AttributeType: 'N' } | ||
], | ||
KeySchema: [ | ||
{ AttributeName: 'userId', KeyType: 'HASH' }, | ||
{ AttributeName: 'gameTitle', KeyType: 'RANGE' } | ||
], | ||
GlobalSecondaryIndexes : [ | ||
{ | ||
IndexName : 'GameTitleIndex', | ||
KeySchema: [ | ||
{ AttributeName: 'gameTitle', KeyType: 'HASH' }, | ||
{ AttributeName: 'topScore', KeyType: 'RANGE' } | ||
], | ||
Projection : { | ||
ProjectionType : 'ALL' | ||
}, | ||
ProvisionedThroughput: { ReadCapacityUnits: 1, WriteCapacityUnits: 1 } | ||
} | ||
], | ||
ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 } | ||
}; | ||
dynamodb.createTable.yields(null, {}); | ||
table.createTable({readCapacity : 5, writeCapacity: 5}, function (err) { | ||
expect(err).to.be.null; | ||
dynamodb.createTable.calledWith(request).should.be.true; | ||
done(); | ||
}); | ||
}); | ||
it('should create table with global secondary index', function (done) { | ||
schema.String('userId', {hashKey: true}); | ||
schema.String('gameTitle', {rangeKey: true}); | ||
schema.Number('topScore'); | ||
schema.globalIndex('GameTitleIndex', { | ||
hashKey: 'gameTitle', | ||
rangeKey : 'topScore', | ||
readCapacity: 10, | ||
writeCapacity: 5, | ||
Projection: { NonKeyAttributes: [ 'wins' ], ProjectionType: 'INCLUDE' } | ||
}); | ||
table = new Table('gameScores', schema, serializer, dynamodb); | ||
var request = { | ||
TableName: 'gameScores', | ||
AttributeDefinitions : [ | ||
{ AttributeName: 'userId', AttributeType: 'S' }, | ||
{ AttributeName: 'gameTitle', AttributeType: 'S' }, | ||
{ AttributeName: 'topScore', AttributeType: 'N' } | ||
], | ||
KeySchema: [ | ||
{ AttributeName: 'userId', KeyType: 'HASH' }, | ||
{ AttributeName: 'gameTitle', KeyType: 'RANGE' } | ||
], | ||
GlobalSecondaryIndexes : [ | ||
{ | ||
IndexName : 'GameTitleIndex', | ||
KeySchema: [ | ||
{ AttributeName: 'gameTitle', KeyType: 'HASH' }, | ||
{ AttributeName: 'topScore', KeyType: 'RANGE' } | ||
], | ||
Projection: { | ||
NonKeyAttributes: [ 'wins' ], | ||
ProjectionType: 'INCLUDE' | ||
}, | ||
ProvisionedThroughput: { ReadCapacityUnits: 10, WriteCapacityUnits: 5 } | ||
} | ||
], | ||
ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 } | ||
}; | ||
dynamodb.createTable.yields(null, {}); | ||
table.createTable({readCapacity : 5, writeCapacity: 5}, function (err) { | ||
expect(err).to.be.null; | ||
dynamodb.createTable.calledWith(request).should.be.true; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -661,0 +757,0 @@ |
151269
42
3544
786
12
+ Addedaws-sdk@2.0.31(transitive)
+ Addedlodash@2.4.2(transitive)
+ Addedreadable-stream@1.1.14(transitive)
+ Addedsax@0.4.2(transitive)
+ Addedxml2js@0.2.6(transitive)
+ Addedxmlbuilder@0.4.2(transitive)
- Removedaws-sdk@1.4.1(transitive)
- Removedlodash@1.3.1(transitive)
- Removedreadable-stream@1.0.34(transitive)
- Removedsax@1.4.1(transitive)
- Removedxml2js@0.2.4(transitive)
- Removedxmlbuilder@15.1.1(transitive)
Updatedasync@~0.2.9
Updatedaws-sdk@~2.0.0-rc3
Updatedlodash@~2.4.1
Updatedreadable-stream@~1.1.9