Table Columns
Table allows you to define and reference columns. A Column is another struct in Vantage.
Using columns outside of table context is convenient, such as for defining an expression:
#![allow(unused)] fn main() { let users = Table::new("users", postgres()) .with_column("id") .with_column("name") .with_column("surname") .with_expression("full_name", |t| { expr!( "concat_ws({}, {}, {})", " ", t.get_column("name").unwrap(), t.get_column("surname").unwrap() ) }); }
Columns can also be used in conditions:
#![allow(unused)] fn main() { let condition = users.get_column("name").unwrap().eq("John"); let john = user.with_condition(condition); }
When you use user.get(), with User { name, surname, full_name }, Table needs to
ensure query would include both columns and expression too. More broadly, lets talk about
what can be deserialised into a User entity fields:
Field could be:
- Column - There is a physical SQL column
nameandsurname. - Expression - No physical column, but
full_nameis defined through a SQL expression. - Joined Columns - Table could be joined with another table, combining columns.
- Callback - Value is calculated after record is fetched using a callback.
Lets dive into the following example scenario:
users: Table<Postgres, User>has a columnnameandsurname, using table "user"- I added an expression
full_namethat combinesnameandsurnamecolumns - I also joined "user_address" table, that contains
streetandcitycolumns - I also define callback for calculating
post_codeby fetching from external API or cache.
After above operations the following is true:
usershas 3 columnsusershas 1 expressionusershas 1 joined table, which has another 2 columnsusershas 1 callbackusershas 6 fields:id,name,surname,full_name,user_address_street,user_address_cityuserscan deserialise intoUserstruct with 7 fields:id,name,surname,full_name,user_address_street,user_address_cityandpost_code
Working with Table Columns: SqlTable
Column operations are implemented in TableWithColumns trait:
add_column- adds a column to the tablecolumns- returns all physical columnsadd_id_columnandadd_title_column- adds a column but also labels itid- returnidcolumntitle- returntitlecolumnid_with_table_alias- returnidcolumn with table aliasget_column- return a column by nameget_column_with_table_alias- return a column by name with table aliassearch_for_field- similar toget_columnbut will look for lazy expressions and columns from joined tables.
Working with Table Columns: Table<D, E>
Table<D, E> implements some additional methods for convenience:
with_column- adds a column to the table and returns itwith_title_column- adds a title column to the table and returns itwith_id_column- adds an id column to the table and returns it
#![allow(unused)] fn main() { let users = Table::new("users", postgres()) .with_id_column("id") .with_title_column("name") .with_column("role_name"); }
Extending Entity with column getters
it is common practice to define field getters like this:
#![allow(unused)] fn main() { pub trait ClientTable: SqlTable { fn name(&self) -> Arc<Column> { self.get_column("name").unwrap() } fn contact_details(&self) -> Arc<Column> { self.get_column("contact_details").unwrap() } fn bakery_id(&self) -> Arc<Column> { self.get_column("bakery_id").unwrap() } } }
This makes it easier to reference columns:
#![allow(unused)] fn main() { expr!( "concat_ws({}, {}, {})", " ", //t.get_column("name").unwrap(), //t.get_column("surname").unwrap() t.name(), t.surname() ) }
Conclusion
Ability to specify columns not by name but through a dedicated method makes use of Rust type system and avoids typos at compile time. Fields in a query can be defined through different means.
Swapping Column into Expression allow you to restructure your field names without changing the code, also you can bring columns from across the entities, but for that we will need to learn more about Expressions