UNPKG

7 kBJavaScriptView Raw
1var once = require('once'),
2 db = require('../db')
3
4module.exports = function query(store, data, cb) {
5 cb = once(cb)
6
7 store.getTable(data.TableName, function(err, table) {
8 if (err) return cb(err)
9
10 var keySchema = table.KeySchema, startKeyNames = keySchema.map(function(key) { return key.AttributeName }),
11 hashKey = startKeyNames[0], rangeKey = startKeyNames[1], fetchFromItemDb = false, isLocal
12
13 if (data.IndexName) {
14 var index = db.traverseIndexes(table, function(attr, type, index, isGlobal) {
15 if (index.IndexName == data.IndexName) {
16 isLocal = !isGlobal
17 return index
18 }
19 })
20 if (index == null) {
21 return cb(db.validationError('The table does not have the specified index: ' + data.IndexName))
22 }
23 if (!isLocal && data.ConsistentRead) {
24 return cb(db.validationError('Consistent reads are not supported on global secondary indexes'))
25 }
26 keySchema = index.KeySchema
27 fetchFromItemDb = data.Select == 'ALL_ATTRIBUTES' && index.Projection.ProjectionType != 'ALL'
28 keySchema.forEach(function(key) { if (!~startKeyNames.indexOf(key.AttributeName)) startKeyNames.push(key.AttributeName) })
29 hashKey = keySchema[0].AttributeName
30 rangeKey = keySchema[1] && keySchema[1].AttributeName
31 }
32
33 if (data.ExclusiveStartKey && Object.keys(data.ExclusiveStartKey).length != startKeyNames.length) {
34 return cb(db.validationError('The provided starting key is invalid'))
35 }
36
37 err = db.traverseKey(table, keySchema, function(attr, type, isHash) {
38 if (data.ExclusiveStartKey) {
39 if (!data.ExclusiveStartKey[attr]) {
40 return db.validationError('The provided starting key is invalid')
41 }
42 err = db.validateKeyPiece(data.ExclusiveStartKey, attr, type, isHash)
43 if (err) return err
44 }
45
46 if (isHash && keySchema.length == 1 && Object.keys(data.KeyConditions).length > 1) {
47 return db.validationError('Query key condition not supported')
48 }
49
50 if (!data.KeyConditions[attr]) {
51 if (isHash || Object.keys(data.KeyConditions).length > 1) {
52 return db.validationError('Query condition missed key schema element: ' + attr)
53 }
54 return
55 }
56
57 var comparisonOperator = data.KeyConditions[attr].ComparisonOperator
58
59 if (~['NULL', 'NOT_NULL', 'NE', 'CONTAINS', 'NOT_CONTAINS', 'IN'].indexOf(comparisonOperator)) {
60 return db.validationError('Attempted conditional constraint is not an indexable operation')
61 }
62
63 if (data.KeyConditions[attr].AttributeValueList.some(function(attrVal) { return attrVal[type] == null })) {
64 return db.validationError('One or more parameter values were invalid: Condition parameter type does not match schema type')
65 }
66
67 if (isHash && comparisonOperator != 'EQ') {
68 return db.validationError('Query key condition not supported')
69 }
70 })
71 if (err) return cb(err)
72
73 var hashType = Object.keys(data.KeyConditions[hashKey].AttributeValueList[0])[0]
74 var hashVal = data.KeyConditions[hashKey].AttributeValueList[0][hashType]
75
76 if (data.ExclusiveStartKey) {
77 var tableStartKey = table.KeySchema.reduce(function(obj, attr) {
78 obj[attr.AttributeName] = data.ExclusiveStartKey[attr.AttributeName]
79 return obj
80 }, {})
81 if ((err = db.validateKey(tableStartKey, table)) != null) {
82 return cb(db.validationError('The provided starting key is invalid: ' + err.message))
83 }
84
85 if (!rangeKey || !data.KeyConditions[rangeKey]) {
86 if (data.ExclusiveStartKey[hashKey][hashType] != hashVal) {
87 return cb(db.validationError('The provided starting key is outside query boundaries based on provided conditions'))
88 }
89 } else {
90 var matchesRange = db.compare(data.KeyConditions[rangeKey].ComparisonOperator,
91 data.ExclusiveStartKey[rangeKey], data.KeyConditions[rangeKey].AttributeValueList)
92 if (!matchesRange) {
93 return cb(db.validationError('The provided starting key does not match the range key predicate'))
94 }
95 if (data.ExclusiveStartKey[hashKey][hashType] != hashVal) {
96 return cb(db.validationError('The query can return at most one row and cannot be restarted'))
97 }
98 }
99 }
100
101 if ((err = db.validateKeyPaths((data._projection || {}).nestedPaths, table)) != null) return cb(err)
102
103 if (data.QueryFilter || data._filter) {
104 var pathHeads = data.QueryFilter ? data.QueryFilter : data._filter.pathHeads
105 var propertyName = data.QueryFilter ? 'QueryFilter' : 'Filter Expression'
106 err = db.traverseKey(table, keySchema, function(attr) {
107 if (pathHeads[attr]) {
108 return db.validationError(propertyName + ' can only contain non-primary key attributes: ' +
109 'Primary key attribute: ' + attr)
110 }
111 })
112 if (err) return cb(err)
113 }
114
115 if (fetchFromItemDb && !isLocal) {
116 return cb(db.validationError('One or more parameter values were invalid: ' +
117 'Select type ALL_ATTRIBUTES is not supported for global secondary index ' +
118 data.IndexName + ' because its projection type is not ALL'))
119 }
120
121 if ((err = db.validateKeyPaths((data._filter || {}).nestedPaths, table)) != null) return cb(err)
122
123 var opts = {reverse: data.ScanIndexForward === false, limit: data.Limit ? data.Limit + 1 : -1}
124
125 opts.gte = db.hashPrefix(hashVal, hashType) + '/' + db.toRangeStr(hashVal, hashType) + '/'
126 opts.lt = opts.gte + '~'
127
128 if (data.KeyConditions[rangeKey]) {
129 var rangeStrPrefix = db.toRangeStr(data.KeyConditions[rangeKey].AttributeValueList[0])
130 var rangeStr = rangeStrPrefix + '/'
131 var comp = data.KeyConditions[rangeKey].ComparisonOperator
132 if (comp == 'EQ') {
133 opts.gte += rangeStr
134 opts.lte = opts.gte + '~'
135 delete opts.lt
136 } else if (comp == 'LT') {
137 opts.lt = opts.gte + rangeStr
138 } else if (comp == 'LE') {
139 opts.lte = opts.gte + rangeStr + '~'
140 delete opts.lt
141 } else if (comp == 'GT') {
142 opts.gt = opts.gte + rangeStr + '~'
143 delete opts.gte
144 } else if (comp == 'GE') {
145 opts.gte += rangeStr
146 } else if (comp == 'BEGINS_WITH') {
147 opts.lt = opts.gte + rangeStrPrefix + '~'
148 opts.gte += rangeStr
149 } else if (comp == 'BETWEEN') {
150 opts.lte = opts.gte + db.toRangeStr(data.KeyConditions[rangeKey].AttributeValueList[1]) + '/~'
151 opts.gte += rangeStr
152 delete opts.lt
153 }
154 }
155
156 if (data.ExclusiveStartKey) {
157 var createKey = data.IndexName ? db.createIndexKey : db.createKey
158 var startKey = createKey(data.ExclusiveStartKey, table, keySchema)
159
160 if (data.ScanIndexForward === false) {
161 opts.lt = startKey
162 delete opts.lte
163 } else {
164 opts.gt = startKey
165 delete opts.gte
166 }
167 }
168
169 db.queryTable(store, table, data, opts, isLocal, fetchFromItemDb, startKeyNames, cb)
170 })
171}