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(qe): Implement findFirstOrThrow and findUniqeOrThrow in the engine #3356

Merged
merged 6 commits into from Nov 4, 2022

Conversation

miguelff
Copy link
Collaborator

@miguelff miguelff commented Oct 31, 2022

Fixes: prisma/prisma#14933

This PR implements the following APIs in the engine:

  • findFirstOrThrowModelName
  • findUniqueOrThrowModelName

Both APIs are currently implemented in the client side using findFirstModelName and findUniqueModelName engine APIs, and throwing an error in case they return 0 results.

After this change is merged, the client would need to remove the code that handles findUniqueOrThrow and findFirstOrThrow to use instead the newly created query-engine APIs.

I will work on a PR in prisma/prisma to address client changes next.

@miguelff miguelff force-pushed the feat/find-or-throw branch 3 times, most recently from 8634209 to 82a2bd8 Compare October 31, 2022 16:10
@miguelff miguelff changed the title [WIP] feat(qe): Implement findFirstOrThrow and findUniqeOrThrow natively [WIP] feat(qe): Implement findFirstOrThrow and findUniqeOrThrow in the engine Nov 2, 2022
@miguelff miguelff added this to the 4.6.0 milestone Nov 2, 2022
@miguelff miguelff changed the title [WIP] feat(qe): Implement findFirstOrThrow and findUniqeOrThrow in the engine feat(qe): Implement findFirstOrThrow and findUniqeOrThrow in the engine Nov 2, 2022
@miguelff miguelff marked this pull request as ready for review November 2, 2022 15:38
@@ -35,6 +35,19 @@ mod find_first_query {
Ok(())
}

#[connector_test]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a failure case for findFirst to check that it actually behaves differently than findFirstOrThrow

@@ -5,7 +5,7 @@ mod find_first_query {
use query_engine_tests::assert_query;

#[connector_test]
async fn fetch_first_matching(runner: Runner) -> TestResult<()> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Did a small replacement of fetch -> find on the test cases

use query_engine_tests::assert_query;

#[connector_test]
async fn find_first_or_throw_matching(runner: Runner) -> TestResult<()> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Test case to ensure that findFirstOrThrow behaves like findFirst when a record is found

Comment on lines +39 to +40
2025,
"An operation failed because it depends on one or more records that were required but not found. Expected a record, found none."
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the error returned to the user, which as per this method is a ConnectorError rendering this

@@ -63,7 +63,9 @@ impl QueryGraphBuilder {

let mut graph = match (&query_info.tag, query_info.model.clone()) {
(QueryTag::FindUnique, Some(m)) => read::find_unique(parsed_field, m).map(Into::into),
(QueryTag::FindUniqueOrThrow, Some(m)) => read::find_unique_or_throw(parsed_field, m).map(Into::into),
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In order to build the new APIs I reused first/many/one modules in query_builder. I added a field of type QueryOptionsto RecordQuery and ManyRecordsQuery. See query_ast/read.rs

query-engine/core/src/query_ast/read.rs Outdated Show resolved Hide resolved
Comment on lines +13 to +18
#[inline]
fn find_many_with_options(
field: ParsedField,
model: ModelRef,
options: QueryOptions,
) -> QueryGraphBuilderResult<ReadQuery> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the old implementation, which now receives QueryOptions. Previous find_many keeps the signature and passes no options QueryOptions::none, while find_many_or_throw builds a query with the THROW_ON_EMPTY option

Copy link
Member

@Weakky Weakky 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 comments, none of them require you to change anything. They're open suggestions/nitpicks. Awesome work 💯!

query-engine/schema-builder/src/output_types/query_type.rs Outdated Show resolved Hide resolved
@@ -96,6 +104,7 @@ pub struct RecordQuery {
pub nested: Vec<ReadQuery>,
pub selection_order: Vec<String>,
pub aggregation_selections: Vec<RelAggregationSelection>,
pub options: BitFlags<QueryOption>,
Copy link
Collaborator Author

@miguelff miguelff Nov 4, 2022

Choose a reason for hiding this comment

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

It's more elegant and better now, @Weakky . Thank you so much for the suggestion!

Copy link
Contributor

@garrensmith garrensmith left a comment

Choose a reason for hiding this comment

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

This is looking good. I've left some comments around the QueryOptions. lets try and make that abstraction cleaner. Great work.

@@ -818,6 +819,7 @@ impl QueryGraph {
nested: vec![],
selection_order: vec![],
aggregation_selections: vec![],
options: BitFlags::EMPTY,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think rather doing options: QueryOptions::none would be better here. That would require adding a new query option but then we keep with the QueryOption abstraction rather than exposing the underlying BitFlags.

Or you could also do options: Default::default and implement the default interface.

}

pub fn find_many_or_throw(field: ParsedField, model: ModelRef) -> QueryGraphBuilderResult<ReadQuery> {
find_many_with_options(field, model, BitFlags::from_flag(QueryOption::ThrowOnEmpty))
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, we leaking the BitFlag abstraction here. Is there another way around this. We want to use BitFlags internally but not expose that outside the QueryOption.

you might have to have something like:

struct QueryOption {
   inner: BitFlags
   
}

@miguelff miguelff requested a review from a team as a code owner November 4, 2022 12:27
@miguelff
Copy link
Collaborator Author

miguelff commented Nov 4, 2022

Hey, @garrensmith Thank you so much for your feedback!

Although I see how generally "leaky abstraction" constitutes an antipattern, the case of BitFlags is IMO slightly different. BitFlags is a generic collection of items packed in a bit array with set semantics. Similar to other collections in the standard library which are used in many places, they don't constitute a leaky abstraction (or at least not to the same extent as it would be to leak a data structure that's specific to our domain model)

Having said that I reckon it would be better not to depend on importing the enum flags library each time we want to use any options. It's too involved for what it does! 👍🏾

I would try to hide some implementation details in the next commit and request your review again. Thank you! ✨

@miguelff
Copy link
Collaborator Author

miguelff commented Nov 4, 2022

@garrensmith Some feedback was addressed in 0dd9c8a . Ready to be merged!

Copy link
Contributor

@garrensmith garrensmith left a comment

Choose a reason for hiding this comment

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

Nice that looks cleaner

@miguelff miguelff merged commit 24b1bcc into main Nov 4, 2022
@miguelff miguelff deleted the feat/find-or-throw branch November 4, 2022 16:05
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.

findFirstOrThrow / findUniqueOrThrow errors are not reported via error event
3 participants