From 98b7c3a89cdd4556042a7d89a405aa2ed4dc13ed Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Thu, 5 Feb 2026 15:18:39 +0300 Subject: [PATCH] Sort fields in compact schema by name --- src/serialization/compact/Schema.ts | 10 +++++++--- src/serialization/compact/SchemaWriter.ts | 6 ++---- .../compact/RabinFingerprintTest.js | 19 +++++++++++++++++++ test/unit/serialization/compact/SchemaTest.js | 4 +++- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/serialization/compact/Schema.ts b/src/serialization/compact/Schema.ts index 4f3106064..e4f935acb 100644 --- a/src/serialization/compact/Schema.ts +++ b/src/serialization/compact/Schema.ts @@ -37,9 +37,13 @@ export class Schema { constructor(typeName: string, fields: FieldDescriptor[]) { this.typeName = typeName; this.fields = fields; - this.fieldDefinitionMap = new Map(); + this.fields.sort((field1, field2) => { + return field1.fieldName > field2.fieldName ? 1 : -1; + }) - for (const field of fields) { + this.fieldDefinitionMap = new Map(); + for (const field of this.fields) { + // map entries are sorted by insertion order this.fieldDefinitionMap.set(field.fieldName, field); } @@ -101,7 +105,7 @@ export class Schema { getFields() : IterableIterator { return this.fieldDefinitionMap.values(); } - + private hasSameFields(other: Schema): boolean { if (other.fieldDefinitionMap.size !== this.fieldDefinitionMap.size) { return false; diff --git a/src/serialization/compact/SchemaWriter.ts b/src/serialization/compact/SchemaWriter.ts index 744b19013..966ce9bcf 100644 --- a/src/serialization/compact/SchemaWriter.ts +++ b/src/serialization/compact/SchemaWriter.ts @@ -212,14 +212,12 @@ export class SchemaWriter implements CompactWriter { 'Field with the name ' + field.fieldName + ' already exists' ); } - + this.fieldNames.add(field.fieldName); this.fields.push(field); } build() : Schema { - return new Schema(this.typeName, this.fields.sort((field1, field2) => { - return field1.fieldName > field2.fieldName ? 1 : -1; - })); + return new Schema(this.typeName, this.fields); } } diff --git a/test/unit/serialization/compact/RabinFingerprintTest.js b/test/unit/serialization/compact/RabinFingerprintTest.js index aa7376992..f8e221ad7 100644 --- a/test/unit/serialization/compact/RabinFingerprintTest.js +++ b/test/unit/serialization/compact/RabinFingerprintTest.js @@ -19,6 +19,10 @@ const chai = require('chai'); const Long = require('long'); const { RabinFingerprint64, INIT } = require('../../../../lib/serialization/compact/RabinFingerprint'); const { SchemaWriter } = require('../../../../lib/serialization/compact/SchemaWriter'); +const {Schema} = require('../../../../lib/serialization/compact/Schema'); +const {FieldDescriptor} = require('../../../../lib/serialization/generic_record/FieldDescriptor'); +const {FieldKind} = require('../../../../lib/serialization/generic_record/FieldKind'); + chai.should(); describe('RabinFingerprintTest', function () { @@ -88,4 +92,19 @@ describe('RabinFingerprintTest', function () { const schema = writer.build(); schema.schemaId.eq(Long.fromString('3662264393229655598')).should.be.true; }); + + it('should compute the same fingerprint for equivalent schemas regardless of the field order', function () { + const schema1 = new Schema('my-schema', [ + new FieldDescriptor('field-1', FieldKind.ARRAY_OF_DATE), + new FieldDescriptor('field-2', FieldKind.ARRAY_OF_DATE), + ]); + const fp1 = RabinFingerprint64.ofSchema(schema1); + + const schema2 = new Schema('my-schema', [ + new FieldDescriptor('field-2', FieldKind.ARRAY_OF_DATE), + new FieldDescriptor('field-1', FieldKind.ARRAY_OF_DATE), + ]); + const fp2 = RabinFingerprint64.ofSchema(schema2); + fp1.eq(fp2).should.be.true; + }); }); diff --git a/test/unit/serialization/compact/SchemaTest.js b/test/unit/serialization/compact/SchemaTest.js index 0581ea6bf..2f7c0bffc 100644 --- a/test/unit/serialization/compact/SchemaTest.js +++ b/test/unit/serialization/compact/SchemaTest.js @@ -27,7 +27,9 @@ describe('SchemaTest', function () { const boolCount = 100; const boolFields = new Array(100); for (let i = 0; i < boolCount; i++) { - boolFields[i] = new FieldDescriptor(i.toString(), FieldKind.BOOLEAN); + // the fields are sorted by name, so have to append 0 for numbers < 10 + const name = i >= 10? i.toString() : '0' + i.toString(); + boolFields[i] = new FieldDescriptor(name, FieldKind.BOOLEAN); } const fields = [