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

fix: allow IN queries on __key__ #1085

Conversation

danieljbruce
Copy link
Contributor

The fix contains:

  • An update to the way that entities are encoded
  • An update to tests so that an instanceOf check passes
  • A cleanup of the PropertyFilter function
  • An additional test to check for IN with keys

Fixes #1024

This change matches the change shown in another PR, but the test cases won’t allow for the new change. There must be some edge cases that haven’t been considered here.
isDsKey currently does not check to see that the key is an entity.Key properly so we need to modify this function so that it does a proper check.
For an instanceof check to pass, we need to import the entity property the right way
Make code a lot more succinct for the PropertyFilter class before adding the new test in.
There is no need for a private encodeValue function so we just inline this change.
This test supports using __key__ with IN and an array.
This change modifies the proto to be what we want it to be so that the deep strict equal check works.
This commit removes a comment that isn’t necessary anymore and renames a variable to demonstrate its proper use.
@danieljbruce danieljbruce requested review from a team as code owners March 14, 2023 21:35
@product-auto-label product-auto-label bot added size: m Pull request size is medium. api: datastore Issues related to the googleapis/nodejs-datastore API. labels Mar 14, 2023
The new test doesn’t enter the DSKey check when the entity is used from the beforeEach hook. Use the global entity instead.
@danieljbruce danieljbruce changed the title 1065 query filter for in with keys test fix: 1065 query filter for in with keys test Mar 15, 2023
The specific query proto used should be moved into the test as it is only used by that specific test.
@danieljbruce danieljbruce added the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 15, 2023
@gcf-owl-bot gcf-owl-bot bot removed the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 15, 2023
Copy link
Contributor

@vicb vicb left a comment

Choose a reason for hiding this comment

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

Looks nice 👍

There are a few "GitHub Actions / lint" that look worth addressing - IMO the typings of this lib could use some love but out of scope for this PR.

src/filter.ts Outdated
@@ -13,7 +13,7 @@
// limitations under the License.

import {Operator, Filter as IFilter} from './query';
import {entity} from './entity';
import {entity, ValueProto} from './entity';
Copy link
Contributor

Choose a reason for hiding this comment

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

Please fix (remove ValueProto)

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

src/filter.ts Outdated
this.op,
this.val
).encodedValue();
const value = entity.encodeValue(this.val, this.name);
Copy link
Contributor

Choose a reason for hiding this comment

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

You can also value: entity.encodeValue(this.val, this.name) inline. Not sure what the preferred style is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. This can be even more compact.

test/entity.ts Outdated
@@ -1894,8 +1894,62 @@ describe('entity', () => {
assert.deepStrictEqual(entity.queryToQueryProto(query), queryProto);
});

