diff --git a/Cargo.lock b/Cargo.lock index cd120ff..b13e8ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,6 +213,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "check_keyword" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3678a4fc14c47dce76127d66f9b2637ff497af665d5618f1f756f4ad1b4a5b1" +dependencies = [ + "phf", +] + [[package]] name = "cipher" version = "0.4.4" @@ -1290,7 +1299,31 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.77", ] [[package]] @@ -1340,6 +1373,16 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "pluralizer" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e4616e94b67b8b61846ea69d4bf041a62147d569d16f437689229e2677d38c" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "portable-atomic" version = "1.7.0" @@ -2025,6 +2068,7 @@ name = "sqlc-core" version = "0.1.0" dependencies = [ "postgres", + "postgres-types", ] [[package]] @@ -2042,8 +2086,10 @@ name = "sqlc-gen" version = "0.1.0" dependencies = [ "bytes", + "check_keyword", "convert_case", "itertools 0.13.0", + "pluralizer", "postgres-types", "prettyplease", "proc-macro2", diff --git a/examples/authors/sqlc.yaml b/examples/authors/sqlc.yaml index fdde40e..93a1a8d 100644 --- a/examples/authors/sqlc.yaml +++ b/examples/authors/sqlc.yaml @@ -6,7 +6,7 @@ plugins: - RUST_LOG wasm: url: file://./../../target/wasm32-wasi/release/sqlc-gen.wasm - sha256: 91320118cfc27080bdd42a70be43198d3430fd774aa2ea3920ce1f8b55d444c4 + sha256: 7c02055c3eba7bcb913da0e9090541314d14da8c350b34891c4a2e5826c23307 # - name: js # process: @@ -19,6 +19,7 @@ sql: - out: src/db plugin: rust-gen options: + type: async lang: en-US # - out: gen # plugin: js diff --git a/examples/authors/src/db/gen.rs b/examples/authors/src/db/gen.rs index 99930ff..6adbd7e 100644 --- a/examples/authors/src/db/gen.rs +++ b/examples/authors/src/db/gen.rs @@ -5,26 +5,10 @@ const GET_AUTHOR: &str = r#" SELECT id, name, bio FROM authors WHERE id = $1 LIMIT 1 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetAuthorParams { - pub id: i64, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetAuthorRow { - pub id: i64, - pub name: String, - pub bio: Option, -} const LIST_AUTHORS: &str = r#" SELECT id, name, bio FROM authors ORDER BY name "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct ListAuthorsRow { - pub id: i64, - pub name: String, - pub bio: Option, -} const CREATE_AUTHOR: &str = r#" INSERT INTO authors ( name, bio @@ -33,25 +17,21 @@ INSERT INTO authors ( ) RETURNING id, name, bio "#; +const DELETE_AUTHOR: &str = r#" +DELETE FROM authors +WHERE id = $1 +"#; #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateAuthorParams { +pub(crate) struct Author { + pub id: i64, pub name: String, pub bio: Option, } #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateAuthorRow { - pub id: i64, +pub(crate) struct CreateAuthorParams { pub name: String, pub bio: Option, } -const DELETE_AUTHOR: &str = r#" -DELETE FROM authors -WHERE id = $1 -"#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct DeleteAuthorParams { - pub id: i64, -} pub struct Queries { client: postgres::Client, } @@ -59,33 +39,27 @@ impl Queries { pub fn new(client: postgres::Client) -> Self { Self { client } } - pub(crate) fn get_author( + pub(crate) fn create_author( &mut self, - params: GetAuthorParams, - ) -> anyhow::Result { - let row = self.client.query_one(GET_AUTHOR, &[¶ms.id])?; + arg: CreateAuthorParams, + ) -> anyhow::Result { + let row = self.client.query_one(CREATE_AUTHOR, &[&arg.name, &arg.bio])?; + Ok(sqlc_core::FromPostgresRow::from_row(&row)?) + } + pub(crate) fn delete_author(&mut self, id: i64) -> anyhow::Result<()> { + self.client.execute(DELETE_AUTHOR, &[&id])?; + Ok(()) + } + pub(crate) fn get_author(&mut self, id: i64) -> anyhow::Result { + let row = self.client.query_one(GET_AUTHOR, &[&id])?; Ok(sqlc_core::FromPostgresRow::from_row(&row)?) } - pub(crate) fn list_authors(&mut self) -> anyhow::Result> { + pub(crate) fn list_authors(&mut self) -> anyhow::Result> { let rows = self.client.query(LIST_AUTHORS, &[])?; - let mut result: Vec = vec![]; + let mut result: Vec = vec![]; for row in rows { result.push(sqlc_core::FromPostgresRow::from_row(&row)?); } Ok(result) } - pub(crate) fn create_author( - &mut self, - params: CreateAuthorParams, - ) -> anyhow::Result { - let row = self.client.query_one(CREATE_AUTHOR, &[¶ms.name, ¶ms.bio])?; - Ok(sqlc_core::FromPostgresRow::from_row(&row)?) - } - pub(crate) fn delete_author( - &mut self, - params: DeleteAuthorParams, - ) -> anyhow::Result<()> { - self.client.execute(DELETE_AUTHOR, &[¶ms.id])?; - Ok(()) - } } diff --git a/examples/authors/src/main.rs b/examples/authors/src/main.rs index 4906c3a..a0c6ef4 100644 --- a/examples/authors/src/main.rs +++ b/examples/authors/src/main.rs @@ -39,12 +39,10 @@ fn main() -> Result<()> { let authors = queries.list_authors().unwrap(); assert_eq!(authors.len(), 0); - let author_res_err = queries.get_author(db::GetAuthorParams { id: 1 }).is_err(); + let author_res_err = queries.get_author(1).is_err(); assert_eq!(author_res_err, true); - let delete_res = queries - .delete_author(db::DeleteAuthorParams { id: 1 }) - .is_ok(); + let delete_res = queries.delete_author(1).is_ok(); assert_eq!(delete_res, true); let author1_req = db::CreateAuthorParams { @@ -56,11 +54,7 @@ fn main() -> Result<()> { assert_eq!(author1_res.bio, author1_req.bio.clone()); assert!(author1_res.id > 0); - let mut authors_list_prepared = vec![db::ListAuthorsRow { - id: author1_res.id, - name: author1_res.name.clone(), - bio: author1_res.bio.clone(), - }]; + let mut authors_list_prepared = vec![author1_res.clone()]; let authors = queries.list_authors().unwrap(); assert_eq!(authors.len(), 1); assert_eq!(authors, authors_list_prepared); @@ -74,24 +68,16 @@ fn main() -> Result<()> { assert_eq!(author2_res.bio, author2_req.bio); assert!(author2_res.id > 1); - authors_list_prepared.push(db::ListAuthorsRow { - id: author2_res.id, - name: author2_res.name, - bio: author2_res.bio, - }); + authors_list_prepared.push(author2_res.clone()); let authors = queries.list_authors().unwrap(); assert_eq!(authors.len(), 2); assert_eq!(authors, authors_list_prepared); - let author = queries.get_author(db::GetAuthorParams { id: 1 }).unwrap(); - assert_eq!(author.id, author1_res.id); - assert_eq!(author.name, author1_res.name); - assert_eq!(author.bio, author1_res.bio); + let author = queries.get_author(1).unwrap(); + assert_eq!(author, author1_res); - queries - .delete_author(db::DeleteAuthorParams { id: 1 }) - .unwrap(); + queries.delete_author(1).unwrap(); let authors = queries.list_authors().unwrap(); assert_eq!(authors.len(), 1); assert_eq!(authors, authors_list_prepared[1..]); diff --git a/sqlc-core/Cargo.toml b/sqlc-core/Cargo.toml index e47d2ac..60f3b5e 100644 --- a/sqlc-core/Cargo.toml +++ b/sqlc-core/Cargo.toml @@ -5,3 +5,4 @@ edition = "2021" [dependencies] postgres = "0.19.9" +postgres-types = "0.2.8" diff --git a/sqlc-gen/Cargo.toml b/sqlc-gen/Cargo.toml index 9a39773..7b9d172 100644 --- a/sqlc-gen/Cargo.toml +++ b/sqlc-gen/Cargo.toml @@ -21,6 +21,8 @@ postgres-types = "0.2.7" strum = "0.26.3" strum_macros = "0.26.4" itertools = "0.13.0" +pluralizer = "0.4.0" +check_keyword = "0.3.1" [build-dependencies] prost-build = "0.9.0" diff --git a/sqlc-gen/Makefile b/sqlc-gen/Makefile index c810c9a..e16ba10 100644 --- a/sqlc-gen/Makefile +++ b/sqlc-gen/Makefile @@ -15,7 +15,7 @@ generate-for-example: file_with_ext=$$(ls sqlc.*) && \ file_ext=$${file_with_ext##*.} && \ ls sqlc.* | yq -iP ".plugins[0].wasm.sha256=\"$$sha_256\", .plugins[0].wasm.url=\"file://./../../../target/wasm32-wasi/release/sqlc-gen.wasm\"" $$file_with_ext -o $$file_ext && \ - sqlc generate && \ + RUST_LOG=debug sqlc generate && \ cd - generate: diff --git a/sqlc-gen/examples/authors/postgresql/gen.rs b/sqlc-gen/examples/authors/postgresql/gen.rs index 99930ff..6adbd7e 100644 --- a/sqlc-gen/examples/authors/postgresql/gen.rs +++ b/sqlc-gen/examples/authors/postgresql/gen.rs @@ -5,26 +5,10 @@ const GET_AUTHOR: &str = r#" SELECT id, name, bio FROM authors WHERE id = $1 LIMIT 1 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetAuthorParams { - pub id: i64, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetAuthorRow { - pub id: i64, - pub name: String, - pub bio: Option, -} const LIST_AUTHORS: &str = r#" SELECT id, name, bio FROM authors ORDER BY name "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct ListAuthorsRow { - pub id: i64, - pub name: String, - pub bio: Option, -} const CREATE_AUTHOR: &str = r#" INSERT INTO authors ( name, bio @@ -33,25 +17,21 @@ INSERT INTO authors ( ) RETURNING id, name, bio "#; +const DELETE_AUTHOR: &str = r#" +DELETE FROM authors +WHERE id = $1 +"#; #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateAuthorParams { +pub(crate) struct Author { + pub id: i64, pub name: String, pub bio: Option, } #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateAuthorRow { - pub id: i64, +pub(crate) struct CreateAuthorParams { pub name: String, pub bio: Option, } -const DELETE_AUTHOR: &str = r#" -DELETE FROM authors -WHERE id = $1 -"#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct DeleteAuthorParams { - pub id: i64, -} pub struct Queries { client: postgres::Client, } @@ -59,33 +39,27 @@ impl Queries { pub fn new(client: postgres::Client) -> Self { Self { client } } - pub(crate) fn get_author( + pub(crate) fn create_author( &mut self, - params: GetAuthorParams, - ) -> anyhow::Result { - let row = self.client.query_one(GET_AUTHOR, &[¶ms.id])?; + arg: CreateAuthorParams, + ) -> anyhow::Result { + let row = self.client.query_one(CREATE_AUTHOR, &[&arg.name, &arg.bio])?; + Ok(sqlc_core::FromPostgresRow::from_row(&row)?) + } + pub(crate) fn delete_author(&mut self, id: i64) -> anyhow::Result<()> { + self.client.execute(DELETE_AUTHOR, &[&id])?; + Ok(()) + } + pub(crate) fn get_author(&mut self, id: i64) -> anyhow::Result { + let row = self.client.query_one(GET_AUTHOR, &[&id])?; Ok(sqlc_core::FromPostgresRow::from_row(&row)?) } - pub(crate) fn list_authors(&mut self) -> anyhow::Result> { + pub(crate) fn list_authors(&mut self) -> anyhow::Result> { let rows = self.client.query(LIST_AUTHORS, &[])?; - let mut result: Vec = vec![]; + let mut result: Vec = vec![]; for row in rows { result.push(sqlc_core::FromPostgresRow::from_row(&row)?); } Ok(result) } - pub(crate) fn create_author( - &mut self, - params: CreateAuthorParams, - ) -> anyhow::Result { - let row = self.client.query_one(CREATE_AUTHOR, &[¶ms.name, ¶ms.bio])?; - Ok(sqlc_core::FromPostgresRow::from_row(&row)?) - } - pub(crate) fn delete_author( - &mut self, - params: DeleteAuthorParams, - ) -> anyhow::Result<()> { - self.client.execute(DELETE_AUTHOR, &[¶ms.id])?; - Ok(()) - } } diff --git a/sqlc-gen/examples/authors/sqlc.yaml b/sqlc-gen/examples/authors/sqlc.yaml index 5d0d8bc..1318b2b 100644 --- a/sqlc-gen/examples/authors/sqlc.yaml +++ b/sqlc-gen/examples/authors/sqlc.yaml @@ -6,7 +6,7 @@ plugins: - RUST_LOG wasm: url: file://./../../../target/wasm32-wasi/release/sqlc-gen.wasm - sha256: 91320118cfc27080bdd42a70be43198d3430fd774aa2ea3920ce1f8b55d444c4 + sha256: 7c02055c3eba7bcb913da0e9090541314d14da8c350b34891c4a2e5826c23307 # # - name: js # process: diff --git a/sqlc-gen/examples/batch/postgresql/gen.rs b/sqlc-gen/examples/batch/postgresql/gen.rs index d40180a..3dc95d0 100644 --- a/sqlc-gen/examples/batch/postgresql/gen.rs +++ b/sqlc-gen/examples/batch/postgresql/gen.rs @@ -1,26 +1,35 @@ /// @generated by the sqlc-gen-rust on sqlc-generate using sqlc.yaml /// DO NOT EDIT. use postgres::{Error, Row}; -#[derive(Debug, Display)] -pub enum BookType { - Fiction, - Nonfiction, -} const GET_AUTHOR: &str = r#" select author_id, name, biography from authors where author_id = $1 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetAuthorParams { - pub author_id: i32, +#[derive(Debug, Display, postgres_types::ToSql, postgres_type::FromSql)] +pub enum BookType { + #[postgres(name = "FICTION")] + Fiction, + #[postgres(name = "NONFICTION")] + Nonfiction, } #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetAuthorRow { +pub(crate) struct Author { pub author_id: i32, pub name: String, pub biography: Option, } +#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] +pub(crate) struct Book { + pub book_id: i32, + pub author_id: i32, + pub isbn: String, + pub book_type: BookType, + pub title: String, + pub year: i32, + pub available: String, + pub tags: Vec, +} pub struct Queries { client: postgres::Client, } @@ -28,11 +37,8 @@ impl Queries { pub fn new(client: postgres::Client) -> Self { Self { client } } - pub(crate) fn get_author( - &mut self, - params: GetAuthorParams, - ) -> anyhow::Result { - let row = self.client.query_one(GET_AUTHOR, &[¶ms.author_id])?; + pub(crate) fn get_author(&mut self, author_id: i32) -> anyhow::Result { + let row = self.client.query_one(GET_AUTHOR, &[&author_id])?; Ok(sqlc_core::FromPostgresRow::from_row(&row)?) } } diff --git a/sqlc-gen/examples/batch/sqlc.json b/sqlc-gen/examples/batch/sqlc.json index ce29e78..038d09d 100644 --- a/sqlc-gen/examples/batch/sqlc.json +++ b/sqlc-gen/examples/batch/sqlc.json @@ -9,7 +9,7 @@ ], "wasm": { "url": "file://./../../../target/wasm32-wasi/release/sqlc-gen.wasm", - "sha256": "91320118cfc27080bdd42a70be43198d3430fd774aa2ea3920ce1f8b55d444c4" + "sha256": "7c02055c3eba7bcb913da0e9090541314d14da8c350b34891c4a2e5826c23307" } } ], diff --git a/sqlc-gen/examples/booktest/gen/gen.rs b/sqlc-gen/examples/booktest/gen/gen.rs index ac615ea..990a6a5 100644 --- a/sqlc-gen/examples/booktest/gen/gen.rs +++ b/sqlc-gen/examples/booktest/gen/gen.rs @@ -1,105 +1,35 @@ /// @generated by the sqlc-gen-rust on sqlc-generate using sqlc.yaml /// DO NOT EDIT. use postgres::{Error, Row}; -#[derive(Debug, Display)] -pub enum BookType { - Fiction, - Nonfiction, -} const GET_AUTHOR: &str = r#" select author_id, name from authors where author_id = $1 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetAuthorParams { - pub author_id: i32, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetAuthorRow { - pub author_id: i32, - pub name: String, -} const GET_BOOK: &str = r#" select book_id, author_id, isbn, book_type, title, year, available, tags from books where book_id = $1 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetBookParams { - pub book_id: i32, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetBookRow { - pub book_id: i32, - pub author_id: i32, - pub isbn: String, - pub book_type: String, - pub title: String, - pub year: i32, - pub available: String, - pub tags: Vec, -} const DELETE_BOOK: &str = r#" delete from books where book_id = $1 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct DeleteBookParams { - pub book_id: i32, -} const BOOKS_BY_TITLE_YEAR: &str = r#" select book_id, author_id, isbn, book_type, title, year, available, tags from books where title = $1 and year = $2 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct BooksByTitleYearParams { - pub title: String, - pub year: i32, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct BooksByTitleYearRow { - pub book_id: i32, - pub author_id: i32, - pub isbn: String, - pub book_type: String, - pub title: String, - pub year: i32, - pub available: String, - pub tags: Vec, -} const BOOKS_BY_TAGS: &str = r#" select book_id, title, name, isbn, tags from books left join authors on books.author_id = authors.author_id where tags && $1::varchar[] "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct BooksByTagsParams { - pub _1: Vec, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct BooksByTagsRow { - pub book_id: i32, - pub title: String, - pub name: Option, - pub isbn: String, - pub tags: Vec, -} const CREATE_AUTHOR: &str = r#" INSERT INTO authors (name) VALUES ($1) RETURNING author_id, name "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateAuthorParams { - pub name: String, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateAuthorRow { - pub author_id: i32, - pub name: String, -} const CREATE_BOOK: &str = r#" INSERT INTO books ( author_id, @@ -120,43 +50,66 @@ INSERT INTO books ( ) RETURNING book_id, author_id, isbn, book_type, title, year, available, tags "#; +const UPDATE_BOOK: &str = r#" +UPDATE books +SET title = $1, tags = $2 +WHERE book_id = $3 +"#; +const UPDATE_BOOK_ISBN: &str = r#" +UPDATE books +SET title = $1, tags = $2, isbn = $4 +WHERE book_id = $3 +"#; +const SAY_HELLO: &str = r#" +select say_hello +from say_hello($1) +"#; +#[derive(Debug, Display, postgres_types::ToSql, postgres_type::FromSql)] +pub enum BookType { + #[postgres(name = "FICTION")] + Fiction, + #[postgres(name = "NONFICTION")] + Nonfiction, +} #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateBookParams { +pub(crate) struct Author { + pub author_id: i32, + pub name: String, +} +#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] +pub(crate) struct Book { + pub book_id: i32, pub author_id: i32, pub isbn: String, - pub book_type: String, + pub book_type: BookType, pub title: String, pub year: i32, pub available: String, pub tags: Vec, } #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateBookRow { +pub(crate) struct BooksByTagsRow { pub book_id: i32, - pub author_id: i32, + pub title: String, + pub name: Option, pub isbn: String, - pub book_type: String, + pub tags: Vec, +} +#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] +pub(crate) struct BooksByTitleYearParams { pub title: String, pub year: i32, - pub available: String, - pub tags: Vec, } -const UPDATE_BOOK: &str = r#" -UPDATE books -SET title = $1, tags = $2 -WHERE book_id = $3 -"#; #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct UpdateBookParams { +pub(crate) struct CreateBookParams { + pub author_id: i32, + pub isbn: String, + pub book_type: BookType, pub title: String, + pub year: i32, + pub available: String, pub tags: Vec, - pub book_id: i32, } -const UPDATE_BOOK_ISBN: &str = r#" -UPDATE books -SET title = $1, tags = $2, isbn = $4 -WHERE book_id = $3 -"#; #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] pub(crate) struct UpdateBookIsbnParams { pub title: String, @@ -164,17 +117,11 @@ pub(crate) struct UpdateBookIsbnParams { pub book_id: i32, pub isbn: String, } -const SAY_HELLO: &str = r#" -select say_hello -from say_hello($1) -"#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct SayHelloParams { - pub s: String, -} #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct SayHelloRow { - pub say_hello: Option, +pub(crate) struct UpdateBookParams { + pub title: String, + pub tags: Vec, + pub book_id: i32, } pub struct Queries { client: postgres::Client, @@ -183,102 +130,78 @@ impl Queries { pub fn new(client: postgres::Client) -> Self { Self { client } } - pub(crate) fn get_author( - &mut self, - params: GetAuthorParams, - ) -> anyhow::Result { - let row = self.client.query_one(GET_AUTHOR, &[¶ms.author_id])?; - Ok(sqlc_core::FromPostgresRow::from_row(&row)?) - } - pub(crate) fn get_book( - &mut self, - params: GetBookParams, - ) -> anyhow::Result { - let row = self.client.query_one(GET_BOOK, &[¶ms.book_id])?; - Ok(sqlc_core::FromPostgresRow::from_row(&row)?) - } - pub(crate) fn delete_book( - &mut self, - params: DeleteBookParams, - ) -> anyhow::Result<()> { - self.client.execute(DELETE_BOOK, &[¶ms.book_id])?; - Ok(()) - } - pub(crate) fn books_by_title_year( + pub(crate) fn books_by_tags( &mut self, - params: BooksByTitleYearParams, - ) -> anyhow::Result> { - let rows = self - .client - .query(BOOKS_BY_TITLE_YEAR, &[¶ms.title, ¶ms.year])?; - let mut result: Vec = vec![]; + dollar_1: String, + ) -> anyhow::Result> { + let rows = self.client.query(BOOKS_BY_TAGS, &[&dollar_1])?; + let mut result: Vec = vec![]; for row in rows { result.push(sqlc_core::FromPostgresRow::from_row(&row)?); } Ok(result) } - pub(crate) fn books_by_tags( + pub(crate) fn books_by_title_year( &mut self, - params: BooksByTagsParams, - ) -> anyhow::Result> { - let rows = self.client.query(BOOKS_BY_TAGS, &[¶ms._1])?; - let mut result: Vec = vec![]; + arg: BooksByTitleYearParams, + ) -> anyhow::Result> { + let rows = self.client.query(BOOKS_BY_TITLE_YEAR, &[&arg.title, &arg.year])?; + let mut result: Vec = vec![]; for row in rows { result.push(sqlc_core::FromPostgresRow::from_row(&row)?); } Ok(result) } - pub(crate) fn create_author( - &mut self, - params: CreateAuthorParams, - ) -> anyhow::Result { - let row = self.client.query_one(CREATE_AUTHOR, &[¶ms.name])?; + pub(crate) fn create_author(&mut self, name: String) -> anyhow::Result { + let row = self.client.query_one(CREATE_AUTHOR, &[&name])?; Ok(sqlc_core::FromPostgresRow::from_row(&row)?) } - pub(crate) fn create_book( - &mut self, - params: CreateBookParams, - ) -> anyhow::Result { + pub(crate) fn create_book(&mut self, arg: CreateBookParams) -> anyhow::Result { let row = self .client .query_one( CREATE_BOOK, &[ - ¶ms.author_id, - ¶ms.isbn, - ¶ms.book_type, - ¶ms.title, - ¶ms.year, - ¶ms.available, - ¶ms.tags, + &arg.author_id, + &arg.isbn, + &arg.book_type, + &arg.title, + &arg.year, + &arg.available, + &arg.tags, ], )?; Ok(sqlc_core::FromPostgresRow::from_row(&row)?) } - pub(crate) fn update_book( - &mut self, - params: UpdateBookParams, - ) -> anyhow::Result<()> { - self.client - .execute(UPDATE_BOOK, &[¶ms.title, ¶ms.tags, ¶ms.book_id])?; + pub(crate) fn delete_book(&mut self, book_id: i32) -> anyhow::Result<()> { + self.client.execute(DELETE_BOOK, &[&book_id])?; + Ok(()) + } + pub(crate) fn get_author(&mut self, author_id: i32) -> anyhow::Result { + let row = self.client.query_one(GET_AUTHOR, &[&author_id])?; + Ok(sqlc_core::FromPostgresRow::from_row(&row)?) + } + pub(crate) fn get_book(&mut self, book_id: i32) -> anyhow::Result { + let row = self.client.query_one(GET_BOOK, &[&book_id])?; + Ok(sqlc_core::FromPostgresRow::from_row(&row)?) + } + pub(crate) fn say_hello(&mut self, s: String) -> anyhow::Result { + let row = self.client.query_one(SAY_HELLO, &[&s])?; + Ok(sqlc_core::FromPostgresRow::from_row(&row)?) + } + pub(crate) fn update_book(&mut self, arg: UpdateBookParams) -> anyhow::Result<()> { + self.client.execute(UPDATE_BOOK, &[&arg.title, &arg.tags, &arg.book_id])?; Ok(()) } pub(crate) fn update_book_isbn( &mut self, - params: UpdateBookIsbnParams, + arg: UpdateBookIsbnParams, ) -> anyhow::Result<()> { self.client .execute( UPDATE_BOOK_ISBN, - &[¶ms.title, ¶ms.tags, ¶ms.book_id, ¶ms.isbn], + &[&arg.title, &arg.tags, &arg.book_id, &arg.isbn], )?; Ok(()) } - pub(crate) fn say_hello( - &mut self, - params: SayHelloParams, - ) -> anyhow::Result { - let row = self.client.query_one(SAY_HELLO, &[¶ms.s])?; - Ok(sqlc_core::FromPostgresRow::from_row(&row)?) - } } diff --git a/sqlc-gen/examples/booktest/sqlc.yaml b/sqlc-gen/examples/booktest/sqlc.yaml index 7318c33..c15ed32 100644 --- a/sqlc-gen/examples/booktest/sqlc.yaml +++ b/sqlc-gen/examples/booktest/sqlc.yaml @@ -6,7 +6,7 @@ plugins: - RUST_LOG wasm: url: file://./../../../target/wasm32-wasi/release/sqlc-gen.wasm - sha256: 91320118cfc27080bdd42a70be43198d3430fd774aa2ea3920ce1f8b55d444c4 + sha256: 7c02055c3eba7bcb913da0e9090541314d14da8c350b34891c4a2e5826c23307 # # - name: js # process: diff --git a/sqlc-gen/examples/jets/postgresql/gen.rs b/sqlc-gen/examples/jets/postgresql/gen.rs index d1792d8..6925325 100644 --- a/sqlc-gen/examples/jets/postgresql/gen.rs +++ b/sqlc-gen/examples/jets/postgresql/gen.rs @@ -2,20 +2,30 @@ /// DO NOT EDIT. use postgres::{Error, Row}; const COUNT_PILOTS: &str = r#"SELECT COUNT(*) FROM pilots"#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CountPilotsRow { - pub count: i64, -} const LIST_PILOTS: &str = r#"SELECT id, name FROM pilots LIMIT 5"#; +const DELETE_PILOT: &str = r#"DELETE FROM pilots WHERE id = $1"#; #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct ListPilotsRow { +pub(crate) struct Jet { pub id: i32, + pub pilot_id: i32, + pub age: i32, pub name: String, + pub color: String, +} +#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] +pub(crate) struct Language { + pub id: i32, + pub language: String, } -const DELETE_PILOT: &str = r#"DELETE FROM pilots WHERE id = $1"#; #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct DeletePilotParams { +pub(crate) struct Pilot { pub id: i32, + pub name: String, +} +#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] +pub(crate) struct PilotLanguage { + pub pilot_id: i32, + pub language_id: i32, } pub struct Queries { client: postgres::Client, @@ -24,23 +34,20 @@ impl Queries { pub fn new(client: postgres::Client) -> Self { Self { client } } - pub(crate) fn count_pilots(&mut self) -> anyhow::Result { + pub(crate) fn count_pilots(&mut self) -> anyhow::Result { let row = self.client.query_one(COUNT_PILOTS, &[])?; Ok(sqlc_core::FromPostgresRow::from_row(&row)?) } - pub(crate) fn list_pilots(&mut self) -> anyhow::Result> { + pub(crate) fn delete_pilot(&mut self, id: i32) -> anyhow::Result<()> { + self.client.execute(DELETE_PILOT, &[&id])?; + Ok(()) + } + pub(crate) fn list_pilots(&mut self) -> anyhow::Result> { let rows = self.client.query(LIST_PILOTS, &[])?; - let mut result: Vec = vec![]; + let mut result: Vec = vec![]; for row in rows { result.push(sqlc_core::FromPostgresRow::from_row(&row)?); } Ok(result) } - pub(crate) fn delete_pilot( - &mut self, - params: DeletePilotParams, - ) -> anyhow::Result<()> { - self.client.execute(DELETE_PILOT, &[¶ms.id])?; - Ok(()) - } } diff --git a/sqlc-gen/examples/jets/sqlc.json b/sqlc-gen/examples/jets/sqlc.json index 61ca933..26f2df0 100644 --- a/sqlc-gen/examples/jets/sqlc.json +++ b/sqlc-gen/examples/jets/sqlc.json @@ -9,7 +9,7 @@ ], "wasm": { "url": "file://./../../../target/wasm32-wasi/release/sqlc-gen.wasm", - "sha256": "91320118cfc27080bdd42a70be43198d3430fd774aa2ea3920ce1f8b55d444c4" + "sha256": "7c02055c3eba7bcb913da0e9090541314d14da8c350b34891c4a2e5826c23307" } } ], diff --git a/sqlc-gen/examples/ondeck/postgresql/gen.rs b/sqlc-gen/examples/ondeck/postgresql/gen.rs index 2444881..8818f05 100644 --- a/sqlc-gen/examples/ondeck/postgresql/gen.rs +++ b/sqlc-gen/examples/ondeck/postgresql/gen.rs @@ -1,35 +1,16 @@ /// @generated by the sqlc-gen-rust on sqlc-generate using sqlc.yaml /// DO NOT EDIT. use postgres::{Error, Row}; -#[derive(Debug, Display)] -pub enum Status { - Open, - Closed, -} const LIST_CITIES: &str = r#" select slug, name from city order by name "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct ListCitiesRow { - pub slug: String, - pub name: String, -} const GET_CITY: &str = r#" select slug, name from city where slug = $1 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetCityParams { - pub slug: String, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetCityRow { - pub slug: String, - pub name: String, -} const CREATE_CITY: &str = r#" INSERT INTO city ( name, @@ -39,80 +20,26 @@ INSERT INTO city ( $2 ) RETURNING slug, name "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateCityParams { - pub name: String, - pub slug: String, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateCityRow { - pub slug: String, - pub name: String, -} const UPDATE_CITY_NAME: &str = r#" UPDATE city SET name = $2 WHERE slug = $1 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct UpdateCityNameParams { - pub slug: String, - pub name: String, -} const LIST_VENUES: &str = r#" SELECT id, status, statuses, slug, name, city, spotify_playlist, songkick_id, tags, created_at FROM venue WHERE city = $1 ORDER BY name "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct ListVenuesParams { - pub city: String, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct ListVenuesRow { - pub id: i32, - pub status: String, - pub statuses: Option>, - pub slug: String, - pub name: String, - pub city: String, - pub spotify_playlist: String, - pub songkick_id: Option, - pub tags: Option>, - pub created_at: String, -} const DELETE_VENUE: &str = r#" DELETE FROM venue WHERE slug = $1 AND slug = $1 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct DeleteVenueParams { - pub slug: String, -} const GET_VENUE: &str = r#" SELECT id, status, statuses, slug, name, city, spotify_playlist, songkick_id, tags, created_at FROM venue WHERE slug = $1 AND city = $2 "#; -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetVenueParams { - pub slug: String, - pub city: String, -} -#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct GetVenueRow { - pub id: i32, - pub status: String, - pub statuses: Option>, - pub slug: String, - pub name: String, - pub city: String, - pub spotify_playlist: String, - pub songkick_id: Option, - pub tags: Option>, - pub created_at: String, -} const CREATE_VENUE: &str = r#" INSERT INTO venue ( slug, @@ -134,43 +61,75 @@ INSERT INTO venue ( $7 ) RETURNING id "#; +const UPDATE_VENUE_NAME: &str = r#" +UPDATE venue +SET name = $2 +WHERE slug = $1 +RETURNING id +"#; +const VENUE_COUNT_BY_CITY: &str = r#" +SELECT + city, + count(*) +FROM venue +GROUP BY 1 +ORDER BY 1 +"#; +#[derive(Debug, Display, postgres_types::ToSql, postgres_type::FromSql)] +pub enum Status { + #[postgres(name = "op!en")] + Open, + #[postgres(name = "clo@sed")] + Closed, +} +#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] +pub(crate) struct City { + pub slug: String, + pub name: String, +} +#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] +pub(crate) struct CreateCityParams { + pub name: String, + pub slug: String, +} #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] pub(crate) struct CreateVenueParams { pub slug: String, pub name: String, pub city: String, pub spotify_playlist: String, - pub status: String, - pub statuses: Option>, + pub status: Status, + pub statuses: Option>, pub tags: Option>, } #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct CreateVenueRow { - pub id: i32, +pub(crate) struct GetVenueParams { + pub slug: String, + pub city: String, +} +#[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] +pub(crate) struct UpdateCityNameParams { + pub slug: String, + pub name: String, } -const UPDATE_VENUE_NAME: &str = r#" -UPDATE venue -SET name = $2 -WHERE slug = $1 -RETURNING id -"#; #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] pub(crate) struct UpdateVenueNameParams { pub slug: String, pub name: String, } #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] -pub(crate) struct UpdateVenueNameRow { +pub(crate) struct Venue { pub id: i32, + pub status: Status, + pub statuses: Option>, + pub slug: String, + pub name: String, + pub city: String, + pub spotify_playlist: String, + pub songkick_id: Option, + pub tags: Option>, + pub created_at: String, } -const VENUE_COUNT_BY_CITY: &str = r#" -SELECT - city, - count(*) -FROM venue -GROUP BY 1 -ORDER BY 1 -"#; #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] pub(crate) struct VenueCountByCityRow { pub city: String, @@ -183,87 +142,70 @@ impl Queries { pub fn new(client: postgres::Client) -> Self { Self { client } } - pub(crate) fn list_cities(&mut self) -> anyhow::Result> { - let rows = self.client.query(LIST_CITIES, &[])?; - let mut result: Vec = vec![]; - for row in rows { - result.push(sqlc_core::FromPostgresRow::from_row(&row)?); - } - Ok(result) - } - pub(crate) fn get_city( - &mut self, - params: GetCityParams, - ) -> anyhow::Result { - let row = self.client.query_one(GET_CITY, &[¶ms.slug])?; + pub(crate) fn create_city(&mut self, arg: CreateCityParams) -> anyhow::Result { + let row = self.client.query_one(CREATE_CITY, &[&arg.name, &arg.slug])?; Ok(sqlc_core::FromPostgresRow::from_row(&row)?) } - pub(crate) fn create_city( + pub(crate) fn create_venue( &mut self, - params: CreateCityParams, - ) -> anyhow::Result { - let row = self.client.query_one(CREATE_CITY, &[¶ms.name, ¶ms.slug])?; + arg: CreateVenueParams, + ) -> anyhow::Result { + let row = self + .client + .query_one( + CREATE_VENUE, + &[ + &arg.slug, + &arg.name, + &arg.city, + &arg.spotify_playlist, + &arg.status, + &arg.statuses, + &arg.tags, + ], + )?; Ok(sqlc_core::FromPostgresRow::from_row(&row)?) } - pub(crate) fn update_city_name( - &mut self, - params: UpdateCityNameParams, - ) -> anyhow::Result<()> { - self.client.execute(UPDATE_CITY_NAME, &[¶ms.slug, ¶ms.name])?; + pub(crate) fn delete_venue(&mut self, slug: String) -> anyhow::Result<()> { + self.client.execute(DELETE_VENUE, &[&slug])?; Ok(()) } - pub(crate) fn list_venues( - &mut self, - params: ListVenuesParams, - ) -> anyhow::Result> { - let rows = self.client.query(LIST_VENUES, &[¶ms.city])?; - let mut result: Vec = vec![]; + pub(crate) fn get_city(&mut self, slug: String) -> anyhow::Result { + let row = self.client.query_one(GET_CITY, &[&slug])?; + Ok(sqlc_core::FromPostgresRow::from_row(&row)?) + } + pub(crate) fn get_venue(&mut self, arg: GetVenueParams) -> anyhow::Result { + let row = self.client.query_one(GET_VENUE, &[&arg.slug, &arg.city])?; + Ok(sqlc_core::FromPostgresRow::from_row(&row)?) + } + pub(crate) fn list_cities(&mut self) -> anyhow::Result> { + let rows = self.client.query(LIST_CITIES, &[])?; + let mut result: Vec = vec![]; + for row in rows { + result.push(sqlc_core::FromPostgresRow::from_row(&row)?); + } + Ok(result) + } + pub(crate) fn list_venues(&mut self, city: String) -> anyhow::Result> { + let rows = self.client.query(LIST_VENUES, &[&city])?; + let mut result: Vec = vec![]; for row in rows { result.push(sqlc_core::FromPostgresRow::from_row(&row)?); } Ok(result) } - pub(crate) fn delete_venue( + pub(crate) fn update_city_name( &mut self, - params: DeleteVenueParams, + arg: UpdateCityNameParams, ) -> anyhow::Result<()> { - self.client.execute(DELETE_VENUE, &[¶ms.slug])?; + self.client.execute(UPDATE_CITY_NAME, &[&arg.slug, &arg.name])?; Ok(()) } - pub(crate) fn get_venue( - &mut self, - params: GetVenueParams, - ) -> anyhow::Result { - let row = self.client.query_one(GET_VENUE, &[¶ms.slug, ¶ms.city])?; - Ok(sqlc_core::FromPostgresRow::from_row(&row)?) - } - pub(crate) fn create_venue( - &mut self, - params: CreateVenueParams, - ) -> anyhow::Result { - let row = self - .client - .query_one( - CREATE_VENUE, - &[ - ¶ms.slug, - ¶ms.name, - ¶ms.city, - ¶ms.spotify_playlist, - ¶ms.status, - ¶ms.statuses, - ¶ms.tags, - ], - )?; - Ok(sqlc_core::FromPostgresRow::from_row(&row)?) - } pub(crate) fn update_venue_name( &mut self, - params: UpdateVenueNameParams, - ) -> anyhow::Result { - let row = self - .client - .query_one(UPDATE_VENUE_NAME, &[¶ms.slug, ¶ms.name])?; + arg: UpdateVenueNameParams, + ) -> anyhow::Result { + let row = self.client.query_one(UPDATE_VENUE_NAME, &[&arg.slug, &arg.name])?; Ok(sqlc_core::FromPostgresRow::from_row(&row)?) } pub(crate) fn venue_count_by_city( diff --git a/sqlc-gen/examples/ondeck/postgresql/schema/0002_venue.sql b/sqlc-gen/examples/ondeck/postgresql/schema/0002_venue.sql index fbde8ad..a04f692 100644 --- a/sqlc-gen/examples/ondeck/postgresql/schema/0002_venue.sql +++ b/sqlc-gen/examples/ondeck/postgresql/schema/0002_venue.sql @@ -1,5 +1,5 @@ --- CREATE TYPE status AS ENUM ('op!en', 'clo@sed'); -CREATE TYPE status AS ENUM ('open', 'closed'); +CREATE TYPE status AS ENUM ('op!en', 'clo@sed'); +-- CREATE TYPE status AS ENUM ('open', 'closed'); COMMENT ON TYPE status IS 'Venues can be either open or closed'; CREATE TABLE venues ( diff --git a/sqlc-gen/examples/ondeck/sqlc.json b/sqlc-gen/examples/ondeck/sqlc.json index 15216b0..4f58f4f 100644 --- a/sqlc-gen/examples/ondeck/sqlc.json +++ b/sqlc-gen/examples/ondeck/sqlc.json @@ -9,7 +9,7 @@ ], "wasm": { "url": "file://./../../../target/wasm32-wasi/release/sqlc-gen.wasm", - "sha256": "91320118cfc27080bdd42a70be43198d3430fd774aa2ea3920ce1f8b55d444c4" + "sha256": "7c02055c3eba7bcb913da0e9090541314d14da8c350b34891c4a2e5826c23307" } } ], diff --git a/sqlc-gen/src/codegen/mod.rs b/sqlc-gen/src/codegen/mod.rs index 2c2c27a..01450ce 100644 --- a/sqlc-gen/src/codegen/mod.rs +++ b/sqlc-gen/src/codegen/mod.rs @@ -1,16 +1,21 @@ +use check_keyword::CheckKeyword; +use convert_case::Casing; +use core::panic; use itertools::Itertools; use proc_macro2::{Punct, Spacing, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::fmt; +use std::hash::Hash; +use std::str::FromStr; use syn::Ident; use type_const::TypeConst; -use type_enum::TypeEnum; -use type_method::TypeMethod; +use type_enum::{enum_name, TypeEnum}; +use type_query::{QueryCommand, QueryValue, TypeQuery}; use type_struct::{StructField, StructType, TypeStruct}; mod type_const; mod type_enum; -mod type_method; +mod type_query; mod type_struct; pub mod plugin { @@ -34,6 +39,56 @@ pub fn get_newline_tokens() -> TokenStream { get_punct_from_char_tokens(newline_char) } +pub fn column_name(name: String, pos: i32) -> String { + let col_name = match name.is_empty() { + false => name.clone(), + true => format!("_{}", pos), + }; + + col_name.to_case(convert_case::Case::Snake) +} + +pub fn param_name(p: &plugin::Parameter) -> String { + let Some(column) = p.column.clone() else { + panic!("column not found"); + }; + + if !column.name.is_empty() { + column.name.to_case(convert_case::Case::Snake) + } else { + format!("dollar_{}", p.number) + } +} + +pub fn escape(s: &str) -> String { + if s.is_keyword() { + format!("s_{s}") + } else { + s.to_string() + } +} + +pub fn same_table( + col_table: Option, + struct_table: Option, + default_schema: String, +) -> bool { + if let Some(table_id) = col_table { + let mut schema = table_id.schema; + if schema.is_empty() { + schema = default_schema; + } + + if let Some(f) = struct_table { + table_id.catalog == f.catalog && schema == f.schema && table_id.name == f.name + } else { + false + } + } else { + false + } +} + #[derive(Debug, Clone)] pub struct MultiLine<'a>(&'a str); @@ -83,18 +138,9 @@ pub struct MultiLineString<'a>(&'a str); impl<'a> ToTokens for MultiLineString<'a> { fn to_tokens(&self, tokens: &mut TokenStream) { - let double_quote_char = char::from_u32(0x0022).unwrap(); - let hashtag_char = char::from_u32(0x0023).unwrap(); - - tokens.extend(get_punct_from_char_tokens('r')); - tokens.extend(get_punct_from_char_tokens(hashtag_char)); - tokens.extend(get_punct_from_char_tokens(double_quote_char)); - + ['r', '#', '"'].map(|c| tokens.extend(get_punct_from_char_tokens(c))); tokens.extend(MultiLine(self.0).to_token_stream()); - - tokens.extend(get_punct_from_char_tokens(double_quote_char)); - tokens.extend(get_punct_from_char_tokens(hashtag_char)); - tokens.extend(get_punct_from_char_tokens(' ')); + ['"', '#', ' '].map(|c| tokens.extend(get_punct_from_char_tokens(c))); } } @@ -114,7 +160,7 @@ impl ToTokens for DataType { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub struct PgDataType(pub String); impl ToTokens for PgDataType { @@ -123,113 +169,72 @@ impl ToTokens for PgDataType { } } -impl fmt::Display for PgDataType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let ident_str = match self.0.as_str() { +impl PgDataType { + pub fn from(s: &str, schemas: Vec, default_schema: String) -> PgDataType { + let pg_data_type_string = match s { "smallint" | "int2" | "pg_catalog.int2" | "smallserial" | "serial2" - | "pg_catalog.serial2" => "i16", + | "pg_catalog.serial2" => "i16".to_string(), "integer" | "int" | "int4" | "pg_catalog.int4" | "serial" | "serial4" - | "pg_catalog.serial4" => "i32", + | "pg_catalog.serial4" => "i32".to_string(), "bigint" | "int8" | "pg_catalog.int8" | "bigserial" | "serial8" - | "pg_catalog.serial8" => "i64", + | "pg_catalog.serial8" => "i64".to_string(), - "real" | "float4" | "pg_catalog.float4" => "f32", - "float" | "double precision" | "float8" | "pg_catalog.float8" => "f64", + "real" | "float4" | "pg_catalog.float4" => "f32".to_string(), + "float" | "double precision" | "float8" | "pg_catalog.float8" => "f64".to_string(), // "numeric" | "pg_catalog.numeric" | "money" => "", - "boolean" | "bool" | "pg_catalog.bool" => "bool", + "boolean" | "bool" | "pg_catalog.bool" => "bool".to_string(), - "json" | "jsonb" => "serde_json::Value", + "json" | "jsonb" => "serde_json::Value".to_string(), - "bytea" | "blob" | "pg_catalog.bytea" => "Vec", + "bytea" | "blob" | "pg_catalog.bytea" => "Vec".to_string(), - "date" => "time::Date", + "date" => "time::Date".to_string(), - "pg_catalog.time" | "pg_catalog.timez" => "time::Time", + "pg_catalog.time" | "pg_catalog.timez" => "time::Time".to_string(), - "pg_catalog.timestamp" => "time::PrimitiveDateTime", - "pg_catalog.timestampz" | "timestampz" => "time::PrimitiveDateTime", + "pg_catalog.timestamp" => "time::PrimitiveDateTime".to_string(), + "pg_catalog.timestampz" | "timestampz" => "time::PrimitiveDateTime".to_string(), - // "interval" | "pg_catalog.interval" => "", + "interval" | "pg_catalog.interval" => "i64".to_string(), "text" | "pg_catalog.varchar" | "pg_catalog.bpchar" | "string" | "citext" | "ltree" - | "lquery" | "ltxtquery" => "String", - - "uuid" => "uuid::Uuid", - "inet" => "cidr::InetCidr", - "cidr" => "cidr::InetAddr", - "macaddr" | "macaddr8" => "eui48::MacAddress", + | "lquery" | "ltxtquery" => "String".to_string(), + + "uuid" => "uuid::Uuid".to_string(), + "inet" => "cidr::InetCidr".to_string(), + "cidr" => "cidr::InetAddr".to_string(), + "macaddr" | "macaddr8" => "eui48::MacAddress".to_string(), + + _ => { + let res = schemas.into_iter().find_map(|schema| { + if schema.name == "pg_catalog" || schema.name == "information_schema" { + None + } else if let Some(matching_enum) = + schema.enums.clone().into_iter().find(|e| s == e.name) + { + Some((matching_enum, schema)) + } else { + None + } + }); - _ => "String", + if let Some((matching_enum, schema)) = res { + enum_name(&matching_enum.name, &schema.name, &default_schema) + } else { + "String".to_string() + } + } }; - f.write_str(ident_str) + PgDataType(pg_data_type_string) } } -pub trait CodePartial { - fn of_type(self: &Self) -> String; - fn generate_code(self: &Self) -> TokenStream; -} - -struct PartialsBuilder { - query: plugin::Query, -} - -impl PartialsBuilder { - pub fn new(query: plugin::Query) -> PartialsBuilder { - Self { query } - } - - fn create_struct_field_from_column(&self, col: plugin::Column, number: i32) -> StructField { - StructField { - name: col.name, - number, - is_array: col.is_array, - not_null: col.not_null, - data_type: PgDataType(col.r#type.unwrap().name), - } - } - - fn params_struct(&self) -> TypeStruct { - let fields = self.query.params.clone(); - let fields = fields - .into_iter() - .map(|field| self.create_struct_field_from_column(field.column.unwrap(), field.number)) - .collect::>(); - - TypeStruct::new(self.query.name.clone(), StructType::Params, fields) - } - - fn result_struct(&self) -> TypeStruct { - let columns = self.query.columns.clone(); - let fields = columns - .into_iter() - .map(|col| self.create_struct_field_from_column(col, 0)) - .collect::>(); - - TypeStruct::new(self.query.name.clone(), StructType::Row, fields) - } - - pub fn build(&self) -> Vec> { - let query_const = TypeConst::new(self.query.name.clone(), self.query.text.clone()); - let params_struct = self.params_struct(); - let result_struct = self.result_struct(); - let query_method = TypeMethod::new( - self.query.name.clone(), - self.query.cmd.clone(), - query_const.clone(), - params_struct.clone(), - result_struct.clone(), - ); - - vec![ - Box::new(query_const), - Box::new(params_struct), - Box::new(result_struct), - Box::new(query_method), - ] +impl fmt::Display for PgDataType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) } } @@ -245,78 +250,264 @@ impl CodeBuilder { } impl CodeBuilder { - fn build_enums(&self) -> Vec { + fn build_enums(&self) -> Vec { let catalog = self.req.catalog.clone().unwrap(); - let schemas = catalog.schemas; - let enums = schemas + let enums = catalog + .schemas + .clone() .into_iter() - .map(|schema| { - schema - .enums - .into_iter() - .map(|e| TypeEnum::new(e.name, e.vals)) - .collect::>() + .filter_map(|schema| { + if schema.name == "pg_catalog" || schema.name == "information_schema" { + None + } else { + Some( + schema + .enums + .clone() + .into_iter() + .map(|e| { + let enum_name = + enum_name(&e.name, &schema.name, &catalog.default_schema); + + TypeEnum::new(enum_name, e.vals) + }) + .collect::>(), + ) + } }) .flatten() .collect::>(); enums .into_iter() - .map(|e| e.generate_code()) + .sorted_by(|a, b| Ord::cmp(&a.name(), &b.name())) .collect::>() } - fn build_queries_struct(&self, queries: Vec) -> TokenStream { - let data_type = DataType("postgres::Client".to_string()); - - quote! { - pub struct Queries { - client: #data_type - } + fn build_constants(&self) -> Vec { + let queries = self.req.queries.clone(); + queries + .into_iter() + .map(|q| TypeConst::new(q.name.clone(), q.text.clone())) + .collect::>() + } - impl Queries { - pub fn new(client: postgres::Client) -> Self { - Self { client } + fn build_structs(&self) -> Vec { + let catalog = self.req.catalog.clone().unwrap(); + let mut structs = catalog + .schemas + .clone() + .into_iter() + .filter_map(|schema| { + if schema.name == "pg_catalog" || schema.name == "information_schema" { + None + } else { + let type_struct = schema + .tables + .clone() + .into_iter() + .map(|table| { + let mut table_name = table.rel.clone().unwrap().name; + if schema.name != catalog.default_schema { + table_name = format!("{}_{table_name}", schema.name); + } + + let struct_name = pluralizer::pluralize(table_name.as_str(), 1, false); + let fields = table + .columns + .into_iter() + .map(|col| { + StructField::from( + col, + 0, + vec![schema.clone()], + catalog.default_schema.clone(), + ) + }) + .collect::>(); + + TypeStruct::new( + struct_name, + Some(plugin::Identifier { + catalog: "".to_string(), + schema: schema.name.clone(), + name: table.rel.unwrap().name, + }), + StructType::Default, + fields, + ) + }) + .collect::>(); + + Some(type_struct) } + }) + .flatten() + .collect::>(); - #(#queries)* - } - } - } + structs.sort_by(|a, b| Ord::cmp(&a.name(), &b.name())); - fn build_queries(&self) -> Vec { - let build_query = |query: plugin::Query| -> Vec> { - PartialsBuilder::new(query).build() - }; + structs + } - self.req + fn build_queries(&self, structs: Vec) -> (Vec, Vec) { + let catalog = self.req.catalog.clone().unwrap(); + let schemas = catalog.schemas.clone(); + let default_schema = catalog.default_schema.clone(); + let mut associated_structs = vec![]; + let mut queries = self + .req .queries .clone() .into_iter() - .map(build_query) - .flatten() - .into_group_map_by(|e| e.of_type() == "method") - .into_iter() - .map(|(is_query_method, values)| { - let tokens = values - .into_iter() - .map(|v| v.generate_code()) - .collect::>(); - if is_query_method { - self.build_queries_struct(tokens) + .filter_map(|query| { + if query.name.is_empty() || query.cmd.is_empty() { + None } else { - quote! { - #(#tokens)* + // Query parameter limit, get it from the options + let qpl = 3; + let mut arg: Option = None; + let params = query.params.clone(); + if params.len() == 1 && qpl != 0 { + let p = params.first().unwrap(); + let col = p.column.clone().unwrap(); + arg = Some(QueryValue::new( + escape(¶m_name(p)), + Some(PgDataType::from( + col.r#type.unwrap().name.as_str(), + schemas.clone(), + default_schema.clone(), + )), + None, + )); + } else if params.len() > 1 { + let fields = params + .into_iter() + .map(|field| { + StructField::from( + field.column.unwrap(), + field.number, + catalog.schemas.clone(), + catalog.default_schema.clone(), + ) + }) + .collect::>(); + + let type_struct = + TypeStruct::new(query.name.clone(), None, StructType::Params, fields); + arg = Some(QueryValue::new("arg", None, Some(type_struct.clone()))); + associated_structs.push(type_struct); } + + let columns = query.columns.clone(); + let mut ret: Option = None; + if columns.len() == 1 { + let col = columns.first().unwrap(); + ret = Some(QueryValue::new( + "", + Some(PgDataType::from( + col.r#type.clone().unwrap().name.as_str(), + schemas.clone(), + default_schema.clone(), + )), + None, + )); + } else if QueryCommand::from_str(&query.cmd) + .expect("invalid query command") + .has_return_value() + { + let found_struct = structs.clone().into_iter().find(|s| { + if s.fields.len() != columns.len() { + false + } else { + s.fields + .clone() + .into_iter() + .zip(columns.clone().into_iter()) + .enumerate() + .all(|(i, (field, c))| { + let same_name = field.name() + == column_name(c.name.to_string(), i as i32); + + let same_type = field.data_type.to_string() + == PgDataType::from( + c.r#type.clone().unwrap().name.as_str(), + schemas.clone(), + default_schema.clone(), + ) + .to_string(); + + let same_table = same_table( + c.table.clone(), + s.table.clone(), + default_schema.clone(), + ); + + same_name && same_type && same_table + }) + } + }); + + let gs = match found_struct { + None => { + let fields = columns + .into_iter() + .enumerate() + .map(|(i, col)| { + StructField::from( + col, + i as i32, + schemas.clone(), + default_schema.clone(), + ) + }) + .collect::>(); + + let type_struct = TypeStruct::new( + query.name.clone(), + None, + StructType::Row, + fields, + ); + associated_structs.push(type_struct.clone()); + type_struct + } + Some(gs) => gs, + }; + + ret = Some(QueryValue::new("", None, Some(gs))); + } + + Some(TypeQuery::new( + query.name.clone(), + query.cmd.clone(), + arg, + ret, + )) } }) - // .flatten() - .collect::>() + .collect::>(); + + queries.sort_by(|a, b| Ord::cmp(&a.name(), &b.name())); + associated_structs.sort_by(|a, b| Ord::cmp(&a.name(), &b.name())); + + (queries, associated_structs) } pub fn generate_code(&self) -> TokenStream { - let enums_tokens = self.build_enums(); - let queries = self.build_queries(); + let enums = self.build_enums(); + let constants = self.build_constants(); + let mut structs = self.build_structs(); + + let (queries, associated_structs) = self.build_queries(structs.clone()); + + structs.extend(associated_structs); + structs.sort_by(|a, b| Ord::cmp(&a.name(), &b.name())); + + // TODO: below + // let (enums, structs) = self.filter_unused_structs(enums, structs, queries); + // validate_structs_and_enums + let generated_comment = MultiLine( r#" /// @generated by the sqlc-gen-rust on sqlc-generate using sqlc.yaml @@ -325,11 +516,26 @@ impl CodeBuilder { ) .to_token_stream(); + let queries_impl = quote! { + pub struct Queries { + client: postgres::Client, + } + impl Queries { + pub fn new(client: postgres::Client) -> Self { + Self { client } + } + + #(#queries)* + } + }; + quote! { #generated_comment use postgres::{Error, Row}; - #(#enums_tokens)* - #(#queries)* + #(#constants)* + #(#enums)* + #(#structs)* + #queries_impl } } } diff --git a/sqlc-gen/src/codegen/type_const.rs b/sqlc-gen/src/codegen/type_const.rs index 51b26f3..dcdcb8b 100644 --- a/sqlc-gen/src/codegen/type_const.rs +++ b/sqlc-gen/src/codegen/type_const.rs @@ -1,4 +1,4 @@ -use super::{get_ident, CodePartial, MultiLineString}; +use super::{get_ident, MultiLineString}; use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; @@ -20,12 +20,6 @@ impl TypeConst { pub fn name(&self) -> String { self.name.to_case(Case::ScreamingSnake) } -} - -impl CodePartial for TypeConst { - fn of_type(&self) -> String { - "const".to_string() - } fn generate_code(&self) -> TokenStream { let ident_const = get_ident(&self.name()); @@ -37,6 +31,12 @@ impl CodePartial for TypeConst { } } +impl ToTokens for TypeConst { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(self.generate_code()); + } +} + #[cfg(test)] pub mod tests { use super::*; diff --git a/sqlc-gen/src/codegen/type_enum.rs b/sqlc-gen/src/codegen/type_enum.rs index e51bb59..6d88de5 100644 --- a/sqlc-gen/src/codegen/type_enum.rs +++ b/sqlc-gen/src/codegen/type_enum.rs @@ -1,7 +1,40 @@ -use super::{get_ident, CodePartial}; +use std::{char, collections::HashSet}; + +use super::get_ident; use convert_case::{Case, Casing}; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; + +pub fn enum_name(name: &str, schema_name: &str, default_schema: &str) -> String { + match schema_name == default_schema { + true => name.to_string(), + false => format!("{}_{name}", schema_name), + } + .to_case(Case::Pascal) +} + +fn enum_replacer(c: char) -> Option { + if ['-', '/', ':', '_'].contains(&c) { + Some('_') + } else if c.is_alphanumeric() { + Some(c) + } else { + None + } +} + +fn generate_enum_variant(i: usize, val: String, seen: &mut HashSet) -> TokenStream { + let mut value = val.chars().filter_map(enum_replacer).collect::(); + if seen.get(&value).is_some() || value.is_empty() { + value = format!("value_{}", i); + } + seen.insert(value.clone()); + let ident_variant = get_ident(&value.to_case(Case::Pascal)); + quote! { + #[postgres(name=#val)] + #ident_variant + } +} #[derive(Default, Debug, PartialEq)] pub struct TypeEnum { @@ -17,27 +50,23 @@ impl TypeEnum { } } - fn name(&self) -> String { + pub fn name(&self) -> String { self.name.to_case(Case::Pascal) } -} - -impl CodePartial for TypeEnum { - fn of_type(&self) -> String { - "enum".to_string() - } fn generate_code(&self) -> TokenStream { let ident_enum_name = get_ident(&self.name()); + let mut seen = HashSet::new(); let variants = self .values .clone() .into_iter() - .map(|val| get_ident(&val.to_case(Case::Pascal))) + .enumerate() + .map(|(i, val)| generate_enum_variant(i, val, &mut seen)) .collect::>(); quote! { - #[derive(Debug, Display)] + #[derive(Debug, Display, postgres_types::ToSql, postgres_type::FromSql)] pub enum #ident_enum_name { #(#variants),* } @@ -45,6 +74,12 @@ impl CodePartial for TypeEnum { } } +impl ToTokens for TypeEnum { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(self.generate_code().to_token_stream()); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/sqlc-gen/src/codegen/type_method.rs b/sqlc-gen/src/codegen/type_method.rs deleted file mode 100644 index 87c7eae..0000000 --- a/sqlc-gen/src/codegen/type_method.rs +++ /dev/null @@ -1,112 +0,0 @@ -use super::type_const::TypeConst; -use super::type_struct::TypeStruct; -use super::{get_ident, CodePartial}; -use convert_case::{Case, Casing}; -use proc_macro2::TokenStream; -use quote::quote; -use std::str::FromStr; -use strum_macros::EnumString; - -#[derive(Debug, PartialEq, EnumString)] -enum QueryCommand { - #[strum(serialize = ":one")] - One, - #[strum(serialize = ":many")] - Many, - #[strum(serialize = ":exec")] - Exec, - #[strum(serialize = ":execresult")] - ExecResult, - #[strum(serialize = ":execrows")] - ExecRows, - #[strum(serialize = ":execlastid")] - ExecLastId, -} - -#[derive(Default)] -pub struct TypeMethod { - name: String, - query_command: String, - query_const: TypeConst, - params_struct: TypeStruct, - row_struct: TypeStruct, -} - -impl TypeMethod { - pub fn new>( - name: S, - query_command: S, - query_const: TypeConst, - params_struct: TypeStruct, - row_struct: TypeStruct, - ) -> Self { - Self { - name: name.into(), - query_command: query_command.into(), - query_const, - params_struct, - row_struct, - } - } - - fn name(&self) -> String { - self.name.to_case(Case::Snake) - } - - fn query_command(&self) -> QueryCommand { - QueryCommand::from_str(&self.query_command).unwrap() - } -} - -impl CodePartial for TypeMethod { - fn of_type(&self) -> String { - "method".to_string() - } - - fn generate_code(&self) -> TokenStream { - let ident_name = get_ident(&self.name()); - let ident_row = get_ident(&self.row_struct.name()); - - let ident_const_name = get_ident(&self.query_const.name()); - let fields_list = self.params_struct.generate_fields_list(); - - let command = self.query_command(); - - let params_arg = self.params_struct.get_as_arg(Some("params")); - - let query_method = match command { - QueryCommand::One => { - quote! { - pub(crate) fn #ident_name(&mut self, #params_arg) -> anyhow::Result<#ident_row> { - let row = self.client.query_one(#ident_const_name, &[#fields_list])?; - Ok(sqlc_core::FromPostgresRow::from_row(&row)?) - } - } - } - QueryCommand::Many => { - quote! { - pub(crate) fn #ident_name(&mut self, #params_arg) -> anyhow::Result> { - let rows = self.client.query(#ident_const_name, &[#fields_list])?; - let mut result: Vec<#ident_row> = vec![]; - for row in rows { - result.push(sqlc_core::FromPostgresRow::from_row(&row)?); - } - - Ok(result) - } - } - } - QueryCommand::Exec - | QueryCommand::ExecRows - | QueryCommand::ExecResult - | QueryCommand::ExecLastId => quote! { - pub(crate) fn #ident_name(&mut self, #params_arg) -> anyhow::Result<()> { - self.client.execute(#ident_const_name, &[#fields_list])?; - Ok(()) - } - }, - }; - - quote! { #query_method } - } -} diff --git a/sqlc-gen/src/codegen/type_query.rs b/sqlc-gen/src/codegen/type_query.rs new file mode 100644 index 0000000..c36bb97 --- /dev/null +++ b/sqlc-gen/src/codegen/type_query.rs @@ -0,0 +1,196 @@ +use super::type_struct::TypeStruct; +use super::{get_ident, PgDataType}; +use convert_case::{Case, Casing}; +use core::panic; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use std::str::FromStr; +use strum_macros::EnumString; + +#[derive(Debug, PartialEq, EnumString)] +pub enum QueryCommand { + #[strum(serialize = ":one")] + One, + #[strum(serialize = ":many")] + Many, + #[strum(serialize = ":exec")] + Exec, + #[strum(serialize = ":execresult")] + ExecResult, + #[strum(serialize = ":execrows")] + ExecRows, + #[strum(serialize = ":execlastid")] + ExecLastId, +} + +impl QueryCommand { + pub fn has_return_value(&self) -> bool { + match *self { + Self::One | Self::Many => true, + _ => false, + } + } +} + +#[derive(Default, Debug, Clone)] +pub struct QueryValue { + name: String, + typ: Option, + type_struct: Option, +} + +impl QueryValue { + pub fn new>( + name: S, + typ: Option, + type_struct: Option, + ) -> Self { + Self { + name: name.into(), + typ, + type_struct, + } + } + + pub fn get_type(&self) -> String { + if let Some(typ) = &self.typ { + typ.to_string() + } else if let Some(type_struct) = self.type_struct.clone() { + type_struct.name() + } else { + panic!("QueryValue neither has `typ` specified nor `type_struct`"); + } + } + + pub fn generate_fields_list(&self) -> TokenStream { + let mut fields_list = quote! {}; + if self.typ.is_some() { + let ident_name = get_ident(&self.name.clone()); + fields_list = quote! { &#ident_name }; + } else if let Some(type_struct) = self.type_struct.clone() { + let ident_name = get_ident(&self.name.clone()); + let fields = type_struct + .fields + .clone() + .into_iter() + .map(|field| { + let ident_field_name = get_ident(&field.name()); + quote! { &#ident_name.#ident_field_name } + }) + .collect::>(); + fields_list = quote! { #(#fields),* } + } else { + // add panic if necessary + // panic!("QueryValue neither has `typ` specified nor `type_struct`"); + } + + fields_list + } +} + +impl ToTokens for QueryValue { + fn to_tokens(&self, tokens: &mut TokenStream) { + if !self.name.is_empty() { + let ident_type = get_ident(&self.get_type()); + let ident_name = get_ident(&self.name); + tokens.extend(quote! { + #ident_name: #ident_type + }); + } else if self.typ.is_some() || self.type_struct.is_some() { + let ident_type = get_ident(&self.get_type()); + tokens.extend(quote! { + #ident_type + }); + } else { + tokens.extend(quote! {}); + } + } +} + +#[derive(Default)] +pub struct TypeQuery { + name: String, + cmd: String, + arg: Option, + ret: Option, +} + +impl TypeQuery { + pub fn new>( + name: S, + cmd: S, + arg: Option, + ret: Option, + ) -> Self { + Self { + name: name.into(), + cmd: cmd.into(), + arg, + ret, + } + } + + fn constant_name(&self) -> String { + self.name.to_case(Case::ScreamingSnake) + } + + pub fn name(&self) -> String { + self.name.to_case(Case::Snake) + } + + fn command(&self) -> QueryCommand { + QueryCommand::from_str(&self.cmd).unwrap() + } + + fn generate_code(&self) -> TokenStream { + let ident_name = get_ident(&self.name()); + let ident_const_name = get_ident(&self.constant_name()); + let fields_list = self.arg.clone().unwrap_or_default().generate_fields_list(); + let command = self.command(); + let arg = self.arg.clone().unwrap_or_default(); + + let query_method = match command { + QueryCommand::One => { + let ret = self.ret.clone().unwrap_or_default(); + quote! { + pub(crate) fn #ident_name(&mut self, #arg) -> anyhow::Result<#ret> { + let row = self.client.query_one(#ident_const_name, &[#fields_list])?; + Ok(sqlc_core::FromPostgresRow::from_row(&row)?) + } + } + } + QueryCommand::Many => { + let ret = self.ret.clone().unwrap_or_default(); + quote! { + pub(crate) fn #ident_name(&mut self, #arg) -> anyhow::Result> { + let rows = self.client.query(#ident_const_name, &[#fields_list])?; + let mut result: Vec<#ret> = vec![]; + for row in rows { + result.push(sqlc_core::FromPostgresRow::from_row(&row)?); + } + + Ok(result) + } + } + } + QueryCommand::Exec + | QueryCommand::ExecRows + | QueryCommand::ExecResult + | QueryCommand::ExecLastId => quote! { + pub(crate) fn #ident_name(&mut self, #arg) -> anyhow::Result<()> { + self.client.execute(#ident_const_name, &[#fields_list])?; + Ok(()) + } + }, + // _ => quote! {}, + }; + + quote! { #query_method } + } +} + +impl ToTokens for TypeQuery { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(self.generate_code().to_token_stream()); + } +} diff --git a/sqlc-gen/src/codegen/type_struct.rs b/sqlc-gen/src/codegen/type_struct.rs index 560b17f..6b4601f 100644 --- a/sqlc-gen/src/codegen/type_struct.rs +++ b/sqlc-gen/src/codegen/type_struct.rs @@ -1,5 +1,7 @@ +use super::column_name; +use super::get_ident; +use super::plugin; use super::PgDataType; -use super::{get_ident, CodePartial}; use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; @@ -30,12 +32,23 @@ impl StructField { } } + pub fn from( + col: plugin::Column, + pos: i32, + schemas: Vec, + default_schema: String, + ) -> Self { + Self::new( + col.name, + pos, + PgDataType::from(&col.r#type.unwrap().name, schemas, default_schema), + col.is_array, + col.not_null, + ) + } + pub fn name(&self) -> String { - if !self.name.is_empty() { - self.name.to_case(Case::Snake) - } else { - format!("_{}", self.number) - } + column_name(self.name.clone(), self.number) } pub fn data_type(&self) -> TokenStream { @@ -59,21 +72,29 @@ impl StructField { } } +impl ToTokens for StructField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let field_name_ident = get_ident(&self.name()); + let field_type_ident = self.data_type(); + + tokens.extend(quote! { + pub #field_name_ident: #field_type_ident + }) + } +} + #[derive(Default, Debug, Clone)] pub enum StructType { #[default] + Default, Params, Row, - Queries, - Other { - prefix: String, - suffix: String, - }, } #[derive(Default, Debug, Clone)] pub struct TypeStruct { name: String, + pub table: Option, struct_type: StructType, pub fields: Vec, } @@ -81,92 +102,34 @@ pub struct TypeStruct { impl TypeStruct { pub fn new>( name: S, + table: Option, struct_type: StructType, fields: Vec, ) -> Self { Self { name: name.into(), + table, struct_type, fields, } } - pub fn exists(&self) -> bool { - self.fields.len() > 0 - } - - pub fn get_as_arg(&self, arg_name: Option<&str>) -> TokenStream { - let mut params_arg = quote! {}; - if let Some(name) = arg_name { - if self.exists() { - let ident_name = get_ident(name); - let ident_params = get_ident(&self.name()); - params_arg = quote! { - #ident_name: #ident_params - } - } - } - - params_arg - } - pub fn name(&self) -> String { let name = match &self.struct_type { + StructType::Default => format!("{}", self.name), StructType::Params => format!("{}Params", self.name), StructType::Row => format!("{}Row", self.name), - StructType::Queries => self.name.clone(), - StructType::Other { prefix, suffix } => { - format!("{}{}{}", prefix, self.name.to_case(Case::Pascal), suffix) - } }; name.to_case(Case::Pascal) } - pub fn generate_fields_list(&self) -> TokenStream { - if self.fields.len() == 0 { - quote! {} - } else { - let fields = self - .fields - .clone() - .into_iter() - .map(|field| { - let ident_field_name = get_ident(&field.name()); - quote! { ¶ms.#ident_field_name } - }) - .collect::>(); - - quote! { #(#fields),* } - } - } - - fn generate_struct_field_code(&self, field: StructField) -> TokenStream { - let field_name_ident = get_ident(&field.name()); - let field_type_ident = field.data_type(); - - quote! { - pub #field_name_ident: #field_type_ident - } - } -} - -impl CodePartial for TypeStruct { - fn of_type(&self) -> String { - "struct".to_string() - } - fn generate_code(&self) -> TokenStream { if self.fields.len() == 0 { quote! {} } else { let ident_struct = get_ident(&self.name()); - let fields = self - .fields - .clone() - .into_iter() - .map(|field| self.generate_struct_field_code(field)) - .collect::>(); + let fields = self.fields.clone().into_iter().collect::>(); quote! { #[derive(Clone, Debug, sqlc_derive::FromPostgresRow, PartialEq)] @@ -178,6 +141,12 @@ impl CodePartial for TypeStruct { } } +impl ToTokens for TypeStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(self.generate_code()); + } +} + #[cfg(test)] mod tests { use super::*; @@ -192,7 +161,7 @@ mod tests { StructField::new( name.unwrap_or(""), number.unwrap_or(0), - data_type.unwrap_or(PgDataType("pg_catalog.int4".to_string())), + data_type.unwrap_or(PgDataType::from("pg_catalog.int4", vec![], "".to_string())), is_array.unwrap_or_default(), not_null.unwrap_or_default(), ) @@ -259,6 +228,11 @@ mod tests { TypeStruct::new( name.unwrap_or("struct_name"), + Some(plugin::Identifier { + catalog: "".to_string(), + schema: "".to_string(), + name: "".to_string(), + }), struct_type.unwrap_or_default(), fields.unwrap_or(default_fields), ) @@ -274,48 +248,6 @@ mod tests { create_type_struct(None, Some(StructType::Row), None).name(), "StructNameRow".to_string() ); - assert_eq!( - create_type_struct(None, Some(StructType::Queries), None).name(), - "StructName".to_string() - ); - assert_eq!( - create_type_struct( - None, - Some(StructType::Other { - prefix: "A".to_string(), - suffix: "B".to_string() - }), - None - ) - .name(), - "AStructNameB".to_string() - ); - } - - #[test] - fn test_generate_field_code() { - let type_struct = create_type_struct(None, None, None); - assert_eq!( - type_struct - .generate_struct_field_code(type_struct.fields[0].clone()) - .to_string(), - quote! { - pub(crate) f_1: Option - } - .to_string() - ) - } - - #[test] - fn test_generate_fields_list() { - let type_struct = create_type_struct(None, None, None); - assert_eq!( - type_struct.generate_fields_list().to_string(), - quote! { - ¶ms.f_1, ¶ms.f_2, ¶ms.f, ¶ms.f_3, ¶ms._3 - } - .to_string() - ) } #[test]