Skip to content

Commit

Permalink
Enable sqlite returning with feature flag (#2070)
Browse files Browse the repository at this point in the history
* Enable sqlite returning with feature flag

* Add runtime check for sqlite version

* Apply suggestions from code review

* Fix clippy

* Rename feature

* Fix tests when feature flag is not enabled

---------

Co-authored-by: Chris Tsang <chris.2y3@outlook.com>
acrrd and tyt2y3 authored Jan 25, 2024
1 parent 1abb0b2 commit c56c072
Showing 7 changed files with 118 additions and 11 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -308,6 +308,7 @@ jobs:
matrix:
runtime: [async-std]
tls: [native-tls, rustls]
sqlite_flavor: ["", sqlite-use-returning-for-3_35]
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
@@ -319,8 +320,8 @@ jobs:
Cargo.lock
target
key: ${{ github.sha }}-${{ github.run_id }}-${{ runner.os }}-sqlite-${{ matrix.runtime }}-${{ matrix.tls }}
- run: cargo test --test '*' --features default,sqlx-sqlite,runtime-${{ matrix.runtime }}-${{ matrix.tls }}
- run: cargo test --manifest-path sea-orm-migration/Cargo.toml --test '*' --features sqlx-sqlite,runtime-${{ matrix.runtime }}-${{ matrix.tls }}
- run: cargo test --test '*' --features default,sqlx-sqlite,runtime-${{ matrix.runtime }}-${{ matrix.tls }},${{ matrix.sqlite_flavor }}
- run: cargo test --manifest-path sea-orm-migration/Cargo.toml --test '*' --features sqlx-sqlite,runtime-${{ matrix.runtime }}-${{ matrix.tls }},${{ matrix.sqlite_flavor }}

mysql:
name: MySQL
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -90,6 +90,7 @@ sqlx-all = ["sqlx-mysql", "sqlx-postgres", "sqlx-sqlite"]
sqlx-mysql = ["sqlx-dep", "sea-query-binder/sqlx-mysql", "sqlx/mysql"]
sqlx-postgres = ["sqlx-dep", "sea-query-binder/sqlx-postgres", "sqlx/postgres", "postgres-array"]
sqlx-sqlite = ["sqlx-dep", "sea-query-binder/sqlx-sqlite", "sqlx/sqlite"]
sqlite-use-returning-for-3_35 = []
runtime-async-std = []
runtime-async-std-native-tls = [
"sqlx?/runtime-async-std-native-tls",
@@ -128,4 +129,4 @@ seaography = ["sea-orm-macros/seaography"]

# This allows us to develop using a local version of sea-query
# [patch.crates-io]
# sea-query = { path = "../sea-query" }
# sea-query = { path = "../sea-query" }
1 change: 1 addition & 0 deletions sea-orm-migration/Cargo.toml
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@ cli = ["clap", "dotenvy", "sea-orm-cli/cli"]
sqlx-mysql = ["sea-orm/sqlx-mysql"]
sqlx-postgres = ["sea-orm/sqlx-postgres"]
sqlx-sqlite = ["sea-orm/sqlx-sqlite"]
sqlite-use-returning-for-3_35 = ["sea-orm/sqlite-use-returning-for-3_35"]
runtime-actix-native-tls = ["sea-orm/runtime-actix-native-tls"]
runtime-async-std-native-tls = ["sea-orm/runtime-async-std-native-tls"]
runtime-tokio-native-tls = ["sea-orm/runtime-tokio-native-tls"]
10 changes: 9 additions & 1 deletion src/database/db_connection.rs
Original file line number Diff line number Diff line change
@@ -578,7 +578,15 @@ impl DbBackend {

/// Check if the database supports `RETURNING` syntax on insert and update
pub fn support_returning(&self) -> bool {
matches!(self, Self::Postgres)
#[cfg(not(feature = "sqlite-use-returning-for-3_35"))]
{
matches!(self, Self::Postgres)
}

#[cfg(feature = "sqlite-use-returning-for-3_35")]
{
matches!(self, Self::Postgres | Self::Sqlite)
}
}
}

98 changes: 92 additions & 6 deletions src/driver/sqlx_sqlite.rs
Original file line number Diff line number Diff line change
@@ -12,8 +12,9 @@ use sea_query_binder::SqlxValues;
use tracing::{instrument, warn};

use crate::{
debug_print, error::*, executor::*, AccessMode, ConnectOptions, DatabaseConnection,
DatabaseTransaction, IsolationLevel, QueryStream, Statement, TransactionError,
debug_print, error::*, executor::*, sqlx_error_to_exec_err, AccessMode, ConnectOptions,
DatabaseConnection, DatabaseTransaction, IsolationLevel, QueryStream, Statement,
TransactionError,
};

use super::sqlx_common::*;
@@ -68,12 +69,20 @@ impl SqlxSqliteConnector {
options.max_connections(1);
}
match options.pool_options().connect_with(opt).await {
Ok(pool) => Ok(DatabaseConnection::SqlxSqlitePoolConnection(
SqlxSqlitePoolConnection {
Ok(pool) => {
let pool = SqlxSqlitePoolConnection {
pool,
metric_callback: None,
},
)),
};

#[cfg(feature = "sqlite-use-returning-for-3_35")]
{
let version = get_version(&pool).await?;
ensure_returning_version(&version)?;
}

Ok(DatabaseConnection::SqlxSqlitePoolConnection(pool))
}
Err(e) => Err(sqlx_error_to_conn_err(e)),
}
}
@@ -268,3 +277,80 @@ pub(crate) async fn set_transaction_config(
}
Ok(())
}

