Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ on:
types: [ opened, synchronize, reopened ]

jobs:
build:
name: Unit tests
unit-tests-amd64:
name: Unit tests AMD64
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -23,11 +23,28 @@ jobs:
run: make check
- name: Run unit tests
run: make test-unit

unit-tests-arm64:
name: Unit tests ARM64
runs-on: ubuntu-24.04-arm
strategy:
matrix:
go: [ '1.25', '1.26' ]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
- name: Run linting
run: make check
- name: Run unit tests
run: make test-unit

integration-cassandra:
timeout-minutes: 15
needs:
- build
- unit-tests-amd64
- unit-tests-arm64
name: Integration Tests
runs-on: ubuntu-latest
strategy:
Expand Down Expand Up @@ -93,7 +110,8 @@ jobs:
integration-auth-cassandra:
timeout-minutes: 15
needs:
- build
- unit-tests-amd64
- unit-tests-arm64
name: Integration Tests with auth
runs-on: ubuntu-latest
strategy:
Expand Down
16 changes: 16 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Agent guidance

This file is read by automated agents (security scanners, code analyzers,
AI assistants) operating on this repository. It points them at the
human-authored references they should consult before producing output.

## Security

Security model: [SECURITY.md](./SECURITY.md), which links to the Apache
Cassandra project security model.

This repository is part of the Apache Cassandra project. Its security model -
trust boundaries, in-scope / out-of-scope declarations, the security
properties the project provides and disclaims, and how findings are triaged -
is the umbrella Cassandra security model linked from SECURITY.md. Consult it
before reporting security issues.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.2.0]

### Added

- Security-model discoverability (CASSANDRA-21464)
- Improve host_source locking and ring refresh concurrency (CASSGO-121)
- Add PreparedMetadata (Keyspace, Table) and IsPrepared fields to ObservedQuery, and parallel PreparedMetadata / IsPrepared slices to ObservedBatch, for statement-level observability without CQL parsing (CASSGO-119)
- Query.Binding() method to override binding function for a query object.

### Fixed
- Correct protocol negotiation with non-Cassandra servers (CASSGO-131)
- LZ4 test fails on arm64 (CASSGO-128)

## [2.1.2]

### Fixed

- Prevent panic when using a HostFilter and keyspace is not replicated to every DC (CASSGO-122)
- system.peers fallback doesn't work in some scenarios (CASSGO-126)
- Many "Pool connection error" with small Session.Timeout (CASSGO-125)

## [2.1.1]

### Fixed
Expand Down
17 changes: 17 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Security Policy

## Reporting a Vulnerability

Please report suspected security vulnerabilities privately to the Apache
Security Team at <security@apache.org>, following the ASF process at
<https://www.apache.org/security/>. Do not open public GitHub issues or pull
requests for security reports.

## Security Model

This repository is part of the Apache Cassandra project. The project's
security model - what is in and out of scope, the trust boundaries it
assumes, the security properties it provides and disclaims, and how findings
are triaged - is documented in the main apache/cassandra repository:

https://github.com/apache/cassandra/blob/trunk/doc/modules/cassandra/pages/reference/security-model.adoc
95 changes: 84 additions & 11 deletions cassandra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,22 +172,31 @@ func TestObserve(t *testing.T) {
}

var (
observedErr error
observedKeyspace string
observedStmt string
observedErr error
observedKeyspace string
observedPreparedKeyspace string
observedStmt string
observedPreparedTable string
observedIsPrepared bool
)

const keyspace = "gocql_test"

resetObserved := func() {
observedErr = errors.New("placeholder only") // used to distinguish err=nil cases
observedKeyspace = ""
observedPreparedKeyspace = ""
observedStmt = ""
observedPreparedTable = ""
observedIsPrepared = false
}

observer := funcQueryObserver(func(ctx context.Context, o ObservedQuery) {
observedKeyspace = o.Keyspace
observedPreparedKeyspace = o.PreparedMetadata.Keyspace
observedStmt = o.Statement
observedPreparedTable = o.PreparedMetadata.Table
observedIsPrepared = o.IsPrepared
observedErr = o.Err
})

Expand All @@ -202,6 +211,12 @@ func TestObserve(t *testing.T) {
t.Fatal("select: unexpected observed keyspace", observedKeyspace)
} else if observedStmt != `SELECT id FROM observe WHERE id = ?` {
t.Fatal("select: unexpected observed stmt", observedStmt)
} else if !observedIsPrepared {
t.Fatal("select: expected observed IsPrepared to be true")
} else if observedPreparedKeyspace != keyspace {
t.Fatal("select: unexpected observed prepared keyspace", observedPreparedKeyspace)
} else if observedPreparedTable != "observe" {
t.Fatal("select: unexpected observed prepared table", observedPreparedTable)
}

resetObserved()
Expand All @@ -213,6 +228,12 @@ func TestObserve(t *testing.T) {
t.Fatal("insert: unexpected observed keyspace", observedKeyspace)
} else if observedStmt != `INSERT INTO observe (id) VALUES (?)` {
t.Fatal("insert: unexpected observed stmt", observedStmt)
} else if !observedIsPrepared {
t.Fatal("insert: expected observed IsPrepared to be true")
} else if observedPreparedKeyspace != keyspace {
t.Fatal("insert: unexpected observed prepared keyspace", observedPreparedKeyspace)
} else if observedPreparedTable != "observe" {
t.Fatal("insert: unexpected observed prepared table", observedPreparedTable)
}

