|
8 | 8 | "encoding/json" |
9 | 9 | "errors" |
10 | 10 | "fmt" |
| 11 | + "reflect" |
11 | 12 | "sort" |
12 | 13 | "strings" |
13 | 14 | "time" |
@@ -771,20 +772,22 @@ func (s *Store) GetSchemaBySubjectVersion(ctx context.Context, subject string, v |
771 | 772 | // GetSchemasBySubject retrieves all schemas for a subject. |
772 | 773 | func (s *Store) GetSchemasBySubject(ctx context.Context, subject string, includeDeleted bool) ([]*storage.SchemaRecord, error) { |
773 | 774 | iter := s.readQuery( |
774 | | - fmt.Sprintf(`SELECT version, schema_id, deleted, created_at FROM %s.subject_versions WHERE subject = ?`, qident(s.cfg.Keyspace)), |
| 775 | + fmt.Sprintf(`SELECT version, schema_id, deleted, created_at, metadata, ruleset FROM %s.subject_versions WHERE subject = ?`, qident(s.cfg.Keyspace)), |
775 | 776 | subject, |
776 | 777 | ).WithContext(ctx).Iter() |
777 | 778 |
|
778 | 779 | type versionInfo struct { |
779 | | - version int |
780 | | - schemaID int |
781 | | - deleted bool |
782 | | - createdAt gocql.UUID |
| 780 | + version int |
| 781 | + schemaID int |
| 782 | + deleted bool |
| 783 | + createdAt gocql.UUID |
| 784 | + metadataStr string |
| 785 | + rulesetStr string |
783 | 786 | } |
784 | 787 | var entries []versionInfo |
785 | 788 | var vi versionInfo |
786 | 789 | hasAnyRows := false |
787 | | - for iter.Scan(&vi.version, &vi.schemaID, &vi.deleted, &vi.createdAt) { |
| 790 | + for iter.Scan(&vi.version, &vi.schemaID, &vi.deleted, &vi.createdAt, &vi.metadataStr, &vi.rulesetStr) { |
788 | 791 | hasAnyRows = true |
789 | 792 | if includeDeleted || !vi.deleted { |
790 | 793 | entries = append(entries, vi) |
@@ -813,6 +816,13 @@ func (s *Store) GetSchemasBySubject(ctx context.Context, subject string, include |
813 | 816 | rec.Version = e.version |
814 | 817 | rec.Deleted = e.deleted |
815 | 818 | rec.CreatedAt = e.createdAt.Time() |
| 819 | + // Overlay per-version metadata/ruleset from subject_versions |
| 820 | + if m := unmarshalJSONText[storage.Metadata](e.metadataStr); m != nil { |
| 821 | + rec.Metadata = m |
| 822 | + } |
| 823 | + if r := unmarshalJSONText[storage.RuleSet](e.rulesetStr); r != nil { |
| 824 | + rec.RuleSet = r |
| 825 | + } |
816 | 826 | out = append(out, rec) |
817 | 827 | } |
818 | 828 | return out, nil |
@@ -982,6 +992,8 @@ func (s *Store) DeleteSchema(ctx context.Context, subject string, version int, p |
982 | 992 | if !deleted { |
983 | 993 | return storage.ErrVersionNotSoftDeleted |
984 | 994 | } |
| 995 | + // Clean up references_by_target for any schemas this version references |
| 996 | + s.cleanupReferencesByTarget(ctx, existingSchemaID, subject, version) |
985 | 997 | if err := s.writeQuery( |
986 | 998 | fmt.Sprintf(`DELETE FROM %s.subject_versions WHERE subject = ? AND version = ?`, qident(s.cfg.Keyspace)), |
987 | 999 | subject, version, |
@@ -1044,6 +1056,27 @@ func (s *Store) cleanupOrphanedSchema(ctx context.Context, schemaID int) { |
1044 | 1056 | ).WithContext(ctx).Exec() |
1045 | 1057 | } |
1046 | 1058 |
|
| 1059 | +// cleanupReferencesByTarget removes references_by_target entries for a schema |
| 1060 | +// that is being permanently deleted. This ensures other schemas can be deleted |
| 1061 | +// after their referrers are removed. |
| 1062 | +func (s *Store) cleanupReferencesByTarget(ctx context.Context, schemaID int, subject string, version int) { |
| 1063 | + // Read all references this schema has |
| 1064 | + refIter := s.readQuery( |
| 1065 | + fmt.Sprintf(`SELECT ref_subject, ref_version FROM %s.schema_references WHERE schema_id = ?`, qident(s.cfg.Keyspace)), |
| 1066 | + schemaID, |
| 1067 | + ).WithContext(ctx).Iter() |
| 1068 | + var refSubject string |
| 1069 | + var refVersion int |
| 1070 | + for refIter.Scan(&refSubject, &refVersion) { |
| 1071 | + // Delete the reverse lookup entry |
| 1072 | + _ = s.writeQuery( |
| 1073 | + fmt.Sprintf(`DELETE FROM %s.references_by_target WHERE ref_subject = ? AND ref_version = ? AND schema_subject = ? AND schema_version = ?`, qident(s.cfg.Keyspace)), |
| 1074 | + refSubject, refVersion, subject, version, |
| 1075 | + ).WithContext(ctx).Exec() |
| 1076 | + } |
| 1077 | + refIter.Close() |
| 1078 | +} |
| 1079 | + |
1047 | 1080 | // ---------- Subject Operations ---------- |
1048 | 1081 |
|
1049 | 1082 | // ListSubjects returns all subjects. |
@@ -2105,6 +2138,11 @@ func marshalJSONText(v interface{}) string { |
2105 | 2138 | if v == nil { |
2106 | 2139 | return "" |
2107 | 2140 | } |
| 2141 | + // Handle typed nil pointers (e.g., (*storage.Metadata)(nil) passed as interface{}) |
| 2142 | + rv := reflect.ValueOf(v) |
| 2143 | + if rv.Kind() == reflect.Ptr && rv.IsNil() { |
| 2144 | + return "" |
| 2145 | + } |
2108 | 2146 | data, err := json.Marshal(v) |
2109 | 2147 | if err != nil { |
2110 | 2148 | return "" |
|
0 commit comments