queryx

JSON-queryable filter DSL with foxql SQL builder bridge

query
filter
json
dsl
moon add jaredzhou/queryx@0.2.1
Download zip
Author
Version
0.2.1
License
Apache-2.0
Last updated
26 days ago
Downloads
6

Dependencies

README

#QueryX

JSON-queryable filter DSL with to/from JSON, eval, and foxql bridge.

#Setup

// moon.mod
import {
"jaredzhou/queryx@0.1.0",
}

// moon.pkg
import {
"jaredzhou/queryx",
}

#Quick start

///|
let input : Json = {
"filter": { "age": { "gt": 18.0, "lt": 65.0 }, "name": { "contains": "tom" } },
"order_by": [{ "age": "desc" }],
"page": { "after": "", "size": 10.0 },
}

///|
let query : Query = @json.from_json(input)

// JSON round-trip

///|
let json = query.to_json()

#Expr — filter expression tree

Supports Compare, Between, StrMatch (contains/starts_with/ends_with), And, Or, Not, and Empty.

let expr = Expr::And([
Expr::Compare("age", CmpOp::Gt, Value::Int(18L)),
Expr::StrMatch("name", StrKind::Contains, "tom"),
])

// Evaluate against field bindings
let values = Map([])
values.set("age", Value::Int(25L))
values.set("name", Value::String("tommy"))
assert_true(expr.eval(values)!)

#FoxQL bridge

Convert queryx Expr / Query into foxql SQL via FieldResolver.

#1. Implement FieldResolver for your table

///|
struct User {
table : Table
id : Column[Int]
name : Column[String]
age : Column[Int]
}

///|
impl FieldResolver for User with fn resolve_column(self, field) -> ColumnRef? {
match field {
"id" => Some(self.id.to_ref())
"name" => Some(self.name.to_ref())
"age" => Some(self.age.to_ref())
_ => None
}
}

///|
impl FieldResolver for User with fn table_name(self) -> String {
"users"
}

#2. Expr → foxql Expr

///|
let expr = Expr::Compare("age", CmpOp::Gt, Value::Int(18L))

///|
let foxql_expr = to_foxql_expr(expr, user)
// → Expr::Compare(users.age, Gt, Value::Int(18))

#3. Query → foxql SelectBuilder

let query : Query = @json.from_json(...)
let (sql, args) = to_select(query, user).to_sql()
// WHERE + ORDER BY + LIMIT all handled automatically

#4. Dynamic schema (SchemaTable)

let schema = Schema::load([...])
let users = schema.table("users")
// SchemaTable implements FieldResolver natively
let (sql, args) = to_select(query, users).to_sql()

#Package structure

queryx/ ├── queryx.mbt # Query, Order, Page types + JSON serde ├── expr.mbt # Expr, Value, CmpOp, StrKind + eval + JSON serde ├── foxql_bridge.mbt # FieldResolver trait, to_foxql_expr, to_select ├── foxql_bridge_test.mbt ├── queryx_test.mbt └── expr_test.mbt

#
ToValue

pub trait ToValue {
fn to_value(Self) -> Value
}

impl ToValue for Bool
impl ToValue for Int
impl ToValue for Int64
impl ToValue for Double
impl ToValue for String

#
QueryError

pub suberror QueryError {
EvalError(String)
} derive(ToJson)

#
QueryxError

pub suberror QueryxError {
Unsupported(String)
UnknownField(String)
EmptyAnd
EmptyOr
} derive(
Debug
)

#
CmpOp

pub(all) enum CmpOp {
Eq
Ne
Gt
Lt
Ge
Le
} derive(Eq,
Debug
)

#
Expr

pub(all) enum Expr {
Lit(Value)
Field(String)
Compare(String, CmpOp, Value)
Between(String, Value, Value)
StrMatch(String, StrKind, String)
And(Array[Expr])
Or(Array[Expr])
Not(Expr)
Empty
} derive(Eq,
Debug
)

impl ToJson for Expr

#
Expr::eval

fn Expr::eval(self : Expr, values : Map[String, Value]) -> Bool raise QueryError

Evaluate the expression against a concrete map of field → value bindings.

#
Order

pub(all) struct Order(String) derive(
Debug
)

#
Page

pub(all) struct Page {
after : String
size : Int
} derive(ToJson,
Debug
,
FromJson
)

#
Query

pub(all) struct Query {
filter : Expr
order_by : Order?
page : Page?
set : Json?
} derive(
Debug
)

#
StrKind

pub(all) enum StrKind {
Contains
StartsWith
EndsWith
} derive(Eq,
Debug
)

#
Value

pub(all) enum Value {
Null
Bool(Bool)
Int(Int64)
Float(Double)
String(String)
} derive(Eq,
Debug
)

impl ToValue for Value
impl ToJson for Value

#
to_foxql_expr

Convert a queryx Expr to a foxql Expr.

#
to_foxql_op

Convert a queryx CmpOp to a foxql Operator.

#
to_foxql_value

fn to_foxql_value(v : Value) ->
Value

Convert a queryx Value to a foxql Value.

#
to_select

Convert a queryx Query to a foxql SelectBuilder (WHERE, ORDER BY, LIMIT). The resolver provides both field→column mapping and the table name.