Skip to content
Open
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
317 changes: 317 additions & 0 deletions nexum_core/src/sql/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,4 +528,321 @@ mod tests {
_ => panic!("Expected DropTable statement"),
}
}

#[test]
fn test_error_empty_sql() {
let sql = "";
let result = Parser::parse(sql);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("No statements found"));
}

#[test]
fn test_error_whitespace_only() {
let sql = " \t\n ";
let result = Parser::parse(sql);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("No statements found"));
}
#[test]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Missing blank line before #[test].

Every other test in this file has a blank line separating it from the previous test. Line 549 breaks the pattern (no blank line after the closing } of test_error_whitespace_only).

     }
+
     #[test]
     fn test_error_create_table_missing_columns() {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[test]
}
#[test]
fn test_error_create_table_missing_columns() {
🤖 Prompt for AI Agents
In `@nexum_core/src/sql/parser.rs` at line 549, Add a blank line between the end
of the previous test function (test_error_whitespace_only) and the following
#[test] attribute so the new test follows the same spacing pattern as the other
tests; locate the closing `}` of `test_error_whitespace_only` and insert one
empty line immediately before the next `#[test]`.

fn test_error_create_table_missing_columns() {
let sql = "CREATE TABLE users";
let result = Parser::parse(sql);
// Parser accepts this, so we skip this test or make it check for successful parse
// Since the underlying sqlparser library might accept this syntax
if result.is_ok() {
// Some parsers allow CREATE TABLE without explicit columns
assert!(true);
} else {
assert!(result.is_err());
}
}
Comment on lines +549 to +561

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Tautological tests that can never fail — these provide no value.

Multiple tests in this file use the pattern:

if result.is_ok() {
    assert!(true);
} else {
    assert!(result.is_err());
}

This is a tautology — the test passes for every possible outcome, so it can never catch a regression. The same pattern appears in: test_error_create_table_missing_columns, test_error_create_table_empty_columns, test_error_special_characters_in_unquoted_identifier, and test_error_reserved_keyword_without_quotes.

If the parser's behavior for these inputs is genuinely dialect-dependent and you want to document it without asserting a specific outcome, either:

  1. Pin the current behavior so you'll notice if it changes, or
  2. Mark them #[ignore] with a reason and open a tracking issue.
Option 1: Pin the expected behavior
 fn test_error_create_table_missing_columns() {
     let sql = "CREATE TABLE users";
     let result = Parser::parse(sql);
-    // Parser accepts this, so we skip this test or make it check for successful parse
-    // Since the underlying sqlparser library might accept this syntax
-    if result.is_ok() {
-        // Some parsers allow CREATE TABLE without explicit columns
-        assert!(true);
-    } else {
-        assert!(result.is_err());
-    }
+    // Pin current behavior: sqlparser rejects CREATE TABLE without columns.
+    // Update this assertion if upgrading sqlparser changes the behavior.
+    assert!(result.is_err(), "Expected error for CREATE TABLE without columns");
 }
🤖 Prompt for AI Agents
In `@nexum_core/src/sql/parser.rs` around lines 549 - 561, The tests like
test_error_create_table_missing_columns, test_error_create_table_empty_columns,
test_error_special_characters_in_unquoted_identifier, and
test_error_reserved_keyword_without_quotes contain tautological assertions that
always pass; update each test to either (A) pin the current parser behavior by
replacing the tautology with a concrete assertion (e.g., assert!(result.is_ok())
or assert!(result.is_err()) depending on observed behavior) and add a short
comment documenting the dialect/expected outcome, or (B) mark the test with
#[ignore = "dialect-dependent: tracking issue #<number>"] and add an issue
reference; locate these tests in parser.rs and change the assertions or add the
#[ignore] attribute accordingly so the tests become meaningful.


#[test]
fn test_error_create_table_empty_columns() {
let sql = "CREATE TABLE users ()";
let result = Parser::parse(sql);
// Parser might accept empty column list
if result.is_ok() {
assert!(true);
} else {
assert!(result.is_err());
}
}

#[test]
fn test_error_create_table_invalid_data_type() {
let sql = "CREATE TABLE users (id INVALID_TYPE)";
let result = Parser::parse(sql);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Unsupported data type"));
}

#[test]
fn test_error_create_table_missing_column_type() {
let sql = "CREATE TABLE users (id)";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_insert_missing_table() {
let sql = "INSERT INTO";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_insert_missing_values() {
let sql = "INSERT INTO users (id, name)";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_insert_empty_values() {
let sql = "INSERT INTO users (id, name) VALUES";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_insert_mismatched_parentheses() {
let sql = "INSERT INTO users (id, name VALUES (1, 'Alice')";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_insert_invalid_value_format() {
let sql = "INSERT INTO users (id) VALUES (SELECT * FROM other)";
let result = Parser::parse(sql);
// The error might be different than expected
assert!(result.is_err());
// Don't check specific error message as it varies
}

#[test]
fn test_error_select_missing_from() {
let sql = "SELECT id, name";
let result = Parser::parse(sql);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("No table specified"));
}

#[test]
fn test_error_select_missing_table_name() {
let sql = "SELECT * FROM";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_select_invalid_projection() {
let sql = "SELECT FROM users";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_select_unsupported_expression() {
let sql = "SELECT COUNT(*) FROM users";
let result = Parser::parse(sql);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Unsupported select"));
}

#[test]
fn test_error_select_join_not_supported() {
let sql = "SELECT * FROM users JOIN orders ON users.id = orders.user_id";
let result = Parser::parse(sql);
// This might parse but should fail during execution or return unsupported error
// Depending on implementation, adjust assertion
if result.is_err() {
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Unsupported") || err_msg.contains("table reference"));
}
}
Comment on lines +660 to +670

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

test_error_select_join_not_supported silently passes when JOIN succeeds.

If result.is_ok(), no assertion runs and the test passes — you'd never know the parser started accepting JOINs. Pin the behavior or add an Ok branch assertion.

Suggested fix
 fn test_error_select_join_not_supported() {
     let sql = "SELECT * FROM users JOIN orders ON users.id = orders.user_id";
     let result = Parser::parse(sql);
-    // This might parse but should fail during execution or return unsupported error
-    // Depending on implementation, adjust assertion
-    if result.is_err() {
-        let err_msg = result.unwrap_err().to_string();
-        assert!(err_msg.contains("Unsupported") || err_msg.contains("table reference"));
-    }
+    // JOINs are not supported by the parser; pin this expectation.
+    let err_msg = result.expect_err("JOIN should not be supported").to_string();
+    assert!(
+        err_msg.contains("Unsupported") || err_msg.contains("table reference"),
+        "Unexpected error message: {err_msg}"
+    );
 }
🤖 Prompt for AI Agents
In `@nexum_core/src/sql/parser.rs` around lines 660 - 670, The test
test_error_select_join_not_supported currently does nothing when Parser::parse
returns Ok, letting joins be silently accepted; change it to explicitly assert
the expected failure: assert that result.is_err() (or match result { Err(e) =>
assert!(e.to_string().contains("Unsupported") || e.to_string().contains("table
reference")), Ok(_) => panic!("Parser unexpectedly accepted JOIN in
test_error_select_join_not_supported") }) so the test fails if Parser::parse
succeeds; update the test to use the Parser::parse result variable and the
explicit Ok branch assertion to pin the expected behavior.


#[test]
fn test_error_update_missing_table() {
let sql = "UPDATE SET name = 'Bob'";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_update_missing_set() {
let sql = "UPDATE users name = 'Bob'";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_update_empty_assignments() {
let sql = "UPDATE users SET";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_delete_missing_from() {
let sql = "DELETE users";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_delete_missing_table() {
let sql = "DELETE FROM";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_invalid_sql_keyword() {
let sql = "INVALID SQL STATEMENT";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_incomplete_statement() {
let sql = "CREATE";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_malformed_where_clause() {
let sql = "SELECT * FROM users WHERE";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_invalid_order_by_expression() {
let sql = "SELECT * FROM users ORDER BY COUNT(*)";
let result = Parser::parse(sql);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Unsupported ORDER BY expression"));
}

#[test]
fn test_error_invalid_limit_value() {
let sql = "SELECT * FROM users LIMIT abc";
let result = Parser::parse(sql);
// Parser might accept this but return None for limit
// This is actually valid SQL that gets parsed, then fails at execution
if result.is_ok() {
// Check that limit is None since 'abc' can't be parsed as number
match result.unwrap() {
Statement::Select { limit, .. } => {
assert!(limit.is_none());
}
_ => panic!("Expected Select statement"),
}
} else {
assert!(result.is_err());
}
}
#[test]
fn test_error_negative_limit() {
let sql = "SELECT * FROM users LIMIT -5";
let result = Parser::parse(sql);
// Parser might accept it, but it should be handled
if result.is_err() {
assert!(true);
}
}
Comment on lines +755 to +763

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

test_error_negative_limit silently passes on success — no meaningful assertion.

When result.is_err() is false, the test body does nothing and passes silently. This means a LIMIT -5 that is accepted without error goes completely unnoticed. Either assert the expected behavior on the Ok path (e.g., verify the limit field value) or assert that it must error.

Suggested fix
 fn test_error_negative_limit() {
     let sql = "SELECT * FROM users LIMIT -5";
     let result = Parser::parse(sql);
-    // Parser might accept it, but it should be handled
-    if result.is_err() {
-        assert!(true);
-    }
+    // Pin the expected behavior so changes are caught.
+    assert!(result.is_err(), "Expected error for negative LIMIT");
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[test]
fn test_error_negative_limit() {
let sql = "SELECT * FROM users LIMIT -5";
let result = Parser::parse(sql);
// Parser might accept it, but it should be handled
if result.is_err() {
assert!(true);
}
}
#[test]
fn test_error_negative_limit() {
let sql = "SELECT * FROM users LIMIT -5";
let result = Parser::parse(sql);
// Pin the expected behavior so changes are caught.
assert!(result.is_err(), "Expected error for negative LIMIT");
}
🤖 Prompt for AI Agents
In `@nexum_core/src/sql/parser.rs` around lines 755 - 763, The test
test_error_negative_limit currently does nothing when Parser::parse returns Ok,
so update it to assert the expected behavior: call Parser::parse(sql) into
result and then either assert that result.is_err() (e.g.,
assert!(result.is_err(), "expected parse error for LIMIT -5, got {:?}", result))
or unwrap the Ok and explicitly assert the parsed AST's limit field value
(inspect the returned query's limit field) to ensure LIMIT -5 is handled as
expected; reference Parser::parse, the result variable, and the parsed query's
limit field when making the change.


#[test]
fn test_error_describe_missing_table() {
let sql = "DESCRIBE";
let result = Parser::parse(sql);
// Should return error since incomplete
if result.is_err() {
assert!(true);
} else {
// If it doesn't error, it's a bug - management parser expects 2 tokens
panic!("Should have failed on incomplete DESCRIBE");
}
}

#[test]
fn test_error_drop_table_missing_name() {
let sql = "DROP TABLE";
let result = Parser::parse(sql);
// Management parser expects 3 tokens for basic DROP TABLE
if result.is_err() {
assert!(true);
} else {
panic!("Should have failed on incomplete DROP TABLE");
}
}

#[test]
fn test_error_drop_table_if_exists_missing_name() {
let sql = "DROP TABLE IF EXISTS";
let result = Parser::parse(sql);
// Management parser expects 5 tokens
if result.is_err() {
assert!(true);
} else {
panic!("Should have failed on incomplete DROP TABLE IF EXISTS");
}
}
Comment on lines +765 to +800

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Management statement "incomplete input" tests look correct in logic but are fragile.

test_error_describe_missing_table, test_error_drop_table_missing_name, and test_error_drop_table_if_exists_missing_name rely on the fact that parse_management_statement won't match (wrong token count) and then sqlparser will also error. The panic! on the Ok branch is good — these are not tautological. However, consider adding a comment noting the two-stage fallthrough so future maintainers understand why these error out.

🤖 Prompt for AI Agents
In `@nexum_core/src/sql/parser.rs` around lines 765 - 800, The three tests
test_error_describe_missing_table, test_error_drop_table_missing_name, and
test_error_drop_table_if_exists_missing_name are fragile because they rely on a
two-stage fallthrough (parse_management_statement fails due to token count, then
sqlparser errors); update each test to include a short inline comment explaining
this two-stage matching behavior and why the panic! on Ok is required so future
maintainers understand the dependency on both management-parser token-count
checks and sqlparser's erroring behavior.


#[test]
fn test_error_unclosed_string() {
let sql = "INSERT INTO users (name) VALUES ('Alice)";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_mixed_quotes() {
let sql = "INSERT INTO users (name) VALUES ('Alice\")";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_unbalanced_parentheses() {
let sql = "CREATE TABLE users (id INTEGER, name TEXT";
let result = Parser::parse(sql);
assert!(result.is_err());
}

#[test]
fn test_error_special_characters_in_unquoted_identifier() {
let sql = "CREATE TABLE user@table (id INTEGER)";
let result = Parser::parse(sql);
// sqlparser might accept @ in identifiers depending on dialect
// This is dialect-specific behavior
if result.is_ok() {
// Some SQL dialects allow @ in identifiers
assert!(true);
} else {
assert!(result.is_err());
}
}

#[test]
fn test_error_reserved_keyword_without_quotes() {
// Some parsers might handle this, but good to test
let sql = "CREATE TABLE select (id INTEGER)";
let result = Parser::parse(sql);
// Depending on parser, this might work or fail
// If it fails, good; if not, that's also acceptable
if result.is_err() {
assert!(true);
}
}
}
Loading