dynalite
Version:
An implementation of Amazon's DynamoDB built on LevelDB
182 lines (156 loc) • 7.12 kB
JavaScript
var once = require('once'),
db = require('../db')
module.exports = function query (store, data, cb) {
cb = once(cb)
store.getTable(data.TableName, function (err, table) {
if (err) return cb(err)
var keySchema = table.KeySchema, startKeyNames = keySchema.map(function (key) { return key.AttributeName }),
hashKey = startKeyNames[0], rangeKey = startKeyNames[1], fetchFromItemDb = false, isLocal
if (data.IndexName) {
var index = db.traverseIndexes(table, function (attr, type, index, isGlobal) {
if (index.IndexName == data.IndexName) {
isLocal = !isGlobal
return index
}
})
if (index == null) {
return cb(db.validationError('The table does not have the specified index: ' + data.IndexName))
}
if (!isLocal && data.ConsistentRead) {
return cb(db.validationError('Consistent reads are not supported on global secondary indexes'))
}
keySchema = index.KeySchema
fetchFromItemDb = data.Select == 'ALL_ATTRIBUTES' && index.Projection.ProjectionType != 'ALL'
keySchema.forEach(function (key) { if (!~startKeyNames.indexOf(key.AttributeName)) startKeyNames.push(key.AttributeName) })
hashKey = keySchema[0].AttributeName
rangeKey = keySchema[1] && keySchema[1].AttributeName
}
if (data.ExclusiveStartKey && Object.keys(data.ExclusiveStartKey).length != startKeyNames.length) {
return cb(db.validationError('The provided starting key is invalid'))
}
err = db.traverseKey(table, keySchema, function (attr, type, isHash) {
if (data.ExclusiveStartKey) {
if (!data.ExclusiveStartKey[attr]) {
return db.validationError('The provided starting key is invalid')
}
err = db.validateKeyPiece(data.ExclusiveStartKey, attr, type, isHash)
if (err) return err
}
if (isHash && keySchema.length == 1 && Object.keys(data.KeyConditions).length > 1) {
return db.validationError('Query key condition not supported')
}
if (!data.KeyConditions[attr]) {
if (isHash || Object.keys(data.KeyConditions).length > 1) {
return db.validationError('Query condition missed key schema element: ' + attr)
}
return
}
var comparisonOperator = data.KeyConditions[attr].ComparisonOperator
if (~[ 'NULL', 'NOT_NULL', 'NE', 'CONTAINS', 'NOT_CONTAINS', 'IN' ].indexOf(comparisonOperator)) {
return db.validationError('Attempted conditional constraint is not an indexable operation')
}
if (data.KeyConditions[attr].AttributeValueList.some(function (attrVal) { return attrVal[type] == null })) {
return db.validationError('One or more parameter values were invalid: Condition parameter type does not match schema type')
}
if (isHash && comparisonOperator != 'EQ') {
return db.validationError('Query key condition not supported')
}
})
if (err) return cb(err)
var hashType = Object.keys(data.KeyConditions[hashKey].AttributeValueList[0])[0]
var hashVal = data.KeyConditions[hashKey].AttributeValueList[0][hashType]
if (data.ExclusiveStartKey) {
var tableStartKey = table.KeySchema.reduce(function (obj, attr) {
obj[attr.AttributeName] = data.ExclusiveStartKey[attr.AttributeName]
return obj
}, {})
let invalid = db.validateKey(tableStartKey, table)
if (invalid != null) return cb(db.validationError('The provided starting key is invalid: ' + invalid.message))
if (!rangeKey || !data.KeyConditions[rangeKey]) {
if (data.ExclusiveStartKey[hashKey][hashType] != hashVal) {
return cb(db.validationError('The provided starting key is outside query boundaries based on provided conditions'))
}
}
else {
var matchesRange = db.compare(data.KeyConditions[rangeKey].ComparisonOperator,
data.ExclusiveStartKey[rangeKey], data.KeyConditions[rangeKey].AttributeValueList)
if (!matchesRange) {
return cb(db.validationError('The provided starting key does not match the range key predicate'))
}
if (data.ExclusiveStartKey[hashKey][hashType] != hashVal) {
return cb(db.validationError('The query can return at most one row and cannot be restarted'))
}
}
}
let invalid
invalid = db.validateKeyPaths((data._projection || {}).nestedPaths, table)
if (invalid != null) return cb(invalid)
if (data.QueryFilter || data._filter) {
var pathHeads = data.QueryFilter ? data.QueryFilter : data._filter.pathHeads
var propertyName = data.QueryFilter ? 'QueryFilter' : 'Filter Expression'
err = db.traverseKey(table, keySchema, function (attr) {
if (pathHeads[attr]) {
return db.validationError(propertyName + ' can only contain non-primary key attributes: ' +
'Primary key attribute: ' + attr)
}
})
if (err) return cb(err)
}
if (fetchFromItemDb && !isLocal) {
return cb(db.validationError('One or more parameter values were invalid: ' +
'Select type ALL_ATTRIBUTES is not supported for global secondary index ' +
data.IndexName + ' because its projection type is not ALL'))
}
invalid = db.validateKeyPaths((data._filter || {}).nestedPaths, table)
if (invalid != null) return cb(invalid)
var opts = { reverse: data.ScanIndexForward === false, limit: data.Limit ? data.Limit + 1 : -1 }
opts.gte = db.hashPrefix(hashVal, hashType) + '/' + db.toRangeStr(hashVal, hashType) + '/'
opts.lt = opts.gte + '~'
if (data.KeyConditions[rangeKey]) {
var rangeStrPrefix = db.toRangeStr(data.KeyConditions[rangeKey].AttributeValueList[0])
var rangeStr = rangeStrPrefix + '/'
var comp = data.KeyConditions[rangeKey].ComparisonOperator
if (comp == 'EQ') {
opts.gte += rangeStr
opts.lte = opts.gte + '~'
delete opts.lt
}
else if (comp == 'LT') {
opts.lt = opts.gte + rangeStr
}
else if (comp == 'LE') {
opts.lte = opts.gte + rangeStr + '~'
delete opts.lt
}
else if (comp == 'GT') {
opts.gt = opts.gte + rangeStr + '~'
delete opts.gte
}
else if (comp == 'GE') {
opts.gte += rangeStr
}
else if (comp == 'BEGINS_WITH') {
opts.lt = opts.gte + rangeStrPrefix + '~'
opts.gte += rangeStr
}
else if (comp == 'BETWEEN') {
opts.lte = opts.gte + db.toRangeStr(data.KeyConditions[rangeKey].AttributeValueList[1]) + '/~'
opts.gte += rangeStr
delete opts.lt
}
}
if (data.ExclusiveStartKey) {
var createKey = data.IndexName ? db.createIndexKey : db.createKey
var startKey = createKey(data.ExclusiveStartKey, table, keySchema)
if (data.ScanIndexForward === false) {
opts.lt = startKey
delete opts.lte
}
else {
opts.gt = startKey
delete opts.gte
}
}
db.queryTable(store, table, data, opts, isLocal, fetchFromItemDb, startKeyNames, cb)
})
}