...

Source file src/github.com/go-kivik/kivik/v4/kivik.go

Documentation: github.com/go-kivik/kivik/v4

     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  }
   304  

View as plain text