init
This commit is contained in:
30
.gitea/workflows/nix-ci.yml
Normal file
30
.gitea/workflows/nix-ci.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Nix CI
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: nix-flakes
|
||||
|
||||
steps:
|
||||
- name: Set up attic binary cache
|
||||
uses: https://git.naxdy.org/Mirror/attic-action@v0.3
|
||||
with:
|
||||
endpoint: '${{ vars.BINARY_CACHE_URL }}'
|
||||
token: '${{ secrets.BINARY_CACHE_AUTH_KEY }}'
|
||||
cache: '${{ vars.BINARY_CACHE_NAME }}'
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch flake inputs
|
||||
run: |
|
||||
nix flake prefetch-inputs
|
||||
|
||||
- name: Run flake checks
|
||||
run: |
|
||||
nix flake check -j auto --print-build-logs --keep-going
|
||||
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/.direnv
|
||||
/result
|
||||
/result-*
|
||||
/target
|
||||
|
||||
|
||||
# Added by cargo
|
||||
#
|
||||
# already existing elements were commented out
|
||||
|
||||
#/target
|
||||
1682
Cargo.lock
generated
Normal file
1682
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
Cargo.toml
Normal file
32
Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "sqlx-utils"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[workspace]
|
||||
members = [".", "macros"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.6"
|
||||
license = "MIT"
|
||||
edition = "2024"
|
||||
repository = "https://git.naxdy.org/NaxdyOrg/sqlx-utils"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
pedantic = { level = "warn", priority = -3 }
|
||||
nursery = { level = "warn", priority = -2 }
|
||||
unwrap_used = { level = "warn", priority = -1 }
|
||||
print_stdout = { level = "deny", priority = -1 }
|
||||
|
||||
[workspace.dependencies]
|
||||
sqlx-utils-macros = { version = "0.8.6", path = "macros" }
|
||||
|
||||
[dependencies]
|
||||
sqlx = "0.8.6"
|
||||
tracing = "0.1.41"
|
||||
sqlx-utils-macros.workspace = true
|
||||
18
LICENSE
Normal file
18
LICENSE
Normal file
@@ -0,0 +1,18 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Naxdy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
75
default.nix
Normal file
75
default.nix
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
pkgs,
|
||||
crane,
|
||||
}:
|
||||
let
|
||||
rustToolchain = pkgs.fenix.stable.withComponents [
|
||||
"cargo"
|
||||
"rustc"
|
||||
"rustfmt"
|
||||
"rust-std"
|
||||
"rust-analyzer"
|
||||
"clippy"
|
||||
];
|
||||
|
||||
# more info on https://crane.dev/API.html
|
||||
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
|
||||
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||
|
||||
craneArgs = {
|
||||
pname = cargoToml.workspace.package.name or cargoToml.package.name;
|
||||
version = cargoToml.workspace.package.version or cargoToml.package.version;
|
||||
|
||||
src = craneLib.cleanCargoSource ./.;
|
||||
|
||||
strictDeps = true;
|
||||
|
||||
cargoExtraArgs = "--locked --workspace";
|
||||
|
||||
# can add `nativeBuildInputs` or `buildInputs` here
|
||||
|
||||
env = {
|
||||
# print backtrace on compilation failure
|
||||
RUST_BACKTRACE = "1";
|
||||
|
||||
# treat warnings as errors
|
||||
RUSTFLAGS = "-Dwarnings";
|
||||
RUSTDOCFLAGS = "-Dwarnings";
|
||||
};
|
||||
};
|
||||
|
||||
cargoArtifacts = craneLib.buildDepsOnly craneArgs;
|
||||
|
||||
craneBuildArgs = craneArgs // {
|
||||
inherit cargoArtifacts;
|
||||
};
|
||||
in
|
||||
{
|
||||
package = craneLib.buildPackage (
|
||||
craneBuildArgs
|
||||
// {
|
||||
passthru = {
|
||||
tests = {
|
||||
test = craneLib.cargoTest craneBuildArgs;
|
||||
|
||||
doc = craneLib.cargoDoc craneBuildArgs;
|
||||
|
||||
clippy = craneLib.cargoClippy craneBuildArgs;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
docs = craneLib.cargoDoc (
|
||||
craneBuildArgs
|
||||
// {
|
||||
# used to disable `--no-deps`, which crane enables by default,
|
||||
# so we include all packages in the resulting docs, to have fully-functional
|
||||
# offline docs
|
||||
cargoDocExtraArgs = "";
|
||||
}
|
||||
);
|
||||
|
||||
inherit rustToolchain cargoToml;
|
||||
}
|
||||
131
flake.lock
generated
Normal file
131
flake.lock
generated
Normal file
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1754269165,
|
||||
"narHash": "sha256-0tcS8FHd4QjbCVoxN9jI+PjHgA4vc/IjkUSp+N3zy0U=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "444e81206df3f7d92780680e45858e31d2f07a08",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1754894611,
|
||||
"narHash": "sha256-TEyTVDhzFyfvPahhi1iAmkopt6fMiTlmn6f278lTdDs=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "a01861ebeb4d9c504845e7fb81509b82333ca0aa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1754725699,
|
||||
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1754725699,
|
||||
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1754340878,
|
||||
"narHash": "sha256-lgmUyVQL9tSnvvIvBp7x1euhkkCho7n3TMzgjdvgPoU=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cab778239e705082fe97bb4990e0d24c50924c04",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"fenix": "fenix",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1754834452,
|
||||
"narHash": "sha256-otzv/l7c1rL+eH1cuJnUZVp4DR2dMdEIfhtLxTelIBY=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "4e147e787987fdb1baf081bd5c60bedfb0aabe16",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1754847726,
|
||||
"narHash": "sha256-2vX8QjO5lRsDbNYvN9hVHXLU6oMl+V/PsmIiJREG4rE=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "7d81f6fb2e19bf84f1c65135d1060d829fae2408",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
120
flake.nix
Normal file
120
flake.nix
Normal file
@@ -0,0 +1,120 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
fenix.url = "github:nix-community/fenix";
|
||||
|
||||
crane.url = "github:ipetkov/crane";
|
||||
|
||||
treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
fenix,
|
||||
crane,
|
||||
treefmt-nix,
|
||||
}:
|
||||
let
|
||||
supportedSystems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
forEachSupportedSystem =
|
||||
f:
|
||||
nixpkgs.lib.genAttrs supportedSystems (
|
||||
system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ fenix.overlays.default ];
|
||||
};
|
||||
|
||||
package = pkgs.callPackage ./default.nix { inherit crane; };
|
||||
|
||||
treefmtEval = treefmt-nix.lib.evalModule pkgs (
|
||||
import ./treefmt.nix { inherit (package) rustToolchain cargoToml; }
|
||||
);
|
||||
|
||||
treefmt = treefmtEval.config.build.wrapper;
|
||||
in
|
||||
f {
|
||||
inherit
|
||||
package
|
||||
pkgs
|
||||
system
|
||||
treefmt
|
||||
treefmtEval
|
||||
;
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
devShells = forEachSupportedSystem (
|
||||
{
|
||||
pkgs,
|
||||
treefmt,
|
||||
system,
|
||||
package,
|
||||
...
|
||||
}:
|
||||
{
|
||||
default = self.devShells.${system}.full;
|
||||
|
||||
full = pkgs.mkShell {
|
||||
packages = [
|
||||
treefmt
|
||||
];
|
||||
|
||||
inputsFrom = [ self.packages.${system}.default ];
|
||||
};
|
||||
|
||||
toolchainOnly = pkgs.mkShell {
|
||||
nativeBuildInputs = [
|
||||
package.rustToolchain
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
formatter = forEachSupportedSystem ({ treefmt, ... }: treefmt);
|
||||
|
||||
packages = forEachSupportedSystem (
|
||||
{
|
||||
package,
|
||||
...
|
||||
}:
|
||||
{
|
||||
default = package.package;
|
||||
|
||||
inherit (package) docs;
|
||||
}
|
||||
);
|
||||
|
||||
checks = forEachSupportedSystem (
|
||||
{
|
||||
pkgs,
|
||||
treefmtEval,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
testsFrom =
|
||||
pkg:
|
||||
pkgs.lib.mapAttrs' (name: value: {
|
||||
name = "${pkg.pname}-${name}";
|
||||
inherit value;
|
||||
}) pkg.passthru.tests;
|
||||
in
|
||||
{
|
||||
treefmt = treefmtEval.config.build.check self;
|
||||
}
|
||||
// (testsFrom self.packages.${system}.default)
|
||||
);
|
||||
};
|
||||
}
|
||||
19
macros/Cargo.toml
Normal file
19
macros/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "sqlx-utils-macros"
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
darling = "0.21.1"
|
||||
proc-macro-error2 = "2.0.1"
|
||||
proc-macro2 = "1.0.96"
|
||||
quote = "1.0.40"
|
||||
syn = "2.0.104"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
8
macros/src/lib.rs
Normal file
8
macros/src/lib.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
mod push_to_builder;
|
||||
|
||||
#[proc_macro_derive(PushToBuilder)]
|
||||
pub fn push_to_builder(input: TokenStream) -> TokenStream {
|
||||
push_to_builder::push_to_builder(input.into()).into()
|
||||
}
|
||||
57
macros/src/push_to_builder.rs
Normal file
57
macros/src/push_to_builder.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use darling::{FromDeriveInput, ast::Data};
|
||||
use proc_macro2::Span;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{DeriveInput, Field, Ident, parse2};
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
struct PushToBuilderArgs {
|
||||
ident: Ident,
|
||||
data: Data<(), Field>,
|
||||
}
|
||||
|
||||
pub fn push_to_builder(input: TokenStream) -> TokenStream {
|
||||
let input: DeriveInput = match parse2(input) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return e.into_compile_error(),
|
||||
};
|
||||
|
||||
let args = match PushToBuilderArgs::from_derive_input(&input) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return e.write_errors(),
|
||||
};
|
||||
|
||||
let struct_name = args.ident;
|
||||
|
||||
let push_statements = args
|
||||
.data
|
||||
.take_struct()
|
||||
.map(|e| {
|
||||
e.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, e)| {
|
||||
let ident = e
|
||||
.ident
|
||||
.unwrap_or_else(|| Ident::new(&format!("{idx}"), Span::call_site()));
|
||||
|
||||
quote! {
|
||||
::sqlx_utils::builder::PushToBuilder::push_to(self.#ident, builder);
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
quote! {
|
||||
impl<DB> ::sqlx_utils::builder::PushToBuilder<DB> for #struct_name
|
||||
where
|
||||
DB: ::sqlx::Database
|
||||
{
|
||||
fn push_to(&self, builder: &mut ::sqlx::QueryBuilder<'_, DB>) {
|
||||
#(
|
||||
#push_statements
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
201
src/builder.rs
Normal file
201
src/builder.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
pub mod expr;
|
||||
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use sqlx::{Database, Decode, Encode, QueryBuilder, Type};
|
||||
|
||||
use crate::builder::expr::{BinaryOperand, ListGlue};
|
||||
|
||||
pub struct BinaryExpr<L, R, DB, Op>
|
||||
where
|
||||
L: PushToBuilder<DB>,
|
||||
R: PushToBuilder<DB>,
|
||||
DB: Database,
|
||||
Op: BinaryOperand,
|
||||
{
|
||||
left: L,
|
||||
right: R,
|
||||
marker: PhantomData<DB>,
|
||||
marker_op: PhantomData<Op>,
|
||||
}
|
||||
|
||||
impl<L, R, DB, Op> BinaryExpr<L, R, DB, Op>
|
||||
where
|
||||
L: PushToBuilder<DB>,
|
||||
R: PushToBuilder<DB>,
|
||||
DB: Database,
|
||||
Op: BinaryOperand,
|
||||
{
|
||||
pub fn new(left: L, _operand: Op, right: R) -> Self {
|
||||
Self {
|
||||
left,
|
||||
right,
|
||||
marker: PhantomData,
|
||||
marker_op: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R, DB, Op> PushToBuilder<DB> for BinaryExpr<L, R, DB, Op>
|
||||
where
|
||||
L: PushToBuilder<DB>,
|
||||
R: PushToBuilder<DB>,
|
||||
DB: Database,
|
||||
Op: BinaryOperand,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
self.left.push_to(builder);
|
||||
builder.push(Op::EXPR);
|
||||
self.right.push_to(builder);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueryVariable<T, DB>
|
||||
where
|
||||
T: Type<DB> + for<'a> Encode<'a, DB> + for<'a> Decode<'a, DB> + Clone + 'static,
|
||||
DB: Database,
|
||||
{
|
||||
inner: T,
|
||||
marker: PhantomData<DB>,
|
||||
}
|
||||
|
||||
impl<T, DB> PushToBuilder<DB> for QueryVariable<T, DB>
|
||||
where
|
||||
T: Type<DB> + for<'a> Encode<'a, DB> + for<'a> Decode<'a, DB> + Clone + 'static,
|
||||
DB: Database,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
builder.push_bind(self.inner.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BracketsExpr<P, DB>
|
||||
where
|
||||
P: PushToBuilder<DB>,
|
||||
DB: Database,
|
||||
{
|
||||
inner: P,
|
||||
marker: PhantomData<DB>,
|
||||
}
|
||||
|
||||
impl<P, DB> BracketsExpr<P, DB>
|
||||
where
|
||||
P: PushToBuilder<DB>,
|
||||
DB: Database,
|
||||
{
|
||||
pub const fn new(inner: P) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, DB> PushToBuilder<DB> for BracketsExpr<P, DB>
|
||||
where
|
||||
P: PushToBuilder<DB>,
|
||||
DB: Database,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
builder.push("(");
|
||||
self.inner.push_to(builder);
|
||||
builder.push(")");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExprList<DB, Op>
|
||||
where
|
||||
DB: Database,
|
||||
Op: ListGlue,
|
||||
{
|
||||
list: Vec<Arc<dyn PushToBuilder<DB>>>,
|
||||
marker: PhantomData<Op>,
|
||||
}
|
||||
|
||||
impl<DB, Op> ExprList<DB, Op>
|
||||
where
|
||||
DB: Database,
|
||||
Op: ListGlue,
|
||||
{
|
||||
#[must_use]
|
||||
pub const fn new(list: Vec<Arc<dyn PushToBuilder<DB>>>) -> Self {
|
||||
Self {
|
||||
list,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB, Op> Deref for ExprList<DB, Op>
|
||||
where
|
||||
DB: Database,
|
||||
Op: ListGlue,
|
||||
{
|
||||
type Target = Vec<Arc<dyn PushToBuilder<DB>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.list
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB, Op> DerefMut for ExprList<DB, Op>
|
||||
where
|
||||
DB: Database,
|
||||
Op: ListGlue,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.list
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB, Op> PushToBuilder<DB> for ExprList<DB, Op>
|
||||
where
|
||||
DB: Database,
|
||||
Op: ListGlue,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
let mut elems = self.list.clone();
|
||||
|
||||
if let Some(init) = elems.pop() {
|
||||
BracketsExpr::new(init).push_to(builder);
|
||||
}
|
||||
|
||||
for e in elems {
|
||||
builder.push(Op::EXPR);
|
||||
BracketsExpr::new(e).push_to(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PushToBuilder<DB>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>);
|
||||
}
|
||||
|
||||
impl<DB> PushToBuilder<DB> for Arc<dyn PushToBuilder<DB>>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
self.deref().push_to(builder);
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB, T> PushToBuilder<DB> for Option<T>
|
||||
where
|
||||
T: PushToBuilder<DB>,
|
||||
DB: Database,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
match self {
|
||||
None => {}
|
||||
Some(e) => e.push_to(builder),
|
||||
}
|
||||
}
|
||||
}
|
||||
203
src/builder/expr.rs
Normal file
203
src/builder/expr.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use crate::builder::PushToBuilder;
|
||||
use crate::builder::expr::sealed::Sealed;
|
||||
use crate::builder::expr::sealed::SealedBinary;
|
||||
use crate::builder::expr::sealed::SealedList;
|
||||
use crate::builder::expr::sealed::SealedWhere;
|
||||
|
||||
use sqlx::Database;
|
||||
use sqlx::QueryBuilder;
|
||||
|
||||
macro_rules! impl_push {
|
||||
($($struct:ident),*) => {
|
||||
$(
|
||||
impl<DB> PushToBuilder<DB> for $struct
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
builder.push(<Self as SqlOperand>::EXPR);
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
pub trait SqlOperand: Sealed {
|
||||
const EXPR: &str;
|
||||
}
|
||||
|
||||
pub trait BinaryOperand: SqlOperand + SealedBinary {}
|
||||
|
||||
impl<T> BinaryOperand for T where T: SealedBinary + SqlOperand {}
|
||||
|
||||
pub trait ListGlue: SqlOperand + SealedList {}
|
||||
|
||||
impl<T> ListGlue for T where T: SealedList + SqlOperand {}
|
||||
|
||||
pub trait WhereOp: SqlOperand + SealedWhere {}
|
||||
|
||||
impl<T> WhereOp for T where T: SealedWhere + SqlOperand {}
|
||||
|
||||
pub struct And;
|
||||
|
||||
impl SqlOperand for And {
|
||||
const EXPR: &str = " AND ";
|
||||
}
|
||||
|
||||
pub struct Or;
|
||||
|
||||
impl SqlOperand for Or {
|
||||
const EXPR: &str = " OR ";
|
||||
}
|
||||
|
||||
pub struct Comma;
|
||||
|
||||
impl SqlOperand for Comma {
|
||||
const EXPR: &str = ", ";
|
||||
}
|
||||
|
||||
pub struct Equals;
|
||||
|
||||
impl SqlOperand for Equals {
|
||||
const EXPR: &str = " = ";
|
||||
}
|
||||
|
||||
pub struct Is;
|
||||
|
||||
impl SqlOperand for Is {
|
||||
const EXPR: &str = " IS ";
|
||||
}
|
||||
|
||||
pub struct NotEquals;
|
||||
|
||||
impl SqlOperand for NotEquals {
|
||||
const EXPR: &str = " != ";
|
||||
}
|
||||
|
||||
pub struct IsNot;
|
||||
|
||||
impl SqlOperand for IsNot {
|
||||
const EXPR: &str = " IS NOT ";
|
||||
}
|
||||
|
||||
pub struct Like;
|
||||
|
||||
impl SqlOperand for Like {
|
||||
const EXPR: &str = " LIKE ";
|
||||
}
|
||||
|
||||
pub struct NotLike;
|
||||
|
||||
impl SqlOperand for NotLike {
|
||||
const EXPR: &str = " NOT LIKE ";
|
||||
}
|
||||
|
||||
pub struct Ilike;
|
||||
|
||||
impl SqlOperand for Ilike {
|
||||
const EXPR: &str = " ILIKE ";
|
||||
}
|
||||
|
||||
pub struct NotIlike;
|
||||
|
||||
impl SqlOperand for NotIlike {
|
||||
const EXPR: &str = " NOT ILIKE ";
|
||||
}
|
||||
|
||||
pub struct In;
|
||||
|
||||
impl SqlOperand for In {
|
||||
const EXPR: &str = " IN ";
|
||||
}
|
||||
|
||||
pub struct NotIn;
|
||||
|
||||
impl SqlOperand for NotIn {
|
||||
const EXPR: &str = " NOT IN ";
|
||||
}
|
||||
|
||||
impl_push!(
|
||||
And, Or, Comma, Equals, Is, NotEquals, IsNot, Like, NotLike, Ilike, NotIlike, In, NotIn
|
||||
);
|
||||
|
||||
#[must_use]
|
||||
pub const fn get_binary_expr<T: BinaryOperand>() -> &'static str {
|
||||
<T as SqlOperand>::EXPR
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn get_list_expr<T: ListGlue>() -> &'static str {
|
||||
<T as SqlOperand>::EXPR
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn get_where_expr<T: WhereOp>() -> &'static str {
|
||||
<T as SqlOperand>::EXPR
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
use crate::builder::expr::{
|
||||
And, Comma, Equals, Ilike, In, Is, IsNot, Like, NotEquals, NotIlike, NotIn, NotLike, Or,
|
||||
};
|
||||
|
||||
pub trait Sealed {}
|
||||
|
||||
pub trait SealedBinary {}
|
||||
|
||||
pub trait SealedList {}
|
||||
|
||||
pub trait SealedWhere {}
|
||||
|
||||
impl SealedList for And {}
|
||||
impl SealedList for Comma {}
|
||||
impl SealedList for Or {}
|
||||
|
||||
impl Sealed for And {}
|
||||
impl Sealed for Comma {}
|
||||
impl Sealed for Equals {}
|
||||
impl Sealed for Ilike {}
|
||||
impl Sealed for In {}
|
||||
impl Sealed for Is {}
|
||||
impl Sealed for IsNot {}
|
||||
impl Sealed for Like {}
|
||||
impl Sealed for NotEquals {}
|
||||
impl Sealed for NotIlike {}
|
||||
impl Sealed for NotIn {}
|
||||
impl Sealed for NotLike {}
|
||||
impl Sealed for Or {}
|
||||
|
||||
impl SealedBinary for And {}
|
||||
impl SealedBinary for Comma {}
|
||||
impl SealedBinary for Equals {}
|
||||
impl SealedBinary for Ilike {}
|
||||
impl SealedBinary for In {}
|
||||
impl SealedBinary for Is {}
|
||||
impl SealedBinary for IsNot {}
|
||||
impl SealedBinary for Like {}
|
||||
impl SealedBinary for NotEquals {}
|
||||
impl SealedBinary for NotIlike {}
|
||||
impl SealedBinary for NotIn {}
|
||||
impl SealedBinary for NotLike {}
|
||||
impl SealedBinary for Or {}
|
||||
|
||||
impl SealedWhere for Equals {}
|
||||
impl SealedWhere for Ilike {}
|
||||
impl SealedWhere for In {}
|
||||
impl SealedWhere for Is {}
|
||||
impl SealedWhere for IsNot {}
|
||||
impl SealedWhere for Like {}
|
||||
impl SealedWhere for NotEquals {}
|
||||
impl SealedWhere for NotIlike {}
|
||||
impl SealedWhere for NotIn {}
|
||||
impl SealedWhere for NotLike {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::builder::expr::{And, get_binary_expr};
|
||||
|
||||
#[test]
|
||||
fn ensure_cast() {
|
||||
assert_eq!(get_binary_expr::<And>(), " AND ");
|
||||
}
|
||||
}
|
||||
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod builder;
|
||||
35
treefmt.nix
Normal file
35
treefmt.nix
Normal file
@@ -0,0 +1,35 @@
|
||||
{ rustToolchain, cargoToml }:
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
# rust
|
||||
programs.rustfmt = {
|
||||
enable = true;
|
||||
package = rustToolchain;
|
||||
edition = cargoToml.workspace.package.edition or cargoToml.package.edition;
|
||||
};
|
||||
|
||||
# nix
|
||||
programs.nixfmt.enable = true;
|
||||
|
||||
# toml
|
||||
programs.taplo.enable = true;
|
||||
|
||||
# markdown, yaml, etc.
|
||||
programs.prettier = {
|
||||
enable = true;
|
||||
settings = {
|
||||
trailingComma = "all";
|
||||
semi = true;
|
||||
printWidth = 120;
|
||||
singleQuote = true;
|
||||
};
|
||||
};
|
||||
|
||||
programs.typos = {
|
||||
enable = true;
|
||||
includes = [
|
||||
"*.rs"
|
||||
"*.nix"
|
||||
];
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user