Yorgos S.
07/19/2026, 8:11 AM@Query SQL for syntax and schema.
The next step parses each query once and runs a set of extra safeguards + codegen rewrites β all through KSP, all on by default, each toggleable per module.
Validations that fail the build:
β’ Column validation β reference a column that doesn't exist on the entity (in SELECT / WHERE / GROUP BY / HAVING / ORDER BY / SET)? Build fails.
β’ Table validation β a single-table query pointing at the wrong table.
β’ Statement-kind check β find*/count*/exists* must be SELECT, delete* must be DELETE, etc.
β’ Projection check β a find* bound to the generated row mapper must select every column (no half-mapped rows at runtime).
β’ Stacked-statement guard β one @Query, one statement.
Optimizations the processor applies for you:
β’ SELECT * expansion β select * from users β select id, name, email from users in the generated statement.
β’ findOne* limiting β appends LIMIT 2 so the driver still detects multi-row results but transfers far less on wide tables.
Everything maps columns βοΈ properties with the same rules as the CRUD generator (snake_case / @Column), so it always stays consistent with the SQL that gets generated. Don't like one of them? arg("findone-limit", "false") in your KSP block and it's gone.
π§ͺ 2. In-memory repositories (for unit testing) (experimental)
A companion module, sqlx4k-codegen-test, generates a thread-safe in-memory implementation of every @Repository β so you can unit-test the code around your repos with no database at all. For Sqlx4kRepository you get InMemorySqlx4kRepository.
val repo = InMemorySqlx4kRepository()
// A QueryExecutor is accepted to match the interface, but never touched.
repo.insert(db, Sqlx4k(id = 1, test = "test")).getOrThrow()
val all = repo.findAll(db).getOrThrow()
What you get out of the box:
β’ Full CRUD + save / batchInsert / batchUpdate, with auto-increment numeric ids.
β’ Whole-table findAll / countAll / deleteAll.
β’ Derived @Query methods β the in-memory behavior is derived from the query's actual SQL: the WHERE clause is parsed into a predicate over stored entities. @Query("select * from users where name = :name") just behaves like filter { it.name == name }. Supports = <> < <= > >=, IS [NOT] NULL, AND/OR, named params & literals.
β’ Test helpers β clear(), findAllStored(), and a withStore { } block for dirmap.
Anything the generator can't translate (LIKE, IN, subqueries, function callsws NotImplementedError β and since the class is open, you just override thosefew methods yourself. It's opt-in per KSP config (kspTest), so it never touches your production wiring.
Heads-up: this one's early/experimental β the generated API may still shift, so feedback is very welcome. π
Repo & full docs: github.com/smyrgeorge/sqlx4k β would love to hear what you think, especially on the in-memory repos. π¬