25 Commits

Author SHA1 Message Date
Prayag Bhakar f7ebba6230 fix(fetch_with_meta) :: document json bodies under json_body (#1393) 2026-08-23 17:42:39 +02:00
Ophir LOJKINE ae69528f5d Stop casting variables to TEXT on PostgreSQL, MySQL and SQL Server (#1397)
* 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.

* 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.

* refine: drop the text cast on MySQL, SQL Server and DuckDB behind ODBC too

ODBC connections were conservatively keeping the cast for every database.
Testing against real ODBC drivers shows the cast is needed only where the
parameter type cannot be determined without it:

- psqlodbc -> PostgreSQL: needed. Without it, context-free parameters fail
  with 'could not determine data type of parameter' (the earlier CI failure).
- sqliteodbc -> SQLite: needed. Without it, '? = 1' compares text against an
  integer and silently returns false, like on native SQLite.
- duckdb-odbc: not needed. The full test suite passes without the cast, as
  DuckDB defaults untyped parameters to VARCHAR.

The cast is therefore dropped for MySQL, SQL Server and DuckDB behind ODBC,
mirroring their native behavior (MySQL and SQL Server convert the bound
string at execution time, which also fixes the unicode mangling for SQL
Server reached through ODBC), and kept for PostgreSQL, SQLite, Oracle,
Snowflake and unknown databases.

Verified with the full test suite on native SQLite, PostgreSQL, MySQL and
SQL Server, and through ODBC on PostgreSQL, SQLite and DuckDB. The only
ODBC failure is a pre-existing database-filesystem timestamp test that also
fails on main.

* docs(changelog): make variable cast entry concise and user-oriented

The previous entry described internal CAST(? AS TEXT) generation
and load-bearing type affinity details. Rephrase for users:
focus on the visible fix (MSSQL nvarchar Unicode mangling)
and the general simplification (no unnecessary text cast
where the database infers the type).

* docs(changelog): add issue references for variable cast fix

Fixes: #516 (CONTAINS with MSSQL variable fails due to CAST), #1154
(LIMIT/OFFSET with variables fails on MySQL/MariaDB).
See: #1317 (per-database logic still scattered, this is a step
toward the SqlDialect abstraction).

* 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.

* 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.

* 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.

* Refactor SQL variable cast tests to use typed assertions

* clippy

* 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.

* rename mssql variable fixture to match its sp_executesql repro

The file reproduces issue #516 (CONTAINS rejects CAST expressions), but
the query itself uses sp_executesql, which has the same restriction
without needing a full-text index. Rename the fixture to reflect that
and add a comment explaining why CONTAINS is not used directly.
2026-08-23 16:55:00 +02:00
Ophir LOJKINE 93afed919c Email sending improvement 2026-07-17 15:22:53 +02:00
Ophir LOJKINE 04a94ddf31 fix mysql incompatibility in tests 2026-07-16 17:16:03 +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 614c6303f5 Handle parentheses around computed projections 2026-07-15 18:03:14 +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 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 9b4970d4c9 Preserve NULL semantics for constant SQLPage functions 2026-07-11 00:23:10 +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 0d5373509a new function: sqlpage.regex_match (#1271)
* new function: sqlpage.regex_match

* fix mysql tests

* Update CHANGELOG.md
2026-04-08 21:39:32 +02:00
lovasoa 16c089705c Fix: sqlpage.variables() no longer returns duplicate keys
The `sqlpage.variables()` function previously allowed duplicate keys
when GET, POST, and SET variables of the same name were present. This
commit ensures that the returned JSON object contains only unique keys,
with precedence given to SET variables, then POST, then GET.
2025-12-30 03:12:29 +01:00
lovasoa 5688140ca7 add more exhaustive tests for sqlpage.variables
see https://github.com/sqlpage/SQLPage/issues/1169
2025-12-30 02:20:11 +01:00
Ophir LOJKINE 747cc78a91 sqlpage.fetch(null) = null (#1131)
* Fix: fetch(null) and fetch_with_meta(null) return null

Co-authored-by: contact <contact@ophir.dev>

* clean up implementation

* update docs

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2025-11-26 22:02:51 +01:00
lovasoa 9c89f02c9b fix regression: SET x = NULL now works even when ?x=something is set in
the URL

fix https://github.com/sqlpage/SQLPage/issues/1130
2025-11-26 21:29:14 +01:00
Ophir LOJKINE 95f228f383 simplify SQL test files (#1129)
* simplify SQL test files

- Simplified SQL test files by removing unnecessary components and restructuring queries to focus on expected vs actual results.
- Updated the request handling in `run_sql_test` to differentiate between JSON and HTML responses based on test file content.
- Enhanced error handling and assertions for both JSON and HTML responses to improve test reliability and clarity.
- Removed redundant code and improved readability in the test execution flow.

* move more tests to the new expected/actual format

* migrate more tests to the expected/actual format

- Changed references in various test files to point to the new `simple.sql` instead of the outdated `it_works_simple.sql`.
- Removed several obsolete SQL test files that are no longer needed, streamlining the test suite.
- Updated assertions and request paths in the test cases to reflect the new structure and improve clarity.

* Enhance error handling in SQL tests and update SQL syntax

- Added a new function `format_error` to improve error reporting in SQL test assertions, capturing detailed error descriptions and backtraces.
- Updated SQL syntax in `sqrt.sql` to use `INT` instead of `integer` for consistency with SQL standards.

* sqrt test
2025-11-26 00:45:13 +01:00