1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 package driver 14 15 import ( 16 "context" 17 "encoding/json" 18 "io" 19 "time" 20 ) 21 22 // Options represents a collection of arbitrary client or query options. 23 // 24 // An implementation should also implement the [fmt.Stringer] interface for the 25 // sake of display when used by [github.com/go-kivik/kivik/v4/mockdb]. 26 type Options interface { 27 // Apply applies the option to target, if target is of the expected type. 28 // Unexpected/recognized target types should be ignored. 29 Apply(target interface{}) 30 } 31 32 // Driver is the interface that must be implemented by a database driver. 33 type Driver interface { 34 // NewClient returns a connection handle to the database. The name is in a 35 // driver-specific format. 36 NewClient(name string, options Options) (Client, error) 37 } 38 39 // Version represents a server version response. 40 type Version struct { 41 // Version is the version number reported by the server or backend. 42 Version string 43 // Vendor is the vendor string reported by the server or backend. 44 Vendor string 45 // Features is a list of enabled, optional features. This was added in 46 // CouchDB 2.1.0, and can be expected to be empty for older versions. 47 Features []string 48 // RawResponse is the raw response body as returned by the server. 49 RawResponse json.RawMessage 50 } 51 52 // Client is a connection to a database server. 53 type Client interface { 54 // Version returns the server implementation's details. 55 Version(ctx context.Context) (*Version, error) 56 // AllDBs returns a list of all existing database names. 57 AllDBs(ctx context.Context, options Options) ([]string, error) 58 // DBExists returns true if the database exists. 59 DBExists(ctx context.Context, dbName string, options Options) (bool, error) 60 // CreateDB creates the requested database. 61 CreateDB(ctx context.Context, dbName string, options Options) error 62 // DestroyDB deletes the requested database. 63 DestroyDB(ctx context.Context, dbName string, options Options) error 64 // DB returns a handle to the requested database. 65 DB(dbName string, options Options) (DB, error) 66 } 67 68 // DBsStatser is an optional interface that a [DB] may implement, added to 69 // support CouchDB 2.2.0's /_dbs_info endpoint. If this is not supported, or 70 // if this method returns status 404, Kivik will fall back to calling the method 71 // of issuing a GET /{db} for each database requested. 72 type DBsStatser interface { 73 // DBsStats returns database statistical information for each database 74 // named in dbNames. The returned values should be in the same order as 75 // the requested database names, and any missing databases should return 76 // a nil *DBStats value. 77 DBsStats(ctx context.Context, dbNames []string) ([]*DBStats, error) 78 } 79 80 // AllDBsStatser is an optional interface that a [DB] may implement, added to 81 // support CouchDB 3.2's GET /_dbs_info endpoint. If this is not supported, or 82 // if this method returns status 404 or 405, Kivik will fall back to using 83 // _all_dbs + the [DBStatser] interface (or its respective emulation). 84 type AllDBsStatser interface { 85 // AllDBsStats returns database statistical information for each database 86 // in the CouchDB instance. See the [CouchDB documentation] for supported 87 // options. 88 // 89 // [CouchDB documentation]: https://docs.couchdb.org/en/stable/api/server/common.html#get--_dbs_info 90 AllDBsStats(ctx context.Context, options Options) ([]*DBStats, error) 91 } 92 93 // Replication represents a _replicator document. 94 type Replication interface { 95 // The following methods are called just once, when the Replication is first 96 // returned from [ClientReplicator.Replicate] or 97 // [ClientReplicator.GetReplications]. 98 ReplicationID() string 99 Source() string 100 Target() string 101 102 // The following methods return values may be updated by calls to [Update]. 103 StartTime() time.Time 104 EndTime() time.Time 105 State() string 106 Err() error 107 108 // These methods may be triggered by user actions. 109 110 // Delete deletes a replication, which cancels it if it is running. 111 Delete(context.Context) error 112 // Update fetches the latest replication state from the server. 113 Update(context.Context, *ReplicationInfo) error 114 } 115 116 // ReplicationInfo represents a snapshot state of a replication, as provided 117 // by the _active_tasks endpoint. 118 type ReplicationInfo struct { 119 DocWriteFailures int64 120 DocsRead int64 121 DocsWritten int64 122 Progress float64 123 } 124 125 // ClientReplicator is an optional interface that may be implemented by a [Client] 126 // that supports replication between two database. 127 type ClientReplicator interface { 128 // Replicate initiates a replication. 129 Replicate(ctx context.Context, targetDSN, sourceDSN string, options Options) (Replication, error) 130 // GetReplications returns a list of replications (i.e. all docs in the 131 // _replicator database) 132 GetReplications(ctx context.Context, options Options) ([]Replication, error) 133 } 134 135 // DBStats contains database statistics. 136 type DBStats struct { 137 Name string `json:"db_name"` 138 CompactRunning bool `json:"compact_running"` 139 DocCount int64 `json:"doc_count"` 140 DeletedCount int64 `json:"doc_del_count"` 141 UpdateSeq string `json:"update_seq"` 142 DiskSize int64 `json:"disk_size"` 143 ActiveSize int64 `json:"data_size"` 144 ExternalSize int64 `json:"-"` 145 Cluster *ClusterStats `json:"cluster,omitempty"` 146 RawResponse json.RawMessage `json:"-"` 147 } 148 149 // ClusterStats contains the cluster configuration for the database. 150 type ClusterStats struct { 151 Replicas int `json:"n"` 152 Shards int `json:"q"` 153 ReadQuorum int `json:"r"` 154 WriteQuorum int `json:"w"` 155 } 156 157 // Members represents the members of a database security document. 158 type Members struct { 159 Names []string `json:"names,omitempty"` 160 Roles []string `json:"roles,omitempty"` 161 } 162 163 // Security represents a database security document. 164 type Security struct { 165 Admins Members `json:"admins,omitempty"` 166 Members Members `json:"members,omitempty"` 167 Cloudant map[string][]string `json:"cloudant,omitempty"` 168 CouchdbAuthOnly *bool `json:"couchdb_auth_only,omitempty"` 169 } 170 171 // MarshalJSON satisfies the json.Marshaler interface. 172 func (s Security) MarshalJSON() ([]byte, error) { 173 var v struct { 174 Admins *Members `json:"admins,omitempty"` 175 Members *Members `json:"members,omitempty"` 176 Cloudant map[string][]string `json:"cloudant,omitempty"` 177 CouchdbAuthOnly *bool `json:"couchdb_auth_only,omitempty"` 178 } 179 if len(s.Admins.Names) > 0 || len(s.Admins.Roles) > 0 { 180 v.Admins = &s.Admins 181 } 182 if len(s.Members.Names) > 0 || len(s.Members.Roles) > 0 { 183 v.Members = &s.Members 184 } 185 if len(s.Cloudant) > 0 { 186 v.Cloudant = s.Cloudant 187 } 188 if s.CouchdbAuthOnly != nil { 189 v.CouchdbAuthOnly = s.CouchdbAuthOnly 190 } 191 return json.Marshal(v) 192 } 193 194 // DB is a database handle. 195 type DB interface { 196 // AllDocs returns all of the documents in the database, subject to the 197 // options provided. 198 AllDocs(ctx context.Context, options Options) (Rows, error) 199 // Put writes the document in the database. 200 Put(ctx context.Context, docID string, doc interface{}, options Options) (rev string, err error) 201 // Get fetches the requested document from the database. 202 Get(ctx context.Context, docID string, options Options) (*Document, error) 203 // Delete marks the specified document as deleted. 204 Delete(ctx context.Context, docID string, options Options) (newRev string, err error) 205 // Stats returns database statistics. 206 Stats(ctx context.Context) (*DBStats, error) 207 // Compact initiates compaction of the database. 208 Compact(ctx context.Context) error 209 // CompactView initiates compaction of the view. 210 CompactView(ctx context.Context, ddocID string) error 211 // ViewCleanup cleans up stale view files. 212 ViewCleanup(ctx context.Context) error 213 // Changes returns an iterator for the changes feed. In continuous mode, 214 // the iterator will continue indefinitely, until [Changes.Close] is called. 215 Changes(ctx context.Context, options Options) (Changes, error) 216 // PutAttachment uploads an attachment to the specified document, returning 217 // the new revision. 218 PutAttachment(ctx context.Context, docID string, att *Attachment, options Options) (newRev string, err error) 219 // GetAttachment fetches an attachment for the associated document ID. 220 GetAttachment(ctx context.Context, docID, filename string, options Options) (*Attachment, error) 221 // DeleteAttachment deletes an attachment from a document, returning the 222 // document's new revision. 223 DeleteAttachment(ctx context.Context, docID, filename string, options Options) (newRev string, err error) 224 // Query performs a query against a view, subject to the options provided. 225 // ddoc will be the design doc name without the '_design/' previx. 226 // view will be the view name without the '_view/' prefix. 227 Query(ctx context.Context, ddoc, view string, options Options) (Rows, error) 228 // Close is called to clean up any resources used by the database. 229 Close() error 230 } 231 232 // DocCreator is an optional interface that extends a [DB] to support the 233 // creation of new documents. If not implemented, [DB.Put] will be used to 234 // emulate the functionality, with missing document IDs generated as V4 UUIDs. 235 type DocCreator interface { 236 // CreateDoc creates a new doc, with a server-generated ID. 237 CreateDoc(ctx context.Context, doc interface{}, options Options) (docID, rev string, err error) 238 } 239 240 // OpenRever is an optional interface that extends a [DB] to support the open_revs 241 // option of the CouchDB get document endpoint. It is used by the replicator. 242 // Drivers that don't support this endpoint may not be able to replicate as 243 // efficiently, or at all. 244 type OpenRever interface { 245 // OpenRevs fetches the requested document revisions from the database. 246 // revs may be a list of revisions, or a single item with value "all" to 247 // request all open revs. 248 OpenRevs(ctx context.Context, docID string, revs []string, options Options) (Rows, error) 249 } 250 251 // SecurityDB is an optional interface that extends a [DB], for backends which 252 // support security documents. 253 type SecurityDB interface { 254 // Security returns the database's security document. 255 Security(ctx context.Context) (*Security, error) 256 // SetSecurity sets the database's security document. 257 SetSecurity(ctx context.Context, security *Security) error 258 } 259 260 // Document represents a single document returned by [DB.Get]. 261 type Document struct { 262 // Rev is the revision number returned 263 Rev string 264 265 // Body returns the document JSON. 266 Body io.ReadCloser 267 268 // Attachments will be nil except when attachments=true. 269 Attachments Attachments 270 } 271 272 // Attachments is an iterator over the attachments included in a document when 273 // [DB.Get] is called with `attachments=true`. 274 type Attachments interface { 275 // Next is called to populate att with the next attachment in the result 276 // set. 277 // 278 // Next should return [io.EOF] when there are no more attachments. 279 Next(att *Attachment) error 280 281 // Close closes the Attachments iterator. 282 Close() error 283 } 284 285 // Purger is an optional interface which may be implemented by a [DB] to support 286 // document purging. 287 type Purger interface { 288 // Purge permanently removes the references to deleted documents from the 289 // database. 290 Purge(ctx context.Context, docRevMap map[string][]string) (*PurgeResult, error) 291 } 292 293 // PurgeResult is the result of a purge request. 294 type PurgeResult struct { 295 Seq int64 `json:"purge_seq"` 296 Purged map[string][]string `json:"purged"` 297 } 298 299 // BulkDocer is an optional interface which may be implemented by a [DB] to 300 // support bulk insert/update operations. For any driver that does not support 301 // the BulkDocer interface, the [DB.Put] or [DB.CreateDoc] methods will be 302 // called for each document to emulate the same functionality, with options 303 // passed through unaltered. 304 type BulkDocer interface { 305 // BulkDocs alls bulk create, update and/or delete operations. It returns an 306 // iterator over the results. 307 BulkDocs(ctx context.Context, docs []interface{}, options Options) ([]BulkResult, error) 308 } 309 310 // Finder is an optional interface which may be implemented by a [DB]. It 311 // provides access to the MongoDB-style query interface added in CouchDB 2. 312 type Finder interface { 313 // Find executes a query using the new /_find interface. query is always 314 // converted to a [encoding/json.RawMessage] value before passing it to the 315 // driver. The type remains `interface{}` for backward compatibility, but 316 // will change with Kivik 5.x. See [issue #1015] for details. 317 // 318 // [issue #1014]: https://github.com/go-kivik/kivik/issues/1015 319 Find(ctx context.Context, query interface{}, options Options) (Rows, error) 320 // CreateIndex creates an index if it doesn't already exist. If the index 321 // already exists, it should do nothing. ddoc and name may be empty, in 322 // which case they should be provided by the backend. If index is a string, 323 // []byte, or [encoding/json.RawMessage], it should be treated as a raw 324 // JSON payload. Any other type should be marshaled to JSON. 325 CreateIndex(ctx context.Context, ddoc, name string, index interface{}, options Options) error 326 // GetIndexes returns a list of all indexes in the database. 327 GetIndexes(ctx context.Context, options Options) ([]Index, error) 328 // Delete deletes the requested index. 329 DeleteIndex(ctx context.Context, ddoc, name string, options Options) error 330 // Explain returns the query plan for a given query. Explain takes the same 331 // arguments as [Finder.Find]. 332 Explain(ctx context.Context, query interface{}, options Options) (*QueryPlan, error) 333 } 334 335 // QueryPlan is the response of an Explain query. 336 type QueryPlan struct { 337 DBName string `json:"dbname"` 338 Index map[string]interface{} `json:"index"` 339 Selector map[string]interface{} `json:"selector"` 340 Options map[string]interface{} `json:"opts"` 341 Limit int64 `json:"limit"` 342 Skip int64 `json:"skip"` 343 344 // Fields is the list of fields to be returned in the result set, or 345 // an empty list if all fields are to be returned. 346 Fields []interface{} `json:"fields"` 347 Range map[string]interface{} `json:"range"` 348 } 349 350 // Index is a MonboDB-style index definition. 351 type Index struct { 352 DesignDoc string `json:"ddoc,omitempty"` 353 Name string `json:"name"` 354 Type string `json:"type"` 355 Definition interface{} `json:"def"` 356 } 357 358 // Attachment represents a file attachment to a document. 359 type Attachment struct { 360 Filename string `json:"-"` 361 ContentType string `json:"content_type"` 362 Stub bool `json:"stub"` 363 Follows bool `json:"follows"` 364 Content io.ReadCloser `json:"-"` 365 Size int64 `json:"length"` 366 ContentEncoding string `json:"encoding"` 367 EncodedLength int64 `json:"encoded_length"` 368 RevPos int64 `json:"revpos"` 369 Digest string `json:"digest"` 370 } 371 372 // AttachmentMetaGetter is an optional interface which may be implemented by a 373 // [DB]. When not implemented, [DB.GetAttachment] will be used to emulate the 374 // functionality. 375 type AttachmentMetaGetter interface { 376 // GetAttachmentMeta returns meta information about an attachment. 377 GetAttachmentMeta(ctx context.Context, docID, filename string, options Options) (*Attachment, error) 378 } 379 380 // BulkResult is the result of a single doc update in a BulkDocs request. 381 type BulkResult struct { 382 ID string `json:"id"` 383 Rev string `json:"rev"` 384 Error error 385 } 386 387 // RevGetter is an optional interface that may be implemented by a [DB]. If not 388 // implemented, [DB.Get] will be used to emulate the functionality, with options 389 // passed through unaltered. 390 type RevGetter interface { 391 // GetRev returns the document revision of the requested document. GetRev 392 // should accept the same options as [DB.Get]. 393 GetRev(ctx context.Context, docID string, options Options) (rev string, err error) 394 } 395 396 // Flusher is an optional interface that may be implemented by a [DB] that can 397 // force a flush of the database backend file(s) to disk or other permanent 398 // storage. 399 type Flusher interface { 400 // Flush requests a flush of disk cache to disk or other permanent storage. 401 // 402 // See the [CouchDB documentation]. 403 // 404 // [CouchDB documentation]: http://docs.couchdb.org/en/2.0.0/api/database/compact.html#db-ensure-full-commit 405 Flush(ctx context.Context) error 406 } 407 408 // Copier is an optional interface that may be implemented by a [DB]. 409 // 410 // If a [DB] does not implement Copier, the functionality will be emulated by 411 // calling [DB.Get] followed by [DB.Put], with options passed through unaltered, 412 // except that the 'rev' option will be removed for the [DB.Put] call. 413 type Copier interface { 414 Copy(ctx context.Context, targetID, sourceID string, options Options) (targetRev string, err error) 415 } 416 417 // DesignDocer is an optional interface that may be implemented by a [DB]. 418 type DesignDocer interface { 419 // DesignDocs returns all of the design documents in the database, subject 420 // to the options provided. 421 DesignDocs(ctx context.Context, options Options) (Rows, error) 422 } 423 424 // LocalDocer is an optional interface that may be implemented by a [DB]. 425 type LocalDocer interface { 426 // LocalDocs returns all of the local documents in the database, subject to 427 // the options provided. 428 LocalDocs(ctx context.Context, options Options) (Rows, error) 429 } 430 431 // Pinger is an optional interface that may be implemented by a [Client]. When 432 // not implemented, Kivik will call [Client.Version] instead to emulate the 433 // functionality. 434 type Pinger interface { 435 // Ping returns true if the database is online and available for requests. 436 Ping(ctx context.Context) (bool, error) 437 } 438 439 // ClusterMembership contains the list of known nodes, and cluster nodes, as 440 // returned by the [_membership endpoint]. 441 // 442 // [_membership endpoint]: https://docs.couchdb.org/en/latest/api/server/common.html#get--_membership 443 type ClusterMembership struct { 444 AllNodes []string `json:"all_nodes"` 445 ClusterNodes []string `json:"cluster_nodes"` 446 } 447 448 // Cluster is an optional interface that may be implemented by a [Client] to 449 // support CouchDB cluster configuration operations. 450 type Cluster interface { 451 // ClusterStatus returns the current cluster status. 452 ClusterStatus(ctx context.Context, options Options) (string, error) 453 // ClusterSetup performs the action specified by action. 454 ClusterSetup(ctx context.Context, action interface{}) error 455 // Membership returns a list of all known nodes, and all nodes configured as 456 // part of the cluster. 457 Membership(ctx context.Context) (*ClusterMembership, error) 458 } 459 460 // ClientCloser is an optional interface that may be implemented by a [Client] 461 // to clean up resources when a client is no longer needed. 462 type ClientCloser interface { 463 Close() error 464 } 465 466 // RevDiff represents a rev diff for a single document, as returned by 467 // [RevsDiffer.RevsDiff]. 468 type RevDiff struct { 469 Missing []string `json:"missing,omitempty"` 470 PossibleAncestors []string `json:"possible_ancestors,omitempty"` 471 } 472 473 // RevsDiffer is an optional interface that may be implemented by a [DB]. 474 type RevsDiffer interface { 475 // RevsDiff returns a Rows iterator, which should populate the ID and Value 476 // fields, and nothing else. 477 RevsDiff(ctx context.Context, revMap interface{}) (Rows, error) 478 } 479