#[cfg(feature = "sqlite-use-returning-for-3_35")]
async fn get_version(conn: &SqlxSqlitePoolConnection) -> Result<String, DbErr> {
let stmt = Statement {
sql: "SELECT sqlite_version()".to_string(),
values: None,
db_backend: crate::DbBackend::Sqlite,
};
conn.query_one(stmt)
.await?
.ok_or_else(|| {
DbErr::Conn(RuntimeErr::Internal(
"Error reading SQLite version".to_string(),
))
})?
.try_get_by(0)
}

#[cfg(feature = "sqlite-use-returning-for-3_35")]
fn ensure_returning_version(version: &str) -> Result<(), DbErr> {
let mut parts = version.trim().split('.').map(|part| {
part.parse::<u32>().map_err(|_| {
DbErr::Conn(RuntimeErr::Internal(
"Error parsing SQLite version".to_string(),
))
})
});

let mut extract_next = || {
parts.next().transpose().and_then(|part| {
part.ok_or_else(|| {
DbErr::Conn(RuntimeErr::Internal("SQLite version too short".to_string()))
})
})
};

let major = extract_next()?;
let minor = extract_next()?;

if major > 3 || (major == 3 && minor >= 35) {
Ok(())
} else {
Err(DbErr::Conn(RuntimeErr::Internal(
"SQLite version does not support returning".to_string(),
)))
}
}

#[cfg(all(test, feature = "sqlite-use-returning-for-3_35"))]
mod tests {
use super::*;

#[test]
fn test_ensure_returning_version() {
assert!(ensure_returning_version("").is_err());
assert!(ensure_returning_version(".").is_err());
assert!(ensure_returning_version(".a").is_err());
assert!(ensure_returning_version(".4.9").is_err());
assert!(ensure_returning_version("a").is_err());
assert!(ensure_returning_version("1.").is_err());
assert!(ensure_returning_version("1.a").is_err());

assert!(ensure_returning_version("1.1").is_err());
assert!(ensure_returning_version("1.0.").is_err());
assert!(ensure_returning_version("1.0.0").is_err());
assert!(ensure_returning_version("2.0.0").is_err());
assert!(ensure_returning_version("3.34.0").is_err());
assert!(ensure_returning_version("3.34.999").is_err());

// valid version
assert!(ensure_returning_version("3.35.0").is_ok());
assert!(ensure_returning_version("3.35.1").is_ok());
assert!(ensure_returning_version("3.36.0").is_ok());
assert!(ensure_returning_version("4.0.0").is_ok());
assert!(ensure_returning_version("99.0.0").is_ok());
}
}
4 changes: 4 additions & 0 deletions tests/crud/error.rs
Original file line number Diff line number Diff line change
@@ -40,6 +40,10 @@ pub async fn test_cake_error_sqlx(db: &DbConn) {
}
_ => panic!("Unexpected sqlx-error kind"),
},
#[cfg(all(feature = "sqlx-sqlite", feature = "sqlite-use-returning-for-3_35"))]
DbErr::Query(RuntimeErr::SqlxError(Error::Database(e))) => {
assert_eq!(e.code().unwrap(), "1555");
}
_ => panic!("Unexpected Error kind"),
}
#[cfg(feature = "sqlx-postgres")]
8 changes: 7 additions & 1 deletion tests/returning_tests.rs
Original file line number Diff line number Diff line change
@@ -75,7 +75,13 @@ async fn main() -> Result<(), DbErr> {
feature = "sqlx-postgres"
))]
#[cfg_attr(
any(feature = "sqlx-mysql", feature = "sqlx-sqlite"),
any(
feature = "sqlx-mysql",
all(
feature = "sqlx-sqlite",
not(feature = "sqlite-use-returning-for-3_35")
)
),
should_panic(expected = "Database backend doesn't support RETURNING")
)]
async fn update_many() {

0 comments on commit c56c072

Please sign in to comment.