Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions features/search.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Feature: Search Features

Scenario: A property search with ors
Given an orm is setup
Given ModelList1 is created and inserted into the database
When search named OrPropertySearch is executed on model named ModelA
Then 3 instances are found

134 changes: 133 additions & 1 deletion features/step_definitions/steps.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import { assert } from 'chai'
import { Given, When, Then } from '@cucumber/cucumber'
import { datastoreAdapter } from '../../src'
import { createOrm, Orm, PrimaryKeyUuidProperty, queryBuilder, TextProperty } from 'functional-models'
import {
createOrm,
DatetimeProperty,
IntegerProperty,
Model,
ModelType,
ModelWithReferencesConstructorProps,
Orm,
OrmModelInstance,
PrimaryKeyUuidProperty,
queryBuilder,
TextProperty,
} from 'functional-models'

const SeedData = {
SeedData1: () => {
Expand Down Expand Up @@ -60,6 +72,91 @@ const Models = {
}
}

const MODELS: Record<
string,
(props: ModelWithReferencesConstructorProps) => {
models: readonly ModelType<any>[]
instances: readonly OrmModelInstance<any>[]
}
> = {
ModelList1: ({ Model, fetcher }) => {
const ModelA = Model({
pluralName: 'ModelA',
namespace: 'functional-models-orm-memory',
properties: {
id: PrimaryKeyUuidProperty(),
name: TextProperty({ required: true }),
age: IntegerProperty({ required: true }),
datetime: DatetimeProperty(),
},
})

return {
models: [ModelA] as ModelType<any>[],
instances: [
ModelA.create({
id: 'edf73dba-216a-4e10-a38f-398a4b38350a',
name: 'name-2',
age: 2,
}),
ModelA.create({
id: '2c3e6547-2d6b-44c3-ad2c-1220a3d305be',
name: 'name-3',
age: 10,
datetime: new Date('2020-02-01T00:00:00.000Z'),
}),
ModelA.create({
id: 'ed1dc8ff-fdc5-401c-a229-8566a418ceb5',
name: 'name-1',
age: 1,
datetime: new Date('2020-01-01T00:00:00.000Z'),
}),
ModelA.create({
name: 'name-4',
age: 15,
datetime: new Date('2020-03-01T00:00:00.000Z'),
}),
ModelA.create({
name: 'name-5',
age: 20,
}),
ModelA.create({
name: 'name-7',
age: 20,
}),
ModelA.create({
name: 'name-6',
age: 20,
}),
ModelA.create({
name: 'name-9',
age: 30,
}),
ModelA.create({
name: 'name-10',
age: 100,
datetime: new Date('2020-05-01T00:00:00.000Z'),
}),
ModelA.create({
name: 'name-8',
age: 50,
}),
] as OrmModelInstance<any>[],
}
},
}

const SEARCHES = {
OrPropertySearch: () =>
queryBuilder()
.property('name', 'name-8')
.or()
.property('name', 'name-1')
.or()
.property('name', 'name-10')
.compile(),
}

Given('a datastore using seed data {word} is created', function(key: string) {
const seedData = SeedData[key]()
this.datastoreAdapter = datastoreAdapter.create({seedData})
Expand Down Expand Up @@ -94,3 +191,38 @@ Then('the result matches {word}', function(dataKey: string) {
const actual = this.result
assert.deepEqual(actual, expected)
})

Given('an orm is setup', function () {
this.datastoreAdapter = datastoreAdapter.create()
this.orm = createOrm({ datastoreAdapter: this.datastoreAdapter })
})

Given(
'{word} is created and inserted into the database',
async function (key: string) {
const result = MODELS[key](this.orm)
this.models = result.models
// @ts-ignore
await result.instances.reduce(async (accP, i) => {
await accP
return i.save()
}, Promise.resolve())
}
)

When(
'search named {word} is executed on model named {word}',
async function (key: string, modelPluralName: string) {
const search = SEARCHES[key]()
const model = this.models.find(
x => x.getModelDefinition().pluralName === modelPluralName
)
this.result = await model.search(search)
}
)

Then(/^(\d+) instances are found$/, function (count: number) {
const actual = this.result.instances.length
const expected = count
assert.equal(actual, expected)
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "functional-models-orm-memory",
"version": "3.0.4",
"version": "3.0.5",
"description": "An in-memory datastore adapter for functional-models",
"main": "index.js",
"types": "index.d.ts",
Expand Down
16 changes: 15 additions & 1 deletion src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ const _allCheck = (listOfChecks: any[]) => (obj: object) => {
return x(obj)
})
}
const _anyCheck = (listOfChecks: any[]) => (obj: object) => {
return listOfChecks.some(x => {
return x(obj)
})
}

const _buildChecks = (o: QueryTokens): ((obj: object) => boolean) => {
if (isPropertyBasedQuery(o)) {
Expand All @@ -174,14 +179,23 @@ const _buildChecks = (o: QueryTokens): ((obj: object) => boolean) => {
}

const threes = threeitize(o)
// Check if all links are the same type (all OR or all AND)
const allLinksAreSame = threes.every(
([, link]) => link.toLowerCase() === threes[0][1].toLowerCase()
)
const allLinksAreOr = allLinksAreSame && threes[0][1].toLowerCase() === 'or'

const checks = threes.reduce((acc, [a, link, b]) => {
const check1 = _buildChecks(a)
const check2 = _buildChecks(b)
const checkFunc = link.toLowerCase() === 'and' ? _andCheck : _orCheck
const combinedCheck = checkFunc(check1, check2)
return [...acc, combinedCheck]
}, [])
return _allCheck(checks)

// If all links are OR, combine checks with OR logic
// Otherwise, combine with AND logic (which handles mixed AND/OR correctly)
return allLinksAreOr ? _anyCheck(checks) : _allCheck(checks)
}
/* istanbul ignore next */
throw new Error('Should never happen')
Expand Down
105 changes: 105 additions & 0 deletions test/src/lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,59 @@ const TestData2 = [
},
]

const OrQueryTestData = [
{
id: 'edf73dba-216a-4e10-a38f-398a4b38350a',
name: 'name-2',
age: 2,
},
{
id: '2c3e6547-2d6b-44c3-ad2c-1220a3d305be',
name: 'name-3',
age: 10,
},
{
id: 'ed1dc8ff-fdc5-401c-a229-8566a418ceb5',
name: 'name-1',
age: 1,
},
{
id: 'name-4-id',
name: 'name-4',
age: 15,
},
{
id: 'name-5-id',
name: 'name-5',
age: 20,
},
{
id: 'name-7-id',
name: 'name-7',
age: 20,
},
{
id: 'name-6-id',
name: 'name-6',
age: 20,
},
{
id: 'name-9-id',
name: 'name-9',
age: 30,
},
{
id: 'name-10-id',
name: 'name-10',
age: 100,
},
{
id: 'name-8-id',
name: 'name-8',
age: 50,
},
]

describe('/src/lib.ts', () => {
describe('#filterResults()', () => {
it('should return 1 result with TestData2 when searching an object as a stringifyied json', () => {
Expand Down Expand Up @@ -330,5 +383,57 @@ describe('/src/lib.ts', () => {
const expected = 2
assert.deepEqual(actual, expected)
})
it('should return 3 results with OrQueryTestData when searching three OR properties (name-8 OR name-1 OR name-10)', () => {
const actual = filterResults(
queryBuilder()
.property('name', 'name-8')
.or()
.property('name', 'name-1')
.or()
.property('name', 'name-10')
.compile(),
OrQueryTestData
)
const expected = 3
assert.equal(
actual.length,
expected,
`Expected 3 results but got ${actual.length}. Results: ${JSON.stringify(actual.map(r => r.name))}`
)
// Verify we got the correct items (order doesn't matter)
const names = new Set(actual.map(r => r.name))
const expectedNames = new Set(['name-1', 'name-8', 'name-10'])
assert.deepEqual(
names,
expectedNames,
'Should find name-1, name-8, and name-10 (order independent)'
)
})
it('should return 3 results with OrQueryTestData when searching three OR properties in different order (name-1 OR name-10 OR name-8)', () => {
const actual = filterResults(
queryBuilder()
.property('name', 'name-1')
.or()
.property('name', 'name-10')
.or()
.property('name', 'name-8')
.compile(),
OrQueryTestData
)
const expected = 3
assert.equal(
actual.length,
expected,
`Expected 3 results but got ${actual.length}. Results: ${JSON.stringify(actual.map(r => r.name))}`
)
// Verify we got the correct items (order doesn't matter)
const names = new Set(actual.map(r => r.name))
const expectedNames = new Set(['name-1', 'name-8', 'name-10'])
assert.deepEqual(
names,
expectedNames,
'Should find name-1, name-8, and name-10 (order independent)'
)
})
})
})