Skip to content

v0.4.7

Choose a tag to compare

@Dev-Pasaka Dev-Pasaka released this 14 Oct 17:37
· 97 commits to main since this release

[0.4.7] — 2026-10-12

No version was cut between 0.4.6 (2026-07-23) and this release — everything below merged to main
across that gap in 66 PRs (GH #5–#151) with no corresponding gradle.properties bump, so this entry
consolidates all of it. The headline work: three adversarial review rounds (2026-09-08 pre-multi-DC
review, 2026-09-09 post-fix critical audit, 2026-10-11 docs reality-check follow-up) each
specifically re-checked the fixes from the round before, which is how several of the "Fixed" items
below are themselves fixes to regressions introduced by an earlier fix in this same release.

Breaking / behavior changes

  • findActive()'s ALLOW FILTERING is now an explicit opt-in (GH #12) — without
    @SecondaryIndex on the marker column, it throws KandraQueryException unless called with
    allowFullScan = true, instead of silently running ALLOW FILTERING with a WARN.
  • KandraBatchScope split into suspend-only and blocking (KandraBlockingBatchScope) types
    (GH #99) — mixing a blocking repository's saveInBatch into a suspend batch { } block (which
    would block the coroutine dispatcher) is now a compile error instead of a runtime footgun.
  • @Version LWT updates no longer blindly retry on transient errors (GH #15) — retrying the
    identical conditional statement after a timeout could raise a spurious
    KandraOptimisticLockException for a write that had already succeeded; see the retry recipe in
    docs/USER_GUIDE.md.
  • EACH_QUORUM (as a read consistency) and SERIAL/LOCAL_SERIAL (as a write consistency) now
    throw KandraQueryException
    instead of reaching the driver unvalidated (GH #140/#141) — a caller
    relying on the previous unvalidated pass-through will now get a clear exception at query time.
  • AUTO_MIGRATE refuses to ALTER TABLE ADD a missing partition/clustering key column (GH #92)
    — throws KandraSchemaException instead of silently adding it as a plain column and corrupting row
    identity.

Added

  • saveAndGet() / saveIfNotExistsAndGet() / updateAndGet() on KandraRepository/
    KandraSuspendRepository (GH #132/#138/#136) — same writes as save()/saveIfNotExists()/
    update(), but return the entity actually persisted (generated id, @CreatedAt/@UpdatedAt,
    post-write @Version) instead of discarding it.
  • update()/updateAndGet() gained a serialConsistency parameter (GH #134) — lets a versioned
    update request SERIAL (cross-DC Paxos) instead of the previous hardcoded LOCAL_SERIAL for its
    IF version = ? check.
  • throttle { } config block — client-side admission control wiring the driver's own
    ConcurrencyLimitingRequestThrottler (maxConcurrentRequests/maxQueueSize), off by default
    (GH #81/ISS-073).
  • PoolConfig gained local/remote connection-pool size fields — previously no
    Kandra-level configuration at all (GH #80).
  • KandraThrottledException (GH #103) — wraps a backpressure/admission-control rejection instead
    of letting the raw driver RequestThrottlingException leak through unwrapped.
  • Consistency Strict Mode (GH #5) — opt-in, WARN-only warning when a query resolves to
    LOCAL_ONE/ONE in a deployment loadBalancing.allowedRemoteDcs marks as multi-DC.
  • kandra-codegen generates typed Koin/Kodein DI accessors (GH #17) — a compile-checked
    alternative to hand-typed named("...") qualifier lookups.
  • DebugConfig.rawQueryStrictMode — makes the raw()/rawQuery() injection heuristic throw
    KandraQueryException instead of only warning.

Fixed — correctness / data integrity

  • @Transient columns broke every full-row decode (GH #130).
  • save() discarded the entity's generated id/timestamps (GH #132); update() discarded the
    actually-persisted post-update version (GH #136); saveIfNotExists() discarded the actually-
    persisted entity (GH #138) — all three addressed by the new ...AndGet() methods above.
  • Predicate values bypassed codec encoding in buildWhere() — broke eq/gt/lt/in on any
    non-primitive column type (GH #144).
  • append()/remove() bound a raw Collection, not the column's actual CQL collection type —
    broke every Set<T> column (GH #145).
  • IN on a @SecondaryIndex column silently queried the partition key instead of the indexed column
    (GH #146).
  • Counter columns threw on decode whenever any counter cell in a row was untouched (NULL) (GH #93).
  • Repositories silently discarded the plugin's own configured StatementBuilder/codec.
  • BatchEngine-bypassing write paths now route through executeWithRetry (GH #29).
  • Eager SchemaRegistry validation for @PartitionKey/@ClusteringKey conflicts, blank @Column
    names, and identifier shape (GH #30); duplicate cqlNames now rejected eagerly, with frozen<>
    support for collection keys/nesting (GH #31).
  • DI qualifier collisions are now detected before binding instead of resolving the wrong repository
    at runtime (GH #35).
  • The AUTO_CREATE/AUTO_MIGRATE DDL bootstrap claim is now keyed by schema fingerprint (GH #90)
    and fenced by holder with a clock-skew-proof staleness fallback (GH #91), after an LWT claim guard
    against concurrent-instance races was added first (GH #79).
  • Migration claim resolution now distinguishes losing a race to an APPLIED row from losing to a
    CLAIMED one (GH #101); checksum now hashes every sibling compiled class file up() produces
    (object expressions, anonymous classes, SAM lambdas), not just the migration's own (GH #102), and
    normalizes compiled bytecode before hashing (GH #63); migration claim staleness is now measured by
    the DB cluster's own clock, not the app instance's (GH #64); first crash-safety pass
    (CLAIMED/APPLIED distinction + kandra-migrate test suite) (GH #26/#37).
  • Codegen now gives clear diagnostics for file-name collisions on same-simple-name nested entity
    classes and for @ScyllaTable on a non-data-class, instead of a late, confusing failure (GH #104).
  • FakePreparedStatement.bind() now simulates real binding instead of unconditionally throwing
    (GH #28) — the fix the kandra-test skill was still describing the pre-fix state of as of the
    2026-09-09 docs pass.

Fixed — consistency & multi-DC

  • LOGGED BATCH/versioned-update consistency and decodeEntity column-map caching (GH #54/#68);
    generic reads (find/findAll/findPage/exists) ignored configured read consistency, and
    unpaged reads had no row cap (GH #55/#67 — the latter is the findAll() 10,000-row truncation
    documented in docs/USER_GUIDE.md/docs/features/repositories.md).
  • Multi-DC failover, credential rotation re-applying to the live session, keyspace/DC identifier
    validation, and a dead pool-config field removed (GH #58/#61/#65/#69).
  • Retry idempotency, saveIfNotExists LWT retry, versioned-update TTL preservation, and soft-delete
    consistency (GH #56/#57/#59/#62).
  • Suspend-safe batch collection, failure-path metrics, and RF-aware Strict Mode (GH #60/#82/#83).
  • Lookup-index reads and versioned-update lookup-table writes both bypassed configured consistency
    (GH #96); findById() cache hits silently ignored the caller's consistency override (GH #95); a
    cache invalidate-after-write race could pin a stale value indefinitely, and @CacheResult's
    per-process-only scope is now documented (GH #100).
  • Strict Mode's RF-vs-consistency math was wrong for multi-DC NetworkTopologyStrategy, causing
    false-positive warnings (GH #98).

Fixed — security / hardening

  • raw()/rawQuery()'s injection guard is no longer suppressed by an unrelated bound parameter
    elsewhere in the same string (GH #32), and now also catches a bare ; statement terminator or a
    --//* */ comment marker — closing a gap where a payload against an unquoted numeric/UUID column
    reached the driver undetected (GH #142).
  • KandraColumnRef's cqlName is now validated against CQL identifier shape (GH #33).
  • KandraCredentials.toString() no longer prints the plaintext password (GH #66).
  • SSL config (requireEncryption/minimumTlsVersion/cipherSuites) is now actually enforced,
    not dead fields (GH #78); TLS floor now fails closed, the reverse-DNS hostname-verification bug is
    fixed, and pool size is validated (GH #105).
  • A missing-auth fallback is now logged, existsQuery's raw CQL path is guarded, and predicate
    columns are validated (GH #107).

Fixed — performance / scalability

  • Entity reflection (copy fn, properties, constructor) is now cached per KClass instead of
    recomputed every call (GH #13).
  • findActive()/findActiveSuspend() had no row cap — an unbounded ALLOW FILTERING scan could
    OOM the process (GH #94).
  • Suspend read/write paths no longer block the coroutine dispatcher on a prepared-statement cache
    miss (GH #27); graceful shutdown's drain loop replaced a Thread.sleep busy-wait with coroutine
    polling (GH #34).
  • kandra-runtime read-path/metrics polish: implicit LIMIT 1 where applicable, atomic
    cache getOrPut, cache-stampede protection, and more specific metrics labels (GH #106).

Testing & CI

  • kandra-ktor tests now run in CI, with new kandra-runtime unit test coverage (GH #16).
  • Codegen tests now assert on generated *Table.kt content, not just file existence (GH #37, part).
  • New SSL round-trip, multi-DC failover, and migration-claim concurrency test coverage (GH #84);
    kandra-multidc/SSL integration suites are now wired into CI instead of tagged "manual" with zero
    scheduled execution (GH #97); multi-DC/SSL test fixtures hardened — fixed compose ports, pause-vs-
    real-partition realism, a missing hostname-mismatch SSL test (GH #108).
  • Jakarta validator factory reuse, codegen nullability, and health-check debounce (GH #36, items
    1/2/4).
  • Assorted low-severity findings from the 2026-09-09 post-fix critical audit (GH #109).

Documentation

  • @LookupIndex + @SoftDelete combined storage-growth behavior documented (GH #6); LWT documented
    as a kandra-migrate prerequisite (GH #7).
  • Two full reality-check passes against current source (2026-09-08, 2026-09-09) corrected drift
    across README.md/docs/USER_GUIDE.md/docs/features//.claude/skills/ — see
    docs/reviews/2026-09-09-docs-reality-check.md.
  • kandra-multidc documented as experimental — not yet validated against a genuine multi-region
    topology
    (every round so far has been an approximation: Testcontainers on one host, a two-
    physical-host self-hosted round, and a ScyllaDB Cloud attempt that turned out to provision
    multi-AZ, not multi-DC) — GH #149, and extended to docs/USER_GUIDE.md's Multi-DC Support section.
  • A further reality-check pass (2026-10-11) found and fixed 9 gaps left by a month of the above work
    shipping with no doc updates — the findAll() row cap, the three ...AndGet() methods,
    KandraThrottledException/throttle { }, the EACH_QUORUM/SERIAL consistency-validation
    caveat, rawQueryStrictMode, the AUTO_MIGRATE key-column guard, the migration-checksum
    sibling-class coverage, and a stray (0.4.7) version tag that predated this actual release — see
    docs/reviews/2026-10-11-docs-reality-check-followup.md.