github.com/btccom/go-micro/v2@v2.9.3/store/cockroach/cockroach.go (about)

     1  // Package cockroach implements the cockroach store
     2  package cockroach
     3  
     4  import (
     5  	"database/sql"
     6  	"fmt"
     7  	"net/url"
     8  	"regexp"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/lib/pq"
    14  	"github.com/btccom/go-micro/v2/logger"
    15  	"github.com/btccom/go-micro/v2/store"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  // DefaultDatabase is the namespace that the sql store
    20  // will use if no namespace is provided.
    21  var (
    22  	DefaultDatabase = "micro"
    23  	DefaultTable    = "micro"
    24  )
    25  
    26  var (
    27  	re = regexp.MustCompile("[^a-zA-Z0-9]+")
    28  
    29  	statements = map[string]string{
    30  		"list":       "SELECT key, value, metadata, expiry FROM %s.%s;",
    31  		"read":       "SELECT key, value, metadata, expiry FROM %s.%s WHERE key = $1;",
    32  		"readMany":   "SELECT key, value, metadata, expiry FROM %s.%s WHERE key LIKE $1;",
    33  		"readOffset": "SELECT key, value, metadata, expiry FROM %s.%s WHERE key LIKE $1 ORDER BY key DESC LIMIT $2 OFFSET $3;",
    34  		"write":      "INSERT INTO %s.%s(key, value, metadata, expiry) VALUES ($1, $2::bytea, $3, $4) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, metadata = EXCLUDED.metadata, expiry = EXCLUDED.expiry;",
    35  		"delete":     "DELETE FROM %s.%s WHERE key = $1;",
    36  	}
    37  )
    38  
    39  type sqlStore struct {
    40  	options store.Options
    41  	db      *sql.DB
    42  
    43  	sync.RWMutex
    44  	// known databases
    45  	databases map[string]bool
    46  }
    47  
    48  func (s *sqlStore) getDB(database, table string) (string, string) {
    49  	if len(database) == 0 {
    50  		if len(s.options.Database) > 0 {
    51  			database = s.options.Database
    52  		} else {
    53  			database = DefaultDatabase
    54  		}
    55  	}
    56  
    57  	if len(table) == 0 {
    58  		if len(s.options.Table) > 0 {
    59  			table = s.options.Table
    60  		} else {
    61  			database = DefaultTable
    62  		}
    63  	}
    64  
    65  	// store.namespace must only contain letters, numbers and underscores
    66  	database = re.ReplaceAllString(database, "_")
    67  	table = re.ReplaceAllString(table, "_")
    68  
    69  	return database, table
    70  }
    71  
    72  func (s *sqlStore) createDB(database, table string) error {
    73  	database, table = s.getDB(database, table)
    74  
    75  	s.Lock()
    76  	defer s.Unlock()
    77  
    78  	if _, ok := s.databases[database+":"+table]; ok {
    79  		return nil
    80  	}
    81  
    82  	if err := s.initDB(database, table); err != nil {
    83  		return err
    84  	}
    85  
    86  	s.databases[database+":"+table] = true
    87  	return nil
    88  }
    89  
    90  func (s *sqlStore) initDB(database, table string) error {
    91  	if s.db == nil {
    92  		return errors.New("Database connection not initialised")
    93  	}
    94  
    95  	// Create the namespace's database
    96  	_, err := s.db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s;", database))
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	_, err = s.db.Exec(fmt.Sprintf("SET DATABASE = %s;", database))
   102  	if err != nil {
   103  		return errors.Wrap(err, "Couldn't set database")
   104  	}
   105  
   106  	// Create a table for the namespace's prefix
   107  	_, err = s.db.Exec(fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s
   108  	(
   109  		key text NOT NULL,
   110  		value bytea,
   111  		metadata JSONB,
   112  		expiry timestamp with time zone,
   113  		CONSTRAINT %s_pkey PRIMARY KEY (key)
   114  	);`, table, table))
   115  	if err != nil {
   116  		return errors.Wrap(err, "Couldn't create table")
   117  	}
   118  
   119  	// Create Index
   120  	_, err = s.db.Exec(fmt.Sprintf(`CREATE INDEX IF NOT EXISTS "%s" ON %s.%s USING btree ("key");`, "key_index_"+table, database, table))
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	// Create Metadata Index
   126  	_, err = s.db.Exec(fmt.Sprintf(`CREATE INDEX IF NOT EXISTS "%s" ON %s.%s USING GIN ("metadata");`, "metadata_index_"+table, database, table))
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  func (s *sqlStore) configure() error {
   135  	if len(s.options.Nodes) == 0 {
   136  		s.options.Nodes = []string{"postgresql://root@localhost:26257?sslmode=disable"}
   137  	}
   138  
   139  	source := s.options.Nodes[0]
   140  	// check if it is a standard connection string eg: host=%s port=%d user=%s password=%s dbname=%s sslmode=disable
   141  	// if err is nil which means it would be a URL like postgre://xxxx?yy=zz
   142  	_, err := url.Parse(source)
   143  	if err != nil {
   144  		if !strings.Contains(source, " ") {
   145  			source = fmt.Sprintf("host=%s", source)
   146  		}
   147  	}
   148  
   149  	// create source from first node
   150  	db, err := sql.Open("postgres", source)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	if err := db.Ping(); err != nil {
   156  		return err
   157  	}
   158  
   159  	if s.db != nil {
   160  		s.db.Close()
   161  	}
   162  
   163  	// save the values
   164  	s.db = db
   165  
   166  	// get DB
   167  	database, table := s.getDB(s.options.Database, s.options.Table)
   168  
   169  	// initialise the database
   170  	return s.initDB(database, table)
   171  }
   172  
   173  func (s *sqlStore) prepare(database, table, query string) (*sql.Stmt, error) {
   174  	st, ok := statements[query]
   175  	if !ok {
   176  		return nil, errors.New("unsupported statement")
   177  	}
   178  
   179  	// get DB
   180  	database, table = s.getDB(database, table)
   181  
   182  	q := fmt.Sprintf(st, database, table)
   183  	stmt, err := s.db.Prepare(q)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	return stmt, nil
   188  }
   189  
   190  func (s *sqlStore) Close() error {
   191  	if s.db != nil {
   192  		return s.db.Close()
   193  	}
   194  	return nil
   195  }
   196  
   197  func (s *sqlStore) Init(opts ...store.Option) error {
   198  	for _, o := range opts {
   199  		o(&s.options)
   200  	}
   201  	// reconfigure
   202  	return s.configure()
   203  }
   204  
   205  // List all the known records
   206  func (s *sqlStore) List(opts ...store.ListOption) ([]string, error) {
   207  	var options store.ListOptions
   208  	for _, o := range opts {
   209  		o(&options)
   210  	}
   211  
   212  	// create the db if not exists
   213  	if err := s.createDB(options.Database, options.Table); err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	st, err := s.prepare(options.Database, options.Table, "list")
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	defer st.Close()
   222  
   223  	rows, err := st.Query()
   224  	if err != nil {
   225  		if err == sql.ErrNoRows {
   226  			return nil, nil
   227  		}
   228  		return nil, err
   229  	}
   230  	defer rows.Close()
   231  
   232  	var keys []string
   233  	var timehelper pq.NullTime
   234  
   235  	for rows.Next() {
   236  		record := &store.Record{}
   237  		metadata := make(Metadata)
   238  
   239  		if err := rows.Scan(&record.Key, &record.Value, &metadata, &timehelper); err != nil {
   240  			return keys, err
   241  		}
   242  
   243  		// set the metadata
   244  		record.Metadata = toMetadata(&metadata)
   245  
   246  		if timehelper.Valid {
   247  			if timehelper.Time.Before(time.Now()) {
   248  				// record has expired
   249  				go s.Delete(record.Key)
   250  			} else {
   251  				record.Expiry = time.Until(timehelper.Time)
   252  				keys = append(keys, record.Key)
   253  			}
   254  		} else {
   255  			keys = append(keys, record.Key)
   256  		}
   257  
   258  	}
   259  	rowErr := rows.Close()
   260  	if rowErr != nil {
   261  		// transaction rollback or something
   262  		return keys, rowErr
   263  	}
   264  	if err := rows.Err(); err != nil {
   265  		return keys, err
   266  	}
   267  	return keys, nil
   268  }
   269  
   270  // Read a single key
   271  func (s *sqlStore) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) {
   272  	var options store.ReadOptions
   273  	for _, o := range opts {
   274  		o(&options)
   275  	}
   276  
   277  	// create the db if not exists
   278  	if err := s.createDB(options.Database, options.Table); err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	if options.Prefix || options.Suffix {
   283  		return s.read(key, options)
   284  	}
   285  
   286  	var records []*store.Record
   287  	var timehelper pq.NullTime
   288  
   289  	st, err := s.prepare(options.Database, options.Table, "read")
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	defer st.Close()
   294  
   295  	row := st.QueryRow(key)
   296  	record := &store.Record{}
   297  	metadata := make(Metadata)
   298  
   299  	if err := row.Scan(&record.Key, &record.Value, &metadata, &timehelper); err != nil {
   300  		if err == sql.ErrNoRows {
   301  			return records, store.ErrNotFound
   302  		}
   303  		return records, err
   304  	}
   305  
   306  	// set the metadata
   307  	record.Metadata = toMetadata(&metadata)
   308  
   309  	if timehelper.Valid {
   310  		if timehelper.Time.Before(time.Now()) {
   311  			// record has expired
   312  			go s.Delete(key)
   313  			return records, store.ErrNotFound
   314  		}
   315  		record.Expiry = time.Until(timehelper.Time)
   316  		records = append(records, record)
   317  	} else {
   318  		records = append(records, record)
   319  	}
   320  
   321  	return records, nil
   322  }
   323  
   324  // Read Many records
   325  func (s *sqlStore) read(key string, options store.ReadOptions) ([]*store.Record, error) {
   326  	pattern := "%"
   327  	if options.Prefix {
   328  		pattern = key + pattern
   329  	}
   330  	if options.Suffix {
   331  		pattern = pattern + key
   332  	}
   333  
   334  	var rows *sql.Rows
   335  	var err error
   336  
   337  	if options.Limit != 0 {
   338  		st, err := s.prepare(options.Database, options.Table, "readOffset")
   339  		if err != nil {
   340  			return nil, err
   341  		}
   342  		defer st.Close()
   343  
   344  		rows, err = st.Query(pattern, options.Limit, options.Offset)
   345  	} else {
   346  		st, err := s.prepare(options.Database, options.Table, "readMany")
   347  		if err != nil {
   348  			return nil, err
   349  		}
   350  		defer st.Close()
   351  
   352  		rows, err = st.Query(pattern)
   353  	}
   354  	if err != nil {
   355  		if err == sql.ErrNoRows {
   356  			return []*store.Record{}, nil
   357  		}
   358  		return []*store.Record{}, errors.Wrap(err, "sqlStore.read failed")
   359  	}
   360  
   361  	defer rows.Close()
   362  
   363  	var records []*store.Record
   364  	var timehelper pq.NullTime
   365  
   366  	for rows.Next() {
   367  		record := &store.Record{}
   368  		metadata := make(Metadata)
   369  
   370  		if err := rows.Scan(&record.Key, &record.Value, &metadata, &timehelper); err != nil {
   371  			return records, err
   372  		}
   373  
   374  		// set the metadata
   375  		record.Metadata = toMetadata(&metadata)
   376  
   377  		if timehelper.Valid {
   378  			if timehelper.Time.Before(time.Now()) {
   379  				// record has expired
   380  				go s.Delete(record.Key)
   381  			} else {
   382  				record.Expiry = time.Until(timehelper.Time)
   383  				records = append(records, record)
   384  			}
   385  		} else {
   386  			records = append(records, record)
   387  		}
   388  	}
   389  	rowErr := rows.Close()
   390  	if rowErr != nil {
   391  		// transaction rollback or something
   392  		return records, rowErr
   393  	}
   394  	if err := rows.Err(); err != nil {
   395  		return records, err
   396  	}
   397  
   398  	return records, nil
   399  }
   400  
   401  // Write records
   402  func (s *sqlStore) Write(r *store.Record, opts ...store.WriteOption) error {
   403  	var options store.WriteOptions
   404  	for _, o := range opts {
   405  		o(&options)
   406  	}
   407  
   408  	// create the db if not exists
   409  	if err := s.createDB(options.Database, options.Table); err != nil {
   410  		return err
   411  	}
   412  
   413  	st, err := s.prepare(options.Database, options.Table, "write")
   414  	if err != nil {
   415  		return err
   416  	}
   417  	defer st.Close()
   418  
   419  	metadata := make(Metadata)
   420  	for k, v := range r.Metadata {
   421  		metadata[k] = v
   422  	}
   423  
   424  	if r.Expiry != 0 {
   425  		_, err = st.Exec(r.Key, r.Value, metadata, time.Now().Add(r.Expiry))
   426  	} else {
   427  		_, err = st.Exec(r.Key, r.Value, metadata, nil)
   428  	}
   429  
   430  	if err != nil {
   431  		return errors.Wrap(err, "Couldn't insert record "+r.Key)
   432  	}
   433  
   434  	return nil
   435  }
   436  
   437  // Delete records with keys
   438  func (s *sqlStore) Delete(key string, opts ...store.DeleteOption) error {
   439  	var options store.DeleteOptions
   440  	for _, o := range opts {
   441  		o(&options)
   442  	}
   443  
   444  	// create the db if not exists
   445  	if err := s.createDB(options.Database, options.Table); err != nil {
   446  		return err
   447  	}
   448  
   449  	st, err := s.prepare(options.Database, options.Table, "delete")
   450  	if err != nil {
   451  		return err
   452  	}
   453  	defer st.Close()
   454  
   455  	result, err := st.Exec(key)
   456  	if err != nil {
   457  		return err
   458  	}
   459  
   460  	_, err = result.RowsAffected()
   461  	if err != nil {
   462  		return err
   463  	}
   464  
   465  	return nil
   466  }
   467  
   468  func (s *sqlStore) Options() store.Options {
   469  	return s.options
   470  }
   471  
   472  func (s *sqlStore) String() string {
   473  	return "cockroach"
   474  }
   475  
   476  // NewStore returns a new micro Store backed by sql
   477  func NewStore(opts ...store.Option) store.Store {
   478  	options := store.Options{
   479  		Database: DefaultDatabase,
   480  		Table:    DefaultTable,
   481  	}
   482  
   483  	for _, o := range opts {
   484  		o(&options)
   485  	}
   486  
   487  	// new store
   488  	s := new(sqlStore)
   489  	// set the options
   490  	s.options = options
   491  	// mark known databases
   492  	s.databases = make(map[string]bool)
   493  	// best-effort configure the store
   494  	if err := s.configure(); err != nil {
   495  		if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
   496  			logger.Error("Error configuring store ", err)
   497  		}
   498  	}
   499  
   500  	// return store
   501  	return s
   502  }