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

PHPLIB-1363 PHPLIB-1355 Add enum for $type query and $meta expression #38

Closed
wants to merge 4 commits into from

Conversation

GromNaN
Copy link
Member

@GromNaN GromNaN commented Jan 18, 2024

Fix PHPLIB-1363
Fix PHPLIB-1355

The enum implements FieldQueryInterface so it can be used anywhere a field query is expected. Deprecated types have been skipped.

List imported from the doc, including the alias number.

Usage:

        $pipeline = new Pipeline(
            Stage::match(
                zipCode: QueryType::Int,
            ),
        );

Limitation, it's not possible to use the enum in a list.

        $pipeline = new Pipeline(
            Stage::match(
                zipCode: [QueryType::Int, QueryType::Long],
            ),
        );

@GromNaN GromNaN changed the title PHPLIB-1363 Add enum for list PHPLIB-1363 Add enum for $type list Jan 18, 2024
src/Builder/BuilderEncoder.php Outdated Show resolved Hide resolved
*/
enum QueryType: string implements OperatorInterface, FieldQueryInterface
{
public const ENCODE = Encode::Array;
Copy link
Member

Choose a reason for hiding this comment

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

Is this the first time a BackedEnum is being used for an encoding type? If so, perhaps it'd be prudent to have BuilderEncoder::encode() assert that that $value::ENCODE is Encode::Array (and you can revise that as needed should another use case come up).

That would address my question about how a BackedEnum might be handled for other switch cases.

Copy link
Member Author

@GromNaN GromNaN Jan 22, 2024

Choose a reason for hiding this comment

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

We need to rework the whole encoder class. The "ENCODE" constant will not stay, it may become a method of the Operator interface.

Copy link
Member

Choose a reason for hiding this comment

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

If this isn't already ticketed please do so and drop a reference to the issue.

@GromNaN GromNaN force-pushed the PHPLIB-1363 branch 2 times, most recently from 9443998 to 2134961 Compare January 18, 2024 20:21
@@ -32,7 +33,7 @@ public function testQueryingByDataType(): void
zipCode: Query::type(2),
),
Stage::match(
zipCode: Query::type('string'),
zipCode: QueryType::String,
Copy link
Member

Choose a reason for hiding this comment

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

Huh, this is interesting. I had initially considered using an enum case as the argument for Query::type, but this is interesting as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can customize the Query::type factory to accept enum values and convert them to string if necessary.

Copy link
Member

Choose a reason for hiding this comment

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

Not sure if that is necessary. This allows people to do whatever they want with the $type operator, but provides a nice shorthand for all known types.

Copy link
Member

Choose a reason for hiding this comment

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

I assume this only works because QueryType is both an enum and an OperatorInterface (and FieldQueryInterface in that it's associated with a field path). I assume this convenience only works for a single type check, though. Trying to match zipCode with [QueryType::String, ...] in order to match multiple types would produce invalid syntax, correct?

And that's where Query::type(...) comes in.

Copy link
Member

@jmikola jmikola left a comment

Choose a reason for hiding this comment

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

Per the Psalm error below, what enforces that OperatorInterface instances define an ENCODE constant?

LGTM, but I think the various Psalm errors need correction.

@@ -129,6 +130,10 @@ public function encode($value): stdClass|array|string
*/
private function encodeAsArray(OperatorInterface $value): stdClass
{
if ($value instanceof BackedEnum) {
return $this->wrap($value, [$value->value]);
Copy link
Member

Choose a reason for hiding this comment

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

Is the NoInterfaceProperties error below the result of Psalm failing to infer that BackedEnum has an implicit $value property?

Copy link
Member Author

@GromNaN GromNaN Jan 22, 2024

Choose a reason for hiding this comment

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

Yes it looks like an issue in palm: vimeo/psalm#10585

phpstan don't have this issue: https://phpstan.org/r/e7094884-1adf-4369-b255-6c7e45367865

Copy link
Member

Choose a reason for hiding this comment

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

How is this related to the NoInterfaceProperties warning earlier in encode()? There, you're only checking for an instance of FieldPathInterface and then accessing a $value property. That seems incorrect for an entirely different reason.

src/Builder/BuilderEncoder.php Outdated Show resolved Hide resolved
@@ -32,7 +33,7 @@ public function testQueryingByDataType(): void
zipCode: Query::type(2),
),
Stage::match(
zipCode: Query::type('string'),
zipCode: QueryType::String,
Copy link
Member

Choose a reason for hiding this comment

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

I assume this only works because QueryType is both an enum and an OperatorInterface (and FieldQueryInterface in that it's associated with a field path). I assume this convenience only works for a single type check, though. Trying to match zipCode with [QueryType::String, ...] in order to match multiple types would produce invalid syntax, correct?

And that's where Query::type(...) comes in.

@GromNaN GromNaN changed the title PHPLIB-1363 Add enum for $type list PHPLIB-1363 Add enum for $type query and $meta expression Jan 22, 2024
@GromNaN GromNaN changed the title PHPLIB-1363 Add enum for $type query and $meta expression PHPLIB-1363 PHPLIB-1355 Add enum for $type query and $meta expression Jan 23, 2024
*
* @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/
*/
enum Meta: string implements OperatorInterface, ExpressionInterface
Copy link
Member Author

Choose a reason for hiding this comment

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

@alcaeus suggested to name it SortMeta. #56 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

$meta: Behavior suggests the operator can be used in context beyond sorting, such as projections.

Copy link
Member Author

Choose a reason for hiding this comment

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

The ExpressionInterface allows to use it anywhere an expression is expected.

Copy link
Member

@jmikola jmikola left a comment

Choose a reason for hiding this comment

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

Just realized I had some pending comments that were never posted. Doing so now.

@@ -129,6 +130,10 @@ public function encode($value): stdClass|array|string
*/
private function encodeAsArray(OperatorInterface $value): stdClass
{
if ($value instanceof BackedEnum) {
return $this->wrap($value, [$value->value]);
Copy link
Member

Choose a reason for hiding this comment

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

How is this related to the NoInterfaceProperties warning earlier in encode()? There, you're only checking for an instance of FieldPathInterface and then accessing a $value property. That seems incorrect for an entirely different reason.

*/
enum QueryType: string implements OperatorInterface, FieldQueryInterface
{
public const ENCODE = Encode::Array;
Copy link
Member

Choose a reason for hiding this comment

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

If this isn't already ticketed please do so and drop a reference to the issue.

*
* @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/
*/
enum Meta: string implements OperatorInterface, ExpressionInterface
Copy link
Member

Choose a reason for hiding this comment

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

$meta: Behavior suggests the operator can be used in context beyond sorting, such as projections.

* { $meta: "indexKey" } expression is for debugging purposes only, and
* not for application logic, and is preferred over cursor.returnKey().
*/
case IndexKey = 'indexKey';
Copy link
Member

Choose a reason for hiding this comment

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

Do the cases here preclude users from using this with additional modes? $meta: Definition mentions searchScore and searchHighlights for Atlas.

@GromNaN
Copy link
Member Author

GromNaN commented Jan 29, 2024

Rebased on top of #46

Copy link
Member

@jmikola jmikola left a comment

Choose a reason for hiding this comment

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

Left some additional comments on the enum cases, and there are already a number of open comment threads from last week.

src/Builder/QueryType.php Outdated Show resolved Hide resolved
src/Builder/QueryType.php Outdated Show resolved Hide resolved
src/Builder/QueryType.php Outdated Show resolved Hide resolved
case JavaScript = 'javascript';
case Int = 'int';
case Timestamp = 'timestamp';
case Long = 'long';
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
case Long = 'long';
case Int64 = 'long';

This also leaves nothing to the imagination.

Copy link
Member

Choose a reason for hiding this comment

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

It just occurred to me that you might have chosen these names to be consistent with the ExpressionFactoryTrait field path methods. Unless it was just the first column from the table in $type: Available Types.

In any event, you may want to keep the factory methods consistent with the enums here. And if factory methods will be distinguishing 32-bit and 64-bit integers, I do think explicit names with the bit size are more clear to users than "int" and "long".

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated all the type name to take more descriptive ones, from the BSON classes when they exist.

src/Builder/QueryType.php Outdated Show resolved Hide resolved
src/Builder/QueryType.php Outdated Show resolved Hide resolved
case Int = 'int';
case Timestamp = 'timestamp';
case Long = 'long';
case Decimal128 = 'decimal';
Copy link
Member

Choose a reason for hiding this comment

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

I do think using the BSON class name was a good idea here, as users could confuse "decimal" with "double" if they didn't realize there was a specific type.

src/Builder/QueryType.php Outdated Show resolved Hide resolved
@GromNaN
Copy link
Member Author

GromNaN commented Feb 16, 2024

Closing for now. Lets revisit later. Ticket still open.

@GromNaN GromNaN closed this Feb 16, 2024
@GromNaN GromNaN deleted the PHPLIB-1363 branch February 16, 2024 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants