Files
rustfs/crates/s3select-query/src/test/error_handling_test.rs
T
junxiang Mu c03f86b23c s3Select: add unit test case
Signed-off-by: junxiang Mu <1948535941@qq.com>
2025-07-28 09:19:47 +08:00

248 lines
9.2 KiB
Rust

// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[cfg(test)]
mod error_handling_tests {
use crate::get_global_db;
use rustfs_s3select_api::{
QueryError,
query::{Context, Query},
};
use s3s::dto::{
CSVInput, ExpressionType, FileHeaderInfo, InputSerialization, SelectObjectContentInput, SelectObjectContentRequest,
};
use std::sync::Arc;
fn create_test_input_with_sql(sql: &str) -> SelectObjectContentInput {
SelectObjectContentInput {
bucket: "test-bucket".to_string(),
expected_bucket_owner: None,
key: "test.csv".to_string(),
sse_customer_algorithm: None,
sse_customer_key: None,
sse_customer_key_md5: None,
request: SelectObjectContentRequest {
expression: sql.to_string(),
expression_type: ExpressionType::from_static("SQL"),
input_serialization: InputSerialization {
csv: Some(CSVInput {
file_header_info: Some(FileHeaderInfo::from_static(FileHeaderInfo::USE)),
..Default::default()
}),
..Default::default()
},
output_serialization: s3s::dto::OutputSerialization::default(),
request_progress: None,
scan_range: None,
},
}
}
#[tokio::test]
async fn test_syntax_error_handling() {
let invalid_sqls = vec![
"INVALID SQL",
"SELECT FROM",
"SELECT * FORM S3Object", // typo in FROM
"SELECT * FROM",
"SELECT * FROM S3Object WHERE",
"SELECT COUNT( FROM S3Object", // missing closing parenthesis
];
for sql in invalid_sqls {
let input = create_test_input_with_sql(sql);
let db = get_global_db(input.clone(), true).await.unwrap();
let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());
let result = db.execute(&query).await;
assert!(result.is_err(), "Expected error for SQL: {sql}");
}
}
#[tokio::test]
async fn test_multi_statement_error() {
let multi_statement_sqls = vec![
"SELECT * FROM S3Object; SELECT 1;",
"SELECT 1; SELECT 2; SELECT 3;",
"SELECT * FROM S3Object; DROP TABLE test;",
];
for sql in multi_statement_sqls {
let input = create_test_input_with_sql(sql);
let db = get_global_db(input.clone(), true).await.unwrap();
let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());
let result = db.execute(&query).await;
assert!(result.is_err(), "Expected multi-statement error for SQL: {sql}");
if let Err(QueryError::MultiStatement { num, .. }) = result {
assert!(num >= 2, "Expected at least 2 statements, got: {num}");
}
}
}
#[tokio::test]
async fn test_unsupported_operations() {
let unsupported_sqls = vec![
"INSERT INTO S3Object VALUES (1, 'test')",
"UPDATE S3Object SET name = 'test'",
"DELETE FROM S3Object",
"CREATE TABLE test (id INT)",
"DROP TABLE S3Object",
];
for sql in unsupported_sqls {
let input = create_test_input_with_sql(sql);
let db = get_global_db(input.clone(), true).await.unwrap();
let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());
let result = db.execute(&query).await;
// These should either fail with syntax error or not implemented error
assert!(result.is_err(), "Expected error for unsupported SQL: {sql}");
}
}
#[tokio::test]
async fn test_invalid_column_references() {
let invalid_column_sqls = vec![
"SELECT nonexistent_column FROM S3Object",
"SELECT * FROM S3Object WHERE nonexistent_column = 1",
"SELECT * FROM S3Object ORDER BY nonexistent_column",
"SELECT * FROM S3Object GROUP BY nonexistent_column",
];
for sql in invalid_column_sqls {
let input = create_test_input_with_sql(sql);
let db = get_global_db(input.clone(), true).await.unwrap();
let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());
let result = db.execute(&query).await;
// These might succeed or fail depending on schema inference
// The test verifies that the system handles them gracefully
match result {
Ok(_) => {
// If it succeeds, verify we can get results
let handle = result.unwrap();
let output = handle.result().chunk_result().await;
// Should either succeed with empty results or fail gracefully
let _ = output;
}
Err(_) => {
// Expected to fail - this is acceptable
}
}
}
}
#[tokio::test]
async fn test_complex_query_error_recovery() {
let complex_invalid_sql = r#"
SELECT
name,
age,
INVALID_FUNCTION(salary) as invalid_calc,
department
FROM S3Object
WHERE age > 'invalid_number'
GROUP BY department, nonexistent_column
HAVING COUNT(*) > INVALID_FUNCTION()
ORDER BY invalid_column
"#;
let input = create_test_input_with_sql(complex_invalid_sql);
let db = get_global_db(input.clone(), true).await.unwrap();
let query = Query::new(Context { input: Arc::new(input) }, complex_invalid_sql.to_string());
let result = db.execute(&query).await;
assert!(result.is_err(), "Expected error for complex invalid SQL");
}
#[tokio::test]
async fn test_empty_query() {
let empty_sqls = vec!["", " ", "\n\t \n"];
for sql in empty_sqls {
let input = create_test_input_with_sql(sql);
let db = get_global_db(input.clone(), true).await.unwrap();
let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());
let result = db.execute(&query).await;
// Empty queries might be handled differently by the parser
match result {
Ok(_) => {
// Some parsers might accept empty queries
}
Err(_) => {
// Expected to fail for empty SQL
}
}
}
}
#[tokio::test]
async fn test_very_long_query() {
// Create a very long but valid query
let mut long_sql = "SELECT ".to_string();
for i in 0..1000 {
if i > 0 {
long_sql.push_str(", ");
}
long_sql.push_str(&format!("'column_{i}' as col_{i}"));
}
long_sql.push_str(" FROM S3Object LIMIT 1");
let input = create_test_input_with_sql(&long_sql);
let db = get_global_db(input.clone(), true).await.unwrap();
let query = Query::new(Context { input: Arc::new(input) }, long_sql);
let result = db.execute(&query).await;
// This should either succeed or fail gracefully
match result {
Ok(handle) => {
let output = handle.result().chunk_result().await;
assert!(output.is_ok(), "Query execution should complete successfully");
}
Err(_) => {
// Acceptable to fail due to resource constraints
}
}
}
#[tokio::test]
async fn test_sql_injection_patterns() {
let injection_patterns = vec![
"SELECT * FROM S3Object WHERE name = 'test'; DROP TABLE users; --",
"SELECT * FROM S3Object UNION SELECT * FROM information_schema.tables",
"SELECT * FROM S3Object WHERE 1=1 OR 1=1",
];
for sql in injection_patterns {
let input = create_test_input_with_sql(sql);
let db = get_global_db(input.clone(), true).await.unwrap();
let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());
let result = db.execute(&query).await;
// These should be handled safely - either succeed with limited scope or fail
match result {
Ok(_) => {
// If successful, it should only access S3Object data
}
Err(_) => {
// Expected to fail for security reasons
}
}
}
}
}