2021-08-03 16:55:52 +00:00
|
|
|
//
|
|
|
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
|
|
|
// Copyright © 2021 Hardcore Engineering Inc.
|
|
|
|
//
|
|
|
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License. You may
|
|
|
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
//
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
//
|
|
|
|
|
|
|
|
import type { Doc } from './classes'
|
2022-01-13 09:07:41 +00:00
|
|
|
import { getObjectValue } from './objvalue'
|
2022-02-16 09:02:31 +00:00
|
|
|
import { escapeLikeForRegexp } from './utils'
|
2021-08-03 16:55:52 +00:00
|
|
|
|
2023-01-04 17:58:54 +00:00
|
|
|
import { deepEqual } from 'fast-equals'
|
|
|
|
|
2021-08-03 16:55:52 +00:00
|
|
|
type Predicate = (docs: Doc[]) => Doc[]
|
|
|
|
type PredicateFactory = (pred: any, propertyKey: string) => Predicate
|
|
|
|
|
2021-11-18 12:48:05 +00:00
|
|
|
type ExecPredicate = (value: any) => boolean
|
|
|
|
|
|
|
|
function execPredicate (docs: Doc[], propertyKey: string, pred: ExecPredicate): Doc[] {
|
|
|
|
const result: Doc[] = []
|
|
|
|
for (const doc of docs) {
|
2022-01-13 09:07:41 +00:00
|
|
|
const value = getObjectValue(propertyKey, doc)
|
2021-11-18 12:48:05 +00:00
|
|
|
if (pred(value)) {
|
|
|
|
result.push(doc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-08-03 16:55:52 +00:00
|
|
|
const predicates: Record<string, PredicateFactory> = {
|
2021-11-18 12:48:05 +00:00
|
|
|
$in: (o, propertyKey) => {
|
2021-08-03 16:55:52 +00:00
|
|
|
if (!Array.isArray(o)) {
|
|
|
|
throw new Error('$in predicate requires array')
|
|
|
|
}
|
2023-05-31 18:46:24 +00:00
|
|
|
return (docs) =>
|
|
|
|
execPredicate(docs, propertyKey, (value) => {
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
return o.some((p) => value.includes(p))
|
|
|
|
} else {
|
|
|
|
// eslint-disable-next-line eqeqeq
|
|
|
|
return o.some((p) => p == value)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
$all: (o, propertyKey) => {
|
|
|
|
if (!Array.isArray(o)) {
|
|
|
|
throw new Error('$all predicate requires array')
|
|
|
|
}
|
|
|
|
return (docs) =>
|
|
|
|
execPredicate(docs, propertyKey, (value: any[]) => {
|
|
|
|
for (const val of o) {
|
|
|
|
if (!value.includes(val)) return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2021-08-03 16:55:52 +00:00
|
|
|
},
|
2022-03-14 09:05:02 +00:00
|
|
|
$nin: (o, propertyKey) => {
|
|
|
|
if (!Array.isArray(o)) {
|
|
|
|
throw new Error('$nin predicate requires array')
|
|
|
|
}
|
2022-05-20 07:17:57 +00:00
|
|
|
// eslint-disable-next-line eqeqeq
|
|
|
|
return (docs) => execPredicate(docs, propertyKey, (value) => !o.some((p) => p == value))
|
2022-03-14 09:05:02 +00:00
|
|
|
},
|
2021-08-03 16:55:52 +00:00
|
|
|
|
|
|
|
$like: (query: string, propertyKey: string): Predicate => {
|
2022-04-16 03:00:45 +00:00
|
|
|
const searchString = query
|
|
|
|
.split('%')
|
|
|
|
.map((it) => escapeLikeForRegexp(it))
|
|
|
|
.join('.*')
|
2021-10-01 13:07:09 +00:00
|
|
|
const regex = RegExp(`^${searchString}$`, 'i')
|
2022-01-13 09:07:41 +00:00
|
|
|
|
|
|
|
return (docs) => execPredicate(docs, propertyKey, (value) => regex.test(value))
|
2021-10-01 10:44:57 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
$regex: (o: { $regex: string, $options: string }, propertyKey: string): Predicate => {
|
|
|
|
const re = new RegExp(o.$regex, o.$options)
|
2022-01-13 09:07:41 +00:00
|
|
|
return (docs) => execPredicate(docs, propertyKey, (value) => value.match(re) !== null)
|
2022-04-16 03:00:45 +00:00
|
|
|
},
|
|
|
|
$gt: (o, propertyKey) => {
|
|
|
|
return (docs) => execPredicate(docs, propertyKey, (value) => value > o)
|
|
|
|
},
|
|
|
|
$gte: (o, propertyKey) => {
|
|
|
|
return (docs) => execPredicate(docs, propertyKey, (value) => value >= o)
|
|
|
|
},
|
|
|
|
$lt: (o, propertyKey) => {
|
|
|
|
return (docs) => execPredicate(docs, propertyKey, (value) => value < o)
|
|
|
|
},
|
|
|
|
$lte: (o, propertyKey) => {
|
|
|
|
return (docs) => execPredicate(docs, propertyKey, (value) => value <= o)
|
2022-05-28 04:05:36 +00:00
|
|
|
},
|
2022-05-28 07:33:11 +00:00
|
|
|
$exists: (o, propertyKey) => {
|
2022-05-28 04:05:36 +00:00
|
|
|
return (docs) => execPredicate(docs, propertyKey, (value) => (value !== undefined) === o)
|
2023-01-04 17:58:54 +00:00
|
|
|
},
|
|
|
|
$ne: (o, propertyKey) => {
|
|
|
|
// eslint-disable-next-line eqeqeq
|
2023-09-25 09:54:05 +00:00
|
|
|
return (docs) => execPredicate(docs, propertyKey, (value) => (o != null ? !deepEqual(o, value) : value != null))
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isPredicate (o: Record<string, any>): boolean {
|
2022-04-16 03:00:45 +00:00
|
|
|
if (o === null || typeof o !== 'object') {
|
|
|
|
return false
|
|
|
|
}
|
2021-08-03 16:55:52 +00:00
|
|
|
const keys = Object.keys(o)
|
|
|
|
return keys.length > 0 && keys.every((key) => key.startsWith('$'))
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createPredicates (o: Record<string, any>, propertyKey: string): Predicate[] {
|
|
|
|
const keys = Object.keys(o)
|
|
|
|
const result: Predicate[] = []
|
|
|
|
for (const key of keys) {
|
|
|
|
const factory = predicates[key]
|
|
|
|
if (factory === undefined) throw new Error('unknown predicate: ' + keys[0])
|
|
|
|
result.push(factory(o[key], propertyKey))
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|