diff --git a/src/query/compileQuery.ts b/src/query/compileQuery.ts index 5f6d3ac..3d500f6 100644 --- a/src/query/compileQuery.ts +++ b/src/query/compileQuery.ts @@ -1,6 +1,6 @@ import { debug } from 'debug' import { invariant } from 'outvariant' -import { ComparatorFn, QuerySelector } from './queryTypes' +import { ComparatorFn, isOrQuery, QuerySelector } from './queryTypes' import { getComparatorsForValue } from './getComparatorsForValue' import { isObject } from '../utils/isObject' @@ -14,8 +14,18 @@ export function compileQuery>( query: QuerySelector, ) { log('%j', query) - return (data: Data): boolean => { + if (isOrQuery(query.where)) { + const { OR: orConditions } = query.where; + for (const condition of orConditions) { + const subQuery = compileQuery({ where: condition }) + if (subQuery(data)) { + return true; + } + } + return false; + } + return Object.entries(query.where) .map(([property, queryChunk]) => { const actualValue = data[property] diff --git a/src/query/executeQuery.ts b/src/query/executeQuery.ts index 7933d32..932c8ea 100644 --- a/src/query/executeQuery.ts +++ b/src/query/executeQuery.ts @@ -3,6 +3,7 @@ import { Entity, PrimaryKeyType, PRIMARY_KEY } from '../glossary' import { compileQuery } from './compileQuery' import { BulkQueryOptions, + isOrQuery, QuerySelector, WeakQuerySelector, } from './queryTypes' @@ -57,8 +58,9 @@ export function executeQuery( const records = db.getModel(modelName) // Reduce the query scope if there's a query by primary key of the model. + const queryWhere = query.where || {} const { [primaryKey]: primaryKeyComparator, ...restQueries } = - query.where || {} + isOrQuery(queryWhere) ? { [primaryKey]: undefined, ...queryWhere } : queryWhere log('primary key query', primaryKeyComparator) const scopedRecords = primaryKeyComparator diff --git a/src/query/queryTypes.ts b/src/query/queryTypes.ts index 86de35b..77cf14e 100644 --- a/src/query/queryTypes.ts +++ b/src/query/queryTypes.ts @@ -29,10 +29,20 @@ export type RecursiveQuerySelectorWhere = } : never -export type QuerySelectorWhere = { +export type NormalQuerySelectorWhere = { [Key in keyof EntityType]?: RecursiveQuerySelectorWhere } +export type OrQuerySelectorWhere = { + OR: Array> +} + +export function isOrQuery(queryWhere: QuerySelectorWhere): queryWhere is OrQuerySelectorWhere { + return (queryWhere as OrQuerySelectorWhere).OR !== undefined; +} + +export type QuerySelectorWhere = OrQuerySelectorWhere | NormalQuerySelectorWhere + export interface WeakQuerySelectorWhere { [key: string]: Partial> } diff --git a/test/query/or.test.ts b/test/query/or.test.ts new file mode 100644 index 0000000..4b272a1 --- /dev/null +++ b/test/query/or.test.ts @@ -0,0 +1,72 @@ +import { faker } from '@faker-js/faker' +import { factory, primaryKey, nullable } from '../../src' + +const setup = () => { + const db = factory({ + book: { + id: primaryKey(faker.datatype.uuid), + title: String, + published: Boolean, + finished: nullable(() => null), + }, + }) + + db.book.create({ + title: 'The Winds of Winter', + published: false, + finished: false, + }) + db.book.create({ + title: 'New Spring', + published: true, + finished: true, + }) + db.book.create({ + title: 'The Doors of Stone', + published: false, + finished: null, // Who knows with Patrick? + }) + db.book.create({ + title: 'The Fellowship of the Ring', + published: true, + finished: true, + }) + + return db +} + +test('queries entities based on a boolean value', () => { + const db = setup() + + const books = db.book.findMany({ + where: { + OR: [ + { + OR: [ + { + title: { + contains: 'Doors', + }, + }, + { + title: { + equals: 'New Spring', + }, + }, + ], + }, + { + title: { + contains: 'Winter', + }, + }, + ], + }, + }) + const bookTitles = books.map((book) => book.title) + expect(bookTitles).toEqual([ + 'The Winds of Winter', + 'New Spring', + 'The Doors of Stone', + ]) +}) \ No newline at end of file