From c675cb2743f715226c32be7c27108e784f39c6af Mon Sep 17 00:00:00 2001 From: Elijah Date: Fri, 20 Mar 2026 17:23:03 +0000 Subject: [PATCH 1/9] Improve model guide --- docs/db-models.md | 118 ++++++++++++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/docs/db-models.md b/docs/db-models.md index cc6d4ef2..6a2efe9c 100644 --- a/docs/db-models.md +++ b/docs/db-models.md @@ -35,6 +35,82 @@ cot migration make This will create a new file in your `migrations` directory in the crate's src directory. We will come back to the contents of this file later in this guide, but for now, let's focus on how to use the model to interact with the database. +## Model Fields + + +### primary_key +This is used to mark a field as the primary key of the table. This is a required field for every model. + +```rust +#[model] +pub struct Post { + #[model(primary_key)] + id: Auto, + title: String, + content: String, +} +``` + +### unique +This is used to mark a field as unique, which means that each value in this field must be unique across all rows in the table. For more information see the [model field reference](https://docs.rs/cot_macros/0.5.0/cot_macros/attr.model.html). + +```rust +#[model] +pub struct User { + #[model(primary_key)] + id: Auto, + #[model(unique)] + username: String, +} +``` + +## Field Types + +Cot + +## Relationships +Relational databases are all about relationships between tables, and Cot provides a convenient way to define database relationships between models. + + +### Foreign keys + +To define a foreign key relationship between two models, you can use the [`ForeignKey`](https://docs.rs/cot/latest/cot/db/enum.ForeignKey.html) type. Here's an example of how you can define a foreign key relationship between a `Link` model and some other `User` model: + +```rust +use cot::db::ForeignKey; + +#[model] +pub struct Link { + #[model(primary_key)] + id: Auto, + #[model(unique)] + slug: LimitedString<32>, + url: String, + user: ForeignKey, +} + +#[model] +pub struct User { + #[model(primary_key)] + id: Auto, + name: String, +} +``` + +When you define a foreign key relationship, Cot will automatically create a foreign key constraint in the database. This constraint will ensure that the value in the `user_id` field of the `Link` model corresponds to a valid primary key in the `User` model. + +When you retrieve a model that has a foreign key relationship, Cot will not automatically fetch the related model and populate the foreign key field with the corresponding value. Instead, you need to explicitly fetch the related model using the `get` method of the `ForeignKey` object. Here's an example of how you can fetch the related user for a link: + +```rust +let mut link = query!(Link, $slug == LimitedString::new("cot").unwrap()) + .get(db) + .await? + .expect("Link not found"); + +let user = link.user.get(db).await?; +``` + + ## Common operations ### Saving models @@ -42,9 +118,9 @@ This will create a new file in your `migrations` directory in the crate's src di In order to write a model instance to the database, you can use the [`save`](trait@cot::db::Model#method.save) method. Note that you need to have an instance of the [`Database`](struct@cot::db::Database) structure to do this – typically you can get it from the request object in your view. Here's an example of how you can save a new link to the database inside a view: ```rust -use cot::request::extractors::RequestDb; +use cot::db::{Auto, LimitedString, Database}; -async fn create_link(RequestDb(db): RequestDb) -> cot::Result { +async fn create_link(db: Database) -> cot::Result { let mut link = Link { id: Auto::default(), slug: LimitedString::new("slug").unwrap(), @@ -140,44 +216,6 @@ Similarly, there is also [`bulk_insert_or_update`](trait@cot::db::Model#method.b Link::bulk_insert_or_update(db, &mut links).await?; ``` -## Foreign keys - -To define a foreign key relationship between two models, you can use the [`ForeignKey`](enum@cot::db::ForeignKey) type. Here's an example of how you can define a foreign key relationship between a `Link` model and some other `User` model: - -```rust -use cot::db::ForeignKey; - -#[model] -pub struct Link { - #[model(primary_key)] - id: Auto, - #[model(unique)] - slug: LimitedString<32>, - url: String, - user: ForeignKey, -} - -#[model] -pub struct User { - #[model(primary_key)] - id: Auto, - name: String, -} -``` - -When you define a foreign key relationship, Cot will automatically create a foreign key constraint in the database. This constraint will ensure that the value in the `user_id` field of the `Link` model corresponds to a valid primary key in the `User` model. - -When you retrieve a model that has a foreign key relationship, Cot will not automatically fetch the related model and populate the foreign key field with the corresponding value. Instead, you need to explicitly fetch the related model using the [`get`](enum@cot::db::ForeignKey#method.get) method of the [`ForeignKey`](enum@cot::db::ForeignKey) object. Here's an example of how you can fetch the related user for a link: - -```rust -let mut link = query!(Link, $slug == LimitedString::new("cot").unwrap()) - .get(db) - .await? - .expect("Link not found"); - -let user = link.user.get(db).await?; -``` - ## Database Configuration Configure your database connection in the configuration files inside your `config` directory: From 6043d59a46cd0b6c5f58b4af437f583fde2403e6 Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 31 Mar 2026 23:17:46 +0000 Subject: [PATCH 2/9] break db docs into multiple pages --- docs/databases/overview.md | 134 +++++++++++++++ docs/databases/queries.md | 323 +++++++++++++++++++++++++++++++++++++ 2 files changed, 457 insertions(+) create mode 100644 docs/databases/overview.md create mode 100644 docs/databases/queries.md diff --git a/docs/databases/overview.md b/docs/databases/overview.md new file mode 100644 index 00000000..03477076 --- /dev/null +++ b/docs/databases/overview.md @@ -0,0 +1,134 @@ +--- +title: Overview +--- + +Cot comes with its own ORM (Object-Relational Mapping) system, which is a layer of abstraction that allows you to interact with your database using objects instead of raw SQL queries. This makes it easier to work with your database and allows you to write more maintainable code. It abstracts over the specific database engine that you are using, so you can switch between different databases without changing your code. The Cot ORM is also capable of automatically creating migrations for you, so you can easily update your database schema as your application evolves, just by modifying the corresponding Rust structures. + +## Defining models + +To define a model in Cot, you need to create a new Rust structure that implements the [`Model`](trait@cot::db::Model) trait. This trait requires you to define the name of the table that the model corresponds to, as well as the fields that the table should have. Here's an example of a simple model that represents a link in a link shortener service: + +```rust +use cot::db::{model, Auto, LimitedString}; + +#[model] +pub struct Link { + #[model(primary_key)] + id: Auto, + #[model(unique)] + slug: LimitedString<32>, + url: String, +} +``` + +There's some very useful stuff going on here, so let's break it down: + +* The [`#[model]`](attr@cot::db::model) attribute is used to mark the structure as a model. This is required for the Cot ORM to recognize it as such. +* The `id` field is a typical database primary key, which means that it uniquely identifies each row in the table. It's of type `i64`, which is a 64-bit signed integer. [`Auto`](enum@cot::db::Auto) wrapper is used to automatically generate a new value for this field when a new row is inserted into the table (`AUTOINCREMENT` or `SERIAL` value in the database nomenclature). +* The `slug` field is marked as [`unique`](attr@cot::db::model), which means that each value in this field must be unique across all rows in the table. It's of type [`LimitedString<32>`](struct@cot::db::LimitedString), which is a string with a maximum length of `32` characters. This is a custom type provided by Cot that ensures that the string is not longer than the specified length at the time of constructing an instance of the structure. + +After putting this structure in your project, you can use it to interact with the database. Before you do that though, it's necessary to create the table in the database that corresponds to this model. Cot CLI has got you covered and can automatically create migrations for you – just run the following command: + +```bash +cot migration make +``` + +This will create a new file in your `migrations` directory in the crate's src directory. We will come back to the contents of this file later in this guide, but for now, let's focus on how to use the model to interact with the database. + +## Model Fields Options +Cot provides specific field-level attributes that provide special meaning to fields in a model. The most common ones are listed below: + +### `primary_key` +This is used to mark a field as the primary key of the table. This is a required field for every model. + +```rust +#[model] +pub struct Post { + #[model(primary_key)] + id: Auto, + title: String, + content: String, +} +``` + +### `unique` +This is used to mark a field as unique, which means that each value in this field must be unique across all rows in the table. For more information see the [model field reference](https://docs.rs/cot_macros/0.5.0/cot_macros/attr.model.html). + +```rust +#[model] +pub struct User { + #[model(primary_key)] + id: Auto, + #[model(unique)] + username: String, +} +``` + +## Field Types + +Cot + +## Relationships +Relational databases are all about relationships between tables, and Cot provides a convenient way to define database relationships between models. + + +### Foreign keys + +To define a foreign key relationship between two models, you can use the [`ForeignKey`](https://docs.rs/cot/latest/cot/db/enum.ForeignKey.html) type. Here's an example of how you can define a foreign key relationship between a `Link` model and some other `User` model: + +```rust +use cot::db::ForeignKey; + +#[model] +pub struct Link { + #[model(primary_key)] + id: Auto, + #[model(unique)] + slug: LimitedString<32>, + url: String, + user: ForeignKey, +} + +#[model] +pub struct User { + #[model(primary_key)] + id: Auto, + name: String, +} +``` + +When you define a foreign key relationship, Cot will automatically create a foreign key constraint in the database. This constraint will ensure that the value in the `user_id` field of the `Link` model corresponds to a valid primary key in the `User` model. + +When you retrieve a model that has a foreign key relationship, Cot will not automatically fetch the related model and populate the foreign key field with the corresponding value. Instead, you need to explicitly fetch the related model using the `get` method of the `ForeignKey` object. Here's an example of how you can fetch the related user for a link: + +```rust +let mut link = query!(Link, $slug == LimitedString::new("cot").unwrap()) + .get(db) + .await? + .expect("Link not found"); + +let user = link.user.get(db).await?; +``` + + +## Database Configuration + +Configure your database connection in the configuration files inside your `config` directory: + +```toml +[database] +# SQLite +url = "sqlite://db.sqlite3?mode=rwc" + +# Or PostgreSQL +url = "postgresql://user:password@localhost/dbname" + +# Or MySQL +url = "mysql://user:password@localhost/dbname" +``` + +Cot tries to be as consistent as possible when it comes to the database engine you are using. This means that you can use SQLite for development and testing, and then switch to PostgreSQL or MySQL for production without changing your code. The only thing you need to do is to change the [`url`](struct@cot::config::DatabaseConfig#structfield.url) value in the configuration file! + +## Summary + +In this chapter you learned how to define your own models in Cot, how to interact with the database using these models, and how to define foreign key relationships between models. In the next chapter, we'll try to register these models in the admin panel so that you can manage them through an easy-to-use web interface. diff --git a/docs/databases/queries.md b/docs/databases/queries.md new file mode 100644 index 00000000..a47920a7 --- /dev/null +++ b/docs/databases/queries.md @@ -0,0 +1,323 @@ +--- +title: Queries +--- + +Cot provides a [`Query`](struct@cot::query::Query) interface that allows you to write queries +to the database. In this guide, we'll cover the basics of writing queries after you've setup your +database and models. For a complete reference, see the [`Query`](struct@cot::query::Query) docs. + +For the rest of this guide, we'll use the following models which show a simple e-commerce application. + +```rust +use cot::db::ForeignKey; +use cot::{db, Auto, LimitedString}; +use cot::common_types::Email; + +#[model] +pub struct Customer { + #[model(primary_key)] + id: Auto, + #[model(unique)] + email: Email, + full_name: LimitedString<128>, + is_verified: bool, +} + +#[model] +pub struct Product { + #[model(primary_key)] + id: Auto, + #[model(unique)] + sku: LimitedString<64>, + name: LimitedString<255>, + price_cents: i64, + stock: i32, + is_available: bool, +} + +#[model] +pub struct Order { + #[model(primary_key)] + id: Auto, + customer: ForeignKey, + product: ForeignKey, + quantity: i32, + is_fulfilled: bool, +} + +``` + +## Creating an object + +To create a new model instance, cot provides the [`insert`](trait@cot::db::Model#method.insert) method. +In the example below, we create a new `Customer` instance and save it to the database. + +```rust +use cot::db::{Auto, Database}; +use cot::common_types::Email; + +async fn create_customer(db: Database) -> cot::Result<()> { + let mut customer = Customer { + id: Auto::default(), + email: Email::from_str("jondoe@example.com").unwrap(), + full_name: LimitedString::new("Jon Doe").unwrap(), + is_verified: false, + }; + customer.insert(db).await?; +} +``` + +Note that the `insert` method will return a `UniqueViolation` error if a record with the same primary key already exists. +The example below shows an attempt to insert a new `Customer` instance with the same primary key as an existing one. + +```rust +use cot::db::{Auto, Database}; +use cot::common_types::Email; + +async fn create_customer(db: Database) -> cot::Result<()> { + let mut customer1 = Customer { + id: Auto::fixed(1), + email: Email::from_str("doejon@example.com").unwrap(), + full_name: LimitedString::new("Jon Doe").unwrap(), + is_verified: false, + }; + customer1.insert(db).await?; + + // This will fail with a UniqueViolation error. + let mut customer2 = Customer { + id: Auto::fixed(1), + email: Email::from_str("jondoe@example.com").unwrap(), + full_name: LimitedString::new("Jon Doe").unwrap(), + is_verified: false, + }; + customer2.insert(db).await?; +} +``` + +[//]: # (Cot also provides the [`save`](trait@cot::db::Model#method.save) method, which is a shortcut for) + +[//]: # (inserting a new model instance if it doesn't exist, or updating it if it does. Replacing `insert` with `save`) + +[//]: # (in the previous example should yield the same result.) + +#### Creating multiple objects +If you need to create multiple objects, cot provides the [`bulk_insert`](trait@cot::db::Model#method.bulk_insert) method for this purpose. +It is recommended to prefer `bulk_insert` for multiple insertions as it performs the operations in a single database query which is much more efficient than performing multiple individual `insert` or `save` calls. + +The example below shows how to create multiple `Customer` instances. + +```rust +use cot::db::{Auto, Database}; +use cot::common_types::Email; + +async fn create_customers(db: Database) -> cot::Result<()> { + let customers = vec![ + Customer { + id: Auto::default(), + email: Email::from_str("janedoe@example.com").unwrap(), + full_name: LimitedString::new("Jane Doe").unwrap(), + is_verified: false, + }, + Customer { + id: Auto::default(), + email: Email::from_str("jondoe@example.com").unwrap(), + full_name: LimitedString::new("Jon Doe").unwrap(), + is_verified: false, + }, + ]; + + Customer::bulk_insert(db, customers).await?; +} +``` + +Note that [`bulk_insert`](trait@cot::db::Model#method.bulk_insert) takes a mutable slice of models, because it needs to update the primary keys of the inserted models with the values generated by the database. + +Similarly, there is also [`bulk_insert_or_update`](trait@cot::db::Model#method.bulk_insert_or_update) method, which works like [`bulk_insert`](trait@cot::db::Model#method.bulk_insert), but updates the existing rows if they conflict with the new ones. + +## Updating an object +Cot provides the [`update`](trait@cot::db::Model#method.update) method to update an existing model instance. +The example below shows how to update the `full_name` field of a `Customer` instance. + +```rust +customer.full_name = "Jane Doe".into(); +customer.update(db).await?; +``` + +## Creating or Updating an object +Cot provides the [`save`](trait@cot::db::Model#method.save) method to create a new model instance if it doesn't exist, or update it if it does. +In the example below, we create a Customer instance if it doesnt exist and then update it's verified status. + +```rust +use cot::db::{Auto, Database, LimitedString}; +use cot::common_types::Email; + +async fn save_customer(db: Database) -> cot::Result<()> { + let mut customer = Customer { + id: Auto::default(), + email: Email::from_str("jondoe@example.com").unwrap(), + full_name: LimitedString::new("Jon Doe").unwrap(), + is_verified: false, + }; + customer.save(db).await?; + + // update the customer's verified status + customer.is_verified = true; + customer.save(db).await?; +} +``` + +## Saving ForeignKey fields +Saving a foreign key field is similar to saving a regular field. There are two variants of foreign key fields provided by cot: + +### `ForeignKey::Model` +This is the most common variant which allows you to save a foreign key field with a model instance. +The example below shows how to save a `Customer` instance as a foreign key field of an `Order` instance. + +```rust +use cot::db::{Auto, Database}; +use cot::common_types::Email; + +async fn save_order(db: Database) -> cot::Result<()> { + let mut customer = Customer { + id: Auto::default(), + email: Email::from_str("jondoe@example.com").unwrap(), + full_name: LimitedString::new("Jon Doe").unwrap(), + is_verified: false, + }; + customer.save(db).await?; + + let product = Product { + id: Auto::default(), + sku: "ABC123".into(), + name: "Product 1".into(), + price_cents: 1000, + stock: 10, + is_available: true, + }; + product.save(db).await?; + + let mut order = Order { + id: Auto::default(), + customer: ForeignKey::Model(Box::new(customer)), + product: ForeignKey::Model(Box::new(product)), + quantity: 1, + is_fulfilled: false, + }; + order.save(db).await?; +} + +``` + +### `ForeignKey::PrimaryKey` +Alternatively, cot allows you to specify the primary key of the model instance (which is the foreign key id) directly. + +```rust +use cot::db::{Auto, Database}; +use cot::common_types::Email; + +async fn save_order(db: Database) -> cot::Result<()> { + let mut order = Order { + id: Auto::default(), + customer: ForeignKey::PrimaryKey(Auto::fixed(1)), // customer id + product: ForeignKey::PrimaryKey(Auto::fixed(1)), // product id + quantity: 1, + is_fulfilled: false, + }; + order.save(db).await?; +} +``` + +Note that this will fail if the primary key of the referenced model does not exist. + + +## Retrieving objects + +To retrieve objects from the database, cot provides the [`Query`](struct@cot::query::Query) struct which allows you to +perform various queries on the database. The [`Query`](struct@cot::query::Query) struct provides methods such as `filter`, which can be used filter to the +results of a query.The [`Query`](struct@cot::query::Query) object can be accessed by calling the `objects` method on the model. The example below shows how to retrieve a +Customer instance with the primary key of `5`. + +```rust +use cot::db::{Database}; + +async fn get_customer(db: Database) -> cot::Result<()> { + let customer = Customer::objects().filter(Expr::eq(Expr::field("id"), Expr::value("5"))).get(&db).await?; + println!("Customer: {:?}", customer); +} + +``` + +The `filter` method takes a [`filter expression`](enum@cot::db::query::Expr). In the example above, the expression, `Expr::eq(Expr::field("id"), Expr::value("5"))`, is +evaluated as `id = 5`. + +Cot also provides the [`query!`](macro@cot::query) macro which provides a convenient way to write queries in a declarative style without having to manually construct [`Expr`] expressions. +The example above can be rewritten as follows: + +```rust +use cot::db::{Database}; +use cot::query; + +async fn get_customer(db: Database) -> cot::Result<()> { + let customer = query!(Customer, $id==5).get(db).await?; + println!("Customer: {:?}", customer); +} +``` +Note that the `get` method will return an error if the query returns more than one result. + +### Retrieving all objects + +One way to retrieve all objects of a model is to call the `all` method after filtering the query results. + +```rust +use cot::db::{Database}; + +async fn get_all_customers(db: Database) -> cot::Result<()> { + let customers = Customer::objects().filter(Expr::gt(Expr::field("id"), Expr::value("5"))).all(db).await?; + println!("Customers: {:?}", customers); +} + +``` + +The example above retrieves all customers with a primary key greater than `5`. This returns a list of `Customer` instances. + + +##### Chaining filters +The `filter` method returns a new [`Query`](struct@cot::query::Query) instance which makes it convenient to chain multiple filters. + +```rust +use cot::db::{Database}; + +async fn get_customers(db: Database) -> cot::Result<()> { + let customers = Customer::objects() + .filter(Expr::gt(Expr::field("id"), Expr::value("5"))) + .filter(Expr::eq(Expr::field("full_name"), Expr::value("Jon Doe"))).all(db).await?; + println!("Customers: {:?}", customers); +} + +``` + +The example above shows how to retrieve all customers with a primary key greater than `5` and whose full name is `Jon Doe`. + +> Note: Although this example works, the idiomatic way to do this is to use the [`Expr::and`]() expression instead. + +Similarly, the [`query`](macro@cot::query) macro returns a new [`Query`](struct@cot::query::Query) instance which can be used to chain multiple filters. + + +## Removing an object +The `delete` method can be used to remove an object from the database. The example below shows how to remove a `Customer` instance with the primary key of `5`. + +```rust +use cot::db::{Database}; +use cot::query; + +async fn delete_customer(db: Database) -> cot::Result<()> { + query!(Customer, $id==5).delete(&db).await?; +} + +``` + +### Other Query methods +The methods listed on this page are the most commonly used query methods. For a complete comprehensive list of supported query methods, see the [`Query`](struct@cot::query::Query) docs. + + + From e28805f60cd1dd7fb3ae7a6e8998e22110dc02d0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 23:18:05 +0000 Subject: [PATCH 3/9] chore(pre-commit.ci): auto fixes from pre-commit hooks --- docs/databases/queries.md | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/docs/databases/queries.md b/docs/databases/queries.md index a47920a7..f515e0e7 100644 --- a/docs/databases/queries.md +++ b/docs/databases/queries.md @@ -49,7 +49,7 @@ pub struct Order { ## Creating an object -To create a new model instance, cot provides the [`insert`](trait@cot::db::Model#method.insert) method. +To create a new model instance, cot provides the [`insert`](trait@cot::db::Model#method.insert) method. In the example below, we create a new `Customer` instance and save it to the database. ```rust @@ -82,7 +82,7 @@ async fn create_customer(db: Database) -> cot::Result<()> { is_verified: false, }; customer1.insert(db).await?; - + // This will fail with a UniqueViolation error. let mut customer2 = Customer { id: Auto::fixed(1), @@ -110,7 +110,7 @@ The example below shows how to create multiple `Customer` instances. use cot::db::{Auto, Database}; use cot::common_types::Email; -async fn create_customers(db: Database) -> cot::Result<()> { +async fn create_customers(db: Database) -> cot::Result<()> { let customers = vec![ Customer { id: Auto::default(), @@ -122,10 +122,10 @@ async fn create_customers(db: Database) -> cot::Result<()> { id: Auto::default(), email: Email::from_str("jondoe@example.com").unwrap(), full_name: LimitedString::new("Jon Doe").unwrap(), - is_verified: false, + is_verified: false, }, ]; - + Customer::bulk_insert(db, customers).await?; } ``` @@ -159,7 +159,7 @@ async fn save_customer(db: Database) -> cot::Result<()> { is_verified: false, }; customer.save(db).await?; - + // update the customer's verified status customer.is_verified = true; customer.save(db).await?; @@ -170,7 +170,7 @@ async fn save_customer(db: Database) -> cot::Result<()> { Saving a foreign key field is similar to saving a regular field. There are two variants of foreign key fields provided by cot: ### `ForeignKey::Model` -This is the most common variant which allows you to save a foreign key field with a model instance. +This is the most common variant which allows you to save a foreign key field with a model instance. The example below shows how to save a `Customer` instance as a foreign key field of an `Order` instance. ```rust @@ -185,7 +185,7 @@ async fn save_order(db: Database) -> cot::Result<()> { is_verified: false, }; customer.save(db).await?; - + let product = Product { id: Auto::default(), sku: "ABC123".into(), @@ -195,7 +195,7 @@ async fn save_order(db: Database) -> cot::Result<()> { is_available: true, }; product.save(db).await?; - + let mut order = Order { id: Auto::default(), customer: ForeignKey::Model(Box::new(customer)), @@ -232,9 +232,9 @@ Note that this will fail if the primary key of the referenced model does not exi ## Retrieving objects -To retrieve objects from the database, cot provides the [`Query`](struct@cot::query::Query) struct which allows you to -perform various queries on the database. The [`Query`](struct@cot::query::Query) struct provides methods such as `filter`, which can be used filter to the -results of a query.The [`Query`](struct@cot::query::Query) object can be accessed by calling the `objects` method on the model. The example below shows how to retrieve a +To retrieve objects from the database, cot provides the [`Query`](struct@cot::query::Query) struct which allows you to +perform various queries on the database. The [`Query`](struct@cot::query::Query) struct provides methods such as `filter`, which can be used filter to the +results of a query.The [`Query`](struct@cot::query::Query) object can be accessed by calling the `objects` method on the model. The example below shows how to retrieve a Customer instance with the primary key of `5`. ```rust @@ -318,6 +318,3 @@ async fn delete_customer(db: Database) -> cot::Result<()> { ### Other Query methods The methods listed on this page are the most commonly used query methods. For a complete comprehensive list of supported query methods, see the [`Query`](struct@cot::query::Query) docs. - - - From 228717f6895eba9bf332e7c76dfa28da5825f5de Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 2 Apr 2026 16:52:46 +0000 Subject: [PATCH 4/9] change md_page to support new GuideItem syntax on cot-site --- docs/db-models.md | 8 ++++---- docs/site/src/main.rs | 40 ++++++++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/docs/db-models.md b/docs/db-models.md index 6a2efe9c..015738d7 100644 --- a/docs/db-models.md +++ b/docs/db-models.md @@ -35,10 +35,10 @@ cot migration make This will create a new file in your `migrations` directory in the crate's src directory. We will come back to the contents of this file later in this guide, but for now, let's focus on how to use the model to interact with the database. -## Model Fields +## Model Fields Options +Cot provides specific field-level attributes that provide special meaning to fields in a model. The most common ones are listed below: - -### primary_key +### `primary_key` This is used to mark a field as the primary key of the table. This is a required field for every model. ```rust @@ -51,7 +51,7 @@ pub struct Post { } ``` -### unique +### `unique` This is used to mark a field as unique, which means that each value in this field must be unique across all rows in the table. For more information see the [model field reference](https://docs.rs/cot_macros/0.5.0/cot_macros/attr.model.html). ```rust diff --git a/docs/site/src/main.rs b/docs/site/src/main.rs index 96761329..3a93106a 100644 --- a/docs/site/src/main.rs +++ b/docs/site/src/main.rs @@ -6,7 +6,8 @@ use cot::error::handler::DynErrorPageHandler; use cot::project::{MiddlewareContext, RegisterAppsContext, RootHandler, RootHandlerBuilder}; use cot::static_files::StaticFilesMiddleware; use cot::{AppBuilder, Project}; -use cot_site::{CotSiteApp, cot_site_common, cot_site_handle_error, md_page}; +use cot_site::{CotSiteApp, cot_site_common, cot_site_handle_error, md_page, GuideItem}; + struct CotSiteProject; @@ -34,21 +35,32 @@ impl Project for CotSiteProject { ( "Getting started", vec![ - md_page!("introduction"), - md_page!("templates"), - md_page!("forms"), - md_page!("db-models"), - md_page!("admin-panel"), - md_page!("static-files"), - md_page!("sending-emails"), - md_page!("caching"), - md_page!("error-pages"), - md_page!("openapi"), - md_page!("testing"), + GuideItem::Page(md_page!("introduction")), + GuideItem::Page(md_page!("templates")), + GuideItem::Page(md_page!("forms")), + GuideItem::SubCategory { + title: "Database", + pages: vec![ + md_page!("databases/overview"), + md_page!("databases/queries"), + ], + }, + GuideItem::Page(md_page!("admin-panel")), + GuideItem::Page(md_page!("static-files")), + GuideItem::Page(md_page!("sending-emails")), + GuideItem::Page(md_page!("caching")), + GuideItem::Page(md_page!("error-pages")), + GuideItem::Page(md_page!("openapi")), + GuideItem::Page(md_page!("testing")), ], ), - ("Upgrading", vec![md_page!("upgrade-guide")]), - ("About", vec![md_page!("framework-comparison")]), + ("Upgrading", vec![ + GuideItem::Page(md_page!("upgrade-guide")) + ] + ), + ("About", vec![ + GuideItem::Page(md_page!("framework-comparison")) + ]), ]), "", ); From 9f50f27e554f82c500637bd822147c766f02dccf Mon Sep 17 00:00:00 2001 From: Elijah Date: Sun, 5 Apr 2026 00:21:33 +0000 Subject: [PATCH 5/9] fix links --- docs/databases/queries.md | 49 ++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/docs/databases/queries.md b/docs/databases/queries.md index f515e0e7..66859ea3 100644 --- a/docs/databases/queries.md +++ b/docs/databases/queries.md @@ -2,9 +2,9 @@ title: Queries --- -Cot provides a [`Query`](struct@cot::query::Query) interface that allows you to write queries +Cot provides a [`Query`](struct@cot::db::query::Query) interface that allows you to write queries to the database. In this guide, we'll cover the basics of writing queries after you've setup your -database and models. For a complete reference, see the [`Query`](struct@cot::query::Query) docs. +database and models. For a complete reference, see the [`Query`](struct@cot::db::query::Query) docs. For the rest of this guide, we'll use the following models which show a simple e-commerce application. @@ -67,7 +67,7 @@ async fn create_customer(db: Database) -> cot::Result<()> { } ``` -Note that the `insert` method will return a `UniqueViolation` error if a record with the same primary key already exists. +Note that the [`insert`](trait@cot::db::Model#method.insert) method will return a `UniqueViolation` error if a record with the same primary key already exists. The example below shows an attempt to insert a new `Customer` instance with the same primary key as an existing one. ```rust @@ -94,15 +94,9 @@ async fn create_customer(db: Database) -> cot::Result<()> { } ``` -[//]: # (Cot also provides the [`save`](trait@cot::db::Model#method.save) method, which is a shortcut for) - -[//]: # (inserting a new model instance if it doesn't exist, or updating it if it does. Replacing `insert` with `save`) - -[//]: # (in the previous example should yield the same result.) - #### Creating multiple objects If you need to create multiple objects, cot provides the [`bulk_insert`](trait@cot::db::Model#method.bulk_insert) method for this purpose. -It is recommended to prefer `bulk_insert` for multiple insertions as it performs the operations in a single database query which is much more efficient than performing multiple individual `insert` or `save` calls. +It is recommended to prefer [`bulk_insert`](trait@cot::db::Model#method.bulk_insert) for multiple insertions as it performs the operations in a single database query which is much more efficient than performing multiple individual [`insert`](trait@cot::db::Model#method.insert) or [`save`](trait@cot::db::Model#method.save) calls. The example below shows how to create multiple `Customer` instances. @@ -111,7 +105,7 @@ use cot::db::{Auto, Database}; use cot::common_types::Email; async fn create_customers(db: Database) -> cot::Result<()> { - let customers = vec![ + let mut customers = vec![ Customer { id: Auto::default(), email: Email::from_str("janedoe@example.com").unwrap(), @@ -126,7 +120,7 @@ async fn create_customers(db: Database) -> cot::Result<()> { }, ]; - Customer::bulk_insert(db, customers).await?; + Customer::bulk_insert(db, &mut customers).await?; } ``` @@ -171,7 +165,7 @@ Saving a foreign key field is similar to saving a regular field. There are two v ### `ForeignKey::Model` This is the most common variant which allows you to save a foreign key field with a model instance. -The example below shows how to save a `Customer` instance as a foreign key field of an `Order` instance. +The example below shows how to save a `Customer` instance and a `Product` instance as foreign key fields of an `Order` instance. ```rust use cot::db::{Auto, Database}; @@ -218,8 +212,8 @@ use cot::common_types::Email; async fn save_order(db: Database) -> cot::Result<()> { let mut order = Order { id: Auto::default(), - customer: ForeignKey::PrimaryKey(Auto::fixed(1)), // customer id - product: ForeignKey::PrimaryKey(Auto::fixed(1)), // product id + customer: ForeignKey::PrimaryKey(Auto::fixed(1)), + product: ForeignKey::PrimaryKey(Auto::fixed(1)), quantity: 1, is_fulfilled: false, }; @@ -232,9 +226,9 @@ Note that this will fail if the primary key of the referenced model does not exi ## Retrieving objects -To retrieve objects from the database, cot provides the [`Query`](struct@cot::query::Query) struct which allows you to -perform various queries on the database. The [`Query`](struct@cot::query::Query) struct provides methods such as `filter`, which can be used filter to the -results of a query.The [`Query`](struct@cot::query::Query) object can be accessed by calling the `objects` method on the model. The example below shows how to retrieve a +To retrieve objects from the database, cot provides the [`Query`](struct@cot::db::query::Query) struct which allows you to +perform various queries on the database. The [`Query`](struct@cot::db::query::Query) struct provides methods such as [`filter`](struct@cot::db::query::Query#method.filter), which can be used filter to the +results of a query.The [`Query`](struct@cot::db::query::Query) object can be accessed by calling the [`objects`](trait@cot::db::Model#method.objects) method on the model. The example below shows how to retrieve a Customer instance with the primary key of `5`. ```rust @@ -247,10 +241,10 @@ async fn get_customer(db: Database) -> cot::Result<()> { ``` -The `filter` method takes a [`filter expression`](enum@cot::db::query::Expr). In the example above, the expression, `Expr::eq(Expr::field("id"), Expr::value("5"))`, is +The [`filter`](struct@cot::db::query::Query#method.filter) method takes a [`filter expression`](enum@cot::db::query::Expr). In the example above, the expression, `Expr::eq(Expr::field("id"), Expr::value("5"))`, is evaluated as `id = 5`. -Cot also provides the [`query!`](macro@cot::query) macro which provides a convenient way to write queries in a declarative style without having to manually construct [`Expr`] expressions. +Cot also provides the [`query!`](macro@cot::db::query) macro which provides a convenient way to write queries in a declarative style without having to manually construct [`Expr`](enum@cot::db::query::Expr) expressions. The example above can be rewritten as follows: ```rust @@ -262,11 +256,12 @@ async fn get_customer(db: Database) -> cot::Result<()> { println!("Customer: {:?}", customer); } ``` -Note that the `get` method will return an error if the query returns more than one result. +The [`get`](struct@cot::db::query::Query#method.get) method (as well as the [`all`](struct@cot::db::query::Query#method.all) method which we'll see in the next section) are terminal query methods which are applied to the query object after filtering to retrieve the final results. +Also note that the [`get`](struct@cot::db::query::Query#method.get) method will return an error if the query returns more than one result. ### Retrieving all objects -One way to retrieve all objects of a model is to call the `all` method after filtering the query results. +One way to retrieve all objects of a model is to call the [`all`](struct@cot::db::query::Query#method.all) method after filtering the query results. ```rust use cot::db::{Database}; @@ -282,7 +277,7 @@ The example above retrieves all customers with a primary key greater than `5`. T ##### Chaining filters -The `filter` method returns a new [`Query`](struct@cot::query::Query) instance which makes it convenient to chain multiple filters. +The [`filter`](struct@cot::db::query::Query#method.filter) method returns a new [`Query`](struct@cot::db::query::Query) instance which makes it convenient to chain multiple filters. ```rust use cot::db::{Database}; @@ -298,13 +293,13 @@ async fn get_customers(db: Database) -> cot::Result<()> { The example above shows how to retrieve all customers with a primary key greater than `5` and whose full name is `Jon Doe`. -> Note: Although this example works, the idiomatic way to do this is to use the [`Expr::and`]() expression instead. +> Note: Although this example works, the idiomatic way to do this is to use the [`Expr::and`](enum@cot::db::query::Expr::method.and) expression instead. -Similarly, the [`query`](macro@cot::query) macro returns a new [`Query`](struct@cot::query::Query) instance which can be used to chain multiple filters. +Similarly, the [`query`](macro@cot::db::query) macro returns a new [`Query`](struct@cot::db::query::Query) instance which can be used to chain multiple filters. ## Removing an object -The `delete` method can be used to remove an object from the database. The example below shows how to remove a `Customer` instance with the primary key of `5`. +The [`delete`](struct@cot::db::query::Query#method.delete) method can be used to remove an object from the database. The example below shows how to remove a `Customer` instance with the primary key of `5`. ```rust use cot::db::{Database}; @@ -317,4 +312,4 @@ async fn delete_customer(db: Database) -> cot::Result<()> { ``` ### Other Query methods -The methods listed on this page are the most commonly used query methods. For a complete comprehensive list of supported query methods, see the [`Query`](struct@cot::query::Query) docs. +The methods listed on this page are the most commonly used query methods. For a complete comprehensive list of supported query methods, see the [`Query`](struct@cot::db::query::Query) docs. From 4a035434ea12930dac8420b6983c870b39b5a8bb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 00:24:09 +0000 Subject: [PATCH 6/9] chore(pre-commit.ci): auto fixes from pre-commit hooks --- docs/databases/queries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/databases/queries.md b/docs/databases/queries.md index 66859ea3..96c0b986 100644 --- a/docs/databases/queries.md +++ b/docs/databases/queries.md @@ -256,7 +256,7 @@ async fn get_customer(db: Database) -> cot::Result<()> { println!("Customer: {:?}", customer); } ``` -The [`get`](struct@cot::db::query::Query#method.get) method (as well as the [`all`](struct@cot::db::query::Query#method.all) method which we'll see in the next section) are terminal query methods which are applied to the query object after filtering to retrieve the final results. +The [`get`](struct@cot::db::query::Query#method.get) method (as well as the [`all`](struct@cot::db::query::Query#method.all) method which we'll see in the next section) are terminal query methods which are applied to the query object after filtering to retrieve the final results. Also note that the [`get`](struct@cot::db::query::Query#method.get) method will return an error if the query returns more than one result. ### Retrieving all objects From bfe82284124a438a3e847d16a9092c0093f33ff5 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 8 Apr 2026 23:11:33 +0000 Subject: [PATCH 7/9] add some more docs on model field types and db config --- docs/databases/overview.md | 81 +++++++++++++++++++++++++++++++++++++- docs/introduction.md | 2 +- docs/openapi.md | 2 +- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/docs/databases/overview.md b/docs/databases/overview.md index 03477076..f348d783 100644 --- a/docs/databases/overview.md +++ b/docs/databases/overview.md @@ -65,8 +65,61 @@ pub struct User { ``` ## Field Types +To use a type in a model, they **must** implement both the [`ToDbValue`](trait@cot::db::ToDbValue) and [`FromDbValue`](trait@cot::db::FromDbValue) traits. The [`ToDbValue`](trait@cot::db::ToDbValue) trait tells Cot how to serialize the field value into a format that can be stored in the database (e.g. a string, a number, a boolean, etc.) while the [`FromDbValue`](trait@cot::db::FromDbValue) trait tells Cot how to deserialize the field value from the database format back into the Rust type. +Cot provides implementations of these traits for many common types on a best-effort basis. Refer to the [implementations](trait@cot::db::FromDbValue#foreign-impls) and [implementors](trait@cot::db::FromDbValue#implementors) section of the docs for a complete list of the supported types. -Cot +In the example below, we show how to use a custom type as a field in a model: + +```rust +use cot::db::{DbFieldValue, model, Auto}; +use cot::db::{ToDbFieldValue, FromDbValue, SqlxValueRef}; +use cot::db::impl_mysql::MySqlValueRef; +use cot::db::impl_postgres::PostgresValueRef; +use cot::db::impl_sqlite::SqliteValueRef; + +#[derive(Debug, Clone)] +struct NewType(i32); + +impl FromDbValue for NewType { + fn from_sqlite(value: SqliteValueRef<'_>) -> cot::db::Result + where + Self: Sized + { + Ok(NewType(value.get::()?)) + } + + fn from_postgres(value: PostgresValueRef<'_>) -> cot::db::Result + where + Self: Sized + { + Ok(NewType(value.get::()?)) + } + + fn from_mysql(value: MySqlValueRef<'_>) -> cot::db::Result + where + Self: Sized + { + Ok(NewType(value.get::()?)) + } +} + + +impl ToDbFieldValue for NewType { + fn to_db_field_value(&self) -> DbFieldValue { + self.0.clone().into() + } +} + + +#[model] +#[derive(Debug, Clone)] +pub struct Post { + #[model(primary_key)] + id: Auto, + new_type: NewType, +} + +``` ## Relationships Relational databases are all about relationships between tables, and Cot provides a convenient way to define database relationships between models. @@ -129,6 +182,30 @@ url = "mysql://user:password@localhost/dbname" Cot tries to be as consistent as possible when it comes to the database engine you are using. This means that you can use SQLite for development and testing, and then switch to PostgreSQL or MySQL for production without changing your code. The only thing you need to do is to change the [`url`](struct@cot::config::DatabaseConfig#structfield.url) value in the configuration file! +As an alternative to setting the database configuration in the `TOML` file, you can also set it programmatically in the [`config`](trait@cot::project::Project#method.config) method of your project: + + +```rust +use cot::config::DatabaseConfig; + +struct MyProject; + +impl Project for MyProject { + fn config(&self) -> cot::Result { + Ok( + ProjectConfig::builder() + .database( + DatabaseConfig::builder() + .url("sqlite://db.sqlite3?mode=rwc") + .build(), + ) + .build() + ) + } +} + +``` + ## Summary -In this chapter you learned how to define your own models in Cot, how to interact with the database using these models, and how to define foreign key relationships between models. In the next chapter, we'll try to register these models in the admin panel so that you can manage them through an easy-to-use web interface. +In this chapter you learned about the Cot ORM and how to define models, fields, and relationships between models. You also learned how to configure your database connection and how to use the models to interact with the database. In the next chapter, we will dive deeper into how to perform various database operations using the Cot ORM. diff --git a/docs/introduction.md b/docs/introduction.md index b2658a61..244d606a 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -252,7 +252,7 @@ fn main() -> impl Project { Finally, the `main` function just returns the Project implementation, which is the entry point for your application. Cot takes care of running it by providing a command line interface! -## Final words +## Summary In this chapter, you learned about: diff --git a/docs/openapi.md b/docs/openapi.md index 983dcf94..97cd7450 100644 --- a/docs/openapi.md +++ b/docs/openapi.md @@ -305,7 +305,7 @@ In order for your parameter or response type to generate OpenAPI specification, The key is to modify the `Operation` object appropriately for your extractor, adding parameters, request bodies, or other OpenAPI elements as needed. -## Conclusion +## Summary Cot's OpenAPI integration provides a powerful way to automatically generate comprehensive API documentation while maintaining type safety. By leveraging the schema generation capabilities, you can create well-documented APIs with minimal overhead, making your services more accessible and easier to use. From 5019b832ca1d476e6963e11a094d205ac512ac25 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 8 Apr 2026 23:20:56 +0000 Subject: [PATCH 8/9] delete db-models file --- docs/db-models.md | 239 ---------------------------------------------- 1 file changed, 239 deletions(-) delete mode 100644 docs/db-models.md diff --git a/docs/db-models.md b/docs/db-models.md deleted file mode 100644 index 015738d7..00000000 --- a/docs/db-models.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -title: Database models ---- - -Cot comes with its own ORM (Object-Relational Mapping) system, which is a layer of abstraction that allows you to interact with your database using objects instead of raw SQL queries. This makes it easier to work with your database and allows you to write more maintainable code. It abstracts over the specific database engine that you are using, so you can switch between different databases without changing your code. The Cot ORM is also capable of automatically creating migrations for you, so you can easily update your database schema as your application evolves, just by modifying the corresponding Rust structures. - -## Defining models - -To define a model in Cot, you need to create a new Rust structure that implements the [`Model`](trait@cot::db::Model) trait. This trait requires you to define the name of the table that the model corresponds to, as well as the fields that the table should have. Here's an example of a simple model that represents a link in a link shortener service: - -```rust -use cot::db::{model, Auto, LimitedString}; - -#[model] -pub struct Link { - #[model(primary_key)] - id: Auto, - #[model(unique)] - slug: LimitedString<32>, - url: String, -} -``` - -There's some very useful stuff going on here, so let's break it down: - -* The [`#[model]`](attr@cot::db::model) attribute is used to mark the structure as a model. This is required for the Cot ORM to recognize it as such. -* The `id` field is a typical database primary key, which means that it uniquely identifies each row in the table. It's of type `i64`, which is a 64-bit signed integer. [`Auto`](enum@cot::db::Auto) wrapper is used to automatically generate a new value for this field when a new row is inserted into the table (`AUTOINCREMENT` or `SERIAL` value in the database nomenclature). -* The `slug` field is marked as [`unique`](attr@cot::db::model), which means that each value in this field must be unique across all rows in the table. It's of type [`LimitedString<32>`](struct@cot::db::LimitedString), which is a string with a maximum length of `32` characters. This is a custom type provided by Cot that ensures that the string is not longer than the specified length at the time of constructing an instance of the structure. - -After putting this structure in your project, you can use it to interact with the database. Before you do that though, it's necessary to create the table in the database that corresponds to this model. Cot CLI has got you covered and can automatically create migrations for you – just run the following command: - -```bash -cot migration make -``` - -This will create a new file in your `migrations` directory in the crate's src directory. We will come back to the contents of this file later in this guide, but for now, let's focus on how to use the model to interact with the database. - -## Model Fields Options -Cot provides specific field-level attributes that provide special meaning to fields in a model. The most common ones are listed below: - -### `primary_key` -This is used to mark a field as the primary key of the table. This is a required field for every model. - -```rust -#[model] -pub struct Post { - #[model(primary_key)] - id: Auto, - title: String, - content: String, -} -``` - -### `unique` -This is used to mark a field as unique, which means that each value in this field must be unique across all rows in the table. For more information see the [model field reference](https://docs.rs/cot_macros/0.5.0/cot_macros/attr.model.html). - -```rust -#[model] -pub struct User { - #[model(primary_key)] - id: Auto, - #[model(unique)] - username: String, -} -``` - -## Field Types - -Cot - -## Relationships -Relational databases are all about relationships between tables, and Cot provides a convenient way to define database relationships between models. - - -### Foreign keys - -To define a foreign key relationship between two models, you can use the [`ForeignKey`](https://docs.rs/cot/latest/cot/db/enum.ForeignKey.html) type. Here's an example of how you can define a foreign key relationship between a `Link` model and some other `User` model: - -```rust -use cot::db::ForeignKey; - -#[model] -pub struct Link { - #[model(primary_key)] - id: Auto, - #[model(unique)] - slug: LimitedString<32>, - url: String, - user: ForeignKey, -} - -#[model] -pub struct User { - #[model(primary_key)] - id: Auto, - name: String, -} -``` - -When you define a foreign key relationship, Cot will automatically create a foreign key constraint in the database. This constraint will ensure that the value in the `user_id` field of the `Link` model corresponds to a valid primary key in the `User` model. - -When you retrieve a model that has a foreign key relationship, Cot will not automatically fetch the related model and populate the foreign key field with the corresponding value. Instead, you need to explicitly fetch the related model using the `get` method of the `ForeignKey` object. Here's an example of how you can fetch the related user for a link: - -```rust -let mut link = query!(Link, $slug == LimitedString::new("cot").unwrap()) - .get(db) - .await? - .expect("Link not found"); - -let user = link.user.get(db).await?; -``` - - -## Common operations - -### Saving models - -In order to write a model instance to the database, you can use the [`save`](trait@cot::db::Model#method.save) method. Note that you need to have an instance of the [`Database`](struct@cot::db::Database) structure to do this – typically you can get it from the request object in your view. Here's an example of how you can save a new link to the database inside a view: - -```rust -use cot::db::{Auto, LimitedString, Database}; - -async fn create_link(db: Database) -> cot::Result { - let mut link = Link { - id: Auto::default(), - slug: LimitedString::new("slug").unwrap(), - url: "https://example.com".to_string(), - }; - link.save(db).await?; - - // ... -} -``` - -### Updating models - -Updating a model is similar to saving a new one, but you need to have an existing instance of the model that you want to update, or another instance with the same primary key. Here's an example of how you can update an existing link in the database: - -```rust -link.url = "https://example.org".to_string(); -link.save(db).await?; -``` - -Note that the [`save`](trait@cot::db::Model#method.save) is a convenient method that can be used for both creating new rows and updating existing ones. If the primary key of the model is set to [`Auto`](enum@cot::db::Auto), the method will always create a new row in the database. If the primary key is set to a specific value, the method will update the row with that primary key, or create a new one if it doesn't exist. - -If you specifically want to update a row in the database for given primary key, you can use the [`update`](trait@cot::db::Model#method.update) method: - -```rust -link.url = "https://example.org".to_string(); -link.update(db).await?; -``` - -Similarly, if you want to insert a new row in the database and cause an error if a row with the same primary key already exists, you can use the [`insert`](trait@cot::db::Model#method.insert) method: - -```rust -let mut link = Link { - id: Auto::default(), - slug: LimitedString::new("slug").unwrap(), - url: "https://example.com".to_string(), -}; -link.insert(db).await?; -``` - -### Retrieving models - -The basis for retrieving models from the database is the [`Query`](struct@cot::db::query::Query) structure. It contains information about which model you want to retrieve and allows you to filter, sort, and limit the results. - -The easiest way to work with the [`Query`](struct@cot::db::query::Query) structure is the [`query!`](macro@cot::db::query) macro, which allows you to write complicated queries in readable way using Rusty syntax. For example, to retrieve the link which has slug "cot" from the database, you can write: - -```rust -use cot::db::query; - -let link = query!(Link, $slug == LimitedString::new("cot").unwrap()) - .get(db) - .await?; -``` - -As you can see, the [`Query`](struct@cot::db::query::Query) macro takes the model type as the first argument, followed by the filter expression. The filter expression supports many of the common comparison operators, such as `==`, `!=`, `>`, `<`, `>=`, and `<=`. You can also use logical operators like `&&` and `||` to combine multiple conditions. The `$` sign is used to access the fields of the model in the filter expression—this is needed so that the macro can differentiate between fields of the model and other variables. What's nice about the filter expression is that it's type-checked at compile time, so not only you won't be able to filter using a non-existent field, but also you won't be able to compare fields of different types. - -### Deleting models - -To delete a model from the database, you can use the [`delete`](struct@cot::db::query::Query#method.delete) method of the [`Query`](struct@cot::db::query::Query) object returned by the [`query!`](macro@cot::db::query) macro. Here's an example of how you can delete a link from the database: - -```rust -query!(Link, $slug == LimitedString::new("cot").unwrap()).delete(db).await?; -``` - -### Bulk operations - -If you need to insert multiple rows at once, you can use the [`bulk_insert`](trait@cot::db::Model#method.bulk_insert) method. This is much more efficient than calling [`save`](trait@cot::db::Model#method.save) or [`insert`](trait@cot::db::Model#method.insert) for each row individually, as it performs the operation in a single database query. - -```rust -let mut links = vec![ - Link { - id: Auto::default(), - slug: LimitedString::new("cot").unwrap(), - url: "https://cot.rs".to_string(), - user: ForeignKey::new(1), - }, - Link { - id: Auto::default(), - slug: LimitedString::new("rust").unwrap(), - url: "https://rust-lang.org".to_string(), - user: ForeignKey::new(1), - }, -]; - -Link::bulk_insert(db, &mut links).await?; -``` - -Note that [`bulk_insert`](trait@cot::db::Model#method.bulk_insert) takes a mutable slice of models, because it needs to update the primary keys of the inserted models with the values generated by the database. - -Similarly, there is also [`bulk_insert_or_update`](trait@cot::db::Model#method.bulk_insert_or_update) method, which works like [`bulk_insert`](trait@cot::db::Model#method.bulk_insert), but updates the existing rows if they conflict with the new ones. - -```rust -Link::bulk_insert_or_update(db, &mut links).await?; -``` - -## Database Configuration - -Configure your database connection in the configuration files inside your `config` directory: - -```toml -[database] -# SQLite -url = "sqlite://db.sqlite3?mode=rwc" - -# Or PostgreSQL -url = "postgresql://user:password@localhost/dbname" - -# Or MySQL -url = "mysql://user:password@localhost/dbname" -``` - -Cot tries to be as consistent as possible when it comes to the database engine you are using. This means that you can use SQLite for development and testing, and then switch to PostgreSQL or MySQL for production without changing your code. The only thing you need to do is to change the [`url`](struct@cot::config::DatabaseConfig#structfield.url) value in the configuration file! - -## Summary - -In this chapter you learned how to define your own models in Cot, how to interact with the database using these models, and how to define foreign key relationships between models. In the next chapter, we'll try to register these models in the admin panel so that you can manage them through an easy-to-use web interface. From fd3992d31d50c6a5faa8543b7f732786523011da Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 9 Apr 2026 17:38:10 +0000 Subject: [PATCH 9/9] some small improvements --- docs/databases/overview.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/databases/overview.md b/docs/databases/overview.md index f348d783..5500ef48 100644 --- a/docs/databases/overview.md +++ b/docs/databases/overview.md @@ -103,14 +103,12 @@ impl FromDbValue for NewType { } } - impl ToDbFieldValue for NewType { fn to_db_field_value(&self) -> DbFieldValue { self.0.clone().into() } } - #[model] #[derive(Debug, Clone)] pub struct Post { @@ -124,7 +122,6 @@ pub struct Post { ## Relationships Relational databases are all about relationships between tables, and Cot provides a convenient way to define database relationships between models. - ### Foreign keys To define a foreign key relationship between two models, you can use the [`ForeignKey`](https://docs.rs/cot/latest/cot/db/enum.ForeignKey.html) type. Here's an example of how you can define a foreign key relationship between a `Link` model and some other `User` model: @@ -163,7 +160,6 @@ let mut link = query!(Link, $slug == LimitedString::new("cot").unwrap()) let user = link.user.get(db).await?; ``` - ## Database Configuration Configure your database connection in the configuration files inside your `config` directory: @@ -182,6 +178,12 @@ url = "mysql://user:password@localhost/dbname" Cot tries to be as consistent as possible when it comes to the database engine you are using. This means that you can use SQLite for development and testing, and then switch to PostgreSQL or MySQL for production without changing your code. The only thing you need to do is to change the [`url`](struct@cot::config::DatabaseConfig#structfield.url) value in the configuration file! +Currently, Cot supports the following database engines: +* SQLite +* PostgreSQL +* MySQL + + As an alternative to setting the database configuration in the `TOML` file, you can also set it programmatically in the [`config`](trait@cot::project::Project#method.config) method of your project: