Entities and mapping
A Canyon entity starts as an ordinary Rust struct. Its fields tell the mapper what to read from a row. The database table still has to exist, and its columns must match the model.
For the teams table from the previous chapter:
#![allow(unused)] fn main() { use canyon_sql::macros::{canyon_entity, CanyonMapper, Crud, Fields}; #[derive(Debug, Fields, Crud, CanyonMapper)] #[canyon_entity(table_name = "teams")] pub struct Team { #[primary_key] pub id: i64, pub name: String, } }
There are four Canyon pieces in this example:
#[canyon_entity]supplies the table name and processes field markers such as#[primary_key]. It also registers this struct for the experimental migrations machinery.CanyonMapperconverts driver rows toTeam.Crudgenerates read, insert, update, and delete operations.Fieldsgenerates names and typed values for the query builder. Ordinaryfind_all()orinsert()calls do not need it.
When do you need #[canyon_entity]?
The derives can infer team from Team without an entity attribute. If a model has no Canyon field markers and its table follows that naming convention, you can omit #[canyon_entity].
Use #[canyon_entity] when:
- The physical table or schema differs from the inferred name: pass
table_name = "..."orschema = "...". - You annotate fields with Canyon markers such as
#[primary_key]or#[foreign_key(...)]. The current attribute macro processes these markers; keep it on models that use them.
Registration for experimental migrations is a side effect, not a reason to enable that feature for normal CRUD. Query execution uses the metadata generated by the derives; it does not look up a runtime registry of entities.
Table names and schemas
Without an explicit name, Canyon derives a snake_case table name from the Rust type: TournamentDetails maps to tournament_details. Existing schemas do not always follow that rule. Use explicit metadata when they do not:
#![allow(unused)] fn main() { #[canyon_entity(table_name = "tournament_entries", schema = "public")] }
Generated CRUD and relationship operations on the model use that name. Repository adapters have a current limitation with custom table names.
Primary keys
#[primary_key] identifies the field used by key-based reads, updates, and deletes. For a numeric key, it is treated as database-generated and auto-incrementing unless you say otherwise. After a successful insert(&mut self), Canyon writes the generated key back to the Rust instance.
#![allow(unused)] fn main() { #[primary_key(autoincremental = false)] pub external_id: i64, }
Use the non-incrementing form when your application supplies the key. You can map a table without annotating a primary key, but find_by_pk, update, and delete then have no key to use and return a typed error.
What Fields generates
For Team, the derive exposes three types:
TeamTablerepresents table metadata.TeamFieldnames a column, for exampleTeamField::name. Joins and ordering need this form.TeamFieldValuepairs a column with a value of its Rust type, for exampleTeamFieldValue::name("Blue".to_owned()). Predicates need both pieces.
Fields is the supported API in 0.5.1, and several models may derive it in one Rust module. A future major version may use typed column descriptors instead. Nothing is being removed in this release.
The query-builder chapter shows how the enums work now.
Mapping failures are real errors
A row may contain extra columns; the derived mapper ignores those it does not need. It cannot ignore a problem with a field the struct does require:
- A required column is missing.
- A non-optional field receives SQL
NULL. - The database value cannot be converted to the Rust field type.
Each case returns CanyonError::Mapping, not an empty result.
Nullable column? Use
Option<T>on the model field when the SQL column can containNULL.
The CRUD chapters reuse this Team model when they omit its definition.
For an unusual projection, you can implement canyon_sql::core::RowMapper yourself. Its backend-specific deserialization methods return CanyonResult. You then take responsibility for column names, nullability, and conversions on every backend you enable; start with CanyonMapper when a normal row struct will do.