fix(cassandra): list table triggers from system schema

Closes #6751
This commit is contained in:
t8y2
2026-08-20 06:34:13 +00:00
parent 5a921b3b76
commit be1785d4fa
3 changed files with 115 additions and 1 deletions
+2 -1
View File
@@ -358,7 +358,8 @@ func (s *server) dispatch(method string, params map[string]json.RawMessage) (any
case "list_foreign_keys":
return []foreignKeyInfo{}, false, nil
case "list_triggers":
return []triggerInfo{}, false, nil
result, err := s.listTriggers(stringParam(params, "schema"), stringParam(params, "table"))
return result, false, err
case "get_object_source":
return nil, false, errors.New("object source is not supported by Cassandra")
case "get_table_ddl":
+38
View File
@@ -82,6 +82,15 @@ type triggerInfo struct {
Timing string `json:"timing"`
}
const cassandraListTriggersCQL = "SELECT trigger_name, options FROM system_schema.triggers WHERE keyspace_name = ? AND table_name = ?"
type metadataIterator interface {
Scan(...any) bool
Close() error
}
type metadataQuery func(string, ...any) metadataIterator
type metadataListConstraints struct {
Filter string
Limit int
@@ -321,6 +330,35 @@ func (s *server) querySystemIndexes(schema, table string) ([]indexInfo, error) {
return result, nil
}
func (s *server) listTriggers(schema, table string) ([]triggerInfo, error) {
session, err := s.runtime.sessionFor("")
if err != nil {
return nil, err
}
return listTriggersWithQuery(func(query string, values ...any) metadataIterator {
return session.Query(query, values...).Iter()
}, schema, table)
}
func listTriggersWithQuery(query metadataQuery, schema, table string) ([]triggerInfo, error) {
iter := query(cassandraListTriggersCQL, schema, table)
result := []triggerInfo{}
var name string
var options map[string]string
for iter.Scan(&name, &options) {
// Cassandra trigger metadata stores only the trigger class. Cassandra
// invokes every trigger before any DML statement, so expose those fixed
// semantics through the common TriggerInfo contract.
result = append(result, triggerInfo{Name: name, Event: "DML", Timing: "BEFORE"})
options = nil
}
if err := iter.Close(); err != nil {
return nil, err
}
sort.Slice(result, func(left, right int) bool { return result[left].Name < result[right].Name })
return result, nil
}
func mergeIndexes(first, second []indexInfo) []indexInfo {
byName := make(map[string]indexInfo, len(first)+len(second))
for _, index := range first {
@@ -1,12 +1,37 @@
package main
import (
"errors"
"reflect"
"testing"
gocql "github.com/apache/cassandra-gocql-driver/v2"
)
type triggerMetadataIterator struct {
rows []struct {
name string
options map[string]string
}
index int
closeErr error
}
func (i *triggerMetadataIterator) Scan(dest ...any) bool {
if i.index >= len(i.rows) {
return false
}
row := i.rows[i.index]
i.index++
*dest[0].(*string) = row.name
*dest[1].(*map[string]string) = row.options
return true
}
func (i *triggerMetadataIterator) Close() error {
return i.closeErr
}
func TestColumnsIndexesAndDDLFromMetadata(t *testing.T) {
textType := gocql.NewNativeType(4, gocql.TypeVarchar, "")
intType := gocql.NewNativeType(4, gocql.TypeInt, "")
@@ -84,3 +109,53 @@ func TestQuoteCQLIdentifierEscapesQuotes(t *testing.T) {
t.Fatalf("unexpected quoted identifier: %s", got)
}
}
func TestListTriggersQueriesExactTableAndMapsMetadata(t *testing.T) {
var statement string
var values []any
triggers, err := listTriggersWithQuery(func(query string, args ...any) metadataIterator {
statement = query
values = args
return &triggerMetadataIterator{rows: []struct {
name string
options map[string]string
}{
{name: "capture_changes", options: map[string]string{"class": "example.CaptureDataTrigger"}},
}}
}, "dev", "example")
if err != nil {
t.Fatal(err)
}
if statement != cassandraListTriggersCQL {
t.Fatalf("unexpected trigger query: %s", statement)
}
if !reflect.DeepEqual(values, []any{"dev", "example"}) {
t.Fatalf("trigger filters = %#v", values)
}
want := []triggerInfo{{Name: "capture_changes", Event: "DML", Timing: "BEFORE"}}
if !reflect.DeepEqual(triggers, want) {
t.Fatalf("triggers = %#v, want %#v", triggers, want)
}
}
func TestListTriggersReturnsEmptySlice(t *testing.T) {
triggers, err := listTriggersWithQuery(func(string, ...any) metadataIterator {
return &triggerMetadataIterator{}
}, "dev", "empty_table")
if err != nil {
t.Fatal(err)
}
if triggers == nil || len(triggers) != 0 {
t.Fatalf("triggers = %#v, want a non-nil empty slice", triggers)
}
}
func TestListTriggersReturnsQueryError(t *testing.T) {
wantErr := errors.New("trigger metadata unavailable")
triggers, err := listTriggersWithQuery(func(string, ...any) metadataIterator {
return &triggerMetadataIterator{closeErr: wantErr}
}, "dev", "example")
if !errors.Is(err, wantErr) || triggers != nil {
t.Fatalf("triggers = %#v, err = %v", triggers, err)
}
}