gitee.com/liuxuezhan/go-micro-v1.18.0@v1.0.0/store/postgresql/postgresql.go (about)

     1  // Package postgresql implements a micro Store backed by sql
     2  package postgresql
     3  
     4  import (
     5  	"database/sql"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  	"unicode"
    10  
    11  	"github.com/lib/pq"
    12  	"github.com/pkg/errors"
    13  
    14  	"gitee.com/liuxuezhan/go-micro-v1.18.0/config/options"
    15  	"gitee.com/liuxuezhan/go-micro-v1.18.0/store"
    16  )
    17  
    18  // DefaultNamespace is the namespace that the sql store
    19  // will use if no namespace is provided.
    20  const DefaultNamespace = "micro"
    21  
    22  type sqlStore struct {
    23  	db *sql.DB
    24  
    25  	table string
    26  	options.Options
    27  }
    28  
    29  // List all the known records
    30  func (s *sqlStore) List() ([]*store.Record, error) {
    31  	q, err := s.db.Prepare(fmt.Sprintf("SELECT key, value, expiry FROM micro.%s;", s.table))
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	var records []*store.Record
    36  	var timehelper pq.NullTime
    37  	rows, err := q.Query()
    38  	if err != nil {
    39  		if err == sql.ErrNoRows {
    40  			return records, nil
    41  		}
    42  		return nil, err
    43  	}
    44  	defer rows.Close()
    45  	for rows.Next() {
    46  		record := &store.Record{}
    47  		if err := rows.Scan(&record.Key, &record.Value, &timehelper); err != nil {
    48  			return records, err
    49  		}
    50  		if timehelper.Valid {
    51  			if timehelper.Time.Before(time.Now()) {
    52  				// record has expired
    53  				go s.Delete(record.Key)
    54  			} else {
    55  				record.Expiry = time.Until(timehelper.Time)
    56  				records = append(records, record)
    57  			}
    58  		} else {
    59  			records = append(records, record)
    60  		}
    61  
    62  	}
    63  	rowErr := rows.Close()
    64  	if rowErr != nil {
    65  		// transaction rollback or something
    66  		return records, rowErr
    67  	}
    68  	if err := rows.Err(); err != nil {
    69  		return records, err
    70  	}
    71  	return records, nil
    72  }
    73  
    74  // Read all records with keys
    75  func (s *sqlStore) Read(keys ...string) ([]*store.Record, error) {
    76  	q, err := s.db.Prepare(fmt.Sprintf("SELECT key, value, expiry FROM micro.%s WHERE key = $1;", s.table))
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	var records []*store.Record
    81  	var timehelper pq.NullTime
    82  	for _, key := range keys {
    83  		row := q.QueryRow(key)
    84  		record := &store.Record{}
    85  		if err := row.Scan(&record.Key, &record.Value, &timehelper); err != nil {
    86  			if err == sql.ErrNoRows {
    87  				return records, store.ErrNotFound
    88  			}
    89  			return records, err
    90  		}
    91  		if timehelper.Valid {
    92  			if timehelper.Time.Before(time.Now()) {
    93  				// record has expired
    94  				go s.Delete(key)
    95  				return records, store.ErrNotFound
    96  			}
    97  			record.Expiry = time.Until(timehelper.Time)
    98  			records = append(records, record)
    99  		} else {
   100  			records = append(records, record)
   101  		}
   102  	}
   103  	return records, nil
   104  }
   105  
   106  // Write records
   107  func (s *sqlStore) Write(rec ...*store.Record) error {
   108  	q, err := s.db.Prepare(fmt.Sprintf(`INSERT INTO micro.%s(key, value, expiry)
   109  		VALUES ($1, $2::bytea, $3)
   110  		ON CONFLICT (key)
   111  		DO UPDATE
   112  		SET value = EXCLUDED.value, expiry = EXCLUDED.expiry;`, s.table))
   113  	if err != nil {
   114  		return err
   115  	}
   116  	for _, r := range rec {
   117  		var err error
   118  		if r.Expiry != 0 {
   119  			_, err = q.Exec(r.Key, r.Value, time.Now().Add(r.Expiry))
   120  		} else {
   121  			_, err = q.Exec(r.Key, r.Value, nil)
   122  		}
   123  		if err != nil {
   124  			return errors.Wrap(err, "Couldn't insert record "+r.Key)
   125  		}
   126  	}
   127  
   128  	return nil
   129  }
   130  
   131  // Delete records with keys
   132  func (s *sqlStore) Delete(keys ...string) error {
   133  	q, err := s.db.Prepare(fmt.Sprintf("DELETE FROM micro.%s WHERE key = $1;", s.table))
   134  	if err != nil {
   135  		return err
   136  	}
   137  	for _, key := range keys {
   138  		result, err := q.Exec(key)
   139  		if err != nil {
   140  			return err
   141  		}
   142  		_, err = result.RowsAffected()
   143  		if err != nil {
   144  			return err
   145  		}
   146  	}
   147  	return nil
   148  }
   149  
   150  func (s *sqlStore) initDB(options options.Options) error {
   151  	// Get the store.namespace option, or use sql.DefaultNamespace
   152  	namespaceOpt, found := options.Values().Get("store.namespace")
   153  	if !found {
   154  		s.table = DefaultNamespace
   155  	} else {
   156  		if namespace, ok := namespaceOpt.(string); ok {
   157  			s.table = namespace
   158  		} else {
   159  			return errors.New("store.namespace option must be a string")
   160  		}
   161  	}
   162  
   163  	// Create "micro" schema
   164  	schema, err := s.db.Prepare("CREATE SCHEMA IF NOT EXISTS micro ;")
   165  	if err != nil {
   166  		return err
   167  	}
   168  	_, err = schema.Exec()
   169  	if err != nil {
   170  		return errors.Wrap(err, "Couldn't create Schema")
   171  	}
   172  
   173  	// Create a table for the Store namespace
   174  	tableq, err := s.db.Prepare(fmt.Sprintf(`CREATE TABLE IF NOT EXISTS micro.%s
   175  	(
   176  		key text COLLATE "default" NOT NULL,
   177  		value bytea,
   178  		expiry timestamp with time zone,
   179  		CONSTRAINT %s_pkey PRIMARY KEY (key)
   180  	);`, s.table, s.table))
   181  	if err != nil {
   182  		return errors.Wrap(err, "SQL statement preparation failed")
   183  	}
   184  	_, err = tableq.Exec()
   185  	if err != nil {
   186  		return errors.Wrap(err, "Couldn't create table")
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  // New returns a new micro Store backed by sql
   193  func New(opts ...options.Option) (store.Store, error) {
   194  	options := options.NewOptions(opts...)
   195  	driver, dataSourceName, err := validateOptions(options)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	if !strings.Contains(dataSourceName, " ") {
   200  		dataSourceName = fmt.Sprintf("host=%s", dataSourceName)
   201  	}
   202  	db, err := sql.Open(driver, dataSourceName)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	if err := db.Ping(); err != nil {
   207  		return nil, err
   208  	}
   209  	s := &sqlStore{
   210  		db: db,
   211  	}
   212  
   213  	return s, s.initDB(options)
   214  }
   215  
   216  // validateOptions checks whether the provided options are valid, then returns the driver
   217  // and data source name.
   218  func validateOptions(options options.Options) (driver, dataSourceName string, err error) {
   219  	driverOpt, found := options.Values().Get("store.sql.driver")
   220  	if !found {
   221  		return "", "", errors.New("No store.sql.driver option specified")
   222  	}
   223  	nodesOpt, found := options.Values().Get("store.nodes")
   224  	if !found {
   225  		return "", "", errors.New("No store.nodes option specified (expected a database connection string)")
   226  	}
   227  	driver, ok := driverOpt.(string)
   228  	if !ok {
   229  		return "", "", errors.New("store.sql.driver option must be a string")
   230  	}
   231  	nodes, ok := nodesOpt.([]string)
   232  	if !ok {
   233  		return "", "", errors.New("store.nodes option must be a []string")
   234  	}
   235  	if len(nodes) != 1 {
   236  		return "", "", errors.New("expected only 1 store.nodes option")
   237  	}
   238  	namespaceOpt, found := options.Values().Get("store.namespace")
   239  	if found {
   240  		namespace, ok := namespaceOpt.(string)
   241  		if !ok {
   242  			return "", "", errors.New("store.namespace must me a string")
   243  		}
   244  		for _, r := range namespace {
   245  			if !unicode.IsLetter(r) {
   246  				return "", "", errors.New("store.namespace must only contain letters")
   247  			}
   248  		}
   249  	}
   250  	return driver, nodes[0], nil
   251  }