diff --git a/CHANGELOG.md b/CHANGELOG.md index 07dbf3c3d..f044c2144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add Query and Batch to ObservedQuery and ObservedBatch (CASSGO-73) - Add way to create HostInfo objects for testing purposes (CASSGO-71) - Add missing Context methods on Query and Batch (CASSGO-81) +- Update example and test code for 2.0 release (CASSGO-80) ### Changed diff --git a/batch_test.go b/batch_test.go index 7f7d00253..57eafd640 100644 --- a/batch_test.go +++ b/batch_test.go @@ -93,10 +93,10 @@ func TestBatch_WithNowInSeconds(t *testing.T) { t.Fatal(err) } - b := session.NewBatch(LoggedBatch) + b := session.Batch(LoggedBatch) b.WithNowInSeconds(0) b.Query("INSERT INTO batch_now_in_seconds (id, val) VALUES (?, ?) USING TTL 20", 1, "val") - if err := session.ExecuteBatch(b); err != nil { + if err := b.Exec(); err != nil { t.Fatal(err) } @@ -140,10 +140,10 @@ func TestBatch_SetKeyspace(t *testing.T) { ids := []int{1, 2} texts := []string{"val1", "val2"} - b := session.NewBatch(LoggedBatch).SetKeyspace("gocql_keyspace_override_test") + b := session.Batch(LoggedBatch).SetKeyspace("gocql_keyspace_override_test") b.Query("INSERT INTO batch_keyspace(id, value) VALUES (?, ?)", ids[0], texts[0]) b.Query("INSERT INTO batch_keyspace(id, value) VALUES (?, ?)", ids[1], texts[1]) - err = session.ExecuteBatch(b) + err = b.Exec() if err != nil { t.Fatal(err) } diff --git a/cassandra_test.go b/cassandra_test.go index b92c3cd62..d90207729 100644 --- a/cassandra_test.go +++ b/cassandra_test.go @@ -3899,15 +3899,15 @@ func TestStmtCacheUsesOverriddenKeyspace(t *testing.T) { // Inserting data via Batch to ensure that batches // properly accounts for keyspace overriding - b1 := session.NewBatch(LoggedBatch) + b1 := session.Batch(LoggedBatch) b1.Query(insertQuery, 1) - err = session.ExecuteBatch(b1) + err = b1.Exec() require.NoError(t, err) - b2 := session.NewBatch(LoggedBatch) + b2 := session.Batch(LoggedBatch) b2.SetKeyspace("gocql_test_stmt_cache") b2.Query(insertQuery, 2) - err = session.ExecuteBatch(b2) + err = b2.Exec() require.NoError(t, err) var scannedID int diff --git a/example_batch_test.go b/example_batch_test.go index 9f778c847..2322a3b4d 100644 --- a/example_batch_test.go +++ b/example_batch_test.go @@ -49,31 +49,46 @@ func Example_batch() { ctx := context.Background() - b := session.Batch(gocql.UnloggedBatch).WithContext(ctx) + // Example 1: Simple batch using the Query() method - recommended approach + batch := session.Batch(gocql.LoggedBatch) + batch.Query("INSERT INTO example.batches (pk, ck, description) VALUES (?, ?, ?)", 1, 2, "1.2") + batch.Query("INSERT INTO example.batches (pk, ck, description) VALUES (?, ?, ?)", 1, 3, "1.3") + + err = batch.ExecContext(ctx) + if err != nil { + log.Fatal(err) + } + + // Example 2: Advanced batch usage with Entries for more control + b := session.Batch(gocql.UnloggedBatch) b.Entries = append(b.Entries, gocql.BatchEntry{ Stmt: "INSERT INTO example.batches (pk, ck, description) VALUES (?, ?, ?)", - Args: []interface{}{1, 2, "1.2"}, + Args: []interface{}{1, 4, "1.4"}, Idempotent: true, }) b.Entries = append(b.Entries, gocql.BatchEntry{ Stmt: "INSERT INTO example.batches (pk, ck, description) VALUES (?, ?, ?)", - Args: []interface{}{1, 3, "1.3"}, + Args: []interface{}{1, 5, "1.5"}, Idempotent: true, }) - err = b.Exec() + err = b.ExecContext(ctx) if err != nil { log.Fatal(err) } - err = b.Query("INSERT INTO example.batches (pk, ck, description) VALUES (?, ?, ?)", 1, 4, "1.4"). - Query("INSERT INTO example.batches (pk, ck, description) VALUES (?, ?, ?)", 1, 5, "1.5"). - Exec() + // Example 3: Fluent style chaining + err = session.Batch(gocql.LoggedBatch). + Query("INSERT INTO example.batches (pk, ck, description) VALUES (?, ?, ?)", 1, 6, "1.6"). + Query("INSERT INTO example.batches (pk, ck, description) VALUES (?, ?, ?)", 1, 7, "1.7"). + ExecContext(ctx) if err != nil { log.Fatal(err) } - scanner := session.Query("SELECT pk, ck, description FROM example.batches").Iter().Scanner() + // Verification: Display all inserted data + fmt.Println("All inserted data:") + scanner := session.Query("SELECT pk, ck, description FROM example.batches").IterContext(ctx).Scanner() for scanner.Next() { var pk, ck int32 var description string @@ -83,8 +98,16 @@ func Example_batch() { } fmt.Println(pk, ck, description) } + + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + // All inserted data: // 1 2 1.2 // 1 3 1.3 // 1 4 1.4 // 1 5 1.5 + // 1 6 1.6 + // 1 7 1.7 } diff --git a/example_dynamic_columns_test.go b/example_dynamic_columns_test.go index 984bc730d..38f4c66ec 100644 --- a/example_dynamic_columns_test.go +++ b/example_dynamic_columns_test.go @@ -56,7 +56,7 @@ func Example_dynamicColumns() { defer session.Close() printQuery := func(ctx context.Context, session *gocql.Session, stmt string, values ...interface{}) error { - iter := session.Query(stmt, values...).WithContext(ctx).Iter() + iter := session.Query(stmt, values...).IterContext(ctx) fmt.Println(stmt) w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) diff --git a/example_lwt_batch_test.go b/example_lwt_batch_test.go index c65ce9618..1d0acad99 100644 --- a/example_lwt_batch_test.go +++ b/example_lwt_batch_test.go @@ -32,8 +32,8 @@ import ( gocql "github.com/apache/cassandra-gocql-driver/v2" ) -// ExampleSession_MapExecuteBatchCAS demonstrates how to execute a batch lightweight transaction. -func ExampleSession_MapExecuteBatchCAS() { +// ExampleBatch_MapExecCAS demonstrates how to execute a batch lightweight transaction. +func ExampleBatch_MapExecCAS() { /* The example assumes the following CQL was used to setup the keyspace: create keyspace example with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }; create table example.my_lwt_batch_table(pk text, ck text, version int, value text, PRIMARY KEY(pk, ck)); @@ -50,13 +50,13 @@ func ExampleSession_MapExecuteBatchCAS() { ctx := context.Background() err = session.Query("INSERT INTO example.my_lwt_batch_table (pk, ck, version, value) VALUES (?, ?, ?, ?)", - "pk1", "ck1", 1, "a").WithContext(ctx).Exec() + "pk1", "ck1", 1, "a").ExecContext(ctx) if err != nil { log.Fatal(err) } err = session.Query("INSERT INTO example.my_lwt_batch_table (pk, ck, version, value) VALUES (?, ?, ?, ?)", - "pk1", "ck2", 1, "A").WithContext(ctx).Exec() + "pk1", "ck2", 1, "A").ExecContext(ctx) if err != nil { log.Fatal(err) } @@ -72,7 +72,7 @@ func ExampleSession_MapExecuteBatchCAS() { Args: []interface{}{"B", "pk1", "ck2", ck2Version}, }) m := make(map[string]interface{}) - applied, iter, err := b.WithContext(ctx).MapExecCAS(m) + applied, iter, err := b.MapExecCASContext(ctx, m) if err != nil { log.Fatal(err) } @@ -91,7 +91,7 @@ func ExampleSession_MapExecuteBatchCAS() { printState := func() { scanner := session.Query("SELECT ck, value FROM example.my_lwt_batch_table WHERE pk = ?", "pk1"). - WithContext(ctx).Iter().Scanner() + IterContext(ctx).Scanner() for scanner.Next() { var ck, value string err = scanner.Scan(&ck, &value) diff --git a/example_lwt_test.go b/example_lwt_test.go index 17dc3c29e..a7aa0a875 100644 --- a/example_lwt_test.go +++ b/example_lwt_test.go @@ -50,21 +50,21 @@ func ExampleQuery_MapScanCAS() { ctx := context.Background() err = session.Query("INSERT INTO example.my_lwt_table (pk, version, value) VALUES (?, ?, ?)", - 1, 1, "a").WithContext(ctx).Exec() + 1, 1, "a").ExecContext(ctx) if err != nil { log.Fatal(err) } m := make(map[string]interface{}) applied, err := session.Query("UPDATE example.my_lwt_table SET value = ? WHERE pk = ? IF version = ?", - "b", 1, 0).WithContext(ctx).MapScanCAS(m) + "b", 1, 0).MapScanCASContext(ctx, m) if err != nil { log.Fatal(err) } fmt.Println(applied, m) var value string - err = session.Query("SELECT value FROM example.my_lwt_table WHERE pk = ?", 1).WithContext(ctx). - Scan(&value) + err = session.Query("SELECT value FROM example.my_lwt_table WHERE pk = ?", 1). + ScanContext(ctx, &value) if err != nil { log.Fatal(err) } @@ -72,15 +72,15 @@ func ExampleQuery_MapScanCAS() { m = make(map[string]interface{}) applied, err = session.Query("UPDATE example.my_lwt_table SET value = ? WHERE pk = ? IF version = ?", - "b", 1, 1).WithContext(ctx).MapScanCAS(m) + "b", 1, 1).MapScanCASContext(ctx, m) if err != nil { log.Fatal(err) } fmt.Println(applied, m) var value2 string - err = session.Query("SELECT value FROM example.my_lwt_table WHERE pk = ?", 1).WithContext(ctx). - Scan(&value2) + err = session.Query("SELECT value FROM example.my_lwt_table WHERE pk = ?", 1). + ScanContext(ctx, &value2) if err != nil { log.Fatal(err) } diff --git a/example_marshaler_test.go b/example_marshaler_test.go index fce4ded05..3b6c81927 100644 --- a/example_marshaler_test.go +++ b/example_marshaler_test.go @@ -95,20 +95,20 @@ func Example_marshalerUnmarshaler() { patch: 3, } err = session.Query("INSERT INTO example.my_marshaler_table (pk, value) VALUES (?, ?)", - 1, value).WithContext(ctx).Exec() + 1, value).ExecContext(ctx) if err != nil { log.Fatal(err) } var stringValue string - err = session.Query("SELECT value FROM example.my_marshaler_table WHERE pk = 1").WithContext(ctx). - Scan(&stringValue) + err = session.Query("SELECT value FROM example.my_marshaler_table WHERE pk = 1"). + ScanContext(ctx, &stringValue) if err != nil { log.Fatal(err) } fmt.Println(stringValue) var unmarshaledValue MyMarshaler - err = session.Query("SELECT value FROM example.my_marshaler_table WHERE pk = 1").WithContext(ctx). - Scan(&unmarshaledValue) + err = session.Query("SELECT value FROM example.my_marshaler_table WHERE pk = 1"). + ScanContext(ctx, &unmarshaledValue) if err != nil { log.Fatal(err) } diff --git a/example_setkeyspace_test.go b/example_setkeyspace_test.go new file mode 100644 index 000000000..93b9c1ca8 --- /dev/null +++ b/example_setkeyspace_test.go @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gocql_test + +import ( + "context" + "fmt" + "log" + + gocql "github.com/apache/cassandra-gocql-driver/v2" +) + +// Example_setKeyspace demonstrates the SetKeyspace method that allows +// specifying keyspace per query, available with Protocol 5+ (Cassandra 4.0+). +// +// This example shows the complete keyspace precedence hierarchy: +// 1. Keyspace in CQL query string (keyspace.table) - HIGHEST precedence +// 2. SetKeyspace() method - MIDDLE precedence +// 3. Default session keyspace - LOWEST precedence +func Example_setKeyspace() { + /* The example assumes the following CQL was used to setup the keyspaces: + create keyspace example with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }; + create keyspace example2 with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }; + create keyspace example3 with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }; + create table example.users(id int, name text, PRIMARY KEY(id)); + create table example2.users(id int, name text, PRIMARY KEY(id)); + create table example3.users(id int, name text, PRIMARY KEY(id)); + */ + cluster := gocql.NewCluster("localhost:9042") + cluster.ProtoVersion = 5 // SetKeyspace requires Protocol 5+, available in Cassandra 4.0+ + cluster.Keyspace = "example" // Set a default keyspace + session, err := cluster.CreateSession() + if err != nil { + log.Fatal(err) + } + defer session.Close() + + ctx := context.Background() + + // Example 1: Keyspace Precedence Hierarchy Demonstration + fmt.Println("Demonstrating complete keyspace precedence hierarchy:") + fmt.Println("1. Keyspace in CQL (keyspace.table) - HIGHEST") + fmt.Println("2. SetKeyspace() method - MIDDLE") + fmt.Println("3. Default session keyspace - LOWEST") + fmt.Println() + + // Insert test data + // Default keyspace (example) - lowest precedence + err = session.Query("INSERT INTO users (id, name) VALUES (?, ?)"). + Bind(1, "Alice"). + ExecContext(ctx) + if err != nil { + log.Fatal(err) + } + + // SetKeyspace overrides default - middle precedence + err = session.Query("INSERT INTO users (id, name) VALUES (?, ?)"). + SetKeyspace("example2"). + Bind(1, "Bob"). + ExecContext(ctx) + if err != nil { + log.Fatal(err) + } + + // Fully qualified table name - highest precedence + err = session.Query("INSERT INTO example3.users (id, name) VALUES (?, ?)"). + Bind(1, "Charlie"). + ExecContext(ctx) + if err != nil { + log.Fatal(err) + } + + // Example 2: Fully qualified table names override SetKeyspace + fmt.Println("Example 2: Fully qualified table names take precedence over SetKeyspace:") + + // This query sets keyspace to "example2" via SetKeyspace, but the fully qualified + // table name "example3.users" takes precedence - query will target example3 + err = session.Query("INSERT INTO example3.users (id, name) VALUES (?, ?)"). + SetKeyspace("example2"). // This is IGNORED because CQL has "example3.users" + Bind(2, "Diana"). + ExecContext(ctx) + if err != nil { + log.Fatal(err) + } + fmt.Println("Inserted Diana into example3.users despite SetKeyspace(\"example2\")") + + // Verify data went to example3, not example2 + var count int + iter := session.Query("SELECT COUNT(*) FROM users"). + SetKeyspace("example2"). + IterContext(ctx) + if iter.Scan(&count) { + fmt.Printf("Count in example2: %d (only Bob)\n", count) + } + if err := iter.Close(); err != nil { + log.Fatal(err) + } + + iter = session.Query("SELECT COUNT(*) FROM users"). + SetKeyspace("example3"). + IterContext(ctx) + if iter.Scan(&count) { + fmt.Printf("Count in example3: %d (Charlie and Diana)\n", count) + } + if err := iter.Close(); err != nil { + log.Fatal(err) + } + + // Example 3: SetKeyspace overrides default keyspace + fmt.Println("\nExample 3: SetKeyspace overrides default keyspace:") + + // Query using default keyspace (no SetKeyspace) + var id int + var name string + iter = session.Query("SELECT id, name FROM users WHERE id = ?", 1). + IterContext(ctx) // Uses default keyspace "example" + if iter.Scan(&id, &name) { + fmt.Printf("Default keyspace (example): ID %d, Name %s\n", id, name) + } + if err := iter.Close(); err != nil { + log.Fatal(err) + } + + // SetKeyspace overrides default + iter = session.Query("SELECT id, name FROM users WHERE id = ?", 1). + SetKeyspace("example2"). // Override default keyspace + IterContext(ctx) + if iter.Scan(&id, &name) { + fmt.Printf("SetKeyspace override (example2): ID %d, Name %s\n", id, name) + } + if err := iter.Close(); err != nil { + log.Fatal(err) + } + + // Example 4: Mixed query patterns in one workflow + fmt.Println("\nExample 4: Using all precedence levels in one workflow:") + + // Query from default keyspace + iter = session.Query("SELECT name FROM users WHERE id = 1").IterContext(ctx) + if iter.Scan(&name) { + fmt.Printf("Default (example): %s\n", name) + } + iter.Close() + + // Query using SetKeyspace + iter = session.Query("SELECT name FROM users WHERE id = 1"). + SetKeyspace("example2").IterContext(ctx) + if iter.Scan(&name) { + fmt.Printf("SetKeyspace (example2): %s\n", name) + } + iter.Close() + + // Query using fully qualified table name (ignores both default and SetKeyspace) + iter = session.Query("SELECT name FROM example3.users WHERE id = 1"). + SetKeyspace("example2"). // This is ignored due to qualified table name + IterContext(ctx) + if iter.Scan(&name) { + fmt.Printf("Qualified name (example3): %s\n", name) + } + iter.Close() + + // Demonstrating complete keyspace precedence hierarchy: + // 1. Keyspace in CQL (keyspace.table) - HIGHEST + // 2. SetKeyspace() method - MIDDLE + // 3. Default session keyspace - LOWEST + // + // Example 2: Fully qualified table names take precedence over SetKeyspace: + // Inserted Diana into example3.users despite SetKeyspace("example2") + // Count in example2: 1 (only Bob) + // Count in example3: 2 (Charlie and Diana) + // + // Example 3: SetKeyspace overrides default keyspace: + // Default keyspace (example): ID 1, Name Alice + // SetKeyspace override (example2): ID 1, Name Bob + // + // Example 4: Using all precedence levels in one workflow: + // Default (example): Alice + // SetKeyspace (example2): Bob + // Qualified name (example3): Charlie +} diff --git a/example_structured_logging_test.go b/example_structured_logging_test.go new file mode 100644 index 000000000..c5bd36645 --- /dev/null +++ b/example_structured_logging_test.go @@ -0,0 +1,217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gocql_test + +import ( + "context" + "log" + "os" + + "github.com/rs/zerolog" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + gocql "github.com/apache/cassandra-gocql-driver/v2" + "github.com/apache/cassandra-gocql-driver/v2/gocqlzap" + "github.com/apache/cassandra-gocql-driver/v2/gocqlzerolog" +) + +// Example_structuredLogging demonstrates the new structured logging features +// introduced in 2.0.0. The driver now supports structured logging with proper +// log levels and integration with popular logging libraries like Zap and Zerolog. +// This example shows production-ready configurations for structured logging with +// proper component separation to distinguish between application and driver logs. +func Example_structuredLogging() { + /* The example assumes the following CQL was used to setup the keyspace: + create keyspace example with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }; + create table example.log_demo(id int, value text, PRIMARY KEY(id)); + */ + + ctx := context.Background() + + // Example 1: Using Zap logger integration + // Create a production Zap logger with structured JSON output and human-readable timestamps + // Production config uses JSON encoding, info level, and proper error handling + config := zap.NewProductionConfig() + config.EncoderConfig.TimeKey = "timestamp" + config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // Human-readable timestamp format + + zapLogger, err := config.Build() + if err != nil { + log.Fatal(err) + } + defer zapLogger.Sync() + + // Create base logger with service identifier + baseLogger := zapLogger.With(zap.String("service", "gocql-app")) + + // Create application and driver loggers with component identifiers + appLogger := baseLogger.With(zap.String("component", "app")) + driverLogger := baseLogger.With(zap.String("component", "gocql-driver")) + + appLogger.Info("Starting Zap structured logging example", + zap.String("example", "structured_logging"), + zap.String("logger_type", "zap")) + + // Create gocql logger from driver logger + gocqlZapLogger := gocqlzap.NewUnnamedZapLogger(driverLogger) + + zapCluster := gocql.NewCluster("localhost:9042") + zapCluster.Keyspace = "example" + zapCluster.Logger = gocqlZapLogger + + zapSession, err := zapCluster.CreateSession() + if err != nil { + appLogger.Fatal("Failed to create session", zap.Error(err)) + } + defer zapSession.Close() + + // Perform some operations that will generate logs + appLogger.Info("Inserting data into database", + zap.String("operation", "insert"), + zap.Int("record_id", 1)) + + err = zapSession.Query("INSERT INTO example.log_demo (id, value) VALUES (?, ?)"). + Bind(1, "zap logging demo"). + ExecContext(ctx) + if err != nil { + appLogger.Error("Insert operation failed", zap.Error(err)) + log.Fatal(err) + } + + appLogger.Info("Querying data from database", + zap.String("operation", "select"), + zap.Int("record_id", 1)) + + var id int + var value string + iter := zapSession.Query("SELECT id, value FROM example.log_demo WHERE id = ?"). + Bind(1). + IterContext(ctx) + + if iter.Scan(&id, &value) { + // Successfully scanned the row + } + err = iter.Close() + if err != nil { + appLogger.Error("Select operation failed", zap.Error(err)) + log.Fatal(err) + } + + appLogger.Info("Database operation completed successfully", + zap.String("operation", "select"), + zap.Int("record_id", id), + zap.String("record_value", value)) + + // Example 2: Using Zerolog integration + // Create a production Zerolog logger with structured JSON output + // Production config includes timestamps, service info, and appropriate log level + baseZerologLogger := zerolog.New(os.Stdout). + Level(zerolog.InfoLevel). + With(). + Timestamp(). + Str("service", "gocql-app"). + Logger() + + // Create application logger with component identifier + appZerologLogger := baseZerologLogger.With(). + Str("component", "app"). + Logger() + + // Create driver logger with component identifier + driverZerologLogger := baseZerologLogger.With(). + Str("component", "gocql-driver"). + Logger() + + appZerologLogger.Info(). + Str("example", "structured_logging"). + Str("logger_type", "zerolog"). + Msg("Starting Zerolog structured logging example") + + // Create gocql logger from driver logger + gocqlZerologLogger := gocqlzerolog.NewUnnamedZerologLogger(driverZerologLogger) + + zerologCluster := gocql.NewCluster("localhost:9042") + zerologCluster.Keyspace = "example" + zerologCluster.Logger = gocqlZerologLogger + + zerologSession, err := zerologCluster.CreateSession() + if err != nil { + appZerologLogger.Fatal().Err(err).Msg("Failed to create session") + } + defer zerologSession.Close() + + // Perform operations with Zerolog + appZerologLogger.Info(). + Str("operation", "insert"). + Int("record_id", 2). + Msg("Inserting data into database") + + err = zerologSession.Query("INSERT INTO example.log_demo (id, value) VALUES (?, ?)"). + Bind(2, "zerolog logging demo"). + ExecContext(ctx) + if err != nil { + appZerologLogger.Error().Err(err).Msg("Insert operation failed") + log.Fatal(err) + } + + appZerologLogger.Info(). + Str("operation", "select"). + Int("record_id", 2). + Msg("Querying data from database") + + iter = zerologSession.Query("SELECT id, value FROM example.log_demo WHERE id = ?"). + Bind(2). + IterContext(ctx) + + if iter.Scan(&id, &value) { + // Successfully scanned the row + } + err = iter.Close() + if err != nil { + appZerologLogger.Error().Err(err).Msg("Select operation failed") + log.Fatal(err) + } + + appZerologLogger.Info(). + Str("operation", "select"). + Int("record_id", id). + Str("record_value", value). + Msg("Database operation completed successfully") + + // Example 1 - Zap structured logging output (JSON format): + // {"level":"info","timestamp":"2023-12-31T12:00:00.000Z","msg":"Starting Zap structured logging example","service":"gocql-app","component":"app","example":"structured_logging","logger_type":"zap"} + // {"level":"info","timestamp":"2023-12-31T12:00:00.100Z","msg":"Discovered protocol version.","service":"gocql-app","component":"gocql-driver","protocol_version":5} + // {"level":"info","timestamp":"2023-12-31T12:00:00.200Z","msg":"Control connection connected to host.","service":"gocql-app","component":"gocql-driver","host_addr":"127.0.0.1","host_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"} + // {"level":"info","timestamp":"2023-12-31T12:00:00.300Z","msg":"Refreshed ring.","service":"gocql-app","component":"gocql-driver","ring":"[127.0.0.1-a1b2c3d4-e5f6-7890-abcd-ef1234567890:UP]"} + // {"level":"info","timestamp":"2023-12-31T12:00:00.400Z","msg":"Session initialized successfully.","service":"gocql-app","component":"gocql-driver"} + // {"level":"info","timestamp":"2023-12-31T12:00:01.000Z","msg":"Inserting data into database","service":"gocql-app","component":"app","operation":"insert","record_id":1} + // {"level":"info","timestamp":"2023-12-31T12:00:02.000Z","msg":"Querying data from database","service":"gocql-app","component":"app","operation":"select","record_id":1} + // {"level":"info","timestamp":"2023-12-31T12:00:03.000Z","msg":"Database operation completed successfully","service":"gocql-app","component":"app","operation":"select","record_id":1,"record_value":"zap logging demo"} + // + // Example 2 - Zerolog structured logging output (JSON format): + // {"level":"info","service":"gocql-app","component":"app","example":"structured_logging","logger_type":"zerolog","time":"2023-12-31T12:00:10Z","message":"Starting Zerolog structured logging example"} + // {"level":"info","service":"gocql-app","component":"gocql-driver","protocol_version":5,"time":"2023-12-31T12:00:10.1Z","message":"Discovered protocol version."} + // {"level":"info","service":"gocql-app","component":"gocql-driver","host_addr":"127.0.0.1","host_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","time":"2023-12-31T12:00:10.2Z","message":"Control connection connected to host."} + // {"level":"info","service":"gocql-app","component":"gocql-driver","ring":"[127.0.0.1-a1b2c3d4-e5f6-7890-abcd-ef1234567890:UP]","time":"2023-12-31T12:00:10.3Z","message":"Refreshed ring."} + // {"level":"info","service":"gocql-app","component":"gocql-driver","time":"2023-12-31T12:00:10.4Z","message":"Session initialized successfully."} + // {"level":"info","service":"gocql-app","component":"app","operation":"insert","record_id":2,"time":"2023-12-31T12:00:11Z","message":"Inserting data into database"} + // {"level":"info","service":"gocql-app","component":"app","operation":"select","record_id":2,"time":"2023-12-31T12:00:12Z","message":"Querying data from database"} + // {"level":"info","service":"gocql-app","component":"app","operation":"select","record_id":2,"record_value":"zerolog logging demo","time":"2023-12-31T12:00:13Z","message":"Database operation completed successfully"} +} diff --git a/example_test.go b/example_test.go index 0fd432ed9..917251985 100644 --- a/example_test.go +++ b/example_test.go @@ -52,7 +52,7 @@ func Example() { // insert a tweet if err := session.Query(`INSERT INTO tweet (timeline, id, text) VALUES (?, ?, ?)`, - "me", gocql.TimeUUID(), "hello world").WithContext(ctx).Exec(); err != nil { + "me", gocql.TimeUUID(), "hello world").ExecContext(ctx); err != nil { log.Fatal(err) } @@ -63,7 +63,7 @@ func Example() { * the value 'me'. The secondary index that we created earlier will be * used for optimizing the search */ if err := session.Query(`SELECT id, text FROM tweet WHERE timeline = ? LIMIT 1`, - "me").WithContext(ctx).Consistency(gocql.One).Scan(&id, &text); err != nil { + "me").Consistency(gocql.One).ScanContext(ctx, &id, &text); err != nil { log.Fatal(err) } fmt.Println("Tweet:", id, text) @@ -71,7 +71,7 @@ func Example() { // list all tweets scanner := session.Query(`SELECT id, text FROM tweet WHERE timeline = ?`, - "me").WithContext(ctx).Iter().Scanner() + "me").IterContext(ctx).Scanner() for scanner.Next() { err = scanner.Scan(&id, &text) if err != nil { diff --git a/example_udt_map_test.go b/example_udt_map_test.go index 099008adc..03018c314 100644 --- a/example_udt_map_test.go +++ b/example_udt_map_test.go @@ -56,14 +56,14 @@ func Example_userDefinedTypesMap() { "field_b": 42, } err = session.Query("INSERT INTO example.my_udt_table (pk, value) VALUES (?, ?)", - 1, value).WithContext(ctx).Exec() + 1, value).ExecContext(ctx) if err != nil { log.Fatal(err) } + // Read the UDT value back var readValue map[string]interface{} - - err = session.Query("SELECT value FROM example.my_udt_table WHERE pk = 1").WithContext(ctx).Scan(&readValue) + err = session.Query("SELECT value FROM example.my_udt_table WHERE pk = 1").ScanContext(ctx, &readValue) if err != nil { log.Fatal(err) } diff --git a/example_udt_marshaler_test.go b/example_udt_marshaler_test.go index 3e20cacd6..b2dc6acc4 100644 --- a/example_udt_marshaler_test.go +++ b/example_udt_marshaler_test.go @@ -75,7 +75,7 @@ func ExampleUDTMarshaler() { fieldB: 42, } err = session.Query("INSERT INTO example.my_udt_table (pk, value) VALUES (?, ?)", - 1, value).WithContext(ctx).Exec() + 1, value).ExecContext(ctx) if err != nil { log.Fatal(err) } diff --git a/example_udt_struct_test.go b/example_udt_struct_test.go index 1038741c3..1bfdc7b92 100644 --- a/example_udt_struct_test.go +++ b/example_udt_struct_test.go @@ -61,14 +61,14 @@ func Example_userDefinedTypesStruct() { FieldB: 42, } err = session.Query("INSERT INTO example.my_udt_table (pk, value) VALUES (?, ?)", - 1, value).WithContext(ctx).Exec() + 1, value).ExecContext(ctx) if err != nil { log.Fatal(err) } + // Read the UDT value back var readValue MyUDT - - err = session.Query("SELECT value FROM example.my_udt_table WHERE pk = 1").WithContext(ctx).Scan(&readValue) + err = session.Query("SELECT value FROM example.my_udt_table WHERE pk = 1").ScanContext(ctx, &readValue) if err != nil { log.Fatal(err) } diff --git a/example_udt_unmarshaler_test.go b/example_udt_unmarshaler_test.go index 3467b161d..2db6c611c 100644 --- a/example_udt_unmarshaler_test.go +++ b/example_udt_unmarshaler_test.go @@ -72,8 +72,9 @@ func ExampleUDTUnmarshaler() { ctx := context.Background() + // Read the UDT value back var value MyUDTUnmarshaler - err = session.Query("SELECT value FROM example.my_udt_table WHERE pk = 1").WithContext(ctx).Scan(&value) + err = session.Query("SELECT value FROM example.my_udt_table WHERE pk = 1").ScanContext(ctx, &value) if err != nil { log.Fatal(err) } diff --git a/example_vector_test.go b/example_vector_test.go new file mode 100644 index 000000000..8e88c3272 --- /dev/null +++ b/example_vector_test.go @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gocql_test + +import ( + "context" + "fmt" + "log" + + gocql "github.com/apache/cassandra-gocql-driver/v2" +) + +// Example_vector demonstrates how to work with vector search in Cassandra 5.0+. +// This example shows Cassandra's native vector search capabilities using ANN (Approximate Nearest Neighbor) +// search with ORDER BY ... ANN OF syntax for finding similar vectors. +// Note: Requires Cassandra 5.0+ and a SAI index on the vector column for ANN search. +func Example_vector() { + /* The example assumes the following CQL was used to setup the keyspace: + create keyspace example with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }; + create table example.vectors( + id int, + item_name text, + embedding vector, + PRIMARY KEY(id) + ); + -- Create SAI index for vector search (required for ANN) + CREATE INDEX IF NOT EXISTS ann_index ON example.vectors(embedding) USING 'sai'; + */ + cluster := gocql.NewCluster("localhost:9042") + cluster.Keyspace = "example" + cluster.ProtoVersion = 4 + session, err := cluster.CreateSession() + if err != nil { + log.Fatal(err) + } + defer session.Close() + + ctx := context.Background() + + // Create the table first (if it doesn't exist) + err = session.Query(`CREATE TABLE IF NOT EXISTS example.vectors( + id int, + item_name text, + embedding vector, + PRIMARY KEY(id) + )`).ExecContext(ctx) + if err != nil { + log.Fatal(err) + } + + // Create SAI index for vector search (required for ANN search) + fmt.Println("Creating SAI index for vector search...") + err = session.Query(`CREATE INDEX IF NOT EXISTS ann_index + ON example.vectors(embedding) USING 'sai'`).ExecContext(ctx) + if err != nil { + log.Fatal(err) + } + + // Insert sample vectors representing different items + // These could be embeddings from ML models for products, documents, etc. + vectorData := []struct { + id int + name string + vector []float32 + }{ + {1, "apple", []float32{0.8, 0.2, 0.1, 0.9, 0.3}}, + {2, "orange", []float32{0.7, 0.3, 0.2, 0.8, 0.4}}, + {3, "banana", []float32{0.6, 0.4, 0.9, 0.2, 0.7}}, + {4, "grape", []float32{0.9, 0.1, 0.3, 0.7, 0.5}}, + {5, "watermelon", []float32{0.2, 0.8, 0.6, 0.4, 0.9}}, + {6, "strawberry", []float32{0.8, 0.3, 0.2, 0.9, 0.4}}, + {7, "pineapple", []float32{0.3, 0.7, 0.8, 0.1, 0.6}}, + {8, "mango", []float32{0.7, 0.4, 0.5, 0.8, 0.2}}, + } + + // Insert all vectors + fmt.Println("Inserting sample vectors...") + for _, item := range vectorData { + err = session.Query("INSERT INTO example.vectors (id, item_name, embedding) VALUES (?, ?, ?)", + item.id, item.name, item.vector).ExecContext(ctx) + if err != nil { + log.Fatal(err) + } + } + + // Define a query vector (e.g., searching for items similar to "apple-like" characteristics) + queryVector := []float32{0.8, 0.2, 0.1, 0.9, 0.3} + fmt.Printf("Searching for vectors similar to: %v\n\n", queryVector) + + // Perform ANN (Approximate Nearest Neighbor) search using ORDER BY ... ANN OF + // This finds the 3 most similar vectors to our query vector + fmt.Println("Top 3 most similar items (using ANN search):") + iter := session.Query(` + SELECT id, item_name, embedding + FROM example.vectors + ORDER BY embedding ANN OF ? + LIMIT 3`, + queryVector, + ).IterContext(ctx) + + for { + var id int + var itemName string + var embedding []float32 + + if !iter.Scan(&id, &itemName, &embedding) { + break + } + fmt.Printf(" %s (ID: %d) - Vector: %v\n", itemName, id, embedding) + } + if err := iter.Close(); err != nil { + log.Fatal(err) + } + + fmt.Println() + + // Perform similarity search with a different query vector + queryVector2 := []float32{0.2, 0.8, 0.6, 0.4, 0.9} + fmt.Printf("Searching for vectors similar to: %v\n", queryVector2) + fmt.Println("Top 4 most similar items:") + + scanner := session.Query(` + SELECT id, item_name, embedding + FROM example.vectors + ORDER BY embedding ANN OF ? + LIMIT 4`, + queryVector2, + ).IterContext(ctx).Scanner() + + for scanner.Next() { + var id int + var itemName string + var embedding []float32 + + err = scanner.Scan(&id, &itemName, &embedding) + if err != nil { + log.Fatal(err) + } + fmt.Printf(" %s (ID: %d) - Vector: %v\n", itemName, id, embedding) + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + fmt.Println() + + // Basic vector retrieval (traditional approach) + fmt.Println("Basic vector retrieval by ID:") + var id int + var itemName string + var embedding []float32 + iter = session.Query("SELECT id, item_name, embedding FROM example.vectors WHERE id = ?", 1). + IterContext(ctx) + if !iter.Scan(&id, &itemName, &embedding) { + log.Fatal(iter.Close()) + } + fmt.Printf(" %s (ID: %d) - Vector: %v\n", itemName, id, embedding) + + fmt.Println() + + // Show all vectors for comparison + fmt.Println("All vectors in the database:") + allVectors := session.Query("SELECT id, item_name, embedding FROM example.vectors").IterContext(ctx) + for { + var id int + var itemName string + var embedding []float32 + + if !allVectors.Scan(&id, &itemName, &embedding) { + break + } + fmt.Printf(" %s (ID: %d) - Vector: %v\n", itemName, id, embedding) + } + if err := allVectors.Close(); err != nil { + log.Fatal(err) + } + + // Example output: + // Creating SAI index for vector search... + // Inserting sample vectors... + // Searching for vectors similar to: [0.8 0.2 0.1 0.9 0.3] + // + // Top 3 most similar items (using ANN search): + // apple (ID: 1) - Vector: [0.8 0.2 0.1 0.9 0.3] + // strawberry (ID: 6) - Vector: [0.8 0.3 0.2 0.9 0.4] + // orange (ID: 2) - Vector: [0.7 0.3 0.2 0.8 0.4] +} diff --git a/session_test.go b/session_test.go index 3fcb21630..3eeba22f1 100644 --- a/session_test.go +++ b/session_test.go @@ -78,7 +78,7 @@ func TestSessionAPI(t *testing.T) { err := testBatch.Exec() if err != ErrNoConnections { - t.Fatalf("expected session.ExecuteBatch to return '%v', got '%v'", ErrNoConnections, err) + t.Fatalf("expected batch.Exec to return '%v', got '%v'", ErrNoConnections, err) } s.Close() @@ -90,7 +90,7 @@ func TestSessionAPI(t *testing.T) { err = testBatch.Exec() if err != ErrSessionClosed { - t.Fatalf("expected session.ExecuteBatch to return '%v', got '%v'", ErrSessionClosed, err) + t.Fatalf("expected batch.Exec to return '%v', got '%v'", ErrSessionClosed, err) } }