-
Notifications
You must be signed in to change notification settings - Fork 645
Expand file tree
/
Copy pathdoc.go
More file actions
849 lines (848 loc) · 38.8 KB
/
doc.go
File metadata and controls
849 lines (848 loc) · 38.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
/*
* 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.
*/
/*
* Content before git sha 34fdeebefcbf183ed7f916f931aa0586fdaa1b40
* Copyright (c) 2016, The Gocql authors,
* provided under the BSD-3-Clause License.
* See the NOTICE file distributed with this work for additional information.
*/
// Package gocql implements a fast and robust Cassandra driver for the
// Go programming language.
//
// # Upgrading to a new major version
//
// For detailed migration instructions between major versions, see the [upgrade guide].
//
// # Connecting to the cluster
//
// Pass a list of initial node IP addresses to [NewCluster] to create a new cluster configuration:
//
// cluster := gocql.NewCluster("192.168.1.1", "192.168.1.2", "192.168.1.3")
//
// Port can be specified as part of the address, the above is equivalent to:
//
// cluster := gocql.NewCluster("192.168.1.1:9042", "192.168.1.2:9042", "192.168.1.3:9042")
//
// It is recommended to use the value set in the Cassandra config for broadcast_address or listen_address,
// an IP address not a domain name. This is because events from Cassandra will use the configured IP
// address, which is used to index connected hosts. If the domain name specified resolves to more than 1 IP address
// then the driver may connect multiple times to the same host, and will not mark the node being down or up from events.
//
// Then you can customize more options (see [ClusterConfig]):
//
// cluster.Keyspace = "example"
// cluster.Consistency = gocql.Quorum
// cluster.ProtoVersion = 4
//
// The driver tries to automatically detect the protocol version to use if not set, but you might want to set the
// protocol version explicitly, as it's not defined which version will be used in certain situations (for example
// during upgrade of the cluster when some of the nodes support different set of protocol versions than other nodes).
//
// Native protocol versions 3, 4, and 5 are supported.
// For features like per-query keyspace setting and timestamp override, use native protocol version 5.
//
// The driver advertises the module name and version in the STARTUP message, so servers are able to detect the version.
// If you use replace directive in go.mod, the driver will send information about the replacement module instead.
//
// When ready, create a session from the configuration. Don't forget to [Session.Close] the session once you are done with it:
//
// session, err := cluster.CreateSession()
// if err != nil {
// return err
// }
// defer session.Close()
//
// # Reconnection and Host Recovery
//
// The driver provides robust reconnection mechanisms to handle network failures and host outages.
// Two main configuration settings control reconnection behavior:
//
// - ClusterConfig.ReconnectionPolicy: Controls retry behavior for immediate connection failures, query-driven reconnection, and background recovery
// - ClusterConfig.ReconnectInterval: Controls background recovery of DOWN hosts
//
// [ReconnectionPolicy] controls retry behavior for immediate connection failures, query-driven reconnection, and background recovery.
//
// [ConstantReconnectionPolicy] provides predictable fixed intervals (Default):
//
// cluster.ReconnectionPolicy = &gocql.ConstantReconnectionPolicy{
// MaxRetries: 3, // Maximum retry attempts
// Interval: 1 * time.Second, // Fixed interval between retries
// }
//
// [ExponentialReconnectionPolicy] provides gentler backoff with capped intervals:
//
// cluster.ReconnectionPolicy = &gocql.ExponentialReconnectionPolicy{
// MaxRetries: 5, // 6 total attempts: 0+1+2+4+8+15 = 30s total
// InitialInterval: 1 * time.Second, // Initial retry interval
// MaxInterval: 15 * time.Second, // Maximum retry interval (prevents excessive delays)
// }
//
// Note: Each reconnection attempt sequence starts fresh from InitialInterval.
// This applies both to immediate connection failures and each ClusterConfig.ReconnectInterval cycle.
// For example, if ClusterConfig.ReconnectInterval=60s, every 60 seconds the background process
// starts a new sequence beginning at InitialInterval, not continuing from where
// the previous 60-second cycle ended.
//
// ClusterConfig.ReconnectInterval controls background recovery of DOWN hosts. When a host is marked DOWN, this process periodically
// attempts reconnection using the same ReconnectionPolicy settings:
//
// cluster.ReconnectInterval = 60 * time.Second // Check DOWN hosts every 60 seconds (default)
//
// Setting ClusterConfig.ReconnectInterval to 0 disables background reconnection.
//
// The reconnection process involves several components working together in a specific sequence:
//
// 1. Individual Connection Reconnection - Immediate retry attempts for failed connections within UP hosts
// 2. Host State Management - Marking hosts DOWN when all connections fail and retries are exhausted
// 3. Background Recovery - Periodic reconnection attempts for DOWN hosts via ReconnectInterval
//
// Individual connection reconnection occurs when connections fail within a host's pool, and the driver immediately attempts
// reconnection using ReconnectionPolicy. For hosts that remain UP (with working connections), failed individual connections
// are reconnected on a query-driven basis - every query execution triggers asynchronous reconnection attempts for missing
// connections. Queries proceed immediately using available connections while reconnection happens asynchronously in the
// background. There is no query latency impact from reconnection attempts. Multiple concurrent queries to the same host
// will not trigger parallel reconnection attempts - the driver uses a "filling" flag to ensure only one reconnection process runs per host.
//
// Host state management determines when a host is marked DOWN. Only when ALL connections to a host fail and ReconnectionPolicy
// retries are exhausted does the host get marked DOWN. DOWN hosts are excluded from query routing. Since DOWN hosts don't
// receive queries, they cannot benefit from query-driven reconnection. This is why the background ClusterConfig.ReconnectInterval process
// is essential for DOWN host recovery.
//
// Background recovery through ClusterConfig.ReconnectInterval periodically attempts to reconnect DOWN hosts using ReconnectionPolicy settings.
// Event-driven recovery also triggers immediate reconnection when Cassandra sends STATUS_CHANGE UP events.
//
// The complete recovery process follows these steps:
//
// 1. Connection fails → ReconnectionPolicy immediate retry attempts
// 2. Query-driven recovery → Each query to partially-failed hosts triggers reconnection attempts
// 3. Host marked DOWN → All connections failed and retries exhausted
// 4. Background recovery → ClusterConfig.ReconnectInterval process attempts reconnection using ReconnectionPolicy
// 5. Event recovery → Cassandra events can trigger immediate reconnection
//
// Here's a practical example showing how the settings work together:
//
// cluster.ReconnectionPolicy = &gocql.ExponentialReconnectionPolicy{
// MaxRetries: 8, // 9 total attempts (0s, 1s, 2s, 4s, 8s, 16s, 30s, 30s, 30s)
// InitialInterval: 1 * time.Second, // Starts at 1 second
// MaxInterval: 30 * time.Second, // Caps exponential growth at 30 seconds
// }
//
// cluster.ReconnectInterval = 60 * time.Second // Background checks every 60 seconds
//
// Timeline Example: With this configuration, when a host loses ALL connections:
//
// T=0:00 - Host has 2 connections, both fail
// T=0:00 - Immediate reconnection attempt 1: 0s delay
// T=0:01 - Immediate reconnection attempt 2: 1s delay
// T=0:03 - Immediate reconnection attempt 3: 2s delay
// T=0:07 - Immediate reconnection attempt 4: 4s delay
// T=0:15 - Immediate reconnection attempt 5: 8s delay
// T=0:31 - Immediate reconnection attempt 6: 16s delay
// T=1:01 - Immediate reconnection attempt 7: 30s delay (capped by MaxInterval)
// T=1:31 - Immediate reconnection attempt 8: 30s delay
// T=2:01 - Immediate reconnection attempt 9: 30s delay
// T=2:31 - All immediate attempts failed, host marked DOWN
//
// T=3:31 - Background recovery attempt 1 starts (60s after DOWN)
// ReconnectionPolicy sequence: 0s, 1s, 2s, 4s, 8s, 16s, 30s, 30s, 30s
//
// T=4:31 - ClusterConfig.ReconnectInterval timer fires, tick buffered (timer channel capacity=1)
// T=5:31 - ClusterConfig.ReconnectInterval timer fires again, there is already a tick buffered so ignore
// T=5:32 - Background recovery attempt 1 completes (after 2:01), immediately reads buffered tick
// T=5:32 - Background recovery attempt 2 starts (buffered timer from T=5:31)
// T=6:32 - ClusterConfig.ReconnectInterval timer fires, tick buffered
// T=7:32 - ClusterConfig.ReconnectInterval timer fires again, there is already a tick buffered so ignore
// T=7:33 - Background recovery attempt 2 completes (after 2:01), immediately reads buffered tick
// T=7:33 - Background recovery attempt 3 starts (buffered timer from T=7:32)
//
// Timer Behavior and Predictable Timing:
//
// Note: [time.Ticker].C has buffer capacity=1, but Go drops ticks for "slow receivers."
// The reconnection process is a slow receiver (taking 2+ minutes vs 60s interval).
// First missed tick gets buffered, subsequent ticks are dropped. When reconnection
// completes, it immediately reads the buffered tick and starts the next attempt.
// This causes attempts to run back-to-back at the ReconnectionPolicy duration interval
// (121s) instead of the intended ClusterConfig.ReconnectInterval (60s), but timing remains predictable.
//
// To avoid this buffering/dropping behavior, ensure ClusterConfig.ReconnectInterval is larger than the
// total ReconnectionPolicy duration. You can achieve this by either:
//
// 1. Increasing ClusterConfig.ReconnectInterval (e.g., 150s > 121s sequence duration)
// 2. Reducing ReconnectionPolicy duration (e.g., 30s sequence < 60s ClusterConfig.ReconnectInterval)
//
// This ensures predictable timing with each recovery attempt starting exactly ClusterConfig.ReconnectInterval apart.
// Approach #2 provides faster recovery while maintaining predictable timing.
//
// Individual failed connections within UP hosts are reconnected asynchronously without affecting query performance.
//
// Best Practices and Configuration Guidelines:
//
// - ReconnectionPolicy: Use ConstantReconnectionPolicy for predictable behavior or ExponentialReconnectionPolicy
// for gentler recovery. Aggressive settings affect background reconnection frequency but don't impact query latency
// - ClusterConfig.ReconnectInterval: Set to 30-60 seconds for most cases. Shorter intervals provide faster recovery but more traffic
// - Timing Predictability: For predictable background recovery timing, ensure ClusterConfig.ReconnectInterval exceeds the total
// ReconnectionPolicy sequence duration. This prevents Go's ticker from buffering/dropping ticks due to "slow receiver"
// behavior. You can achieve this by either increasing ClusterConfig.ReconnectInterval or reducing ReconnectionPolicy duration
// (fewer retries/shorter intervals). The latter approach provides faster recovery while maintaining predictable timing
// - Monitoring: Enable logging to observe reconnection behavior and tune settings
//
// # Compression
//
// The driver supports Snappy and LZ4 compression of protocol frames.
//
// For Snappy compression (via [github.com/apache/cassandra-gocql-driver/v2/snappy] package):
//
// import "github.com/apache/cassandra-gocql-driver/v2/snappy"
//
// cluster.Compressor = &snappy.SnappyCompressor{}
//
// For LZ4 compression (via [github.com/apache/cassandra-gocql-driver/v2/lz4] package):
//
// import "github.com/apache/cassandra-gocql-driver/v2/lz4"
//
// cluster.Compressor = &lz4.LZ4Compressor{}
//
// Both compressors use efficient append-like semantics for optimal performance and memory usage.
//
// # Structured Logging
//
// The driver provides structured logging through the [StructuredLogger] interface.
// Built-in integrations are available for popular logging libraries:
//
// For Zap logger (via [github.com/apache/cassandra-gocql-driver/v2/gocqlzap] package):
//
// import "github.com/apache/cassandra-gocql-driver/v2/gocqlzap"
//
// zapLogger, _ := zap.NewProduction()
// cluster.Logger = gocqlzap.NewZapLogger(zapLogger)
//
// For Zerolog (via [github.com/apache/cassandra-gocql-driver/v2/gocqlzerolog] package):
//
// import "github.com/apache/cassandra-gocql-driver/v2/gocqlzerolog"
//
// zerologLogger := zerolog.New(os.Stdout).With().Timestamp().Logger()
// cluster.Logger = gocqlzerolog.NewZerologLogger(&zerologLogger)
//
// You can also use the built-in standard library logger:
//
// cluster.Logger = gocql.NewLogger(gocql.LogLevelInfo)
//
// # Native Protocol Version 5 Features
//
// Native protocol version 5 provides several advanced capabilities:
//
// Set keyspace for individual queries (useful for multi-tenant applications):
//
// err := session.Query("SELECT * FROM table").SetKeyspace("tenant1").Exec()
//
// Target queries to specific nodes (useful for virtual tables in Cassandra 4.0+):
//
// err := session.Query("SELECT * FROM system_views.settings").
// SetHostID("host-uuid").Exec()
//
// Use current timestamp override for testing and consistency:
//
// err := session.Query("INSERT INTO table (id, data) VALUES (?, ?)").
// WithNowInSeconds(specificTimestamp).
// Bind(id, data).Exec()
//
// These features are also available on batch operations, excluding SetHostID():
//
// err := session.Batch(LoggedBatch).
// Query("INSERT INTO table (id, data) VALUES (?, ?)", id, data).
// SetKeyspace("tenant1").
// WithNowInSeconds(specificTimestamp).
// Exec()
//
// # Authentication
//
// CQL protocol uses a SASL-based authentication mechanism and so consists of an exchange of server challenges and
// client response pairs. The details of the exchanged messages depend on the authenticator used.
//
// To use authentication, set ClusterConfig.Authenticator or ClusterConfig.AuthProvider.
//
// [PasswordAuthenticator] is provided to use for username/password authentication:
//
// cluster := gocql.NewCluster("192.168.1.1", "192.168.1.2", "192.168.1.3")
// cluster.Authenticator = gocql.PasswordAuthenticator{
// Username: "user",
// Password: "password"
// }
// session, err := cluster.CreateSession()
// if err != nil {
// return err
// }
// defer session.Close()
//
// By default, PasswordAuthenticator will attempt to authenticate regardless of what implementation the server returns
// in its AUTHENTICATE message as its authenticator, (e.g. org.apache.cassandra.auth.PasswordAuthenticator). If you
// wish to restrict this you may use PasswordAuthenticator.AllowedAuthenticators:
//
// cluster.Authenticator = gocql.PasswordAuthenticator {
// Username: "user",
// Password: "password"
// AllowedAuthenticators: []string{"org.apache.cassandra.auth.PasswordAuthenticator"},
// }
//
// # Transport layer security
//
// It is possible to secure traffic between the client and server with TLS.
//
// To use TLS, set the ClusterConfig.SslOpts field. [SslOptions] embeds *[crypto/tls.Config] so you can set that directly.
// There are also helpers to load keys/certificates from files.
//
// Warning: Due to historical reasons, the SslOptions is insecure by default, so you need to set SslOptions.EnableHostVerification
// to true if no Config is set. Most users should set SslOptions.Config to a *crypto/tls.Config.
// SslOptions and crypto/tls.Config.InsecureSkipVerify interact as follows:
//
// Config.InsecureSkipVerify | EnableHostVerification | Result
// Config is nil | false | do not verify host
// Config is nil | true | verify host
// false | false | verify host
// true | false | do not verify host
// false | true | verify host
// true | true | verify host
//
// For example:
//
// cluster := gocql.NewCluster("192.168.1.1", "192.168.1.2", "192.168.1.3")
// cluster.SslOpts = &gocql.SslOptions{
// EnableHostVerification: true,
// }
// session, err := cluster.CreateSession()
// if err != nil {
// return err
// }
// defer session.Close()
//
// # Cluster schema metadata caching
//
// The driver supports cluster schema metadata caching. The driver queries system.schema_* and system_schema.* tables
// depending on Cassandra version for all keyspaces in async manner during session initialization. Also, the driver will
// refresh the cached metadata in case of receiving schema change events from Cassandra.
//
// This is useful for efficient query routing with [TokenAwareHostPolicy] which is a recommended policy for production use.
// There are three modes of metadata caching:
//
// - Full: All cluster schema metadata is cached. This is the default mode.
// - KeyspaceOnly: Only keyspace schema metadata is cached.
// - Disabled: No schema metadata is cached.
//
// See [MetadataCacheMode] and [ClusterConfig.Metadata] for more details.
//
// # Data-center awareness and query routing
//
// To route queries to local DC first, use [DCAwareRoundRobinPolicy]. For example, if the datacenter you
// want to primarily connect is called dc1 (as configured in the database):
//
// cluster := gocql.NewCluster("192.168.1.1", "192.168.1.2", "192.168.1.3")
// cluster.PoolConfig.HostSelectionPolicy = gocql.DCAwareRoundRobinPolicy("dc1")
//
// The driver can route queries to nodes that hold data replicas based on partition key (preferring local DC).
//
// cluster := gocql.NewCluster("192.168.1.1", "192.168.1.2", "192.168.1.3")
// cluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(gocql.DCAwareRoundRobinPolicy("dc1"))
//
// Note that [TokenAwareHostPolicy] can take options such as [ShuffleReplicas], [DoNotShuffleReplicas] and [NonLocalReplicasFallback].
//
// We recommend running with a token aware host policy in production for maximum performance.
//
// The driver can only use token-aware routing for queries where all partition key columns are query parameters.
// For example, instead of
//
// session.Query("select value from mytable where pk1 = 'abc' AND pk2 = ?", "def")
//
// use
//
// session.Query("select value from mytable where pk1 = ? AND pk2 = ?", "abc", "def")
//
// # Rack-level awareness
//
// The DCAwareRoundRobinPolicy can be replaced with [RackAwareRoundRobinPolicy], which takes two parameters, datacenter and rack.
//
// Instead of dividing hosts with two tiers (local datacenter and remote datacenters) it divides hosts into three
// (the local rack, the rest of the local datacenter, and everything else).
//
// RackAwareRoundRobinPolicy can be combined with TokenAwareHostPolicy in the same way as DCAwareRoundRobinPolicy.
//
// # Executing queries
//
// Create queries with [Session.Query]. Query values must not be reused between different executions and must not be
// modified after starting execution of the query.
//
// To execute a query without reading results, use [Query.Exec]:
//
// err := session.Query(`INSERT INTO tweet (timeline, id, text) VALUES (?, ?, ?)`,
// "me", gocql.TimeUUID(), "hello world").WithContext(ctx).Exec()
//
// Single row can be read by calling [Query.Scan]:
//
// err := session.Query(`SELECT id, text FROM tweet WHERE timeline = ? LIMIT 1`,
// "me").WithContext(ctx).Consistency(gocql.One).Scan(&id, &text)
//
// Multiple rows can be read using [Iter.Scanner]:
//
// scanner := session.Query(`SELECT id, text FROM tweet WHERE timeline = ?`,
// "me").WithContext(ctx).Iter().Scanner()
// for scanner.Next() {
// var (
// id gocql.UUID
// text string
// )
// err = scanner.Scan(&id, &text)
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println("Tweet:", id, text)
// }
// // scanner.Err() closes the iterator, so scanner nor iter should be used afterwards.
// if err := scanner.Err(); err != nil {
// log.Fatal(err)
// }
//
// See Example for complete example.
//
// # Vector types (Cassandra 5.0+)
//
// The driver supports Cassandra 5.0 vector types, enabling powerful vector search capabilities:
//
// // Create a table with vector column
// err := session.Query(`CREATE TABLE vectors (
// id int PRIMARY KEY,
// embedding vector<float, 128>
// )`).Exec()
//
// // Insert vector data
// embedding := make([]float32, 128)
// // ... populate embedding values
// err = session.Query("INSERT INTO vectors (id, embedding) VALUES (?, ?)",
// 1, embedding).Exec()
//
// // Query vector data
// var retrievedEmbedding []float32
// err = session.Query("SELECT embedding FROM vectors WHERE id = ?", 1).
// Scan(&retrievedEmbedding)
//
// Vector types support various element types including basic types, collections, and user-defined types.
// Vector search requires Cassandra 5.0 or later.
//
// # Prepared statements
//
// The driver automatically prepares DML queries (SELECT/INSERT/UPDATE/DELETE/BATCH statements) and maintains a cache
// of prepared statements.
// CQL protocol does not support preparing other query types.
//
// When using native protocol >= 4, it is possible to use [UnsetValue] as the bound value of a column.
// This will cause the database to ignore writing the column.
// The main advantage is the ability to keep the same prepared statement even when you don't
// want to update some fields, where before you needed to make another prepared statement.
//
// # Executing multiple queries concurrently
//
// [Session] is safe to use from multiple goroutines, so to execute multiple concurrent queries, just execute them
// from several worker goroutines. Gocql provides synchronously-looking API (as recommended for Go APIs) and the queries
// are executed asynchronously at the protocol level.
//
// results := make(chan error, 2)
// go func() {
// results <- session.Query(`INSERT INTO tweet (timeline, id, text) VALUES (?, ?, ?)`,
// "me", gocql.TimeUUID(), "hello world 1").Exec()
// }()
// go func() {
// results <- session.Query(`INSERT INTO tweet (timeline, id, text) VALUES (?, ?, ?)`,
// "me", gocql.TimeUUID(), "hello world 2").Exec()
// }()
//
// # Nulls
//
// Null values are are unmarshalled as zero value of the type. If you need to distinguish for example between text
// column being null and empty string, you can unmarshal into *string variable instead of string.
//
// var text *string
// err := scanner.Scan(&text)
// if err != nil {
// // handle error
// }
// if text != nil {
// // not null
// }
// else {
// // null
// }
//
// See Example_nulls for full example.
//
// # Reusing slices
//
// The driver reuses backing memory of slices when unmarshalling. This is an optimization so that a buffer does not
// need to be allocated for every processed row. However, you need to be careful when storing the slices to other
// memory structures.
//
// scanner := session.Query(`SELECT myints FROM table WHERE pk = ?`, "key").WithContext(ctx).Iter().Scanner()
// var myInts []int
// for scanner.Next() {
// // This scan reuses backing store of myInts for each row.
// err = scanner.Scan(&myInts)
// if err != nil {
// log.Fatal(err)
// }
// }
//
// When you want to save the data for later use, pass a new slice every time. A common pattern is to declare the
// slice variable within the scanner loop:
//
// scanner := session.Query(`SELECT myints FROM table WHERE pk = ?`, "key").WithContext(ctx).Iter().Scanner()
// for scanner.Next() {
// var myInts []int
// // This scan always gets pointer to fresh myInts slice, so does not reuse memory.
// err = scanner.Scan(&myInts)
// if err != nil {
// log.Fatal(err)
// }
// }
//
// # Paging
//
// The driver supports paging of results with automatic prefetch, see ClusterConfig.PageSize,
// [Query.PageSize], and [Query.Prefetch].
//
// It is also possible to control the paging manually with Query.PageState (this disables automatic prefetch).
// Manual paging is useful if you want to store the page state externally, for example in a URL to allow users
// browse pages in a result. You might want to sign/encrypt the paging state when exposing it externally since
// it contains data from primary keys.
//
// Paging state is specific to the native protocol version and the exact query used. It is meant as opaque state that
// should not be modified. If you send paging state from different query or protocol version, then the behaviour
// is not defined (you might get unexpected results or an error from the server). For example, do not send paging state
// returned by node using protocol version 3 to a node using protocol version 4. Also, when using protocol version 4,
// paging state between Cassandra 2.2 and 3.0 is incompatible (see [CASSANDRA-10880]).
//
// The driver does not check whether the paging state is from the same protocol version/statement.
// You might want to validate yourself as this could be a problem if you store paging state externally.
// For example, if you store paging state in a URL, the URLs might become broken when you upgrade your cluster.
//
// Call Query.PageState(nil) to fetch just the first page of the query results. Pass the page state returned by
// [Iter.PageState] to Query.PageState of a subsequent query to get the next page. If the length of slice returned
// by Iter.PageState is zero, there are no more pages available (or an error occurred).
//
// Using too low values of ClusterConfig.PageSize will negatively affect performance, a value below 100 is probably too low.
// While Cassandra returns exactly ClusterConfig.PageSize items (except for last page) in a page currently, the protocol authors
// explicitly reserved the right to return smaller or larger amount of items in a page for performance reasons, so don't
// rely on the page having the exact count of items.
//
// See Example_paging for an example of manual paging.
//
// # Dynamic list of columns
//
// There are certain situations when you don't know the list of columns in advance, mainly when the query is supplied
// by the user. [Iter.Columns], Iter.RowData, Iter.MapScan and Iter.SliceMap can be used to handle this case.
//
// See Example_dynamicColumns.
//
// # Batches
//
// The CQL protocol supports sending batches of DML statements (INSERT/UPDATE/DELETE) and so does gocql.
// Use [Session.Batch] to create a new batch and then fill-in details of individual queries.
// Then execute the batch with [Batch.Exec].
//
// Logged batches ensure atomicity, either all or none of the operations in the batch will succeed, but they have
// overhead to ensure this property.
// Unlogged batches don't have the overhead of logged batches, but don't guarantee atomicity.
// Updates of counters are handled specially by Cassandra so batches of counter updates have to use [CounterBatch] type.
// A counter batch can only contain statements to update counters.
//
// For unlogged batches it is recommended to send only single-partition batches (i.e. all statements in the batch should
// involve only a single partition).
// Multi-partition batch needs to be split by the coordinator node and re-sent to
// correct nodes.
// With single-partition batches you can send the batch directly to the node for the partition without incurring the
// additional network hop.
//
// It is also possible to pass entire BEGIN BATCH .. APPLY BATCH statement to [Query.Exec].
// There are differences how those are executed.
// BEGIN BATCH statement passed to Query.Exec is prepared as a whole in a single statement.
// Batch.Exec prepares individual statements in the batch.
// If you have variable-length batches using the same statement, using Batch.Exec is more efficient.
//
// See Example_batch for an example.
//
// The [Batch] API provides a fluent interface for building and executing batch operations:
//
// // Create and execute a batch using fluent API
// err := session.Batch(LoggedBatch).
// Query("INSERT INTO table1 (id, name) VALUES (?, ?)", id1, name1).
// Query("INSERT INTO table2 (id, value) VALUES (?, ?)", id2, value2).
// Exec()
//
// // Lightweight transactions with batches
// applied, iter, err := session.Batch(LoggedBatch).
// Query("INSERT INTO users (id, name) VALUES (?, ?) IF NOT EXISTS", id, name).
// ExecCAS()
// if err != nil {
// // handle error
// }
// if !applied {
// // handle conditional failure
// }
//
// # Lightweight transactions
//
// [Query.ScanCAS] or [Query.MapScanCAS] can be used to execute a single-statement lightweight transaction (an
// INSERT/UPDATE .. IF statement) and reading its result. See example for Query.MapScanCAS.
//
// Multiple-statement lightweight transactions can be executed as a logged batch that contains at least one conditional
// statement. All the conditions must return true for the batch to be applied. You can use [Batch.ExecCAS] and
// [Batch.MapExecCAS] when executing the batch to learn about the result of the LWT. See example for
// Batch.MapExecCAS.
//
// # SERIAL Consistency for Reads
//
// The driver supports SERIAL and LOCAL_SERIAL consistency levels on SELECT statements.
// These special consistency levels are designed for reading data that may have been written using
// lightweight transactions (LWT) with IF conditions, providing linearizable consistency guarantees.
//
// When to use SERIAL consistency levels:
//
// Use SERIAL or LOCAL_SERIAL consistency when you need to:
// - Read the most recent committed value after lightweight transactions
// - Ensure linearizable consistency (stronger than eventual consistency)
// - Read data that might have uncommitted lightweight transactions in progress
//
// Important considerations:
//
// - SERIAL reads have higher latency and resource usage than normal reads
// - Only use when you specifically need linearizable consistency
// - If a SERIAL read finds an uncommitted transaction, it will commit that transaction
// - Most applications should use regular consistency levels (ONE, QUORUM, etc.)
//
// # Immutable Execution
//
// [Query] and [Batch] objects follow an immutable execution model that enables safe reuse and concurrent
// execution without object mutation.
//
// Query and Batch Object Reusability:
//
// Query and Batch objects remain unchanged during execution, allowing for safe reuse and concurrent execution:
//
// // Create a query once
// query := session.Query("SELECT * FROM users WHERE id = ?", userID)
//
// // Safe to execute multiple times
// iter1 := query.Iter()
// defer iter1.Close()
//
// iter2 := query.Iter() // Same query, separate execution
// defer iter2.Close()
//
// // Safe to use from multiple goroutines
// go func() {
// iter := query.Iter()
// defer iter.Close()
// // ... process results
// }()
//
// The same applies to Batch objects:
//
// // Create batch once using fluent API
// batch := session.Batch(LoggedBatch).
// Query("INSERT INTO table1 (id, name) VALUES (?, ?)", id1, name1).
// Query("INSERT INTO table2 (id, value) VALUES (?, ?)", id2, value2)
//
// // Safe to execute multiple times
// err1 := batch.Exec()
// err2 := batch.Exec() // Same batch, separate execution
//
// Execution Metrics and Metadata:
//
// Execution-specific information such as metrics, attempts, and latency are available through the [Iter] object
// returned by execution. This provides per-execution metrics while keeping the original objects unchanged:
//
// query := session.Query("SELECT * FROM users WHERE id = ?", userID)
// iter := query.Iter()
// defer iter.Close()
//
// // Access execution metrics through Iter
// attempts := iter.Attempts() // Number of times this execution was attempted
// latency := iter.Latency() // Average latency per attempt in nanoseconds
// keyspace := iter.Keyspace() // Keyspace the query was executed against
// table := iter.Table() // Table the query was executed against (if determinable)
// host := iter.Host() // Host that executed the query
//
// For batches, execution methods that return an Iter (like ExecCAS) provide the same metrics:
//
// // Execute CAS operation and get metrics through Iter using fluent API
// applied, iter, err := session.Batch(LoggedBatch).
// Query("INSERT INTO users (id, name) VALUES (?, ?) IF NOT EXISTS", id, name).
// ExecCAS()
// defer iter.Close()
//
// if err != nil {
// log.Printf("Batch failed after %d attempts with average latency %d ns",
// iter.Attempts(), iter.Latency())
// }
//
// # Retries and speculative execution
//
// Queries can be marked as idempotent. Marking the query as idempotent tells the driver that the query can be executed
// multiple times without affecting its result. Non-idempotent queries are not eligible for retrying nor speculative
// execution.
//
// Idempotent queries are retried in case of errors based on the configured RetryPolicy.
//
// Queries can be retried even before they fail by setting a SpeculativeExecutionPolicy. The policy can
// cause the driver to retry on a different node if the query is taking longer than a specified delay even before the
// driver receives an error or timeout from the server. When a query is speculatively executed, the original execution
// is still executing. The two parallel executions of the query race to return a result, the first received result will
// be returned.
//
// # User-defined types
//
// Cassandra User-Defined Types (UDTs) are composite data types that group related fields together.
// GoCQL provides several ways to work with UDTs in Go, from simple struct mapping to advanced custom marshaling.
//
// Basic UDT Usage with Structs:
//
// The simplest way to work with UDTs is using Go structs with `cql` tags:
//
// // Cassandra UDT definition:
// // CREATE TYPE address (street text, city text, zip_code int);
//
// type Address struct {
// Street string `cql:"street"`
// City string `cql:"city"`
// ZipCode int `cql:"zip_code"`
// }
//
// // Usage in queries
// addr := Address{Street: "123 Main St", City: "Anytown", ZipCode: 12345}
// err := session.Query("INSERT INTO users (id, address) VALUES (?, ?)",
// userID, addr).Exec()
//
// // Reading UDTs
// var readAddr Address
// err = session.Query("SELECT address FROM users WHERE id = ?",
// userID).Scan(&readAddr)
//
// Field Mapping:
//
// GoCQL maps struct fields to UDT fields using two strategies:
//
// 1. CQL tags: Use `cql:"field_name"` to explicitly map fields
// 2. Name matching: If no tag is present, field names must match exactly (case-sensitive)
//
// type MyUDT struct {
// FieldA int32 `cql:"field_a"` // Maps to UDT field "field_a"
// FieldB string `cql:"field_b"` // Maps to UDT field "field_b"
// FieldC string // Maps to UDT field "FieldC" (exact name match)
// }
//
// Working with Maps:
//
// UDTs can also be marshaled to/from `map[string]interface{}`:
//
// // Marshal to map
// var udtMap map[string]interface{}
// err := session.Query("SELECT address FROM users WHERE id = ?",
// userID).Scan(&udtMap)
//
// // Access fields
// street := udtMap["street"].(string)
// zipCode := udtMap["zip_code"].(int)
//
// Advanced Custom Marshaling:
//
// For complex scenarios, implement the UDTMarshaler and UDTUnmarshaler interfaces:
//
// type CustomUDT struct {
// fieldA string
// fieldB int32
// }
//
// // UDTMarshaler for writing to Cassandra
// func (c CustomUDT) MarshalUDT(name string, info TypeInfo) ([]byte, error) {
// switch name {
// case "field_a":
// return Marshal(info, c.fieldA)
// case "field_b":
// return Marshal(info, c.fieldB)
// default:
// return nil, nil // Unknown fields set to null
// }
// }
//
// // UDTUnmarshaler for reading from Cassandra
// func (c *CustomUDT) UnmarshalUDT(name string, info TypeInfo, data []byte) error {
// switch name {
// case "field_a":
// return Unmarshal(info, data, &c.fieldA)
// case "field_b":
// return Unmarshal(info, data, &c.fieldB)
// default:
// return nil // Ignore unknown fields for forward compatibility
// }
// }
//
// Nested UDTs and Collections:
//
// UDTs can contain other UDTs and collection types:
//
// // Cassandra definitions:
// // CREATE TYPE address (street text, city text);
// // CREATE TYPE person (name text, addresses list<frozen<address>>);
//
// type Person struct {
// Name string `cql:"name"`
// Addresses []Address `cql:"addresses"`
// }
//
// See Example_userDefinedTypesMap, Example_userDefinedTypesStruct, ExampleUDTMarshaler, ExampleUDTUnmarshaler.
//
// # Metrics and tracing
//
// It is possible to provide observer implementations that could be used to gather metrics:
//
// - [QueryObserver] for monitoring individual queries.
// - [BatchObserver] for monitoring batch queries.
// - [ConnectObserver] for monitoring new connections from the driver to the database.
// - [FrameHeaderObserver] for monitoring individual protocol frames.
//
// CQL protocol also supports tracing of queries. When enabled, the database will write information about
// internal events that happened during execution of the query. You can use [Query.Trace] to request tracing and receive
// the session ID that the database used to store the trace information in system_traces.sessions and
// system_traces.events tables. [NewTraceWriter] returns an implementation of [Tracer] that writes the events to a writer.
// Gathering trace information might be essential for debugging and optimizing queries, but writing traces has overhead,
// so this feature should not be used on production systems with very high load unless you know what you are doing.
//
// # Event listening
//
// The driver supports event listening for cluster schema changes and host state changes. It is useful for monitoring and debugging the driver's behavior.
// You can register listeners for the following events:
// - Cluster schema changes: keyspace created, updated, dropped, table created, updated, dropped, function created, updated, dropped, aggregate created, updated, dropped
// - Host state changes: host up, host down
// - Topology changes: new host added, host removed, host moved
//
// Schema and topology callbacks are sequential, but host status callbacks can be concurrent. If your listener
// implements multiple event types (e.g., both topology and status changes), add proper synchronization.
//
// Also, if there is a need to know when the session is ready to be used, you can register a [SessionReadyListener].
//
// Consider using multiplexer types like [SchemaListenersMux], [HostListenersMux], and [SessionReadyListenersMux]
// if you need to have multiple listeners for the same event.
//
// See [ClusterConfig.Metadata] for more details and Example_eventListeners for a complete implementation example.
package gocql // import "github.com/apache/cassandra-gocql-driver/v2"