github.com/go-kivik/kivik/v4@v4.3.2/kivik.go (about) 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 kivik 14 15 import ( 16 "context" 17 "encoding/json" 18 "fmt" 19 "net/http" 20 "sync" 21 22 "github.com/go-kivik/kivik/v4/driver" 23 internal "github.com/go-kivik/kivik/v4/int/errors" 24 "github.com/go-kivik/kivik/v4/internal/registry" 25 ) 26 27 // Client is a client connection handle to a CouchDB-like server. 28 type Client struct { 29 dsn string 30 driverName string 31 driverClient driver.Client 32 33 closed bool 34 mu sync.Mutex 35 wg sync.WaitGroup 36 } 37 38 // Register makes a database driver available by the provided name. If Register 39 // is called twice with the same name or if driver is nil, it panics. 40 func Register(name string, driver driver.Driver) { 41 registry.Register(name, driver) 42 } 43 44 // New creates a new client object specified by its database driver name 45 // and a driver-specific data source name. 46 // 47 // The use of options is driver-specific, so consult with the documentation for 48 // your driver for supported options. 49 func New(driverName, dataSourceName string, options ...Option) (*Client, error) { 50 driveri := registry.Driver(driverName) 51 if driveri == nil { 52 return nil, &internal.Error{Status: http.StatusBadRequest, Message: fmt.Sprintf("kivik: unknown driver %q (forgotten import?)", driverName)} 53 } 54 client, err := driveri.NewClient(dataSourceName, multiOptions(options)) 55 if err != nil { 56 return nil, err 57 } 58 return &Client{ 59 dsn: dataSourceName, 60 driverName: driverName, 61 driverClient: client, 62 }, nil 63 } 64 65 // Driver returns the name of the driver string used to connect this client. 66 func (c *Client) Driver() string { 67 return c.driverName 68 } 69 70 // DSN returns the data source name used to connect this client. 71 func (c *Client) DSN() string { 72 return c.dsn 73 } 74 75 // ServerVersion represents a server version response. 76 type ServerVersion struct { 77 // Version is the version number reported by the server or backend. 78 Version string 79 // Vendor is the vendor string reported by the server or backend. 80 Vendor string 81 // Features is a list of enabled, optional features. This was added in 82 // CouchDB 2.1.0, and can be expected to be empty for older versions. 83 Features []string 84 // RawResponse is the raw response body returned by the server, useful if 85 // you need additional backend-specific information. Refer to the 86 // [CouchDB documentation] for format details. 87 // 88 // [CouchDB documentation]: http://docs.couchdb.org/en/2.0.0/api/server/common.html#get 89 RawResponse json.RawMessage 90 } 91 92 func (c *Client) startQuery() (end func(), _ error) { 93 c.mu.Lock() 94 defer c.mu.Unlock() 95 if c.closed { 96 return nil, ErrClientClosed 97 } 98 var once sync.Once 99 c.wg.Add(1) 100 return func() { 101 once.Do(func() { 102 c.mu.Lock() 103 c.wg.Done() 104 c.mu.Unlock() 105 }) 106 }, nil 107 } 108 109 // Version returns version and vendor info about the backend. 110 func (c *Client) Version(ctx context.Context) (*ServerVersion, error) { 111 endQuery, err := c.startQuery() 112 if err != nil { 113 return nil, err 114 } 115 defer endQuery() 116 ver, err := c.driverClient.Version(ctx) 117 if err != nil { 118 return nil, err 119 } 120 v := &ServerVersion{} 121 *v = ServerVersion(*ver) 122 return v, nil 123 } 124 125 // DB returns a handle to the requested database. Any errors encountered during 126 // initiation of the DB object is deferred until the first method call, or may 127 // be checked directly with [DB.Err]. 128 func (c *Client) DB(dbName string, options ...Option) *DB { 129 db, err := c.driverClient.DB(dbName, multiOptions(options)) 130 return &DB{ 131 client: c, 132 name: dbName, 133 driverDB: db, 134 err: err, 135 } 136 } 137 138 // AllDBs returns a list of all databases. 139 func (c *Client) AllDBs(ctx context.Context, options ...Option) ([]string, error) { 140 endQuery, err := c.startQuery() 141 if err != nil { 142 return nil, err 143 } 144 defer endQuery() 145 return c.driverClient.AllDBs(ctx, multiOptions(options)) 146 } 147 148 // DBExists returns true if the specified database exists. 149 func (c *Client) DBExists(ctx context.Context, dbName string, options ...Option) (bool, error) { 150 endQuery, err := c.startQuery() 151 if err != nil { 152 return false, err 153 } 154 defer endQuery() 155 return c.driverClient.DBExists(ctx, dbName, multiOptions(options)) 156 } 157 158 // CreateDB creates a DB of the requested name. 159 func (c *Client) CreateDB(ctx context.Context, dbName string, options ...Option) error { 160 endQuery, err := c.startQuery() 161 if err != nil { 162 return err 163 } 164 defer endQuery() 165 return c.driverClient.CreateDB(ctx, dbName, multiOptions(options)) 166 } 167 168 // DestroyDB deletes the requested DB. 169 func (c *Client) DestroyDB(ctx context.Context, dbName string, options ...Option) error { 170 endQuery, err := c.startQuery() 171 if err != nil { 172 return err 173 } 174 defer endQuery() 175 return c.driverClient.DestroyDB(ctx, dbName, multiOptions(options)) 176 } 177 178 func missingArg(arg string) error { 179 return &internal.Error{Status: http.StatusBadRequest, Message: fmt.Sprintf("kivik: %s required", arg)} 180 } 181 182 // DBsStats returns database statistics about one or more databases. 183 func (c *Client) DBsStats(ctx context.Context, dbnames []string) ([]*DBStats, error) { 184 endQuery, err := c.startQuery() 185 if err != nil { 186 return nil, err 187 } 188 defer endQuery() 189 dbstats, err := c.nativeDBsStats(ctx, dbnames) 190 switch HTTPStatus(err) { 191 case http.StatusNotFound, http.StatusNotImplemented: 192 return c.fallbackDBsStats(ctx, dbnames) 193 } 194 return dbstats, err 195 } 196 197 func (c *Client) fallbackDBsStats(ctx context.Context, dbnames []string) ([]*DBStats, error) { 198 dbstats := make([]*DBStats, len(dbnames)) 199 for i, dbname := range dbnames { 200 stat, err := c.DB(dbname).Stats(ctx) 201 switch { 202 case HTTPStatus(err) == http.StatusNotFound: 203 continue 204 case err != nil: 205 return nil, err 206 default: 207 dbstats[i] = stat 208 } 209 } 210 return dbstats, nil 211 } 212 213 func (c *Client) nativeDBsStats(ctx context.Context, dbnames []string) ([]*DBStats, error) { 214 statser, ok := c.driverClient.(driver.DBsStatser) 215 if !ok { 216 return nil, &internal.Error{Status: http.StatusNotImplemented, Message: "kivik: not supported by driver"} 217 } 218 stats, err := statser.DBsStats(ctx, dbnames) 219 if err != nil { 220 return nil, err 221 } 222 dbstats := make([]*DBStats, len(stats)) 223 for i, stat := range stats { 224 if stat != nil { 225 dbstats[i] = driverStats2kivikStats(stat) 226 } 227 } 228 return dbstats, nil 229 } 230 231 // AllDBsStats returns database statistics for all databases. If the driver does 232 // not natively support this operation, it will be emulated by effectively 233 // calling [Client.AllDBs] followed by [DB.DBsStats]. 234 // 235 // See the [CouchDB documentation] for accepted options. 236 // 237 // [CouchDB documentation]: https://docs.couchdb.org/en/stable/api/server/common.html#get--_dbs_info 238 func (c *Client) AllDBsStats(ctx context.Context, options ...Option) ([]*DBStats, error) { 239 endQuery, err := c.startQuery() 240 if err != nil { 241 return nil, err 242 } 243 defer endQuery() 244 dbstats, err := c.nativeAllDBsStats(ctx, options...) 245 switch HTTPStatus(err) { 246 case http.StatusMethodNotAllowed, http.StatusNotImplemented: 247 return c.fallbackAllDBsStats(ctx, options...) 248 } 249 return dbstats, err 250 } 251 252 func (c *Client) nativeAllDBsStats(ctx context.Context, options ...Option) ([]*DBStats, error) { 253 statser, ok := c.driverClient.(driver.AllDBsStatser) 254 if !ok { 255 return nil, &internal.Error{Status: http.StatusNotImplemented, Message: "kivik: not supported by driver"} 256 } 257 stats, err := statser.AllDBsStats(ctx, multiOptions(options)) 258 if err != nil { 259 return nil, err 260 } 261 dbstats := make([]*DBStats, len(stats)) 262 for i, stat := range stats { 263 dbstats[i] = driverStats2kivikStats(stat) 264 } 265 return dbstats, nil 266 } 267 268 func (c *Client) fallbackAllDBsStats(ctx context.Context, options ...Option) ([]*DBStats, error) { 269 dbs, err := c.AllDBs(ctx, options...) 270 if err != nil { 271 return nil, err 272 } 273 return c.DBsStats(ctx, dbs) 274 } 275 276 // Ping returns true if the database is online and available for requests. 277 func (c *Client) Ping(ctx context.Context) (bool, error) { 278 endQuery, err := c.startQuery() 279 if err != nil { 280 return false, err 281 } 282 defer endQuery() 283 if pinger, ok := c.driverClient.(driver.Pinger); ok { 284 return pinger.Ping(ctx) 285 } 286 _, err = c.driverClient.Version(ctx) 287 return err == nil, err 288 } 289 290 // Close cleans up any resources used by the client. Close is safe to call 291 // concurrently with other operations and will block until all other operations 292 // finish. After calling Close, any other client operations will return 293 // [ErrClientClosed]. 294 func (c *Client) Close() error { 295 c.mu.Lock() 296 c.closed = true 297 c.mu.Unlock() 298 c.wg.Wait() 299 if closer, ok := c.driverClient.(driver.ClientCloser); ok { 300 return closer.Close() 301 } 302 return nil 303 }