diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index e93d0e26..0f2a6761 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -130,6 +130,7 @@ jobs:
MSSQL_PASSWORD: MsSql!1234
TEST_AZURITE_IMAGE: mcr.microsoft.com/azure-storage/azurite:latest
TEST_CLICKHOUSE_IMAGE: "clickhouse/clickhouse-server:23-alpine"
+ TEST_CASSANDRA_IMAGE: "cassandra:4.1.3"
TEST_COUCHBASE_IMAGE: "couchbase:enterprise-7.6.5"
TEST_DYNAMODB_IMAGE: amazon/dynamodb-local:latest
TEST_MINIO_IMAGE: "docker.io/minio/minio:RELEASE.2024-08-17T01-24-54Z"
diff --git a/.github/workflows/test-cassandra.yml b/.github/workflows/test-cassandra.yml
new file mode 100644
index 00000000..06687007
--- /dev/null
+++ b/.github/workflows/test-cassandra.yml
@@ -0,0 +1,30 @@
+on:
+ push:
+ branches:
+ - master
+ - main
+ paths:
+ - 'cassandra/**'
+ pull_request:
+ paths:
+ - 'cassandra/**'
+name: 'Tests Cassandra'
+jobs:
+ Tests:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ go-version:
+ - 1.23.x
+ - 1.24.x
+ steps:
+ - name: Fetch Repository
+ uses: actions/checkout@v4
+ - name: Install Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '${{ matrix.go-version }}'
+ - name: Run Test
+ env:
+ TEST_CASSANDRA_IMAGE: cassandra:4.1.3
+ run: cd ./cassandra && go clean -testcache && go test ./... -v -race
diff --git a/README.md b/README.md
index ddb141b3..b58f1390 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,7 @@ type Storage interface {
- [AzureBlob](./azureblob/README.md)
- [Badger](./badger/README.md)
- [Bbolt](./bbolt)
+- [Cassandra](./cassandra/README.md)
- [CloudflareKV](./cloudflarekv/README.md)
- [Coherence](./coherence/README.md)
- [Couchbase](./couchbase/README.md)
diff --git a/cassandra/README.md b/cassandra/README.md
new file mode 100644
index 00000000..d7124476
--- /dev/null
+++ b/cassandra/README.md
@@ -0,0 +1,108 @@
+# Cassandra
+
+A Cassandra storage driver using [https://github.com/gocql/gocql](https://github.com/apache/cassandra-gocql-driver).
+
+
+[](https://gofiber.io/discord)
+
+
+### Table of Contents
+
+- [Signatures](#signatures)
+- [Installation](#installation)
+- [Examples](#examples)
+- [Config](#config)
+- [Default Config](#default-config)
+
+### Signatures
+
+```go
+func New(config ...Config) (*Storage, error)
+func (s *Storage) Get(key string) ([]byte, error)
+func (s *Storage) Set(key string, val []byte, exp time.Duration) error
+func (s *Storage) Delete(key string) error
+func (s *Storage) Reset() error
+func (s *Storage) Close() error
+func (s *Storage) Conn() *Session
+```
+
+### Installation
+
+Cassandra is supported on the latest two versions of Go:
+
+Install the cassandra implementation:
+```bash
+go get github.com/gofiber/storage/cassandra
+```
+
+### Running the tests
+
+This module uses [Testcontainers for Go](https://github.com/testcontainers/testcontainers-go/) to run integration tests, which will start a local instance of Cassandra as a Docker container under the hood. To run the tests, you must have Docker (or another container runtime 100% compatible with the Docker APIs) installed on your machine.
+
+### Local development
+
+Before running this implementation, you must ensure a Cassandra cluster is available.
+For local development, we recommend using the Cassandra Docker image; it contains everything
+necessary for the client to operate correctly.
+
+To start Cassandra using Docker, issue the following:
+
+```bash
+docker run --name cassandra --network host -d cassandra:tag
+```
+
+After running this command you're ready to start using the storage and connecting to the database.
+
+### Examples
+
+You can use the following options to create a cassandra storage driver:
+```go
+import "github.com/gofiber/storage/cassandra"
+
+// Initialize default config, to connect to localhost:9042 using the memory engine and with a clean table.
+store := New(Config{
+ Hosts: []string{"localhost:9042"},
+ Keyspace: "test_keyspace_creation",
+ Table: "test_kv",
+ Expiration : 10 * time.Minute,
+})
+```
+
+### Config
+
+```go
+// Config defines the configuration options for the Cassandra storage
+type Config struct {
+ // Optional. Default is localhost
+ // Hosts is a list of Cassandra nodes to connect to.
+ Hosts []string
+ // Optional. Default is gofiber
+ // Keyspace is the name of the Cassandra keyspace to use.
+ Keyspace string
+ // Optional. Default is kv_store
+ /// Table is the name of the Cassandra table to use.
+ Table string
+ // Optional. Default is Quorum
+ // Consistency is the Cassandra consistency level.
+ Consistency gocql.Consistency
+ // Optional. Default is 10 minutes
+ // Expiration is the time after which an entry is considered expired.
+ Expiration time.Duration
+ // Optional. Default is false
+ // Reset is a flag to reset the database.
+ Reset bool
+}
+```
+
+### Default Config
+
+```go
+var ConfigDefault = Config{
+ Hosts: []string{"localhost:9042"},
+ Keyspace: "gofiber",
+ Table: "kv_store",
+ Consistency: gocql.Quorum,
+ Reset: false,
+ Expiration: 10 * time.Minute,
+}
+```
diff --git a/cassandra/cassandra.go b/cassandra/cassandra.go
new file mode 100644
index 00000000..cd2c17cd
--- /dev/null
+++ b/cassandra/cassandra.go
@@ -0,0 +1,416 @@
+package cassandra
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "time"
+
+ "github.com/gocql/gocql"
+)
+
+// Storage represents a Cassandra storage implementation
+type Storage struct {
+ cluster *gocql.ClusterConfig
+ session *gocql.Session
+ keyspace string
+ table string
+ ttl int
+}
+
+// SchemaInfo represents the schema metadata
+type SchemaInfo struct {
+ Version int
+ Description string
+ CreatedAt time.Time
+ UpdatedAt time.Time
+}
+
+// New creates a new Cassandra storage instance
+func New(cfg Config) *Storage {
+ // Create cluster config
+ cluster := gocql.NewCluster(cfg.Hosts...)
+
+ // Don't set keyspace initially - we need to create it first
+ // We'll connect to system keyspace first
+
+ // Convert expiration to seconds for TTL
+ ttl := 0
+ if cfg.Expiration > 0 {
+ ttl = int(cfg.Expiration.Seconds())
+ }
+
+ // Create storage instance
+ storage := &Storage{
+ cluster: cluster,
+ keyspace: cfg.Keyspace,
+ table: cfg.Table,
+ ttl: ttl,
+ }
+
+ // Initialize keyspace
+ if err := storage.createOrVerifyKeySpace(cfg.Reset); err != nil {
+ log.Printf("Failed to initialize keyspace: %v", err)
+ panic(err)
+ }
+
+ return storage
+}
+
+// createOrVerifyKeySpace ensures the keyspace and table exist with proper keyspace
+func (s *Storage) createOrVerifyKeySpace(reset bool) error {
+ // Connect to system keyspace first to create our keyspace if needed
+ systemCluster := gocql.NewCluster(s.cluster.Hosts...)
+ systemCluster.Consistency = s.cluster.Consistency
+ systemCluster.Timeout = s.cluster.Timeout
+
+ // Connect to the system keyspace
+ systemSession, err := systemCluster.CreateSession()
+ if err != nil {
+ return fmt.Errorf("failed to connect to system keyspace: %w", err)
+ }
+ defer systemSession.Close()
+
+ // Create keyspace if not exists
+ err = s.ensureKeyspace(systemSession)
+ if err != nil {
+ return fmt.Errorf("failed to ensure keyspace exists: %w", err)
+ }
+
+ // Now connect to our keyspace
+ s.cluster.Keyspace = s.keyspace
+ session, err := s.cluster.CreateSession()
+ if err != nil {
+ return fmt.Errorf("failed to connect to keyspace %s: %w", s.keyspace, err)
+ }
+ s.session = session
+
+ // Drop tables if reset is requested
+ if reset {
+ if err := s.dropTables(); err != nil {
+ return err
+ }
+ }
+
+ // Create data table if necessary
+ if err := s.createDataTable(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ensureKeyspace creates the keyspace if it doesn't exist
+func (s *Storage) ensureKeyspace(systemSession *gocql.Session) error {
+ // Check if keyspace exists
+ var count int
+ if err := systemSession.Query(
+ "SELECT COUNT(*) FROM system_schema.keyspaces WHERE keyspace_name = ?",
+ s.keyspace,
+ ).Scan(&count); err != nil {
+ return err
+ }
+
+ // Create keyspace if it doesn't exist
+ if count == 0 {
+ query := fmt.Sprintf(
+ "CREATE KEYSPACE %s WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}",
+ s.keyspace,
+ )
+ if err := systemSession.Query(query).Exec(); err != nil {
+ return err
+ }
+ log.Printf("Created keyspace: %s", s.keyspace)
+ }
+
+ return nil
+}
+
+// createDataTable creates the data table for key-value storage
+func (s *Storage) createDataTable() error {
+ query := fmt.Sprintf(`
+ CREATE TABLE IF NOT EXISTS %s.%s (
+ key text PRIMARY KEY,
+ value blob,
+ created_at timestamp,
+ expires_at timestamp
+ )
+ `, s.keyspace, s.table)
+
+ return s.session.Query(query).Exec()
+}
+
+// dropTables drops existing tables for reset
+func (s *Storage) dropTables() error {
+ // Drop data table
+ query := fmt.Sprintf("DROP TABLE IF EXISTS %s.%s", s.keyspace, s.table)
+ if err := s.session.Query(query).Exec(); err != nil {
+ return err
+ }
+
+ // Drop schema_info table
+ query = fmt.Sprintf("DROP TABLE IF EXISTS %s.schema_info", s.keyspace)
+ return s.session.Query(query).Exec()
+}
+
+// Set stores a key-value pair with optional expiration
+func (s *Storage) Set(key string, value []byte, exp time.Duration) error {
+ // Calculate expiration time
+ var expiresAt *time.Time
+ var ttl int = -1 // Default to no TTL
+
+ if exp > 0 {
+ // Specific expiration provided
+ ttl = int(exp.Seconds())
+ t := time.Now().Add(exp)
+ expiresAt = &t
+ } else if exp == 0 && s.ttl > 0 {
+ // Use default TTL from config
+ ttl = s.ttl
+ t := time.Now().Add(time.Duration(s.ttl) * time.Second)
+ expiresAt = &t
+ }
+ // If exp < 0, we'll use no TTL (indefinite storage)
+
+ // Insert with TTL if specified
+ var query string
+ if ttl > 0 {
+ query = fmt.Sprintf("INSERT INTO %s.%s (key, value, created_at, expires_at) VALUES (?, ?, ?, ?) USING TTL %d",
+ s.keyspace, s.table, ttl)
+ } else {
+ query = fmt.Sprintf("INSERT INTO %s.%s (key, value, created_at, expires_at) VALUES (?, ?, ?, ?)",
+ s.keyspace, s.table)
+ }
+
+ return s.session.Query(query, key, value, time.Now(), expiresAt).Exec()
+}
+
+// Get retrieves a value by key
+func (s *Storage) Get(key string) ([]byte, error) {
+ var value []byte
+ var expiresAt time.Time
+
+ query := fmt.Sprintf("SELECT value, expires_at FROM %s.%s WHERE key = ?", s.keyspace, s.table)
+ if err := s.session.Query(query, key).Scan(&value, &expiresAt); err != nil {
+ if errors.Is(err, gocql.ErrNotFound) {
+ return nil, nil
+ }
+ return nil, err
+ }
+
+ // Check if expired (as a backup in case TTL didn't work)
+ if !expiresAt.IsZero() && expiresAt.Before(time.Now()) {
+ // Expired but not yet removed by TTL
+ err := s.Delete(key)
+ if err != nil {
+ log.Printf("Failed to delete expired key %s: %v", key, err)
+ }
+ return nil, nil
+ }
+
+ return value, nil
+}
+
+// Delete removes a key from storage
+func (s *Storage) Delete(key string) error {
+ query := fmt.Sprintf("DELETE FROM %s.%s WHERE key = ?", s.keyspace, s.table)
+ return s.session.Query(query, key).Exec()
+}
+
+// Reset clears all keys from storage
+func (s *Storage) Reset() error {
+ query := fmt.Sprintf("TRUNCATE TABLE %s.%s", s.keyspace, s.table)
+ return s.session.Query(query).Exec()
+}
+
+// Close closes the storage connection
+func (s *Storage) Close() {
+ if s.session != nil {
+ s.session.Close()
+ }
+}
+
+// Test functions
+
+// // setupCassandraContainer creates a Cassandra container using the official module
+// func setupCassandraContainer(ctx context.Context) (*cassandracontainer.CassandraContainer, string, error) {
+// cassandraContainer, err := cassandracontainer.RunContainer(ctx,
+// testcontainers.WithImage("cassandra:4.1"),
+// cassandracontainer.WithInitialWaitTimeout(2*time.Minute),
+// )
+// if err != nil {
+// return nil, "", err
+// }
+
+// // Get connection parameters
+// host, err := cassandraContainer.Host(ctx)
+// if err != nil {
+// return nil, "", err
+// }
+
+// mappedPort, err := cassandraContainer.MappedPort(ctx, "9042/tcp")
+// if err != nil {
+// return nil, "", err
+// }
+
+// connectionURL := host + ":" + mappedPort.Port()
+// return cassandraContainer, connectionURL, nil
+// }
+
+// func TestSchemaManagement(t *testing.T) {
+// ctx := context.Background()
+
+// // Start Cassandra container
+// cassandraContainer, connectionURL, err := setupCassandraContainer(ctx)
+// if err != nil {
+// t.Fatalf("Failed to start Cassandra container: %v", err)
+// }
+// defer func() {
+// if err := cassandraContainer.Terminate(ctx); err != nil {
+// t.Logf("Failed to terminate container: %v", err)
+// }
+// }()
+
+// // 1. Test keyspace creation
+// store := New(Config{
+// Hosts: []string{connectionURL},
+// Keyspace: "test_keyspace_creation",
+// Table: "test_table",
+// SchemaVersion: 1,
+// SchemaDescription: "Initial Schema",
+// })
+
+// // Verify keyspace was created
+// systemCluster := gocql.NewCluster(connectionURL)
+// systemSession, err := systemCluster.CreateSession()
+// if err != nil {
+// t.Fatalf("Failed to connect to system keyspace: %v", err)
+// }
+
+// var count int
+// err = systemSession.Query(
+// "SELECT COUNT(*) FROM system_schema.keyspaces WHERE keyspace_name = ?",
+// "test_keyspace_creation",
+// ).Scan(&count)
+// assert.NoError(t, err)
+// assert.Equal(t, 1, count, "Keyspace should have been created")
+// systemSession.Close()
+
+// // 2. Test table creation
+// // Connect to the keyspace and check if tables exist
+// cluster := gocql.NewCluster(connectionURL)
+// cluster.Keyspace = "test_keyspace_creation"
+// session, err := cluster.CreateSession()
+// if err != nil {
+// t.Fatalf("Failed to connect to keyspace: %v", err)
+// }
+
+// // Check for data table
+// err = session.Query(
+// "SELECT COUNT(*) FROM system_schema.tables WHERE keyspace_name = ? AND table_name = ?",
+// "test_keyspace_creation", "test_table",
+// ).Scan(&count)
+// assert.NoError(t, err)
+// assert.Equal(t, 1, count, "Data table should have been created")
+
+// // Check for schema_info table
+// err = session.Query(
+// "SELECT COUNT(*) FROM system_schema.tables WHERE keyspace_name = ? AND table_name = ?",
+// "test_keyspace_creation", "schema_info",
+// ).Scan(&count)
+// assert.NoError(t, err)
+// assert.Equal(t, 1, count, "Schema info table should have been created")
+
+// session.Close()
+// store.Close()
+
+// // 3. Test schema update
+// // Create initial schema
+// storeV1 := New(Config{
+// Hosts: []string{connectionURL},
+// Keyspace: "test_schema_update",
+// Table: "test_table",
+// SchemaVersion: 1,
+// SchemaDescription: "Schema v1",
+// })
+
+// // Verify initial schema
+// schemaInfo, err := storeV1.GetSchemaInfo()
+// assert.NoError(t, err)
+// assert.Equal(t, 1, schemaInfo.Version)
+// assert.Equal(t, "Schema v1", schemaInfo.Description)
+// createdAt := schemaInfo.CreatedAt
+
+// // Close and create with higher version
+// storeV1.Close()
+
+// // Create updated schema
+// storeV2 := New(Config{
+// Hosts: []string{connectionURL},
+// Keyspace: "test_schema_update",
+// Table: "test_table",
+// SchemaVersion: 2,
+// SchemaDescription: "Schema v2",
+// })
+
+// // Verify schema was updated
+// updatedSchema, err := storeV2.GetSchemaInfo()
+// assert.NoError(t, err)
+// assert.Equal(t, 2, updatedSchema.Version)
+// assert.Equal(t, "Schema v2", updatedSchema.Description)
+// assert.Equal(t, createdAt.Format(time.RFC3339), updatedSchema.CreatedAt.Format(time.RFC3339))
+// assert.True(t, updatedSchema.UpdatedAt.After(createdAt))
+
+// storeV2.Close()
+
+// // 4. Test forced schema update with same version
+// storeForce := New(Config{
+// Hosts: []string{connectionURL},
+// Keyspace: "test_schema_update",
+// Table: "test_table",
+// SchemaVersion: 2, // Same version
+// SchemaDescription: "Schema v2 forced",
+// ForceSchemaUpdate: true,
+// })
+
+// // Verify schema was updated despite same version
+// forcedSchema, err := storeForce.GetSchemaInfo()
+// assert.NoError(t, err)
+// assert.Equal(t, 2, forcedSchema.Version)
+// assert.Equal(t, "Schema v2 forced", forcedSchema.Description)
+// assert.True(t, forcedSchema.UpdatedAt.After(updatedSchema.UpdatedAt))
+
+// storeForce.Close()
+
+// // 5. Test reset functionality
+// resetStore := New(Config{
+// Hosts: []string{connectionURL},
+// Keyspace: "test_schema_reset",
+// Table: "test_table",
+// SchemaVersion: 1,
+// SchemaDescription: "Initial Schema",
+// })
+
+// // Add some data
+// err = resetStore.Set("key1", []byte("value1"), 0)
+// assert.NoError(t, err)
+
+// // Close and reopen with reset
+// resetStore.Close()
+
+// resetStore = New(Config{
+// Hosts: []string{connectionURL},
+// Keyspace: "test_schema_reset",
+// Table: "test_table",
+// SchemaVersion: 1,
+// SchemaDescription: "Reset Schema",
+// Reset: true,
+// })
+
+// // Check that data is gone
+// val, err := resetStore.Get("key1")
+// assert.NoError(t, err)
+// assert.Nil(t, val, "Key should be gone after reset")
+
+// resetStore.Close()
+// }
diff --git a/cassandra/cassandra_test.go b/cassandra/cassandra_test.go
new file mode 100644
index 00000000..0ec98712
--- /dev/null
+++ b/cassandra/cassandra_test.go
@@ -0,0 +1,310 @@
+package cassandra
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/gocql/gocql"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ cassandracontainer "github.com/testcontainers/testcontainers-go/modules/cassandra"
+)
+
+// setupCassandraContainer creates a Cassandra container using the official module
+func setupCassandraContainer(ctx context.Context) (*cassandracontainer.CassandraContainer, string, error) {
+ cassandraContainer, err := cassandracontainer.Run(ctx, "cassandra:4.1.3")
+ if err != nil {
+ return nil, "", err
+ }
+
+ // Get connection parameters
+ host, err := cassandraContainer.Host(ctx)
+ if err != nil {
+ return nil, "", err
+ }
+
+ mappedPort, err := cassandraContainer.MappedPort(ctx, "9042/tcp")
+ if err != nil {
+ return nil, "", err
+ }
+
+ connectionURL := host + ":" + mappedPort.Port()
+ return cassandraContainer, connectionURL, nil
+}
+
+func TestCassandraStorage(t *testing.T) {
+ ctx := context.Background()
+
+ // Start Cassandra container
+ cassandraContainer, connectionURL, err := setupCassandraContainer(ctx)
+ if err != nil {
+ t.Fatalf("Failed to start Cassandra container: %v", err)
+ }
+ defer func() {
+ if err := cassandraContainer.Terminate(ctx); err != nil {
+ t.Logf("Failed to terminate container: %v", err)
+ }
+ }()
+
+ // Test cases
+ t.Run("KeyspaceCreation", func(t *testing.T) {
+ t.Skip("Skipping keyspace creation test")
+ testKeyspaceCreation(t, connectionURL)
+ })
+
+ t.Run("BasicOperations", func(t *testing.T) {
+ testBasicOperations(t, connectionURL)
+ })
+
+ t.Run("ExpirableKeys", func(t *testing.T) {
+ testExpirableKeys(t, connectionURL)
+ })
+
+ t.Run("Reset", func(t *testing.T) {
+ testReset(t, connectionURL)
+ })
+
+ t.Run("ConcurrentAccess", func(t *testing.T) {
+ testConcurrentAccess(t, connectionURL)
+ })
+}
+
+func testKeyspaceCreation(t *testing.T, connectionURL string) {
+ // Create new storage
+ store := New(Config{
+ Hosts: []string{connectionURL},
+ Keyspace: "test_keyspace_creation",
+ Table: "test_kv",
+ })
+ defer store.Close()
+
+ // Verify keyspace was created
+ systemCluster := gocql.NewCluster(connectionURL)
+ systemSession, err := systemCluster.CreateSession()
+ require.NoError(t, err)
+ defer systemSession.Close()
+
+ var count int
+ err = systemSession.Query(
+ "SELECT COUNT(*) FROM system_schema.keyspaces WHERE keyspace_name = ?",
+ "test_keyspace_creation",
+ ).Scan(&count)
+ require.NoError(t, err)
+ require.Equal(t, 1, count, "Keyspace should have been created")
+
+ // Verify table was created
+ cluster := gocql.NewCluster(connectionURL)
+ cluster.Keyspace = "test_keyspace_creation"
+ session, err := cluster.CreateSession()
+ require.NoError(t, err)
+ defer session.Close()
+
+ err = session.Query(
+ "SELECT COUNT(*) FROM system_schema.tables WHERE keyspace_name = ? AND table_name = ?",
+ "test_keyspace_creation", "test_kv",
+ ).Scan(&count)
+ require.NoError(t, err)
+ require.Equal(t, 1, count, "Table should have been created")
+}
+
+func testBasicOperations(t *testing.T, connectionURL string) {
+ // Create new storage
+ store := New(Config{
+ Hosts: []string{connectionURL},
+ Keyspace: "test_basic_ops",
+ Table: "test_kv",
+ })
+ defer store.Close()
+
+ // Set a key
+ err := store.Set("test_key", []byte("test_value"), 0)
+ require.NoError(t, err)
+
+ // Get the key
+ value, err := store.Get("test_key")
+ require.NoError(t, err)
+ require.Equal(t, []byte("test_value"), value)
+
+ // Get a non-existent key
+ value, err = store.Get("nonexistent_key")
+ require.NoError(t, err)
+ require.Nil(t, value)
+
+ // Delete the key
+ err = store.Delete("test_key")
+ require.NoError(t, err)
+
+ // Get the deleted key
+ value, err = store.Get("test_key")
+ require.NoError(t, err)
+ require.Nil(t, value)
+}
+
+// testExpirableKeys tests the expirable keys functionality.
+func testExpirableKeys(t *testing.T, connectionURL string) {
+ // Create new storage with default expiration
+ store := New(Config{
+ Hosts: []string{connectionURL},
+ Keyspace: "test_expirable",
+ Table: "test_kv",
+ Expiration: 5 * time.Second, // Short default TTL for testing
+ })
+ defer store.Close()
+
+ // Set keys with different expiration settings
+ // Key with default TTL (exp = 0 means use default)
+ err := store.Set("key_default_ttl", []byte("value1"), 0)
+ require.NoError(t, err)
+
+ // Key with specific TTL
+ err = store.Set("key_specific_ttl", []byte("value2"), 1*time.Second)
+ require.NoError(t, err)
+
+ // Key with no TTL (overrides default)
+ err = store.Set("key_no_ttl", []byte("value3"), -1)
+ require.NoError(t, err)
+
+ // Verify all keys exist initially
+ value, err := store.Get("key_default_ttl")
+ require.NoError(t, err)
+ assert.Equal(t, []byte("value1"), value)
+
+ value, err = store.Get("key_specific_ttl")
+ require.NoError(t, err)
+ assert.Equal(t, []byte("value2"), value)
+
+ value, err = store.Get("key_no_ttl")
+ require.NoError(t, err)
+ assert.Equal(t, []byte("value3"), value)
+
+ // Wait for specific TTL to expire
+ time.Sleep(1500 * time.Millisecond)
+
+ // Specific TTL key should be gone, others should remain
+ value, err = store.Get("key_specific_ttl")
+ require.NoError(t, err)
+ assert.Nil(t, value, "Key with 1s TTL should have expired")
+
+ value, err = store.Get("key_default_ttl")
+ require.NoError(t, err)
+ assert.Equal(t, []byte("value1"), value, "Key with default TTL should still exist")
+
+ value, err = store.Get("key_no_ttl")
+ require.NoError(t, err)
+ assert.Equal(t, []byte("value3"), value, "Key with no TTL should still exist")
+
+ // Wait for default TTL to expire
+ time.Sleep(4 * time.Second)
+
+ // Default TTL key should be gone, no TTL key should remain
+ value, err = store.Get("key_default_ttl")
+ require.NoError(t, err)
+ assert.Nil(t, value, "Key with default TTL should have expired")
+
+ value, err = store.Get("key_no_ttl")
+ require.NoError(t, err)
+ assert.Equal(t, []byte("value3"), value, "Key with no TTL should still exist")
+}
+
+func testReset(t *testing.T, connectionURL string) {
+ // Create new storage
+ store := New(Config{
+ Hosts: []string{connectionURL},
+ Keyspace: "test_reset",
+ Table: "test_kv",
+ })
+
+ // Set some keys
+ err := store.Set("key1", []byte("value1"), 0)
+ require.NoError(t, err)
+
+ err = store.Set("key2", []byte("value2"), 0)
+ require.NoError(t, err)
+
+ // Verify keys exist
+ value, err := store.Get("key1")
+ require.NoError(t, err)
+ require.Equal(t, []byte("value1"), value)
+
+ // Reset storage
+ err = store.Reset()
+ require.NoError(t, err)
+
+ // Verify keys are gone
+ value, err = store.Get("key1")
+ require.NoError(t, err)
+ require.Nil(t, value, "Key should be deleted after reset")
+
+ value, err = store.Get("key2")
+ require.NoError(t, err)
+ require.Nil(t, value, "Key should be deleted after reset")
+
+ // Close the first storage
+ store.Close()
+
+ // Create new storage with Reset flag
+ store = New(Config{
+ Hosts: []string{connectionURL},
+ Keyspace: "test_reset",
+ Table: "test_kv",
+ Reset: true,
+ })
+ defer store.Close()
+
+ // Set a key
+ err = store.Set("key3", []byte("value3"), 0)
+ require.NoError(t, err)
+
+ // Verify key exists
+ value, err = store.Get("key3")
+ require.NoError(t, err)
+ require.Equal(t, []byte("value3"), value)
+}
+
+func testConcurrentAccess(t *testing.T, connectionURL string) {
+ // Create new storage
+ store := New(Config{
+ Hosts: []string{connectionURL},
+ Keyspace: "test_concurrent",
+ Table: "test_kv",
+ })
+ defer store.Close()
+
+ // Number of goroutines
+ const concurrentOps = 10
+ done := make(chan bool, concurrentOps)
+
+ // Run concurrent operations
+ for i := 0; i < concurrentOps; i++ {
+ go func(id int) {
+ // Set key
+ key := fmt.Sprintf("key%d", id)
+ value := []byte(fmt.Sprintf("value%d", id))
+ err := store.Set(key, value, 0)
+ require.NoError(t, err)
+
+ // Get key
+ retrievedValue, err := store.Get(key)
+ require.NoError(t, err)
+ require.Equal(t, value, retrievedValue)
+
+ // Delete key
+ err = store.Delete(key)
+ require.NoError(t, err)
+
+ // Verify deletion
+ retrievedValue, err = store.Get(key)
+ require.NoError(t, err)
+ require.Nil(t, retrievedValue)
+
+ done <- true
+ }(i)
+ }
+
+ // Wait for all goroutines to complete
+ for i := 0; i < concurrentOps; i++ {
+ <-done
+ }
+}
diff --git a/cassandra/config.go b/cassandra/config.go
new file mode 100644
index 00000000..3c49b008
--- /dev/null
+++ b/cassandra/config.go
@@ -0,0 +1,73 @@
+package cassandra
+
+import (
+ "time"
+
+ "github.com/gocql/gocql"
+)
+
+// Config defines the configuration options for the Cassandra storage
+type Config struct {
+ // Optional. Default is localhost
+ // Hosts is a list of Cassandra nodes to connect to.
+ Hosts []string
+ // Optional. Default is gofiber
+ // Keyspace is the name of the Cassandra keyspace to use.
+ Keyspace string
+ // Optional. Default is kv_store
+ /// Table is the name of the Cassandra table to use.
+ Table string
+ // Optional. Default is Quorum
+ // Consistency is the Cassandra consistency level.
+ Consistency gocql.Consistency
+ // Optional. Default is 10 minutes
+ // Expiration is the time after which an entry is considered expired.
+ Expiration time.Duration
+ // Optional. Default is false
+ // Reset is a flag to reset the database.
+ Reset bool
+}
+
+// ConfigDefault is the default config
+var ConfigDefault = Config{
+ Hosts: []string{"localhost:9042"},
+ Keyspace: "gofiber",
+ Table: "kv_store",
+ Consistency: gocql.Quorum,
+ Reset: false,
+ Expiration: 10 * time.Minute,
+}
+
+// ConfigDefault is the default config
+func configDefault(config ...Config) Config {
+ // Return default config if nothing provided
+ if len(config) < 1 {
+ return ConfigDefault
+ }
+
+ // Override default config
+ cfg := config[0]
+
+ // Set default values
+ if len(cfg.Hosts) == 0 {
+ cfg.Hosts = ConfigDefault.Hosts
+ }
+
+ if cfg.Keyspace == "" {
+ cfg.Keyspace = ConfigDefault.Keyspace
+ }
+
+ if cfg.Table == "" {
+ cfg.Table = ConfigDefault.Table
+ }
+
+ if cfg.Consistency == 0 {
+ cfg.Consistency = ConfigDefault.Consistency
+ }
+
+ if cfg.Expiration == 0 {
+ cfg.Expiration = ConfigDefault.Expiration
+ }
+
+ return cfg
+}
diff --git a/cassandra/go.mod b/cassandra/go.mod
new file mode 100644
index 00000000..557997c5
--- /dev/null
+++ b/cassandra/go.mod
@@ -0,0 +1,67 @@
+module github.com/gofiber/storage/cassandra/v2
+
+go 1.23.0
+
+require (
+ github.com/gocql/gocql v1.7.0
+ github.com/stretchr/testify v1.10.0
+ github.com/testcontainers/testcontainers-go/modules/cassandra v0.36.0
+)
+
+require (
+ dario.cat/mergo v1.0.1 // indirect
+ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
+ github.com/Microsoft/go-winio v0.6.2 // indirect
+ github.com/cenkalti/backoff/v4 v4.2.1 // indirect
+ github.com/containerd/log v0.1.0 // indirect
+ github.com/containerd/platforms v0.2.1 // indirect
+ github.com/cpuguy83/dockercfg v0.3.2 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/distribution/reference v0.6.0 // indirect
+ github.com/docker/docker v28.0.1+incompatible // indirect
+ github.com/docker/go-connections v0.5.0 // indirect
+ github.com/docker/go-units v0.5.0 // indirect
+ github.com/ebitengine/purego v0.8.2 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/snappy v0.0.3 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
+ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
+ github.com/klauspost/compress v1.17.4 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
+ github.com/magiconair/properties v1.8.9 // indirect
+ github.com/moby/docker-image-spec v1.3.1 // indirect
+ github.com/moby/patternmatcher v0.6.0 // indirect
+ github.com/moby/sys/sequential v0.5.0 // indirect
+ github.com/moby/sys/user v0.1.0 // indirect
+ github.com/moby/sys/userns v0.1.0 // indirect
+ github.com/moby/term v0.5.0 // indirect
+ github.com/morikuni/aec v1.0.0 // indirect
+ github.com/opencontainers/go-digest v1.0.0 // indirect
+ github.com/opencontainers/image-spec v1.1.1 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
+ github.com/shirou/gopsutil/v4 v4.25.1 // indirect
+ github.com/sirupsen/logrus v1.9.3 // indirect
+ github.com/testcontainers/testcontainers-go v0.36.0 // indirect
+ github.com/tklauser/go-sysconf v0.3.12 // indirect
+ github.com/tklauser/numcpus v0.6.1 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
+ go.opentelemetry.io/otel v1.35.0 // indirect
+ go.opentelemetry.io/otel/metric v1.35.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.32.0 // indirect
+ go.opentelemetry.io/otel/trace v1.35.0 // indirect
+ golang.org/x/crypto v0.35.0 // indirect
+ golang.org/x/sys v0.31.0 // indirect
+ google.golang.org/grpc v1.70.0 // indirect
+ google.golang.org/protobuf v1.36.5 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/cassandra/go.sum b/cassandra/go.sum
new file mode 100644
index 00000000..287fe403
--- /dev/null
+++ b/cassandra/go.sum
@@ -0,0 +1,202 @@
+dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
+dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
+github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
+github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
+github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
+github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
+github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
+github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
+github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
+github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
+github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
+github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
+github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
+github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus=
+github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
+github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
+github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
+github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
+github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
+github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
+github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
+github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
+github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
+github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
+github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/testcontainers/testcontainers-go v0.36.0 h1:YpffyLuHtdp5EUsI5mT4sRw8GZhO/5ozyDT1xWGXt00=
+github.com/testcontainers/testcontainers-go v0.36.0/go.mod h1:yk73GVJ0KUZIHUtFna6MO7QS144qYpoY8lEEtU9Hed0=
+github.com/testcontainers/testcontainers-go/modules/cassandra v0.36.0 h1:vIOOfizBKSHfVcs+5u/VS6Zn4Bbo1lYM28DhHYJm4i8=
+github.com/testcontainers/testcontainers-go/modules/cassandra v0.36.0/go.mod h1:ZsSC3MYjRLXLccXMnBth8Qh4AkS2HWzGobVhMMY3Z/k=
+github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
+github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
+github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
+github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
+go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
+go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
+go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
+go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
+go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
+go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
+go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
+go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
+go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
+golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
+golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
+google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
+google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
+google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
+google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
+gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=