KandraBatchScope
DSL scope for building a caller-controlled LOGGED batch inside KandraRuntime.batch (suspend).
All saveInBatch() and deleteInBatch() calls inside the block are collected and executed as a single atomic LOGGED BATCH when the block exits. See KandraBlockingBatchScope for the batchBlocking (non-suspend) counterpart.
Deliberately not named save/delete: Kotlin resolves a member function of the extension receiver over an extension function with a matching name unconditionally, even when the extension is a member-extension of an implicit receiver in closer scope (as these are, being declared inside this class). Since every repository already has its own real save/delete member, repo.save(entity) (or with(repo) { save(entity) }) inside a batch block would always silently call the repository's own immediately-executing method — never this class's statement-collecting one — with no compiler warning. Distinct names route the call correctly and make it a compile error to reach for the wrong one.
Suspend-only, and deliberately a distinct type from KandraBlockingBatchScope (GH #99 / ISS-086): this class exposes saveInBatch/deleteInBatch only as extensions on KandraSuspendRepository, collected via BatchEngine.collectSaveSuspend/ BatchEngine.collectDeleteSuspend — which use StatementBuilder's suspend prepare path (StatementBuilder.insertPrimarySuspend etc.) so a prepared-statement cache miss during collection never blocks the calling coroutine's dispatcher thread. Earlier, a single KandraBatchScope exposed both this suspend overload set and KandraRepository (blocking) overloads at once, so blockingRepo.saveInBatch(entity) inside a suspend batch { } block compiled cleanly and silently resolved to the blocking collectSave/prepare() path, blocking the calling coroutine's dispatcher thread — exactly the problem #60/ISS-059 fixed for the common case, reachable again through the other repository type. Splitting into two scope types closes this: KandraBlockingBatchScope's extensions simply aren't in scope inside a suspend batch { } block, so a mixed call is now a compile error (unresolved reference) instead of a silent dispatcher-blocking bug.
Restrictions:
findAll,findById, and all read operations are not available — reads cannot be batched.saveIfNotExiststhrows KandraQueryException — LWT cannot be mixed with regular statements.EVENTUAL lookup writes are not included in the batch (they fire separately after commit).
Functions
Adds the entity delete (primary + all lookup tables) to this batch. See saveInBatch's doc.
Always throws — LWT cannot be mixed with regular batch statements. Named distinctly (not saveIfNotExists) for the same shadowing reason as saveInBatch/deleteInBatch: a same-named guard here would never actually be reachable, since repo.saveIfNotExists(...) would always resolve to the repository's own real, immediately-executing member instead — defeating the guard silently rather than enforcing it.
Adds the entity save (primary + BATCH lookups) to this batch. Uses the suspend prepare path (see class doc, ISS-059 / GH #60) so a prepared-statement cache miss never blocks the calling coroutine's dispatcher thread.
Same as saveInBatch, but returns the entity that will be persisted when this batch commits — with any @GeneratedUuid/@CreatedAt/@UpdatedAt values already resolved (they are computed at collection time by BatchEngine.injectTimestamps, not deferred to commit time), instead of discarding that copy the way saveInBatch does. See KandraRepository.saveAndGet/KandraSuspendRepository.saveAndGet's doc for the underlying gap this closes for standalone save(). Note this does not inject an initial @Version value the way the standalone save()/saveAndGet() path does.