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 }