From da48ceca67ce95c06eafe5268a3a01a2be4b5d5c Mon Sep 17 00:00:00 2001 From: rich-murphey Date: Fri, 26 Feb 2021 21:25:26 -0600 Subject: [PATCH 1/6] #[derive(Serialize)] for Record --- sqlx-macros/src/query/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sqlx-macros/src/query/mod.rs b/sqlx-macros/src/query/mod.rs index 2c1a71544d..a1603e96f1 100644 --- a/sqlx-macros/src/query/mod.rs +++ b/sqlx-macros/src/query/mod.rs @@ -279,7 +279,9 @@ where ); let mut record_tokens = quote! { - #[derive(Debug)] + #[derive(Debug,Serialize)] + // I cannot get this to work: + // #[cfg_attr(feature = "json", derive(Serialize))] struct #record_name { #(#record_fields)* } From fc8538286b04525e6f972b40e3fda7c567c28667 Mon Sep 17 00:00:00 2001 From: rich-murphey Date: Sat, 27 Feb 2021 16:05:00 -0600 Subject: [PATCH 2/6] put cfg(feature = "json") outside quote!{} --- sqlx-macros/src/query/mod.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sqlx-macros/src/query/mod.rs b/sqlx-macros/src/query/mod.rs index a1603e96f1..3a72acb77c 100644 --- a/sqlx-macros/src/query/mod.rs +++ b/sqlx-macros/src/query/mod.rs @@ -278,14 +278,19 @@ where }| quote!(#ident: #type_,), ); - let mut record_tokens = quote! { - #[derive(Debug,Serialize)] - // I cannot get this to work: - // #[cfg_attr(feature = "json", derive(Serialize))] + let mut record_tokens = TokenStream::new(); + + #[cfg(feature = "json")] + record_tokens.extend(quote! { + #[derive(serde::Serialize)] + }); + + record_tokens.extend(quote! { + #[derive(Debug)] struct #record_name { #(#record_fields)* } - }; + }); record_tokens.extend(output::quote_query_as::( &input, From a89207a81fafee0671bd6494c1784005956f5f95 Mon Sep 17 00:00:00 2001 From: rich-murphey Date: Sun, 28 Feb 2021 18:56:18 -0600 Subject: [PATCH 3/6] add "serialize" feature,and it_row_serializes_to_json() test --- Cargo.toml | 1 + sqlx-core/Cargo.toml | 1 + sqlx-macros/Cargo.toml | 1 + sqlx-macros/src/query/mod.rs | 2 +- tests/postgres/postgres.rs | 15 +++++++++++++++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ea93952103..8f12e9eda1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ time = [ "sqlx-core/time", "sqlx-macros/time" ] bit-vec = [ "sqlx-core/bit-vec", "sqlx-macros/bit-vec"] bstr = [ "sqlx-core/bstr" ] git2 = [ "sqlx-core/git2" ] +serialize = [ "sqlx-core/serialize", "sqlx-macros/serialize" ] [dependencies] sqlx-core = { version = "0.5.1", path = "sqlx-core", default-features = false } diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 1a7b795cec..b8310b0567 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -32,6 +32,7 @@ all-types = [ "chrono", "time", "bigdecimal", "decimal", "ipnetwork", "json", "u bigdecimal = [ "bigdecimal_", "num-bigint" ] decimal = [ "rust_decimal", "num-bigint" ] json = [ "serde", "serde_json" ] +serialize = [ "serde", "serde_json" ] # runtimes runtime-actix-native-tls = [ "sqlx-rt/runtime-actix-native-tls", "_tls-native-tls", "_rt-actix" ] diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index 0310a39fe2..85def82236 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -51,6 +51,7 @@ ipnetwork = [ "sqlx-core/ipnetwork" ] uuid = [ "sqlx-core/uuid" ] bit-vec = [ "sqlx-core/bit-vec" ] json = [ "sqlx-core/json", "serde_json" ] +serialize = [ "serde", "serde_json" ] [dependencies] dotenv = { version = "0.15.0", default-features = false } diff --git a/sqlx-macros/src/query/mod.rs b/sqlx-macros/src/query/mod.rs index 3a72acb77c..d5b5a37184 100644 --- a/sqlx-macros/src/query/mod.rs +++ b/sqlx-macros/src/query/mod.rs @@ -280,7 +280,7 @@ where let mut record_tokens = TokenStream::new(); - #[cfg(feature = "json")] + #[cfg(feature = "serialize")] record_tokens.extend(quote! { #[derive(serde::Serialize)] }); diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index dee9062d8e..1aa9f71dbd 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -887,3 +887,18 @@ from (values (null)) vals(val) Ok(()) } + +#[cfg(feature = "serialize")] +#[sqlx_macros::test] +async fn it_row_serializes_to_json() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let json = + serde_json::to_string( + &sqlx::query!(" select (1) as id ") + .fetch_all(&pool) + .await?)?; + assert_eq!(&json, r#"[{"id":1}]"#); + + Ok(()) +} From 21e0c1ba9d1c0ed9d9724d9b69de8985243ac9f9 Mon Sep 17 00:00:00 2001 From: rich-murphey Date: Sun, 28 Feb 2021 18:57:24 -0600 Subject: [PATCH 4/6] fmt --- tests/postgres/postgres.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index 1aa9f71dbd..055a4dd4d8 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -893,11 +893,7 @@ from (values (null)) vals(val) async fn it_row_serializes_to_json() -> anyhow::Result<()> { let mut conn = new::().await?; - let json = - serde_json::to_string( - &sqlx::query!(" select (1) as id ") - .fetch_all(&pool) - .await?)?; + let json = serde_json::to_string(&sqlx::query!(" select (1) as id ").fetch_all(&pool).await?)?; assert_eq!(&json, r#"[{"id":1}]"#); Ok(()) From 815c94fa31bafe50e5e8f82ed469d4b0b88d5386 Mon Sep 17 00:00:00 2001 From: rich-murphey Date: Wed, 3 Mar 2021 01:49:59 -0600 Subject: [PATCH 5/6] add examples/postgres/serialize demo of serialize feature --- Cargo.lock | 15 +++ Cargo.toml | 1 + examples/postgres/serialize/Cargo.toml | 16 +++ examples/postgres/serialize/README.md | 47 ++++++++ .../migrations/20200824190010_json.sql | 5 + examples/postgres/serialize/src/main.rs | 100 ++++++++++++++++++ 6 files changed, 184 insertions(+) create mode 100644 examples/postgres/serialize/Cargo.toml create mode 100644 examples/postgres/serialize/README.md create mode 100644 examples/postgres/serialize/migrations/20200824190010_json.sql create mode 100644 examples/postgres/serialize/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index aaa5a44291..c4dd0d5358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2150,6 +2150,21 @@ dependencies = [ "serde", ] +[[package]] +name = "serialize" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-std", + "dotenv", + "futures", + "paw", + "serde", + "serde_json", + "sqlx", + "structopt", +] + [[package]] name = "sha-1" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index 8f12e9eda1..445120a975 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "examples/postgres/listen", "examples/postgres/todos", "examples/postgres/mockable-todos", + "examples/postgres/serialize", "examples/sqlite/todos", ] diff --git a/examples/postgres/serialize/Cargo.toml b/examples/postgres/serialize/Cargo.toml new file mode 100644 index 0000000000..990b6eb37b --- /dev/null +++ b/examples/postgres/serialize/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "serialize" +version = "0.1.0" +edition = "2018" +workspace = "../../../" + +[dependencies] +anyhow = "1.0" +async-std = { version = "1.6.0", features = [ "attributes" ] } +dotenv = "0.15.0" +futures = "0.3" +paw = "1.0" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +sqlx = { path = "../../../", features = ["runtime-async-std-rustls", "postgres", "json", "serialize"] } +structopt = { version = "0.3", features = ["paw"] } diff --git a/examples/postgres/serialize/README.md b/examples/postgres/serialize/README.md new file mode 100644 index 0000000000..4163709fac --- /dev/null +++ b/examples/postgres/serialize/README.md @@ -0,0 +1,47 @@ +# JSON Example using serialize feature + +When the serialize feature is enabled, the query!() macro returns a +struct that implements serde::Serialize. This means that each 'Row' +value can be converted to json text using serde_json::to_string(&row). +This includes nested 'jsonb', such as the person column in this +example. + +## Setup + +1. Declare the database URL + + ``` + export DATABASE_URL="postgres://postgres:password@localhost/serialize" + ``` + +2. Create the database. + + ``` + $ sqlx db create + ``` + +3. Run sql migrations + + ``` + $ sqlx migrate run + ``` + +## Usage + +Add a person + +``` +echo '{ "name": "John Doe", "age": 30 }' | cargo run -- add +``` + +or with extra keys + +``` +echo '{ "name": "Jane Doe", "age": 25, "array": ["string", true, 0] }' | cargo run -- add +``` + +List all people + +``` +cargo run +``` diff --git a/examples/postgres/serialize/migrations/20200824190010_json.sql b/examples/postgres/serialize/migrations/20200824190010_json.sql new file mode 100644 index 0000000000..02458ff72d --- /dev/null +++ b/examples/postgres/serialize/migrations/20200824190010_json.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS people +( + id BIGSERIAL PRIMARY KEY, + person JSONB NOT NULL +); diff --git a/examples/postgres/serialize/src/main.rs b/examples/postgres/serialize/src/main.rs new file mode 100644 index 0000000000..ef2aa1484d --- /dev/null +++ b/examples/postgres/serialize/src/main.rs @@ -0,0 +1,100 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; +use sqlx::postgres::PgPool; +use sqlx::types::Json; +use std::io::{self, Read}; +use std::num::NonZeroU8; +use structopt::StructOpt; + +#[derive(StructOpt)] +struct Args { + #[structopt(subcommand)] + cmd: Option, +} + +#[derive(StructOpt)] +enum Command { + Add, +} + +#[derive(Deserialize, Serialize)] +struct Person { + name: String, + age: NonZeroU8, + #[serde(flatten)] + extra: Map, +} + +#[async_std::main] +#[paw::main] +async fn main(args: Args) -> anyhow::Result<()> { + let pool = PgPool::connect(&dotenv::var("DATABASE_URL")?).await?; + + match args.cmd { + Some(Command::Add) => { + let mut json = String::new(); + io::stdin().read_to_string(&mut json)?; + + let person: Person = serde_json::from_str(&json)?; + println!( + "Adding new person: {}", + &serde_json::to_string_pretty(&person)? + ); + + let person_id = add_person(&pool, person).await?; + println!("Added new person with ID {}", person_id); + } + None => { + println!("{}", list_people(&pool).await?); + } + } + + Ok(()) +} + +async fn add_person(pool: &PgPool, person: Person) -> anyhow::Result { + let rec = sqlx::query!( + r#" +INSERT INTO people ( person ) +VALUES ( $1 ) +RETURNING id + "#, + Json(person) as _ + ) + .fetch_one(pool) + .await?; + + Ok(rec.id) +} + +async fn list_people(pool: &PgPool) -> anyhow::Result { + let mut buf = String::from("["); + for (i, row) in sqlx::query!( + r#" +SELECT id, person +FROM people +ORDER BY id + "# + ) + .fetch_all(pool) + .await? + .iter() + .enumerate() + { + if i > 0 { + buf.push_str(",\n"); + } + buf.push_str(&serde_json::to_string_pretty(&row)?); + } + buf.push_str("]\n"); + Ok(buf) +} +/* + +echo 'DATABASE_URL=postgresql:///test?host=/var/run/postgresql' >.env +sqlx db create +sqlx migrate run +echo '{ "name": "Jane Doe", "age": 25, "array": ["string", true, 0] }' | cargo run -- add +cargo run + +*/ From f6af8893e4d246ec899eae3ad9ec2921bdb04d39 Mon Sep 17 00:00:00 2001 From: rich-murphey Date: Wed, 3 Mar 2021 01:53:45 -0600 Subject: [PATCH 6/6] remove stale comments --- examples/postgres/serialize/src/main.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/examples/postgres/serialize/src/main.rs b/examples/postgres/serialize/src/main.rs index ef2aa1484d..f063ae66ca 100644 --- a/examples/postgres/serialize/src/main.rs +++ b/examples/postgres/serialize/src/main.rs @@ -89,12 +89,3 @@ ORDER BY id buf.push_str("]\n"); Ok(buf) } -/* - -echo 'DATABASE_URL=postgresql:///test?host=/var/run/postgresql' >.env -sqlx db create -sqlx migrate run -echo '{ "name": "Jane Doe", "age": 25, "array": ["string", true, 0] }' | cargo run -- add -cargo run - -*/