Repository navigation
Releases: OxideR-System/OxideR-Query
Release list
v0.2.2
OxideR-Query v0.2.2
An ergonomics fix. Purely additive: nothing is removed or renamed, and no behaviour changes.
The type-state markers are now in the prelude
Writing a helper that returns a statement needs the marker nameable:
fn new_user(name: &str) -> Insert<User, OneRow> {
User::insert().set(User::name, name.to_string())
}Before this release OneRow was reachable only as oxider_query::builder::OneRow, so the first thing you hit after moving a query out of the expression that built it was an import error for a type you had never had to write. The same held for Locked, Unlocked, NoRows, FromQuery, NoFrame and Framed.
All seven are now exported from the crate root and the prelude:
| Marker | Says |
|---|---|
Unlocked / Locked |
whether a Select carries a row-locking clause |
NoRows / OneRow / FromQuery |
where an Insert gets its rows |
NoFrame / Framed |
whether a Window has a frame |
These are never written while building a query, only while naming one, and naming one is exactly what a function signature does. That is why they belong beside the builders rather than a module deeper.
A doc test now writes all three kinds of signature with nothing imported but the prelude, which is the case that was broken:
use oxider_query::prelude::*;
fn new_user(name: &str) -> Insert<User, OneRow> { ... }
fn next_job() -> Select<Only<Job>, Nil, Locked> { ... }
fn trailing_total() -> Window<Nil, Framed> { ... }Chapter 15 of the guide gained a table of the markers.
How it was found
By writing an example project outside the workspace, against the published tag. From there the facade's exports are the whole API rather than an implementation detail, and the gap showed up immediately. The in-tree tests never saw it, because they can reach into any module they like.
Still true
The builder targets PostgreSQL, MySQL and SQLite. Execution covers PostgreSQL and SQLite; schema codegen is SQLite only. On MySQL you render here and bind with your own driver.
Pre-1.0: the API tracks latest stable Rust and will change.
Upgrading
Nothing to change. If you were importing a marker from oxider_query::builder, that path still works; the prelude import is now available as well.
v0.2.1
OxideR-Query v0.2.1
A hygiene release. No change to library behaviour: everything in v0.2.0 behaves identically. What changed is the lock file and the test suite, and the reason for cutting a new tag is that v0.2.0 went out on a commit whose CI then failed.
Upgrade if you consume the repository by tag. Nothing in your own code has to change.
Why v0.2.0 was tagged red
make release runs the full local gate before tagging, and that gate passed. It is not CI, and the difference is exactly where the two problems hid:
- The PostgreSQL end-to-end suite skips itself when
OXIDER_POSTGRES_URLis unset, so locally it ran nothing. - Nothing local ran
cargo audit.
CI has a Postgres service and an audit job, so it found both within a minute of the tag being pushed.
What is fixed
The PostgreSQL suite raced itself. Every test drops and recreates the same tables on one shared server, so running them concurrently means one clearing another's rows mid-assertion, or both issuing the DDL and one losing:
ERROR 42P07: relation "ox_users" already exists
It passed locally only because the make target pinned it to a single thread, which hid the defect rather than fixing it. The suite serialises itself with a mutex now, and the flag is gone, so a local run and CI do the same thing. The SQLite suite never had this problem because each of its tests gets a private in-memory database.
cargo audit flagged RUSTSEC-2023-0071, the rsa timing sidechannel, against a crate nothing here compiles. sqlx/chrono fans the feature out to every backend, so enabling it pulled sqlx-mysql and its dependencies into Cargo.lock for a driver this workspace never builds. Enabling chrono on the Postgres driver directly keeps the lock to what is actually used, and takes the advisory with it. No audit.toml ignore was added; the dependency is simply gone.
make release now runs an advisory check before tagging, and says so plainly when cargo-audit is missing rather than skipping in silence. make check still does not require it, since not every contributor will have it installed.
What is still true from v0.2.0
The builder targets PostgreSQL, MySQL and SQLite. Execution covers PostgreSQL and SQLite; schema codegen is SQLite only. On MySQL you render here and bind with your own driver.
Pre-1.0: the API tracks latest stable Rust and will change.
Known gaps
Unchanged from v0.2.0: the MySQL execution backend, PostgreSQL and MySQL codegen backends, #[derive(Projection)], a GroupBy transformer, the renderer's per-Value clone, and the recursive drop of deeply nested subqueries.
v0.2.0
OxideR-Query v0.2.0
PostgreSQL can now execute, not only render.
What changed
PostgresDb, behind the postgres feature. The builder always targeted three dialects while only SQLite could run a statement, so a Postgres user rendered here and then bound the (sql, params) pair themselves. That gap is closed:
oxider-query-exec = { git = "...", tag = "v0.2.0", features = ["postgres"] }let db = PostgresDb::connect("postgres://...").await?;
let adults: Vec<User> = db
.fetch_all(User::query().filter(User::age.ge(18)))
.await?;Every method is the same as SQLite's. Pointing at the other database is a change of the handle's type and nothing else, which is what the Backend trait existed to make true.
A temporal-encoding defect, found by porting. Value stores dates, times and timestamps as text, because the AST must not depend on a date library. SQLite accepts that, since it types a column by the value it is handed. PostgreSQL carries a type OID per bound parameter and refuses:
ERROR 42804: column "on_day" is of type date but expression is of type text
The Postgres backend parses temporals back into chrono types before binding. The formats were private to the writing side; they are public as oxider_query_core::formats now, so writing and reading share one definition instead of two that can drift apart.
DATE_TIME_UTC changed shape. It wrote a literal +00:00, which formats correctly but cannot be parsed back, because chrono will not read an offset that is not a format specifier. It is %:z now: identical output for a UTC instant, and parseable. This matters most on a TIMESTAMPTZ column, where text is rejected outright but a naive value would have been accepted and silently reinterpreted in the session's own time zone.
Still true from v0.1.0
The builder targets PostgreSQL, MySQL and SQLite. Execution covers PostgreSQL and SQLite; schema codegen is SQLite only. On MySQL you render here and bind with your own driver.
Pre-1.0: the API tracks latest stable Rust and will change.
Testing
The Postgres end-to-end suite skips itself unless OXIDER_POSTGRES_URL is set, so a plain cargo test still needs no server. CI runs a Postgres service so the suite is not merely skipped everywhere. Locally:
make pg-up # a throwaway PostgreSQL in Docker
make test-pg
make pg-downIt covers what only a real strict server can prove: temporals typed as the columns actually are, an instant surviving a session time-zone change, DISTINCT ON and a native aggregate FILTER on an engine that has them, named parameters, and a transaction rolling back with the caller's own error intact.
Known gaps
- The MySQL execution backend. With two
Backendimplementations proven and MySQL's lenient typing, it should be close to the SQLite shape. - PostgreSQL and MySQL codegen backends.
#[derive(Projection)], and a GroupBy transformer.- The renderer clones each
Valueinto the parameter list. - A tree thousands of subqueries deep can still overflow when dropped. Rendering it is refused either way.
Upgrading from v0.1.0
Nothing to change unless you depended on the exact text of a DateTime<Utc> parameter, which now carries +00:00 from a format specifier rather than a literal. The rendered string is the same.
v0.1.0
OxideR-Query v0.1.0
First release. A type-safe, multi-dialect SQL query builder for Rust, inspired by Java's QueryDSL but pushing the type-checking further than a JVM can.
Read this first
The builder targets PostgreSQL, MySQL and SQLite. The optional execution layer and schema codegen support SQLite only.
On PostgreSQL and MySQL you build and render here, then bind the (sql, params) pair with your own driver. You do not get the Db handle yet. If that is a blocker, wait for a later release.
Pre-1.0: the API tracks latest stable Rust and will change.
What the compiler checks
Two things QueryDSL cannot check on the JVM:
- Operand types.
User::name.eq(123)does not compile, becausei64is not an expression of typeString. - Table scope.
User::query().filter(Department::name.eq("AI"))does not compile, because the query never joinedDepartment.
Correlated subqueries track the outer entities they are free in separately from their own scope, so using one where the outer table is absent is also a compile error.
Every advertised guarantee is pinned by a compile_fail doc test. Chapter 14 of the guide is equally explicit about what is not checked: nullability stays out of the type, col and raw are unchecked by design, and SQL's own rules (GROUP BY completeness, aggregates in WHERE) are left to the database.
What is in it
- SELECT, complete: projection, every join kind, aliasing, derived tables,
DISTINCTandDISTINCT ON, null ordering (native or emulated), pagination, row locking with wait policies, set operations, CTEs including recursive ones, window functions with frames, correlated and scalar subqueries. - DML, complete: multi-row insert, insert-select, upsert,
RETURNING,UPDATE ... FROM,DELETE ... USING. - Roughly 200 operators rendered for all three dialects from a template table, with parenthesisation decided once from a central precedence ladder.
- Dialect refusals. A construct the engine cannot run is an
Errfromto_sqlrather than plausible-but-wrong SQL that fails at the database. - Execution over sqlx, and codegen from an existing schema. SQLite only, as above.
Security posture
The crate went through a security, transaction and memory audit before this release, and everything it found is fixed. The trust boundary is now written down in chapter 16 of the guide. In short:
- Values always bind. A payload in a filter reaches the engine as a parameter, never as SQL text.
- Identifiers are
&'static str, so a string an application assembled at runtime does not compile in an identifier position. LIKEoperands are wildcard-escaped with an explicitESCAPE '!', unconditionally, not only for constants.- Codegen escapes every database name written into generated Rust and binds the table name into its introspection query, so a hostile schema is a naming problem rather than a code-execution one.
- Deep expression trees are refused at
MAX_DEPTHrather than overflowing the stack, andAND/ORchains flatten so ordinary dynamic filters never approach the limit.
#![forbid(unsafe_code)] on every crate. No unsafe, no Box::leak, no reference cycle, no global mutable state.
Install
Not on crates.io yet, so depend on the Git repository:
[dependencies]
oxider-query = { git = "https://github.com/OxideR-System/OxideR-Query", tag = "v0.1.0" }
# Optional: async execution over sqlx (SQLite today)
oxider-query-exec = { git = "https://github.com/OxideR-System/OxideR-Query", tag = "v0.1.0" }
# Optional: generate entities from an existing schema (SQLite today)
oxider-query-codegen = { git = "https://github.com/OxideR-System/OxideR-Query", tag = "v0.1.0" }Known gaps
- PostgreSQL and MySQL execution and codegen backends.
#[derive(Projection)].- A GroupBy transformer.
- The renderer clones each
Valueinto the parameter list; removing it needs the render entry points to consume the AST rather than borrow it. - A tree thousands of subqueries deep can still overflow when dropped. Rendering it is refused either way.
Documentation
The guide is sixteen chapters, in Vietnamese, in docs/, and is also published as a Docusaurus site under website/.