E Expressions & Safe Relation Evaluation
In the TeaQL Rust ecosystem, navigating entity graphs is heavily optimized for zero-cost abstractions, memory safety, and explicit error handling.
A persistent challenge in ORM layers is dealing with the "N+1 problem" or accidentally accessing properties that were not explicitly fetched from the database. In traditional architectures, this often results in a hidden lazy-loading database query or a fatal NullPointerException.
TeaQL takes a pure Rust approach governed by a core philosophy: "流式表达是人类的心智糖果,而严苛的宕机才是 Rust 的底线。" (Fluent expressions are mental candy for humans, while strict panics are the bottom line of Rust.)
The E Wrapper: Zero-Cost AST Traversal
Previously, complex object-oriented AST wrappers in Rust required expensive .clone() operations. With TeaQL's new ValueExpression<'a, T> architecture, you can use the E wrapper to traverse relationships smoothly with zero-cost borrow propagation.
Instead of directly accessing entity.company or manually matching EvalResult, you wrap your entity in E and chain the accessors.
// 1. Fluent access with default value (no allocations unless evaluated)
let company_name = E::merchant(&merchant)
.get_platform()
.get_company()
.get_name()
.or_else("Unknown".to_string());
// 2. Lazy evaluated default
let company_name = E::merchant(&merchant)
.get_platform()
.get_company()
.get_name()
.or_else_with(|| fetch_default_name());
// 3. Strict unwrap (Panics if legitimately null in DB)
let company_name = E::merchant(&merchant)
.get_platform()
.get_company()
.get_name()
.unwrap();
// 4. Safe Option<T> handling
if let Some(company_name) = E::merchant(&merchant).get_platform().get_company().get_name().eval() {
println!("Company is {}", company_name);
}
Strict Panic Bottom Line
What happens if you forgot to .with_platform() in your database query?
Unlike systems that silently return Null or swallow the error, the E wrapper acts as a strict guard. In ANY ending operation (eval, unwrap, or_else), if the traversal encounters a NotLoaded state along the chain, the program will instantly panic.
| Data State | Root Cause | .eval() | .unwrap() | .or_else("x") |
|---|---|---|---|---|
| Value | Query loaded it, DB has a value | Returns Some(value) | Returns value | Returns value |
| Null | Query loaded it, DB is truly Null | Returns None | 💥 Panic | Returns "x" |
| NotLoaded | Logic Bug! Forgot with_xxx | 💥 Panic (Structured Diagnostic) | 💥 Panic (Structured Diagnostic) | 💥 Panic (Structured Diagnostic) |
Structured Logic Bug Panic (AI Auto-Healing)
This strict, transparent exception model naturally aligns with AI-assisted coding (Agentic Coding).
When an LLM agent writes a script using TeaQL APIs and forgets to include a .select_relation() in its query, the script will halt immediately with a highly structured diagnostic panic pointing directly to the exact path.
Because this error is explicitly structured and highly contextual, the AI agent doesn't have to guess what went wrong. It reads the error message and instantly heals its own code by amending the query.
Here is an example of the structured panic output:
================================================================================
☕ TeaQL Logic Bug Detected ☕
Severity: FATAL - System halted to prevent undefined business logic.
[Human Message]
You attempted to access a relation that was not loaded in the initial query.
Root Entity: Merchant(id=42)
Attempted Path: platform.company.name
[Diagnostic Context]
original_expr_with_broken_point: E::merchant(id=42).get_platform().get_company()<broken>.get_name()
missing_preload: select_company()
suggested_fix: .select_platform_with(Q::platforms().select_company())
================================================================================
The Magic of <broken>
The original_expr_with_broken_point explicitly visualizes exactly where the chain broke with a <broken> marker. The payload also provides the missing_preload and suggested_fix that can be directly pasted into the query builder. AI agents easily parse this and rewrite their queries in seconds.
Manually Creating Entities
When you manually instantiate an entity for testing or business logic (instead of fetching it from the database), its internal __load_state is automatically initialized as FullyLoaded.
Note that all entity creations require an explicit audit .purpose().
let mut task = Q::tasks().purpose("Create mock task for testing").new_entity(&ctx);
// Manually created entities are considered FullyLoaded by default.
// Unassigned fields will evaluate to None, not a NotLoaded panic.
assert!(E::task(&task).get_status().eval().is_none());
This ensures that your mock tests and data-seeding logic never falsely trip the NotLoaded traps.