it('should support using key with IN', () => {
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'm not sure if "with IN" is relevant here?

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 point.

I think more specifically, it is addressing the case of using an array as a value as that was what was causing the error in the first place.

Remove an unused import, make value more inline and change name of test case to reflect new functionality.
@danieljbruce danieljbruce added the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 15, 2023
@gcf-owl-bot gcf-owl-bot bot removed the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 15, 2023
@danieljbruce
Copy link
Contributor Author

@vicb There is just one more thing we should confirm. I notice that val in Property Filter is of type any so even though filtering by __key__ ought to use only values that are keys and arrays of keys, the compiler doesn't guarantee it. Are these definitely the only two cases with which we can expect users to filter with __key__?

Since the new test simply introduces a new proto and checks it against __key__ with an array, we still have no way to verify if this encoding will actually work. In this system test we verify that using __key__ with an array actually works in a query.
@vicb
Copy link
Contributor

vicb commented Mar 17, 2023

val in Property Filter is of type any

As discussed it should probably be unknown instead.

Are these definitely the only two cases with which we can expect users to filter with key?

Probably.
You should be able to leverage Typescript conditional types to enforce that.

Modify the type to use `unknown` instead of `any`.
Leverage typescript to let the user know at compile time that Keys and arrays of keys are to be used when __key__ is used.
The restricted value type should be used on filter as well as the property filter.
src/filter.ts Outdated
@@ -56,42 +57,34 @@ export abstract class EntityFilter {
abstract toProto(): any;
}

export type restrictedValueType<T> = T extends '__key__'
Copy link
Contributor

Choose a reason for hiding this comment

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

RestrictedValueType

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the name of this variable to more specifically describe what this does

src/query.ts Outdated
value?: restrictedValueType<T> | null
): Query;
filter<T extends string>(
propertyOrFilter: T,
Copy link
Contributor

Choose a reason for hiding this comment

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

T | EntityFilter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Signature has been updated

src/query.ts Outdated
value: restrictedValueType<T> | null
): Query;
filter<T extends string>(
propertyOrFilter: T | EntityFilter,
operatorOrValue?: Operator,
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be restrictedValueType<T> | null | Operator

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Signature has been updated

@danieljbruce
Copy link
Contributor Author

See 8d269d2 for a case where the compiler doesn't seem to complain even though it should.

@danieljbruce
Copy link
Contributor Author

I mean see 8d269d2

@danieljbruce
Copy link
Contributor Author

I mean #1089

Checking for nulls is now included in the restricted value type so we don’t need to declare it in the signature of the function.
rename a value type variable to allowed filter value type to more accurately describe the type’s purpose.
The old variable name doesn’t follow the naming convention.
prettier and change the typeguard so that it checks to see if the input is an instance of EntityFilter.
@danieljbruce danieljbruce added the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 20, 2023
@gcf-owl-bot gcf-owl-bot bot removed the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 20, 2023
@danieljbruce danieljbruce changed the title fix: 1065 query filter for in with keys test fix: 1065 eliminate unnecessary logic when using __key__ for a filter Mar 21, 2023
@danieljbruce danieljbruce changed the title fix: 1065 eliminate unnecessary logic when using __key__ for a filter fix: allow IN queries on __key__ Mar 21, 2023
@@ -156,5 +144,5 @@ class CompositeFilter extends EntityFilter {
}

export function isFilter(filter: any): filter is EntityFilter {
return (filter as EntityFilter).toProto !== undefined;
return filter instanceof EntityFilter;
Copy link
Contributor

Choose a reason for hiding this comment

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

why does this have to change?

Copy link
Contributor

Choose a reason for hiding this comment

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

I suggested the fix so I'll answer.

filter.toProto !== undefined only checks if the object has a toProto properties (not set to undefined)
It is the case that EntityFilters have a toProto methods but it is not necessarily the case that an object with a toProto property is an EntityFilter.

filter instanceof EntityFilter is the correct check for filter is EntityFilter

I hope that makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

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

thanks @vicb, both using instanceof and avoiding type assertions (filter as EntityFilter) are recommended in the Google TypeScript Style Guide. LGTM 👍

propertyOrFilter: string | EntityFilter,
operatorOrValue?: Operator,
value?: {} | null
filter(filter: EntityFilter): Query;
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 signature change makes the input parameters more restrictive. Do we really want to go through with this? If so would this be a major version bump?

@vicb @bcoe

Copy link
Contributor

Choose a reason for hiding this comment

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

My take is that the old signature was buggy and wouldn't catch unsupported calls. So I see this as a bug fix.

Also I think the impl could be improved by switching on arguments.length. 1, 2, 3 corresponding to the first 3 signatures in order.

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 will do this in a separate issue:

#1096

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to go with a patch version bump

Copy link
Contributor

@ruyadorno ruyadorno left a comment

Choose a reason for hiding this comment

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

LGTM 👍

Let's just make sure to keep an eye and monitor if the internal change from src/filter.ts encodeValue() is going to have any impact in the end users of the library. Releasing this as a patch version release is going to help us having the flexibility to rollback if needed, so I'd advise to proceed with a fix: commit.

Great work @danieljbruce!

propertyOrFilter: string | EntityFilter,
operatorOrValue?: Operator,
value?: {} | null
filter(filter: EntityFilter): Query;
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to go with a patch version bump

@@ -156,5 +144,5 @@ class CompositeFilter extends EntityFilter {
}

export function isFilter(filter: any): filter is EntityFilter {
return (filter as EntityFilter).toProto !== undefined;
return filter instanceof EntityFilter;
Copy link
Contributor

Choose a reason for hiding this comment

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

thanks @vicb, both using instanceof and avoiding type assertions (filter as EntityFilter) are recommended in the Google TypeScript Style Guide. LGTM 👍

@danieljbruce danieljbruce added the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 28, 2023
@gcf-owl-bot gcf-owl-bot bot removed the owlbot:run Add this label to trigger the Owlbot post processor. label Mar 28, 2023
@danieljbruce danieljbruce merged commit dd2d5f4 into googleapis:main Mar 29, 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: m Pull request size is medium.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Query filter on __key__ using the "IN" operator
4 participants