-
Notifications
You must be signed in to change notification settings - Fork 107
feat: introduce EntityFilter class with support for and/or filters #1061
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
TODO: Add a unit test for OR in query and a unit test for AND in entity. |
system-test/datastore.ts
Outdated
]) | ||
); | ||
const [entities] = await datastore.runQuery(q); | ||
assert.strictEqual(entities!.length, 6); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will change.
This reverts commit 37400a6.
Adds a new setAncestor method for ensuring only one ancestor is set for the query at a time. This will avoid errors that result because of setting multiple ancestors. Also deprecate hasAncestor because it will lead to warnings like this. Add parser logic to use the value provided in setAncestor for query sent to backend.
Added tests for the query proto, one to make sure the query structure is right when setting ancestor once and one to make sure the query structure is right when setting ancestor twice. Also added a unit test to make sure that ancestor is set the right way internally when using setAncestor.
This code change adjusts the expected result for running an OR query. Old result used to correspond with AND, but now corresponds to OR.
This reverts commit e84582f.
This commit is done to avoid a breaking typescript change which could have the potential to affect some users who read `filters` on a query as its type had been changed to be more flexible, but is now back to what it was.
AND and OR should not be static functions of the filter class because then the user has to type Filter.AND instead of AND for example.
Artifacts of having imports laying around and moving functionality between files
src/filter.ts
Outdated
* @see {@link https://cloud.google.com/datastore/docs/concepts/queries#filters| Filters Reference} | ||
* | ||
*/ | ||
export abstract class Filter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this should be an interface.
system-test/datastore.ts
Outdated
'Character', | ||
'Eddard', | ||
]); | ||
it('should run a query using hasAncestor first', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo first/last should be switched around
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solved
]) | ||
); | ||
const [entities] = await datastore.runQuery(q); | ||
assert.strictEqual(entities!.length, 6); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to also assert that the resulting entities indeed have the values family = Stark
and apprearances >= 20
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at the data and there is actually one entity that has family ['Stark', 'Tully']
. I believe this is intended behaviour for the filter because this is also the result when we test a case where legacy .filter
is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just checked should filter queries with defined indexes
on main. It seems this filter checks to see if Stark
is present. I guess this matches what you said. We can check for its pressence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in last commit
]) | ||
); | ||
const [entities] = await datastore.runQuery(q); | ||
assert.strictEqual(entities!.length, 8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to assert that in resulting query, there are entities that have value family = Stark
and not appearances >=20
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now I am seeing that all the entities which are returned have a family name of Stark.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still looking at this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in last commit
src/entity.ts
Outdated
filter => new PropertyFilter(filter.name, filter.op, filter.val) | ||
); | ||
const newFilters = query.newFilters; | ||
queryProto.filter = AND(newFilters.concat(filters)).toProto(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a little confused by this, what is this logic doing? Can you add some comments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comments to the code here.
This is mainly just a refactor. Old code is being moved into the Filter
classes, but this new code does the same thing as the removed code. The functionality had to be pulled into the Filter
classes to avoid creating duplicate code.
src/query.ts
Outdated
@@ -76,6 +76,7 @@ class Query { | |||
namespace?: string | null; | |||
kinds: string[]; | |||
filters: Filter[]; | |||
newFilters: NewFilter[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need another variable for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first I put all the filters into the filters
object whether they were the old type of filter or the new filter class. This caused a compiler error however caught by https://github.com/googleapis/nodejs-datastore/pull/1067/files#diff-a9fb5a76e455ff1f3d63199cae37a32e289867ec02b1ec205b79468d074a211eR881.
Currently, users may be popping off the filters
variable and expecting that variable to have the val
property, but it won't if we allow filter
to store the new Filter
class.
src/query.ts
Outdated
@@ -201,24 +207,29 @@ class Query { | |||
* const keyQuery = query.filter('__key__', key); | |||
* ``` | |||
*/ | |||
filter(property: string, value: {} | null): Query; | |||
filter(property: string, operator: Operator, value: {} | null): Query; | |||
filter(propertyOrFilter: string | NewFilter, value?: {} | null): Query; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why can't this just be string | Filter
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this module, NewFilter
is just what we call the imported Filter
class. We cannot call it Filter
because the name Filter
is already taken in this module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filter has been renamed entityFilter
so as not to cause confusion.
We add additional asserts to the data in order to capture the nuances of the composite operator. For example, for the OR test we make sure the filter doesn’t always require both conditions to be true.
Code for building the `filter` property of the query proto was pulled into the `Filter` object. Comments indicate how legacy functionality was maintained and which lines of code perform which task.
rename new filter to entity filter to eliminate the need for an internal rename that causes confusion
These test cases have mistakes in their names. We should change them to reflect the position of `hasAncestor`.
src/query.ts
Outdated
@@ -170,7 +176,7 @@ class Query { | |||
* | |||
* @see {@link https://cloud.google.com/datastore/docs/concepts/queries#datastore-property-filter-nodejs| Datastore Filters} | |||
* | |||
* @param {string} property The field name. | |||
* @param {string | NewFilter} propertyOrFilter The field name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be EntityFilter now? (side note - how is this compiling?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. This needs to change. This is compiling because this is actually just a documentation comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
The name of the base class is now EntityFilter. Adjust this comment so that it correctly matches the parameter type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few minor suggestions, this is looking really good IMO:
- A comprehensive suite of tests.
- Code reads well.
src/entity.ts
Outdated
@@ -21,6 +21,7 @@ import {PathType} from '.'; | |||
import {protobuf as Protobuf} from 'google-gax'; | |||
import * as path from 'path'; | |||
import {google} from '../protos/protos'; | |||
import {AND, PropertyFilter} from './filter'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, upper case is usually reserved for variable constants const FOO = 9; BAR = 99
, to make it clear that this is an exported method, I might go with:
and
/or
, or applyAnd
, applyOr
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choosing the former
src/filter.ts
Outdated
*/ | ||
// eslint-disable-next-line | ||
toProto(): any { | ||
const OP_TO_OPERATOR = new Map([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I would probably hoist this to the top of the file, rather than declaring in toProto
, at scale this will speed things up since you're allocating less Maps
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good plan.
value = operatorOrValue as {}; | ||
operator = '='; | ||
} | ||
if (isFilter(propertyOrFilter)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good use of TypeScript's is
functionality 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
@danieljbruce the tile of this PR seems a bit off, I believe you're actually introducing the ability to filter using a class rather than just a string? Perhaps a better title:
Or something like this. |
rename composite filter functions so that they don’t look like constants and move a map up so that it doesn’t have to be initialized every time.
src/entity.ts
Outdated
const filters = query.filters.map( | ||
filter => new PropertyFilter(filter.name, filter.op, filter.val) | ||
); | ||
const newFilters = query.entityFilters; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit - rename variable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was done
This code will enable the composite OR filters (much like the same way the composite AND filters work).