Skip to content
Draft
Show file tree
Hide file tree
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
119 changes: 119 additions & 0 deletions agents.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# CQL2-RS Project AI Agent Configuration

## Project Overview
This is cql2-rs, a Rust library with Python bindings for parsing, validating, and converting Common Query Language 2 (CQL2) expressions. The project supports text and JSON CQL2 formats and can convert to SQL for various database backends.

## Key Technologies
- **Primary Language**: Rust (edition 2021)
- **Python Bindings**: PyO3 and maturin
- **Parser**: Pest parser generator
- **SQL Generation**: sqlparser-rs with serde features
- **Geometry**: geo-types, geojson, geozero, wkt
- **Testing**: rstest, pytest
- **Documentation**: mkdocs
- **Package Management**: Cargo (Rust), uv (Python)
- **Linting**: rustfmt, ruff, clippy

## Project Structure
```
├── src/ # Core Rust library (CQL2 parsing, validation, SQL conversion)
├── python/ # Python bindings using PyO3
├── cli/ # Command-line interface
├── wasm/ # WebAssembly bindings
├── tests/ # Rust unit and integration tests
├── examples/ # Example CQL2 expressions and usage demos
├── docs/ # Documentation source
└── scripts/ # Development scripts (test, lint, build)
```

## Core Capabilities
- Parse CQL2 text and JSON expressions
- Validate CQL2 syntax and semantics
- Convert between CQL2 text, JSON, and SQL formats
- Support for spatial, temporal, and array operations
- Database-specific SQL generation (standard SQL, DuckDB)
- Direct AST access without string reparsing
- Python API with comprehensive type stubs

## Development Guidelines

### Code Quality
- Use `scripts/lint` for comprehensive linting (rustfmt, clippy, ruff)
- Use `scripts/test` for running all tests (Rust + Python)
- Maintain type safety and comprehensive error handling
- Follow Rust API guidelines and naming conventions
- Use descriptive error messages and documentation

### Testing
- Write unit tests for all core functionality
- Include integration tests for CQL2 -> SQL conversion
- Test Python bindings separately with pytest
- Use rstest for parameterized Rust tests
- Validate against OGC CQL2 specification examples

### Documentation
- Maintain rustdoc comments for all public APIs
- Keep Python type stubs (cql2.pyi) synchronized
- Update README examples when adding features
- Document breaking changes in CHANGELOG.md

### Dependencies
- Prefer well-maintained crates with active communities
- Use workspace dependencies for version consistency
- Enable only necessary features to minimize build time
- Keep Python dependencies minimal and compatible

## Common Tasks

### Adding New CQL2 Features
1. Update grammar in `src/cql2.pest`
2. Modify parser in `src/parser.rs`
3. Add SQL conversion logic in `src/sql.rs` or `src/duckdb.rs`
4. Update Python bindings in `python/src/lib.rs`
5. Add comprehensive tests and examples
6. Update type stubs and documentation

### SQL Backend Support
- Standard SQL: `src/sql.rs`
- DuckDB-specific: `src/duckdb.rs`
- Consider spatial function differences between backends
- Test array operations thoroughly

### Python Binding Changes
- Use PyO3 best practices for error handling
- Maintain compatibility with sqloxide and other AST tools
- Provide both string and direct AST access methods
- Update type stubs in `cql2.pyi`

## Performance Considerations
- Parser uses zero-copy where possible
- AST manipulation avoids string reparsing
- SQL generation is lazy and cached
- Python bindings minimize copies between Rust/Python

## Error Handling
- Use thiserror for Rust error definitions
- Provide context-rich error messages
- Map Rust errors to appropriate Python exceptions
- Include position information for parse errors

## Release Process
- Follow semantic versioning
- Update CHANGELOG.md with all changes
- Coordinate Rust crate and Python package releases
- Test across supported Python versions
- Update documentation and examples

