mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user