Added support for BIT columns in Microsoft SQL Server.

fix https://github.com/sqlpage/SQLPage/issues/666

also add more tests for database type decoding
This commit is contained in:
lovasoa
2024-11-02 18:12:30 +01:00
parent a72cb74659
commit bd463759ad
5 changed files with 270 additions and 61 deletions
+1
View File
@@ -6,6 +6,7 @@
- Fixed a bug where [timeline chart tooltips displayed the wrong labels](https://github.com/sqlpage/SQLPage/issues/659).
- Fixed an incorrect warning polluting logs when using sqlpage functions with json arguments in sqlite: `WARN sqlpage::webserver::database::execute_queries] The column _sqlpage_f0_a1 is missing from the result set, so it cannot be converted to JSON.`.
- Fixed Microsoft SQL Server driver not being able to read VARCHAR columns from databases with non-european collations.
- Added support for `BIT` columns in Microsoft SQL Server.
## 0.30.1 (2024-10-31)
- fix a bug where table sorting would break if table search was not also enabled.
Generated
+34 -34
View File
@@ -66,7 +66,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
dependencies = [
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -104,7 +104,7 @@ dependencies = [
"parse-size",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -245,7 +245,7 @@ dependencies = [
"actix-router",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -528,7 +528,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -550,7 +550,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -567,7 +567,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -798,9 +798,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.1.31"
version = "1.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9"
dependencies = [
"jobserver",
"libc",
@@ -858,7 +858,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1088,7 +1088,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1099,7 +1099,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1158,7 +1158,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1202,7 +1202,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1428,7 +1428,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -2301,7 +2301,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -2332,7 +2332,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -2776,7 +2776,7 @@ checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -2985,14 +2985,14 @@ checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
name = "sqlx-core-oldapi"
version = "0.6.30"
version = "0.6.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d300a966e0d42da5e0efff2cb2cf289b0df9e5d6be9859b716060469da12329"
checksum = "549c5d36f47ea1468260b164561d3824319199e6f9df22be3917e083273540ca"
dependencies = [
"ahash",
"atoi",
@@ -3054,9 +3054,9 @@ dependencies = [
[[package]]
name = "sqlx-macros-oldapi"
version = "0.6.30"
version = "0.6.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "393d2e9a261be031d7707ac53d128ba2c655097be4a74d087027468014e8512a"
checksum = "5b4e97bfb6525ede22cefeb01166f59b5156fc288c946c3e16dae691c9ae8783"
dependencies = [
"dotenvy",
"either",
@@ -3074,9 +3074,9 @@ dependencies = [
[[package]]
name = "sqlx-oldapi"
version = "0.6.30"
version = "0.6.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cde0a390c5079a7d0bca3ea3b900e246c618b176b2606ec586b1cc711312cd5"
checksum = "f6db6ce44bac821c06994efc399f243904390fded53c71cd8cb27017b1221233"
dependencies = [
"sqlx-core-oldapi",
"sqlx-macros-oldapi",
@@ -3084,9 +3084,9 @@ dependencies = [
[[package]]
name = "sqlx-rt-oldapi"
version = "0.6.30"
version = "0.6.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870a763531da2e47a4efca6b6483c4ae57aedeafe5c57edef05b6063c0923278"
checksum = "bb59aa67aa1b9b6f79a82f0f234029e47cdb47c3f17f0b57a5c2e1d2c6d8ed77"
dependencies = [
"once_cell",
"tokio",
@@ -3129,9 +3129,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.86"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
@@ -3180,7 +3180,7 @@ checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -3264,7 +3264,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -3384,7 +3384,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -3555,7 +3555,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
"wasm-bindgen-shared",
]
@@ -3589,7 +3589,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3851,7 +3851,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
+1 -1
View File
@@ -18,7 +18,7 @@ panic = "abort"
codegen-units = 2
[dependencies]
sqlx = { package = "sqlx-oldapi", version = "0.6.30", features = [
sqlx = { package = "sqlx-oldapi", version = "0.6.32", features = [
"any",
"runtime-actix-rustls",
"sqlite",
+5
View File
@@ -1,4 +1,9 @@
# You can easily switch between different databases by changing the value of COMPOSE_PROFILES in the .env file.
# possible database connection strings:
# DATABASE_URL='postgres://root:Password123!@localhost/sqlpage'
# DATABASE_URL='mssql://root:Password123!@localhost/sqlpage'
# DATABASE_URL='mysql://root:Password123!@localhost/sqlpage'
services:
web:
build: { context: "." }
+229 -26
View File
@@ -1,7 +1,7 @@
use crate::utils::add_value_to_map;
use chrono::{DateTime, Utc};
use serde_json::{self, Map, Value};
use sqlx::any::AnyRow;
use sqlx::any::{AnyRow, AnyTypeInfo, AnyTypeInfoKind};
use sqlx::Decode;
use sqlx::{Column, Row, TypeInfo, ValueRef};
@@ -63,6 +63,11 @@ pub fn sql_nonnull_to_json<'r>(mut get_ref: impl FnMut() -> sqlx::any::AnyValueR
"BOOL" | "BOOLEAN" => <bool as Decode<sqlx::any::Any>>::decode(raw_value)
.unwrap_or_default()
.into(),
"BIT" if matches!(*type_info, AnyTypeInfo(AnyTypeInfoKind::Mssql(_))) => {
<bool as Decode<sqlx::any::Any>>::decode(raw_value)
.unwrap_or_default()
.into()
}
"DATE" => <chrono::NaiveDate as Decode<sqlx::any::Any>>::decode(raw_value)
.as_ref()
.map_or_else(std::string::ToString::to_string, ToString::to_string)
@@ -103,30 +108,228 @@ pub fn row_to_string(row: &AnyRow) -> Option<String> {
}
}
#[actix_web::test]
async fn test_row_to_json() -> anyhow::Result<()> {
#[cfg(test)]
mod tests {
use super::*;
use sqlx::Connection;
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite://:memory:".to_string());
let mut c = sqlx::AnyConnection::connect(&db_url).await?;
let row = sqlx::query(
"SELECT \
123.456 as one_value, \
1 as two_values, \
2 as two_values, \
'x' as three_values, \
'y' as three_values, \
'z' as three_values \
",
)
.fetch_one(&mut c)
.await?;
assert_eq!(
row_to_json(&row),
serde_json::json!({
"one_value": 123.456,
"two_values": [1,2],
"three_values": ["x","y","z"],
})
);
Ok(())
fn db_specific_test(db_type: &str) -> Option<String> {
let db_url =
std::env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite://:memory:".to_string());
if db_url.starts_with(db_type) {
Some(db_url)
} else {
log::warn!("Skipping test because DATABASE_URL is not set to a {db_type} database");
None
}
}
#[actix_web::test]
async fn test_row_to_json() -> anyhow::Result<()> {
use sqlx::Connection;
let db_url =
std::env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite://:memory:".to_string());
let mut c = sqlx::AnyConnection::connect(&db_url).await?;
let row = sqlx::query(
"SELECT \
123.456 as one_value, \
1 as two_values, \
2 as two_values, \
'x' as three_values, \
'y' as three_values, \
'z' as three_values \
",
)
.fetch_one(&mut c)
.await?;
assert_eq!(
row_to_json(&row),
serde_json::json!({
"one_value": 123.456,
"two_values": [1,2],
"three_values": ["x","y","z"],
})
);
Ok(())
}
#[actix_web::test]
async fn test_mssql_bit_to_json() -> anyhow::Result<()> {
let Some(db_url) = db_specific_test("mssql") else {
return Ok(());
};
let mut c = sqlx::AnyConnection::connect(&db_url).await?;
let row = sqlx::query("SELECT CAST(1 AS BIT) as true_bit, CAST(0 AS BIT) as false_bit, CAST(NULL AS BIT) as null_bit")
.fetch_one(&mut c)
.await?;
assert_eq!(
row_to_json(&row),
serde_json::json!({
"true_bit": true,
"false_bit": false,
"null_bit": null,
})
);
Ok(())
}
#[actix_web::test]
async fn test_postgres_types() -> anyhow::Result<()> {
let Some(db_url) = db_specific_test("postgres") else {
return Ok(());
};
let mut c = sqlx::AnyConnection::connect(&db_url).await?;
let row = sqlx::query(
"SELECT
42::INT2 as small_int,
42::INT4 as integer,
42::INT8 as big_int,
42.42::FLOAT4 as float4,
42.42::FLOAT8 as float8,
TRUE as boolean,
'2024-03-14'::DATE as date,
'13:14:15'::TIME as time,
'2024-03-14 13:14:15'::TIMESTAMP as timestamp,
'2024-03-14 13:14:15+00'::TIMESTAMPTZ as timestamptz,
'{\"key\": \"value\"}'::JSON as json,
'{\"key\": \"value\"}'::JSONB as jsonb",
)
.fetch_one(&mut c)
.await?;
assert_eq!(
row_to_json(&row),
serde_json::json!({
"small_int": 42,
"integer": 42,
"big_int": 42,
"float4": 42.42,
"float8": 42.42,
"boolean": true,
"date": "2024-03-14",
"time": "13:14:15",
"timestamp": "2024-03-14T13:14:15+00:00",
"timestamptz": "2024-03-14T13:14:15+00:00",
"json": {"key": "value"},
"jsonb": {"key": "value"},
})
);
Ok(())
}
#[actix_web::test]
async fn test_mysql_types() -> anyhow::Result<()> {
let Some(db_url) = db_specific_test("mysql") else {
return Ok(());
};
let mut c = sqlx::AnyConnection::connect(&db_url).await?;
let row = sqlx::query(
"SELECT
CAST(42 AS SIGNED) as signed_int,
CAST(42 AS UNSIGNED) as unsigned_int,
42.42 as decimal_number,
TRUE as boolean,
CAST('2024-03-14' AS DATE) as date,
CAST('13:14:15' AS TIME) as time,
CAST('2024-03-14 13:14:15' AS DATETIME) as datetime,
CAST('{\"key\": \"value\"}' AS JSON) as json",
)
.fetch_one(&mut c)
.await?;
assert_eq!(
row_to_json(&row),
serde_json::json!({
"signed_int": 42,
"unsigned_int": 42,
"decimal_number": 42.42,
"boolean": true,
"date": "2024-03-14",
"time": "13:14:15",
"datetime": "2024-03-14T13:14:15+00:00",
"json": {"key": "value"},
})
);
Ok(())
}
#[actix_web::test]
async fn test_sqlite_types() -> anyhow::Result<()> {
let Some(db_url) = db_specific_test("sqlite") else {
return Ok(());
};
let mut c = sqlx::AnyConnection::connect(&db_url).await?;
let row = sqlx::query(
"SELECT
42 as integer,
42.42 as real,
'xxx' as string,
x'68656c6c6f20776f726c64' as blob",
)
.fetch_one(&mut c)
.await?;
assert_eq!(
row_to_json(&row),
serde_json::json!({
"integer": 42,
"real": 42.42,
"string": "xxx",
"blob": "hello world",
})
);
Ok(())
}
#[actix_web::test]
async fn test_mssql_types() -> anyhow::Result<()> {
let Some(db_url) = db_specific_test("mssql") else {
return Ok(());
};
let mut c = sqlx::AnyConnection::connect(&db_url).await?;
let row = sqlx::query(
"SELECT
CAST(1 AS BIT) as true_bit,
CAST(0 AS BIT) as false_bit,
CAST(NULL AS BIT) as null_bit,
CAST(42 AS SMALLINT) as small_int,
CAST(42 AS INT) as integer,
CAST(42 AS BIGINT) as big_int,
CAST(42.42 AS REAL) as real,
CAST(42.42 AS FLOAT) as float,
CAST(42.42 AS DECIMAL(10,2)) as decimal,
CAST('2024-03-14' AS DATE) as date,
CAST('13:14:15' AS TIME) as time,
CAST('2024-03-14 13:14:15' AS DATETIME) as datetime,
CAST('2024-03-14 13:14:15' AS DATETIME2) as datetime2,
CAST('2024-03-14 13:14:15 +00:00' AS DATETIMEOFFSET) as datetimeoffset,
N'Unicode String' as nvarchar,
'ASCII String' as varchar",
)
.fetch_one(&mut c)
.await?;
assert_eq!(
row_to_json(&row),
serde_json::json!({
"true_bit": true,
"false_bit": false,
"null_bit": null,
"small_int": 42,
"integer": 42,
"big_int": 42,
"real": 42.42,
"float": 42.42,
"decimal": 42.42,
"date": "2024-03-14",
"time": "13:14:15",
"datetime": "2024-03-14T13:14:15+00:00",
"datetime2": "2024-03-14T13:14:15+00:00",
"datetimeoffset": "2024-03-14T13:14:15+00:00",
"nvarchar": "Unicode String",
"varchar": "ASCII String",
})
);
Ok(())
}
}