## External Integration
- Compatible with sqloxide for AST manipulation
- Supports geospatial libraries (geo, geojson)
- Works with popular SQL databases
- Can be embedded in larger geospatial applications

## Troubleshooting
- Use `RUST_LOG=debug` for detailed logging
- Check `target/debug/` for intermediate build artifacts
- Python binding issues often relate to maturin configuration
- Spatial operations may need specific geometry formats

This project bridges the gap between CQL2 specifications and practical SQL usage, making it easier to work with geospatial query languages in both Rust and Python ecosystems.
36 changes: 31 additions & 5 deletions src/duckdb.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
use crate::sql::func;
use crate::Error;
use crate::Expr;
use crate::ToSqlAst;
use crate::{ToSqlAst, ToSqlOptions};
use sqlparser::ast::visit_expressions_mut;
use sqlparser::ast::Expr as SqlExpr;
use std::ops::ControlFlow;

/// Traits for generating SQL for DuckDB with Spatial Extension
pub trait ToDuckSQL {
/// Convert Expression to SQL for DuckDB with Spatial Extension
fn to_ducksql(&self) -> Result<String, Error>;
fn to_ducksql(&self) -> Result<String, Error> {
self.to_ducksql_with_options(ToSqlOptions::default())
}

/// Convert Expression to DuckDB SQL using custom SQL generation options.
fn to_ducksql_with_options(&self, options: ToSqlOptions<'_>) -> Result<String, Error>;
}

impl ToDuckSQL for Expr {
Expand Down Expand Up @@ -46,8 +51,8 @@ impl ToDuckSQL for Expr {
/// let expr: Expr = "t_overlaps(interval(a,b),interval('2020-01-01T00:00:00Z','2020-02-01T00:00:00Z'))".parse().unwrap();
/// assert_eq!(expr.to_ducksql().unwrap(), "(a < CAST('2020-02-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE) AND CAST('2020-01-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE) < b AND b < CAST('2020-02-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE))");
/// ```
fn to_ducksql(&self) -> Result<String, Error> {
let mut ast = self.to_sql_ast()?;
fn to_ducksql_with_options(&self, options: ToSqlOptions<'_>) -> Result<String, Error> {
let mut ast = self.to_sql_ast_with_options(options)?;
let _ = visit_expressions_mut(&mut ast, |expr| {
if let SqlExpr::BinaryOp { op, right, left } = expr {
match *op {
Expand All @@ -73,7 +78,7 @@ impl ToDuckSQL for Expr {
#[cfg(test)]
mod tests {
use super::ToDuckSQL;
use crate::Expr;
use crate::{Expr, NameKind, ToSqlOptions};

#[test]
fn test_to_ducksql() {
Expand All @@ -86,4 +91,25 @@ mod tests {
let expr: Expr = "a_contains(foo, bar)".parse().unwrap();
assert_eq!(expr.to_ducksql().unwrap(), "list_has_all(foo, bar)");
}

#[test]
fn test_ducksql_with_options() {
let expr: Expr = "collection = 'landsat' AND a_contains(tags, required)"
.parse()
.unwrap();
let resolver = |name: &str, kind: NameKind| match (kind, name) {
(NameKind::Property, "collection") => Some("payload ->> 'collection'".to_string()),
(NameKind::Property, "tags") => Some("payload -> 'tags'".to_string()),
_ => None,
};

let sql = expr
.to_ducksql_with_options(ToSqlOptions::with_callback(&resolver))
.unwrap();

assert_eq!(
sql,
"payload ->> 'collection' = 'landsat' AND list_has_all(payload -> 'tags', required)"
);
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub use error::Error;
pub use expr::*;
pub use geometry::{spatial_op, Geometry};
pub use parser::parse_text;
pub use sql::ToSqlAst;
pub use sql::{NameKind, ToSqlAst, ToSqlOptions};
use std::{fs, path::Path};
pub use temporal::{temporal_op, DateRange};
pub use validator::Validator;
Expand Down
Loading
Loading