11import { isEmpty } from 'lodash-es'
22import { dbClient as _client , createIndex } from './database-client.js'
33import logger from './logger.js'
4+ import { ValidationError } from './errors.js'
45
56const COLLECTIONS_INDEX = process . env [ 'COLLECTIONS_INDEX' ] || 'collections'
67const DEFAULT_INDICES = [ '*' , '-.*' , '-collections' ]
7- const LOGICAL_OP = {
8+ const OP = {
89 AND : 'and' ,
910 OR : 'or' ,
10- NOT : 'not'
11- }
12- const COMPARISON_OP = {
11+ NOT : 'not' ,
1312 EQ : '=' ,
1413 NEQ : '<>' ,
1514 LT : '<' ,
1615 LTE : '<=' ,
1716 GT : '>' ,
1817 GTE : '>=' ,
19- IS_NULL : 'isNull'
18+ IS_NULL : 'isNull' ,
19+ IN : 'in' ,
20+ BETWEEN : 'between' ,
21+ LIKE : 'like' ,
2022}
2123const RANGE_TRANSLATION = {
2224 '<' : 'lt' ,
@@ -213,34 +215,34 @@ function buildFilterExtQuery(filter) {
213215
214216 switch ( filter . op ) {
215217 // recursive cases
216- case LOGICAL_OP . AND :
218+ case OP . AND :
217219 return {
218220 bool : {
219221 filter : filter . args . map ( buildFilterExtQuery )
220222 }
221223 }
222- case LOGICAL_OP . OR :
224+ case OP . OR :
223225 return {
224226 bool : {
225227 should : filter . args . map ( buildFilterExtQuery ) ,
226228 minimum_should_match : 1
227229 }
228230 }
229- case LOGICAL_OP . NOT :
231+ case OP . NOT :
230232 return {
231233 bool : {
232234 must_not : filter . args . map ( buildFilterExtQuery )
233235 }
234236 }
235237
236238 // direct cases
237- case COMPARISON_OP . EQ :
239+ case OP . EQ :
238240 return {
239241 term : {
240242 [ cql2Field ] : cql2Value
241243 }
242244 }
243- case COMPARISON_OP . NEQ :
245+ case OP . NEQ :
244246 return {
245247 bool : {
246248 must_not : [
@@ -252,7 +254,7 @@ function buildFilterExtQuery(filter) {
252254 ]
253255 }
254256 }
255- case COMPARISON_OP . IS_NULL :
257+ case OP . IS_NULL :
256258 return {
257259 bool : {
258260 must_not : [
@@ -266,17 +268,61 @@ function buildFilterExtQuery(filter) {
266268 }
267269
268270 // range cases
269- case COMPARISON_OP . LT :
270- case COMPARISON_OP . LTE :
271- case COMPARISON_OP . GT :
272- case COMPARISON_OP . GTE :
271+ case OP . LT :
272+ case OP . LTE :
273+ case OP . GT :
274+ case OP . GTE :
273275 return {
274276 range : {
275277 [ cql2Field ] : {
276278 [ RANGE_TRANSLATION [ filter . op ] ] : cql2Value
277279 }
278280 }
279281 }
282+ case OP . IN :
283+ if ( ! Array . isArray ( cql2Value ) || cql2Value . length === 0 ) {
284+ throw new ValidationError ( "Operand for 'in' must be a non-empty array" )
285+ }
286+ if ( ! cql2Value . every ( ( x ) => x !== Object ( x ) ) ) {
287+ throw new ValidationError (
288+ "Operand for 'in' must contain only string, number, or boolean types"
289+ )
290+ }
291+
292+ return {
293+ terms : {
294+ [ cql2Field ] : cql2Value
295+ }
296+ }
297+ case OP . BETWEEN :
298+ if ( filter . args . length < 3 ) {
299+ throw new ValidationError ( "Two operands must be provided for the 'between' operator" )
300+ }
301+
302+ // eslint-disable-next-line no-case-declarations
303+ const cql2Value2 = filter . args [ 2 ]
304+ if ( ! ( typeof cql2Value === 'number' && typeof cql2Value2 === 'number' ) ) {
305+ throw new ValidationError ( "Operands for 'between' must be numbers" )
306+ }
307+
308+ if ( cql2Value > cql2Value2 ) {
309+ throw new ValidationError (
310+ "For the 'between' operator, the first operand must be less than or equal "
311+ + 'to the second operand'
312+ )
313+ }
314+
315+ return {
316+ range : {
317+ [ cql2Field ] : {
318+ gte : cql2Value ,
319+ lte : cql2Value2
320+ }
321+ }
322+ }
323+
324+ case OP . LIKE :
325+ throw new ValidationError ( "The 'like' operator is not currently supported" )
280326
281327 // should not get here
282328 default :
0 commit comments