Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

### Added

- [#57](https://github.com/bobozaur/sqlx-exasol/pull/57): Complete proc-macro override
- Removes conflicts with `sqlx` in the same crate when using proc-macros

### Fixed

- [#50](https://github.com/bobozaur/sqlx-exasol/issues/50): Fix ETL `IMPORT` deadlocks
Expand Down
45 changes: 26 additions & 19 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,58 +42,58 @@ features = [
[features]
default = ["any", "macros", "migrate", "json"]

derive = ["sqlx/derive", "sqlx-exasol-macros?/derive"]
macros = ["derive", "sqlx/macros", "sqlx-exasol-macros/macros"]
derive = ["sqlx-a-orig/derive", "sqlx-exasol-macros?/derive"]
macros = ["derive", "sqlx-a-orig/macros", "sqlx-exasol-macros/macros"]
migrate = [
"sqlx/migrate",
"sqlx-a-orig/migrate",
"sqlx-exasol-impl/migrate",
"sqlx-exasol-macros?/migrate",
]

sqlx-toml = ["sqlx-exasol-impl/sqlx-toml", "sqlx-exasol-macros?/sqlx-toml"]

# Base runtime features without TLS
runtime-async-std = ["sqlx/runtime-async-std"]
runtime-tokio = ["sqlx/runtime-tokio"]
runtime-async-std = ["sqlx-a-orig/runtime-async-std"]
runtime-tokio = ["sqlx-a-orig/runtime-tokio"]

# TLS features
tls-native-tls = ["sqlx/tls-native-tls", "sqlx-exasol-impl/native-tls"]
tls-native-tls = ["sqlx-a-orig/tls-native-tls", "sqlx-exasol-impl/native-tls"]
tls-rustls-aws-lc-rs = [
"sqlx/tls-rustls-aws-lc-rs",
"sqlx-a-orig/tls-rustls-aws-lc-rs",
"sqlx-exasol-impl/rustls-aws-lc-rs",
]
tls-rustls-ring-webpki = [
"sqlx/tls-rustls-ring-webpki",
"sqlx-a-orig/tls-rustls-ring-webpki",
"sqlx-exasol-impl/rustls-ring",
]
tls-rustls-ring-native-roots = [
"sqlx/tls-rustls-ring-native-roots",
"sqlx-a-orig/tls-rustls-ring-native-roots",
"sqlx-exasol-impl/rustls-ring",
]

# Database
any = ["sqlx/any", "sqlx-exasol-impl/any"]
any = ["sqlx-a-orig/any", "sqlx-exasol-impl/any"]

# Types
bigdecimal = [
"sqlx/bigdecimal",
"sqlx-a-orig/bigdecimal",
"sqlx-exasol-impl/bigdecimal",
"sqlx-exasol-macros?/bigdecimal",
]
chrono = [
"sqlx/chrono",
"sqlx-a-orig/chrono",
"sqlx-exasol-impl/chrono",
"sqlx-exasol-macros?/chrono",
]
geo-types = ["sqlx-exasol-impl/geo-types", "sqlx-exasol-macros?/geo-types"]
json = ["sqlx/json", "sqlx-exasol-impl/json", "sqlx-exasol-macros?/json"]
json = ["sqlx-a-orig/json", "sqlx-exasol-impl/json", "sqlx-exasol-macros?/json"]
rust_decimal = [
"sqlx/rust_decimal",
"sqlx-a-orig/rust_decimal",
"sqlx-exasol-impl/rust_decimal",
"sqlx-exasol-macros?/rust_decimal",
]
time = ["sqlx/time", "sqlx-exasol-impl/time", "sqlx-exasol-macros?/time"]
uuid = ["sqlx/uuid", "sqlx-exasol-impl/uuid", "sqlx-exasol-macros?/uuid"]
time = ["sqlx-a-orig/time", "sqlx-exasol-impl/time", "sqlx-exasol-macros?/time"]
uuid = ["sqlx-a-orig/uuid", "sqlx-exasol-impl/uuid", "sqlx-exasol-macros?/uuid"]

# Driver specific features
compression = ["sqlx-exasol-impl/compression"]
Expand Down Expand Up @@ -140,6 +140,9 @@ hyper = { version = "1", default-features = false, features = [
] }
native-tls = { version = "0.2", default-features = false }
paste = { version = "1", default-features = false }
proc-macro2 = { version = "1", default-features = false, features = [
"proc-macro",
] }
quote = { version = "1", default-features = false }
rand = { version = "0.8", default-features = false, features = [
"std",
Expand All @@ -162,16 +165,21 @@ serde_json = { version = "1", default-features = false, features = [
"raw_value",
] }
sha2 = { version = "0.10", default-features = false, features = ["std"] }
sqlx = { version = "0.9.0-alpha.1", default-features = false }
sqlx-cli = { version = "0.9.0-alpha.1", default-features = false }
sqlx-core = { version = "0.9.0-alpha.1", default-features = false, features = [
"offline",
"migrate",
] }
sqlx-macros-core = { version = "0.9.0-alpha.1", default-features = false }
# Purposely named like this to prevent formatting reordering.
# We also get the benefit of ensuring that our overrides work correctly since `sqlx` is not available
sqlx-a-orig = { version = "0.9.0-alpha.1", default-features = false, package = "sqlx" }
syn = { version = "2", default-features = false, features = [
"full",
"parsing",
"printing",
"proc-macro",
"visit-mut",
] }
thiserror = { version = "2", default-features = false }
time = { version = "0.3", default-features = false, features = [
Expand All @@ -195,7 +203,7 @@ wkt = { version = "0.14", default-features = false, features = [
[dependencies]
sqlx-exasol-macros = { workspace = true, optional = true }
sqlx-exasol-impl = { workspace = true }
sqlx = { workspace = true }
sqlx-a-orig = { workspace = true }

[dev-dependencies]
dotenvy = { workspace = true }
Expand Down Expand Up @@ -225,4 +233,3 @@ unused_extern_crates = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_qualifications = "warn"
# Enable parsing of `sqlx.toml` for configuring macros and migrations.
17 changes: 3 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,9 @@ it can do all the drivers shipped with `sqlx` do, with some caveats:

## Compile-time query checks

The driver now supports compile-time query validation.

However, full functionality is implemented through path overrides and due to `sqlx` macros
implementation details you will currently need to either add `extern crate sqlx_exasol as sqlx;`
to the root of your crate or rename the crate import to `sqlx` in `Cargo.toml`:

```toml
sqlx = { version = "*", package = "sqlx-exasol" }
```

This implies that the compile time query macros from both `sqlx-exasol` and `sqlx`
cannot co-exist within the same crate without collisions or unexpected surprises.

See <https://github.com/launchbadge/sqlx/pull/3944> for more details.
The driver now supports compile-time query validation and can be used alongside
`sqlx` within the same crate. Note however that derive proc-macros from `sqlx` are
database agnostic and thus `sqlx-exasol` just re-exports them as-is.

## CLI utility

Expand Down
6 changes: 5 additions & 1 deletion sqlx-exasol-impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ uuid = { workspace = true, optional = true }
wkt = { workspace = true, optional = true }

[dev-dependencies]
sqlx = { workspace = true, features = ["runtime-tokio", "macros", "migrate"] }
sqlx-a-orig = { workspace = true, features = [
"runtime-tokio",
"macros",
"migrate",
] }

[lints]
workspace = true
3 changes: 3 additions & 0 deletions sqlx-exasol-impl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
//! **EXASOL** database driver.

#[cfg(test)]
extern crate sqlx_a_orig as sqlx;

#[cfg(feature = "native-tls")]
use native_tls as _;
#[cfg(feature = "tls")]
Expand Down
8 changes: 4 additions & 4 deletions sqlx-exasol-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ proc-macro = true
# SQLx features
derive = ["dep:sqlx-macros-core", "sqlx-macros-core?/derive"]
macros = [
"dep:syn",
"dep:quote",
"dep:sqlx-macros-core",
"dep:sqlx-exasol-impl",
"sqlx-macros-core?/macros",
Expand Down Expand Up @@ -72,10 +70,12 @@ uuid = [
]

[dependencies]
proc-macro2 = { workspace = true }
syn = { workspace = true }
quote = { workspace = true }

sqlx-macros-core = { workspace = true, optional = true }
sqlx-exasol-impl = { workspace = true, optional = true }
syn = { workspace = true, optional = true }
quote = { workspace = true, optional = true }

[lints]
workspace = true
89 changes: 87 additions & 2 deletions sqlx-exasol-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,97 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]

mod parse;

#[allow(unused_imports, reason = "built-in; conditionally compiled")]
use proc_macro::TokenStream;

#[cfg(feature = "macros")]
#[proc_macro]
pub fn expand_query(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
pub fn expand_query(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as sqlx_macros_core::query::QueryMacroInput);

match sqlx_macros_core::query::expand_input(input, &[sqlx_exasol_impl::QUERY_DRIVER]) {
Ok(ts) => ts.into(),
Ok(ts) => parse::rewrite(ts).into(),
Err(e) => {
if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
parse_err.to_compile_error().into()
} else {
let msg = e.to_string();
quote::quote!(::std::compile_error!(#msg)).into()
}
}
}
}

#[cfg(feature = "derive")]
#[proc_macro_derive(Encode, attributes(sqlx))]
pub fn derive_encode(tokenstream: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(tokenstream as syn::DeriveInput);
match sqlx_macros_core::derives::expand_derive_encode(&input) {
Ok(ts) => parse::rewrite(ts).into(),
Err(e) => e.to_compile_error().into(),
}
}

#[cfg(feature = "derive")]
#[proc_macro_derive(Decode, attributes(sqlx))]
pub fn derive_decode(tokenstream: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(tokenstream as syn::DeriveInput);
match sqlx_macros_core::derives::expand_derive_decode(&input) {
Ok(ts) => parse::rewrite(ts).into(),
Err(e) => e.to_compile_error().into(),
}
}

#[cfg(feature = "derive")]
#[proc_macro_derive(Type, attributes(sqlx))]
pub fn derive_type(tokenstream: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(tokenstream as syn::DeriveInput);
match sqlx_macros_core::derives::expand_derive_type_encode_decode(&input) {
Ok(ts) => parse::rewrite(ts).into(),
Err(e) => e.to_compile_error().into(),
}
}

#[cfg(feature = "derive")]
#[proc_macro_derive(FromRow, attributes(sqlx))]
pub fn derive_from_row(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);

match sqlx_macros_core::derives::expand_derive_from_row(&input) {
Ok(ts) => parse::rewrite(ts).into(),
Err(e) => e.to_compile_error().into(),
}
}

#[cfg(feature = "migrate")]
#[proc_macro]
pub fn migrate(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as Option<syn::LitStr>);
match sqlx_macros_core::migrate::expand(input) {
// Wrap the TokenStream in a block so it can be parsed as a syn::Stmt.
// Otherwise we'd have to special case this to a syn::Expr.
//
// NOTE: Stmt::Expr variant does not normally apply here!
Ok(ts) => parse::rewrite(quote::quote!({#ts})).into(),
Err(e) => {
if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
parse_err.to_compile_error().into()
} else {
let msg = e.to_string();
quote::quote!(::std::compile_error!(#msg)).into()
}
}
}
}

#[cfg(feature = "macros")]
#[proc_macro_attribute]
pub fn test(args: TokenStream, input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::ItemFn);

match sqlx_macros_core::test_attr::expand(args.into(), input) {
Ok(ts) => parse::rewrite(ts).into(),
Err(e) => {
if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
parse_err.to_compile_error().into()
Expand Down
57 changes: 57 additions & 0 deletions sqlx-exasol-macros/src/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use proc_macro2::TokenStream;
use syn::{parse::Parse, visit_mut::VisitMut, ItemUse, Path, Stmt, UseTree};

/// Rewrites `::sqlx::` to `::sqlx_exasol::`.
pub fn rewrite(token_stream: TokenStream) -> TokenStream {
let mut stmts: Vec<Stmt> = match syn::parse2(token_stream) {
Ok(Stmts(stmts)) => stmts,
Err(err) => return err.to_compile_error(),
};

for stmt in &mut stmts {
SqlxToSqlxExasol.visit_stmt_mut(stmt);
}

quote::quote!(#(#stmts)*)
}

struct Stmts(Vec<Stmt>);

impl Parse for Stmts {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let mut items = Vec::new();
while !input.is_empty() {
items.push(input.parse()?);
}
Ok(Self(items))
}
}

struct SqlxToSqlxExasol;

impl VisitMut for SqlxToSqlxExasol {
fn visit_path_mut(&mut self, path: &mut Path) {
// Recurse to also match inner paths such as generics.
syn::visit_mut::visit_path_mut(self, path);

// Match ::sqlx::...
if path.leading_colon.is_some() {
if let Some(segment) = path.segments.first_mut() {
if segment.ident == "sqlx" {
segment.ident = syn::Ident::new("sqlx_exasol", segment.ident.span());
}
}
}
}

fn visit_item_use_mut(&mut self, item_use: &mut ItemUse) {
// Match ::sqlx::...
if item_use.leading_colon.is_some() {
if let UseTree::Path(path) = &mut item_use.tree {
if path.ident == "sqlx" {
path.ident = syn::Ident::new("sqlx_exasol", path.ident.span());
}
}
}
}
}
Loading