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..f515e0e7 --- /dev/null +++ b/docs/databases/queries.md @@ -0,0 +1,320 @@ +--- +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. 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: