This is an automated email from the ASF dual-hosted git repository.
joaoreis pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-gocql-driver.git
The following commit(s) were added to refs/heads/trunk by this push:
new d7df11b8 Prevent panic in compileMetadata() when final func is not
defined for an aggregate
d7df11b8 is described below
commit d7df11b840781b69950549e22c8b095da4c265e0
Author: Bohdan Siryk <[email protected]>
AuthorDate: Thu Jan 29 11:12:16 2026 +0200
Prevent panic in compileMetadata() when final func is not defined for an
aggregate
Previously, when there was a user defined aggregate without FINAL_FUNC
defined the compileMetadata() function would panic due to nil pointer
dereference. Gocql should properly handle this case since FINAL_FUNC is
optional for user defined aggregates.
This patch fixes the described problem by adding nil-checking before
dereferencing.
Patch by Bohdan Siryk; Reviewed by João Reis for CASSGO-105
---
CHANGELOG.md | 1 +
metadata.go | 10 +++-
metadata_test.go | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 169 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 872ebdd1..cfd0714a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0
- Return correct values from RowData (CASSGO-95)
- Prevent setting a compression flag in a frame header when native proto v5 is
being used (CASSGO-98)
- Use protocol downgrading approach during protocol negotiation (CASSGO-97)
+- Prevent panic iin compileMetadata() when final func is not defined for an
aggregate (CASSGO-105)
## [2.0.0]
diff --git a/metadata.go b/metadata.go
index a1e98eda..7909f22d 100644
--- a/metadata.go
+++ b/metadata.go
@@ -359,8 +359,14 @@ func compileMetadata(
}
keyspace.Aggregates = make(map[string]*AggregateMetadata,
len(aggregates))
for i, _ := range aggregates {
- aggregates[i].FinalFunc =
*keyspace.Functions[aggregates[i].finalFunc]
- aggregates[i].StateFunc =
*keyspace.Functions[aggregates[i].stateFunc]
+ finalFunc := keyspace.Functions[aggregates[i].finalFunc]
+ if finalFunc != nil {
+ aggregates[i].FinalFunc = *finalFunc
+ }
+ stateFunc := keyspace.Functions[aggregates[i].stateFunc]
+ if stateFunc != nil {
+ aggregates[i].StateFunc = *stateFunc
+ }
keyspace.Aggregates[aggregates[i].Name] = &aggregates[i]
}
keyspace.UserTypes = make(map[string]*UserTypeMetadata, len(uTypes))
diff --git a/metadata_test.go b/metadata_test.go
index e2af014e..884d3cc2 100644
--- a/metadata_test.go
+++ b/metadata_test.go
@@ -34,6 +34,8 @@ package gocql
import (
"strconv"
"testing"
+
+ "github.com/stretchr/testify/require"
)
// Tests V1 and V2 metadata "compilation" from example data which might be
returned
@@ -596,3 +598,161 @@ func assertParseNonCompositeTypes(
}
}
}
+
+func TestCompileMetadataWithFunctions(t *testing.T) {
+ session := &Session{
+ cfg: ClusterConfig{
+ ProtoVersion: protoVersion5,
+ Logger: NewLogger(LogLevelInfo),
+ },
+ types: GlobalTypes.Copy(),
+ }
+
+ keyspace := &KeyspaceMetadata{
+ Name: "test_keyspace",
+ }
+
+ functions := []FunctionMetadata{
+ {
+ Keyspace: "test_keyspace",
+ Name: "test_func",
+ ArgumentTypes: []TypeInfo{intTypeInfo{}},
+ ArgumentNames: []string{"arg1"},
+ Body: "return arg1 + 1;",
+ CalledOnNullInput: false,
+ },
+ {
+ Keyspace: "test_keyspace",
+ Name: "test_func_no_args",
+ ArgumentTypes: []TypeInfo{},
+ ArgumentNames: []string{},
+ Body: "return 1;",
+ CalledOnNullInput: false,
+ },
+ {
+ Keyspace: "test_keyspace",
+ Name: "test_func_null_input",
+ ArgumentTypes: []TypeInfo{intTypeInfo{}},
+ ArgumentNames: []string{"arg1"},
+ Body: "if (arg1 == null) return 0; else
return arg1;",
+ CalledOnNullInput: true,
+ },
+ }
+
+ compileMetadata(session, keyspace, nil, nil, functions, nil, nil, nil)
+
+ require.Len(t, keyspace.Functions, 3, "Expected to have 3 functions")
+ require.Contains(t, keyspace.Functions, "test_func")
+ require.Contains(t, keyspace.Functions, "test_func_no_args")
+ require.Contains(t, keyspace.Functions, "test_func_null_input")
+
+ testFunc := keyspace.Functions["test_func"]
+ require.Equal(t, "test_func", testFunc.Name)
+ require.Len(t, testFunc.ArgumentTypes, 1)
+ require.Equal(t, TypeInt, testFunc.ArgumentTypes[0].Type())
+ require.Len(t, testFunc.ArgumentNames, 1)
+ require.Equal(t, "arg1", testFunc.ArgumentNames[0])
+ require.Equal(t, "return arg1 + 1;", testFunc.Body)
+ require.False(t, testFunc.CalledOnNullInput)
+
+ testFuncNoArgs := keyspace.Functions["test_func_no_args"]
+ require.Equal(t, "test_func_no_args", testFuncNoArgs.Name)
+ require.Empty(t, testFuncNoArgs.ArgumentTypes)
+ require.Empty(t, testFuncNoArgs.ArgumentNames)
+ require.Equal(t, "return 1;", testFuncNoArgs.Body)
+ require.False(t, testFuncNoArgs.CalledOnNullInput)
+
+ testFuncNullInput := keyspace.Functions["test_func_null_input"]
+ require.Equal(t, "test_func_null_input", testFuncNullInput.Name)
+ require.Len(t, testFuncNullInput.ArgumentTypes, 1)
+ require.Equal(t, TypeInt, testFuncNullInput.ArgumentTypes[0].Type())
+ require.Len(t, testFuncNullInput.ArgumentNames, 1)
+ require.Equal(t, "arg1", testFuncNullInput.ArgumentNames[0])
+ require.Equal(t, "if (arg1 == null) return 0; else return arg1;",
testFuncNullInput.Body)
+ require.True(t, testFuncNullInput.CalledOnNullInput)
+}
+
+func TestCompileMetadataWithAggregates(t *testing.T) {
+ session := &Session{
+ cfg: ClusterConfig{
+ ProtoVersion: protoVersion5,
+ Logger: NewLogger(LogLevelInfo),
+ },
+ types: GlobalTypes.Copy(),
+ }
+
+ keyspace := &KeyspaceMetadata{
+ Name: "test_keyspace",
+ }
+
+ functions := []FunctionMetadata{
+ {
+ Keyspace: "test_keyspace",
+ Name: "test_state_func",
+ ArgumentTypes: []TypeInfo{intTypeInfo{}},
+ ArgumentNames: []string{"arg1"},
+ Body: "return arg1 + 1;",
+ CalledOnNullInput: false,
+ },
+ {
+ Keyspace: "test_keyspace",
+ Name: "test_final_func",
+ ArgumentTypes: []TypeInfo{floatTypeInfo{}},
+ ArgumentNames: []string{"arg1"},
+ Body: "return arg1 + 1;",
+ CalledOnNullInput: false,
+ },
+ }
+
+ aggregates := []AggregateMetadata{
+ {
+ Keyspace: "test_keyspace",
+ Name: "test_agg",
+ ArgumentTypes: []TypeInfo{
+ intTypeInfo{},
+ },
+ InitCond: "0",
+ StateFunc: functions[0],
+ FinalFunc: functions[1],
+ ReturnType: intTypeInfo{},
+ StateType: intTypeInfo{},
+ stateFunc: "test_state_func",
+ finalFunc: "test_final_func",
+ },
+ {
+ Keyspace: "test_keyspace",
+ Name: "test_agg_no_final_func",
+ ArgumentTypes: []TypeInfo{
+ doubleTypeInfo{},
+ },
+ InitCond: "0",
+ StateFunc: functions[0],
+ ReturnType: doubleTypeInfo{},
+ StateType: doubleTypeInfo{},
+ stateFunc: "test_state_func",
+ finalFunc: "",
+ },
+ }
+
+ compileMetadata(session, keyspace, nil, nil, functions, aggregates,
nil, nil)
+
+ require.Len(t, keyspace.Aggregates, 2, "Expected to have 2 aggregates")
+ require.Contains(t, keyspace.Aggregates, "test_agg")
+ require.Contains(t, keyspace.Aggregates, "test_agg_no_final_func")
+
+ testAgg := keyspace.Aggregates["test_agg"]
+ require.Equal(t, "test_agg", testAgg.Name)
+ require.Len(t, testAgg.ArgumentTypes, 1)
+ require.Equal(t, TypeInt, testAgg.ArgumentTypes[0].Type())
+ require.Equal(t, "0", testAgg.InitCond)
+ require.Equal(t, TypeInt, testAgg.ReturnType.Type())
+ require.Equal(t, TypeInt, testAgg.StateType.Type())
+
+ testAggNoFinalFunc := keyspace.Aggregates["test_agg_no_final_func"]
+ require.Equal(t, "test_agg_no_final_func", testAggNoFinalFunc.Name)
+ require.Len(t, testAggNoFinalFunc.ArgumentTypes, 1)
+ require.Equal(t, TypeDouble, testAggNoFinalFunc.ArgumentTypes[0].Type())
+ require.Equal(t, "0", testAggNoFinalFunc.InitCond)
+ require.Equal(t, TypeDouble, testAggNoFinalFunc.ReturnType.Type())
+ require.Equal(t, TypeDouble, testAggNoFinalFunc.StateType.Type())
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]