MetaDataEvolutionValidator { @Nonnull public static Builder newBuilder() { return new Builder(DEFAULT_INSTANCE); } private  MetaDataEvolutionValidator(); private  MetaDataEvolutionValidator(@Nonnull Builder builder); void validate(@Nonnull RecordMetaData oldMetaData, @Nonnull RecordMetaData newMetaData); @SuppressWarnings("PMD.CompareObjectsWithEquals") void validateUnion(@Nonnull Descriptor oldUnionDescriptor, @Nonnull Descriptor newUnionDescriptor); @Nonnull IndexMaintainerRegistry getIndexMaintainerRegistry(); boolean allowsNoVersionChange(); boolean allowsNoSinceVersion(); boolean allowsIndexRebuilds(); boolean allowsMissingFormerIndexNames(); boolean allowsOlderFormerIndexAddedVersions(); boolean allowsUnsplitToSplit(); boolean disallowsTypeRenames(); @Nonnull Builder asBuilder(); @Nonnull static Builder newBuilder(); @Nonnull static MetaDataEvolutionValidator getDefaultInstance();  }
@Test public void mergeTypes() { FileDescriptor updatedDescriptor = mutateFile(fileBuilder -> { DescriptorProtos.DescriptorProto newMessageType = fileBuilder.getMessageTypeList().stream() .filter(message -> message.getName().equals("MyOtherRecord")) .findFirst() .get() .toBuilder() .setName("MyOtherOtherRecord") .build(); fileBuilder.addMessageType(newMessageType); fileBuilder.getMessageTypeBuilderList().forEach(message -> { if (message.getName().equals(RecordMetaDataBuilder.DEFAULT_UNION_NAME)) { message.addField(DescriptorProtos.FieldDescriptorProto.newBuilder() .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE) .setTypeName("MyOtherOtherRecord") .setName("_MyOtherOtherRecord") .setNumber(message.getFieldList().stream().mapToInt(DescriptorProtos.FieldDescriptorProto::getNumber).max().orElse(0) + 1) ); } }); }); assertThat(updatedDescriptor.getMessageTypes().stream().map(Descriptor::getName).collect(Collectors.toSet()), containsInAnyOrder("MySimpleRecord", "MyOtherRecord", "MyOtherOtherRecord", RecordMetaDataBuilder.DEFAULT_UNION_NAME)); RecordMetaData metaData1 = RecordMetaData.build(updatedDescriptor); assertThat(metaData1.getRecordTypes().keySet(), containsInAnyOrder("MySimpleRecord", "MyOtherRecord", "MyOtherOtherRecord")); FileDescriptor secondDescriptor = mutateFile(updatedDescriptor, fileBuilder -> { DescriptorProtos.DescriptorProto.Builder myOtherOtherDescriptor = fileBuilder.getMessageTypeBuilderList().stream() .filter(message -> message.getName().equals("MyOtherOtherRecord")) .findFirst() .get(); int index = fileBuilder.getMessageTypeBuilderList().indexOf(myOtherOtherDescriptor); fileBuilder.removeMessageType(index); fileBuilder.getMessageTypeBuilderList().forEach(message -> { if (message.getName().equals(RecordMetaDataBuilder.DEFAULT_UNION_NAME)) { message.getFieldBuilderList().forEach(field -> { if (field.getTypeName().equals("MyOtherOtherRecord")) { field.setTypeName("MyOtherRecord"); } }); } }); }); RecordMetaData metaData2 = replaceRecordsDescriptor(metaData1, secondDescriptor, metaDataProtoBuilder -> { RecordMetaDataProto.RecordType.Builder myOtherOtherRecord = metaDataProtoBuilder.getRecordTypesBuilderList().stream() .filter(recordType -> recordType.getName().equals("MyOtherOtherRecord")) .findFirst() .get(); int index = metaDataProtoBuilder.getRecordTypesBuilderList().indexOf(myOtherOtherRecord); metaDataProtoBuilder.removeRecordTypes(index); }); assertThat(metaData2.getRecordTypes().keySet(), containsInAnyOrder("MySimpleRecord", "MyOtherRecord")); assertInvalid("record type corresponds to multiple types in old meta-data", metaData1, metaData2); }
@Test public void splitTypes() { FileDescriptor updatedDescriptor = mutateFile(fileBuilder -> fileBuilder.getMessageTypeBuilderList().forEach(message -> { if (message.getName().equals(RecordMetaDataBuilder.DEFAULT_UNION_NAME)) { message.addField(DescriptorProtos.FieldDescriptorProto.newBuilder() .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE) .setTypeName("MyOtherRecord") .setName("_MyOtherOtherRecord") .setNumber(message.getFieldList().stream().mapToInt(DescriptorProtos.FieldDescriptorProto::getNumber).max().orElse(0) + 1)); } }) ); RecordMetaData metaData1 = RecordMetaData.build(updatedDescriptor); assertThat(metaData1.getRecordTypes().keySet(), containsInAnyOrder("MySimpleRecord", "MyOtherRecord")); FileDescriptor secondDescriptor = mutateFile(updatedDescriptor, fileBuilder -> { DescriptorProtos.DescriptorProto newMessageType = fileBuilder.getMessageTypeList().stream() .filter(message -> message.getName().equals("MyOtherRecord")) .findFirst() .get() .toBuilder() .setName("MyOtherOtherRecord") .build(); fileBuilder.addMessageType(newMessageType); fileBuilder.getMessageTypeBuilderList().forEach(message -> { if (message.getName().equals(RecordMetaDataBuilder.DEFAULT_UNION_NAME)) { message.getFieldBuilderList().forEach(field -> { if (field.getName().equals("_MyOtherOtherRecord")) { field.setTypeName("MyOtherOtherRecord"); } }); } }); }); RecordMetaData metaData2 = replaceRecordsDescriptor(metaData1, secondDescriptor, metaDataProtoBuilder -> metaDataProtoBuilder.addRecordTypes(RecordMetaDataProto.RecordType.newBuilder() .setName("MyOtherOtherRecord") .setPrimaryKey(Key.Expressions.field("rec_no").toKeyExpression()) ) ); assertThat(metaData2.getRecordTypes().keySet(), containsInAnyOrder("MySimpleRecord", "MyOtherRecord", "MyOtherOtherRecord")); assertInvalid("record type corresponds to multiple types in new meta-data", metaData1, metaData2); }
@Test public void swapRecordTypes() { FileDescriptor updatedFile = mutateFile(fileBuilder -> { fileBuilder.getMessageTypeBuilderList().forEach(messageType -> { if (messageType.getName().equals(RecordMetaDataBuilder.DEFAULT_UNION_NAME)) { messageType.getFieldBuilderList().forEach(field -> { if (field.getName().equals("_MyOtherRecord")) { field.setTypeName("MySimpleRecord"); } if (field.getName().equals("_MySimpleRecord")) { field.setTypeName("MyOtherRecord"); } }); } }); }); RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder().setRecords(TestRecords1Proto.getDescriptor()); metaDataBuilder.addIndex("MyOtherRecord", "num_value_3_indexed"); RecordMetaData metaData1 = metaDataBuilder.getRecordMetaData(); RecordMetaData metaData2 = replaceRecordsDescriptor(metaData1, updatedFile); assertInvalid("", metaData1, metaData2); }
@Test public void fieldChangedFromMessageToGroup() { assertInvalid("field type changed", TestRecordsWithHeaderProto.getDescriptor(), TestHeaderAsGroupProto.getDescriptor()); assertInvalid("field type changed", TestHeaderAsGroupProto.getDescriptor(), TestRecordsWithHeaderProto.getDescriptor()); RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder().setRecords(TestRecordsWithHeaderProto.getDescriptor()); metaDataBuilder.getRecordType("MyRecord").setPrimaryKey(Key.Expressions.field("header").nest(Key.Expressions.concatenateFields("path", "rec_no"))); RecordMetaData metaData1 = metaDataBuilder.getRecordMetaData(); RecordMetaData metaData2 = replaceRecordsDescriptor(metaData1, TestHeaderAsGroupProto.getDescriptor()); assertInvalid("field type changed", metaData1, metaData2); }
@Test public void nestedTypeChangesFieldName() { FileDescriptor updatedFile = mutateField("HeaderRecord", "num", TestRecordsWithHeaderProto.getDescriptor(), field -> field.setName("numb")); assertInvalid("field renamed", TestRecordsWithHeaderProto.getDescriptor(), updatedFile); RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder().setRecords(TestRecordsWithHeaderProto.getDescriptor()); metaDataBuilder.getRecordType("MyRecord").setPrimaryKey(Key.Expressions.field("header").nest(Key.Expressions.concatenateFields("path", "rec_no"))); RecordMetaData metaData1 = metaDataBuilder.getRecordMetaData(); RecordMetaData metaData2 = replaceRecordsDescriptor(metaData1, updatedFile); assertInvalid("field renamed", metaData1, metaData2); }
@Test public void nestedTypeChangesFieldType() { FileDescriptor updatedFile = mutateField("HeaderRecord", "num", TestRecordsWithHeaderProto.getDescriptor(), field -> field.setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_SFIXED32)); assertInvalid("field type changed", TestRecordsWithHeaderProto.getDescriptor(), updatedFile); RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder().setRecords(TestRecordsWithHeaderProto.getDescriptor()); metaDataBuilder.getRecordType("MyRecord").setPrimaryKey(Key.Expressions.field("header").nest(Key.Expressions.concatenateFields("path", "rec_no"))); RecordMetaData metaData1 = metaDataBuilder.getRecordMetaData(); RecordMetaData metaData2 = replaceRecordsDescriptor(metaData1, updatedFile); assertInvalid("field type changed", metaData1, metaData2); }
@Test public void addRequiredField() { FileDescriptor updatedFile = mutateFile(fileBuilder -> fileBuilder.getMessageTypeBuilderList().forEach(message -> { if (message.getName().equals("MySimpleRecord")) { message.addField(DescriptorProtos.FieldDescriptorProto.newBuilder() .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_REQUIRED) .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT32) .setName("new_int_field") .setNumber(message.getFieldList().stream().mapToInt(DescriptorProtos.FieldDescriptorProto::getNumber).max().getAsInt() + 1) ); } }) ); assertInvalid("required field added to record type", TestRecords1Proto.getDescriptor(), updatedFile); RecordMetaData metaData1 = RecordMetaData.build(TestRecords1Proto.getDescriptor()); RecordMetaData metaData2 = replaceRecordsDescriptor(metaData1, updatedFile); assertInvalid("required field added to record type", metaData1, metaData2); }
@Test public void recordTypeKeyChanged() { RecordMetaData metaData1 = RecordMetaData.build(TestRecords1Proto.getDescriptor()); RecordMetaDataProto.MetaData.Builder protoBuilder = metaData1.toProto().toBuilder() .setVersion(metaData1.getVersion() + 1); protoBuilder.getRecordTypesBuilder(0) .setExplicitKey(RecordMetaDataProto.Value.newBuilder() .setStringValue("new_key")); RecordMetaData metaData2 = RecordMetaData.build(protoBuilder.build()); assertInvalid("record type key changed", metaData1, metaData2); }
@Test public void removeFormerIndex() { RecordMetaDataBuilder metaData1Builder = RecordMetaData.newBuilder().setRecords(TestRecords1Proto.getDescriptor()); metaData1Builder.removeIndex("MySimpleRecord$str_value_indexed"); RecordMetaData metaData1 = metaData1Builder.getRecordMetaData(); RecordMetaData metaData2 = RecordMetaData.build( metaData1.toProto().toBuilder().setVersion(metaData1.getVersion() + 1).clearFormerIndexes().build() ); assertInvalid("former index removed", metaData1, metaData2); }
@Test public void changeFormerIndexVersion() { RecordMetaDataBuilder metaData1Builder = RecordMetaData.newBuilder().setRecords(TestRecords1Proto.getDescriptor()); metaData1Builder.removeIndex("MySimpleRecord$str_value_indexed"); RecordMetaData metaData1 = metaData1Builder.getRecordMetaData(); RecordMetaDataProto.MetaData.Builder metaData2ProtoBuilder = metaData1.toProto() .toBuilder() .setVersion(metaData1.getVersion() + 1); metaData2ProtoBuilder.setFormerIndexes(0, metaData2ProtoBuilder.getFormerIndexesBuilder(0).setRemovedVersion(metaData1.getVersion() + 1)); RecordMetaData metaData2 = RecordMetaData.build(metaData2ProtoBuilder.build()); assertInvalid("removed version of former index differs from prior version", metaData1, metaData2); metaData2ProtoBuilder.setFormerIndexes(0, metaData2ProtoBuilder.getFormerIndexesBuilder(0).setRemovedVersion(metaData1.getVersion()).setAddedVersion(metaData1.getVersion() - 2)); metaData2 = RecordMetaData.build(metaData2ProtoBuilder.build()); assertInvalid("added version of former index differs from prior version", metaData1, metaData2); metaData2ProtoBuilder.setFormerIndexes(0, metaData2ProtoBuilder.getFormerIndexesBuilder(0).clearAddedVersion()); metaData2 = RecordMetaData.build(metaData2ProtoBuilder.build()); assertInvalid("added version of former index differs from prior version", metaData1, metaData2); }
@Test public void changeFormerIndexName() { RecordMetaDataBuilder metaData1Builder = RecordMetaData.newBuilder().setRecords(TestRecords1Proto.getDescriptor()); metaData1Builder.removeIndex("MySimpleRecord$str_value_indexed"); RecordMetaData metaData1 = metaData1Builder.getRecordMetaData(); RecordMetaDataProto.MetaData.Builder metaData2ProtoBuilder = metaData1.toProto() .toBuilder() .setVersion(metaData1.getVersion() + 1); metaData2ProtoBuilder.setFormerIndexes(0, metaData2ProtoBuilder.getFormerIndexesBuilder(0).setFormerName("some_other_name")); RecordMetaData metaData2 = RecordMetaData.build(metaData2ProtoBuilder.build()); assertInvalid("name of former index differs from prior version", metaData1, metaData2); metaData2ProtoBuilder.setFormerIndexes(0, metaData2ProtoBuilder.getFormerIndexesBuilder(0).clearFormerName()); metaData2 = RecordMetaData.build(metaData2ProtoBuilder.build()); assertInvalid("name of former index differs from prior version", metaData1, metaData2); MetaDataEvolutionValidator laxerValidator = MetaDataEvolutionValidator.newBuilder() .setAllowMissingFormerIndexNames(true) .build(); assertInvalid("name of former index differs from prior version", laxerValidator, metaData1, metaData2); }
@Test public void formerIndexFromThePast() { RecordMetaData metaData1 = RecordMetaData.build(TestRecords1Proto.getDescriptor()); RecordMetaData metaData2 = RecordMetaData.build( metaData1.toProto().toBuilder() .setVersion(metaData1.getVersion() + 1) .addFormerIndexes(RecordMetaDataProto.FormerIndex.newBuilder() .setSubspaceKey(ByteString.copyFrom(Tuple.from("dummy_key").pack())) .setRemovedVersion(metaData1.getVersion() - 1) .build()) .build() ); assertInvalid("new former index has removed version that is not newer than the old meta-data version", metaData1, metaData2); RecordMetaData metaData3 = RecordMetaData.build( metaData1.toProto().toBuilder() .setVersion(metaData1.getVersion() + 1) .addFormerIndexes(RecordMetaDataProto.FormerIndex.newBuilder() .setSubspaceKey(ByteString.copyFrom(Tuple.from("dummy_key").pack())) .setRemovedVersion(metaData1.getVersion()) .build()) .build() ); assertInvalid("new former index has removed version that is not newer than the old meta-data version", metaData1, metaData3); }
@Test public void indexUsedWhereFormerIndexWas() { RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder().setRecords(TestRecords1Proto.getDescriptor()); metaDataBuilder.removeIndex("MySimpleRecord$str_value_indexed"); RecordMetaData metaData1 = metaDataBuilder.getRecordMetaData(); RecordMetaDataBuilder metaData2Builder = RecordMetaData.newBuilder().setRecords( metaData1.toProto().toBuilder().clearFormerIndexes().build() ); Index newIndex = new Index("newIndex", "str_value_indexed"); newIndex.setSubspaceKey("MySimpleRecord$str_value_indexed"); metaData2Builder.addIndex("MySimpleRecord", newIndex); RecordMetaData metaData2 = metaData2Builder.getRecordMetaData(); assertInvalid("former index key used for new index in meta-data", metaData1, metaData2); }
@Test public void newIndexFromThePast() { RecordMetaData metaData1 = RecordMetaData.build(TestRecords1Proto.getDescriptor()); Index newIndex = new Index("newIndex", Key.Expressions.field("num_value_2")); newIndex.setAddedVersion(metaData1.getVersion() - 1); newIndex.setLastModifiedVersion(metaData1.getVersion() - 1); RecordMetaDataBuilder metaData2Builder = RecordMetaData.newBuilder().setRecords(TestRecords1Proto.getDescriptor()); metaData2Builder.addIndex(metaData2Builder.getRecordType("MySimpleRecord"), newIndex); metaData2Builder.setVersion(metaData1.getVersion() + 1); RecordMetaData metaData2 = metaData2Builder.getRecordMetaData(); assertInvalid("new index has version that is not newer than the old meta-data version", metaData1, metaData2); RecordMetaDataBuilder metaData3Builder = RecordMetaData.newBuilder().setRecords(TestRecords1Proto.getDescriptor()); newIndex.setAddedVersion(metaData1.getVersion()); metaData3Builder.addIndex(metaData2Builder.getRecordType("MySimpleRecord"), newIndex); metaData2Builder.setVersion(metaData1.getVersion() + 1); RecordMetaData metaData3 = metaData2.getRecordMetaData(); assertInvalid("new index has version that is not newer than the old meta-data version", metaData1, metaData3); }
@Test public void indexRecordTypeRemoved() { final String indexName = "simple&other$num_value_2"; RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder().setRecords(TestRecords1Proto.getDescriptor()); metaDataBuilder.addMultiTypeIndex(Arrays.asList(metaDataBuilder.getRecordType("MySimpleRecord"), metaDataBuilder.getRecordType("MyOtherRecord")), new Index(indexName, "num_value_2")); RecordMetaData metaData1 = metaDataBuilder.getRecordMetaData(); validateIndexMutation("new index removes record type", metaData1, indexName, indexProto -> indexProto.toBuilder().clearRecordType().addRecordType("MySimpleRecord").build() ); }