class CouchDB::Database

Overview

Public facade for CouchDB database operations.

Database auto-detects the appropriate adapter from the location string and delegates all operations to it. It implements the Adapter interface so it can be passed anywhere an adapter is accepted.

db = CouchDB::Database.new("notes.db")                            # local SQLite
db = CouchDB::Database.new(":memory:")                            # in-memory SQLite (testing)
db = CouchDB::Database.new("http://admin:pw@localhost:5984/mydb") # remote CouchDB
adapter = CouchDB::Adapter::HTTP.bearer("https://db.example.com/mydb", token: "secret")
db = CouchDB::Database.new(adapter) # any `Adapter` instance → used directly (no auto-detection)

Included Modules

Direct Known Subclasses

Defined in:

couchdb/database.cr

Constant Summary

QUERY_BATCH_SIZE = 1000

Constructors

Class Method Summary

Instance Method Summary

Instance methods inherited from module CouchDB::Adapter

all_docs(include_docs : Bool, limit : Int32 | Nil, skip : Int32, startkey : String | Nil, endkey : String | Nil) : NamedTuple(total_rows: Int64, offset: Int32, rows: Array(JSON::Any)) all_docs, bulk_docs(docs : Array(Document), new_edits : Bool) : Array(NamedTuple(id: String, rev: String, ok: Bool)) bulk_docs, bulk_get(id_revs : Array(NamedTuple(id: String, rev: String))) : Array(Document) bulk_get, changes(since : String, limit : Int32 | Nil, include_docs : Bool) : NamedTuple(last_seq: String, results: Array(JSON::Any)) changes, changes_feed(since : String, heartbeat : Int32, include_docs : Bool, &block : JSON::Any -> _) changes_feed, close close, delete_attachment(id : String, attname : String, rev : String) : NamedTuple(ok: Bool, id: String, rev: String) delete_attachment, get(id : String) : Document get, get_attachment(id : String, attname : String) : NamedTuple(data: Bytes, content_type: String) get_attachment, get_local(id : String) : Document get_local, info : NamedTuple(db_name: String, doc_count: Int64, update_seq: Int64) info, put(doc : Document) : NamedTuple(ok: Bool, id: String, rev: String) put, put_attachment(id : String, attname : String, rev : String, data : Bytes, content_type : String) : NamedTuple(ok: Bool, id: String, rev: String) put_attachment, put_local(doc : Document) : NamedTuple(ok: Bool, id: String, rev: String) put_local, remove(id : String, rev : String) : NamedTuple(ok: Bool) remove, revs_diff(id_revs : Hash(String, Array(String))) : Hash(String, NamedTuple(missing: Array(String))) revs_diff

Constructor Detail

def self.new(location : String) #

Creates a new database, auto-selecting the adapter based on location.


[View source]
def self.new(adapter : Adapter) #

