diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index deb65a14..018192c4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -53,36 +53,6 @@ jobs: command: test args: --features protobuf - test-psql: - name: Test Suite (psql) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: actions-rs/cargo@v1 - with: - command: test - args: --features psql - - test-async-psql: - name: Test Suite (async-psql) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: actions-rs/cargo@v1 - with: - command: test - args: --features async-psql - test-all: name: Test Suite (all) runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 3681e776..dc9f5cf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "asn1rs" -version = "0.3.1" +version = "0.4.0" authors = ["Michael Watzko "] -edition = "2018" -description = "ASN.1 to Rust, Protobuf and SQL compiler/code generator. Supports ASN.1 UPER" -keywords = ["asn1", "uper", "protobuf", "sql", "compiler"] +edition = "2021" +description = "ASN.1 to Rust compiler with Protobuf code generator. Supports ASN.1 UPER" +keywords = ["asn1", "uper", "protobuf", "compiler"] categories = ["encoding", "parsing"] repository = "https://github.com/kellerkindt/asn1rs" license = "MIT/Apache-2.0" @@ -28,34 +28,23 @@ required-features = ["model"] [dependencies] backtrace = "0.3.9" -clap = "2.32.0" -codegen = "0.1.1" +clap = "2.34.0" +codegen = "0.1.3" byteorder = "1.2.4" serde = "1.0.115" serde_derive = "1.0.115" -# feature postgres -postgres = { version = "0.19.1", optional = true } - -# feature async-psql -tokio = { version = "1.8.1", optional = true, features = ["macros"] } -tokio-postgres = { version = "0.7.2", optional = true } -futures = { version = "0.3.4", optional = true } -bytes = { version = "1.0", optional = true } - # feature asn1rs-* asn1rs-model = { version = "0.3.0", path = "asn1rs-model", optional = true } asn1rs-macros = { version = "0.3.1", path = "asn1rs-macros", optional = true } [dev-dependencies] -syn = {version = "1.0.109", features = ["full", "visit", "extra-traits"] } +syn = { version = "1.0.109", features = ["full", "visit", "extra-traits"] } quote = "1.0.3" proc-macro2 = "1.0.10" [features] default = ["macros", "model"] -psql = ["asn1rs-model/psql", "postgres", "bytes"] -async-psql = ["asn1rs-model/async-psql", "tokio", "tokio-postgres", "futures", "bytes"] protobuf = ["asn1rs-model/protobuf"] macros = ["asn1rs-macros"] model = ["asn1rs-model"] diff --git a/README.md b/README.md index e379cd61..a85cd5cc 100644 --- a/README.md +++ b/README.md @@ -19,60 +19,60 @@ The crate can be used as standalone CLI binary or used as library through its AP ### Supported Features -| Feature | Parses | UPER | Protobuf | PSQL | Async PSQL | -| --------------------|:--------|:--------|:------------|:------------|:-----------| -| ...extensible | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `SEQUENCE OF` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `SET` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| ...extensible | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `SET OF` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `ENUMERATED` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| ...extensible | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `CHOICE` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| ...extensible | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `BIT STRING` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `OCTET STRING` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `UTF8String` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `IA5String` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `NumericString` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `PrintableString` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `VisibleString` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | -| `INTEGER` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| ...`A..B` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยฒ | โœ”๏ธ yesยฒ | โœ”๏ธ yesยฒ | -| ...`A..B,...` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยฒ | โœ”๏ธ yesยฒ | โœ”๏ธ yesยฒ | -| `BOOLEAN` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| `OPTIONAL` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | -| `DEFAULT ...` | โœ”๏ธ yes | | | | | -| ...`INTEGER` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| ...`*String` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| ...`BOOLEAN` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| ...`ENUMERATED` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| `NULL` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | โœ”๏ธ yesยน | โœ”๏ธ yesยน | -| `IMPORTS..FROM..;` | โœ”๏ธ yes | | | | | -| `ObjectIdentifiers` | โœ”๏ธ yes | | | | | -| Value References | โœ”๏ธ yes | | | | | -| ... in Range | โœ”๏ธ yes | | | | | -| ... in Size | โœ”๏ธ yes | | | | | -| ... in Default | โœ”๏ธ yes | | | | | -| `WITH COMPONENTS` | โœ”๏ธ yes | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | ๐Ÿ†— ignored | +| Feature | Parses | UPER | Protobuf | +| --------------------|:--------|:-------|:-----------| +| ...extensible | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `SEQUENCE OF` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `SET` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| ...extensible | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `SET OF` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `ENUMERATED` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| ...extensible | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `CHOICE` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| ...extensible | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `BIT STRING` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `OCTET STRING` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `UTF8String` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `IA5String` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `NumericString` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `PrintableString` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `VisibleString` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| ...`SIZE(A..B)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| ...`SIZE(A..B,...)` | โœ”๏ธ yes | โœ”๏ธ yes | ๐Ÿ†— ignored | +| `INTEGER` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| ...`A..B` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยฒ | +| ...`A..B,...` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยฒ | +| `BOOLEAN` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| `OPTIONAL` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yes | +| `DEFAULT ...` | โœ”๏ธ yes | | | +| ...`INTEGER` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| ...`*String` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| ...`BOOLEAN` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| ...`ENUMERATED` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| `NULL` | โœ”๏ธ yes | โœ”๏ธ yes | โœ”๏ธ yesยน | +| `IMPORTS..FROM..;` | โœ”๏ธ yes | | | +| `ObjectIdentifiers` | โœ”๏ธ yes | | | +| Value References | โœ”๏ธ yes | | | +| ... in Range | โœ”๏ธ yes | | | +| ... in Size | โœ”๏ธ yes | | | +| ... in Default | โœ”๏ธ yes | | | +| `WITH COMPONENTS` | โœ”๏ธ yes | | | - โœ”๏ธ yes: according to specification - โœ”๏ธ yesยน: different representation @@ -83,12 +83,6 @@ The crate can be used as standalone CLI binary or used as library through its AP - โŒ ub: undefined behavior - whatever seems reasonable to prevent compiler errors and somehow transmit the value - ๐ŸŸฅ error: fails to compile / translate -\*The legacy UPER Reader/Writer is deprecated and will be removed in version 0.3.0 - -**TLDR** -- The new (v0.2.0) UPER Reader/Writer supports all listed features -- Protobuf, sync&async PSQL ignore most constraints -- The legacy UPER Reader/Writer does not support all features (pre v0.2.0) #### Supported standards - [๐Ÿ“œ๏ธ ETSI TS 102 894-2 (PDF)](https://www.etsi.org/deliver/etsi_ts/102800_102899/10289402/01.02.01_60/ts_10289402v010201p.pdf) @@ -114,14 +108,10 @@ asn1rs -t rust directory/for/rust/files some.asn1 messages.asn1 asn1rs -t proto directory/for/protobuf/files some.asn1 messages.asn1 ``` -``` -asn1rs -t sql directory/for/sql/schema/files some.asn1 messages.asn1 -``` - ### Example: build.rs -The following example generates Rust, Protobuf and SQL files for all ```.asn1```-files in the ```asn/``` directory of a workspace. -While the generated Rust code is written to the ```src/``` directory, the Protobuf files are written to ```proto/``` and the SQL files are written to ```sql/ ```. +The following example generates Rust and Protobuf files for all ```.asn1```-files in the ```asn/``` directory of a workspace. +While the generated Rust code is written to the ```src/``` directory, the Protobuf files are written to ```proto/```. Additionally, in this example each generated Rust-Type also receives ```Serialize``` and ```Deserialize``` derive directives (```#[derive(Serialize, Deserialize)]```) for [serde](https://crates.io/crates/serde) integration. Sample ```build.rs``` file: @@ -129,7 +119,6 @@ Sample ```build.rs``` file: ```rust use asn1rs::converter::Converter; use asn1rs::gen::rust::RustCodeGenerator; -use asn1rs::gen::sql::SqlDefGenerator; pub fn main() { let mut converter = Converter::default(); @@ -159,7 +148,7 @@ pub fn main() { }); // writing the .rs files into src with serde_derive support - // feature flags decide whether additional code for protobuf and (async) psql is generated + // feature flags decide whether additional code for protobuf is generated if let Err(e) = converter.to_rust("src/", |generator: &mut RustCodeGenerator| { generator.add_global_derive("Serialize"); // Adds serde_derive support: #[derive(Serialize)] generator.add_global_derive("Deserialize"); // Adds serde_derive support: #[derive(Deserialize)] @@ -171,16 +160,6 @@ pub fn main() { if let Err(e) = converter.to_protobuf("../protocol/proto/") { panic!("Conversion to proto failed: {:?}", e); } - - // OPTIONAL: writing the .sql schema files to ../protocol/sql - if let Err(e) = converter.to_sql_with( - "../protocol/sql/", - SqlDefGenerator::default() // optional parameter, alternatively see Converter::to_sql(&self, &str) - .optimize_tables_for_write_performance() // optional - .wrap_primary_key_on_overflow(), // optional - ) { - panic!("Conversion to sql failed: {:?}", e); - } } ``` @@ -233,7 +212,7 @@ fn test_constraint_eq() { ``` -### Example: ASN.1-Definition converted to Rust, Protobuf and SQL +### Example: ASN.1-Definition converted to Rust and Protobuf Minimal example showcasing what is being generated from an ASN.1 definition: @@ -258,19 +237,6 @@ use asn1rs::prelude::*; pub struct Header { #[asn(integer(0..1209600000))] pub timestamp: u32, } - -// only with the feature "async-psql": Insert and query functions for async PostgreSQL -impl Header { - pub async fn apsql_retrieve_many(context: &apsql::Context<'_>, ids: &[i32]) -> Result, apsql::Error> { /*..*/ } - pub async fn apsql_retrieve(context: &apsql::Context<'_>, id: i32) -> Result { /*..*/ } - pub async fn apsql_load(context: &apsql::Context<'_>, row: &apsql::Row) -> Result { /*..*/ } - pub async fn apsql_insert(&self, context: &apsql::Context<'_>) -> Result { /*..*/ } -} - -// only with the feature "psql": Insert and query functions for non-async PostgreSQL -impl PsqlRepresentable for Header { /*..*/ } -impl PsqlInsertable for Header { /*..*/ } -impl PsqlQueryable for Header { /*..*/ } ``` The generated protobuf file (optional): @@ -284,88 +250,6 @@ message Header { } ``` -The generated SQL file (optional): - -```sql -DROP TABLE IF EXISTS Header CASCADE; - -CREATE TABLE Header ( - id SERIAL PRIMARY KEY, - timestamp INTEGER NOT NULL -); -``` - -#### Example: Usage of async postgres -NOTE: This requires the `async-psql` feature. - -Using async postgres allows the message - or the batched messages - to take advantage of [`pipelining`]. -This can provide a significant speedup for deep message types (personal experience this is around 26%) compared to the synchronous/blocking postgres implementation. - -```rust -use asn1rs::io::async_psql::*; -use tokio_postgres::NoTls; - -#[tokio::main] -async fn main() { - let transactional = true; - let (mut client, connection) = tokio_postgres::connect( - "host=localhost user=postgres application_name=psql_async_demo", - NoTls, - ) - .await - .expect("Failed to connect"); - - tokio::spawn(connection); - - - let context = if transactional { - let transaction = client - .transaction() - .await - .expect("Failed to open a new transaction"); - Cache::default().into_transaction_context(transaction) - } else { - Cache::default().into_client_context(client) - }; - - // using sample message from above - let message = Header { - timestamp: 1234, - }; - - // This issues all necessary insert statements on the given Context and - // because it does not require exclusive access to the context, you can - // issue multiple inserts and await them concurrently with for example - // tokio::try_join, futures::try_join_all or the like. - let id = message.apsql_insert(&context).await.expect("Insert failed"); - - // This disassembles the context, allowing the Transaction to be committed - // or rolled back. This operation also optimizes the read access to - // prepared statements of the Cache. If you do not want to do that, then call - // Context::split_unoptimized instead. - // You can also call `Cache::optimize()` manually to optimize the read access - // to the cached prepared statements. - // See the doc for more information about the usage of cached prepared statements - let (mut cache, transaction) = context.split(); - - // this is (logically) a nop on a non-transactional context - transaction.commit().await.expect("failed to commit"); - - let context = if transactional { - let transaction = client - .transaction() - .await - .expect("Failed to open a new transaction"); - Cache::default().into_transaction_context(transaction) - } else { - Cache::default().into_client_context(client) - }; - - let message_from_db = Header::apsql_retrieve(&context, id).await.expect("Failed to load"); - assert_eq!(message, message_from_db); -} -``` - #### Example: Raw uPER usage The module ```asn1rs::io``` exposes (de-)serializers and helpers for direct usage without ASN.1 definition: ```rust @@ -391,6 +275,26 @@ buffer.write_string("Still UTF8 Text").unwrap(); send_to_another_host(buffer): ``` +### Extending Rust Codegen + +```rust +use asn1rs::model::model::gen::rust::RustCodeGenerator; +use asn1rs::model::model::gen::rust::GeneratorSupplement; + +impl GeneratorSupplement for MyRustCodeGeneratorExtension { + // .. implement the trait +} + +fn main() { + let model: Model = ... ; + let (_file_name, file_content) = RustCodeGenerator::from(model) + .to_string_with_generators(&[&MyRustCodeGeneratorExtension]) + .into_iter() + .next() + .unwrap(); +} +``` + #### Finding deserialization error origins For a more detailed report on deserialization errors, enable the `descriptive-deserialize-errors` feature. @@ -401,7 +305,6 @@ but it will list intermediate results with the error origin and the current loca Things to do at some point in time (PRs are welcome) - generate a proper rust module hierarchy from the modules' object-identifier - - remove legacy rust+uper code generator (v0.3.0) - support ```#![no_std]``` - refactor / clean-up (rust) code-generators (most will be removed in v0.3.0) - support more encoding formats of ASN.1 (help is welcome!) diff --git a/asn1rs-macros/Cargo.toml b/asn1rs-macros/Cargo.toml index 9995a2ce..f3b7ce44 100644 --- a/asn1rs-macros/Cargo.toml +++ b/asn1rs-macros/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.1" authors = ["Michael Watzko "] edition = "2018" description = "Macros for asn1rs" -keywords = ["proc", "macro", "asn1", "protobuf", "sql"] +keywords = ["proc", "macro", "asn1", "protobuf"] categories = ["parsing"] repository = "https://github.com/kellerkindt/asn1rs-proc" license = "MIT/Apache-2.0" diff --git a/asn1rs-model/Cargo.toml b/asn1rs-model/Cargo.toml index 11d34048..2916a9bf 100644 --- a/asn1rs-model/Cargo.toml +++ b/asn1rs-model/Cargo.toml @@ -3,8 +3,8 @@ name = "asn1rs-model" version = "0.3.0" authors = ["Michael Watzko "] edition = "2018" -description = "Rust, Protobuf and SQL model definitions for asn1rs" -keywords = ["asn1", "protobuf", "sql", "model"] +description = "Rust and Protobuf model definitions for asn1rs" +keywords = ["asn1", "protobuf", "model"] categories = ["parsing"] repository = "https://github.com/kellerkindt/asn1rs" license = "MIT/Apache-2.0" @@ -21,9 +21,6 @@ strum_macros = "0.19.2" [features] default = [] -# RustCodeGenerator -> GeneratorSupplement -psql = [] -async-psql = [] protobuf = [] debug-proc-macro = [] generate-internal-docs = [] diff --git a/asn1rs-model/src/gen/mod.rs b/asn1rs-model/src/gen/mod.rs index 88666bd1..9d95c7bd 100644 --- a/asn1rs-model/src/gen/mod.rs +++ b/asn1rs-model/src/gen/mod.rs @@ -1,6 +1,5 @@ pub mod protobuf; pub mod rust; -pub mod sql; pub use self::rust::RustCodeGenerator; diff --git a/asn1rs-model/src/gen/rust/async_psql.rs b/asn1rs-model/src/gen/rust/async_psql.rs deleted file mode 100644 index 565e8b64..00000000 --- a/asn1rs-model/src/gen/rust/async_psql.rs +++ /dev/null @@ -1,845 +0,0 @@ -use crate::gen::rust::shared_psql::*; -use crate::gen::rust::GeneratorSupplement; -use crate::gen::RustCodeGenerator; -use crate::model::rust::PlainEnum; -use crate::model::rust::{DataEnum, Field}; -use crate::model::sql::{Sql, SqlType, ToSql}; -use crate::model::{Definition, Model, Rust, RustType}; -use codegen::{Block, Function, Impl, Scope}; - -const MODULE_NAME: &str = "apsql"; -const FN_PREFIX: &str = "apsql_"; - -#[allow(clippy::module_name_repetitions)] -pub struct AsyncPsqlInserter; - -impl GeneratorSupplement for AsyncPsqlInserter { - fn add_imports(&self, scope: &mut Scope) { - scope.import("asn1rs::io", &format!("async_psql as {}", MODULE_NAME)); - } - - fn impl_supplement(&self, _scope: &mut Scope, _definition: &Definition) {} - - fn extend_impl_of_struct<'a>(&self, name: &str, impl_scope: &mut Impl, fields: &[Field]) { - AsyncPsqlInserter::append_retrieve_many_for_container_type(name, impl_scope); - AsyncPsqlInserter::append_retrieve_for_container_type(name, impl_scope); - AsyncPsqlInserter::append_load_struct(name, impl_scope, fields); - - let fn_insert = create_insert_fn(impl_scope, true); - fn_insert.line(prepare_struct_insert_statement(name, fields)); - impl_insert_fn_content( - false, - true, - name, - || fields.iter().map(Field::fallback_representation), - fn_insert, - ); - } - - fn extend_impl_of_enum(&self, _name: &str, impl_scope: &mut Impl, _r_enum: &PlainEnum) { - AsyncPsqlInserter::append_retrieve_many_enums(impl_scope); - AsyncPsqlInserter::append_retrieve_enum(impl_scope); - - create_load_fn(impl_scope, true).line(format!( - "Self::{}(context, row.try_get::(0)?).await", - retrieve_fn_name() - )); - - let fn_insert = create_insert_fn(impl_scope, false).arg_self(); - fn_insert.line("Ok(self.value_index() as i32)"); - } - - fn extend_impl_of_data_enum(&self, name: &str, impl_scope: &mut Impl, enumeration: &DataEnum) { - Self::append_retrieve_many_for_container_type(name, impl_scope); - Self::append_retrieve_for_container_type(name, impl_scope); - - let fn_load = create_load_fn(impl_scope, true); - for (index, variant) in enumeration.variants().enumerate() { - let mut block = Block::new(&format!( - "if row.try_get::>({})?.is_some()", - index + 1 - )); - Self::append_load_field(false, name, &mut block, index, "value", variant.r#type()); - block.line(&format!("return Ok({}::{}(value));", name, variant.name())); - fn_load.push_block(block); - } - fn_load.line(format!("Err({}::Error::RowUnloadable)", MODULE_NAME)); - - let fn_insert = create_insert_fn(impl_scope, true); - fn_insert.line(&format!( - "let statement = context.prepared(\"{}\");", - data_enum_insert_statement(name, enumeration) - )); - let mut updated_variants = Vec::with_capacity(enumeration.len()); - for variant in enumeration.variants() { - let module_name = RustCodeGenerator::rust_module_name(variant.name()); - fn_insert.line(&format!( - "let {} = if let Self::{}(value) = self {{ Some({}value) }} else {{ None }};", - module_name, - variant.name(), - if variant.r#type().is_primitive() { - "*" - } else { - "" - } - )); - updated_variants.push(( - module_name, - RustType::Option(Box::new(variant.r#type().clone())), - )); - } - impl_insert_fn_content(false, false, name, || updated_variants.iter(), fn_insert); - } - - fn extend_impl_of_tuple(&self, name: &str, impl_scope: &mut Impl, definition: &RustType) { - let fields = [("0".to_string(), definition.clone())]; - - // append_default_retrieve_many_fn(impl_scope); - // let fn_retrieve = create_retrieve_fn(impl_scope); - Self::append_retrieve_many_for_container_type(name, impl_scope); - Self::append_retrieve_for_container_type(name, impl_scope); - Self::append_load_tuple(name, impl_scope, definition); - - let fn_insert = create_insert_fn(impl_scope, true); - fn_insert.line(&format!( - "let statement = context.prepared(\"{}\");", - tuple_struct_insert_statement(name) - )); - impl_insert_fn_content(true, true, name, || fields.iter(), fn_insert); - } -} - -fn impl_insert_fn_content<'a, I: ExactSizeIterator>( - is_tuple_struct: bool, - on_self: bool, - name: &str, - fields: impl Fn() -> I, - container: &mut impl Container, -) { - let mut params = Vec::default(); - let mut to_await = Vec::default(); - for insert in fields().filter_map(|(field_name, r_type)| { - let field_name = RustCodeGenerator::rust_field_name(field_name, true); - let field_name_as_variable = if field_name - .chars() - .next() - .map(|c| c.is_numeric()) - .unwrap_or(false) - { - Some(format!("value_{}", field_name)) - } else { - None - }; - let field_name_as_variable = field_name_as_variable.as_deref(); - - if r_type.is_vec() { - None - } else { - Some(insert_field( - is_tuple_struct, - on_self, - name, - container, - &field_name, - r_type, - field_name_as_variable, - )) - } - }) { - match insert { - FieldInsert::AsyncVec => { - panic!("Unexpected result, vecs should not appear here because filtered"); - } - FieldInsert::AsyncComplex(name) => { - to_await.push(name.clone()); - params.push(name.clone()); - } - FieldInsert::Primitive(name, _conversion) => { - params.push(name); - } - } - } - if to_await.is_empty() { - container.line("let statement = statement.await?;"); - } else { - to_await.push("statement".to_string()); - let elements = to_await.join(", "); - - container.line(&format!( - "let ({}) = {}::try_join!({})?;", - elements, MODULE_NAME, elements - )); - } - container.line(format!( - "let id: i32 = context.query_one(&statement, &[{}]).await?.get(0);", - params - .iter() - .map(|p| format!("&{}", p)) - .collect::>() - .join(", ") - )); - to_await.clear(); - for insert in fields().filter_map(|(field_name, r_type)| { - if r_type.is_vec() { - Some(insert_field( - is_tuple_struct, - on_self, - name, - container, - field_name, - r_type, - None, - )) - } else { - None - } - }) { - match insert { - FieldInsert::AsyncVec => {} // fine - FieldInsert::AsyncComplex(name) => { - to_await.push(name); - } - _ => panic!("Unexpected result, only vecs should appear here because filtered"), - } - } - if !to_await.is_empty() { - container.line(&format!( - "{}::try_join!({})?;", - MODULE_NAME, - to_await.join(", ") - )); - } - container.line("Ok(id)"); -} - -fn prepare_struct_insert_statement(name: &str, fields: &[Field]) -> String { - format!( - "let statement = context.prepared(\"{}\");", - struct_insert_statement(name, fields) - ) -} - -fn retrieve_many_fn_name() -> String { - format!("{}retrieve_many", FN_PREFIX) -} - -fn create_retrieve_many_fn(impl_scope: &mut Impl) -> &mut Function { - impl_scope - .new_fn(&retrieve_many_fn_name()) - .vis("pub async") - .arg("context", format!("&{}::Context<'_>", MODULE_NAME)) - .arg("ids", "&[i32]") - .ret(format!("Result, {}::Error>", MODULE_NAME)) -} - -fn retrieve_fn_name() -> String { - format!("{}retrieve", FN_PREFIX) -} - -fn create_retrieve_fn(impl_scope: &mut Impl, context_used: bool) -> &mut Function { - impl_scope - .new_fn(&retrieve_fn_name()) - .vis("pub async") - .arg( - if context_used { "context" } else { "_context" }, - format!("&{}::Context<'_>", MODULE_NAME), - ) - .arg("id", "i32") - .ret(format!("Result", MODULE_NAME)) -} - -fn load_fn_name() -> String { - format!("{}load", FN_PREFIX) -} - -fn create_load_fn(impl_scope: &mut Impl, context_used: bool) -> &mut Function { - impl_scope - .new_fn(&load_fn_name()) - .vis("pub async") - .arg( - if context_used { "context" } else { "_context" }, - format!("&{}::Context<'_>", MODULE_NAME), - ) - .arg("row", format!("&{}::Row", MODULE_NAME)) - .ret(format!("Result", MODULE_NAME)) -} - -fn insert_fn_name() -> String { - format!("{}insert", FN_PREFIX) -} - -fn create_insert_fn(impl_scope: &mut Impl, context_used: bool) -> &mut Function { - impl_scope - .new_fn(&insert_fn_name()) - .arg_ref_self() - .vis("pub async") - .arg( - if context_used { "context" } else { "_context" }, - format!("&{}::Context<'_>", MODULE_NAME), - ) - .ret(format!("Result", MODULE_NAME)) -} - -fn insert_field( - is_tuple_struct: bool, - on_self: bool, - struct_name: &str, - container: &mut impl Container, - field_name: &str, - r_type: &RustType, - field_name_as_variable: Option<&str>, -) -> FieldInsert { - if let RustType::Option(inner) = r_type { - insert_optional_field( - is_tuple_struct, - on_self, - struct_name, - container, - field_name, - inner, - field_name_as_variable, - ) - } else if r_type.is_vec() { - insert_vec_field( - is_tuple_struct, - on_self, - struct_name, - container, - field_name, - r_type, - ) - } else if Model::::is_primitive(r_type) { - insert_sql_primitive_field( - on_self, - container, - field_name, - r_type, - field_name_as_variable, - ) - } else { - insert_complex_field(on_self, container, field_name, field_name_as_variable) - } -} - -fn insert_optional_field( - is_tuple_struct: bool, - on_self: bool, - struct_name: &str, - container: &mut impl Container, - field_name: &str, - inner: &RustType, - field_name_as_variable: Option<&str>, -) -> FieldInsert { - insert_optional_field_maybe_async( - is_tuple_struct, - on_self, - struct_name, - container, - field_name, - inner, - field_name_as_variable, - false, - ) -} - -#[allow(clippy::too_many_arguments)] // sad but for that's the way it is, one could try to refactor this with a new type combining on_self, field_name, inner and field_name_as_variable into some reasonable type -fn insert_optional_field_maybe_async( - is_tuple_struct: bool, - on_self: bool, - struct_name: &str, - container: &mut impl Container, - field_name: &str, - inner: &RustType, - field_name_as_variable: Option<&str>, - call_await: bool, -) -> FieldInsert { - let variable_name = field_name_as_variable.unwrap_or(field_name).to_string(); - let mut block_async = Block::new(&format!("let {} = async", field_name)); - if inner.as_no_option().is_vec() { - let mut let_some = Block::new(&format!( - "if let Some({}) = {}self.{}", - variable_name, - if inner.as_no_option().is_primitive() { - "" - } else { - "&" - }, - field_name - )); - if let RustType::Option(next) = inner { - insert_optional_field_maybe_async( - is_tuple_struct, - on_self, - struct_name, - &mut let_some, - &variable_name, - next, - None, - true, - ); - } else { - // now is a vec - insert_vec_field( - is_tuple_struct, - false, - struct_name, - &mut let_some, - field_name, - inner, - ); - } - let_some.line("Ok(())"); - let_some.after(" else { Ok(()) }"); - block_async.push_block(let_some); - } else { - let mut block_some = Block::new(&format!( - "if let Some({}) = {}{}{}", - variable_name, - if inner.is_primitive() { "" } else { "&" }, - if on_self { "self." } else { "" }, - field_name - )); - let mut block_some_inner = Block::new("Ok(Some("); - if Model::::is_primitive(inner) { - if inner.is_primitive() && !inner.as_no_option().to_sql().to_rust().similar(inner) { - let conversion = inner.as_no_option().to_sql().to_rust(); - block_some_inner.line(&format!( - "{} as {}", - variable_name, - conversion.to_inner_type_string() - )); - } else { - block_some_inner.line(variable_name); - } - } else { - match insert_field( - is_tuple_struct, - false, - struct_name, - &mut block_some_inner, - &variable_name, - inner, - None, - ) { - FieldInsert::AsyncVec => {} - FieldInsert::AsyncComplex(name) => { - block_some_inner.line(&format!("{}.await?", name)); - } - FieldInsert::Primitive(name, _) => { - block_some_inner.line(name); - } - } - } - block_some_inner.after("))"); - block_some.push_block(block_some_inner); - block_some.after(" else { Ok(None) } "); - block_async.push_block(block_some); - } - if call_await { - block_async.after(".await?;"); - } else { - block_async.after(";"); - } - container.push_block(block_async); - FieldInsert::AsyncComplex(field_name.to_string()) -} - -fn insert_vec_field( - is_tuple_struct: bool, - on_self: bool, - struct_name: &str, - container: &mut impl Container, - field_name: &str, - r_type: &RustType, -) -> FieldInsert { - let mut many_insert = Block::new("async"); - let inner_primitive = Model::::is_primitive(r_type.as_inner_type()); - if inner_primitive { - many_insert.line(&format!( - "let inserted = &{}{};", - if on_self { "self." } else { "" }, - field_name, - )); - } else { - many_insert.line(&format!( - "let inserted = {}::try_join_all({}{}.iter().map(|v| v.{}(context)));", - MODULE_NAME, - if on_self { "self." } else { "" }, - field_name, - insert_fn_name() - )); - } - many_insert.line(&format!( - "let prepared = context.prepared(\"{}\");", - if is_tuple_struct { - list_entry_insert_statement(struct_name) - } else { - struct_list_entry_insert_statement(struct_name, field_name) - } - )); - if inner_primitive { - many_insert.line("let prepared = prepared.await?;"); - } else { - many_insert.line(&format!( - "let (inserted, prepared) = {}::try_join!(inserted, prepared)?;", - MODULE_NAME - )); - } - let conversion = !r_type.to_sql().to_rust().similar(r_type); - many_insert.line("let prepared = &prepared;"); - many_insert.line(&format!( - "{}::try_join_all(inserted.iter().map(|i| async move {{ context.query(prepared, &[&id, {}]).await }} )).await", - MODULE_NAME, - if conversion { format!("&(*i as {})", r_type.to_sql().to_rust().to_inner_type_string()) } else { "&i".to_string() } - )); - many_insert.after(".await?;"); - container.push_block(many_insert); - FieldInsert::AsyncVec -} - -fn insert_sql_primitive_field( - on_self: bool, - container: &mut impl Container, - field_name: &str, - r_type: &RustType, - field_name_as_variable: Option<&str>, -) -> FieldInsert { - let rerust = r_type.to_sql().to_rust(); - let conversion = if !rerust.similar(r_type) { - Some(rerust) - } else { - None - }; - let variable = field_name_as_variable.unwrap_or(field_name).to_string(); - container.line(&format!( - "let {} = {}{}{}{}{};", - variable, - if r_type.is_primitive() { "" } else { "&" }, - if on_self { "self." } else { "" }, - field_name, - conversion - .as_ref() - .filter(|_| r_type.is_primitive()) - .map(|_| " as ") - .unwrap_or_default(), - conversion - .as_ref() - .filter(|_| r_type.is_primitive()) - .map(|r| r.to_string()) - .unwrap_or_default(), - )); - FieldInsert::Primitive(variable, conversion) -} - -fn insert_complex_field( - on_self: bool, - container: &mut impl Container, - field_name: &str, - field_name_as_variable: Option<&str>, -) -> FieldInsert { - let variable_name = field_name_as_variable.unwrap_or(field_name).to_string(); - container.line(&format!( - "let {} = {}{}.{}(context);", - variable_name, - if on_self { "self." } else { "" }, - field_name, - insert_fn_name() - )); - FieldInsert::AsyncComplex(variable_name) -} - -enum FieldInsert { - AsyncVec, - AsyncComplex(String), - Primitive(String, Option), -} - -pub trait Container { - fn line(&mut self, line: T); - fn push_block(&mut self, block: Block); -} - -impl Container for Function { - fn line(&mut self, line: T) { - Function::line(self, line); - } - - fn push_block(&mut self, block: Block) { - Function::push_block(self, block); - } -} - -impl Container for Block { - fn line(&mut self, line: T) { - Block::line(self, line); - } - - fn push_block(&mut self, block: Block) { - Block::push_block(self, block); - } -} - -impl AsyncPsqlInserter { - fn append_retrieve_many_enums(impl_scope: &mut Impl) { - let fn_retrieve_many = create_retrieve_many_fn(impl_scope); - fn_retrieve_many.line("let mut result = Vec::with_capacity(ids.len());"); - fn_retrieve_many.line(format!("for id in ids {{ result.push(Self::{}(context, *id).await?); }} // awaiting here is fine because {} returns immediately", retrieve_fn_name(), retrieve_fn_name())); - fn_retrieve_many.line("Ok(result)"); - } - - fn append_retrieve_enum(impl_scope: &mut Impl) { - create_retrieve_fn(impl_scope, false).line(format!( - "Self::variant(id as usize).ok_or({}::Error::UnexpectedVariant(id as usize))", - MODULE_NAME, - )); - } - - fn append_retrieve_many_for_container_type(name: &str, impl_scope: &mut Impl) { - let fn_retrieve_many = create_retrieve_many_fn(impl_scope); - fn_retrieve_many.line(format!( - "let prepared = context.prepared(\"{}\").await?;", - select_statement_many(name) - )); - fn_retrieve_many.line("let rows = context.query(&prepared, &[&ids]).await?;"); - fn_retrieve_many.line(format!( - "{}::try_join_all(rows.iter().map(|row| Self::{}(context, row))).await", - MODULE_NAME, - load_fn_name() - )); - } - - fn append_retrieve_for_container_type(name: &str, impl_scope: &mut Impl) { - let fn_retrieve = create_retrieve_fn(impl_scope, true); - fn_retrieve.line(format!( - "let prepared = context.prepared(\"{}\").await?;", - select_statement_single(name) - )); - fn_retrieve.line("let row = context.query_opt(&prepared, &[&id]).await?;"); - fn_retrieve.line(format!( - "let row = row.ok_or({}::Error::NoEntryFoundForId(id))?;", - MODULE_NAME - )); - fn_retrieve.line(format!("Self::{}(context, &row).await", load_fn_name())); - } - - fn append_load_struct(name: &str, impl_scope: &mut Impl, fields: &[Field]) { - let fn_load = create_load_fn( - impl_scope, - fields.iter().any(|field| { - !Model::::is_primitive(field.r#type()) - || Model::::has_no_column_in_embedded_struct(field.r#type().as_no_option()) - }), - ); - for (index, field) in fields.iter().enumerate() { - AsyncPsqlInserter::append_load_field( - false, - name, - fn_load, - index, - field.name(), - field.r#type(), - ); - } - - let mut result_block = Block::new("Ok(Self"); - for field in fields { - result_block.line(format!( - "{},", - RustCodeGenerator::rust_field_name(field.name(), true) - )); - } - result_block.after(")"); - fn_load.push_block(result_block); - } - - fn append_load_tuple(name: &str, impl_scope: &mut Impl, field_type: &RustType) { - let fn_load = create_load_fn(impl_scope, true); - AsyncPsqlInserter::append_load_field(true, name, fn_load, 0, "value", field_type); - fn_load.line("Ok(Self(value))"); - } - - fn append_load_field( - is_tuple_struct: bool, - struct_name: &str, - container: &mut impl Container, - index: usize, - field: &str, - f_type: &RustType, - ) { - let sql = f_type.to_sql(); - if let RustType::Option(inner) = f_type { - AsyncPsqlInserter::append_load_option_field( - is_tuple_struct, - struct_name, - container, - index, - field, - f_type, - &sql, - &**inner, - ) - } else if let RustType::Vec(inner, _size, _ordering) = f_type { - AsyncPsqlInserter::append_load_vec_field( - is_tuple_struct, - struct_name, - container, - field, - f_type, - &sql, - &**inner, - ) - } else if Model::::is_primitive(f_type) { - AsyncPsqlInserter::append_load_primitive_field(container, index, field, f_type, &sql); - } else { - container.line(format!( - "let {} = row.try_get::({})?;", - RustCodeGenerator::rust_field_name(field, true), - index + 1, - )); - Self::append_load_complex_field(container, field, f_type) - } - } - - fn append_load_primitive_field( - container: &mut impl Container, - index: usize, - field: &str, - f_type: &RustType, - sql: &SqlType, - ) { - container.line(format!( - "let {} = row.try_get::({})?{};", - RustCodeGenerator::rust_field_name(field, true), - sql.to_rust().to_inner_type_string(), - index + 1, - if !sql.to_rust().similar(f_type) { - format!(" as {}", f_type.to_inner_type_string()) - } else { - String::default() - } - )); - } - - fn append_load_vec_field( - is_tuple_struct: bool, - struct_name: &str, - container: &mut impl Container, - field: &str, - f_type: &RustType, - sql: &SqlType, - inner: &RustType, - ) { - if Model::::is_primitive(inner) { - container.line(format!( - "let prepared = context.prepared(\"{}\").await?;", - if is_tuple_struct { - list_entry_query_statement(struct_name, inner) - } else { - struct_list_entry_select_value_statement(struct_name, field) - } - )); - container.line( - "let rows = context.query(&prepared, &[&row.try_get::(0)?]).await?;", - ); - container.line(format!( - "let mut {} = Vec::with_capacity(rows.len());", - RustCodeGenerator::rust_field_name(field, true) - )); - container.line(format!( - "for row in rows {{ {}.push(row.try_get::(0)?{}); }}", - RustCodeGenerator::rust_field_name(field, true), - inner.to_sql().to_rust().to_inner_type_string(), - if !sql.to_rust().similar(f_type) { - format!(" as {}", f_type.to_inner_type_string()) - } else { - String::default() - } - )); - } else { - container.line(format!( - "let prepared = context.prepared(\"{}\").await?;", - if is_tuple_struct { - list_entry_query_statement(struct_name, inner.as_inner_type()) - } else { - struct_list_entry_select_referenced_value_statement( - struct_name, - field, - &inner.to_inner_type_string(), - ) - } - )); - container.line( - "let rows = context.query(&prepared, &[&row.try_get::(0)?]).await?;", - ); - container.line(format!( - "let mut {} = Vec::with_capacity(rows.len());", - RustCodeGenerator::rust_field_name(field, true) - )); - - container.line(format!( - "for row in rows {{ {}.push({}::{}(context, &row).await?); }}", - RustCodeGenerator::rust_field_name(field, true), - inner.to_inner_type_string(), - load_fn_name(), - )); - } - } - - #[allow(clippy::too_many_arguments)] // for now this is fine-ish - fn append_load_option_field( - is_tuple_struct: bool, - struct_name: &str, - container: &mut impl Container, - index: usize, - field: &str, - f_type: &RustType, - sql: &SqlType, - inner: &RustType, - ) { - if inner.is_vec() { - Self::append_load_field(is_tuple_struct, struct_name, container, index, field, inner); - container.line(format!( - "let {} = if {}.is_empty() {{ None }} else {{ Some({}) }};", - RustCodeGenerator::rust_field_name(field, true), - RustCodeGenerator::rust_field_name(field, true), - RustCodeGenerator::rust_field_name(field, true), - )) - } else if Model::::is_primitive(inner) { - container.line(format!( - "let {} = row.try_get::>({})?{};", - RustCodeGenerator::rust_field_name(field, true), - sql.to_rust().as_no_option().to_inner_type_string(), - index + 1, - if !sql.to_rust().similar(f_type) { - format!(".map(|v| v as {})", inner.to_inner_type_string()) - } else { - String::default() - } - )); - } else { - let mut block = Block::new(&format!( - "let {} = if let Some({}) = row.try_get::>({})?", - RustCodeGenerator::rust_field_name(field, false), - RustCodeGenerator::rust_field_name(field, false), - index + 1 - )); - Self::append_load_complex_field(&mut block, field, inner); - block.line(format!( - "Some({})", - RustCodeGenerator::rust_field_name(field, false) - )); - block.after(" else { None };"); - container.push_block(block); - } - } - - fn append_load_complex_field(container: &mut impl Container, field: &str, f_type: &RustType) { - container.line(format!( - "let {} = {}::{}(context, {}).await?;", - RustCodeGenerator::rust_field_name(field, true), - f_type.to_inner_type_string(), - retrieve_fn_name(), - RustCodeGenerator::rust_field_name(field, true), - )); - } -} diff --git a/asn1rs-model/src/gen/rust/mod.rs b/asn1rs-model/src/gen/rust/mod.rs index 83394add..7abb4cd6 100644 --- a/asn1rs-model/src/gen/rust/mod.rs +++ b/asn1rs-model/src/gen/rust/mod.rs @@ -1,14 +1,5 @@ pub mod walker; -#[cfg(feature = "psql")] -pub mod psql; - -#[cfg(feature = "async-psql")] -pub mod async_psql; - -#[cfg(any(feature = "psql", feature = "async-psql"))] -pub(crate) mod shared_psql; - use crate::gen::Generator; use crate::model::rust::{DataEnum, Field}; use crate::model::rust::{EncodingOrdering, PlainEnum}; @@ -26,12 +17,6 @@ use std::borrow::Cow; use std::convert::Infallible; use std::fmt::Display; -#[cfg(feature = "psql")] -use self::psql::PsqlInserter; - -#[cfg(feature = "async-psql")] -use self::async_psql::AsyncPsqlInserter; - const KEYWORDS: [&str; 9] = [ "use", "mod", "const", "type", "pub", "enum", "struct", "impl", "trait", ]; @@ -94,13 +79,9 @@ impl Generator for RustCodeGenerator { &mut self.models[..] } + #[inline] fn to_string(&self) -> Result, Self::Error> { - Ok(self.to_string_with_generators(&[ - #[cfg(feature = "psql")] - &PsqlInserter, - #[cfg(feature = "async-psql")] - &AsyncPsqlInserter, - ])) + Ok(self.to_string_without_generators()) } } @@ -138,11 +119,10 @@ impl RustCodeGenerator { &self, generators: &[&dyn GeneratorSupplement], ) -> Vec<(String, String)> { - let mut files = Vec::new(); - for model in &self.models { - files.push(self.model_to_file(model, generators)); - } - files + self.models + .iter() + .map(|model| self.model_to_file(model, generators)) + .collect() } pub fn model_to_file( diff --git a/asn1rs-model/src/gen/rust/psql.rs b/asn1rs-model/src/gen/rust/psql.rs deleted file mode 100644 index 66389d02..00000000 --- a/asn1rs-model/src/gen/rust/psql.rs +++ /dev/null @@ -1,770 +0,0 @@ -use crate::gen::rust::shared_psql::*; -use crate::gen::rust::GeneratorSupplement; -use crate::gen::rust::RustCodeGenerator; -use crate::model::rust::PlainEnum; -use crate::model::rust::{DataEnum, Field}; -use crate::model::sql::Sql; -use crate::model::sql::ToSql; -use crate::model::Definition; -use crate::model::Model; -use crate::model::Rust; -use crate::model::RustType; -use codegen::Block; -use codegen::Function; -use codegen::Impl; -use codegen::Scope; - -const ERROR_TYPE: &str = "PsqlError"; -const ROW_TYPE: &str = "PsqlRow"; -const TRAIT_PSQL_REPRESENTABLE: &str = "PsqlRepresentable"; -const TRAIT_PSQL_INSERTABLE: &str = "PsqlInsertable"; -const TRAIT_PSQL_QUERYABLE: &str = "PsqlQueryable"; - -/// The tuple implementation has general flaws in where in which -/// it was initially designed to represent lists only. Later extensions -/// patch only certain uncovered edge-cases and are hacked into place. -/// -/// The CHOICE/data-enum implementation also had/s quite a few flaws -#[allow(clippy::module_name_repetitions)] -pub struct PsqlInserter; - -impl GeneratorSupplement for PsqlInserter { - fn add_imports(&self, scope: &mut Scope) { - scope.import("asn1rs::io::psql", &format!("Error as {}", ERROR_TYPE)); - scope.import("asn1rs::io::psql", &format!("Row as {}", ROW_TYPE)); - scope.import( - "asn1rs::io::psql", - &format!("Representable as {}", TRAIT_PSQL_REPRESENTABLE), - ); - scope.import( - "asn1rs::io::psql", - &format!("Insertable as {}", TRAIT_PSQL_INSERTABLE), - ); - scope.import( - "asn1rs::io::psql", - &format!("Queryable as {}", TRAIT_PSQL_QUERYABLE), - ); - scope.import("asn1rs::io::psql", "Transaction"); - } - - fn impl_supplement(&self, scope: &mut Scope, definition: &Definition) { - Self::impl_representable(scope, definition); - Self::impl_insertable(scope, definition); - Self::impl_queryable(scope, definition); - } -} - -impl PsqlInserter { - fn new_representable_impl<'a>(scope: &'a mut Scope, name: &str) -> &'a mut Impl { - scope.new_impl(name).impl_trait(TRAIT_PSQL_REPRESENTABLE) - } - - fn new_insertable_impl<'a>(scope: &'a mut Scope, name: &str) -> &'a mut Impl { - scope.new_impl(name).impl_trait(TRAIT_PSQL_INSERTABLE) - } - - fn new_queryable_impl<'a>(scope: &'a mut Scope, name: &str) -> &'a mut Impl { - scope.new_impl(name).impl_trait(TRAIT_PSQL_QUERYABLE) - } - - fn impl_representable(scope: &mut Scope, Definition(name, _rust): &Definition) { - let implementation = Self::new_representable_impl(scope, name); - Self::impl_table_name(Self::new_table_name_fn(implementation), name); - } - - fn impl_insertable(scope: &mut Scope, Definition(name, rust): &Definition) { - let implementation = Self::new_insertable_impl(scope, name); - match rust { - Rust::Struct { - fields, - tag: _, - extension_after: _, - ordering: _, - } => { - Self::impl_struct_insert_statement( - Self::new_insert_statement_fn(implementation), - name, - fields, - ); - Self::impl_struct_insert_fn( - Self::new_insert_fn(implementation, true), - name, - fields.iter().map(Field::fallback_representation), - ); - } - Rust::DataEnum(enumeration) => { - Self::impl_data_enum_insert_statement( - Self::new_insert_statement_fn(implementation), - name, - enumeration, - ); - Self::impl_data_enum_insert_fn( - Self::new_insert_fn(implementation, true), - name, - enumeration, - ); - } - Rust::Enum(_) => { - Self::impl_enum_insert_statement(Self::new_insert_statement_fn(implementation)); - Self::impl_enum_insert_fn(Self::new_insert_fn(implementation, false)); - } - Rust::TupleStruct { r#type: rust, .. } => { - Self::impl_tuple_insert_statement( - Self::new_insert_statement_fn(implementation), - name, - ); - Self::impl_tuple_insert_fn(Self::new_insert_fn(implementation, true), name, rust); - } - } - } - - fn new_table_name_fn(implementation: &mut Impl) -> &mut Function { - implementation - .new_fn("table_name") - .arg_ref_self() - .ret("&'static str") - } - - fn new_insert_statement_fn(implementation: &mut Impl) -> &mut Function { - implementation - .new_fn("insert_statement") - .arg_ref_self() - .ret("&'static str") - } - - fn new_insert_fn(implementation: &mut Impl, using_transaction: bool) -> &mut Function { - implementation - .new_fn("insert_with") - .arg_ref_self() - .arg( - if using_transaction { - "transaction" - } else { - "_" - }, - "&mut Transaction", - ) - .ret(&format!("Result", ERROR_TYPE)) - } - - fn impl_table_name(function: &mut Function, name: &str) { - function.line(&format!("\"{}\"", name)); - } - - fn impl_struct_insert_statement(function: &mut Function, name: &str, fields: &[Field]) { - if fields.is_empty() { - Self::impl_tuple_insert_statement(function, name); - } else { - function.line(&format!("\"{}\"", struct_insert_statement(name, fields))); - } - } - - fn impl_data_enum_insert_statement( - function: &mut Function, - name: &str, - enumeration: &DataEnum, - ) { - if enumeration.is_empty() { - Self::impl_tuple_insert_statement(function, name); - } else { - function.line(&format!( - "\"{}\"", - data_enum_insert_statement(name, enumeration) - )); - } - } - - fn impl_enum_insert_statement(function: &mut Function) { - function.line("\"\""); - } - - fn impl_tuple_insert_statement(function: &mut Function, name: &str) { - function.line(&format!("\"{}\"", tuple_struct_insert_statement(name))); - } - - fn impl_struct_insert_fn<'a>( - function: &mut Function, - struct_name: &str, - fields: impl ExactSizeIterator, - ) { - let mut variables = Vec::with_capacity(fields.len()); - let mut vecs = Vec::new(); - for (name, rust) in fields { - let name = RustCodeGenerator::rust_field_name(name, true); - let sql_primitive = Model::::is_primitive(rust); - let is_vec = rust.is_vec(); - - if is_vec { - vecs.push((name.clone(), rust.clone())); - continue; - } else { - variables.push(format!("&{}", name)); - } - if sql_primitive { - function.line(&format!( - "let {} = {}self.{};", - name, - if rust.is_primitive() { "" } else { "&" }, - name, - )); - if let Some(wrap) = Self::wrap_for_insert_in_as_or_from_if_required(&name, rust) { - function.line(format!("let {} = {};", name, wrap,)); - } - } else if !is_vec { - function.line(&format!( - "let {} = {}self.{}{}.insert_with(transaction)?{};", - name, - if let RustType::Option(_) = rust { - "if let Some(ref value) = " - } else { - "" - }, - name, - if let RustType::Option(_) = rust { - " { Some(value" - } else { - "" - }, - if let RustType::Option(_) = rust { - ") } else { None }" - } else { - "" - } - )); - }; - } - function.line("let statement = transaction.prepare(self.insert_statement())?;"); - function.line(format!( - "let result = transaction.query(&statement, &[{}])?;", - variables.join(", ") - )); - if vecs.is_empty() { - function.line(&format!( - "{}::expect_returned_index_in_rows(&result)", - ERROR_TYPE - )); - } else { - function.line(&format!( - "let index = {}::expect_returned_index_in_rows(&result)?;", - ERROR_TYPE - )); - for (name, field) in vecs { - let mut block = Block::new(""); - block.line(&format!( - "let statement = transaction.prepare(\"{}\")?;", - &struct_list_entry_insert_statement(struct_name, &name), - )); - block.push_block(Self::list_insert_for_each(&name, &field, "index")); - function.push_block(block); - } - function.line("Ok(index)"); - } - } - - fn wrap_for_insert_in_as_or_from_if_required(name: &str, rust: &RustType) -> Option { - let inner_sql = rust.as_inner_type().to_sql(); - let inner_rust = rust.as_inner_type(); - if inner_sql.to_rust().as_inner_type().similar(inner_rust) { - None - } else { - Some({ - let rust_from_sql = inner_sql.to_rust().into_inner_type(); - let as_target = rust_from_sql.to_string(); - let use_from_instead_of_as = - rust_from_sql.is_primitive() && rust_from_sql > *inner_rust; - if let RustType::Option(_) = rust { - if use_from_instead_of_as { - format!("{}.map({}::from)", name, as_target) - } else { - format!("{}.map(|v| v as {})", name, as_target) - } - } else if use_from_instead_of_as { - format!("{}::from({})", as_target, name) - } else { - format!("{} as {}", name, as_target) - } - }) - } - } - - fn wrap_for_query_in_as_or_from_if_required(name: &str, rust: &RustType) -> Option { - let inner_sql = rust.as_inner_type().to_sql(); - let inner_rust = rust.as_inner_type(); - if inner_sql.to_rust().as_inner_type().similar(inner_rust) { - None - } else { - Some({ - let as_target = inner_rust.to_string(); - if let RustType::Option(_) = rust { - format!("{}.map(|v| v as {})", name, as_target) - } else { - format!("{} as {}", name, as_target) - } - }) - } - } - - fn impl_data_enum_insert_fn(function: &mut Function, name: &str, enumeration: &DataEnum) { - let mut variables = Vec::with_capacity(enumeration.len()); - for variant in enumeration.variants() { - let variable = RustCodeGenerator::rust_field_name( - &RustCodeGenerator::rust_module_name(variant.name()), - true, - ); - let sql_primitive = Model::::is_primitive(variant.r#type()); - variables.push(format!("&{}", variable)); - let mut block_if = Block::new(&format!( - "let {} = if let {}::{}(value) = self", - variable, - name, - variant.name() - )); - - if sql_primitive { - block_if.line(format!( - "Some({})", - Self::wrap_for_insert_in_as_or_from_if_required("*value", variant.r#type()) - .unwrap_or_else(|| "value".to_string()) - )); - } else { - block_if.line("Some(value.insert_with(transaction)?)"); - }; - - block_if.after(" else { None };"); - function.push_block(block_if); - } - function.line("let statement = transaction.prepare(self.insert_statement())?;"); - function.line(format!( - "let result = transaction.query(&statement, &[{}])?;", - variables.join(", ") - )); - function.line(&format!( - "{}::expect_returned_index_in_rows(&result)", - ERROR_TYPE - )); - } - - fn impl_enum_insert_fn(function: &mut Function) { - function.line("Ok(self.value_index() as i32)"); - } - - fn impl_tuple_insert_fn(function: &mut Function, name: &str, rust: &RustType) { - function.line("let statement = transaction.prepare(self.insert_statement())?;"); - function.line("let result = transaction.query(&statement, &[])?;"); - function.line(&format!( - "let list = {}::expect_returned_index_in_rows(&result)?;", - ERROR_TYPE - )); - function.line(format!( - "let statement = transaction.prepare(\"{}\")?;", - list_entry_insert_statement(name) - )); - function.push_block(Self::list_insert_for_each("0", rust, "list")); - function.line("Ok(list)"); - } - - /// Expects a variable called `statement` to be reachable and usable - fn list_insert_for_each(name: &str, rust: &RustType, list: &str) -> Block { - let mut block_for = if rust.as_no_option().is_vec() { - Block::new(&if let RustType::Option(_) = rust { - format!("for value in self.{}.iter().flatten()", name) - } else { - format!("for value in &self.{}", name) - }) - } else if rust.is_option() { - Block::new(&format!("if let Some(value) = &self.{}", name)) - } else { - let mut block = Block::new(""); - block.line(format!("let value = &self.{};", name)); - block - }; - if Model::::is_primitive(rust) { - let inner_sql = rust.as_inner_type().to_sql(); - let inner_rust = rust.as_inner_type(); - if !inner_sql.to_rust().as_inner_type().similar(inner_rust) { - let rust_from_sql = inner_sql.to_rust().into_inner_type(); - let as_target = rust_from_sql.to_string(); - let use_from_instead_of_as = - rust_from_sql.is_primitive() && rust_from_sql > *inner_rust; - block_for.line(format!( - "let value = {};", - if use_from_instead_of_as { - format!("{}::from(*value)", as_target) - } else { - format!("(*value) as {}", as_target) - }, - )); - } - } else { - block_for.line("let value = value.insert_with(transaction)?;"); - } - block_for.line(format!( - "transaction.execute(&statement, &[&{}, &value])?;", - list - )); - block_for - } - - fn impl_queryable(scope: &mut Scope, Definition(name, rust): &Definition) { - let implementation = Self::new_queryable_impl(scope, name); - match rust { - Rust::Struct { - fields, - tag: _, - extension_after: _, - ordering: _, - } => { - Self::impl_query_statement(Self::new_query_statement_fn(implementation), name); - Self::impl_struct_query_fn(Self::new_query_fn(implementation, true), name); - Self::impl_struct_load_fn( - Self::new_load_fn( - implementation, - fields.iter().any(|field| { - !Model::::is_primitive(field.r#type()) || field.r#type().is_vec() - }), - ), - name, - fields.iter().map(Field::fallback_representation), - ); - } - Rust::DataEnum(enumeration) => { - Self::impl_query_statement(Self::new_query_statement_fn(implementation), name); - Self::impl_data_enum_query_fn(Self::new_query_fn(implementation, true), name); - Self::impl_data_enum_load_fn( - Self::new_load_fn(implementation, true), - name, - enumeration, - ); - } - Rust::Enum(r_enum) => { - Self::impl_empty_query_statement(Self::new_query_statement_fn(implementation)); - Self::impl_enum_query_fn(Self::new_query_fn(implementation, false), name, r_enum); - Self::impl_enum_load_fn(Self::new_load_fn(implementation, true), name); - } - Rust::TupleStruct { r#type: rust, .. } => { - Self::impl_tupl_query_statement( - Self::new_query_statement_fn(implementation), - name, - rust, - ); - Self::impl_tupl_struct_query_fn( - Self::new_query_fn(implementation, true), - name, - rust, - ); - Self::impl_tupl_struct_load_fn(Self::new_load_fn(implementation, true), name); - } - } - } - - fn impl_query_statement(func: &mut Function, name: &str) { - func.line(&format!("\"{}\"", select_statement_single(name))); - } - - fn impl_empty_query_statement(func: &mut Function) { - func.line("\"\""); - } - - fn impl_tupl_query_statement(func: &mut Function, name: &str, inner: &RustType) { - func.line(&format!("\"{}\"", list_entry_query_statement(name, inner))); - } - - fn impl_struct_query_fn(func: &mut Function, name: &str) { - func.line("let statement = transaction.prepare(Self::query_statement())?;"); - func.line("let rows = transaction.query(&statement, &[&id])?;"); - func.line(&format!( - "{}::load_from(transaction, rows.get(0).ok_or_else({}::no_result)?)", - name, ERROR_TYPE, - )); - } - fn impl_struct_load_fn<'a>( - func: &mut Function, - struct_name: &str, - variants: impl ExactSizeIterator, - ) { - let mut block = Block::new(&format!("Ok({}", struct_name)); - let mut index_negative_offset = 0; - - for (index, (name, rust)) in variants.enumerate() { - let index = index - index_negative_offset; - - // Lists do not have a entry in the `holding` table but - // a third table referencing both (M:N relation), therefore - // the index must not be incremented, otherwise one column - // in the container table would be skipped - if Model::::has_no_column_in_embedded_struct(rust) { - index_negative_offset += 1; - } - - if rust.is_vec() { - let mut load_block = Block::new(&format!( - "{}:", - RustCodeGenerator::rust_field_name(name, true) - )); - - load_block.line("let mut vec = Vec::default();"); - - load_block.line(&format!( - "let rows = transaction.query(\"{}\", &[&{}::expect_returned_index(&row)?])?;", - if let RustType::Complex(complex, _tag) = rust.as_inner_type() { - struct_list_entry_select_referenced_value_statement( - struct_name, - name, - complex, - ) - } else { - struct_list_entry_select_value_statement(struct_name, name) - }, - ERROR_TYPE - )); - let mut rows_foreach = Block::new("for row in rows.iter()"); - if let RustType::Complex(complex, _tag) = rust.as_inner_type() { - rows_foreach.line(&format!( - "vec.push({}::load_from(transaction, &row)?);", - complex - )); - } else { - let rust = rust.as_inner_type(); - let sql = rust.to_sql(); - rows_foreach.line(&format!( - "let value = {}::value_at_column::<{}>(&row, 0)?;", - ERROR_TYPE, - sql.to_rust().to_string(), - )); - if *rust < sql.to_rust() { - rows_foreach.line(&format!("let value = value as {};", rust.to_string())); - } - rows_foreach.line("vec.push(value);"); - } - load_block.push_block(rows_foreach); - - if let RustType::Option(_) = rust { - load_block.line("if vec.is_empty() { None } else { Some(vec) }"); - } else { - load_block.line("vec"); - } - load_block.after(","); - block.push_block(load_block); - } else if Model::::is_primitive(rust) { - let load = format!( - "{}::value_at_column::<{}>(&row, {})?", - ERROR_TYPE, - if rust.to_sql().to_rust().similar(rust) { - rust.to_string() - } else { - rust.to_sql().to_rust().to_string() - }, - index + 1, - ); - block.line(&format!( - "{}: {},", - RustCodeGenerator::rust_field_name(name, true), - Self::wrap_for_query_in_as_or_from_if_required(&load, rust,).unwrap_or(load) - )); - } else { - let inner = rust.as_inner_type(); - let load = if let RustType::Option(_) = rust { - format!( - "{}::value_at_column::>(&row, {})?\ - .map(|id| {}::query_with(transaction, id)).transpose()?", - ERROR_TYPE, - index + 1, - inner.to_string(), - ) - } else { - format!( - "{}::query_with(transaction, {}::value_at_column::(&row, {})?)?", - inner.to_string(), - ERROR_TYPE, - index + 1, - ) - }; - block.line(&format!( - "{}: {},", - RustCodeGenerator::rust_field_name(name, true), - load - )); - } - } - - block.after(")"); - func.push_block(block); - } - - fn impl_data_enum_query_fn(func: &mut Function, name: &str) { - func.line("let statement = transaction.prepare(Self::query_statement())?;"); - func.line("let rows = transaction.query(&statement, &[&id])?;"); - func.line(&format!( - "{}::load_from(transaction, rows.get(0).ok_or_else({}::no_result)?)", - name, ERROR_TYPE - )); - } - fn impl_data_enum_load_fn(func: &mut Function, name: &str, enumeration: &DataEnum) { - func.line(format!( - "let index = {}::first_present(row, &[{}])?;", - ERROR_TYPE, - enumeration - .variants() - .enumerate() - .map(|e| format!("{}", e.0 + 1)) - .collect::>() - .join(", ") - )); - let mut block = Block::new("match index"); - for (index, variant) in enumeration.variants().enumerate() { - let mut block_case = Block::new(&format!( - "{} => Ok({}::{}(", - index + 1, - name, - variant.name() - )); - - if Model::::is_primitive(variant.r#type().as_inner_type()) { - if let Some(wrap) = - Self::wrap_for_query_in_as_or_from_if_required("", variant.r#type()) - { - block_case.line(format!( - "{}::value_at_column::<{}>(&row, {})?{}", - ERROR_TYPE, - variant - .r#type() - .as_inner_type() - .to_sql() - .to_rust() - .to_string(), - index + 1, - wrap - )); - } else { - block_case.line(format!( - "{}::value_at_column::<{}>(&row, {})?", - ERROR_TYPE, - variant - .r#type() - .as_inner_type() - .to_sql() - .to_rust() - .to_string(), - index + 1 - )); - } - } else { - block_case.line(&format!( - "{}::query_with(transaction, row.get({}))?", - variant.r#type().clone().as_inner_type().to_string(), - index + 1 - )); - } - - block_case.after(")),"); - block.push_block(block_case); - } - block.line(&format!("_ => Err({}::no_result()),", ERROR_TYPE)); - func.push_block(block); - } - - fn impl_enum_query_fn(func: &mut Function, name: &str, r_enum: &PlainEnum) { - let mut block = Block::new("match id"); - for (index, variant) in r_enum.variants().enumerate() { - block.line(&format!("{} => Ok({}::{}),", index, name, variant)); - } - block.line(&format!("_ => Err({}::no_result()),", ERROR_TYPE)); - func.push_block(block); - } - - fn impl_enum_load_fn(func: &mut Function, name: &str) { - func.line(&format!( - "{}::query_with(transaction, {}::expect_returned_index(&row)?)", - name, ERROR_TYPE, - )); - } - - fn impl_tupl_struct_query_fn(func: &mut Function, name: &str, rust: &RustType) { - func.line("let statement = transaction.prepare(Self::query_statement())?;"); - func.line("let rows = transaction.query(&statement, &[&id])?;"); - let inner = rust.as_inner_type(); - if Model::::is_primitive(inner) { - let from_sql = inner.to_sql().to_rust(); - - let load = format!( - "{}::value_at_column::<{}>(&row, 0)?", - ERROR_TYPE, - from_sql.to_string(), - ); - let load_wrapped = - Self::wrap_for_query_in_as_or_from_if_required(&load, rust).unwrap_or(load); - - if rust.is_vec() { - func.line("let mut values = Vec::with_capacity(rows.len());"); - let mut block = Block::new("for row in rows.iter()"); - block.line(&format!("values.push({});", load_wrapped)); - func.push_block(block); - } else { - func.line(format!( - "let row = rows.get(0).ok_or_else({}::no_result)?;", - ERROR_TYPE - )); - func.line(format!("let values = {};", load_wrapped)); - } - } else if rust.is_vec() { - func.line("let mut values = Vec::with_capacity(rows.len());"); - let mut block = Block::new("for row in rows.iter()"); - block.line(&format!( - "values.push({}::load_from(transaction, &row)?);", - inner.to_string() - )); - func.push_block(block); - } else { - func.line(format!( - "let row = rows.get(0).ok_or_else({}::no_result)?;", - ERROR_TYPE - )); - func.line(format!( - "let values = {}::load_from(transaction, &row)?;", - inner.to_string() - )); - } - func.line(&format!("Ok({}(values))", name)); - } - - fn impl_tupl_struct_load_fn(func: &mut Function, name: &str) { - func.line(&format!( - "{}::query_with(transaction, {}::expect_returned_index(&row)?)", - name, ERROR_TYPE, - )); - } - - fn new_query_statement_fn(implementation: &mut Impl) -> &mut Function { - implementation.new_fn("query_statement").ret("&'static str") - } - - fn new_query_fn(implementation: &mut Impl, using_transaction: bool) -> &mut Function { - implementation - .new_fn("query_with") - .arg( - if using_transaction { - "transaction" - } else { - "_" - }, - "&mut Transaction", - ) - .arg("id", "i32") - .ret(&format!("Result", ERROR_TYPE)) - } - - fn new_load_fn(implementation: &mut Impl, using_transaction: bool) -> &mut Function { - implementation - .new_fn("load_from") - .arg( - if using_transaction { - "transaction" - } else { - "_" - }, - "&mut Transaction", - ) - .arg("row", &format!("&{}", ROW_TYPE)) - .ret(&format!("Result", ERROR_TYPE)) - } -} diff --git a/asn1rs-model/src/gen/rust/shared_psql.rs b/asn1rs-model/src/gen/rust/shared_psql.rs deleted file mode 100644 index 58c33a52..00000000 --- a/asn1rs-model/src/gen/rust/shared_psql.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::gen::RustCodeGenerator; -use crate::model::rust::{DataEnum, Field}; -use crate::model::sql::Sql; -use crate::model::{Model, RustType}; - -pub(crate) fn select_statement_single(name: &str) -> String { - let name = Model::::sql_definition_name(name); - format!("SELECT * FROM {} WHERE id = $1", name) -} - -#[cfg(feature = "async-psql")] -pub(crate) fn select_statement_many(name: &str) -> String { - let name = Model::::sql_definition_name(name); - format!("SELECT * FROM {} WHERE id = ANY($1)", name) -} - -pub(crate) fn tuple_struct_insert_statement(name: &str) -> String { - let name = Model::::sql_definition_name(name); - format!("INSERT INTO {} DEFAULT VALUES RETURNING id", name) -} - -pub(crate) fn struct_insert_statement(name: &str, fields: &[Field]) -> String { - let name = Model::::sql_definition_name(name); - format!( - "INSERT INTO {}({}) VALUES({}) RETURNING id", - name, - fields - .iter() - .filter_map(|field| if field.r#type().is_vec() { - None - } else { - Some(Model::sql_column_name(field.name())) - }) - .collect::>() - .join(", "), - fields - .iter() - .filter_map(|field| if field.r#type().is_vec() { - None - } else { - Some(field.name()) - }) - .enumerate() - .map(|(num, _)| format!("${}", num + 1)) - .collect::>() - .join(", "), - ) -} - -pub(crate) fn data_enum_insert_statement(name: &str, enumeration: &DataEnum) -> String { - let name = Model::::sql_definition_name(name); - format!( - "INSERT INTO {}({}) VALUES({}) RETURNING id", - name, - enumeration - .variants() - .map(|variant| RustCodeGenerator::rust_module_name(variant.name())) - .collect::>() - .join(", "), - enumeration - .variants() - .enumerate() - .map(|(num, _)| format!("${}", num + 1)) - .collect::>() - .join(", "), - ) -} - -pub(crate) fn struct_list_entry_insert_statement(struct_name: &str, field_name: &str) -> String { - format!( - "INSERT INTO {}(list, value) VALUES ($1, $2)", - Model::::sql_definition_name(&Model::::struct_list_entry_table_name( - struct_name, - field_name - )), - ) -} - -pub(crate) fn struct_list_entry_select_referenced_value_statement( - struct_name: &str, - field_name: &str, - other_type: &str, -) -> String { - let listentry_table = Model::::sql_definition_name( - &Model::::struct_list_entry_table_name(struct_name, field_name), - ); - format!( - "SELECT * FROM {} WHERE id IN (SELECT value FROM {} WHERE list = $1)", - RustCodeGenerator::rust_variant_name(other_type), - listentry_table, - ) -} - -pub(crate) fn struct_list_entry_select_value_statement( - struct_name: &str, - field_name: &str, -) -> String { - let listentry_table = Model::::sql_definition_name( - &Model::::struct_list_entry_table_name(struct_name, field_name), - ); - format!("SELECT value FROM {} WHERE list = $1", listentry_table,) -} - -pub(crate) fn list_entry_insert_statement(name: &str) -> String { - let name = Model::::sql_definition_name(&format!("{name}ListEntry")); - format!("INSERT INTO {}(list, value) VALUES ($1, $2)", name) -} - -pub(crate) fn list_entry_query_statement(name: &str, inner: &RustType) -> String { - let name_le = Model::::sql_definition_name(&format!("{name}ListEntry")); - if Model::::is_primitive(inner) { - format!("SELECT value FROM {name_le} WHERE {name_le}.list = $1",) - } else { - let inner = inner.clone().as_inner_type().to_string(); - let inner = Model::::sql_definition_name(&inner); - format!( - "SELECT * FROM {inner} INNER JOIN {name_le} ON {inner}.id = {name_le}.value WHERE {name_le}.list = $1" - ) - } -} diff --git a/asn1rs-model/src/gen/sql.rs b/asn1rs-model/src/gen/sql.rs deleted file mode 100644 index d9414b99..00000000 --- a/asn1rs-model/src/gen/sql.rs +++ /dev/null @@ -1,288 +0,0 @@ -use crate::gen::Generator; -use crate::model::sql::Column; -use crate::model::sql::Constraint; -use crate::model::sql::Sql; -use crate::model::Definition; -use crate::model::Model; -use std::fmt::Write; - -#[derive(Debug)] -pub enum Error { - Fmt(::std::fmt::Error), -} - -impl From<::std::fmt::Error> for Error { - fn from(e: ::std::fmt::Error) -> Self { - Error::Fmt(e) - } -} - -#[derive(Debug)] -pub enum TableOptimizationHint { - WritePerformance, -} - -#[derive(Debug)] -pub enum PrimaryKeyHint { - WrapOnOverflow, -} - -#[allow(clippy::module_name_repetitions)] -#[derive(Debug, Default)] -pub struct SqlDefGenerator { - models: Vec>, - optimize_tables_for: Option, - primary_key_hint: Option, -} - -impl SqlDefGenerator { - pub fn reset(&mut self) { - self.models.clear(); - } -} - -impl Generator for SqlDefGenerator { - type Error = Error; - - fn add_model(&mut self, model: Model) { - self.models.push(model); - } - - fn models(&self) -> &[Model] { - &self.models - } - - fn models_mut(&mut self) -> &mut [Model] { - &mut self.models - } - - fn to_string(&self) -> Result, >::Error> { - let mut files = Vec::with_capacity(self.models.len()); - for model in &self.models { - let mut drop = String::new(); - let mut create = String::new(); - for Definition(name, sql) in &model.definitions { - writeln!(create)?; - match sql { - Sql::Table(columns, constraints) => { - // TODO - writeln!(drop, "DROP TABLE IF EXISTS {} CASCADE;", name)?; - self.append_create_table(&mut create, name, columns, constraints)?; - self.apply_primary_key_hints(&mut create, name, columns)?; - } - Sql::Enum(variants) => { - // TODO - writeln!(drop, "DROP TABLE IF EXISTS {} CASCADE;", name)?; - self.append_create_enum(&mut create, name, variants)? - } - Sql::Index(table, columns) => { - Self::append_index(&mut create, name, table, &columns[..])?; - } - Sql::AbandonChildrenFunction(table, children) => { - Self::append_abandon_children(&mut create, table, name, &children[..])?; - } - Sql::SilentlyPreventAnyDelete(table) => { - Self::append_silently_prevent_any_delete(&mut create, name, table)?; - } - } - } - drop.push_str(&create); - files.push((format!("{}.sql", model.name), drop)); - } - Ok(files) - } -} - -impl SqlDefGenerator { - pub fn optimize_tables_for_write_performance(mut self) -> Self { - self.optimize_tables_for = Some(TableOptimizationHint::WritePerformance); - self - } - - pub const fn no_table_write_optimization(mut self) -> Self { - self.optimize_tables_for = None; - self - } - - pub const fn wrap_primary_key_on_overflow(mut self) -> Self { - self.primary_key_hint = Some(PrimaryKeyHint::WrapOnOverflow); - self - } - - pub const fn no_wrap_of_primary_key_on_overflow(mut self) -> Self { - self.primary_key_hint = None; - self - } - - fn append_create_table( - &self, - target: &mut dyn Write, - name: &str, - columns: &[Column], - constraints: &[Constraint], - ) -> Result<(), Error> { - writeln!( - target, - "CREATE{}TABLE {} (", - match self.optimize_tables_for { - Some(TableOptimizationHint::WritePerformance) => " UNLOGGED ", - None => " ", - }, - name - )?; - for (index, column) in columns.iter().enumerate() { - Self::append_column_statement(target, column)?; - if index + 1 < columns.len() || !constraints.is_empty() { - write!(target, ",")?; - } - writeln!(target)?; - } - for (index, constraint) in constraints.iter().enumerate() { - Self::append_constraint(target, constraint)?; - if index + 1 < constraints.len() { - write!(target, ",")?; - } - writeln!(target)?; - } - writeln!(target, ");")?; - Ok(()) - } - - #[allow(clippy::single_match)] // to get a compiler error on a new variant in PrimaryKeyHint - fn apply_primary_key_hints( - &self, - target: &mut dyn Write, - table: &str, - columns: &[Column], - ) -> Result<(), Error> { - let column_name = columns.iter().find_map(|column| { - if column.primary_key { - Some(column.name.clone()) - } else { - None - } - }); - if let Some(column) = column_name { - match self.primary_key_hint { - Some(PrimaryKeyHint::WrapOnOverflow) => { - writeln!(target, "ALTER SEQUENCE {}_{}_seq CYCLE;", table, column)?; - } - None => {} - } - } - Ok(()) - } - - pub fn append_column_statement(target: &mut dyn Write, column: &Column) -> Result<(), Error> { - write!(target, " {} {}", column.name, column.sql.to_string())?; - if column.primary_key { - write!(target, " PRIMARY KEY")?; - } - Ok(()) - } - - fn append_create_enum( - &self, - target: &mut dyn Write, - name: &str, - variants: &[String], - ) -> Result<(), Error> { - writeln!( - target, - "CREATE{}TABLE {} (", - match self.optimize_tables_for { - Some(TableOptimizationHint::WritePerformance) => " UNLOGGED ", - None => " ", - }, - name - )?; - writeln!(target, " id SERIAL PRIMARY KEY,")?; - writeln!(target, " name TEXT NOT NULL")?; - writeln!(target, ");")?; - - writeln!(target, "INSERT INTO {} (id, name) VALUES", name)?; - for (index, variant) in variants.iter().enumerate() { - write!(target, " ({}, '{}')", index, variant)?; - if index + 1 < variants.len() { - write!(target, ", ")?; - } else { - write!(target, ";")?; - } - writeln!(target)?; - } - Ok(()) - } - - fn append_constraint(target: &mut dyn Write, constraint: &Constraint) -> Result<(), Error> { - match constraint { - Constraint::CombinedPrimaryKey(columns) => { - write!(target, " PRIMARY KEY({})", columns.join(", "))?; - } - Constraint::OneNotNull(columns) => { - write!( - target, - " CHECK (num_nonnulls({}) = 1)", - columns.join(", ") - )?; - } - } - Ok(()) - } - - fn append_index( - target: &mut dyn Write, - name: &str, - table: &str, - columns: &[String], - ) -> Result<(), Error> { - writeln!( - target, - "CREATE INDEX {} ON {}({});", - name, - table, - columns.join(", ") - )?; - Ok(()) - } - - fn append_abandon_children( - target: &mut dyn Write, - table: &str, - name: &str, - children: &[(String, String, String)], - ) -> Result<(), Error> { - writeln!( - target, - "CREATE OR REPLACE FUNCTION {}() RETURNS TRIGGER AS", - name - )?; - writeln!(target, "$$ BEGIN")?; - for (column, other_table, other_column) in children { - writeln!( - target, - " DELETE FROM {} WHERE {} = OLD.{};", - other_table, other_column, column - )?; - } - writeln!(target, " RETURN NULL;")?; - writeln!(target, "END; $$ LANGUAGE plpgsql;")?; - writeln!( - target, - "CREATE TRIGGER OnDelete{} AFTER DELETE ON {}", - name, table - )?; - writeln!(target, " FOR EACH ROW")?; - writeln!(target, " EXECUTE PROCEDURE {}();", name)?; - Ok(()) - } - - fn append_silently_prevent_any_delete( - target: &mut dyn Write, - name: &str, - table: &str, - ) -> Result<(), Error> { - writeln!(target, "CREATE RULE {} AS ON DELETE TO {}", name, table)?; - writeln!(target, " DO INSTEAD NOTHING;")?; - Ok(()) - } -} diff --git a/asn1rs-model/src/model/mod.rs b/asn1rs-model/src/model/mod.rs index 690a8902..7d20ea54 100644 --- a/asn1rs-model/src/model/mod.rs +++ b/asn1rs-model/src/model/mod.rs @@ -1,6 +1,5 @@ pub mod protobuf; pub mod rust; -pub mod sql; pub use self::rust::Rust; pub use self::rust::RustType; diff --git a/asn1rs-model/src/model/sql/mod.rs b/asn1rs-model/src/model/sql/mod.rs deleted file mode 100644 index f40bfb93..00000000 --- a/asn1rs-model/src/model/sql/mod.rs +++ /dev/null @@ -1,1293 +0,0 @@ -use crate::gen::RustCodeGenerator; -use crate::model::rust::{DataEnum, DataVariant, EncodingOrdering}; -use crate::model::rust::{Field, PlainEnum}; -use crate::model::Rust; -use crate::model::RustType; -use crate::model::{Charset, Model}; -use crate::model::{Definition, Size}; -use crate::model::{Range, Target}; -use std::collections::HashMap; -use std::convert::Infallible; - -const FOREIGN_KEY_DEFAULT_COLUMN: &str = "id"; -const TUPLE_LIST_ENTRY_PARENT_COLUMN: &str = "list"; -const TUPLE_LIST_ENTRY_VALUE_COLUMN: &str = "value"; - -/// Default seems to be 63, dont exceed it -/// https://github.com/kellerkindt/asn1rs/issues/75#issuecomment-1110804868 -const TYPENAME_LENGTH_LIMIT_PSQL: usize = 63; - -#[derive(Debug, Clone, PartialEq, PartialOrd)] -#[allow(clippy::module_name_repetitions)] -pub enum SqlType { - SmallInt, // 2byte - Integer, // 4byte - BigInt, // 8byte - Serial, // 4byte - Boolean, - Text, - Array(Box), - NotNull(Box), - ByteArray, - NullByteArray, - BitsReprByByteArrayAndBitsLen, - References(String, String, Option, Option), -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] -pub enum Action { - Cascade, - Restrict, -} - -impl ToString for Action { - fn to_string(&self) -> String { - match self { - Action::Cascade => "CASCADE", - Action::Restrict => "RESTRICT", - } - .into() - } -} - -impl SqlType { - pub fn as_nullable(&self) -> &Self { - match self { - SqlType::NotNull(inner) => inner, - other => other, - } - } - - pub fn nullable(self) -> Self { - match self { - SqlType::NotNull(inner) => *inner, - other => other, - } - } - - pub fn not_null(self) -> Self { - SqlType::NotNull(Box::new(self)) - } - - pub fn to_rust(&self) -> RustType { - #[allow(clippy::match_same_arms)] // to have the same order as the original enum - RustType::Option(Box::new(match self { - SqlType::SmallInt => RustType::I16(Range::inclusive(0, i16::MAX)), - SqlType::Integer => RustType::I32(Range::inclusive(0, i32::MAX)), - SqlType::BigInt => RustType::I64(Range::inclusive(0, i64::MAX)), - SqlType::Serial => RustType::I32(Range::inclusive(0, i32::MAX)), - SqlType::Boolean => RustType::Bool, - SqlType::Text => RustType::String(Size::Any, Charset::Utf8), - SqlType::Array(inner) => { - RustType::Vec(Box::new(inner.to_rust()), Size::Any, EncodingOrdering::Keep) - } - SqlType::NotNull(inner) => return inner.to_rust().no_option(), - SqlType::ByteArray => RustType::VecU8(Size::Any), - SqlType::NullByteArray => return RustType::Null, - SqlType::BitsReprByByteArrayAndBitsLen => RustType::BitVec(Size::Any), - SqlType::References(name, _, _, _) => RustType::Complex(name.clone(), None), - })) - } -} - -impl ToString for SqlType { - fn to_string(&self) -> String { - match self { - SqlType::SmallInt => "SMALLINT".into(), - SqlType::Integer => "INTEGER".into(), - SqlType::BigInt => "BIGINT".into(), - SqlType::Serial => "SERIAL".into(), - SqlType::Boolean => "BOOLEAN".into(), - SqlType::Text => "TEXT".into(), - SqlType::Array(inner) => format!("{}[]", inner.to_string()), - SqlType::NotNull(inner) => format!("{} NOT NULL", inner.to_string()), - SqlType::ByteArray - | SqlType::NullByteArray - | SqlType::BitsReprByByteArrayAndBitsLen => "BYTEA".into(), - SqlType::References(table, column, on_delete, on_update) => format!( - "INTEGER REFERENCES {}({}){}{}", - Model::::sql_definition_name(table), - column, - if let Some(cascade) = on_delete { - format!(" ON DELETE {}", cascade.to_string()) - } else { - "".into() - }, - if let Some(cascade) = on_update { - format!(" ON UPDATE {}", cascade.to_string()) - } else { - "".into() - }, - ), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Column { - pub name: String, - pub sql: SqlType, - pub primary_key: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Constraint { - CombinedPrimaryKey(Vec), - OneNotNull(Vec), -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Sql { - Table(Vec, Vec), - Enum(Vec), - Index(String, Vec), - /// Table being affected to -> - AbandonChildrenFunction(String, Vec<(String, String, String)>), - SilentlyPreventAnyDelete(String), -} - -impl Target for Sql { - type DefinitionType = Self; - type ValueReferenceType = Infallible; -} - -impl Model { - pub fn convert_rust_to_sql(rust_model: &Model) -> Model { - let mut model = Model { - name: rust_model.name.clone(), - oid: rust_model.oid.clone(), - imports: Default::default(), // ignored in SQL - definitions: Vec::with_capacity(rust_model.definitions.len()), - value_references: Vec::default(), - }; - for Definition(name, rust) in &rust_model.definitions { - let name = Self::sql_definition_name(name); - Self::definition_to_sql(&name, rust, &mut model.definitions); - } - model.fix_table_declaration_occurrence(); - model - } - - fn fix_table_declaration_occurrence(&mut self) { - let mut depth = HashMap::::default(); - for i in 0..self.definitions.len() { - self.walk_graph(i, &mut depth, 0); - } - self.definitions - .sort_by_key(|key| usize::MAX - depth.get(key.name()).unwrap_or(&0_usize)); - } - - fn walk_graph(&self, current: usize, depths: &mut HashMap, depth: usize) { - let name = self.definitions[current].name(); - let depth = if let Some(d) = depths.get(name) { - if depth > *d { - depths.insert(name.to_string(), depth); - depth - } else { - *d - } - } else { - depths.insert(name.to_string(), depth); - depth - }; - let mut walk_from_name = |name: &str| { - for (index, def) in self.definitions.iter().enumerate() { - if def.name().eq(name) { - self.walk_graph(index, depths, depth + 1); - break; - } - } - }; - match self.definitions[current].value() { - Sql::Table(columns, _constraints) => { - for column in columns { - if let SqlType::References(other, _, _, _) = column.sql.as_nullable() { - walk_from_name(other.as_str()); - } - } - } - Sql::Enum(_) => {} - Sql::Index(name, _) - | Sql::AbandonChildrenFunction(name, _) - | Sql::SilentlyPreventAnyDelete(name) => { - walk_from_name(name.as_str()); - } - } - } - - fn definition_to_sql(name: &str, rust: &Rust, definitions: &mut Vec>) { - match rust { - Rust::Struct { - fields, - tag: _, - extension_after: _, - ordering: _, - } => Self::rust_struct_to_sql_table(name, fields, definitions), - Rust::Enum(rust_enum) => Self::rust_enum_to_sql_enum(name, rust_enum, definitions), - Rust::DataEnum(enumeration) => { - Self::rust_data_enum_to_sql_table(name, enumeration, definitions) - } - Rust::TupleStruct { r#type: rust, .. } => { - Self::rust_tuple_struct_to_sql_table(name, rust, definitions) - } - } - } - - pub fn rust_struct_to_sql_table( - name: &str, - fields: &[Field], - definitions: &mut Vec>, - ) { - let mut deferred = Vec::default(); - let mut columns = Vec::with_capacity(fields.len() + 1); - columns.push(Column { - name: FOREIGN_KEY_DEFAULT_COLUMN.into(), - sql: SqlType::Serial, - primary_key: true, - }); - for field in fields { - if field.r#type().is_vec() { - let list_entry_name = Self::struct_list_entry_table_name(name, field.name()); - let value_sql_type = field.r#type().clone().as_inner_type().to_sql(); - Self::add_list_table(name, &mut deferred, &list_entry_name, &value_sql_type); - } else { - columns.push(Column { - name: Self::sql_column_name(field.name()), - sql: field.r#type().to_sql(), - primary_key: false, - }); - } - } - definitions.push(Definition( - name.into(), - Sql::Table(columns, Default::default()), - )); - - Self::append_index_and_abandon_function( - name, - fields.iter().map(Field::fallback_representation), - definitions, - ); - deferred.into_iter().for_each(|e| definitions.push(e)); - } - - pub fn rust_data_enum_to_sql_table( - name: &str, - enumeration: &DataEnum, - definitions: &mut Vec>, - ) { - let mut columns = Vec::with_capacity(enumeration.len() + 1); - // TODO - if !enumeration - .variants() - .map(|variant| FOREIGN_KEY_DEFAULT_COLUMN.eq_ignore_ascii_case(variant.name())) - .any(|found| found) - { - columns.push(Column { - name: FOREIGN_KEY_DEFAULT_COLUMN.into(), - sql: SqlType::Serial, - primary_key: true, - }); - } - for variant in enumeration.variants() { - columns.push(Column { - name: Self::sql_column_name(variant.name()), - sql: variant.r#type().to_sql().nullable(), - primary_key: false, - }); - } - definitions.push(Definition( - name.into(), - Sql::Table( - columns, - vec![Constraint::OneNotNull( - enumeration - .variants() - .map(|variant| RustCodeGenerator::rust_module_name(variant.name())) - .collect::>(), - )], - ), - )); - - Self::append_index_and_abandon_function( - name, - enumeration - .variants() - .map(DataVariant::fallback_representation), - definitions, - ); - } - - fn add_index_if_applicable( - table: &str, - column: &str, - rust: &RustType, - definitions: &mut Vec>, - ) { - if let SqlType::References(..) = rust.to_sql().nullable() { - definitions.push(Self::index_def(table, column)); - } - } - - fn index_def(table: &str, column: &str) -> Definition { - Definition( - Self::sql_definition_name(&format!("{}_Index_{}", table, column)), - Sql::Index(table.into(), vec![column.into()]), - ) - } - - pub fn rust_enum_to_sql_enum( - name: &str, - enumeration: &PlainEnum, - definitions: &mut Vec>, - ) { - let variants = enumeration.variants().map(String::clone).collect(); - definitions.push(Definition(name.into(), Sql::Enum(variants))); - Self::add_silently_prevent_any_delete(name, definitions); - } - - pub fn rust_tuple_struct_to_sql_table( - name: &str, - rust_inner: &RustType, - definitions: &mut Vec>, - ) { - { - definitions.push(Definition( - name.into(), - Sql::Table( - vec![Column { - name: FOREIGN_KEY_DEFAULT_COLUMN.into(), - sql: SqlType::Serial, - primary_key: true, - }], - Default::default(), - ), - )); - } - { - let list_entry_name = Self::sql_definition_name(&format!("{}ListEntry", name)); - let value_sql_type = rust_inner.clone().as_inner_type().to_sql(); - Self::add_list_table(name, definitions, &list_entry_name, &value_sql_type); - } - } - - fn add_list_table( - name: &str, - definitions: &mut Vec>, - list_entry_name: &str, - value_sql_type: &SqlType, - ) { - definitions.push(Definition( - list_entry_name.to_string(), - Sql::Table( - vec![ - Column { - name: TUPLE_LIST_ENTRY_PARENT_COLUMN.into(), - sql: SqlType::NotNull(Box::new(SqlType::References( - name.into(), - FOREIGN_KEY_DEFAULT_COLUMN.into(), - Some(Action::Cascade), - Some(Action::Cascade), - ))), - primary_key: false, - }, - Column { - name: TUPLE_LIST_ENTRY_VALUE_COLUMN.into(), - sql: value_sql_type.clone(), - primary_key: false, - }, - ], - vec![Constraint::CombinedPrimaryKey(vec![ - TUPLE_LIST_ENTRY_PARENT_COLUMN.into(), - TUPLE_LIST_ENTRY_VALUE_COLUMN.into(), - ])], - ), - )); - definitions.push(Self::index_def( - list_entry_name, - TUPLE_LIST_ENTRY_PARENT_COLUMN, - )); - definitions.push(Self::index_def( - list_entry_name, - TUPLE_LIST_ENTRY_VALUE_COLUMN, - )); - if let SqlType::References(other_table, other_column, ..) = - value_sql_type.clone().nullable() - { - Self::add_abandon_children( - list_entry_name, - vec![( - TUPLE_LIST_ENTRY_VALUE_COLUMN.into(), - other_table, - other_column, - )], - definitions, - ); - } - } - - pub fn sql_column_name(name: &str) -> String { - if FOREIGN_KEY_DEFAULT_COLUMN.eq_ignore_ascii_case(name.trim()) { - let mut string = RustCodeGenerator::rust_module_name(name); - string.push('_'); - string - } else { - RustCodeGenerator::rust_module_name(name) - } - } - - pub fn sql_definition_name(name: &str) -> String { - if name.len() > TYPENAME_LENGTH_LIMIT_PSQL { - // carve out lower case segments at the beginning until it fits into the limit - let mut result = String::with_capacity(TYPENAME_LENGTH_LIMIT_PSQL); - for (index, character) in name.chars().enumerate() { - if character.is_ascii_lowercase() { - // skip - } else { - result.push(character); - // check if the rest could just be copied over - let remainig = (name.len() - index).saturating_sub(1); - if remainig + result.len() <= TYPENAME_LENGTH_LIMIT_PSQL { - result.push_str(&name[index + 1..]); - break; - } else if result.len() >= TYPENAME_LENGTH_LIMIT_PSQL { - break; - } - } - } - result - } else { - name.to_string() - } - } - - fn append_index_and_abandon_function<'a>( - name: &str, - fields: impl Iterator, - definitions: &mut Vec>, - ) { - let mut children = Vec::new(); - for (column, rust) in fields { - let column = Self::sql_column_name(column); - Self::add_index_if_applicable(name, &column, rust, definitions); - if let SqlType::References(other_table, other_column, _, _) = rust.to_sql().nullable() { - children.push((column, other_table, other_column)); - } - } - if !children.is_empty() { - Self::add_abandon_children(name, children, definitions); - } - } - - fn add_abandon_children( - name: &str, - children: Vec<(String, String, String)>, - definitions: &mut Vec>, - ) { - definitions.push(Definition( - Self::sql_definition_name(&format!("DelChilds_{}", name)), - Sql::AbandonChildrenFunction(name.into(), children), - )); - } - - fn add_silently_prevent_any_delete(name: &str, definitions: &mut Vec>) { - definitions.push(Definition( - Self::sql_definition_name(&format!("SilentlyPreventAnyDeleteOn{}", name)), - Sql::SilentlyPreventAnyDelete(name.into()), - )); - } - - pub fn has_no_column_in_embedded_struct(rust: &RustType) -> bool { - rust.is_vec() - } - - pub fn is_primitive(rust: &RustType) -> bool { - #[allow(clippy::match_same_arms)] // to have the same order as the original enum - match rust.as_inner_type() { - RustType::String(..) => true, - RustType::VecU8(_) => true, - RustType::BitVec(_) => true, - RustType::Null => true, - r => r.is_primitive(), - } - } - - pub fn struct_list_entry_table_name(struct_name: &str, field_name: &str) -> String { - Self::sql_definition_name(&format!( - "{}_{}", - struct_name, - RustCodeGenerator::rust_variant_name(field_name) - )) - } -} - -pub trait ToSqlModel { - fn to_sql(&self) -> Model; -} - -impl ToSqlModel for Model { - fn to_sql(&self) -> Model { - Model::convert_rust_to_sql(self) - } -} - -#[allow(clippy::module_name_repetitions)] -pub trait ToSql { - fn to_sql(&self) -> SqlType; -} - -impl ToSql for RustType { - fn to_sql(&self) -> SqlType { - SqlType::NotNull(Box::new(match self { - RustType::Bool => SqlType::Boolean, - RustType::Null => return SqlType::NullByteArray.nullable(), - RustType::U8(_) | RustType::I8(_) => SqlType::SmallInt, - RustType::U16(Range(_, upper, _)) if *upper <= i16::MAX as u16 => SqlType::SmallInt, - RustType::I16(_) => SqlType::SmallInt, - RustType::U32(Range(_, upper, _)) if *upper <= i32::MAX as u32 => SqlType::Integer, - RustType::U16(_) | RustType::I32(_) => SqlType::Integer, - RustType::U32(_) | RustType::U64(_) | RustType::I64(_) => SqlType::BigInt, - RustType::String(_size, _charset) => SqlType::Text, - RustType::VecU8(_) => SqlType::ByteArray, - RustType::BitVec(_) => SqlType::BitsReprByByteArrayAndBitsLen, - RustType::Vec(inner, _size, _ordering) => SqlType::Array(inner.to_sql().into()), - RustType::Option(inner) => return inner.to_sql().nullable(), - RustType::Default(inner, ..) => return inner.to_sql(), - RustType::Complex(name, _tag) => SqlType::References( - name.clone(), - FOREIGN_KEY_DEFAULT_COLUMN.into(), - Some(Action::Cascade), - Some(Action::Cascade), - ), - })) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::model::rust::{EncodingOrdering, Field}; - use crate::model::{Charset, Model, Tag}; - use crate::model::{Import, Size}; - - #[test] - fn test_conversion_too_long_name() { - let chars_70_length = - "TheQuickBrownFoxJumpsOverTheLazyDogTheQuickBrownFoxJumpsOverTheLazyDog"; - - assert!(chars_70_length.chars().count() > TYPENAME_LENGTH_LIMIT_PSQL); - - assert_eq!( - "TQBFoxJumpsOverTheLazyDogTheQuickBrownFoxJumpsOverTheLazyDog", - &Model::::sql_definition_name(chars_70_length) - ); - - assert_eq!( - "THEQUICKBROWNFOXJUMPSOVERTHELAZYDOGTHEQUICKBROWNFOXJUMPSOVERTHE", - &Model::::sql_definition_name(&chars_70_length.to_ascii_uppercase()) - ); - - assert_eq!( - "TheQuickBrownFoxJumpsOverTheLazyDogTheQuickBrownFoxJumpsOverThe", - &Model::::sql_definition_name(&chars_70_length[..TYPENAME_LENGTH_LIMIT_PSQL]) - ); - } - - #[test] - fn test_conversion_struct() { - let model = Model { - name: "Manfred".into(), - imports: vec![Import { - what: vec!["a".into(), "b".into()], - from: "to_be_ignored".into(), - from_oid: None, - }], - definitions: vec![Definition( - "Person".into(), - Rust::struct_from_fields(vec![ - Field::from_name_type("name", RustType::String(Size::Any, Charset::Utf8)), - Field::from_name_type("birth", RustType::Complex("City".into(), None)), - ]), - )], - ..Default::default() - } - .to_sql(); - assert_eq!("Manfred", &model.name); - assert!(model.imports.is_empty()); - assert_eq!( - &vec![ - Definition( - "Person".into(), - Sql::Table( - vec![ - Column { - name: "id".into(), - sql: SqlType::Serial, - primary_key: true - }, - Column { - name: "name".into(), - sql: SqlType::NotNull(SqlType::Text.into()), - primary_key: false - }, - Column { - name: "birth".into(), - sql: SqlType::NotNull( - SqlType::References( - "City".into(), - FOREIGN_KEY_DEFAULT_COLUMN.into(), - Some(Action::Cascade), - Some(Action::Cascade), - ) - .into() - ), - primary_key: false - }, - ], - vec![] - ) - ), - Definition( - "Person_Index_birth".to_string(), - Sql::Index("Person".into(), vec!["birth".into()]) - ), - Definition( - "DelChilds_Person".into(), - Sql::AbandonChildrenFunction( - "Person".into(), - vec![("birth".into(), "City".into(), "id".into())], - ) - ) - ], - &model.definitions - ); - } - - #[test] - fn test_conversion_data_enum() { - let model = Model { - name: "Hurray".into(), - imports: vec![Import { - what: vec!["a".into(), "b".into()], - from: "to_be_ignored".into(), - from_oid: None, - }], - definitions: vec![Definition( - "PersonState".into(), - Rust::DataEnum( - vec![ - DataVariant::from_name_type( - "DeadSince", - RustType::String(Size::Any, Charset::Utf8), - ), - DataVariant::from_name_type( - "Alive", - RustType::Complex("Person".into(), Some(Tag::DEFAULT_UTF8_STRING)), - ), - ] - .into(), - ), - )], - ..Default::default() - } - .to_sql(); - assert_eq!("Hurray", &model.name); - assert!(model.imports.is_empty()); - assert_eq!( - &vec![ - Definition( - "PersonState".into(), - Sql::Table( - vec![ - Column { - name: "id".into(), - sql: SqlType::Serial, - primary_key: true - }, - Column { - name: "dead_since".into(), - sql: SqlType::Text, - primary_key: false - }, - Column { - name: "alive".into(), - sql: SqlType::References( - "Person".into(), - FOREIGN_KEY_DEFAULT_COLUMN.into(), - Some(Action::Cascade), - Some(Action::Cascade), - ), - primary_key: false - }, - ], - vec![Constraint::OneNotNull(vec![ - "dead_since".into(), - "alive".into(), - ])] - ) - ), - Definition( - "PersonState_Index_alive".to_string(), - Sql::Index("PersonState".into(), vec!["alive".into()]) - ), - Definition( - "DelChilds_PersonState".into(), - Sql::AbandonChildrenFunction( - "PersonState".into(), - vec![("alive".into(), "Person".into(), "id".into())], - ) - ) - ], - &model.definitions - ); - } - - #[test] - fn test_conversion_enum() { - let model = Model { - name: "Alfred".into(), - imports: vec![Import { - what: vec!["a".into(), "b".into()], - from: "to_be_ignored".into(), - from_oid: None, - }], - definitions: vec![Definition( - "City".into(), - Rust::Enum(vec!["Esslingen".into(), "Stuttgart".into()].into()), - )], - ..Default::default() - } - .to_sql(); - assert_eq!("Alfred", &model.name); - assert!(model.imports.is_empty()); - assert_eq!( - &vec![ - Definition( - "City".into(), - Sql::Enum(vec!["Esslingen".into(), "Stuttgart".into(),],) - ), - Definition( - "SilentlyPreventAnyDeleteOnCity".into(), - Sql::SilentlyPreventAnyDelete("City".into()) - ) - ], - &model.definitions - ); - } - - #[test] - fn test_conversion_struct_with_vec() { - let model = Model { - name: "Bernhard".into(), - imports: vec![], - definitions: vec![Definition( - "SomeStruct".into(), - Rust::struct_from_fields(vec![ - Field::from_name_type( - "list_of_primitive", - RustType::Vec( - Box::new(RustType::String(Size::Any, Charset::Utf8)), - Size::Any, - EncodingOrdering::Keep, - ), - ), - Field::from_name_type( - "list_of_reference", - RustType::Vec( - Box::new(RustType::Complex( - "ComplexType".into(), - Some(Tag::DEFAULT_UTF8_STRING), - )), - Size::Any, - EncodingOrdering::Keep, - ), - ), - ]), - )], - ..Default::default() - } - .to_sql(); - assert_eq!("Bernhard", &model.name); - assert!(model.imports.is_empty()); - assert_eq!( - &model.definitions, - &vec![ - Definition( - "SomeStruct".into(), - Sql::Table( - vec![Column { - name: "id".into(), - sql: SqlType::Serial, - primary_key: true - }], - vec![], - ) - ), - Definition( - "SomeStruct_ListOfPrimitive".into(), - Sql::Table( - vec![ - Column { - name: "list".into(), - sql: SqlType::References( - "SomeStruct".into(), - "id".into(), - Some(Action::Cascade), - Some(Action::Cascade), - ) - .not_null(), - primary_key: false, - }, - Column { - name: "value".into(), - sql: SqlType::Text.not_null(), - primary_key: false, - }, - ], - vec![Constraint::CombinedPrimaryKey(vec![ - "list".into(), - "value".into() - ])] - ) - ), - Definition( - "SomeStruct_ListOfReference".into(), - Sql::Table( - vec![ - Column { - name: "list".into(), - sql: SqlType::References( - "SomeStruct".into(), - "id".into(), - Some(Action::Cascade), - Some(Action::Cascade), - ) - .not_null(), - primary_key: false, - }, - Column { - name: "value".into(), - sql: SqlType::References( - "ComplexType".into(), - "id".into(), - Some(Action::Cascade), - Some(Action::Cascade) - ) - .not_null(), - primary_key: false, - }, - ], - vec![Constraint::CombinedPrimaryKey(vec![ - "list".into(), - "value".into() - ])] - ) - ), - Definition( - "SomeStruct_ListOfPrimitive_Index_list".to_string(), - Sql::Index("SomeStruct_ListOfPrimitive".into(), vec!["list".into()]) - ), - Definition( - "SomeStruct_ListOfPrimitive_Index_value".to_string(), - Sql::Index("SomeStruct_ListOfPrimitive".into(), vec!["value".into()]) - ), - Definition( - "SomeStruct_ListOfReference_Index_list".to_string(), - Sql::Index("SomeStruct_ListOfReference".into(), vec!["list".into()]) - ), - Definition( - "SomeStruct_ListOfReference_Index_value".to_string(), - Sql::Index("SomeStruct_ListOfReference".into(), vec!["value".into()]) - ), - Definition( - "DelChilds_SomeStruct_ListOfReference".into(), - Sql::AbandonChildrenFunction( - "SomeStruct_ListOfReference".into(), - vec![("value".into(), "ComplexType".into(), "id".into())] - ) - ) - ], - ); - } - - #[test] - fn test_conversion_tuple_struct() { - let model = Model { - name: "Hurray".into(), - imports: vec![Import { - what: vec!["a".into(), "b".into()], - from: "to_be_ignored".into(), - from_oid: None, - }], - definitions: vec![ - Definition( - "Whatever".into(), - Rust::tuple_struct_from_type(RustType::String(Size::Any, Charset::Utf8)), - ), - Definition( - "Whatelse".into(), - Rust::tuple_struct_from_type(RustType::Complex( - "Whatever".into(), - Some(Tag::DEFAULT_UTF8_STRING), - )), - ), - ], - ..Default::default() - } - .to_sql(); - assert_eq!("Hurray", &model.name); - assert!(model.imports.is_empty()); - assert_eq!( - &vec![ - Definition( - "Whatever".into(), - Sql::Table( - vec![Column { - name: "id".into(), - sql: SqlType::Serial, - primary_key: true - },], - vec![] - ) - ), - Definition( - "Whatelse".into(), - Sql::Table( - vec![Column { - name: "id".into(), - sql: SqlType::Serial, - primary_key: true - },], - vec![] - ) - ), - Definition( - "WhateverListEntry".into(), - Sql::Table( - vec![ - Column { - name: "list".into(), - sql: SqlType::NotNull( - SqlType::References( - "Whatever".into(), - "id".into(), - Some(Action::Cascade), - Some(Action::Cascade), - ) - .into() - ), - primary_key: false - }, - Column { - name: "value".into(), - sql: SqlType::NotNull(SqlType::Text.into()), - primary_key: false - }, - ], - vec![Constraint::CombinedPrimaryKey(vec![ - "list".into(), - "value".into() - ])] - ) - ), - Definition( - "WhatelseListEntry".into(), - Sql::Table( - vec![ - Column { - name: TUPLE_LIST_ENTRY_PARENT_COLUMN.into(), - sql: SqlType::NotNull( - SqlType::References( - "Whatelse".into(), - "id".into(), - Some(Action::Cascade), - Some(Action::Cascade), - ) - .into() - ), - primary_key: false - }, - Column { - name: TUPLE_LIST_ENTRY_VALUE_COLUMN.into(), - sql: SqlType::NotNull( - SqlType::References( - "Whatever".into(), - FOREIGN_KEY_DEFAULT_COLUMN.into(), - Some(Action::Cascade), - Some(Action::Cascade), - ) - .into() - ), - primary_key: false - }, - ], - vec![Constraint::CombinedPrimaryKey(vec![ - TUPLE_LIST_ENTRY_PARENT_COLUMN.into(), - TUPLE_LIST_ENTRY_VALUE_COLUMN.into() - ])] - ) - ), - Definition( - format!("WhateverListEntry_Index_{}", TUPLE_LIST_ENTRY_PARENT_COLUMN), - Sql::Index( - "WhateverListEntry".into(), - vec![TUPLE_LIST_ENTRY_PARENT_COLUMN.into()] - ) - ), - Definition( - format!("WhateverListEntry_Index_{}", TUPLE_LIST_ENTRY_VALUE_COLUMN), - Sql::Index( - "WhateverListEntry".into(), - vec![TUPLE_LIST_ENTRY_VALUE_COLUMN.into()] - ) - ), - Definition( - format!("WhatelseListEntry_Index_{}", TUPLE_LIST_ENTRY_PARENT_COLUMN), - Sql::Index( - "WhatelseListEntry".into(), - vec![TUPLE_LIST_ENTRY_PARENT_COLUMN.into()] - ) - ), - Definition( - format!("WhatelseListEntry_Index_{}", TUPLE_LIST_ENTRY_VALUE_COLUMN), - Sql::Index( - "WhatelseListEntry".into(), - vec![TUPLE_LIST_ENTRY_VALUE_COLUMN.into()] - ) - ), - Definition( - "DelChilds_WhatelseListEntry".into(), - Sql::AbandonChildrenFunction( - "WhatelseListEntry".into(), - vec![( - TUPLE_LIST_ENTRY_VALUE_COLUMN.into(), - "Whatever".into(), - "id".into() - )], - ) - ) - ], - &model.definitions - ); - } - - #[test] - fn test_conversion_on_first_level_name_clash() { - let model = Model { - name: "Alfred".into(), - imports: vec![Import { - what: vec!["a".into(), "b".into()], - from: "to_be_ignored".into(), - from_oid: None, - }], - definitions: vec![Definition( - "City".into(), - Rust::struct_from_fields(vec![Field::from_name_type( - "id", - RustType::String(Size::Any, Charset::Utf8), - )]), - )], - ..Default::default() - } - .to_sql(); - assert_eq!("Alfred", &model.name); - assert!(model.imports.is_empty()); - assert_eq!( - &vec![Definition( - "City".into(), - Sql::Table( - vec![ - Column { - name: FOREIGN_KEY_DEFAULT_COLUMN.into(), - sql: SqlType::Serial, - primary_key: true - }, - Column { - name: "id_".into(), - sql: SqlType::NotNull(SqlType::Text.into()), - primary_key: false - }, - ], - vec![] - ) - ),], - &model.definitions - ); - } - - #[test] - fn test_rust_to_sql_to_rust() { - assert_eq!(RustType::Bool.to_sql().to_rust(), RustType::Bool); - assert_eq!( - RustType::I8(Range::inclusive(0, i8::MAX)) - .to_sql() - .to_rust(), - RustType::I16(Range::inclusive(0, i16::MAX)) - ); - assert_eq!( - RustType::U8(Range::inclusive(0, u8::MAX)) - .to_sql() - .to_rust(), - RustType::I16(Range::inclusive(0, i16::MAX)) - ); - assert_eq!( - RustType::I16(Range::inclusive(0, i16::MAX)) - .to_sql() - .to_rust(), - RustType::I16(Range::inclusive(0, i16::MAX)) - ); - assert_eq!( - RustType::U16(Range::inclusive(0, i16::MAX as u16)) - .to_sql() - .to_rust(), - RustType::I16(Range::inclusive(0, i16::MAX)) - ); - assert_eq!( - RustType::U16(Range::inclusive(0, u16::MAX)) - .to_sql() - .to_rust(), - RustType::I32(Range::inclusive(0, i32::MAX)) - ); - assert_eq!( - RustType::I32(Range::inclusive(0, i32::MAX)) - .to_sql() - .to_rust(), - RustType::I32(Range::inclusive(0, i32::MAX)) - ); - assert_eq!( - RustType::U32(Range::inclusive(0, i32::MAX as u32)) - .to_sql() - .to_rust(), - RustType::I32(Range::inclusive(0, i32::MAX)) - ); - assert_eq!( - RustType::U32(Range::inclusive(0, u32::MAX)) - .to_sql() - .to_rust(), - RustType::I64(Range::inclusive(0, i64::MAX)) - ); - assert_eq!( - RustType::I64(Range::inclusive(0, i64::MAX)) - .to_sql() - .to_rust(), - RustType::I64(Range::inclusive(0, i64::MAX)) - ); - assert_eq!( - RustType::U64(Range::none()).to_sql().to_rust(), - RustType::I64(Range::inclusive(0, i64::MAX)) - ); - assert_eq!( - RustType::U64(Range::inclusive(Some(0), Some(u64::MAX))) - .to_sql() - .to_rust(), - RustType::I64(Range::inclusive(0, i64::MAX)) - ); - - assert_eq!( - RustType::String(Size::Any, Charset::Utf8) - .to_sql() - .to_rust(), - RustType::String(Size::Any, Charset::Utf8), - ); - assert_eq!( - RustType::VecU8(Size::Any).to_sql().to_rust(), - RustType::VecU8(Size::Any) - ); - assert_eq!( - RustType::Vec( - Box::new(RustType::String(Size::Any, Charset::Utf8)), - Size::Any, - EncodingOrdering::Keep - ) - .to_sql() - .to_rust(), - RustType::Vec( - Box::new(RustType::String(Size::Any, Charset::Utf8)), - Size::Any, - EncodingOrdering::Keep - ), - ); - assert_eq!( - RustType::Option(Box::new(RustType::VecU8(Size::Any))) - .to_sql() - .to_rust(), - RustType::Option(Box::new(RustType::VecU8(Size::Any))), - ); - assert_eq!( - RustType::Complex("MuchComplex".into(), None) - .to_sql() - .to_rust(), - RustType::Complex("MuchComplex".into(), None), - ); - } - - #[test] - fn test_sql_to_rust() { - // only cases that are not already tested by above - assert_eq!( - SqlType::NotNull(SqlType::Serial.into()).to_rust(), - RustType::I32(Range::inclusive(0, i32::MAX)) - ); - } - - #[test] - fn test_nullable() { - assert_eq!( - SqlType::NotNull(SqlType::Serial.into()).nullable(), - SqlType::Serial - ); - assert_eq!(SqlType::Serial.nullable(), SqlType::Serial); - } - - #[test] - fn test_to_string() { - assert_eq!("SMALLINT", &SqlType::SmallInt.to_string()); - assert_eq!("INTEGER", &SqlType::Integer.to_string()); - assert_eq!("BIGINT", &SqlType::BigInt.to_string()); - assert_eq!("SERIAL", &SqlType::Serial.to_string()); - assert_eq!("BOOLEAN", &SqlType::Boolean.to_string()); - assert_eq!("TEXT", &SqlType::Text.to_string()); - assert_eq!( - "SMALLINT[]", - &SqlType::Array(SqlType::SmallInt.into()).to_string() - ); - assert_eq!( - "TEXT NOT NULL", - &SqlType::NotNull(SqlType::Text.into()).to_string() - ); - assert_eq!( - "INTEGER REFERENCES tablo(columno)", - &SqlType::References("tablo".into(), "columno".into(), None, None).to_string() - ); - assert_eq!( - "INTEGER REFERENCES tablo(columno) ON DELETE CASCADE ON UPDATE RESTRICT", - &SqlType::References( - "tablo".into(), - "columno".into(), - Some(Action::Cascade), - Some(Action::Restrict), - ) - .to_string() - ); - assert_eq!( - "INTEGER REFERENCES table(column) NOT NULL", - &SqlType::NotNull( - SqlType::References("table".into(), "column".into(), None, None).into() - ) - .to_string() - ); - assert_eq!( - "INTEGER REFERENCES table(column) ON DELETE RESTRICT ON UPDATE CASCADE NOT NULL", - &SqlType::NotNull( - SqlType::References( - "table".into(), - "column".into(), - Some(Action::Restrict), - Some(Action::Cascade), - ) - .into() - ) - .to_string() - ); - } -} diff --git a/src/cli.rs b/src/cli.rs index d6c05856..0750712b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -27,11 +27,9 @@ const ARG_CONVERSION_TARGET: [&str; 5] = [ pub const CONVERSION_TARGET_RUST: &str = "rust"; pub const CONVERSION_TARGET_PROTO: &str = "proto"; -pub const CONVERSION_TARGET_SQL: &str = "sql"; -pub const CONVERSION_TARGET_POSSIBLE_VALUES: [&str; 3] = [ +pub const CONVERSION_TARGET_POSSIBLE_VALUES: [&str; 2] = [ CONVERSION_TARGET_RUST, CONVERSION_TARGET_PROTO, - CONVERSION_TARGET_SQL, ]; #[derive(Debug)] diff --git a/src/converter.rs b/src/converter.rs index cf975160..52b63c6e 100644 --- a/src/converter.rs +++ b/src/converter.rs @@ -1,12 +1,9 @@ use crate::gen::protobuf::Error as ProtobufGeneratorError; use crate::gen::protobuf::ProtobufDefGenerator as ProtobufGenerator; use crate::gen::rust::RustCodeGenerator as RustGenerator; -use crate::gen::sql::Error as SqlGeneratorError; -use crate::gen::sql::SqlDefGenerator as SqlGenerator; use crate::gen::Generator; use crate::model::lor::Error as ResolveError; use crate::model::protobuf::ToProtobufModel; -use crate::model::sql::ToSqlModel; use crate::model::Model; use crate::model::{Error as ModelError, MultiModuleResolver}; use crate::parser::Tokenizer; @@ -18,7 +15,6 @@ use std::path::Path; pub enum Error { RustGenerator, ProtobufGenerator(ProtobufGeneratorError), - SqlGenerator(SqlGeneratorError), Model(ModelError), Io(IoError), ResolveError(ResolveError), @@ -30,12 +26,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: SqlGeneratorError) -> Self { - Error::SqlGenerator(e) - } -} - impl From for Error { fn from(m: ModelError) -> Self { Error::Model(m) @@ -127,42 +117,6 @@ impl Converter { Ok(files) } - - pub fn to_sql>( - &self, - directory: D, - ) -> Result>, Error> { - self.to_sql_with(directory, SqlGenerator::default()) - } - - pub fn to_sql_with>( - &self, - directory: D, - mut generator: SqlGenerator, - ) -> Result>, Error> { - let models = self.models.try_resolve_all()?; - let scope = models.iter().collect::>(); - let mut files = HashMap::with_capacity(models.len()); - - for model in &models { - generator.reset(); - generator.add_model(model.to_rust_with_scope(&scope[..]).to_sql()); - - files.insert( - model.name.clone(), - generator - .to_string()? - .into_iter() - .map(|(file, content)| { - ::std::fs::write(directory.as_ref().join(&file), content)?; - Ok::<_, Error>(file) - }) - .collect::, _>>()?, - ); - } - - Ok(files) - } } #[deprecated(note = "Use the Converter instead")] @@ -208,33 +162,3 @@ pub fn convert_to_proto, D: AsRef>( } Ok(files) } - -#[deprecated(note = "Use the Converter instead")] -pub fn convert_to_sql, D: AsRef>( - file: F, - dir: D, -) -> Result, Error> { - #[allow(deprecated)] - convert_to_sql_with(file, dir, SqlGenerator::default()) -} - -#[deprecated(note = "Use the Converter instead")] -pub fn convert_to_sql_with, D: AsRef>( - file: F, - dir: D, - mut generator: SqlGenerator, -) -> Result, Error> { - let input = ::std::fs::read_to_string(file)?; - let tokens = Tokenizer.parse(&input); - let model = Model::try_from(tokens)?.try_resolve()?; - - generator.add_model(model.to_rust().to_sql()); - let output = generator.to_string()?; - - let mut files = Vec::new(); - for (file, content) in output { - ::std::fs::write(dir.as_ref().join(&file), content)?; - files.push(file); - } - Ok(files) -} diff --git a/src/io/async_psql/mod.rs b/src/io/async_psql/mod.rs deleted file mode 100644 index 238aa349..00000000 --- a/src/io/async_psql/mod.rs +++ /dev/null @@ -1,465 +0,0 @@ -use bytes::Buf; -pub use futures::future::join_all; -pub use futures::future::try_join_all; -use futures::lock::Mutex; -use std::borrow::Cow; -use std::collections::HashMap; -use std::error; -use std::fmt; -use std::sync::Arc; -pub use tokio::join; -pub use tokio::try_join; -use tokio_postgres::types::{ToSql, Type}; -pub use tokio_postgres::Error as PsqlError; -pub use tokio_postgres::Row; -use tokio_postgres::{ - CancelToken, Client, CopyInSink, CopyOutStream, RowStream, SimpleQueryMessage, Statement, -}; -use tokio_postgres::{ToStatement, Transaction}; - -#[derive(Clone)] -pub enum StatementState { - Awaiting(Arc>), - Ready(Statement), -} - -pub enum TransactionOrClient<'a> { - Client(&'a mut Client), - Transaction(Transaction<'a>), -} - -impl TransactionOrClient<'_> { - /// If this instance is a [`Transaction`], it will consume it and commit - /// any pending changes made with it. When this instance is a [`Client`], - /// it has no effect besides consuming itself. - /// - /// [`Transaction`]: Transaction - /// [`Client`]: Client - pub async fn commit(self) -> Result<(), PsqlError> { - match self { - TransactionOrClient::Client(_) => Ok(()), - TransactionOrClient::Transaction(transaction) => transaction.commit().await, - } - } - - /// If this instance is a [`Transaction`], it will consume it and dicard - /// any pending changes made with it. When this instance is a [`Client`], - /// it has no effect besides consuming itself. - /// - /// [`Transaction`]: Transaction - /// [`Client`]: Client - pub async fn rollback(self) -> Result<(), PsqlError> { - match self { - TransactionOrClient::Client(_) => Ok(()), - TransactionOrClient::Transaction(transaction) => transaction.rollback().await, - } - } -} - -/// Combines a [`Cache`] and a [`Transaction`] with useful methods -/// to cache re-occurring statements efficiently. -/// -/// [`Cache`]: Cache -/// [`Transaction`]: Transaction -pub struct Context<'a> { - cache: Cache, - toc: TransactionOrClient<'a>, -} - -impl<'i> Context<'i> { - pub fn new_direct(client: &'i mut Client) -> Self { - Self { - cache: Default::default(), - toc: TransactionOrClient::Client(client), - } - } - - pub fn new_transactional(transaction: Transaction<'i>) -> Self { - Self { - cache: Default::default(), - toc: TransactionOrClient::Transaction(transaction), - } - } - - /// This function will try to retrieve a prepared [`Statement`] from the [`Cache`]. - /// In doing so, it will first try to retrieve the prepared [`Statement`] from the - /// fast lookup map of the [`Cache`]. If this does not provide a result, the `slow` - /// lookup map will be engaged. This will either yield - /// - no result again. In this case, first an [`Awaiting`] marker will be stored - /// so that further calls to this method for the same `statement_str` will not issue - /// further prepare requests on the backend. Then, the backend will be requested to - /// create a new prepared statement. The result will be stored in the lookup map as - /// [`Ready`] and all waiting calls for the same `statement_str` will be notified. - /// - an [`Awaiting`] marker. In this case, the notification will be awaited. Once notified, - /// the lookup map will be re-engaged, hopefully resulting in: - /// - a [`Ready`] marker. A clone of the prepared [`Statement`] will be returned. - /// - /// [`Statement`]: Statement - /// [`Cache`]: Cache - /// [`Awaiting`]: StatementState::Awaiting - /// [`Ready`]: StatementState::Ready - pub async fn prepared>>( - &self, - statement_str: I, - ) -> Result { - let statement_str = statement_str.into(); - loop { - if let Some(statement) = self.cache.fast.get(&statement_str) { - return Ok(statement.clone()); - } - - let mut slow_locked = self.cache.slow.lock().await; - if let Some(statement) = slow_locked.get(&statement_str) { - match statement { - StatementState::Awaiting(mutex) => { - let local = mutex.clone(); - drop(slow_locked); // allow others to access the map - drop(local.lock().await); // await the lock to be released - continue; // this should now result in StatementState::Ready(_) - } - StatementState::Ready(statement) => return Ok(statement.clone()), - }; - } else { - let mutex = Arc::new(Mutex::new(())); - let lock = mutex.lock().await; // this will complete immediately - - // insert placeholder so that further calls of prepared() for the same statement_str - // will not prepare further Statements for the same content - slow_locked.insert( - statement_str.clone(), - StatementState::Awaiting(mutex.clone()), - ); - drop(slow_locked); // allow others to access the map - - // Actually prepare the statement. Because this is awaiting the response, - // it is possible for another future on the same thread to access the cache - // in the meantime. Because of the placeholder Mutex, the other future will - // not prepare a second/third/... Statement with the same content - let statement = self.prepare(&statement_str).await?; - - // Cache the received statement so that any following call to the cache will - // have access to the result immediately - self.cache - .slow - .lock() - .await - .insert(statement_str, StatementState::Ready(statement.clone())); - - drop(lock); // this will "notify" all waiting futures - return Ok(statement); - } - } - } - - /// Disassembles this [`Context`] and returns the underlying - /// [`Cache`] and [`Transaction`]. The [`Cache`] will be optimized - /// before returning, if you do not wish for that, see [`split_unoptimized`] - /// instead. - /// - /// [`Context`]: Context - /// [`Cache`]: Cache - /// [`Transaction`]: Transaction - /// [`split_unoptimized`]: Context::split_unoptimized - pub fn split(mut self) -> (Cache, TransactionOrClient<'i>) { - self.optimize_cache(); - self.split_unoptimized() - } - - /// Disassembles this [`Context`] and returns the underlying - /// [`Cache`] and [`Transaction`]. The [`Cache`] will not be optimized - /// before returning. See also [`split`] for automatically optimizing the - /// [`Cache`] before returning. - /// - /// [`Context`]: Context - /// [`Cache`]: Cache - /// [`Transaction`]: Transaction - /// [`split`]: Context::split - pub fn split_unoptimized(self) -> (Cache, TransactionOrClient<'i>) { - (self.cache, self.toc) - } - - /// Optimizes the [`Cache`] without disassembling - /// - /// [`Cache`]: Cache - pub fn optimize_cache(&mut self) { - self.cache.optimize(); - } - - /// Like `Client::prepare`. - pub async fn prepare(&self, query: &str) -> Result { - match &self.toc { - TransactionOrClient::Client(client) => client.prepare(query).await, - TransactionOrClient::Transaction(transaction) => transaction.prepare(query).await, - } - } - - /// Like `Client::prepare_typed`. - pub async fn prepare_typed( - &self, - query: &str, - parameter_types: &[Type], - ) -> Result { - match &self.toc { - TransactionOrClient::Client(client) => { - client.prepare_typed(query, parameter_types).await - } - TransactionOrClient::Transaction(transaction) => { - transaction.prepare_typed(query, parameter_types).await - } - } - } - - /// Like `Client::query`. - pub async fn query( - &self, - statement: &T, - params: &[&(dyn ToSql + Sync)], - ) -> Result, PsqlError> - where - T: ?Sized + ToStatement, - { - match &self.toc { - TransactionOrClient::Client(client) => client.query(statement, params).await, - TransactionOrClient::Transaction(transaction) => { - transaction.query(statement, params).await - } - } - } - - /// Like `Client::query_one`. - pub async fn query_one( - &self, - statement: &T, - params: &[&(dyn ToSql + Sync)], - ) -> Result - where - T: ?Sized + ToStatement, - { - match &self.toc { - TransactionOrClient::Client(client) => client.query_one(statement, params).await, - TransactionOrClient::Transaction(transaction) => { - transaction.query_one(statement, params).await - } - } - } - - /// Like `Client::query_opt`. - pub async fn query_opt( - &self, - statement: &T, - params: &[&(dyn ToSql + Sync)], - ) -> Result, PsqlError> - where - T: ?Sized + ToStatement, - { - match &self.toc { - TransactionOrClient::Client(client) => client.query_opt(statement, params).await, - TransactionOrClient::Transaction(transaction) => { - transaction.query_opt(statement, params).await - } - } - } - - /// Like `Client::query_raw`. - pub async fn query_raw<'b, T, I>( - &self, - statement: &T, - params: I, - ) -> Result - where - T: ?Sized + ToStatement, - I: IntoIterator, - I::IntoIter: ExactSizeIterator, - { - match &self.toc { - TransactionOrClient::Client(client) => client.query_raw(statement, params).await, - TransactionOrClient::Transaction(transaction) => { - transaction.query_raw(statement, params).await - } - } - } - - /// Like `Client::execute`. - pub async fn execute( - &self, - statement: &T, - params: &[&(dyn ToSql + Sync)], - ) -> Result - where - T: ?Sized + ToStatement, - { - match &self.toc { - TransactionOrClient::Client(client) => client.execute(statement, params).await, - TransactionOrClient::Transaction(transaction) => { - transaction.execute(statement, params).await - } - } - } - - /// Like `Client::execute_iter`. - pub async fn execute_raw<'b, I, T>(&self, statement: &T, params: I) -> Result - where - T: ?Sized + ToStatement, - I: IntoIterator, - I::IntoIter: ExactSizeIterator, - { - match &self.toc { - TransactionOrClient::Client(client) => client.execute_raw(statement, params).await, - TransactionOrClient::Transaction(transaction) => { - transaction.execute_raw(statement, params).await - } - } - } - /// Like `Client::copy_in`. - pub async fn copy_in(&self, statement: &T) -> Result, PsqlError> - where - T: ?Sized + ToStatement, - U: Buf + 'static + Send, - { - match &self.toc { - TransactionOrClient::Client(client) => client.copy_in(statement).await, - TransactionOrClient::Transaction(transaction) => transaction.copy_in(statement).await, - } - } - - /// Like `Client::copy_out`. - pub async fn copy_out(&self, statement: &T) -> Result - where - T: ?Sized + ToStatement, - { - match &self.toc { - TransactionOrClient::Client(client) => client.copy_out(statement).await, - TransactionOrClient::Transaction(transaction) => transaction.copy_out(statement).await, - } - } - - /// Like `Client::simple_query`. - pub async fn simple_query(&self, query: &str) -> Result, PsqlError> { - match &self.toc { - TransactionOrClient::Client(client) => client.simple_query(query).await, - TransactionOrClient::Transaction(transaction) => transaction.simple_query(query).await, - } - } - - /// Like `Client::batch_execute`. - pub async fn batch_execute(&self, query: &str) -> Result<(), PsqlError> { - match &self.toc { - TransactionOrClient::Client(client) => client.batch_execute(query).await, - TransactionOrClient::Transaction(transaction) => transaction.batch_execute(query).await, - } - } - - /// Like `Client::cancel_token`. - pub fn cancel_token(&self) -> CancelToken { - match &self.toc { - TransactionOrClient::Client(client) => client.cancel_token(), - TransactionOrClient::Transaction(transaction) => transaction.cancel_token(), - } - } -} - -/// A cache for prepared statements. It has two lookup maps: -/// - one `fast` map, which is not protected by a Mutex and allows -/// very fast concurrent read access -/// - one `slow` map, which is protected by a Mutex and allows the -/// cache to grow as it is being used. -/// In regular intervals (for example once a [`Transaction`] is -/// going to be submitted) the cache should be optimized. Optimizing -/// the cache will require exclusive access to it and and it will move -/// all prepared statements from the `slow` to the `fast` lookup map. -/// This allows a further [`Context`] to access all currently known -/// prepared statements without locking. -/// -/// The idea is, that in a system with re-occurring statements, the [`Cache`] -/// is warming-up once, and then retrieves these prepared statements very fast. -/// -/// [`Transaction`]: Transaction -/// [`Context`]: Context -/// [`Cache`]: Cache -#[derive(Default)] -pub struct Cache { - fast: HashMap, Statement>, - slow: Mutex, StatementState>>, -} - -impl Cache { - /// Creates a new [`Context`] by using the given [`Transaction`] and this instance of [`Cache`]. - /// - /// [`Context`]: Context - /// [`Transaction`]: Transaction - /// [`Cache`]: Cache - pub fn into_transaction_context(self, transaction: Transaction) -> Context { - Context { - cache: self, - toc: TransactionOrClient::Transaction(transaction), - } - } - /// Creates a new [`Context`] by using the given [`Client`] and this instance of [`Cache`]. - /// - /// [`Context`]: Context - /// [`Client`]: Client - /// [`Cache`]: Cache - pub fn into_client_context(self, client: &mut Client) -> Context { - Context { - cache: self, - toc: TransactionOrClient::Client(client), - } - } - - /// The fast but read-only lookup map - pub fn iter_over_fast(&self) -> impl Iterator { - self.fast.iter().map(|(key, value)| (key.as_ref(), value)) - } - - /// This moves all new prepared statements to the read-only but fast - /// lookup map. - pub fn optimize(&mut self) { - // Removes all key-value pairs. Keeps the allocated memory for re-use - self.fast.extend( - self.slow - .get_mut() - .drain() - .flat_map(|(key, value)| match value { - StatementState::Ready(statement) => Some((key, statement)), - StatementState::Awaiting(_) => None, // failed, so drop it - }), - ); - } -} - -#[derive(Debug)] -pub enum Error { - Psql(PsqlError), - UnexpectedVariant(usize), - NoEntryFoundForId(i32), - RowUnloadable, -} - -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - #[allow(clippy::match_same_arms)] - match self { - Error::Psql(psql) => psql.source(), - Error::UnexpectedVariant(_) => None, - Error::NoEntryFoundForId(_) => None, - Error::RowUnloadable => None, - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::Psql(psql) => psql.fmt(f), - Error::UnexpectedVariant(index) => write!(f, "Unexpected variant index: {}", index), - Error::NoEntryFoundForId(id) => write!(f, "Id {} is unknown", id), - Error::RowUnloadable => write!(f, "The row has an error and cannot be loaded"), - } - } -} - -impl From for Error { - fn from(psql: PsqlError) -> Self { - Error::Psql(psql) - } -} diff --git a/src/io/mod.rs b/src/io/mod.rs index 4e93fd09..49bfac83 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -3,23 +3,8 @@ //! ::io::per Generic Packed Encoding impls and traits //! ::io::per::unaligned UNALIGNED PER specialization //! ::io::per::aligned ALIGNED PER specialization -//! ::io::... Other ASN.1 representations (e.g xer, ber, ...) -//! -//! ::io::async_psql Async PSQL io-utils -//! ::io::protobuf Protocol Buffer io-utils -//! ::io::psql Blocking PSQL io-utils -//! -//! ::io::uper Deprecated UNALIGNED PER decoder/encoder +//! ::io::... Other ASN.1 representations (e.g der, xer, ber, ...) //! ``` pub mod per; -pub mod protobuf; - -#[cfg(feature = "psql")] -pub mod psql; - -#[cfg(feature = "async-psql")] -pub mod async_psql; - -#[cfg(any(feature = "psql", feature = "async-psql"))] -pub mod psql_shared; +pub mod protobuf; \ No newline at end of file diff --git a/src/io/psql/mod.rs b/src/io/psql/mod.rs deleted file mode 100644 index d57d4195..00000000 --- a/src/io/psql/mod.rs +++ /dev/null @@ -1,112 +0,0 @@ -use backtrace::Backtrace; -pub use postgres::row::Row; -pub use postgres::Error as PostgresError; -pub use postgres::Transaction; -use std::fmt::{Display, Formatter}; - -#[derive(Debug)] -pub enum Error { - Postgres(Backtrace, PostgresError), - MissingReturnedIndex(Backtrace), - MissingRow(usize, Backtrace), - MissingColumn(usize, Backtrace), - NoResult(Backtrace), -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for Error {} - -impl Error { - #[inline] - pub fn no_result() -> Self { - Error::NoResult(Backtrace::new()) - } - - pub fn expect_returned_index(row: &Row) -> Result { - Ok(row.try_get::<_, i32>(0)?) - } - - pub fn expect_returned_index_in_rows(rows: &[Row]) -> Result { - if rows.is_empty() { - Err(Error::MissingReturnedIndex(Backtrace::new())) - } else if let Some(row) = rows.get(0) { - Self::expect_returned_index(row) - } else { - Err(Error::MissingReturnedIndex(Backtrace::new())) - } - } - - pub fn value_at_column<'a, T: postgres::types::FromSql<'a>>( - row: &'a Row, - column: usize, - ) -> Result { - Ok(row.try_get::<'a>(column)?) - } - - pub fn value_at<'a, T: postgres::types::FromSql<'a>>( - rows: &'a [Row], - row: usize, - column: usize, - ) -> Result { - if rows.is_empty() || rows.len() <= row { - Err(Error::MissingRow(row, Backtrace::new())) - } else if let Some(r) = rows.get(row) { - Ok(r.try_get::<'a>(column)?) - } else { - Err(Error::MissingColumn(column, Backtrace::new())) - } - } - - pub fn first_present(row: &Row, columns: &[usize]) -> Result { - for column in columns { - if row.try_get::<_, &[u8]>(column).is_ok() { - return Ok(*column); - } - } - Err(Error::no_result()) - } - - pub fn first_not_null<'a, T: postgres::types::FromSql<'a>>( - row: &'a Row, - columns: &[usize], - ) -> Result<(usize, T), Error> { - for column in columns { - match row.try_get::<'a, _, Option>(*column) { - Ok(Some::(value)) => return Ok((*column, value)), - Ok(None::) => {} // null in db, ignore - Err(e) => return Err(Error::from(e)), - }; - } - Err(Error::no_result()) - } -} - -impl From for Error { - fn from(e: PostgresError) -> Self { - Error::Postgres(Backtrace::new(), e) - } -} - -pub trait Representable { - fn table_name(&self) -> &'static str; -} - -pub trait Insertable: Representable { - fn insert_statement(&self) -> &'static str; - fn insert_with(&self, transaction: &mut Transaction) -> Result; -} - -pub trait Queryable: Representable { - fn query_statement() -> &'static str; - fn query_with(transaction: &mut Transaction, id: i32) -> Result - where - Self: Sized; - fn load_from(transaction: &mut Transaction, row: &Row) -> Result - where - Self: Sized; -} diff --git a/src/io/psql_shared/bit_vec_impl.rs b/src/io/psql_shared/bit_vec_impl.rs deleted file mode 100644 index 2d89937e..00000000 --- a/src/io/psql_shared/bit_vec_impl.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::syn::bitstring::BitVec; -use bytes::BytesMut; -use std::error::Error; - -#[cfg(feature = "psql")] -use postgres::types::{FromSql, IsNull, ToSql, Type}; - -#[cfg(all(feature = "async-psql", not(feature = "psql")))] -use tokio_postgres::types::{FromSql, IsNull, ToSql, Type}; - -impl<'a> FromSql<'a> for BitVec { - fn from_sql(ty: &Type, raw: &'a [u8]) -> Result> { - let vec = as FromSql>::from_sql(ty, raw)?; - Ok(BitVec::from_vec_with_trailing_bit_len(vec)) - } - - fn accepts(ty: &Type) -> bool { - as FromSql>::accepts(ty) - } -} - -impl ToSql for BitVec { - fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result> - where - Self: Sized, - { - let vec = self.to_vec_with_trailing_bit_len(); - as ToSql>::to_sql(&vec, ty, out) - } - - fn accepts(ty: &Type) -> bool - where - Self: Sized, - { - as ToSql>::accepts(ty) - } - - fn to_sql_checked( - &self, - ty: &Type, - out: &mut BytesMut, - ) -> Result> { - let vec = self.to_vec_with_trailing_bit_len(); - as ToSql>::to_sql_checked(&vec, ty, out) - } -} diff --git a/src/io/psql_shared/mod.rs b/src/io/psql_shared/mod.rs deleted file mode 100644 index a1107fd5..00000000 --- a/src/io/psql_shared/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod bit_vec_impl; -pub mod unit_impl; diff --git a/src/io/psql_shared/unit_impl.rs b/src/io/psql_shared/unit_impl.rs deleted file mode 100644 index 6381663b..00000000 --- a/src/io/psql_shared/unit_impl.rs +++ /dev/null @@ -1,43 +0,0 @@ -use bytes::BytesMut; -use std::error::Error; - -#[cfg(feature = "psql")] -use postgres::types::{FromSql, IsNull, ToSql, Type}; - -use crate::syn::null::Null; -#[cfg(all(feature = "async-psql", not(feature = "psql")))] -use tokio_postgres::types::{FromSql, IsNull, ToSql, Type}; - -impl<'a> FromSql<'a> for Null { - fn from_sql(_ty: &Type, _raw: &'a [u8]) -> Result> { - Ok(Null) - } - - fn accepts(ty: &Type) -> bool { - > as FromSql>::accepts(ty) - } -} - -impl ToSql for Null { - fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result> - where - Self: Sized, - { - > as ToSql>::to_sql(&None, ty, out) - } - - fn accepts(ty: &Type) -> bool - where - Self: Sized, - { - > as ToSql>::accepts(ty) - } - - fn to_sql_checked( - &self, - ty: &Type, - out: &mut BytesMut, - ) -> Result> { - > as ToSql>::to_sql_checked(&None, ty, out) - } -} diff --git a/src/main.rs b/src/main.rs index ae07ec31..39a3c64e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,6 @@ pub fn main() { rust.set_fields_have_getter_and_setter(params.rust_getter_and_setter); }), cli::CONVERSION_TARGET_PROTO => converter.to_protobuf(¶ms.destination_dir), - cli::CONVERSION_TARGET_SQL => converter.to_sql(¶ms.destination_dir), e => panic!("Unexpected CONVERSION_TARGET={}", e), };