diff --git a/Cargo.lock b/Cargo.lock index 44425d09..5ae3b050 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -879,6 +879,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "files" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-std", + "dotenv", + "sqlx", +] + [[package]] name = "filetime" version = "0.2.17" diff --git a/Cargo.toml b/Cargo.toml index dd26ec28..b36bc198 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "sqlx-cli", "sqlx-bench", "examples/mysql/todos", + "examples/postgres/files", "examples/postgres/json", "examples/postgres/listen", "examples/postgres/todos", diff --git a/examples/postgres/files/Cargo.toml b/examples/postgres/files/Cargo.toml new file mode 100644 index 00000000..9f5493dd --- /dev/null +++ b/examples/postgres/files/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "files" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +async-std = { version = "1.8.0", features = [ "attributes" ] } +sqlx = { path = "../../../", features = ["postgres", "offline", "runtime-async-std-native-tls"] } +dotenv = "0.15.0" diff --git a/examples/postgres/files/README.md b/examples/postgres/files/README.md new file mode 100644 index 00000000..7481ede4 --- /dev/null +++ b/examples/postgres/files/README.md @@ -0,0 +1,36 @@ +# Query files Example + +## Description + +This example demonstrates storing external files to use for querying data. +Encapsulating your SQL queries can be helpful in several ways, assisting with intellisense, +etc. + + +## Setup + +1. Declare the database URL + + ``` + export DATABASE_URL="postgres://postgres:password@localhost/files" + ``` + +2. Create the database. + + ``` + $ sqlx db create + ``` + +3. Run sql migrations + + ``` + $ sqlx migrate run + ``` + +## Usage + +Run the project + +``` +cargo run files +``` \ No newline at end of file diff --git a/examples/postgres/files/migrations/20220712221654_files.sql b/examples/postgres/files/migrations/20220712221654_files.sql new file mode 100644 index 00000000..8cf23e43 --- /dev/null +++ b/examples/postgres/files/migrations/20220712221654_files.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS users +( + id BIGSERIAL PRIMARY KEY, + username TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS posts +( + id BIGSERIAL PRIMARY KEY, + title TEXT NOT NULL, + body TEXT NOT NULL, + user_id BIGINT NOT NULL + REFERENCES users (id) ON DELETE CASCADE +); diff --git a/examples/postgres/files/queries/insert_seed_data.sql b/examples/postgres/files/queries/insert_seed_data.sql new file mode 100644 index 00000000..6ba99797 --- /dev/null +++ b/examples/postgres/files/queries/insert_seed_data.sql @@ -0,0 +1,11 @@ +-- seed some data to work with +WITH inserted_users_cte AS ( + INSERT INTO users (username) + VALUES ('user1'), + ('user2') + RETURNING id as "user_id" +) +INSERT INTO posts (title, body, user_id) +VALUES ('user1 post1 title', 'user1 post1 body', (SELECT user_id FROM inserted_users_cte FETCH FIRST ROW ONLY)), + ('user1 post2 title', 'user1 post2 body', (SELECT user_id FROM inserted_users_cte FETCH FIRST ROW ONLY)), + ('user2 post1 title', 'user2 post2 body', (SELECT user_id FROM inserted_users_cte OFFSET 1 LIMIT 1)); \ No newline at end of file diff --git a/examples/postgres/files/queries/list_all_posts.sql b/examples/postgres/files/queries/list_all_posts.sql new file mode 100644 index 00000000..c432ffdd --- /dev/null +++ b/examples/postgres/files/queries/list_all_posts.sql @@ -0,0 +1,7 @@ +SELECT p.id as "post_id", + p.title, + p.body, + u.id as "author_id", + u.username as "author_username" +FROM users u + JOIN posts p on u.id = p.user_id; \ No newline at end of file diff --git a/examples/postgres/files/src/main.rs b/examples/postgres/files/src/main.rs new file mode 100644 index 00000000..1c6a5d55 --- /dev/null +++ b/examples/postgres/files/src/main.rs @@ -0,0 +1,48 @@ +use sqlx::{query_file, query_file_as, query_file_unchecked, FromRow, PgPool}; +use std::fmt::{Display, Formatter}; + +#[derive(FromRow)] +struct PostWithAuthorQuery { + pub post_id: i64, + pub title: String, + pub body: String, + pub author_id: i64, + pub author_username: String, +} + +impl Display for PostWithAuthorQuery { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + r#" + post_id: {}, + title: {}, + body: {}, + author_id: {}, + author_username: {} + "#, + self.post_id, self.title, self.body, self.author_id, self.author_username + ) + } +} + +#[async_std::main] +async fn main() -> anyhow::Result<()> { + let pool = PgPool::connect(&dotenv::var("DATABASE_URL")?).await?; + + // we can use a tranditional wrapper around the `query!()` macro using files + query_file!("queries/insert_seed_data.sql") + .execute(&pool) + .await?; + + // we can also use `query_file_as!()` similarly to `query_as!()` to map our database models + let posts_with_authors = query_file_as!(PostWithAuthorQuery, "queries/list_all_posts.sql") + .fetch_all(&pool) + .await?; + + for post_with_author in posts_with_authors { + println!("{}", post_with_author); + } + + Ok(()) +}