Accepts a pre-built adapter directly. Use this to inject a custom adapter implementation or a pre-configured Adapter::HTTP (e.g. with #bearer_token= or TLS already set).

adapter = CouchDB::Adapter::HTTP.new("https://db.example.com/mydb")
adapter.bearer_token = "secret"
db = CouchDB::Database.new(adapter)

[View source]

Class Method Detail

def self.local_replica(local_path : String, remote_url : String, heartbeat : Int32 = 2000, write_upstream : Bool = false, sync_initial_backoff : Time::Span = LocalReplica::SYNC_INITIAL_BACKOFF, sync_max_backoff : Time::Span = LocalReplica::SYNC_MAX_BACKOFF) : LocalReplica #

Creates a LocalReplica: a local SQLite database that continuously syncs bidirectionally with the remote CouchDB at remote_url.

All reads go to the local store. Writes go to local by default; pass write_upstream: true to write to the remote instead and block until the change replicates locally.

Checkpoints are stored on the remote only, so deleting and recreating the local file resumes from where replication left off.

db = CouchDB::Database.local_replica("notes.db",
  "https://user:pass@mycouch.example.com/notes")
db.on_sync_error { |dir, ex| Log.error { "#{dir}: #{ex.message}" } }
db.close # stops background sync

[View source]

Instance Method Detail

def adapter : Adapter #

Returns the underlying adapter (SQLite or HTTP).


[View source]
def all_docs(include_docs : Bool = false, limit : Int32 | Nil = nil, skip : Int32 = 0, startkey : String | Nil = nil, endkey : String | Nil = nil) : NamedTuple(total_rows: Int64, offset: Int32, rows: Array(JSON::Any)) #

Lists all non-deleted documents. Pass include_docs: true for full bodies, limit/skip for pagination, startkey/endkey for range queries (inclusive).


[View source]
def all_docs(as klass : T.class, limit : Int32 | Nil = nil, skip : Int32 = 0, startkey : String | Nil = nil, endkey : String | Nil = nil) : NamedTuple(total_rows: Int64, offset: Int32, rows: Array(T)) forall T #

Typed overload — deserializes each row's doc into klass. Implies include_docs: true; all other parameters work identically.

Example:

result = db.all_docs(as: Note, limit: 50)
result[:rows] # => Array(Note)
result[:total_rows]

[View source]
def bearer_token=(token : String) #

Sets Bearer token authentication on the HTTP adapter, replacing any URL credentials. No-op for SQLite.


[View source]
def bulk_docs(docs : Array(Document), new_edits : Bool = true) : Array(NamedTuple(id: String, rev: String, ok: Bool)) #

Batch write. Pass new_edits: false for the replication write path (bypasses conflict detection and stores revisions exactly as supplied).


[View source]
def bulk_get(id_revs : Array(NamedTuple(id: String, rev: String))) : Array(Document) #

Fetches specific {id, rev} pairs in bulk. Used internally by the replication engine.


[View source]
def changes(since : String = "0", limit : Int32 | Nil = nil, include_docs : Bool = false) : NamedTuple(last_seq: String, results: Array(JSON::Any)) #

Returns changes since a sequence number. Use "0" to fetch all changes. Pass include_docs: true to embed full document bodies in each change entry.


[View source]
def changes_feed(since : String = "0", heartbeat : Int32 = 1000, include_docs : Bool = false, & : JSON::Any -> _) #

Streams changes continuously, yielding each change entry to the block. Call break from the block to stop. since defaults to "0" (all changes). heartbeat controls polling interval (SQLite) or CouchDB heartbeat (HTTP) in ms.


[View source]
def close #

Releases the underlying adapter's connection or database handle.


[View source]
def delete_attachment(id : String, attname : String, rev : String) : NamedTuple(ok: Bool, id: String, rev: String) #

Removes an attachment, creating a new document revision. Raises Conflict on rev mismatch.


[View source]
def find(selector : JSON::Any, fields : Array(String) | Nil = nil, sort : Array(JSON::Any) | Nil = nil, limit : Int32 | Nil = nil, skip : Int32 = 0) : NamedTuple(docs: Array(JSON::Any), warning: String | Nil) #

Runs an in-memory Mango selector query over all documents, PouchDB/CouchDB-style.

selector is a JSON::Any hash describing match conditions (see README for full operator reference). fields limits the keys returned per doc. sort is an array of bare field name strings (ascending) or single-key hashes with "asc"/"desc" values. limit and skip control pagination.

result = db.find(JSON.parse(%({"type": "note"})))
result[:docs].each { |doc| puts doc["title"] }
result[:warning] # => always present (full scan, no index)

[View source]
def get(id : String, as klass : T.class) : T forall T #

Typed overload — deserializes the stored document into a specific Document subclass.

Example:

note = db.get("note-1", as: MyNote)
note.title # strongly-typed field

[View source]
def get(id : String) : Document #

Fetches the winning revision of a document. Raises NotFound if absent or deleted.


[View source]
def get_attachment(id : String, attname : String) : NamedTuple(data: Bytes, content_type: String) #

Returns raw bytes and content-type for an attachment. Raises NotFound if absent.


[View source]
def get_local(id : String) : Document #

Reads a _local/ checkpoint document. Used internally by replication.


[View source]
def info : NamedTuple(db_name: String, doc_count: Int64, update_seq: Int64) #

Returns {db_name:, doc_count:, update_seq:} for the underlying database.


[View source]
def on_conflict(&block : Document, Document -> Document | Nil) #

Register a block invoked when #put raises Conflict. Return a Document to retry (system sets the correct rev automatically). Return nil to re-raise. Raise to propagate a custom exception.


[View source]
def on_remove_conflict(&block : Document, String -> Bool | Nil) #

Register a block invoked when #remove raises Conflict. Return true to retry the delete with the current rev. Return nil to re-raise.


[View source]
def on_request(&block : ::HTTP::Request -> Nil) #

Registers a before-request interceptor on the HTTP adapter. No-op for SQLite.


[View source]
def on_response(&block : ::HTTP::Client::Response -> ::HTTP::Client::Response) #

Registers an after-response interceptor on the HTTP adapter. No-op for SQLite.


[View source]
def put(doc : Document) : NamedTuple(ok: Bool, id: String, rev: String) #

Creates or updates a document. Pass doc.rev for updates. Raises Conflict on a rev mismatch. Returns {ok: true, id:, rev:}.


[View source]
def put_attachment(id : String, attname : String, rev : String, data : Bytes, content_type : String) : NamedTuple(ok: Bool, id: String, rev: String) #

Stores binary data as an attachment, creating a new document revision. Raises Conflict on rev mismatch.


[View source]
def put_local(doc : Document) : NamedTuple(ok: Bool, id: String, rev: String) #

Writes a _local/ checkpoint document. Used internally by replication.


[View source]
def query(key : JSON::Any | Nil = nil, keys : Array(JSON::Any) | Nil = nil, startkey : JSON::Any | Nil = nil, endkey : JSON::Any | Nil = nil, limit : Int32 | Nil = nil, skip : Int32 = 0, descending : Bool = false, include_docs : Bool = false, reduce : String | Nil = nil, group : Bool = false, group_level : Int32 | Nil = nil, &map : Document, Proc(JSON::Any | Nil, JSON::Any | Nil, Nil) -> _) : NamedTuple(total_rows: Int64, offset: Int32, rows: Array(JSON::Any)) #

Runs an in-memory map/reduce query over all documents, PouchDB-style.

The map block receives each document and an emit proc; call emit.call(key, value) to include a row in the result set. Rows are sorted by key using CouchDB collation order.

result = db.query do |doc, emit|
  emit.call(JSON::Any.new(doc["type"].as_s), JSON::Any.new(1_i64))
end
result[:rows].each { |r| puts "#{r["key"]}#{r["value"]}" }

[View source]
def remove(id : String, rev : String) : NamedTuple(ok: Bool) #

Soft-deletes a document by writing a tombstone. Raises Conflict on rev mismatch.


[View source]
def replicate_from(source : Database, doc_ids : Array(String) | Nil = nil, selector : JSON::Any | Nil = nil, filter : Proc(Document, Bool) | Nil = nil) : Replication::Session #

Pulls changes from source into this database. Returns a Replication::Session.

Pass doc_ids to replicate only specific documents by ID. Pass selector (a Mango selector as JSON::Any) to filter by document content. Pass filter for an ad-hoc Proc(Document, Bool) predicate.


[View source]
def replicate_to(target : Database, doc_ids : Array(String) | Nil = nil, selector : JSON::Any | Nil = nil, filter : Proc(Document, Bool) | Nil = nil) : Replication::Session #

Pushes local changes to target. Returns a Replication::Session with transfer stats.

Pass doc_ids to replicate only specific documents by ID. Pass selector (a Mango selector as JSON::Any) to filter by document content. Pass filter for an ad-hoc Proc(Document, Bool) predicate. selector and filter are mutually exclusive; filter takes precedence.


[View source]
def revs_diff(id_revs : Hash(String, Array(String))) : Hash(String, NamedTuple(missing: Array(String))) #

Returns missing revisions for the given id → [revs] map. Used internally by the replication engine.


[View source]
def sync(remote : Database, doc_ids : Array(String) | Nil = nil, selector : JSON::Any | Nil = nil, filter : Proc(Document, Bool) | Nil = nil) #

Bidirectional sync: pulls from remote then pushes to remote. Equivalent to replicate_from(remote) followed by replicate_to(remote). Accepts the same doc_ids, selector, and filter options as #replicate_to/#replicate_from.


[View source]
def tls=(ctx : OpenSSL::SSL::Context::Client) #

Assigns a TLS client context used for mutual TLS (mTLS) on HTTPS connections. No-op when the underlying adapter is SQLite.

ctx = OpenSSL::SSL::Context::Client.new
ctx.certificate_file = "client.crt"
ctx.private_key_file = "client.key"
db.tls = ctx

[View source]