Skip to content

Commit 0d10e56

Browse files
committed
sr: fix schema creation and reference with context
When creating a schema in a named context with references to default-context subjects, the schema registry auto prexixes unqualified references with the parent's context. Resulting in non-existen lookups. Now we explicitly qualify default context references with `:.:`. And, in the backend we were sending a request to createSchema which makes additional API calls and we were only interested in the ID as part of the response, so we now just call register and get the ID.
1 parent a9e8f30 commit 0d10e56

4 files changed

Lines changed: 57 additions & 9 deletions

File tree

backend/pkg/console/schema_registry.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -682,24 +682,27 @@ func (s *Service) CreateSchemaRegistrySchema(ctx context.Context, subjectName st
682682
ctx = sr.WithParams(ctx, sr.Normalize)
683683
}
684684

685-
subjectSchema, err := srClient.CreateSchema(ctx, subjectName, schema)
685+
// Use RegisterSchema instead of CreateSchema to avoid a follow-up
686+
// SchemaUsagesByID call that fails for named contexts (schema IDs
687+
// are context-scoped, but the lookup doesn't include context).
688+
schemaID, err := srClient.RegisterSchema(ctx, subjectName, schema, -1, -1)
686689
if err != nil {
687690
// If metadata was included and we got a parse error, retry without metadata.
688691
// Older Redpanda versions don't support the metadata field.
689692
if schema.SchemaMetadata != nil {
690693
s.logger.WarnContext(ctx, "retrying schema creation without metadata (unsupported by this Redpanda version)",
691694
slog.String("subject", subjectName))
692695
schema.SchemaMetadata = nil
693-
subjectSchema, err = srClient.CreateSchema(ctx, subjectName, schema)
696+
schemaID, err = srClient.RegisterSchema(ctx, subjectName, schema, -1, -1)
694697
if err != nil {
695698
return nil, err
696699
}
697-
return &CreateSchemaResponse{ID: subjectSchema.ID}, nil
700+
return &CreateSchemaResponse{ID: schemaID}, nil
698701
}
699702
return nil, err
700703
}
701704

702-
return &CreateSchemaResponse{ID: subjectSchema.ID}, nil
705+
return &CreateSchemaResponse{ID: schemaID}, nil
703706
}
704707

705708
// SchemaRegistrySchemaValidation is the response to a schema validation.

frontend/src/components/pages/schemas/schema-context-utils.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { describe, expect, test } from 'vitest';
1313

1414
import {
1515
ALL_CONTEXT_ID,
16+
buildQualifiedReferences,
1617
buildQualifiedSubjectName,
1718
contextNameToId,
1819
DEFAULT_CONTEXT_ID,
@@ -228,3 +229,40 @@ describe('pluralize', () => {
228229
expect(pluralize(3, 'subject')).toBe('3 subjects');
229230
});
230231
});
232+
233+
describe('buildQualifiedReferences', () => {
234+
const ref = (context: string, subject: string) => ({
235+
name: 'ref1',
236+
subject,
237+
version: 1,
238+
context,
239+
});
240+
241+
test('default parent + default ref → unqualified subject', () => {
242+
const result = buildQualifiedReferences([ref(DEFAULT_CONTEXT_ID, 'aaaa')], DEFAULT_CONTEXT_ID);
243+
expect(result).toEqual([{ name: 'ref1', subject: 'aaaa', version: 1 }]);
244+
});
245+
246+
test('named parent + same-context ref → qualified with context', () => {
247+
const result = buildQualifiedReferences([ref('.supertest', 'aaaa')], '.supertest');
248+
expect(result).toEqual([{ name: 'ref1', subject: ':.supertest:aaaa', version: 1 }]);
249+
});
250+
251+
test('named parent + default ref → explicitly qualified as `:.:subject`', () => {
252+
const result = buildQualifiedReferences([ref(DEFAULT_CONTEXT_ID, 'aaaa')], '.supertest');
253+
expect(result).toEqual([{ name: 'ref1', subject: ':.:aaaa', version: 1 }]);
254+
});
255+
256+
test('named parent + empty-string context ref → explicitly qualified as `:.:subject`', () => {
257+
const result = buildQualifiedReferences([ref('', 'aaaa')], '.supertest');
258+
expect(result).toEqual([{ name: 'ref1', subject: ':.:aaaa', version: 1 }]);
259+
});
260+
261+
test('filters out refs with missing name or subject', () => {
262+
const result = buildQualifiedReferences(
263+
[ref(DEFAULT_CONTEXT_ID, ''), { name: '', subject: 'aaaa', version: 1, context: DEFAULT_CONTEXT_ID }],
264+
DEFAULT_CONTEXT_ID
265+
);
266+
expect(result).toEqual([]);
267+
});
268+
});

frontend/src/components/pages/schemas/schema-context-utils.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,21 @@ export function contextLabelToId(label: string): string {
144144
}
145145

146146
// Build qualified references for API calls (create/validate).
147+
// When the parent subject is in a named context and a reference targets the
148+
// default context, explicitly qualify it as `:.:subject` so the SR doesn't
149+
// auto-prefix with the parent's context.
147150
export function buildQualifiedReferences(
148-
refs: { name: string; subject: string; version: number; context: string }[]
151+
refs: { name: string; subject: string; version: number; context: string }[],
152+
parentContext: string
149153
): { name: string; subject: string; version: number }[] {
150154
return refs
151155
.filter((x) => x.name && x.subject)
152156
.map((r) => ({
153157
name: r.name,
154-
subject: buildQualifiedSubjectName(r.context, r.subject),
158+
subject:
159+
isNamedContext(parentContext) && !isNamedContext(r.context)
160+
? buildQualifiedSubjectName('.', r.subject)
161+
: buildQualifiedSubjectName(r.context, r.subject),
155162
version: r.version,
156163
}));
157164
}

frontend/src/components/pages/schemas/schema-create.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ const SchemaPageButtons = (p: {
415415
.createSchema(subjectName, {
416416
schemaType: editorState.format as SchemaTypeType,
417417
schema: editorState.schemaText,
418-
references: buildQualifiedReferences(editorState.references),
418+
references: buildQualifiedReferences(editorState.references, editorState.context),
419419
metadata: editorState.computedMetadata,
420420
params: {
421421
normalize: editorState.normalize,
@@ -509,7 +509,7 @@ async function validateSchema(state: SchemaEditorStateHelper): Promise<{
509509
.validateSchema(state.qualifiedSubjectName, 'latest', {
510510
schemaType: state.format as SchemaTypeType,
511511
schema: state.schemaText,
512-
references: buildQualifiedReferences(state.references),
512+
references: buildQualifiedReferences(state.references, state.context),
513513
})
514514
.catch(
515515
(err) =>
@@ -1036,7 +1036,7 @@ const ReferencesEditor = (p: {
10361036
...prev,
10371037
references: [
10381038
...prev.references,
1039-
{ id: crypto.randomUUID(), name: '', subject: '', version: 1, context: '' },
1039+
{ id: crypto.randomUUID(), name: '', subject: '', version: 1, context: p.parentContext },
10401040
],
10411041
}));
10421042
}}

0 commit comments

Comments
 (0)