github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/istorage/cas/impl.go (about)

     1  /*
     2   * Copyright (c) 2021-present unTill Pro, Ltd.
     3   */
     4  
     5  package cas
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"strings"
    13  
    14  	"github.com/gocql/gocql"
    15  	"github.com/voedger/voedger/pkg/goutils/logger"
    16  
    17  	"github.com/voedger/voedger/pkg/istorage"
    18  )
    19  
    20  type appStorageProviderType struct {
    21  	casPar  CassandraParamsType
    22  	cluster *gocql.ClusterConfig
    23  }
    24  
    25  func newStorageProvider(casPar CassandraParamsType) (prov *appStorageProviderType) {
    26  	provider := appStorageProviderType{
    27  		casPar: casPar,
    28  	}
    29  	provider.cluster = gocql.NewCluster(strings.Split(casPar.Hosts, ",")...)
    30  	if len(casPar.DC) > 0 {
    31  		provider.cluster.PoolConfig.HostSelectionPolicy = gocql.DCAwareRoundRobinPolicy(casPar.DC)
    32  	}
    33  	if casPar.Port > 0 {
    34  		provider.cluster.Port = casPar.Port
    35  	}
    36  	if casPar.NumRetries <= 0 {
    37  		casPar.NumRetries = retryAttempt
    38  	}
    39  	retryPolicy := gocql.SimpleRetryPolicy{NumRetries: casPar.NumRetries}
    40  	provider.cluster.Consistency = DefaultConsistency
    41  	provider.cluster.ConnectTimeout = initialConnectionTimeout
    42  	provider.cluster.Timeout = ConnectionTimeout
    43  	provider.cluster.RetryPolicy = &retryPolicy
    44  	provider.cluster.Authenticator = gocql.PasswordAuthenticator{Username: casPar.Username, Password: casPar.Pwd}
    45  	provider.cluster.CQLVersion = casPar.cqlVersion()
    46  	provider.cluster.ProtoVersion = casPar.ProtoVersion
    47  	return &provider
    48  }
    49  
    50  func (p appStorageProviderType) AppStorage(appName istorage.SafeAppName) (storage istorage.IAppStorage, err error) {
    51  	session, err := getSession(p.cluster)
    52  	if err != nil {
    53  		// notest
    54  		return nil, err
    55  	}
    56  	defer session.Close()
    57  	keyspaceExists, err := isKeyspaceExists(appName.String(), session)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	if !keyspaceExists {
    62  		return nil, istorage.ErrStorageDoesNotExist
    63  	}
    64  	if storage, err = newStorage(p.cluster, appName.String()); err != nil {
    65  		return nil, fmt.Errorf("can't create application «%s» keyspace: %w", appName, err)
    66  	}
    67  	return storage, nil
    68  }
    69  
    70  func isKeyspaceExists(name string, session *gocql.Session) (bool, error) {
    71  	dummy := ""
    72  	q := "select keyspace_name from system_schema.keyspaces where keyspace_name = ?;"
    73  	logScript(q)
    74  	if err := session.Query(q, name).Scan(&dummy); err != nil {
    75  		if errors.Is(err, gocql.ErrNotFound) {
    76  			return false, nil
    77  		}
    78  		// notest
    79  		return false, err
    80  	}
    81  	return true, nil
    82  }
    83  
    84  func logScript(q string) {
    85  	if logger.IsVerbose() {
    86  		logger.Verbose("executing script:", q)
    87  	}
    88  }
    89  
    90  func (p appStorageProviderType) Init(appName istorage.SafeAppName) error {
    91  	session, err := getSession(p.cluster)
    92  	if err != nil {
    93  		// notest
    94  		return err
    95  	}
    96  	defer session.Close()
    97  	keyspace := appName.String()
    98  	keyspaceExists, err := isKeyspaceExists(keyspace, session)
    99  	if err != nil {
   100  		// notest
   101  		return err
   102  	}
   103  	if keyspaceExists {
   104  		return istorage.ErrStorageAlreadyExists
   105  	}
   106  
   107  	// create keyspace
   108  	//
   109  	q := fmt.Sprintf("create keyspace %s with replication = %s;", keyspace, p.casPar.KeyspaceWithReplication)
   110  	logScript(q)
   111  	err = session.
   112  		Query(q).
   113  		Consistency(gocql.Quorum).
   114  		Exec()
   115  	if err != nil {
   116  		return fmt.Errorf("failed to create keyspace %s: %w", keyspace, err)
   117  	}
   118  
   119  	// prepare storage tables
   120  	q = fmt.Sprintf(`create table if not exists %s.values (p_key blob, c_col blob, value blob, primary key ((p_key), c_col))`, keyspace)
   121  	logScript(q)
   122  	if err = session.Query(q).
   123  		Consistency(gocql.Quorum).Exec(); err != nil {
   124  		return fmt.Errorf("can't create table «values»: %w", err)
   125  	}
   126  	return nil
   127  }
   128  
   129  type appStorageType struct {
   130  	cluster  *gocql.ClusterConfig
   131  	session  *gocql.Session
   132  	keyspace string
   133  }
   134  
   135  func getSession(cluster *gocql.ClusterConfig) (*gocql.Session, error) {
   136  	session, err := cluster.CreateSession()
   137  	if err != nil {
   138  		return nil, fmt.Errorf("can't create session: %w", err)
   139  	}
   140  	return session, err
   141  }
   142  
   143  func newStorage(cluster *gocql.ClusterConfig, keyspace string) (storage istorage.IAppStorage, err error) {
   144  	session, err := getSession(cluster)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return &appStorageType{
   150  		cluster:  cluster,
   151  		session:  session,
   152  		keyspace: keyspace,
   153  	}, nil
   154  }
   155  
   156  func safeCcols(value []byte) []byte {
   157  	if value == nil {
   158  		return []byte{}
   159  	}
   160  	return value
   161  }
   162  
   163  func (s *appStorageType) Put(pKey []byte, cCols []byte, value []byte) (err error) {
   164  	q := fmt.Sprintf("insert into %s.values (p_key, c_col, value) values (?,?,?)", s.keyspace)
   165  	return s.session.Query(q,
   166  		pKey,
   167  		safeCcols(cCols),
   168  		value).
   169  		Consistency(gocql.Quorum).
   170  		Exec()
   171  }
   172  
   173  func (s *appStorageType) PutBatch(items []istorage.BatchItem) (err error) {
   174  	batch := s.session.NewBatch(gocql.LoggedBatch)
   175  	batch.SetConsistency(gocql.Quorum)
   176  	stmt := fmt.Sprintf("insert into %s.values (p_key, c_col, value) values (?,?,?)", s.keyspace)
   177  	for _, item := range items {
   178  		batch.Query(stmt, item.PKey, safeCcols(item.CCols), item.Value)
   179  	}
   180  	return s.session.ExecuteBatch(batch)
   181  }
   182  
   183  func scanViewQuery(ctx context.Context, q *gocql.Query, cb istorage.ReadCallback) (err error) {
   184  	q.Consistency(gocql.Quorum)
   185  	scanner := q.Iter().Scanner()
   186  	sc := scannerCloser(scanner)
   187  	for scanner.Next() {
   188  		clustCols := make([]byte, 0)
   189  		viewRecord := make([]byte, 0)
   190  		err = scanner.Scan(&clustCols, &viewRecord)
   191  		if err != nil {
   192  			return sc(err)
   193  		}
   194  		err = cb(clustCols, viewRecord)
   195  		if err != nil {
   196  			return sc(err)
   197  		}
   198  		if ctx.Err() != nil {
   199  			return nil // TCK contract
   200  		}
   201  	}
   202  	return sc(nil)
   203  }
   204  
   205  func (s *appStorageType) Read(ctx context.Context, pKey []byte, startCCols, finishCCols []byte, cb istorage.ReadCallback) (err error) {
   206  	if (len(startCCols) > 0) && (len(finishCCols) > 0) && (bytes.Compare(startCCols, finishCCols) >= 0) {
   207  		return nil // absurd range
   208  	}
   209  
   210  	qText := fmt.Sprintf("select c_col, value from %s.values where p_key=?", s.keyspace)
   211  
   212  	var q *gocql.Query
   213  	if len(startCCols) == 0 {
   214  		if len(finishCCols) == 0 {
   215  			// opened range
   216  			q = s.session.Query(qText, pKey)
   217  		} else {
   218  			// left-opened range
   219  			q = s.session.Query(qText+" and c_col<?", pKey, finishCCols)
   220  		}
   221  	} else if len(finishCCols) == 0 {
   222  		// right-opened range
   223  		q = s.session.Query(qText+" and c_col>=?", pKey, startCCols)
   224  	} else {
   225  		// closed range
   226  		q = s.session.Query(qText+" and c_col>=? and c_col<?", pKey, startCCols, finishCCols)
   227  	}
   228  
   229  	return scanViewQuery(ctx, q, cb)
   230  }
   231  
   232  func (s *appStorageType) Get(pKey []byte, cCols []byte, data *[]byte) (ok bool, err error) {
   233  	*data = (*data)[0:0]
   234  	q := fmt.Sprintf("select value from %s.values where p_key=? and c_col=?", s.keyspace)
   235  	err = s.session.Query(q, pKey, safeCcols(cCols)).
   236  		Consistency(gocql.Quorum).
   237  		Scan(data)
   238  	if errors.Is(err, gocql.ErrNotFound) {
   239  		return false, nil
   240  	}
   241  	return err == nil, err
   242  }
   243  
   244  func (s *appStorageType) GetBatch(pKey []byte, items []istorage.GetBatchItem) (err error) {
   245  	ccToIdx := make(map[string][]int)
   246  	values := make([]interface{}, 0, len(items)+1)
   247  	values = append(values, pKey)
   248  
   249  	stmt := strings.Builder{}
   250  	stmt.WriteString("select c_col, value from ")
   251  	stmt.WriteString(s.keyspace)
   252  	stmt.WriteString(".values where p_key=? and ")
   253  	stmt.WriteString("c_col in (")
   254  	for i, item := range items {
   255  		items[i].Ok = false
   256  		values = append(values, item.CCols)
   257  		ccToIdx[string(item.CCols)] = append(ccToIdx[string(item.CCols)], i)
   258  		stmt.WriteRune('?')
   259  		if i < len(items)-1 {
   260  			stmt.WriteRune(',')
   261  		}
   262  	}
   263  	stmt.WriteRune(')')
   264  
   265  	scanner := s.session.Query(stmt.String(), values...).
   266  		Consistency(gocql.Quorum).
   267  		Iter().
   268  		Scanner()
   269  	sc := scannerCloser(scanner)
   270  
   271  	for scanner.Next() {
   272  		ccols := make([]byte, 0)
   273  		value := make([]byte, 0)
   274  		err = scanner.Scan(&ccols, &value)
   275  		if err != nil {
   276  			return sc(err)
   277  		}
   278  		ii, ok := ccToIdx[string(ccols)]
   279  		if ok {
   280  			for _, i := range ii {
   281  				items[i].Ok = true
   282  				*items[i].Data = append((*items[i].Data)[0:0], value...)
   283  			}
   284  		}
   285  	}
   286  
   287  	return sc(nil)
   288  }
   289  
   290  func scannerCloser(scanner gocql.Scanner) func(error) error {
   291  	return func(err error) error {
   292  		if scannerErr := scanner.Err(); scannerErr != nil {
   293  			if err != nil {
   294  				err = fmt.Errorf("%s %w", err.Error(), scannerErr)
   295  			} else {
   296  				err = scannerErr
   297  			}
   298  		}
   299  		return err
   300  	}
   301  }