mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
The database tool window can connect to Microsoft SQL Server (tiberius over rustls with a bb8 pool): browse schema objects, edit table data, run queries and view plans; SQL Server dialect, object dialogs and icons in the UI. Values are rendered by column type: exact decimal/numeric, money with four decimals, tinyint, uniqueidentifier, date/time/datetime/datetime2/smalldatetime/datetimeoffset in ISO form, xml and binary. Multi-statement edits roll back on any failure and discard the connection rather than returning it to the pool mid-transaction; connect-time errors (login, database) are reported; result pagination reports more rows; cancelling a query stops reading and drops the stream. The sqlx drivers keep the shared with_pool! path. Co-authored-by: dimassfeb-09 <62858781+dimassfeb-09@users.noreply.github.com>
98 lines
4.5 KiB
Rust
98 lines
4.5 KiB
Rust
//! Live SQL Server checks, skipped unless OPENIDEA_TEST_SQLSERVER=host:port:user:password
|
|
//! names a throwaway server.
|
|
|
|
use ide_core::CancellationToken;
|
|
use ide_db::{DataSource, DbDriver, DbSslMode, Held, Pool};
|
|
use std::time::Duration;
|
|
|
|
#[tokio::test]
|
|
async fn sqlserver_values_paging_transactions_and_cancellation() {
|
|
let Ok(server) = std::env::var("OPENIDEA_TEST_SQLSERVER") else { return };
|
|
let mut parts = server.splitn(4, ':');
|
|
let source = DataSource {
|
|
driver: DbDriver::SqlServer,
|
|
host: parts.next().unwrap().into(),
|
|
port: Some(parts.next().unwrap().parse().unwrap()),
|
|
user: parts.next().unwrap().into(),
|
|
database: "tempdb".into(),
|
|
ssl: DbSslMode::Disable,
|
|
..Default::default()
|
|
};
|
|
let pool = Pool::connect(&source, parts.next(), None).await.unwrap();
|
|
let cancel = CancellationToken::new();
|
|
let mut held = Held::acquire(&pool).await.unwrap();
|
|
let table = format!("tempdb.dbo.oi_pr276_values_{}", std::process::id());
|
|
held.command(&format!("CREATE TABLE {table} (id int PRIMARY KEY, d decimal(38, 7), dt datetime2(7), guid uniqueidentifier, m money, tiny tinyint, x xml)")).await.unwrap();
|
|
held.command(&format!("INSERT INTO {table} VALUES (1, 12345678901234567890.1234567, '2024-02-29T12:34:56.1234567', 'abcdef01-2345-6789-abcd-ef0123456789', 12.3400, 255, '<a/>'), (2, NULL, NULL, NULL, NULL, NULL, NULL)")).await.unwrap();
|
|
let r = held.execute(&pool, &format!("SELECT * FROM {table} ORDER BY id"), 1, &cancel).await.unwrap();
|
|
assert!(r.has_more);
|
|
assert_eq!(
|
|
r.rows[0],
|
|
[
|
|
"1",
|
|
"12345678901234567890.1234567",
|
|
"2024-02-29 12:34:56.1234567",
|
|
"abcdef01-2345-6789-abcd-ef0123456789",
|
|
"12.3400",
|
|
"255",
|
|
"<a/>"
|
|
]
|
|
.map(|s| Some(s.to_owned()))
|
|
);
|
|
let r = held.execute(&pool, &format!("SELECT * FROM {table} WHERE id = 2"), 5, &cancel).await.unwrap();
|
|
assert!(!r.has_more);
|
|
assert!(r.rows[0][1..].iter().all(Option::is_none));
|
|
let r = held.execute(&pool, &format!("SELECT * FROM {table} WHERE id = 3"), 5, &cancel).await.unwrap();
|
|
assert_eq!(r.columns.len(), 7);
|
|
assert!(r.rows.is_empty());
|
|
let r = held.execute(&pool,
|
|
"SELECT CAST('2024-02-29T00:15:00.1234567+05:30' AS datetimeoffset(7)), CAST('2024-02-29' AS date), CAST('12:34:56.1234567' AS time(7)), CAST('2024-02-29T12:34:56.007' AS datetime), CAST('2024-02-29T12:34:00' AS smalldatetime), CAST(-12.3400 AS smallmoney)",
|
|
1, &cancel).await.unwrap();
|
|
assert_eq!(
|
|
r.rows[0],
|
|
[
|
|
"2024-02-29 00:15:00.1234567+05:30",
|
|
"2024-02-29",
|
|
"12:34:56.1234567",
|
|
"2024-02-29 12:34:56.007",
|
|
"2024-02-29 12:34:00",
|
|
"-12.3400"
|
|
]
|
|
.map(|s| Some(s.to_owned()))
|
|
);
|
|
held.command(&format!("DROP TABLE {table}")).await.unwrap();
|
|
drop(held);
|
|
|
|
// A unique permanent table lets transaction calls acquire any pooled connection.
|
|
let table = format!("tempdb.dbo.oi_pr276_{}", std::process::id());
|
|
pool.execute(&format!("CREATE TABLE {table} (id int PRIMARY KEY, v int)"), 1, &cancel).await.unwrap();
|
|
pool.transaction(&[(format!("INSERT INTO {table} VALUES (1, 10)"), true)]).await.unwrap();
|
|
for failing in [format!("INSERT INTO {table} VALUES (1, 20)"), format!("UPDATE {table} SET v = 30 WHERE id = 999")]
|
|
{
|
|
assert!(
|
|
pool.transaction(&[(format!("UPDATE {table} SET v = 99 WHERE id = 1"), true), (failing, true)])
|
|
.await
|
|
.is_err()
|
|
);
|
|
assert_eq!(pool.rows(&format!("SELECT v FROM {table}"), 1).await.unwrap()[0][0].as_deref(), Some("10"));
|
|
assert_eq!(pool.rows("SELECT @@TRANCOUNT", 1).await.unwrap()[0][0].as_deref(), Some("0"));
|
|
}
|
|
assert_eq!(pool.transaction(&[(format!("UPDATE {table} SET v = 11 WHERE id = 1"), true)]).await.unwrap(), 1);
|
|
pool.execute(&format!("DROP TABLE {table}"), 1, &cancel).await.unwrap();
|
|
|
|
let cancel = CancellationToken::new();
|
|
let stop = cancel.clone();
|
|
let task = tokio::spawn(async move {
|
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
stop.cancel();
|
|
});
|
|
assert!(
|
|
tokio::time::timeout(Duration::from_secs(2), pool.execute("WAITFOR DELAY '00:00:10'; SELECT 1", 1, &cancel))
|
|
.await
|
|
.unwrap()
|
|
.is_err()
|
|
);
|
|
task.await.unwrap();
|
|
assert_eq!(pool.rows("SELECT 1", 1).await.unwrap()[0][0].as_deref(), Some("1"));
|
|
}
|