Skip to content
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

feat: introduce EntityFilter class with support for and/or filters #1061

Merged
merged 42 commits into from
Mar 8, 2023

Conversation

danieljbruce
Copy link
Contributor

This code will enable the composite OR filters (much like the same way the composite AND filters work).

@danieljbruce danieljbruce requested review from a team as code owners January 19, 2023 14:55
@product-auto-label product-auto-label bot added size: l Pull request size is large. api: datastore Issues related to the googleapis/nodejs-datastore API. labels Jan 19, 2023
@danieljbruce
Copy link
Contributor Author

TODO: Add a unit test for OR in query and a unit test for AND in entity.

@kolea2 kolea2 added the do not merge Indicates a pull request not ready for merge, due to either quality or timing. label Jan 19, 2023
])
);
const [entities] = await datastore.runQuery(q);
assert.strictEqual(entities!.length, 6);
Copy link
Contributor Author

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 {
Copy link
Contributor Author

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.

'Character',
'Eddard',
]);
it('should run a query using hasAncestor first', async () => {
Copy link
Contributor Author

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

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still looking at this

Copy link
Contributor Author

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 Show resolved Hide resolved
src/entity.ts Outdated
filter => new PropertyFilter(filter.name, filter.op, filter.val)
);
const newFilters = query.newFilters;
queryProto.filter = AND(newFilters.concat(filters)).toProto();
Copy link
Contributor

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?

Copy link
Contributor Author

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[];
Copy link
Contributor

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?

Copy link
Contributor Author

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

https://github.com/googleapis/nodejs-datastore/pull/1061/files#diff-ad0978c0145d3620d2a97bdff4307f9496529c0e3011a845e2d5bf1a169d2538R21

Copy link
Contributor Author

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.
Copy link
Contributor

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?)

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@kolea2 kolea2 removed the do not merge Indicates a pull request not ready for merge, due to either quality or timing. label Mar 8, 2023
The name of the base class is now EntityFilter. Adjust this comment so that it correctly matches the parameter type.
Copy link
Contributor

@bcoe bcoe left a 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';
Copy link
Contributor

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.

Copy link
Contributor Author

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([
Copy link
Contributor

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.

Copy link
Contributor Author

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)) {
Copy link
Contributor

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 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@bcoe
Copy link
Contributor

bcoe commented Mar 8, 2023

@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:

feat: introduce EntityFilter class with support for and/or filters

Or something like this.

@danieljbruce danieljbruce changed the title feat: OR filters feat: introduce EntityFilter class with support for and/or filters Mar 8, 2023
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - rename variable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done

@danieljbruce danieljbruce added the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 8, 2023
@gcf-owl-bot gcf-owl-bot bot removed the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 8, 2023
@danieljbruce danieljbruce merged commit 8fc58c0 into googleapis:main Mar 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: datastore Issues related to the googleapis/nodejs-datastore API. size: l Pull request size is large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants