Commit Graph

298 Commits

Author SHA1 Message Date
lovasoa 5c288b0ac9 Move database-specific SQL tests into per-database subdirectories
Restructure tests so files that only work on a single database engine
are
organized under `database-specific/<engine>/` instead of using long
`_no...`
suffixes. Add a dedicated test that runs these files only when the
current
database matches, and simplify the generic test runner by extracting
shared
execution logic.
2026-08-23 09:47:58 +02:00
Ophir Lojkine 342a5fa522 fix: mssql test escaping and improve changelog
- Fix variable_mssql_contains test: avoid nested single quotes in
  sp_executesql string that caused 'Incorrect syntax near It' on
  CI. Use parameterised expected value instead of embedding
  'It works !' inside the inner N'...' string.

- Improve CHANGELOG: one main bullet about removing CAST with
  subpoints for PostgreSQL/MySQL/DuckDB, SQL Server nvarchar/
  CONTAINS/EXEC, MySQL LIMIT/OFFSET, and retained cast on SQLite/
  ODBC. Move fixes:/see: to PR description.
2026-08-22 21:21:09 +00:00
Ophir Lojkine 0355fc444d test: make variable fixtures self-contained
Remove 'same root cause' and inaccurate GH links that referenced
other files. Each fixture now describes its own invariant without
assuming reader context from another file.
2026-08-22 21:09:22 +00:00
Ophir Lojkine c96bbc0fe2 test: simplify variable cast tests and add focused repros for #516 and #1154
- Replace verbose pattern-matching in sql.rs with helpers sql_for/
  odbc_sql_for and table-driven asserts; keep coverage but drop
  ceremony and duplicated error messages.
- Keep limit and mssql tests as one-liners checking the generated
  SQL string.
- Trim .sql fixtures to minimal scaffold and add GH issue links as
  comments. New fixtures:
  * variable_limit_offset (MySQL, fixes #1154) — LIMIT/OFFSET with SET
    variables must not be wrapped in CAST.
  * variable_mssql_contains (MSSQL, fixes #516) — EXEC sp_executesql
    with a variable must not be wrapped in CAST.
- Simplify existing variable fixtures and add issue links, keep them
  short and readable.
2026-08-22 21:03:39 +00:00
Ophir Lojkine 42cf5b619a fix(odbc) :: keep the text cast around variables on ODBC connections
The ODBC job in CI failed because the cast was removed based on the
database name behind the driver: PostgreSQL reached through psqlodbc no
longer received CAST(? AS TEXT), and the driver could not determine the
type of context-free parameters such as in 'WHERE ? <> ? OR ? IS NULL',
failing with 'could not determine data type of parameter'.

The cast decision now keys off the connection kind: native PostgreSQL,
MySQL and SQL Server connections keep no cast, while every ODBC
connection keeps the previous per-database cast, since ODBC drivers
provide no parameter type information.

Adds a fixture comparing variables without any surrounding type context,
which exercises exactly this scenario on every database.
2026-08-22 20:19:20 +00:00
Ophir Lojkine 7e9adb14e5 feat(database) :: stop casting variables to TEXT on PostgreSQL, MySQL and SQL Server
SQLPage binds every variable as a string. The generated CAST(? AS TEXT)
forced the database to type the parameter as text, which is only needed
where parameter type inference is unpredictable (SQLite, ODBC). On the
natively supported databases the cast was redundant, and on SQL Server it
was harmful: the parameter is bound as NVARCHAR(MAX), and casting it to a
narrow VARCHAR mangled non-ASCII values before comparing them to nvarchar
columns. Generated SQL is now cleaner, e.g. WHERE id = $1 instead of
WHERE id = CAST($1 AS TEXT).

SQLite and ODBC-backed databases (Oracle, DuckDB, Snowflake, Generic)
keep the cast to preserve their comparison semantics.

Verified with the full test suite against SQLite, PostgreSQL, MySQL and
SQL Server, including new fixtures for integer-column comparisons,
numeric-literal comparisons, and unicode nvarchar comparisons on SQL
Server.
2026-08-22 15:44:13 +00:00
Prayag Bhakar 6fb9a35336 fix(rust): enforce lint on all files + fix lint issues (#1394) 2026-08-21 07:38:45 +02:00
Prayag Bhakar 071d4df034 fix(docs) :: address documentation gaps (#1390) 2026-08-20 11:06:02 +02:00
Prayag Bhakar e99957bc93 fix(request_body) :: return NULL when the request has no body' (#1392) 2026-08-18 12:39:54 +02:00
Ophir LOJKINE 6859b560ee fix(form): close searchable single-select dropdown (#1381)
* fix(form) :: close searchable single-select dropdown

* fix and test properly
2026-08-17 14:41:57 +02:00
Prayag Bhakar 71609f2251 feat(typescript) :: typecheck browser JavaScript in CI (#1373) 2026-08-14 22:48:38 +02:00
Prayag Bhakar bfd6d33812 fix(npm) :: install dependencies only once at root level (#1372) 2026-08-13 10:29:29 +02:00
Prayag Bhakar e15b16d211 fix(chart) :: line series up on a category axis for every chart type (#1371) 2026-08-13 09:27:51 +02:00
81reap 8b7eb16bd7 fix(chart) :: align stacked series on their X values 2026-08-11 20:02:59 -04:00
Prayag Bhakar b8f10adc7f feat(chart) :: render column charts as bar charts + filter malformed stack points (#1369) 2026-08-11 17:59:15 +02:00
Prayag Bhakar 98fc68a781 fix(map) :: ignore map coordinates that are not a pair of numbers (#1368) 2026-08-10 21:48:42 +02:00
Prayag Bhakar e44f993a20 fix(modal) :: give modal component an accessible name (#1367)
* fix(biome.js) :: fix remaining lint issues

* fix(modal) :: give modal component an accessible name
2026-08-10 17:14:47 +02:00
Prayag Bhakar fb519dc347 fix(biome.js) :: fix remaining lint issues (#1366) 2026-08-10 14:59:42 +02:00
Ophir LOJKINE 7457370b8d Add toast notification component with JS init, template, examples, migration, and tests (#1360)
* Support hash-triggered toasts

* Address toast review feedback

* Render toast positioning in template

* Avoid reopening initialized toasts

* Fix white toast foreground color

* Configure toasts declaratively

* Simplify toast template styling

* Address toast accessibility reviews
2026-08-03 15:50:18 +02:00
Ophir LOJKINE a065bb8d0f Fix form options source query parameters (#1356) 2026-07-29 11:37:36 +02:00
Ophir LOJKINE b113f3c213 Update frontend development dependencies
Update Biome to 2.5.4 and Playwright to 1.61.1, align type definitions with Node 24, and exclude SVG assets that the new Biome release starts parsing from the existing lint scope.
2026-07-18 00:07:30 +02:00
Ophir LOJKINE 91a28d1631 Remove unused SQLx macro facade
Depend directly on sqlx-core-oldapi and use its module-level runtime APIs. SQLPage uses no SQLx query or derive macros, so this removes sqlx-oldapi and sqlx-macros-oldapi from the resolved graph.

macOS arm64 cargo build --release: 43,037,040 -> 43,020,288 bytes (-16,752 bytes, -0.04%).
2026-07-17 23:54:40 +02:00
Ophir LOJKINE 93afed919c Email sending improvement 2026-07-17 15:22:53 +02:00
Ophir LOJKINE 06459c4071 fmt 2026-07-16 17:17:32 +02:00
Ophir LOJKINE 04a94ddf31 fix mysql incompatibility in tests 2026-07-16 17:16:03 +02:00
Ophir LOJKINE ca8d94baa5 fix bug in test error formatting 2026-07-16 17:15:48 +02:00
Ophir LOJKINE 91160ff9c7 Clarify per-row function evaluation 2026-07-16 15:31:24 +02:00
Ophir LOJKINE f5fddf8ea1 oracle syntax 2026-07-15 18:29:35 +02:00
Ophir LOJKINE 2276b31b91 Merge branch 'main' into lazy-fn-eval-in-set 2026-07-15 18:11:18 +02:00
Ophir LOJKINE 614c6303f5 Handle parentheses around computed projections 2026-07-15 18:03:14 +02:00
Ophir LOJKINE a175fd4215 Avoid dropping shared filesystem test tables 2026-07-15 16:06:18 +02:00
Ophir LOJKINE 8a35fa1e0d Match concat operator null semantics 2026-07-15 15:53:08 +02:00
Ophir LOJKINE 365ea0fd43 fix tests on mssql 2026-07-15 15:41:55 +02:00
Ophir LOJKINE c84eb68d77 Keep private inputs after wildcard projections 2026-07-15 15:33:39 +02:00
Ophir LOJKINE 556c62a573 Match CONCAT null semantics to the database 2026-07-15 15:05:54 +02:00
Ophir LOJKINE 709153b0e9 Validate SET columns before computed expressions 2026-07-15 15:01:07 +02:00
Ophir LOJKINE 3b5fccab2f Make no-row SQL tests portable 2026-07-15 14:57:11 +02:00
Ophir LOJKINE 5c45103eb2 Preserve backend scalar subquery semantics 2026-07-15 14:43:15 +02:00
Ophir LOJKINE c181c21784 Add regressions for computed projection ordering 2026-07-15 14:24:39 +02:00
Ophir LOJKINE 947b189020 Add tests for mixed database and row expression rewriting
Cover cases where SQLPage functions span database and per-row
boundaries, including predicates, projections, and shared request/row
values.

Also add documentation to `QueryResult` and `BoundQuery`, tidy up
borrow expressions in the OIDC client, and rename a conflicting `rows`
alias in a test fixture.
2026-07-15 13:58:14 +02:00
Ophir LOJKINE e07594579b Refactor SQL parameter handling into unified expression tree
Replace the `StmtParam`/`ParameterExtractor` machinery with a shared
`SqlPageExpr` type parameterized over its input source, distinguishing
standalone expressions from per-row expressions via the type system.

Rename `ParsedSqlFile` to `SqlFile` and split SQL parsing into dedicated
`dialect`, `rewrite`, and `statement` submodules. Queries are now
rewritten into either a database query with bindings and computed
columns, or a single-row query evaluated without a database round trip.

Make `SqlPageFunctionName` parsing case-insensitive and support
SQLPage-computed projections and nested functions over query results.
2026-07-15 13:44:31 +02:00
lovasoa 7b72bd5d1a Preserve session state for delayed run_sql 2026-07-11 00:34:44 +02:00
lovasoa 9b4970d4c9 Preserve NULL semantics for constant SQLPage functions 2026-07-11 00:23:10 +02:00
Ophir LOJKINE 8b67a06af4 Prevent caching OIDC redirects (#1344)
* Prevent caching of OIDC redirects

* Clarify OIDC cache replay test comments
2026-07-10 14:37:28 +02:00
Ophir LOJKINE 1ab77de705 Make SQLPage function evaluation deterministic
SQLPage functions that are the whole value of a selected column now
execute after the database query, once per returned row. If the query
returns no rows, the function is not called. This ensures deterministic
behavior and prevents expensive or side-effectful operations from
running unnecessarily.
2026-07-08 10:50:39 +02:00
Ophir LOJKINE 3691724218 Move exec SQL test to dedicated Rust test 2026-07-06 16:50:13 +02:00
Ophir LOJKINE b893455439 Bind OIDC logout URLs to the requesting session (#1313)
* fix(oidc): bind logout URLs to the current session (forced-logout CSRF)

Logout URLs were signed only over the redirect target and a timestamp, so any
valid, unexpired logout URL would clear whoever's cookies followed it. The
logout signature now also covers the caller's sqlpage_auth cookie, so a logout
URL only logs out the session it was issued for. Generation and verification
select the same cookie (the last of any duplicates, matching how RequestInfo
merges them) so the check stays consistent.

* style: run cargo fmt on the logout cookie test
2026-06-11 15:11:35 +02:00
Ophir LOJKINE 4b487c1acb fix: hide SQL details in production error responses for every output format (#1308)
In production, error responses must not leak the SQL statement, the source
file path, the raw database error, environment values, or configuration, for
ANY output format. In development the full detail is shown, and the full error
is always logged server-side.

This centralizes the dev-vs-production decision in a single place. An internal
error stays a full `anyhow::Error` everywhere; the only place that turns it into
a user-facing representation is `ClientError::new` in `src/webserver/error.rs`,
which is also the only caller of `DevOrProd::is_prod`. Every renderer (HTML,
JSON, NDJSON, SSE, CSV) and the header/pre-body path obtains a `ClientError`
from that one function and only formats it; none of them inspect the
environment. Leaking is therefore impossible by construction: a renderer cannot
emit what it never receives.

The error type, the production message, `get_backtrace_as_strings`, and the
error-component data construction live in `error.rs`; `render.rs` only renders
components and formats a `ClientError`.
2026-06-11 11:56:23 +02:00
Ophir LOJKINE c37ce26351 Fix Content-Disposition parameter injection via download filenames (#1309)
The csv and download components built the Content-Disposition header by
string-interpolating the user-supplied filename. A filename containing
characters such as ';', '"' or '=' could inject an additional header
parameter (e.g. a second, agent-preferred filename*=...), letting an app
that interpolates untrusted data into the filename smuggle a different
download name past the intended one.

Build the header with actix-web's structured ContentDisposition type so
the filename is always a single, properly quoted/escaped value and cannot
create new parameters.
2026-06-10 16:39:01 +02:00
Ophir LOJKINE d92a7399e2 fix: enforce unprivileged path guard on fresh cache hits (#1307)
Reserved/private SQL files (sqlpage/ prefix, dotfiles, .. traversal,
absolute paths) became directly routable over HTTP while their parsed
form was fresh in sql_file_cache. A trusted page loading such a file via
sqlpage.run_sql(...) loads it with privilege and caches it; a later
direct unprivileged request hit the fresh cache entry before the path
guard ran, returning 200 and executing the private SQL instead of 403.

The unprivileged path validation is extracted into
filesystem::validate_unprivileged_path and now enforced before
consulting the cache in both HTTP routing (AppFileStore::contains) and
the unprivileged FileCache::get_with_privilege path.
2026-06-10 16:37:20 +02:00