- Counter#increment now seeds a missing key at the delta (Redis INCR/INCRBY
semantics). Couchbase's native increment seeds at 'initial' and ignores the
delta on creation, so the first INCR returned 0 — a drop-in replacement that
silently undercounts by one is exactly the kind of bug nobody ever finds.
Explicit initial: still overrides. Decrement keeps initial: 0 and its
unsigned clamp-at-zero divergence from Redis is now documented and tested.
- Lock#release requires the CAS handle: passing nil used to delete the lock
document unconditionally, so any caller could unlock a lock it never held.
force_release! is the explicit administrative override.
- Lock#acquire validates a positive ttl — a nil/zero ttl would create a lock
that survives its holder's crash forever.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- database.view now raises SimplyBased::UnsupportedView when a declared view
carries CouchDB-era options the N1QL translation silently ignored before:
:conditions (a JS fragment) and :emit_value, plus CouchDB list functions.
Silently dropping :conditions was the worst of these — a
'view :completed, key: :name, conditions: ...' query returned *unfiltered*
results with no hint anything was wrong. Covered by unsupported_view_spec.
- All column/property names interpolated into N1QL are now routed through
quote_ident, which rejects backticks/backslashes. These names come from the
model DSL rather than user input, but a smuggled backtick must never become
query text. Class names in index predicates go through a proper string
literal quoting helper for the same reason.
- ensure_index: failures are still swallowed (an index build in progress must
not take down queries) but now emit a warning naming the index, because the
silent version left every subsequent query on a primary-index scan with no
trace. The ensured-indexes bookkeeping is mutex-guarded for multithreaded
servers (Puma).
- bulk_load / bulk_destroy now actually batch (collection.get_multi /
remove_multi — one KV round trip). The README already advertised
'bulk (batched KV)' but the code looped one get/remove per id.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Live-run finding: the full suite against Couchbase was 423 examples with 17
failures — all in embedding_spec. The session report claimed embedded specs
were 'excluded by design', but the exclusion filter targeted a :embedding tag
no example carries, so the file ran (and failed) all along.
Three fixes:
- HasManyEmbedded::Property#build wired parent_object before installing the
collection ivar on the parent; parent_object= reaches back into the parent's
collection getter, which re-entered build -> SystemStackError on any access
to embedded documents loaded from the database. Install the ivar first.
- EmbeddedClass.all / .count referenced raw JS views (all_documents /
all_documents_for_count) that the N1QL port silently drops, so .all returned
[] and .count raised NoMethodError. Rebuilt them by enumerating the parent
documents (same work as the old view scan, client-side; an UNNEST query can
replace it later).
- Only reverse lookups (N1QL on a foreign key inside an embedded array) remain
unsupported: tagged :embedded_in_reverse_lookup and excluded in spec_helper
with an explanatory comment, replacing the broken :embedding filter line.
embedding_spec.rb: 16 examples, 0 failures against live Couchbase 8.0.1 CE.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>