├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README.tpl ├── release.toml ├── src └── lib.rs └── tests ├── db-roundtrips.rs └── should-not-compile.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | on: 3 | push: 4 | branches: [main, v1] 5 | pull_request: 6 | branches: [main, v1] 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | jobs: 11 | test: 12 | strategy: 13 | matrix: 14 | rust_version: [1.65.0, stable, beta] 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Install rust toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: ${{ matrix.rust_version }} 23 | override: true 24 | components: rustfmt, clippy 25 | - name: Build 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: build 29 | - name: Test 30 | uses: actions-rs/cargo@v1 31 | with: 32 | command: test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | README.html 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | # Version 2.1.2 4 | 5 | * Fix new non_local_definitions lint in nightly (#31) 6 | 7 | # Version 2.1.1 8 | 9 | * Add support for structs with internal references to DieselNewTypes (`ethan-lowman-fp` [#30](https://github.com/quodlibetor/diesel-derive-newtype/pull/30)): 10 | 11 | ```rust 12 | #[derive(DieselNewType)] 13 | pub struct MyIdString(String); 14 | 15 | #[derive(Insertable, Queryable)] 16 | #[diesel(table_name = my_entities)] 17 | pub struct NewMyEntity<'a> { 18 | id: &'a MyIdString, // <-- &'a of DieselNewType 19 | } 20 | ``` 21 | 22 | # 2.1.0 23 | 24 | * Update for Diesel 2.1 (`@marhag87`), not compatible with Diesel 2.0.x. 25 | * Bump MSRV to 1.65, because that is Diesel's MSRV. 26 | 27 | # 2.0.1 28 | 29 | * Bind diesel-derive-newtype 2.0.x to Diesel 2.0.x, Diesel 2.1 has trait bounds that are 30 | non-obvious to make compatible with 2.0. 31 | 32 | # 2.0.0 33 | 34 | * Support diesel 2.0. diesel-derive-newtype v1.* supports Diesel 1.* and v2.* supports Diesel 2.* 35 | 36 | # 1.0.2 37 | 38 | * Update syn to 2.0, bump MSRV to 1.56 39 | 40 | # 1.0.1 41 | 42 | * Update syn/quote/proc-macro2 dependencies to 1.x 43 | 44 | # 1.0.0 45 | 46 | * Remove non-dev dependency on `diesel` -- `diesel-derive-newtype` generates generic diesel code. 47 | * CI improvements. 48 | 49 | # 0.1.1 50 | 51 | Bugs Fixed: 52 | 53 | * Issue #5: Deriving NewType in the same module as an unnamespaced result 54 | caused problems. Report and fix by @adwhit 55 | 56 | 57 | # 0.1.0 58 | 59 | Initial release 60 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "diesel-derive-newtype" 3 | version = "2.1.2" 4 | authors = ["Brandon W Maister "] 5 | categories = ["database", "rust-patterns"] 6 | description = "Automatically connect newtypes to Diesel using their wrapped type" 7 | edition = "2018" 8 | keywords = ["newtype", "derive"] 9 | license = "Apache-2.0/MIT" 10 | readme = "README.md" 11 | repository = "https://github.com/quodlibetor/diesel-derive-newtype" 12 | # Inherited MSRV of `syn`. 13 | rust-version = "1.65" 14 | 15 | [dependencies] 16 | proc-macro2 = "1.0.51" 17 | syn = "2.0.10" 18 | quote = "1.0.23" 19 | 20 | [badges] 21 | maintenance = { status = "passively-maintained" } 22 | 23 | [dev-dependencies] 24 | diesel = { version = "2.1.0", features = ["sqlite"], default-features = false } 25 | 26 | [lib] 27 | proc-macro = true 28 | # required by cargo-readme until this is merged: 29 | # https://github.com/livioribeiro/cargo-readme/pull/16 30 | path = "src/lib.rs" 31 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `diesel-derive-newtype` 2 | 3 | Easy-peasy support of newtypes inside of Diesel. 4 | 5 | [![Rust](https://github.com/quodlibetor/diesel-derive-newtype/actions/workflows/test.yml/badge.svg?branch=diesel-2)](https://github.com/quodlibetor/diesel-derive-newtype/actions/workflows/test.yml) [![Crates.io Version](https://img.shields.io/crates/v/diesel-derive-newtype.svg)](https://crates.io/crates/diesel-derive-newtype) 6 | 7 | ### Status 8 | 9 | diesel-derive-newtype is small and basically complete -- except for some features 10 | that aren't supported by Diesel (see "limitations", below). Additionally, I don't 11 | use diesel on a daily basis any more, so most maintenance comes from bug reports 12 | or PRs, please open them! 13 | 14 | ## Installation 15 | 16 | diesel-derive-newtype supports Diesel according to its major version -- 0.x 17 | through 1.x support the corresponding diesel versions, 2.0 supports diesel 2.0, 18 | and 2.1 supports 2.1. Generally new versions of diesel-derive-newtype are 19 | released when compilation or tests are observed to fail with a newer version 20 | of diesel, so newer versions of diesel than the released version of 21 | diesel-derive-newtype *may* work. Bug reports and PRs welcome. 22 | 23 | New features are only developed for the currently supported version of Diesel. 24 | 25 | ```toml 26 | [dependencies] 27 | diesel-derive-newtype = "2.1.0" 28 | ``` 29 | 30 | for Diesel 2.0.x you have to tell cargo not to upgrade diesel-derive-newtype: 31 | 32 | ```toml 33 | [dependencies] 34 | diesel-derive-newtype = "~ 2.0.0" 35 | ``` 36 | 37 | And for Diesel 1.x: 38 | 39 | ```toml 40 | [dependencies] 41 | diesel-derive-newtype = "1.0" 42 | ``` 43 | 44 | 45 | 46 | ## `#[derive(DieselNewType)]` 47 | 48 | This crate exposes a single custom-derive macro `DieselNewType` which 49 | implements `ToSql`, `FromSql`, `FromSqlRow`, `Queryable`, `AsExpression` 50 | and `QueryId` for the single-field tuple struct ([NewType][]) it is applied 51 | to. 52 | 53 | The goal of this project is that: 54 | 55 | * `derive(DieselNewType)` should be enough for you to use newtypes anywhere you 56 | would use their underlying types within Diesel. (plausibly successful) 57 | * Should get the same compile-time guarantees when using your newtypes as 58 | expression elements in Diesel as you do in other rust code (depends on 59 | your desires, see [Limitations][], below.) 60 | 61 | [NewType]: https://aturon.github.io/features/types/newtype.html 62 | 63 | ## Example 64 | 65 | This implementation: 66 | 67 | ```rust 68 | #[macro_use] 69 | extern crate diesel_derive_newtype; 70 | 71 | #[derive(DieselNewType)] // Doesn't need to be on its own line 72 | #[derive(Debug, Hash, PartialEq, Eq)] // required by diesel 73 | struct MyId(i64); 74 | ``` 75 | 76 | Allows you to use the `MyId` struct inside your entities as though they were 77 | the underlying type: 78 | 79 | ```rust 80 | table! { 81 | my_items { 82 | id -> Integer, 83 | val -> Integer, 84 | } 85 | } 86 | 87 | #[derive(Debug, PartialEq, Identifiable, Queryable)] 88 | struct MyItem { 89 | id: MyId, 90 | val: u8, 91 | } 92 | ``` 93 | 94 | Oooohhh. Ahhhh. 95 | 96 | See [the tests][] for a more complete example. 97 | 98 | [the tests]: https://github.com/quodlibetor/diesel-derive-newtype/blob/master/tests/db-roundtrips.rs 99 | 100 | ## Limitations 101 | [limitations]: #limitations 102 | 103 | The `DieselNewtype` derive does not create new _database_ types, or Diesel 104 | serialization types. That is, if you have a `MyId(i64)`, this will use 105 | Diesel's underlying `BigInt` type, which means that even though your 106 | newtypes can be used anywhere the underlying type can be used, *the 107 | underlying types, or any other newtypes of the same underlying type, can be 108 | used as well*. 109 | 110 | At a certain point everything does become bits on the wire, so if we didn't 111 | do it this way then Diesel would have to do it somewhere else, and this is 112 | reasonable default behavior (it's pretty debuggable), but I'm investigating 113 | auto-generating new proxy types as well to make it impossible to construct 114 | an insert statement using a tuple or a mis-typed struct. 115 | 116 | Here's an example of that this type-hole looks like: 117 | 118 | ```rust 119 | #[derive(Debug, Hash, PartialEq, Eq, DieselNewType)] 120 | struct OneId(i64); 121 | 122 | #[derive(Debug, Hash, PartialEq, Eq, DieselNewType)] 123 | struct OtherId(i64); 124 | 125 | #[derive(Debug, Clone, PartialEq, Identifiable, Insertable, Queryable)] 126 | #[diesel(table_name = my_entities)] 127 | pub struct MyEntity { 128 | id: OneId, 129 | val: i32, 130 | } 131 | 132 | fn darn(conn: &Connection) { 133 | // shouldn't allow constructing the wrong type, but does 134 | let OtherId: Vec = my_entities 135 | .select(id) 136 | .filter(id.eq(OtherId(1))) // shouldn't allow filtering by wrong type 137 | .execute(conn).unwrap(); 138 | } 139 | ``` 140 | 141 | See [`tests/should-not-compile.rs`](tests/should-not-compile.rs) for the 142 | things I think should fail to compile. 143 | 144 | I believe that the root cause of this is that Diesel implements the various 145 | expression methods for types that implement `AsExpression`, based on the 146 | _SQL_ type, not caring about `self` and `other`'s Rust type matching. That 147 | seems like a pretty good decision in general, but it is a bit unfortunate 148 | here. 149 | 150 | I hope to find a solution that doesn't involve implementing every 151 | `*Expression` trait manually with an extra bound, but for now you have to 152 | keep in mind that the Diesel methods basically auto-transmute your data into 153 | the underlying SQL type. 154 | 155 | 156 | ## Releasing 157 | 158 | This workflow uses: 159 | 160 | * [cargo-readme](https://crates.io/crates/cargo-readme) 161 | * [cargo-release](https://crates.io/crates/cargo-release) 162 | 163 | Run, note that we always release patch releases unless diesel has got a new 164 | release: 165 | 166 | ``` 167 | cargo readme > README.md 168 | git diff --exit-code --quiet README.* || (git add README.* && git commit -m "chore: Update README") 169 | cargo release patch 170 | ``` 171 | 172 | ## License 173 | 174 | diesel-derive-newtype is licensed under either of 175 | 176 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 177 | http://www.apache.org/licenses/LICENSE-2.0) 178 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 179 | http://opensource.org/licenses/MIT) 180 | 181 | at your option. 182 | 183 | Patches and bug reports welcome! 184 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # `diesel-derive-newtype` 2 | 3 | Easy-peasy support of newtypes inside of Diesel. 4 | 5 | [![Rust](https://github.com/quodlibetor/diesel-derive-newtype/actions/workflows/test.yml/badge.svg?branch=diesel-2)](https://github.com/quodlibetor/diesel-derive-newtype/actions/workflows/test.yml) [![Crates.io Version](https://img.shields.io/crates/v/diesel-derive-newtype.svg)](https://crates.io/crates/diesel-derive-newtype) 6 | 7 | ## Installation 8 | 9 | diesel-derive-newtype supports Diesel according to its major version -- 0.x 10 | through 1.x support the corresponding diesel versions, 2.0 supports diesel 2.0, 11 | and 2.1 supports 2.1. 12 | 13 | New features are only developed for the currently supported version of Diesel. 14 | 15 | 16 | ```toml 17 | [dependencies] 18 | diesel-derive-newtype = "2.1.0" 19 | ``` 20 | 21 | for Diesel 2.0.x you have to tell cargo not to upgrade diesel-derive-newtype: 22 | 23 | ```toml 24 | [dependencies] 25 | diesel-derive-newtype = "~ 2.0.0" 26 | ``` 27 | 28 | And for Diesel 1.x: 29 | 30 | ```toml 31 | [dependencies] 32 | diesel-derive-newtype = "1.0" 33 | ``` 34 | 35 | 36 | 37 | {{readme}} 38 | 39 | 40 | ## Releasing 41 | 42 | This workflow uses: 43 | 44 | * [cargo-readme](https://crates.io/crates/cargo-readme) 45 | * [cargo-release](https://crates.io/crates/cargo-release) 46 | 47 | Run, note that we always release patch releases unless diesel has got a new 48 | release: 49 | 50 | ``` 51 | cargo readme > README.md 52 | git diff --exit-code --quiet README.* || (git add README.* && git commit -m "chore: Update README") 53 | cargo release patch 54 | ``` 55 | 56 | ## License 57 | 58 | diesel-derive-newtype is licensed under either of 59 | 60 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 61 | http://www.apache.org/licenses/LICENSE-2.0) 62 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 63 | http://opensource.org/licenses/MIT) 64 | 65 | at your option. 66 | 67 | Patches and bug reports welcome! 68 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [ 2 | {file="CHANGELOG.md", search="# Unreleased", replace="# Unreleased\n\n# Version {{version}}"}, 3 | ] 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "1024"] // the new default in rust 1.19, quote! takes a lot 2 | 3 | //! # `#[derive(DieselNewType)]` 4 | //! 5 | //! This crate exposes a single custom-derive macro `DieselNewType` which 6 | //! implements `ToSql`, `FromSql`, `FromSqlRow`, `Queryable`, `AsExpression` 7 | //! and `QueryId` for the single-field tuple struct ([NewType][]) it is applied 8 | //! to. 9 | //! 10 | //! The goal of this project is that: 11 | //! 12 | //! * `derive(DieselNewType)` should be enough for you to use newtypes anywhere you 13 | //! would use their underlying types within Diesel. (plausibly successful) 14 | //! * Should get the same compile-time guarantees when using your newtypes as 15 | //! expression elements in Diesel as you do in other rust code (depends on 16 | //! your desires, see [Limitations][], below.) 17 | //! 18 | //! [NewType]: https://aturon.github.io/features/types/newtype.html 19 | //! 20 | //! # Example 21 | //! 22 | //! This implementation: 23 | //! 24 | //! ``` 25 | //! #[macro_use] 26 | //! extern crate diesel_derive_newtype; 27 | //! 28 | //! #[derive(DieselNewType)] // Doesn't need to be on its own line 29 | //! #[derive(Debug, Hash, PartialEq, Eq)] // required by diesel 30 | //! struct MyId(i64); 31 | //! # fn main() {} 32 | //! ``` 33 | //! 34 | //! Allows you to use the `MyId` struct inside your entities as though they were 35 | //! the underlying type: 36 | //! 37 | //! ``` 38 | //! # #[macro_use] extern crate diesel; 39 | //! # #[macro_use] extern crate diesel_derive_newtype; 40 | //! # use diesel::prelude::*; 41 | //! table! { 42 | //! my_items { 43 | //! id -> Integer, 44 | //! val -> Integer, 45 | //! } 46 | //! } 47 | //! 48 | //! # #[derive(DieselNewType)] // Doesn't need to be on its own line 49 | //! # #[derive(Debug, Hash, PartialEq, Eq)] // required by diesel 50 | //! # struct MyId(i64); 51 | //! #[derive(Debug, PartialEq, Identifiable, Queryable)] 52 | //! struct MyItem { 53 | //! id: MyId, 54 | //! val: u8, 55 | //! } 56 | //! # fn main() {} 57 | //! ``` 58 | //! 59 | //! Oooohhh. Ahhhh. 60 | //! 61 | //! See [the tests][] for a more complete example. 62 | //! 63 | //! [the tests]: https://github.com/quodlibetor/diesel-derive-newtype/blob/master/tests/db-roundtrips.rs 64 | //! 65 | //! # Limitations 66 | //! [limitations]: #limitations 67 | //! 68 | //! The `DieselNewtype` derive does not create new _database_ types, or Diesel 69 | //! serialization types. That is, if you have a `MyId(i64)`, this will use 70 | //! Diesel's underlying `BigInt` type, which means that even though your 71 | //! newtypes can be used anywhere the underlying type can be used, *the 72 | //! underlying types, or any other newtypes of the same underlying type, can be 73 | //! used as well*. 74 | //! 75 | //! At a certain point everything does become bits on the wire, so if we didn't 76 | //! do it this way then Diesel would have to do it somewhere else, and this is 77 | //! reasonable default behavior (it's pretty debuggable), but I'm investigating 78 | //! auto-generating new proxy types as well to make it impossible to construct 79 | //! an insert statement using a tuple or a mis-typed struct. 80 | //! 81 | //! Here's an example of that this type-hole looks like: 82 | //! 83 | //! ```rust,ignore 84 | //! #[derive(Debug, Hash, PartialEq, Eq, DieselNewType)] 85 | //! struct OneId(i64); 86 | //! 87 | //! #[derive(Debug, Hash, PartialEq, Eq, DieselNewType)] 88 | //! struct OtherId(i64); 89 | //! 90 | //! #[derive(Debug, Clone, PartialEq, Identifiable, Insertable, Queryable)] 91 | //! #[diesel(table_name = my_entities)] 92 | //! pub struct MyEntity { 93 | //! id: OneId, 94 | //! val: i32, 95 | //! } 96 | //! 97 | //! fn darn(conn: &Connection) { 98 | //! // shouldn't allow constructing the wrong type, but does 99 | //! let OtherId: Vec = my_entities 100 | //! .select(id) 101 | //! .filter(id.eq(OtherId(1))) // shouldn't allow filtering by wrong type 102 | //! .execute(conn).unwrap(); 103 | //! } 104 | //! ``` 105 | //! 106 | //! See [`tests/should-not-compile.rs`](tests/should-not-compile.rs) for the 107 | //! things I think should fail to compile. 108 | //! 109 | //! I believe that the root cause of this is that Diesel implements the various 110 | //! expression methods for types that implement `AsExpression`, based on the 111 | //! _SQL_ type, not caring about `self` and `other`'s Rust type matching. That 112 | //! seems like a pretty good decision in general, but it is a bit unfortunate 113 | //! here. 114 | //! 115 | //! I hope to find a solution that doesn't involve implementing every 116 | //! `*Expression` trait manually with an extra bound, but for now you have to 117 | //! keep in mind that the Diesel methods basically auto-transmute your data into 118 | //! the underlying SQL type. 119 | 120 | extern crate syn; 121 | #[macro_use] 122 | extern crate quote; 123 | extern crate proc_macro; 124 | extern crate proc_macro2; 125 | 126 | use proc_macro2::{Span, TokenStream}; 127 | 128 | #[proc_macro_derive(DieselNewType)] 129 | #[doc(hidden)] 130 | pub fn diesel_new_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 131 | let ast = syn::parse(input).unwrap(); 132 | 133 | expand_sql_types(&ast).into() 134 | } 135 | 136 | fn expand_sql_types(ast: &syn::DeriveInput) -> TokenStream { 137 | let name = &ast.ident; 138 | let wrapped_ty = match ast.data { 139 | syn::Data::Struct(ref data) => { 140 | let mut iter = data.fields.iter(); 141 | match (iter.next(), iter.next()) { 142 | (Some(field), None) => &field.ty, 143 | (_, _) => panic!( 144 | "#[derive(DieselNewType)] can only be used with structs with exactly one field" 145 | ), 146 | } 147 | } 148 | _ => panic!("#[derive(DieselNewType)] can only be used with structs with a single field"), 149 | }; 150 | 151 | // Required to be able to insert/read from the db, don't allow searching 152 | let to_sql_impl = gen_tosql(name, wrapped_ty); 153 | let as_expr_impl = gen_asexpressions(name, wrapped_ty); 154 | 155 | // raw deserialization 156 | let from_sql_impl = gen_from_sql(name, wrapped_ty); 157 | 158 | // querying 159 | let queryable_impl = gen_queryable(name, wrapped_ty); 160 | 161 | // since our query doesn't take varargs it's fine for the DB to cache it 162 | let query_id_impl = gen_query_id(name); 163 | 164 | wrap_impls_in_const("e! { 165 | #to_sql_impl 166 | #as_expr_impl 167 | 168 | #from_sql_impl 169 | 170 | #queryable_impl 171 | 172 | #query_id_impl 173 | }) 174 | } 175 | 176 | fn gen_tosql(name: &syn::Ident, wrapped_ty: &syn::Type) -> TokenStream { 177 | quote! { 178 | impl diesel::serialize::ToSql for #name 179 | where 180 | #wrapped_ty: diesel::serialize::ToSql, 181 | DB: diesel::backend::Backend, 182 | DB: diesel::sql_types::HasSqlType, 183 | { 184 | fn to_sql<'b>(&'b self, out: &mut diesel::serialize::Output<'b, '_, DB>) -> diesel::serialize::Result 185 | { 186 | self.0.to_sql(out) 187 | } 188 | } 189 | } 190 | } 191 | 192 | fn gen_asexpressions(name: &syn::Ident, wrapped_ty: &syn::Type) -> TokenStream { 193 | quote! { 194 | 195 | impl diesel::expression::AsExpression for #name 196 | where 197 | diesel::internal::derives::as_expression::Bound: 198 | diesel::expression::Expression, 199 | ST: diesel::sql_types::SingleValue, 200 | { 201 | type Expression = diesel::internal::derives::as_expression::Bound; 202 | 203 | fn as_expression(self) -> Self::Expression { 204 | diesel::internal::derives::as_expression::Bound::new(self) 205 | } 206 | } 207 | 208 | impl<'expr, ST> diesel::expression::AsExpression for &'expr #name 209 | where 210 | diesel::internal::derives::as_expression::Bound: 211 | diesel::expression::Expression, 212 | ST: diesel::sql_types::SingleValue, 213 | { 214 | type Expression = diesel::internal::derives::as_expression::Bound; 215 | 216 | fn as_expression(self) -> Self::Expression { 217 | diesel::internal::derives::as_expression::Bound::new(self) 218 | } 219 | } 220 | 221 | impl<'expr2, 'expr, ST> diesel::expression::AsExpression for &'expr2 &'expr #name 222 | where 223 | diesel::internal::derives::as_expression::Bound: 224 | diesel::expression::Expression, 225 | ST: diesel::sql_types::SingleValue, 226 | { 227 | type Expression = diesel::internal::derives::as_expression::Bound; 228 | 229 | fn as_expression(self) -> Self::Expression { 230 | diesel::internal::derives::as_expression::Bound::new(self) 231 | } 232 | } 233 | } 234 | } 235 | 236 | fn gen_from_sql(name: &syn::Ident, wrapped_ty: &syn::Type) -> TokenStream { 237 | quote! { 238 | impl diesel::deserialize::FromSql for #name 239 | where 240 | #wrapped_ty: diesel::deserialize::FromSql, 241 | DB: diesel::backend::Backend, 242 | DB: diesel::sql_types::HasSqlType, 243 | { 244 | fn from_sql(raw: DB::RawValue<'_>) 245 | -> ::std::result::Result> 246 | { 247 | diesel::deserialize::FromSql::::from_sql(raw) 248 | .map(#name) 249 | } 250 | } 251 | } 252 | } 253 | 254 | fn gen_queryable(name: &syn::Ident, wrapped_ty: &syn::Type) -> TokenStream { 255 | quote! { 256 | impl diesel::deserialize::Queryable for #name 257 | where 258 | #wrapped_ty: diesel::deserialize::FromStaticSqlRow, 259 | DB: diesel::backend::Backend, 260 | DB: diesel::sql_types::HasSqlType, 261 | { 262 | type Row = #wrapped_ty; 263 | 264 | fn build(row: Self::Row) -> diesel::deserialize::Result { 265 | Ok(#name(row)) 266 | } 267 | } 268 | } 269 | } 270 | 271 | fn gen_query_id(name: &syn::Ident) -> TokenStream { 272 | quote! { 273 | impl diesel::query_builder::QueryId for #name { 274 | type QueryId = Self; 275 | } 276 | } 277 | } 278 | 279 | /// This guarantees that items we generate don't pollute the module scope 280 | fn wrap_impls_in_const(item: &TokenStream) -> TokenStream { 281 | let dummy_const = syn::Ident::new("_", Span::call_site()); 282 | quote! { 283 | const #dummy_const: () = { 284 | #item 285 | }; 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /tests/db-roundtrips.rs: -------------------------------------------------------------------------------- 1 | use diesel::dsl::sql; 2 | use diesel::prelude::*; 3 | use diesel::sqlite::SqliteConnection; 4 | use diesel_derive_newtype::DieselNewType; 5 | 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, DieselNewType)] 7 | pub struct MyIdString(String); 8 | 9 | #[derive(Debug, Clone, PartialEq, Eq, Hash, DieselNewType)] 10 | pub struct MyI32(i32); 11 | 12 | #[derive(Debug, Clone, PartialEq, Eq, Hash, DieselNewType)] 13 | pub struct MyNullableString(Option); 14 | 15 | #[derive(Debug, Clone, PartialEq, Eq, Hash, DieselNewType)] 16 | pub struct MyNullableI32(Option); 17 | 18 | #[derive(Debug, Clone, PartialEq, Identifiable, Insertable, Queryable)] 19 | #[diesel(table_name = my_entities)] 20 | pub struct MyEntity { 21 | id: MyIdString, 22 | my_i32: MyI32, 23 | my_nullable_string: MyNullableString, 24 | my_nullable_i32: MyNullableI32, 25 | val: i32, 26 | } 27 | 28 | #[derive(Debug, Clone, PartialEq, Insertable)] 29 | #[diesel(table_name = my_entities)] 30 | pub struct MyEntityInternalRefs<'a> { 31 | id: &'a MyIdString, 32 | my_i32: MyI32, 33 | my_nullable_string: &'a MyNullableString, 34 | my_nullable_i32: &'a MyNullableI32, 35 | val: i32, 36 | } 37 | 38 | table! { 39 | my_entities { 40 | id -> Text, 41 | my_i32 -> Integer, 42 | my_nullable_string -> Nullable, 43 | my_nullable_i32 -> Nullable, 44 | val -> Integer, 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | fn setup() -> SqliteConnection { 50 | let mut conn = SqliteConnection::establish(":memory:").unwrap(); 51 | let setup = sql::( 52 | "CREATE TABLE my_entities ( 53 | id TEXT PRIMARY KEY, 54 | my_i32 int NOT NULL, 55 | my_nullable_string TEXT, 56 | my_nullable_i32 int, 57 | val Int NOT NULL 58 | )", 59 | ); 60 | setup.execute(&mut conn).expect("Can't create table"); 61 | conn 62 | } 63 | 64 | #[test] 65 | fn does_roundtrip() { 66 | let mut conn = setup(); 67 | let obj = MyEntity { 68 | id: MyIdString("WooHoo".into()), 69 | my_i32: MyI32(10), 70 | my_nullable_string: MyNullableString(Some("WooHoo".into())), 71 | my_nullable_i32: MyNullableI32(Some(10)), 72 | val: 1, 73 | }; 74 | 75 | diesel::insert_into(my_entities::table) 76 | .values(&obj) 77 | .execute(&mut conn) 78 | .expect("Couldn't insert struct into my_entities"); 79 | 80 | let found: Vec = my_entities::table.load(&mut conn).unwrap(); 81 | println!("found: {:?}", found); 82 | assert_eq!(found[0], obj); 83 | } 84 | 85 | #[test] 86 | fn does_roundtrip_with_ref() { 87 | let mut conn = setup(); 88 | let obj = MyEntityInternalRefs { 89 | id: &MyIdString("WooHoo".into()), 90 | my_i32: MyI32(10), 91 | my_nullable_string: &MyNullableString(Some("WooHoo".into())), 92 | my_nullable_i32: &MyNullableI32(Some(10)), 93 | val: 1, 94 | }; 95 | 96 | diesel::insert_into(my_entities::table) 97 | .values(&obj) 98 | .execute(&mut conn) 99 | .expect("Couldn't insert struct into my_entities"); 100 | 101 | let found: Vec = my_entities::table.load(&mut conn).unwrap(); 102 | println!("found: {:?}", found); 103 | assert_eq!(found[0].id, *obj.id); 104 | assert_eq!(found[0].my_i32, obj.my_i32); 105 | assert_eq!(found[0].my_nullable_string, *obj.my_nullable_string); 106 | assert_eq!(found[0].my_nullable_i32, *obj.my_nullable_i32); 107 | assert_eq!(found[0].val, obj.val); 108 | } 109 | 110 | #[test] 111 | fn does_roundtrip_nulls() { 112 | let mut conn = setup(); 113 | let obj = MyEntity { 114 | id: MyIdString("WooHoo".into()), 115 | my_i32: MyI32(10), 116 | my_nullable_string: MyNullableString(None), 117 | my_nullable_i32: MyNullableI32(None), 118 | val: 1, 119 | }; 120 | 121 | diesel::insert_into(my_entities::table) 122 | .values(&obj) 123 | .execute(&mut conn) 124 | .expect("Couldn't insert struct into my_entities"); 125 | 126 | let found: Vec = my_entities::table.load(&mut conn).unwrap(); 127 | println!("found: {:?}", found); 128 | assert_eq!(found[0], obj); 129 | } 130 | 131 | #[test] 132 | fn queryable() { 133 | let mut conn = setup(); 134 | let objs = vec![ 135 | MyEntity { 136 | id: MyIdString("WooHoo".into()), 137 | my_i32: MyI32(10), 138 | my_nullable_string: MyNullableString(Some("WooHoo".into())), 139 | my_nullable_i32: MyNullableI32(Some(10)), 140 | val: 1, 141 | }, 142 | MyEntity { 143 | id: MyIdString("boo".into()), 144 | my_i32: MyI32(20), 145 | my_nullable_string: MyNullableString(None), 146 | my_nullable_i32: MyNullableI32(None), 147 | val: 2, 148 | }, 149 | ]; 150 | 151 | diesel::insert_into(my_entities::table) 152 | .values(&objs) 153 | .execute(&mut conn) 154 | .expect("Couldn't insert struct into my_entities"); 155 | 156 | let ids: Vec = my_entities::table 157 | .select(my_entities::columns::id) 158 | .load(&mut conn) 159 | .unwrap(); 160 | assert_eq!(&ids[0], &objs[0].id); 161 | assert_eq!(&ids[1], &objs[1].id); 162 | } 163 | 164 | #[test] 165 | fn query_as_id() { 166 | let mut conn = setup(); 167 | let expected = MyEntity { 168 | id: MyIdString("WooHoo".into()), 169 | my_i32: MyI32(10), 170 | my_nullable_string: MyNullableString(Some("WooHoo".into())), 171 | my_nullable_i32: MyNullableI32(Some(10)), 172 | val: 1, 173 | }; 174 | let objs = vec![ 175 | MyEntity { 176 | id: MyIdString("loop".into()), 177 | my_i32: MyI32(0), 178 | my_nullable_string: MyNullableString(Some("loop".into())), 179 | my_nullable_i32: MyNullableI32(Some(0)), 180 | val: 0, 181 | }, 182 | expected.clone(), 183 | MyEntity { 184 | id: MyIdString("boo".into()), 185 | my_i32: MyI32(20), 186 | my_nullable_string: MyNullableString(None), 187 | my_nullable_i32: MyNullableI32(None), 188 | val: 2, 189 | }, 190 | ]; 191 | 192 | diesel::insert_into(my_entities::table) 193 | .values(&objs) 194 | .execute(&mut conn) 195 | .expect("Couldn't insert struct into my_entities"); 196 | 197 | let ids: Vec = my_entities::table 198 | .filter(my_entities::id.eq(MyIdString("WooHoo".into()))) 199 | .load(&mut conn) 200 | .unwrap(); 201 | assert_eq!(ids, vec![expected]) 202 | } 203 | 204 | #[test] 205 | fn query_as_underlying_type() { 206 | let mut conn = setup(); 207 | let expected = MyEntity { 208 | id: MyIdString("WooHoo".into()), 209 | my_i32: MyI32(10), 210 | my_nullable_string: MyNullableString(Some("WooHoo".into())), 211 | my_nullable_i32: MyNullableI32(Some(10)), 212 | val: 1, 213 | }; 214 | let objs = vec![ 215 | MyEntity { 216 | my_i32: MyI32(0), 217 | id: MyIdString("loop".into()), 218 | my_nullable_string: MyNullableString(Some("loop".into())), 219 | my_nullable_i32: MyNullableI32(Some(0)), 220 | val: 0, 221 | }, 222 | expected.clone(), 223 | MyEntity { 224 | id: MyIdString("boo".into()), 225 | my_i32: MyI32(20), 226 | my_nullable_string: MyNullableString(None), 227 | my_nullable_i32: MyNullableI32(None), 228 | val: 2, 229 | }, 230 | ]; 231 | 232 | diesel::insert_into(my_entities::table) 233 | .values(&objs) 234 | .execute(&mut conn) 235 | .expect("Couldn't insert struct into my_entities"); 236 | 237 | let ids: Vec = my_entities::table 238 | .filter(my_entities::id.eq("WooHoo".to_string())) 239 | .load(&mut conn) 240 | .unwrap(); 241 | assert_eq!(ids, vec![expected]) 242 | } 243 | 244 | #[test] 245 | fn set() { 246 | let mut conn = setup(); 247 | let expected = MyEntity { 248 | id: MyIdString("WooHoo".into()), 249 | my_i32: MyI32(10), 250 | my_nullable_string: MyNullableString(Some("WooHoo".into())), 251 | my_nullable_i32: MyNullableI32(Some(10)), 252 | val: 1, 253 | }; 254 | let objs = vec![ 255 | MyEntity { 256 | id: MyIdString("loop".into()), 257 | my_i32: MyI32(0), 258 | my_nullable_string: MyNullableString(Some("loop".into())), 259 | my_nullable_i32: MyNullableI32(Some(0)), 260 | val: 0, 261 | }, 262 | expected.clone(), 263 | MyEntity { 264 | id: MyIdString("boo".into()), 265 | my_i32: MyI32(20), 266 | my_nullable_string: MyNullableString(None), 267 | my_nullable_i32: MyNullableI32(None), 268 | val: 2, 269 | }, 270 | ]; 271 | 272 | diesel::insert_into(my_entities::table) 273 | .values(&objs) 274 | .execute(&mut conn) 275 | .expect("Couldn't insert struct into my_entities"); 276 | 277 | let new_id = MyIdString("Oh My".into()); 278 | diesel::update(my_entities::table.find(&expected.id)) 279 | .set(my_entities::id.eq(&new_id)) 280 | .execute(&mut conn) 281 | .unwrap(); 282 | let updated_ids: Vec = my_entities::table 283 | .filter(my_entities::id.eq(&new_id)) 284 | .load(&mut conn) 285 | .unwrap(); 286 | assert_eq!( 287 | updated_ids, 288 | vec![MyEntity { 289 | id: new_id, 290 | my_i32: MyI32(10), 291 | my_nullable_string: MyNullableString(Some("WooHoo".into())), 292 | my_nullable_i32: MyNullableI32(Some(10)), 293 | val: 1 294 | }] 295 | ) 296 | } 297 | -------------------------------------------------------------------------------- /tests/should-not-compile.rs: -------------------------------------------------------------------------------- 1 | //! This is a test file that *DOES* compile and pass tests, but which should 2 | //! not 3 | 4 | use diesel_derive_newtype::DieselNewType; 5 | 6 | use diesel::dsl::sql; 7 | use diesel::prelude::*; 8 | use diesel::sqlite::SqliteConnection; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq, Hash, DieselNewType)] 11 | pub struct MyId(String); 12 | 13 | #[derive(Debug, Clone, PartialEq, Eq, Hash, DieselNewType)] 14 | pub struct OtherId(String); 15 | 16 | #[derive(Debug, Clone, PartialEq, Identifiable, Insertable, Queryable)] 17 | #[diesel(table_name = my_entities)] 18 | pub struct MyEntity { 19 | id: MyId, 20 | val: i32, 21 | } 22 | 23 | table! { 24 | my_entities { 25 | id -> Text, 26 | val -> Integer, 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | fn setup() -> SqliteConnection { 32 | let mut conn = SqliteConnection::establish(":memory:").unwrap(); 33 | let setup = sql::( 34 | "CREATE TABLE IF NOT EXISTS my_entities ( 35 | id TEXT PRIMARY KEY, 36 | val Int 37 | )", 38 | ); 39 | setup.execute(&mut conn).expect("Can't create table"); 40 | conn 41 | } 42 | 43 | #[cfg(test)] 44 | fn setup_with_items() -> (SqliteConnection, Vec) { 45 | let mut conn = setup(); 46 | let objs = vec![ 47 | MyEntity { 48 | id: MyId("loop".into()), 49 | val: 0, 50 | }, 51 | MyEntity { 52 | id: MyId("WooHoo".into()), 53 | val: 1, 54 | }, 55 | MyEntity { 56 | id: MyId("boo".into()), 57 | val: 2, 58 | }, 59 | ]; 60 | 61 | diesel::insert_into(my_entities::table) 62 | .values(&objs) 63 | .execute(&mut conn) 64 | .expect("Couldn't insert struct into my_entities"); 65 | 66 | (conn, objs) 67 | } 68 | 69 | #[test] 70 | fn query_as_id() { 71 | let (mut conn, _) = setup_with_items(); 72 | 73 | let _: Vec = my_entities::table 74 | .filter(my_entities::id.eq(OtherId("WooHoo".into()))) // <-- OTHERID 75 | .load(&mut conn) 76 | .unwrap(); 77 | } 78 | 79 | #[test] 80 | fn set() { 81 | let (mut conn, objs) = setup_with_items(); 82 | 83 | let expected = objs[1].clone(); 84 | 85 | let new_id = OtherId("Oh My".into()); // <-- OTHERID 86 | diesel::update(my_entities::table.find(&expected.id)) 87 | .set(my_entities::id.eq(&new_id)) 88 | .execute(&mut conn) 89 | .unwrap(); 90 | } 91 | --------------------------------------------------------------------------------