use buffa::Message as _;
use sqlc_gen_sqlx::plugin::{
Catalog, Column, GenerateRequest, Identifier, Parameter, Query, Schema, Table,
};
use sqlc_gen_sqlx::run_with_bytes;
fn make_author_column(name: &str, pg_type: &str, not_null: bool) -> Column {
Column {
name: name.to_string(),
r#type: Some(Identifier {
name: pg_type.to_string(),
..Default::default()
})
.into(),
not_null,
..Default::default()
}
}
fn authors_table() -> Table {
Table {
rel: Some(Identifier {
name: "authors".to_string(),
..Default::default()
})
.into(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
..Default::default()
}
}
fn make_request(queries: Vec<Query>) -> Vec<u8> {
make_request_with_options(queries, b"{}")
}
fn make_request_with_options(queries: Vec<Query>, options: &[u8]) -> Vec<u8> {
let req = GenerateRequest {
catalog: Some(Catalog {
default_schema: "public".to_string(),
schemas: vec![Schema {
name: "public".to_string(),
tables: vec![authors_table()],
..Default::default()
}],
..Default::default()
})
.into(),
queries,
plugin_options: options.to_vec(),
sqlc_version: "1.0.0-test".to_string(),
..Default::default()
};
req.encode_to_vec()
}
fn assert_codegen_snapshot(snapshot_name: &str, code: &str) {
let mut settings = insta::Settings::clone_current();
settings.add_filter(
r"// Code generated by sqlc-gen-sqlx v[0-9A-Za-z][0-9A-Za-z.+-]*\. DO NOT EDIT\.",
"// Code generated by sqlc-gen-sqlx v[VERSION]. DO NOT EDIT.",
);
settings.add_filter(
r"// sqlc version: [0-9A-Za-z][0-9A-Za-z.+-]*",
"// sqlc version: [VERSION]",
);
settings.bind(|| {
insta::assert_snapshot!(snapshot_name, code);
});
}
#[test]
fn snapshot_one() {
let query = Query {
name: "GetAuthor".to_string(),
cmd: ":one".to_string(),
text: "SELECT id, name, bio FROM authors WHERE id = $1".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
params: vec![Parameter {
number: 1,
column: Some(make_author_column("id", "int8", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out_bytes = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out_bytes).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("one", &code);
assert!(
code.contains("#![allow(\n dead_code,\n reason = \"generated queries may expose items a caller does not use\"\n)]"),
"expected dead_code allow in:\n{code}"
);
assert!(
code.contains("pub async fn get_author<E: AsExecutor>"),
"expected free function get_author<E: AsExecutor> in:\n{code}"
);
assert!(
code.contains("mut db: E"),
"expected mut db: E parameter in:\n{code}"
);
assert!(
!code.contains("Queries<E>"),
"Queries<E> wrapper should be gone in:\n{code}"
);
}
#[test]
fn snapshot_many() {
let query = Query {
name: "ListAuthors".to_string(),
cmd: ":many".to_string(),
text: "SELECT id, name, bio FROM authors".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
..Default::default()
};
let bytes = make_request(vec![query]);
let out_bytes = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out_bytes).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("many", &code);
assert!(
code.contains("pub async fn list_authors<E: AsExecutor>"),
"expected free function list_authors<E: AsExecutor> in:\n{code}"
);
assert!(
!code.contains("Queries<E>"),
"Queries<E> wrapper should be gone in:\n{code}"
);
}
#[test]
fn snapshot_exec() {
let query = Query {
name: "DeleteAuthor".to_string(),
cmd: ":exec".to_string(),
text: "DELETE FROM authors WHERE id = $1".to_string(),
params: vec![Parameter {
number: 1,
column: Some(make_author_column("id", "int8", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out_bytes = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out_bytes).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("exec", &code);
assert!(
code.contains("pub async fn delete_author<E: AsExecutor>"),
"expected free function delete_author<E: AsExecutor> in:\n{code}"
);
assert!(
!code.contains("Queries<E>"),
"Queries<E> wrapper should be gone in:\n{code}"
);
}
#[test]
fn snapshot_execrows() {
let query = Query {
name: "DeleteAuthorRows".to_string(),
cmd: ":execrows".to_string(),
text: "DELETE FROM authors WHERE id = $1".to_string(),
params: vec![Parameter {
number: 1,
column: Some(make_author_column("id", "int8", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out_bytes = run_with_bytes(&bytes).unwrap();
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out_bytes).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("execrows", &code);
assert!(
code.contains("pub async fn delete_author_rows<E: AsExecutor>"),
"expected free function delete_author_rows<E: AsExecutor> in:\n{code}"
);
assert!(
!code.contains("Queries<E>"),
"Queries<E> wrapper should be gone in:\n{code}"
);
}
#[test]
fn snapshot_execresult() {
let query = Query {
name: "DeleteAuthorResult".to_string(),
cmd: ":execresult".to_string(),
text: "DELETE FROM authors WHERE id = $1".to_string(),
params: vec![Parameter {
number: 1,
column: Some(make_author_column("id", "int8", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out_bytes = run_with_bytes(&bytes).unwrap();
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out_bytes).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("execresult", &code);
assert!(
code.contains("pub async fn delete_author_result<E: AsExecutor>"),
"expected free function delete_author_result<E: AsExecutor> in:\n{code}"
);
assert!(
!code.contains("Queries<E>"),
"Queries<E> wrapper should be gone in:\n{code}"
);
}
#[test]
fn snapshot_enum_types() {
use sqlc_gen_sqlx::plugin::{self, Catalog, Column, Identifier, Parameter, Query, Schema};
fn make_status_column(not_null: bool) -> Column {
Column {
name: "status".to_string(),
r#type: Some(Identifier {
name: "status".to_string(),
..Default::default()
})
.into(),
not_null,
..Default::default()
}
}
let schema = Schema {
name: "public".to_string(),
enums: vec![plugin::Enum {
name: "status".to_string(),
vals: vec![
"active".to_string(),
"inactive".to_string(),
"banned".to_string(),
],
..Default::default()
}],
..Default::default()
};
let query = Query {
name: "GetUserStatus".to_string(),
cmd: ":one".to_string(),
text: "SELECT status FROM users WHERE id = $1".to_string(),
columns: vec![make_status_column(true)],
params: vec![Parameter {
number: 1,
column: Some(Column {
name: "id".to_string(),
r#type: Some(Identifier {
name: "int8".to_string(),
..Default::default()
})
.into(),
not_null: true,
..Default::default()
})
.into(),
..Default::default()
}],
..Default::default()
};
let req = sqlc_gen_sqlx::plugin::GenerateRequest {
catalog: Some(Catalog {
default_schema: "public".to_string(),
schemas: vec![schema],
..Default::default()
})
.into(),
queries: vec![query],
plugin_options: b"{}".to_vec(),
sqlc_version: "1.0.0-test".to_string(),
..Default::default()
};
let bytes = req.encode_to_vec();
let out_bytes = sqlc_gen_sqlx::run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out_bytes).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("enum_types", &code);
assert!(
code.contains("pub enum Status"),
"expected Status enum in:\n{code}"
);
assert!(
code.contains("Active"),
"expected Active variant in:\n{code}"
);
assert!(
!code.contains("Queries<E>"),
"Queries<E> wrapper should be gone in:\n{code}"
);
}
#[test]
fn snapshot_composite_types() {
use sqlc_gen_sqlx::plugin::{Catalog, Column, CompositeType, Identifier, Schema, Table};
let address_table = Table {
rel: Some(Identifier {
name: "address".to_string(),
..Default::default()
})
.into(),
columns: vec![
Column {
name: "street".to_string(),
r#type: Some(Identifier {
name: "text".to_string(),
..Default::default()
})
.into(),
not_null: false,
..Default::default()
},
Column {
name: "city".to_string(),
r#type: Some(Identifier {
name: "text".to_string(),
..Default::default()
})
.into(),
not_null: false,
..Default::default()
},
Column {
name: "zip".to_string(),
r#type: Some(Identifier {
name: "int4".to_string(),
..Default::default()
})
.into(),
not_null: false,
..Default::default()
},
],
..Default::default()
};
let schema = Schema {
name: "public".to_string(),
composite_types: vec![CompositeType {
name: "address".to_string(),
..Default::default()
}],
tables: vec![address_table],
..Default::default()
};
let req = sqlc_gen_sqlx::plugin::GenerateRequest {
catalog: Some(Catalog {
default_schema: "public".to_string(),
schemas: vec![schema],
..Default::default()
})
.into(),
queries: vec![],
plugin_options: b"{}".to_vec(),
sqlc_version: "1.0.0-test".to_string(),
..Default::default()
};
let bytes = req.encode_to_vec();
let out_bytes = sqlc_gen_sqlx::run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out_bytes).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("composite_types", &code);
assert!(
code.contains("pub struct Address"),
"expected Address struct in:\n{code}"
);
assert!(
code.contains("Option") && code.contains("String"),
"expected Option<String> fields in:\n{code}"
);
assert!(
code.contains("Option") && code.contains("i32"),
"expected Option<i32> for zip in:\n{code}"
);
}
#[test]
fn domain_types_use_base_type() {
use sqlc_gen_sqlx::plugin::{Catalog, Column, GenerateRequest, Identifier, Query, Schema};
let query = Query {
name: "GetEmail".to_string(),
cmd: ":one".to_string(),
text: "SELECT email FROM users WHERE id = $1".to_string(),
columns: vec![Column {
name: "email".to_string(),
r#type: Some(Identifier {
name: "text".to_string(),
..Default::default()
})
.into(),
not_null: true,
..Default::default()
}],
params: vec![],
..Default::default()
};
let req = GenerateRequest {
catalog: Some(Catalog {
default_schema: "public".to_string(),
schemas: vec![Schema {
name: "public".to_string(),
..Default::default()
}],
..Default::default()
})
.into(),
queries: vec![query],
plugin_options: b"{}".to_vec(),
sqlc_version: "1.0.0-test".to_string(),
..Default::default()
};
let bytes = req.encode_to_vec();
let out_bytes = sqlc_gen_sqlx::run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out_bytes).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert!(
code.contains("email: String"),
"expected email: String (domain pre-resolved to text) in:\n{code}"
);
}
#[test]
fn snapshot_range_types() {
fn make_col(name: &str, pg_type: &str) -> Column {
Column {
name: name.to_string(),
r#type: Some(Identifier {
name: pg_type.to_string(),
..Default::default()
})
.into(),
not_null: true,
..Default::default()
}
}
let query = Query {
name: "GetEventWindow".to_string(),
cmd: ":one".to_string(),
text: "SELECT id, window, flags FROM events WHERE id = $1".to_string(),
columns: vec![
make_col("id", "int8"),
make_col("window", "tstzrange"),
make_col("flags", "bit"),
],
params: vec![Parameter {
number: 1,
column: Some(Column {
name: "id".to_string(),
r#type: Some(Identifier {
name: "int8".to_string(),
..Default::default()
})
.into(),
not_null: true,
..Default::default()
})
.into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("range_types", &code);
assert!(code.contains("PgRange"), "expected PgRange in:\n{code}");
assert!(
code.contains("sqlx::types::BitVec"),
"expected SQLx BitVec in:\n{code}"
);
}
#[test]
fn snapshot_slice_param() {
let slice_col = Column {
name: "id".to_string(),
r#type: Some(Identifier {
name: "int8".to_string(),
..Default::default()
})
.into(),
not_null: true,
is_sqlc_slice: true,
..Default::default()
};
let query = Query {
name: "ListAuthorsByIds".to_string(),
cmd: ":many".to_string(),
text: "SELECT id, name, bio FROM authors WHERE id = ANY($1)".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
params: vec![Parameter {
number: 1,
column: Some(slice_col).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("slice_param", &code);
assert!(
code.contains("Vec<i64>"),
"expected Vec<i64> param in:\n{code}"
);
}
#[test]
fn snapshot_embed() {
fn make_embed_col(name: &str, pg_type: &str, not_null: bool, embed: &str) -> Column {
Column {
name: name.to_string(),
r#type: Some(Identifier {
name: pg_type.to_string(),
..Default::default()
})
.into(),
not_null,
embed_table: Some(Identifier {
name: embed.to_string(),
..Default::default()
})
.into(),
..Default::default()
}
}
let query = Query {
name: "GetAuthorEmbed".to_string(),
cmd: ":one".to_string(),
text: "SELECT a.id, a.name, a.bio FROM authors a WHERE a.id = $1".to_string(),
columns: vec![
make_embed_col("id", "int8", true, "authors"),
make_embed_col("name", "text", true, "authors"),
make_embed_col("bio", "text", false, "authors"),
],
params: vec![Parameter {
number: 1,
column: Some(make_author_column("id", "int8", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("embed", &code);
assert!(
code.contains("AuthorsEmbed"),
"expected AuthorsEmbed sub-struct in:\n{code}"
);
assert!(
code.contains("#[sqlx(flatten)]"),
"expected #[sqlx(flatten)] in:\n{code}"
);
}
#[test]
fn snapshot_execlastid() {
let query = Query {
name: "CreateAuthor".to_string(),
cmd: ":execlastid".to_string(),
text: "INSERT INTO authors (name) VALUES ($1) RETURNING id".to_string(),
columns: vec![Column {
name: "id".to_string(),
r#type: Some(Identifier {
name: "int8".to_string(),
..Default::default()
})
.into(),
not_null: true,
..Default::default()
}],
params: vec![Parameter {
number: 1,
column: Some(make_author_column("name", "text", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("execlastid", &code);
assert!(
code.contains("-> Result<i64"),
"expected Result<i64> in:\n{code}"
);
}
#[test]
fn snapshot_batchexec() {
let query = Query {
name: "BatchDeleteAuthor".to_string(),
cmd: ":batchexec".to_string(),
text: "DELETE FROM authors WHERE id = $1".to_string(),
params: vec![Parameter {
number: 1,
column: Some(make_author_column("id", "int8", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("batchexec", &code);
assert!(
code.contains("impl futures_core::stream::Stream<Item = Result<(), sqlx::Error>>"),
"expected stream return type in:\n{code}"
);
assert!(
code.contains("futures_util::stream::try_unfold"),
"expected stream implementation in:\n{code}"
);
assert!(
code.contains("pub fn batch_delete_author<'a, E, I>"),
"expected free function batch_delete_author<'a, E, I> in:\n{code}"
);
assert!(
code.contains("db: E,"),
"expected db: E parameter in:\n{code}"
);
}
#[test]
fn snapshot_batchone() {
let query = Query {
name: "BatchGetAuthor".to_string(),
cmd: ":batchone".to_string(),
text: "SELECT id, name, bio FROM authors WHERE id = $1".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
params: vec![Parameter {
number: 1,
column: Some(make_author_column("id", "int8", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("batchone", &code);
assert!(
code.contains("Item = Result<BatchGetAuthorRow, sqlx::Error>"),
"expected stream return type in:\n{code}"
);
assert!(
code.contains("futures_util::stream::try_unfold"),
"expected stream implementation in:\n{code}"
);
assert!(
code.contains("pub fn batch_get_author<'a, E, I>"),
"expected free function batch_get_author<'a, E, I> in:\n{code}"
);
}
#[test]
fn snapshot_batchmany() {
let query = Query {
name: "BatchListAuthors".to_string(),
cmd: ":batchmany".to_string(),
text: "SELECT id, name, bio FROM authors WHERE bio LIKE $1".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
params: vec![Parameter {
number: 1,
column: Some(make_author_column("bio", "text", false)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("batchmany", &code);
assert!(
code.contains("Item = Result<Vec<BatchListAuthorsRow>, sqlx::Error>"),
"expected stream return type in:\n{code}"
);
assert!(
code.contains("futures_util::stream::try_unfold"),
"expected stream implementation in:\n{code}"
);
assert!(
code.contains("pub fn batch_list_authors<'a, E, I>"),
"expected free function batch_list_authors<'a, E, I> in:\n{code}"
);
}
#[test]
fn snapshot_named_params() {
let query = Query {
name: "UpdateAuthorNamedParams".to_string(),
cmd: ":exec".to_string(),
text: "UPDATE authors SET name = $1 WHERE id = $2".to_string(),
params: vec![
Parameter {
number: 1,
column: Some(Column {
name: "Column1".to_string(),
original_name: "set_name".to_string(),
is_named_param: true,
r#type: Some(Identifier {
name: "text".to_string(),
..Default::default()
})
.into(),
not_null: true,
..Default::default()
})
.into(),
..Default::default()
},
Parameter {
number: 2,
column: Some(Column {
name: "Column2".to_string(),
original_name: "author_id".to_string(),
is_named_param: true,
r#type: Some(Identifier {
name: "int8".to_string(),
..Default::default()
})
.into(),
not_null: true,
..Default::default()
})
.into(),
..Default::default()
},
],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("named_params", &code);
assert!(
code.contains("pub set_name: String"),
"expected named params field set_name in:\n{code}"
);
assert!(
code.contains("pub author_id: i64"),
"expected named params field author_id in:\n{code}"
);
}
#[test]
fn snapshot_nullable_named_param() {
let query = Query {
name: "ListAuthorsByOptionalBio".to_string(),
cmd: ":many".to_string(),
text: "SELECT id, name, bio FROM authors WHERE bio = $1".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
params: vec![Parameter {
number: 1,
column: Some(Column {
name: "Column1".to_string(),
original_name: "bio".to_string(),
is_named_param: true,
r#type: Some(Identifier {
name: "text".to_string(),
..Default::default()
})
.into(),
not_null: false,
..Default::default()
})
.into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("nullable_named_param", &code);
assert!(
code.contains("bio: Option<String>"),
"expected nullable named param bio in:\n{code}"
);
}
#[test]
fn snapshot_copyfrom() {
let query = Query {
name: "CopyAuthors".to_string(),
cmd: ":copyfrom".to_string(),
text: "INSERT INTO authors (name, bio) VALUES ($1, $2)".to_string(),
insert_into_table: Some(Identifier {
name: "authors".to_string(),
..Default::default()
})
.into(),
params: vec![
Parameter {
number: 1,
column: Some(make_author_column("name", "text", true)).into(),
..Default::default()
},
Parameter {
number: 2,
column: Some(make_author_column("bio", "text", false)).into(),
..Default::default()
},
],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("copyfrom", &code);
assert!(
code.contains("sqlx::QueryBuilder::<"),
"expected QueryBuilder in:\n{code}"
);
assert!(
code.contains("-> Result<u64, sqlx::Error>"),
"expected rows affected return type in:\n{code}"
);
assert!(
code.contains("I: IntoIterator<Item = CopyAuthorsParams>"),
"expected iterator-based items argument in:\n{code}"
);
assert!(
code.contains("COPY_AUTHORS_BATCH_SIZE"),
"expected generated copyfrom batch size in:\n{code}"
);
assert!(
code.contains("pub async fn copy_authors<E: AsExecutor, I>"),
"expected free function copy_authors<E: AsExecutor, I> in:\n{code}"
);
}
#[test]
fn snapshot_dynamic_slice_param() {
let slice_col = Column {
name: "Column1".to_string(),
original_name: "ids".to_string(),
is_named_param: true,
r#type: Some(Identifier {
name: "int8".to_string(),
..Default::default()
})
.into(),
not_null: true,
is_sqlc_slice: true,
..Default::default()
};
let query = Query {
name: "ListAuthorsByDynamicIds".to_string(),
cmd: ":many".to_string(),
text: "SELECT id, name, bio FROM authors WHERE id IN ($1)".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
params: vec![Parameter {
number: 1,
column: Some(slice_col).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("dynamic_slice_param", &code);
assert!(
code.contains("let mut sql = LIST_AUTHORS_BY_DYNAMIC_IDS.to_string();"),
"expected runtime SQL expansion in:\n{code}"
);
assert!(
code.contains("for value in ids"),
"expected per-element slice binding in:\n{code}"
);
}
#[test]
fn snapshot_multidimensional_array_types() {
let matrix_col = Column {
name: "matrix".to_string(),
r#type: Some(Identifier {
name: "int8".to_string(),
..Default::default()
})
.into(),
not_null: true,
is_array: true,
array_dims: 2,
..Default::default()
};
let query = Query {
name: "GetMatrix".to_string(),
cmd: ":one".to_string(),
text: "SELECT matrix FROM tensors WHERE id = $1".to_string(),
columns: vec![matrix_col.clone()],
params: vec![Parameter {
number: 1,
column: Some(matrix_col).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("multidimensional_array_types", &code);
assert!(
code.contains("pub matrix: Vec<Vec<i64>>"),
"expected nested Vec row field in:\n{code}"
);
assert!(
code.contains("matrix: Vec<Vec<i64>>"),
"expected nested Vec param in:\n{code}"
);
}
#[test]
fn snapshot_batch_dynamic_slice_param() {
let slice_col = Column {
name: "Column1".to_string(),
original_name: "ids".to_string(),
is_named_param: true,
r#type: Some(Identifier {
name: "int8".to_string(),
..Default::default()
})
.into(),
not_null: true,
is_sqlc_slice: true,
..Default::default()
};
let query = Query {
name: "BatchListAuthorsByDynamicIds".to_string(),
cmd: ":batchmany".to_string(),
text: "SELECT id, name, bio FROM authors WHERE id IN ($1)".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
params: vec![Parameter {
number: 1,
column: Some(slice_col).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request(vec![query]);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("batch_dynamic_slice_param", &code);
assert!(
code.contains("let mut sql = BATCH_LIST_AUTHORS_BY_DYNAMIC_IDS.to_string();"),
"expected runtime SQL expansion inside batch stream in:\n{code}"
);
assert!(
code.contains("for value in ids"),
"expected per-element binding inside batch stream in:\n{code}"
);
}
const BORROWED_TEXT_OPTIONS: &[u8] =
br#"{"overrides":[{"db_type":"text","borrowed_rs_type":"&str"}]}"#;
#[test]
fn snapshot_borrowed_scalar() {
let query = Query {
name: "DeleteByName".to_string(),
cmd: ":exec".to_string(),
text: "DELETE FROM authors WHERE name = $1".to_string(),
params: vec![Parameter {
number: 1,
column: Some(make_author_column("name", "text", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request_with_options(vec![query], BORROWED_TEXT_OPTIONS);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("borrowed_scalar", &code);
assert!(
code.contains("name: &str"),
"expected name: &str (elided) in:\n{code}"
);
assert!(
!code.contains("DeleteByNameParams"),
"single param must not emit a params struct in:\n{code}"
);
}
#[test]
fn snapshot_borrowed_params_struct() {
let query = Query {
name: "UpdateAuthor".to_string(),
cmd: ":exec".to_string(),
text: "UPDATE authors SET name = $1, bio = $2".to_string(),
params: vec![
Parameter {
number: 1,
column: Some(make_author_column("name", "text", true)).into(),
..Default::default()
},
Parameter {
number: 2,
column: Some(make_author_column("bio", "text", false)).into(),
..Default::default()
},
],
..Default::default()
};
let bytes = make_request_with_options(vec![query], BORROWED_TEXT_OPTIONS);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("borrowed_params_struct", &code);
assert!(
code.contains("pub struct UpdateAuthorParams<'a>"),
"expected struct with <'a> in:\n{code}"
);
assert!(
code.contains("pub name: &'a str"),
"expected &'a str field in:\n{code}"
);
assert!(
code.contains("pub bio: Option<&'a str>"),
"expected Option<&'a str> field in:\n{code}"
);
assert!(
code.contains("arg: UpdateAuthorParams<'_>"),
"expected arg: UpdateAuthorParams<'_> (elided) in:\n{code}"
);
}
#[test]
fn snapshot_borrowed_array_param() {
let array_col = Column {
name: "tags".to_string(),
r#type: Some(Identifier {
name: "text".to_string(),
..Default::default()
})
.into(),
not_null: true,
is_array: true,
..Default::default()
};
let query = Query {
name: "ListByTags".to_string(),
cmd: ":exec".to_string(),
text: "DELETE FROM authors WHERE name = ANY($1)".to_string(),
params: vec![Parameter {
number: 1,
column: Some(array_col).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request_with_options(vec![query], BORROWED_TEXT_OPTIONS);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("borrowed_array_param", &code);
assert!(
code.contains("tags: &[String]"),
"expected &[String] for borrowed array of text in:\n{code}"
);
}
#[test]
fn snapshot_borrowed_slice_param() {
let slice_col = Column {
name: "names".to_string(),
r#type: Some(Identifier {
name: "text".to_string(),
..Default::default()
})
.into(),
not_null: true,
is_sqlc_slice: true,
..Default::default()
};
let query = Query {
name: "DeleteByNames".to_string(),
cmd: ":exec".to_string(),
text: "DELETE FROM authors WHERE name = ANY($1)".to_string(),
params: vec![Parameter {
number: 1,
column: Some(slice_col).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request_with_options(vec![query], BORROWED_TEXT_OPTIONS);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("borrowed_slice_param", &code);
assert!(
code.contains("names: &[String]"),
"expected &[String] for sqlc.slice(text) in:\n{code}"
);
}
#[test]
fn snapshot_borrowed_one_row_unaffected() {
let query = Query {
name: "GetAuthorByName".to_string(),
cmd: ":one".to_string(),
text: "SELECT id, name, bio FROM authors WHERE name = $1".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
params: vec![Parameter {
number: 1,
column: Some(make_author_column("name", "text", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request_with_options(vec![query], BORROWED_TEXT_OPTIONS);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("borrowed_one_row_unaffected", &code);
assert!(code.contains("name: &str"), "param uses &str in:\n{code}");
assert!(
code.contains("pub name: String"),
"row field stays owned String in:\n{code}"
);
assert!(
code.contains("pub bio: Option<String>"),
"nullable row field stays Option<String> in:\n{code}"
);
}
#[test]
fn snapshot_borrowed_batchexec() {
let query = Query {
name: "BatchDeleteByName".to_string(),
cmd: ":batchexec".to_string(),
text: "DELETE FROM authors WHERE name = $1".to_string(),
params: vec![Parameter {
number: 1,
column: Some(make_author_column("name", "text", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request_with_options(vec![query], BORROWED_TEXT_OPTIONS);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("borrowed_batchexec", &code);
assert!(
code.contains("IntoIterator<Item = &'a str>"),
"expected Item = &'a str using the stream's lifetime in:\n{code}"
);
}
#[test]
fn snapshot_borrowed_batchexec_struct() {
let query = Query {
name: "BatchUpdateAuthor".to_string(),
cmd: ":batchexec".to_string(),
text: "UPDATE authors SET name = $1, bio = $2 WHERE id = $3".to_string(),
params: vec![
Parameter {
number: 1,
column: Some(make_author_column("name", "text", true)).into(),
..Default::default()
},
Parameter {
number: 2,
column: Some(make_author_column("bio", "text", false)).into(),
..Default::default()
},
Parameter {
number: 3,
column: Some(make_author_column("id", "int8", true)).into(),
..Default::default()
},
],
..Default::default()
};
let bytes = make_request_with_options(vec![query], BORROWED_TEXT_OPTIONS);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("borrowed_batchexec_struct", &code);
assert!(
code.contains("pub struct BatchUpdateAuthorParams<'a>"),
"expected params struct with <'a> in:\n{code}"
);
assert!(
code.contains("IntoIterator<Item = BatchUpdateAuthorParams<'a>>"),
"expected Item = BatchUpdateAuthorParams<'a> in:\n{code}"
);
}
#[test]
fn snapshot_borrowed_copyfrom() {
let query = Query {
name: "CopyAuthors".to_string(),
cmd: ":copyfrom".to_string(),
text: "INSERT INTO authors (name, bio) VALUES ($1, $2)".to_string(),
insert_into_table: Some(Identifier {
name: "authors".to_string(),
..Default::default()
})
.into(),
params: vec![
Parameter {
number: 1,
column: Some(make_author_column("name", "text", true)).into(),
..Default::default()
},
Parameter {
number: 2,
column: Some(make_author_column("bio", "text", false)).into(),
..Default::default()
},
],
..Default::default()
};
let bytes = make_request_with_options(vec![query], BORROWED_TEXT_OPTIONS);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("borrowed_copyfrom", &code);
assert!(
code.contains("pub async fn copy_authors<'a, E: AsExecutor, I>"),
"expected 'a in fn signature in:\n{code}"
);
assert!(
code.contains("I: IntoIterator<Item = CopyAuthorsParams<'a>>"),
"expected Item with <'a> in where clause in:\n{code}"
);
}
#[test]
fn snapshot_borrowed_with_custom_owned() {
let options =
br#"{"overrides":[{"db_type":"text","rs_type":"MyStr","borrowed_rs_type":"&MyStr"}]}"#;
let query = Query {
name: "GetByName".to_string(),
cmd: ":one".to_string(),
text: "SELECT id, name, bio FROM authors WHERE name = $1".to_string(),
columns: vec![
make_author_column("id", "int8", true),
make_author_column("name", "text", true),
make_author_column("bio", "text", false),
],
params: vec![Parameter {
number: 1,
column: Some(make_author_column("name", "text", true)).into(),
..Default::default()
}],
..Default::default()
};
let bytes = make_request_with_options(vec![query], options);
let out = run_with_bytes(&bytes).expect("generate failed");
let resp = sqlc_gen_sqlx::plugin::GenerateResponse::decode_from_slice(&out).unwrap();
let code = String::from_utf8(resp.files[0].contents.clone()).unwrap();
assert_codegen_snapshot("borrowed_with_custom_owned", &code);
assert!(
code.contains("name: &MyStr"),
"param uses borrowed override in:\n{code}"
);
assert!(
code.contains("pub name: MyStr"),
"row uses custom owned rs_type in:\n{code}"
);
}