From 11cf61726211647da41f63f4c65da8c3d9dee2f2 Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Tue, 20 Sep 2022 17:50:56 +0800 Subject: [PATCH 1/2] feat: add single level OR query --- src/query/compileQuery.ts | 14 +++++++-- src/query/executeQuery.ts | 4 ++- src/query/queryTypes.ts | 12 +++++++- test/query/or.test.ts | 62 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 test/query/or.test.ts diff --git a/src/query/compileQuery.ts b/src/query/compileQuery.ts index 5f6d3acd..3d500f6e 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 7933d322..932c8eac 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 86de35b9..77cf14e8 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 00000000..bd4e6c9d --- /dev/null +++ b/test/query/or.test.ts @@ -0,0 +1,62 @@ +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 doorsAndWinter = db.book.findMany({ + where: { + OR: [ + { + title: { + contains: "Doors", + }, + }, + { + title: { + contains: "Winter", + }, + }, + ], + }, + }) + const doorsAndWinterTitles = doorsAndWinter.map((book) => book.title) + expect(doorsAndWinterTitles).toEqual([ + 'The Winds of Winter', + 'The Doors of Stone', + ]) +}) \ No newline at end of file From 96d88e55bf74943908a24f9921f89bf8d42ee055 Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Thu, 22 Sep 2022 11:26:51 +0800 Subject: [PATCH 2/2] feat: add nested OR query to the test --- test/query/or.test.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/test/query/or.test.ts b/test/query/or.test.ts index bd4e6c9d..4b272a1f 100644 --- a/test/query/or.test.ts +++ b/test/query/or.test.ts @@ -38,25 +38,35 @@ const setup = () => { test('queries entities based on a boolean value', () => { const db = setup() - const doorsAndWinter = db.book.findMany({ + const books = db.book.findMany({ where: { OR: [ { - title: { - contains: "Doors", - }, + OR: [ + { + title: { + contains: 'Doors', + }, + }, + { + title: { + equals: 'New Spring', + }, + }, + ], }, { title: { - contains: "Winter", + contains: 'Winter', }, }, ], }, }) - const doorsAndWinterTitles = doorsAndWinter.map((book) => book.title) - expect(doorsAndWinterTitles).toEqual([ + 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