resetObserved()
Expand All @@ -227,6 +248,12 @@ func TestObserve(t *testing.T) {
t.Fatal("select: unexpected observed keyspace", observedKeyspace)
} else if observedStmt != `SELECT id FROM observe WHERE id = ?` {
t.Fatal("select: unexpected observed stmt", observedStmt)
} else if !observedIsPrepared {
t.Fatal("select: expected observed IsPrepared to be true")
} else if observedPreparedKeyspace != keyspace {
t.Fatal("select: unexpected observed prepared keyspace", observedPreparedKeyspace)
} else if observedPreparedTable != "observe" {
t.Fatal("select: unexpected observed prepared table", observedPreparedTable)
}

// also works from session observer
Expand All @@ -240,6 +267,12 @@ func TestObserve(t *testing.T) {
t.Fatal("select: unexpected observed keyspace", observedKeyspace)
} else if observedStmt != `SELECT id FROM observe WHERE id = ?` {
t.Fatal("select: unexpected observed stmt", observedStmt)
} else if !observedIsPrepared {
t.Fatal("select: expected observed IsPrepared to be true")
} else if observedPreparedKeyspace != keyspace {
t.Fatal("select: unexpected observed prepared keyspace", observedPreparedKeyspace)
} else if observedPreparedTable != "observe" {
t.Fatal("select: unexpected observed prepared table", observedPreparedTable)
}

// reports errors when the query is poorly formed
Expand All @@ -254,6 +287,24 @@ func TestObserve(t *testing.T) {
} else if observedStmt != `SELECT id FROM unknown_table WHERE id = ?` {
t.Fatal("select: unexpected observed stmt", observedStmt)
}

// statements that cannot be prepared (e.g. DDL) report IsPrepared=false and
// a zero PreparedMetadata, so the contract is explicit for observers.
resetObserved()
const ddlStmt = `CREATE TABLE IF NOT EXISTS gocql_test.observe_unprepared (id int primary key)`
if err := session.Query(ddlStmt).Observer(observer).Exec(); err != nil {
t.Fatal("ddl:", err)
} else if observedErr != nil {
t.Fatal("ddl:", observedErr)
} else if observedStmt != ddlStmt {
t.Fatal("ddl: unexpected observed stmt", observedStmt)
} else if observedIsPrepared {
t.Fatal("ddl: expected observed IsPrepared to be false for unprepared statement")
} else if observedPreparedKeyspace != "" {
t.Fatal("ddl: expected zero PreparedMetadata.Keyspace, got", observedPreparedKeyspace)
} else if observedPreparedTable != "" {
t.Fatal("ddl: expected zero PreparedMetadata.Table, got", observedPreparedTable)
}
}

func TestObserve_Pagination(t *testing.T) {
Expand Down Expand Up @@ -2134,10 +2185,12 @@ func TestBatchObserve(t *testing.T) {
}

type observation struct {
observedErr error
observedKeyspace string
observedStmts []string
observedValues [][]interface{}
observedErr error
observedKeyspace string
observedPreparedMetadata []PreparedMetadata
observedStmts []string
observedIsPrepared []bool
observedValues [][]interface{}
}

var observedBatch *observation
Expand All @@ -2149,10 +2202,12 @@ func TestBatchObserve(t *testing.T) {
}

observedBatch = &observation{
observedKeyspace: o.Keyspace,
observedStmts: o.Statements,
observedErr: o.Err,
observedValues: o.Values,
observedKeyspace: o.Keyspace,
observedPreparedMetadata: o.PreparedMetadata,
observedStmts: o.Statements,
observedIsPrepared: o.IsPrepared,
observedErr: o.Err,
observedValues: o.Values,
}
}))
for i := 0; i < 100; i++ {
Expand All @@ -2175,11 +2230,29 @@ func TestBatchObserve(t *testing.T) {
if observedBatch.observedKeyspace != "gocql_test" {
t.Fatalf("expecting keyspace 'gocql_test', got %q", observedBatch.observedKeyspace)
}
if len(observedBatch.observedPreparedMetadata) != 100 {
t.Fatal("expecting 100 observed prepared metadata entries, got", len(observedBatch.observedPreparedMetadata))
}
if len(observedBatch.observedIsPrepared) != 100 {
t.Fatal("expecting 100 observed IsPrepared flags, got", len(observedBatch.observedIsPrepared))
}
for i, stmt := range observedBatch.observedStmts {
if stmt != fmt.Sprintf(`INSERT INTO batch_observe_table (id,other) VALUES (?,%d)`, i) {
t.Fatal("unexpected query", stmt)
}

if !observedBatch.observedIsPrepared[i] {
t.Fatalf("expected observed IsPrepared at index %d to be true", i)
}

if observedBatch.observedPreparedMetadata[i].Keyspace != "gocql_test" {
t.Fatalf("unexpected observed prepared keyspace at index %d: %q", i, observedBatch.observedPreparedMetadata[i].Keyspace)
}

if observedBatch.observedPreparedMetadata[i].Table != "batch_observe_table" {
t.Fatalf("unexpected observed prepared table at index %d: %q", i, observedBatch.observedPreparedMetadata[i].Table)
}

assertDeepEqual(t, "observed value", []interface{}{i}, observedBatch.observedValues[i])
}
}
Expand Down
Loading
Loading