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: Adds SelectStatement::apply method for functionally building query in another function #730

Merged
merged 1 commit into from
Jan 12, 2024

Conversation

brahmlower
Copy link
Contributor

@brahmlower brahmlower commented Jan 1, 2024

Heya! 👋 Thanks for all your work on the library- I've really enjoyed working with it! I ran into a quality of life shortcoming with the API around the SelectStatement builder: I wanted a clean way of building a portion of the query in another function, similar to the pattern used by the conditions method, but without the actual conditional part 😁

PR Info

  • Closes: None
  • Dependencies: None
  • Dependents: None

New Features

  • Added apply method to SelectStatement for constructing part of the statement from another function

    This new method is essentially a shorthand form of using the conditions function with a hardcoded value:

    // a function for building up part of the query
    fn build_query(query: &mut SelectStatement) {
        query
            .column(Users::Id)
            .from(Users::Table);
    }
    
    // The only current option is to use the `conditions` method with a hardcoded value 
    let query_count = Query::select()
        .conditions(true, build_query, |_| {})
        .to_owned();
    
    // But the code is easier to grok with the new apply method:
    let query_count = Query::select()
        .apply(build_query)
        .to_owned();

    This is useful in cases where multiple queries have the same "core" expressions, like when gathering results vs counting total query size (for pagination) on a query with joins, conditional where clauses, and groupings:

    // a function for joining the `user` and `user_external_ids` tables and filtering users
    // based on properties of their associated external ids (in this case, the system
    // name being "foo bar baz").
    fn build_query_with_filter(query: &mut SelectStatement) {
        let filter_external = true;
    
        query
            .from(Users::Table)         // the Users table
            .left_join(
                UserExternalIds::Table, // a table of related external user IDs, a many-to-one relationship with Users
                Expr::col(
                    (Users::Table, Users::Id)
                ).equals(
                    (UserExternalIds::Table, UserExternalIds::UserId)
                )
            )
            .conditions(
                filter_external,
                |q| {
                    q.and_where(Expr::col(UserExternalIds::ExternalSystem).eq("foo bar baz"));
                },
                |_| {},
            )
            .add_group_by([Expr::col(Users::Id).into()]);
    }
    
    fn main() {
        let query_count = Query::select()
            .expr(Func::count(Expr::col((Users::Table, Users::Id))))
            .apply(build_query_with_filter)
            .to_owned();
    
        let query_fetch = Query::select()
            .column(Users::Id)
            .apply(build_query_with_filter)
            .order_by(Users::CreatedAt, Order::Desc)
            .limit(10)
            .offset(0)
            .to_owned();
    }

    This is somewhat contrived example to keep it as simple as possible. For my use case, the function used to build common expressions on the query is a struct method that's referencing it's own internal state to apply the conditional filters:

     let filter = UserListFilter { ... };
    
     let query_count = Query::select()
            .expr(Func::count(Expr::col((Users::Table, Users::Id))))
            .apply(|q| filter.apply_query(q))
            .to_owned();

Bug Fixes

None

Breaking Changes

None

Changes

None as of initial authorship, however the SelectStatement::conditions method could be updated to call out to the new apply function if so desired, but it'd be a little pedantic:

    pub fn conditions<T, F>(&mut self, b: bool, if_true: T, if_false: F) -> &mut Self
    where
        T: FnOnce(&mut Self),
        F: FnOnce(&mut Self),
    {
        if b {
-            if_true(self)
+            self.apply(if_true); 
        } else {
-            if_false(self)
+            self.apply(if_false);
        }
        self
    }

Copy link
Member

@tyt2y3 tyt2y3 left a comment

Choose a reason for hiding this comment

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

Thank you for the contribution. Can you add a doc test?

method could be updated to call out to the new apply function if so desired

I think we can leave it as is

@brahmlower
Copy link
Contributor Author

Can you add a doc test?

Oh absolutely! In hindsight it's pretty obvious that I should have included that in the first place 😅 I'll start working on that now, should have it pushed relatively soon

@brahmlower brahmlower force-pushed the select-builder-supports-apply branch from dca777a to 2771da0 Compare January 1, 2024 22:46
@brahmlower
Copy link
Contributor Author

Mkay I've added the doctest example. I just copied and updated the example used for conditionals for the sake of consistency, but I'm happy to make changes if you'd prefer something different 👍

@brahmlower
Copy link
Contributor Author

Hey @tyt2y3 👋 I just wanted to check in to see if there were any other concerns with this PR?

@tyt2y3
Copy link
Member

tyt2y3 commented Jan 12, 2024

Hi! Just came back. Happy new year!

@tyt2y3 tyt2y3 merged commit 122b161 into SeaQL:master Jan 12, 2024
Copy link

🎉 Released In 0.30.7 🎉

Thank you everyone for the contribution!
This feature is now available in the latest release. Now is a good time to upgrade!
Your participation is what makes us unique; your adoption is what drives us forward.
You can support SeaQL 🌊 by starring our repos, sharing our libraries and becoming a sponsor ⭐.

@brahmlower
Copy link
Contributor Author

Hey thanks for merging this yesterday! Didn't intend to rush ya, so sorry about that- hope you had a good break, and happy new year to you too! 🥳

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.

None yet

2 participants