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
Conversation
8634209
to
82a2bd8
Compare
@@ -35,6 +35,19 @@ mod find_first_query { | |||
Ok(()) | |||
} | |||
|
|||
#[connector_test] |
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 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<()> { |
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.
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<()> { |
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.
Test case to ensure that findFirstOrThrow
behaves like findFirst
when a record is found
2025, | ||
"An operation failed because it depends on one or more records that were required but not found. Expected a record, found none." |
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 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), |
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 order to build the new APIs I reused first/many/one modules in query_builder. I added a field of type QueryOptions
to RecordQuery
and ManyRecordsQuery
. See query_ast/read.rs
#[inline] | ||
fn find_many_with_options( | ||
field: ParsedField, | ||
model: ModelRef, | ||
options: QueryOptions, | ||
) -> QueryGraphBuilderResult<ReadQuery> { |
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 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
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 comments, none of them require you to change anything. They're open suggestions/nitpicks. Awesome work 💯!
d8d1b84
to
8481c0d
Compare
@@ -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>, |
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.
It's more elegant and better now, @Weakky . Thank you so much for the suggestion!
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 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, |
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 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)) |
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.
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
}
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! ✨ |
2593df8
to
0dd9c8a
Compare
@garrensmith Some feedback was addressed in 0dd9c8a . Ready to be merged! |
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.
Nice that looks cleaner
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
andfindUniqueModelName
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
andfindFirstOrThrow
to use instead the newly created query-engine APIs.I will work on a PR in prisma/prisma to address client changes next.