From 65929891d581b14ed65dbfd0ab36197ce7f6a633 Mon Sep 17 00:00:00 2001 From: mattisonchao Date: Thu, 29 Jan 2026 13:01:46 +0800 Subject: [PATCH 1/8] [fix][schema] Illegal character '$' in record --- .../validator/StructSchemaDataValidator.java | 31 +++- pulsar-broker/src/main/proto/DataRecord.proto | 17 +++ .../validator/SchemaDataValidatorTest.java | 136 ++++++++++++++++++ 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 pulsar-broker/src/main/proto/DataRecord.proto diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java index 7f3c4e5e46b05..068bc15fe9583 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java @@ -22,6 +22,8 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.module.jsonSchema.JsonSchema; import java.io.IOException; + +import org.apache.avro.NameValidator; import org.apache.avro.Schema; import org.apache.avro.SchemaParseException; import org.apache.pulsar.broker.service.schema.exceptions.InvalidSchemaDataException; @@ -39,6 +41,7 @@ public static StructSchemaDataValidator of() { } private static final StructSchemaDataValidator INSTANCE = new StructSchemaDataValidator(); + private static final CompatibleNameValidator COMPATIBLE_NAME_VALIDATOR = new CompatibleNameValidator(); private StructSchemaDataValidator() {} @@ -49,7 +52,7 @@ public void validate(SchemaData schemaData) throws InvalidSchemaDataException { byte[] data = schemaData.getData(); try { - Schema.Parser avroSchemaParser = new Schema.Parser(); + Schema.Parser avroSchemaParser = new Schema.Parser(COMPATIBLE_NAME_VALIDATOR); avroSchemaParser.setValidateDefaults(false); Schema schema = avroSchemaParser.parse(new String(data, UTF_8)); if (SchemaType.AVRO.equals(schemaData.getType())) { @@ -97,4 +100,30 @@ private static void throwInvalidSchemaDataException(SchemaData schemaData, throw new InvalidSchemaDataException("Invalid schema definition data for " + schemaData.getType() + " schema", cause); } + + static class CompatibleNameValidator implements NameValidator { + + @Override + public Result validate(String name) { + if (name == null) { + return new Result("Null name"); + } + final int length = name.length(); + if (length == 0) { + return new Result("Empty name"); + } + final char first = name.charAt(0); + if (!(Character.isLetter(first) || first == '_' || first == '$')) { + return new Result("Illegal initial character: " + name); + } + for (int i = 1; i < length; i++) { + final char c = name.charAt(i); + // we need to allow $ for the special case + if (!(Character.isLetterOrDigit(c) || c == '_' || c == '$')) { + return new Result("Illegal character in: " + name); + } + } + return OK; + } + } } diff --git a/pulsar-broker/src/main/proto/DataRecord.proto b/pulsar-broker/src/main/proto/DataRecord.proto new file mode 100644 index 0000000000000..17b289037a012 --- /dev/null +++ b/pulsar-broker/src/main/proto/DataRecord.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package pulsar.schema; +option java_package = "org.apache.pulsar.broker.service.schema.proto"; + + +message DataRecord { + string field1 = 1; + int64 field2 = 2; + NestedDataRecord field3 = 3; + repeated NestedDataRecord fields4 = 4; + + message NestedDataRecord { + string field1 = 1; + int64 field2 = 2; + } +} \ No newline at end of file diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java index 302e5879d28fc..ce92b0fed1434 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java @@ -23,14 +23,22 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.module.jsonSchema.JsonSchema; import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator; +import org.apache.avro.protobuf.ProtobufData; import org.apache.pulsar.broker.service.schema.exceptions.InvalidSchemaDataException; +import org.apache.pulsar.broker.service.schema.proto.DataRecordOuterClass; import org.apache.pulsar.client.api.Schema; +import org.apache.pulsar.client.impl.schema.ProtobufSchema; import org.apache.pulsar.common.protocol.schema.SchemaData; +import org.apache.pulsar.common.schema.SchemaInfo; import org.apache.pulsar.common.schema.SchemaType; import org.apache.pulsar.common.util.ObjectMapperFactory; +import org.junit.jupiter.api.Assertions; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import org.apache.pulsar.broker.service.schema.validator.StructSchemaDataValidator.CompatibleNameValidator; +import org.apache.avro.NameValidator; + @Test(groups = "broker") public class SchemaDataValidatorTest { @@ -148,4 +156,132 @@ public void testJsonSchemaTypeWithJsonSchemaData() throws Exception { } } + @Test + public void testCompatibleNameValidatorValidNames() { + CompatibleNameValidator validator = new CompatibleNameValidator(); + + String[] validNames = { + "validName", + "ValidName", + "valid_name", + "valid$name", + "_validName", + "$validName", + "name123", + "Name_123$", + "a", + "A", + "_", + "$", + "validNameWithMultiple$ymbols_and_numbers123" + }; + + for (String name : validNames) { + NameValidator.Result result = validator.validate(name); + Assertions.assertTrue(result.isOK(), + "Expected validation to pass for name: '" + name + "', but got error: " + result.getErrors()); + } + } + + @Test + public void testCompatibleNameValidatorInvalidNames() { + CompatibleNameValidator validator = new CompatibleNameValidator(); + + String[] invalidNames = { + null, + "", + "123name", + "1name", + "name-with-dash", + "name with space", + "name.with.dot", + "name@symbol", + "name#hash", + "name%percent", + "name&ersand", + "name*asterisk", + "name(parentheses)", + "name+plus", + "name=equals", + "name[brackets]", + "name{braces}", + "name|pipe", + "name\\backslash", + "name:colon", + "name;semicolon", + "name\"quote", + "name'apostrophe", + "name", + "name,comma", + "name?question", + "name!exclamation", + "name`backtick", + "name~tilde", + "name^caret" + }; + + for (String name : invalidNames) { + NameValidator.Result result = validator.validate(name); + Assertions.assertFalse(result.isOK(), "Expected validation to fail for name: '" + name + "'"); + } + } + + @Test + public void testCompatibleNameValidatorSpecificErrorMessages() throws Exception { + CompatibleNameValidator validator = new CompatibleNameValidator(); + + NameValidator.Result nullResult = validator.validate(null); + Assertions.assertFalse(nullResult.isOK()); + Assertions.assertEquals("Null name", nullResult.getErrors()); + + NameValidator.Result emptyResult = validator.validate(""); + Assertions.assertFalse(emptyResult.isOK()); + Assertions.assertEquals("Empty name", emptyResult.getErrors()); + + NameValidator.Result invalidFirstCharResult = validator.validate("123name"); + Assertions.assertFalse(invalidFirstCharResult.isOK()); + Assertions.assertTrue(invalidFirstCharResult.getErrors().contains("Illegal initial character")); + + NameValidator.Result invalidCharResult = validator.validate("name-with-dash"); + Assertions.assertFalse(invalidCharResult.isOK()); + Assertions.assertTrue(invalidCharResult.getErrors().contains("Illegal character in")); + } + + @Test + public void testCompatibleNameValidatorEdgeCases() throws Exception { + CompatibleNameValidator validator = new CompatibleNameValidator(); + + Assertions.assertTrue(validator.validate("a").isOK()); + Assertions.assertTrue(validator.validate("A").isOK()); + Assertions.assertTrue(validator.validate("_").isOK()); + Assertions.assertTrue(validator.validate("$").isOK()); + + NameValidator.Result longNameResult = validator.validate("a".repeat(1000)); + Assertions.assertTrue(longNameResult.isOK()); + + NameValidator.Result nameWithOnlyDigits = validator.validate("123"); + Assertions.assertFalse(nameWithOnlyDigits.isOK()); + Assertions.assertTrue(nameWithOnlyDigits.getErrors().contains("Illegal initial character")); + } + + @Test + public void testCompatibleNameValidatorUnicodeCharacters() throws Exception { + CompatibleNameValidator validator = new CompatibleNameValidator(); + + NameValidator.Result unicodeResult = validator.validate("名字"); + Assertions.assertFalse(unicodeResult.isOK()); + Assertions.assertTrue(unicodeResult.getErrors().contains("Illegal initial character")); + + NameValidator.Result mixedResult = validator.validate("name字"); + Assertions.assertFalse(mixedResult.isOK()); + Assertions.assertTrue(mixedResult.getErrors().contains("Illegal character in")); + } + + + @Test + public void testAvroCompatible() throws InvalidSchemaDataException { + final ProtobufSchema protobufSchema = ProtobufSchema.of(DataRecordOuterClass.DataRecord.class); + StructSchemaDataValidator.of().validate(SchemaData.fromSchemaInfo(protobufSchema.getSchemaInfo())); + } + } From a89b93556f7d7a86ff5fad9a08e9689d508ed6ba Mon Sep 17 00:00:00 2001 From: mattisonchao Date: Fri, 20 Feb 2026 09:21:39 +0800 Subject: [PATCH 2/8] fix lint --- .../service/schema/validator/StructSchemaDataValidator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java index 068bc15fe9583..7e084431cd126 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.module.jsonSchema.JsonSchema; import java.io.IOException; - import org.apache.avro.NameValidator; import org.apache.avro.Schema; import org.apache.avro.SchemaParseException; From 6467269078c001bfae83e355f580b75908ef8261 Mon Sep 17 00:00:00 2001 From: mattisonchao Date: Fri, 20 Feb 2026 09:50:12 +0800 Subject: [PATCH 3/8] [fix][schema] Use compatible name validator in all broker-side Avro schema parsers --- .../service/schema/AvroSchemaBasedCompatibilityCheck.java | 6 ++++-- .../broker/service/schema/SchemaRegistryServiceImpl.java | 6 ++++-- .../schema/validator/StructSchemaDataValidator.java | 8 ++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java index e5fc7800c5170..66c094ee6dbe3 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java @@ -28,6 +28,7 @@ import org.apache.avro.SchemaValidationException; import org.apache.avro.SchemaValidator; import org.apache.avro.SchemaValidatorBuilder; +import org.apache.pulsar.broker.service.schema.validator.StructSchemaDataValidator; import org.apache.pulsar.broker.service.schema.exceptions.IncompatibleSchemaException; import org.apache.pulsar.common.policies.data.SchemaCompatibilityStrategy; import org.apache.pulsar.common.protocol.schema.SchemaData; @@ -51,11 +52,12 @@ public void checkCompatible(Iterable from, SchemaData to, SchemaComp checkArgument(from != null, "check compatibility list is null"); try { for (SchemaData schemaData : from) { - Schema.Parser parser = new Schema.Parser(); + Schema.Parser parser = + new Schema.Parser(StructSchemaDataValidator.compatibleNameValidator()); parser.setValidateDefaults(false); fromList.addFirst(parser.parse(new String(schemaData.getData(), UTF_8))); } - Schema.Parser parser = new Schema.Parser(); + Schema.Parser parser = new Schema.Parser(StructSchemaDataValidator.compatibleNameValidator()); parser.setValidateDefaults(false); Schema toSchema = parser.parse(new String(to.getData(), UTF_8)); SchemaValidator schemaValidator = createSchemaValidator(strategy); diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java index dcf7edee1a263..15ff3edaf23de 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java @@ -46,6 +46,7 @@ import org.apache.commons.lang3.mutable.MutableLong; import org.apache.commons.lang3.tuple.Pair; import org.apache.pulsar.broker.PulsarService; +import org.apache.pulsar.broker.service.schema.validator.StructSchemaDataValidator; import org.apache.pulsar.broker.service.schema.exceptions.IncompatibleSchemaException; import org.apache.pulsar.broker.service.schema.exceptions.NotExistSchemaException; import org.apache.pulsar.broker.service.schema.exceptions.SchemaException; @@ -414,12 +415,13 @@ public CompletableFuture getSchemaVersionBySchemaData( final CompletableFuture completableFuture = new CompletableFuture<>(); SchemaVersion schemaVersion; if (isUsingAvroSchemaParser(schemaData.getType())) { - Schema.Parser parser = new Schema.Parser(); + Schema.Parser parser = new Schema.Parser(StructSchemaDataValidator.compatibleNameValidator()); Schema newSchema = parser.parse(new String(schemaData.getData(), UTF_8)); for (SchemaAndMetadata schemaAndMetadata : schemaAndMetadataList) { if (isUsingAvroSchemaParser(schemaAndMetadata.schema.getType())) { - Schema.Parser existParser = new Schema.Parser(); + Schema.Parser existParser = + new Schema.Parser(StructSchemaDataValidator.compatibleNameValidator()); Schema existSchema = existParser.parse(new String(schemaAndMetadata.schema.getData(), UTF_8)); if (newSchema.equals(existSchema) && schemaAndMetadata.schema.getType() == schemaData.getType()) { schemaVersion = schemaAndMetadata.version; diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java index 7e084431cd126..dab0cfc104aba 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java @@ -33,14 +33,18 @@ /** * Validate if the struct schema is in expected form. */ -class StructSchemaDataValidator implements SchemaDataValidator { +public class StructSchemaDataValidator implements SchemaDataValidator { public static StructSchemaDataValidator of() { return INSTANCE; } private static final StructSchemaDataValidator INSTANCE = new StructSchemaDataValidator(); - private static final CompatibleNameValidator COMPATIBLE_NAME_VALIDATOR = new CompatibleNameValidator(); + static final CompatibleNameValidator COMPATIBLE_NAME_VALIDATOR = new CompatibleNameValidator(); + + public static NameValidator compatibleNameValidator() { + return COMPATIBLE_NAME_VALIDATOR; + } private StructSchemaDataValidator() {} From 9b03bb1c8b15808f9f43b6295c63be99a333c667 Mon Sep 17 00:00:00 2001 From: mattisonchao Date: Fri, 20 Feb 2026 09:51:19 +0800 Subject: [PATCH 4/8] [fix][schema] Add ASF license header to DataRecord.proto --- pulsar-broker/src/main/proto/DataRecord.proto | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pulsar-broker/src/main/proto/DataRecord.proto b/pulsar-broker/src/main/proto/DataRecord.proto index 17b289037a012..18d0ad4d70ffb 100644 --- a/pulsar-broker/src/main/proto/DataRecord.proto +++ b/pulsar-broker/src/main/proto/DataRecord.proto @@ -1,3 +1,21 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ syntax = "proto3"; package pulsar.schema; From 6d783d643e96257d80b9a651a546c30c11b126f0 Mon Sep 17 00:00:00 2001 From: mattisonchao Date: Fri, 20 Feb 2026 20:41:29 +0800 Subject: [PATCH 5/8] [fix][schema] Fix checkstyle violations in schema validator changes --- .../AvroSchemaBasedCompatibilityCheck.java | 2 +- .../schema/SchemaRegistryServiceImpl.java | 2 +- .../validator/SchemaDataValidatorTest.java | 80 +++++++++---------- 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java index 66c094ee6dbe3..4ca7bf8bae431 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java @@ -28,8 +28,8 @@ import org.apache.avro.SchemaValidationException; import org.apache.avro.SchemaValidator; import org.apache.avro.SchemaValidatorBuilder; -import org.apache.pulsar.broker.service.schema.validator.StructSchemaDataValidator; import org.apache.pulsar.broker.service.schema.exceptions.IncompatibleSchemaException; +import org.apache.pulsar.broker.service.schema.validator.StructSchemaDataValidator; import org.apache.pulsar.common.policies.data.SchemaCompatibilityStrategy; import org.apache.pulsar.common.protocol.schema.SchemaData; diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java index 15ff3edaf23de..ff95b4d84109f 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java @@ -46,11 +46,11 @@ import org.apache.commons.lang3.mutable.MutableLong; import org.apache.commons.lang3.tuple.Pair; import org.apache.pulsar.broker.PulsarService; -import org.apache.pulsar.broker.service.schema.validator.StructSchemaDataValidator; import org.apache.pulsar.broker.service.schema.exceptions.IncompatibleSchemaException; import org.apache.pulsar.broker.service.schema.exceptions.NotExistSchemaException; import org.apache.pulsar.broker.service.schema.exceptions.SchemaException; import org.apache.pulsar.broker.service.schema.proto.SchemaRegistryFormat; +import org.apache.pulsar.broker.service.schema.validator.StructSchemaDataValidator; import org.apache.pulsar.common.policies.data.SchemaCompatibilityStrategy; import org.apache.pulsar.common.protocol.schema.SchemaData; import org.apache.pulsar.common.protocol.schema.SchemaHash; diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java index ce92b0fed1434..e2ab40b71b290 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java @@ -23,22 +23,19 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.module.jsonSchema.JsonSchema; import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator; -import org.apache.avro.protobuf.ProtobufData; +import org.apache.avro.NameValidator; import org.apache.pulsar.broker.service.schema.exceptions.InvalidSchemaDataException; import org.apache.pulsar.broker.service.schema.proto.DataRecordOuterClass; +import org.apache.pulsar.broker.service.schema.validator.StructSchemaDataValidator.CompatibleNameValidator; import org.apache.pulsar.client.api.Schema; import org.apache.pulsar.client.impl.schema.ProtobufSchema; import org.apache.pulsar.common.protocol.schema.SchemaData; -import org.apache.pulsar.common.schema.SchemaInfo; import org.apache.pulsar.common.schema.SchemaType; import org.apache.pulsar.common.util.ObjectMapperFactory; -import org.junit.jupiter.api.Assertions; +import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import org.apache.pulsar.broker.service.schema.validator.StructSchemaDataValidator.CompatibleNameValidator; -import org.apache.avro.NameValidator; - @Test(groups = "broker") public class SchemaDataValidatorTest { @@ -159,7 +156,7 @@ public void testJsonSchemaTypeWithJsonSchemaData() throws Exception { @Test public void testCompatibleNameValidatorValidNames() { CompatibleNameValidator validator = new CompatibleNameValidator(); - + String[] validNames = { "validName", "ValidName", @@ -175,10 +172,10 @@ public void testCompatibleNameValidatorValidNames() { "$", "validNameWithMultiple$ymbols_and_numbers123" }; - + for (String name : validNames) { NameValidator.Result result = validator.validate(name); - Assertions.assertTrue(result.isOK(), + Assert.assertTrue(result.isOK(), "Expected validation to pass for name: '" + name + "', but got error: " + result.getErrors()); } } @@ -186,7 +183,7 @@ public void testCompatibleNameValidatorValidNames() { @Test public void testCompatibleNameValidatorInvalidNames() { CompatibleNameValidator validator = new CompatibleNameValidator(); - + String[] invalidNames = { null, "", @@ -219,68 +216,69 @@ public void testCompatibleNameValidatorInvalidNames() { "name~tilde", "name^caret" }; - + for (String name : invalidNames) { NameValidator.Result result = validator.validate(name); - Assertions.assertFalse(result.isOK(), "Expected validation to fail for name: '" + name + "'"); + Assert.assertFalse(result.isOK(), "Expected validation to fail for name: '" + name + "'"); } } @Test public void testCompatibleNameValidatorSpecificErrorMessages() throws Exception { CompatibleNameValidator validator = new CompatibleNameValidator(); - + NameValidator.Result nullResult = validator.validate(null); - Assertions.assertFalse(nullResult.isOK()); - Assertions.assertEquals("Null name", nullResult.getErrors()); - + Assert.assertFalse(nullResult.isOK()); + Assert.assertEquals(nullResult.getErrors(), "Null name"); + NameValidator.Result emptyResult = validator.validate(""); - Assertions.assertFalse(emptyResult.isOK()); - Assertions.assertEquals("Empty name", emptyResult.getErrors()); - + Assert.assertFalse(emptyResult.isOK()); + Assert.assertEquals(emptyResult.getErrors(), "Empty name"); + NameValidator.Result invalidFirstCharResult = validator.validate("123name"); - Assertions.assertFalse(invalidFirstCharResult.isOK()); - Assertions.assertTrue(invalidFirstCharResult.getErrors().contains("Illegal initial character")); - + Assert.assertFalse(invalidFirstCharResult.isOK()); + Assert.assertTrue(invalidFirstCharResult.getErrors().contains("Illegal initial character")); + NameValidator.Result invalidCharResult = validator.validate("name-with-dash"); - Assertions.assertFalse(invalidCharResult.isOK()); - Assertions.assertTrue(invalidCharResult.getErrors().contains("Illegal character in")); + Assert.assertFalse(invalidCharResult.isOK()); + Assert.assertTrue(invalidCharResult.getErrors().contains("Illegal character in")); } @Test public void testCompatibleNameValidatorEdgeCases() throws Exception { CompatibleNameValidator validator = new CompatibleNameValidator(); - - Assertions.assertTrue(validator.validate("a").isOK()); - Assertions.assertTrue(validator.validate("A").isOK()); - Assertions.assertTrue(validator.validate("_").isOK()); - Assertions.assertTrue(validator.validate("$").isOK()); - + + Assert.assertTrue(validator.validate("a").isOK()); + Assert.assertTrue(validator.validate("A").isOK()); + Assert.assertTrue(validator.validate("_").isOK()); + Assert.assertTrue(validator.validate("$").isOK()); + NameValidator.Result longNameResult = validator.validate("a".repeat(1000)); - Assertions.assertTrue(longNameResult.isOK()); - + Assert.assertTrue(longNameResult.isOK()); + NameValidator.Result nameWithOnlyDigits = validator.validate("123"); - Assertions.assertFalse(nameWithOnlyDigits.isOK()); - Assertions.assertTrue(nameWithOnlyDigits.getErrors().contains("Illegal initial character")); + Assert.assertFalse(nameWithOnlyDigits.isOK()); + Assert.assertTrue(nameWithOnlyDigits.getErrors().contains("Illegal initial character")); } @Test public void testCompatibleNameValidatorUnicodeCharacters() throws Exception { CompatibleNameValidator validator = new CompatibleNameValidator(); - + NameValidator.Result unicodeResult = validator.validate("名字"); - Assertions.assertFalse(unicodeResult.isOK()); - Assertions.assertTrue(unicodeResult.getErrors().contains("Illegal initial character")); - + Assert.assertFalse(unicodeResult.isOK()); + Assert.assertTrue(unicodeResult.getErrors().contains("Illegal initial character")); + NameValidator.Result mixedResult = validator.validate("name字"); - Assertions.assertFalse(mixedResult.isOK()); - Assertions.assertTrue(mixedResult.getErrors().contains("Illegal character in")); + Assert.assertFalse(mixedResult.isOK()); + Assert.assertTrue(mixedResult.getErrors().contains("Illegal character in")); } @Test public void testAvroCompatible() throws InvalidSchemaDataException { - final ProtobufSchema protobufSchema = ProtobufSchema.of(DataRecordOuterClass.DataRecord.class); + final ProtobufSchema protobufSchema = + ProtobufSchema.of(DataRecordOuterClass.DataRecord.class); StructSchemaDataValidator.of().validate(SchemaData.fromSchemaInfo(protobufSchema.getSchemaInfo())); } From c9629049a7b77406fa25b9feb1801e9ffd397d06 Mon Sep 17 00:00:00 2001 From: mattisonchao Date: Fri, 20 Feb 2026 21:04:11 +0800 Subject: [PATCH 6/8] [fix][schema] Use public static final field instead of getter for COMPATIBLE_NAME_VALIDATOR --- .../service/schema/AvroSchemaBasedCompatibilityCheck.java | 4 ++-- .../broker/service/schema/SchemaRegistryServiceImpl.java | 4 ++-- .../service/schema/validator/StructSchemaDataValidator.java | 6 +----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java index 4ca7bf8bae431..56012a3ddd7dd 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/AvroSchemaBasedCompatibilityCheck.java @@ -53,11 +53,11 @@ public void checkCompatible(Iterable from, SchemaData to, SchemaComp try { for (SchemaData schemaData : from) { Schema.Parser parser = - new Schema.Parser(StructSchemaDataValidator.compatibleNameValidator()); + new Schema.Parser(StructSchemaDataValidator.COMPATIBLE_NAME_VALIDATOR); parser.setValidateDefaults(false); fromList.addFirst(parser.parse(new String(schemaData.getData(), UTF_8))); } - Schema.Parser parser = new Schema.Parser(StructSchemaDataValidator.compatibleNameValidator()); + Schema.Parser parser = new Schema.Parser(StructSchemaDataValidator.COMPATIBLE_NAME_VALIDATOR); parser.setValidateDefaults(false); Schema toSchema = parser.parse(new String(to.getData(), UTF_8)); SchemaValidator schemaValidator = createSchemaValidator(strategy); diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java index ff95b4d84109f..5c5ed992d2456 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java @@ -415,13 +415,13 @@ public CompletableFuture getSchemaVersionBySchemaData( final CompletableFuture completableFuture = new CompletableFuture<>(); SchemaVersion schemaVersion; if (isUsingAvroSchemaParser(schemaData.getType())) { - Schema.Parser parser = new Schema.Parser(StructSchemaDataValidator.compatibleNameValidator()); + Schema.Parser parser = new Schema.Parser(StructSchemaDataValidator.COMPATIBLE_NAME_VALIDATOR); Schema newSchema = parser.parse(new String(schemaData.getData(), UTF_8)); for (SchemaAndMetadata schemaAndMetadata : schemaAndMetadataList) { if (isUsingAvroSchemaParser(schemaAndMetadata.schema.getType())) { Schema.Parser existParser = - new Schema.Parser(StructSchemaDataValidator.compatibleNameValidator()); + new Schema.Parser(StructSchemaDataValidator.COMPATIBLE_NAME_VALIDATOR); Schema existSchema = existParser.parse(new String(schemaAndMetadata.schema.getData(), UTF_8)); if (newSchema.equals(existSchema) && schemaAndMetadata.schema.getType() == schemaData.getType()) { schemaVersion = schemaAndMetadata.version; diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java index dab0cfc104aba..8202c5720c23d 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java @@ -40,11 +40,7 @@ public static StructSchemaDataValidator of() { } private static final StructSchemaDataValidator INSTANCE = new StructSchemaDataValidator(); - static final CompatibleNameValidator COMPATIBLE_NAME_VALIDATOR = new CompatibleNameValidator(); - - public static NameValidator compatibleNameValidator() { - return COMPATIBLE_NAME_VALIDATOR; - } + public static final NameValidator COMPATIBLE_NAME_VALIDATOR = new CompatibleNameValidator(); private StructSchemaDataValidator() {} From 6808d71828db0ec1b9aa4749ed9462af21b65c3f Mon Sep 17 00:00:00 2001 From: mattisonchao Date: Fri, 20 Feb 2026 21:38:26 +0800 Subject: [PATCH 7/8] [fix][schema] Remove Unicode character test for CompatibleNameValidator --- .../schema/validator/SchemaDataValidatorTest.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java index e2ab40b71b290..a69bb649e7ca7 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/broker/service/schema/validator/SchemaDataValidatorTest.java @@ -261,20 +261,6 @@ public void testCompatibleNameValidatorEdgeCases() throws Exception { Assert.assertTrue(nameWithOnlyDigits.getErrors().contains("Illegal initial character")); } - @Test - public void testCompatibleNameValidatorUnicodeCharacters() throws Exception { - CompatibleNameValidator validator = new CompatibleNameValidator(); - - NameValidator.Result unicodeResult = validator.validate("名字"); - Assert.assertFalse(unicodeResult.isOK()); - Assert.assertTrue(unicodeResult.getErrors().contains("Illegal initial character")); - - NameValidator.Result mixedResult = validator.validate("name字"); - Assert.assertFalse(mixedResult.isOK()); - Assert.assertTrue(mixedResult.getErrors().contains("Illegal character in")); - } - - @Test public void testAvroCompatible() throws InvalidSchemaDataException { final ProtobufSchema protobufSchema = From 246583af13df6ac95ae71d0a5fc57667333d5496 Mon Sep 17 00:00:00 2001 From: mattisonchao Date: Tue, 24 Feb 2026 23:07:17 +0800 Subject: [PATCH 8/8] [fix][schema] Move DataRecord.proto to test/proto since it is only used in tests Co-Authored-By: Claude Opus 4.6 --- pulsar-broker/src/{main => test}/proto/DataRecord.proto | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pulsar-broker/src/{main => test}/proto/DataRecord.proto (100%) diff --git a/pulsar-broker/src/main/proto/DataRecord.proto b/pulsar-broker/src/test/proto/DataRecord.proto similarity index 100% rename from pulsar-broker/src/main/proto/DataRecord.proto rename to pulsar-broker/src/test/proto/DataRecord.proto