Skip to content

Conversation

@deusaquilus
Copy link
Member

@deusaquilus deusaquilus commented Dec 3, 2025

PR Title

Make raw SQL execution explicitly opt-in and add internal runBoth APIs

Summary

This PR tightens the story around Terpal-SQL’s “escape hatch” raw-SQL APIs and introduces new internal helpers that return both decoded results and raw column metadata. The main goals are:

  • Make all “run-raw” style APIs explicitly opt-in and visually noisy at call sites.
  • Limit exposure of internal controller plumbing (including new “both decoded + raw” APIs) while keeping them available for tooling like the ExoQuery playground.

Key Changes

1. New unsafe opt-in annotation

  • Introduced TerpalSqlUnsafe:
    • Marked with @RequiresOptIn.
    • Message makes it clear this is a raw SQL API where injection is possible.
    • Targets classes/functions and is BINARY-retained.

This gives a single, consistent annotation for all unsafe SQL entry points.

2. Centralized unsafe run-raw “escape hatch”

  • The controller extension runActionsUnsafe is now annotated with @TerpalSqlUnsafe.
  • It takes a raw SQL string containing one or more ;-separated statements and runs them as actions on a controller.
  • All call sites outside controller-core now:
    • Import TerpalSqlUnsafe.
    • Are wrapped in @OptIn(TerpalSqlUnsafe::class) either at function or class level.

Effectively, any use of the unsafe escape hatch in tests must now opt in explicitly, making these usages easy to audit.

3. Internalization of controller plumbing

  • Introduced TerpalSqlInternal as a @RequiresOptIn annotation for internal library APIs:
    • Message clearly states that these APIs are internal and may change.
    • Targets classes, functions, and fields.
  • Applied @TerpalSqlInternal to internal controller primitives and helpers, including:
    • Encoding and session/transactionality infrastructure.
    • ControllerVerbs methods such as stream, run, and runRaw, plus defaulted overloads.
    • Core controller base types that are not meant to be part of the stable public surface area.

This separates the “intended public surface” from the implementation hooks, while still letting advanced users opt in if needed.

4. New internal runBoth APIs

  • Added new “both decoded + raw columns” APIs on controllers (e.g. JDBC implementation):
    • runBoth(query, options) returns List<Pair<T, RawColumnSet>>.
    • Backed by a corresponding streamBoth variant.
    • Annotated with @TerpalSqlInternal.
  • The methods mirror the existing run / stream API shape but return:
    • The fully decoded entity (T).
    • A RawColumnSet snapshot for the same row.

These will be used by the ExoQuery playground to demonstrate how a controller can surface both typed result sets and raw column metadata without expanding the public API surface yet.


Rationale

  • Terpal-SQL’s primary value is in preventing arbitrary raw SQL execution. However, tests and migration/setup scenarios still need an “escape hatch”.
  • By adding TerpalSqlUnsafe and requiring @OptIn(TerpalSqlUnsafe::class) at call sites, any use of this escape hatch is explicit, reviewable, and intentionally noisy in code.
  • TerpalSqlInternal lets us:
    • Expose richer controller capabilities to tooling (like the playground).
    • Avoid committing to long-term stability for low-level plumbing and experimental APIs (runBoth, runRaw, etc.).
  • The runBoth APIs give the playground a way to show:
    • “What the controller sees” (raw result sets / column info).
    • “What Terpal-SQL gives you” (fully decoded data structures), side by side.

Testing / Impact

  • All unsafe raw SQL usages (primarily in tests and schema setup) were updated to opt in via @OptIn(TerpalSqlUnsafe::class).
  • Existing tests around queries, actions, and encoding should continue to pass:
    • The runtime behavior of runActionsUnsafe itself is unchanged; only its opt-in contract and call sites changed.
  • No breaking changes are expected for consumers who only use the “normal” typed SQL APIs; the new annotations affect:
    • Raw SQL escape hatch usage.
    • Internal controller plumbing that was not intended as public API.

@deusaquilus deusaquilus merged commit e475a08 into main Dec 3, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants