Skip to content
This repository was archived by the owner on Aug 18, 2025. It is now read-only.

Conversation

@epilys
Copy link
Contributor

@epilys epilys commented Jul 14, 2022

This commit adds a Query type that allows you combine WHERE expressions
with some type checks and generate a postgres query. An all() method
is added to the generated table types for conveniently building a
SELECT for all table columns and returning the results in a Vec.

The select.rs module file is self-contained and must be included in generated code.

To see generated code run the basic test with const TMP_DIR: Option<&'static str> = Some("/tmp/basic/"); or some other directory in order to persist the generated code to disk.

cargo test basic -- --nocapture --ignored

An example of a generated SQL query:

let _where1 = Where::gt(Accounts::USER_ID, 1_i32);
let _where = Where::and(_where1, Where::eq(Accounts::USERNAME, "user4".to_string()));
let q = Query::new(
    /*prepare statement, unused parameter for now */ false,
    /* columns to select */ vec![
        "user_id".into(),
        "username".into(),
        "password".into(),
        "email".into(),
    ],
    /* table to select from */ "accounts".into(),
   /* optional WHERE expression */ Some(_where),
);
assert_eq!(q.to_string(), "SELECT accounts.user_id, accounts.username, accounts.password, accounts.email FROM accounts WHERE ((accounts.user_id) > (1)) AND ((accounts.username) = ($basictest$user4$basictest$))");

for row in client.query(&q.to_string(), &[]).unwrap() {
    let id: i32 = row.get(0);
    let name: &str = row.get(1);
    println!("found person: {} {}", id, name);
}

all() uses a TryFrom<postgres::row::Row> implementation to convert a Row result into a Rust type by filling all fields.

This commit adds a Query type that allows you combine WHERE expressions
with some type checks and generate a postgres query. An `all()` method
is added to the generated table types for conveniently building a
`SELECT` for all table columns and returning the results in a `Vec`.
@epilys epilys requested a review from djc July 14, 2022 14:20
@epilys epilys self-assigned this Jul 14, 2022
Comment on lines +95 to +108
let _where1 = Where::gt(Accounts::USER_ID, 1_i32);
let _where = Where::and(_where1, Where::eq(Accounts::USERNAME, "user4".to_string()));
let q = Query::new(
false,
vec![
"user_id".into(),
"username".into(),
"password".into(),
"email".into(),
],
"accounts".into(),
Some(_where),
);
println!("query to string: {}", q.to_string());
Copy link
Contributor

Choose a reason for hiding this comment

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

Here's what I was really hoping for this to look like:

Accounts::query()
    .where(|a| Sql::gt(a.user_id, 1) & Sql::eq(a.username, "user4"))
    .select(|a| (a.user_id, a.username, a.password, a.email))

Otherwise there's still a lot of string values going on, which can't be type checked.

Do you think an interface like that would be hard to add?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm looks possible, I will give it a go!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@djc

I can't figure out a way to type check fields without instantiating the queried type, it's somewhat of blocking progress on this. It might be only possible to do it with a "mirror" struct like here ("Type-checked keypaths in Rust") but that will probably increase the size and complexity of the generated code† so I don't know if I should venture into this road without your feedback first.

† Also, will require proc macros to refer to fields but actually calling the mirror struct methods.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you give an example of where you run into trouble?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Typechecking the closure |a| Sql::gt(a.user_id, 1) & Sql::eq(a.username, "user4") is straightforward I guess. But to get the expression out of it you need to pass an instance of the type. It's not trouble per se but a bit unclean. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I had this implementation in mendes::models where the Model impl has an associated type where the fields are column types with a parameter for the value type, I think something like that might help here?

In general I'm not worried about generating too much code, although we should try to avoid too much duplication.

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 had this implementation in mendes::models where the Model impl has an associated type where the fields are column types with a parameter for the value type, I think something like that might help here?

The problem is matching field names and their types, since you cannot inspect fields by their name without using the dot operator on an actual type instance. That's why the link I provided implements a mirror type that replaces fields with method calls, which don't require an instance.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe my previous comment wasn't quite clear, see here:

https://gist.github.com/djc/7eac20dcfb20648a9e00b2ca3152a940#file-models-rs-L121

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah that makes sense, I didn't find this part by looking at the models module. This is exactly what I was suggesting by "implementing a mirror type". So we can move forward with this solution

@epilys epilys closed this Sep 11, 2022
@instant-labs instant-labs deleted a comment from epilys Sep 12, 2022
@instant-labs instant-labs deleted a comment from epilys Sep 12, 2022
@instant-labs instant-labs deleted a comment from epilys Sep 12, 2022
@instant-labs instant-labs deleted a comment from epilys Sep 12, 2022
@cycraig cycraig mentioned this pull request Sep 27, 2022
10 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants