| Crates.io | sqlexpr-rust |
| lib.rs | sqlexpr-rust |
| version | 1.0.1 |
| created_at | 2025-12-09 17:32:49.749041+00 |
| updated_at | 2025-12-10 17:17:32.855962+00 |
| description | A SQL expression parser and evaluator in Rust |
| homepage | |
| repository | https://github.com/richcar58/sqlexpr-rust |
| max_upload_size | |
| id | 1975813 |
| size | 121,868 |
A Rust library for parsing and evaluating SQL-like boolean expressions with full support for comparisons, arithmetic, pattern matching, and logical operators. Also see CLAUDE.md and SqlExprParser-EBNF-Final.ebnf for related documentation.
There are two Java SqlExpr parser/evaluator implementations that accept basically the same language as this Rust parser. See sqlexpr-javacc for a parser built using the JavaCC parser generator; see sqlexpr-congocc for a parser built using the CongoCC parser generator.
AND, OR, NOT>, >=, <, <=, =, <>, !=LIKE, NOT LIKE (with %, _ wildcards and ESCAPE)BETWEEN, NOT BETWEENIN, NOT INIS NULL, IS NOT NULL+, -, *, /, % (modulo)+, -42), hexadecimal (0xFF), octal (0755)3.14), scientific notation (1.5e-10)'hello\'world')TRUE, FALSENULL--) and block comments (/* */)AND, and, And all workThis parser implements a clean separation between boolean and value expressions at the grammar level, ensuring most type safety during parsing rather than evaluation.
7/2 = 3.5)IS NULLAND and OR operators evaluate efficientlyAdd to your Cargo.toml:
[dependencies]
sqlexpr-rust = "0.1.0"
use sqlexpr_rust::parse;
fn main() {
// Parse a simple comparison
let ast = parse("age >= 18 AND status = 'active'").unwrap();
println!("Parsed: {}", ast);
// Parse complex expressions
let expr = parse("(price * quantity) > 1000 AND customer_type IN ('gold', 'platinum')").unwrap();
// Parse with LIKE pattern matching
let pattern = parse("email LIKE '%@example.com' AND NOT deleted").unwrap();
}
use std::collections::HashMap;
use sqlexpr_rust::{evaluate, RuntimeValue};
fn main() {
// Create variable bindings
let mut bindings = HashMap::new();
bindings.insert("age".to_string(), RuntimeValue::Integer(25));
bindings.insert("status".to_string(), RuntimeValue::String("active".to_string()));
bindings.insert("premium".to_string(), RuntimeValue::Boolean(true));
// Evaluate the expression
let result = evaluate("age >= 18 AND status = 'active' AND premium", &bindings).unwrap();
assert_eq!(result, true);
// Arithmetic evaluation
bindings.insert("price".to_string(), RuntimeValue::Float(99.99));
bindings.insert("quantity".to_string(), RuntimeValue::Integer(10));
let result = evaluate("(price * quantity) > 500", &bindings).unwrap();
assert_eq!(result, true);
// Pattern matching
bindings.insert("email".to_string(), RuntimeValue::String("user@example.com".to_string()));
let result = evaluate("email LIKE '%@example.com'", &bindings).unwrap();
assert_eq!(result, true);
}
use sqlexpr_rust::{evaluate, RuntimeValue, EvalError};
use std::collections::HashMap;
fn main() {
let mut bindings = HashMap::new();
bindings.insert("x".to_string(), RuntimeValue::String("hello".to_string()));
// Type error: string in arithmetic
let result = evaluate("(x + 10) > 0", &bindings);
match result {
Err(EvalError::TypeError { operation, expected, actual, context }) => {
println!("Type error in {}: expected {}, got {} ({})",
operation, expected, actual, context);
}
_ => {}
}
// Type error: incompatible IN list
bindings.insert("y".to_string(), RuntimeValue::Integer(42));
let result = evaluate("y IN ('a', 'b', 'c')", &bindings);
assert!(matches!(result, Err(EvalError::TypeError { .. })));
}
sqlexpr-rust/
├── src/
│ ├── lib.rs # Public API and re-exports
│ ├── lexer.rs # Tokenization
│ ├── parser.rs # Recursive descent parser
│ ├── ast.rs # Abstract Syntax Tree definitions
│ └── evaluator.rs # Expression evaluation engine
├── tests/
│ ├── parser_tests.rs # Parser test suite (155 tests)
│ ├── parser_type_checking_tests.rs # Parser type test suite (97 tests)
│ └── evaluator_tests.rs # Evaluator test suite (111 tests)
├── examples/
│ ├── showcase.rs # Feature demonstration
│ └── ... # Additional examples
├── docs/
│ ├── EVALUATION_DESIGN.md # Design alternatives
│ ├── EVALUATOR_IMPLEMENTATION_PLAN.md # Implementation roadmap
│ └── command_prompts.md # Development notes
├── SqlExprParser-EBNF-Final.ebnf # Formal grammar specification
├── Cargo.toml
├── CLAUDE.md
├── LICENSE
├── README.md
└── SqlExprParser-EBNF-Final.ebnf
src/lexer.rs)Tokenizes input strings into a stream of tokens. Handles:
src/parser.rs)Recursive descent parser implementing the EBNF grammar. Features:
src/ast.rs)Hierarchical AST structure:
BooleanExpr: AND, OR, NOT, literals, variables, relational expressionsRelationalExpr: Comparisons, LIKE, BETWEEN, IN, IS NULLValueExpr: Arithmetic operations, literals, variablessrc/evaluator.rs)Evaluation engine with:
The grammar enforces type safety at parse time:
BooleanExpression = BooleanOrExpression ;
BooleanOrExpression = BooleanAndExpression { "OR" BooleanAndExpression } ;
BooleanAndExpression = BooleanTerm { "AND" BooleanTerm } ;
BooleanTerm = "NOT" BooleanTerm
| "(" BooleanExpression ")"
| BooleanLiteral
| Variable
| RelationalExpression ;
RelationalExpression = ValueExpression ComparisonOp ValueExpression
| ValueExpression "LIKE" Pattern
| ValueExpression "BETWEEN" ValueExpression "AND" ValueExpression
| ValueExpression "IN" "(" ValueList ")"
| ValueExpression "IS" ["NOT"] "NULL" ;
ValueExpression = AdditiveExpression ;
AdditiveExpression = MultiplicativeExpression { ("+" | "-") MultiplicativeExpression } ;
MultiplicativeExpression = UnaryExpression { ("*" | "/" | "%") UnaryExpression } ;
UnaryExpression = ["+" | "-"] PrimaryExpression ;
PrimaryExpression = Literal | Variable | "(" ValueExpression ")" ;
See SqlExprParser-EBNF-Final.ebnf for the complete formal grammar.
Integer(i64): 64-bit signed integersFloat(f64): 64-bit floating pointString(String): UTF-8 stringsBoolean(bool): true/falseNull: SQL NULL value7 / 2 = 3.5)IS NULLTRUE AND FALSE -- false
age >= 18 AND status = 'active' -- depends on bindings
(x > 10 OR y > 10) AND NOT deleted -- compound condition
(price * quantity) > 1000 -- arithmetic in comparison
(revenue - cost) / revenue >= 0.2 -- percentage calculation
amount % 100 = 0 -- check divisibility
email LIKE '%@example.com' -- domain match
name LIKE 'J%n' -- starts with J, ends with n
code LIKE 'A___B' -- A + 3 chars + B
text LIKE '50\%' ESCAPE '\' -- literal % character
age BETWEEN 18 AND 65 -- inclusive range
status IN ('active', 'pending') -- membership test
score NOT BETWEEN 0 AND 59 -- exclusion
role NOT IN ('admin', 'moderator') -- negative membership
middle_name IS NULL -- null check
email IS NOT NULL -- non-null check
-- x + NULL would raise NullInOperation error
-- x > NULL would raise NullInOperation error
# Run the feature showcase
cargo run --example showcase
# Enable pretty-printing of AST
SQLEXPR_PRETTY=true cargo run --example showcase
# Run all tests
cargo test
# Run specific test suite
cargo test --test parser_tests
cargo test --test evaluator_tests
# Build documentation
cargo doc --open
The project includes comprehensive test coverage:
tests/parser_tests.rs): 155 tests covering all grammar featurestests/evaluator_tests.rs): 111 tests covering all operationssrc/lib.rs, modules): 13 embedded testsTotal: 280 tests
Run tests with:
cargo test # All tests
cargo test --verbose # With output
cargo test <pattern> # Specific tests
Tell the parser to pretty print ASTs of parsed expressions using the SQLEXPR_PRETTY environment variable. For example, the following commands can be used to dump the ASTs generated by the parser_tests and evaluator_tests programs. These commands should be run from the top-level project directory. For easy reference, the output files from these test programs are shipped with the source code.
SQLEXPR_PRETTY=true cargo test --test parser_tests -- --nocapture --test-threads=1 > examples/output/parser_tests.out
SQLEXPR_PRETTY=true cargo test --test evaluator_tests -- --nocapture --test-threads=1 > examples/output/evaluator_tests.out
The library provides detailed error messages:
Parse error: Unexpected token ')' near position 15 in:
(x > 5 AND y < )
Type error in addition: expected numeric types, got string and integer
(context: arithmetic operation)
NULL value in GreaterThan operation (context: cannot compare NULL).
NULL is only allowed in IS NULL/IS NOT NULL
Division by zero in expression: x / 0 > 5
regex crateSUM, COUNT, etc.See LICENSE file for details.
Contributions are welcome! Please ensure:
cargo testcargo fmtcargo clippySqlExprParser-EBNF-Final.ebnfcargo doc --opendocs/ directoryexamples/ directoryAnthopic's Claude Sonnet 4.5 was used to generate most of the code and documentation in this project.