Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding translated.from annotation when using SchemaTranslator #970

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ project.ext.externalDependency = [
'parseq_restClient': 'com.linkedin.parseq:parseq-restli-client:5.1.2',
'parseq_testApi': 'com.linkedin.parseq:parseq-test-api:5.1.2',
'servletApi': 'javax.servlet:javax.servlet-api:3.1.0',
'skyScreamer': 'org.skyscreamer:jsonassert:1.5.1',
'slf4jApi': 'org.slf4j:slf4j-api:1.7.30',
'slf4jLog4j2': 'org.apache.logging.log4j:log4j-slf4j-impl:2.0.2',
'snappy': 'org.iq80.snappy:snappy:0.4',
Expand Down
1 change: 1 addition & 0 deletions data-avro/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {
compile externalDependency.avro
compile externalDependency.avroUtil
testCompile externalDependency.testng
testCompile externalDependency.skyScreamer
testCompile project(path: ':data', configuration: 'testArtifacts')
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@


import com.linkedin.avroutil1.compatibility.AvroCompatibilityHelper;
import com.linkedin.avroutil1.compatibility.ConfigurableSchemaComparator;
import com.linkedin.avroutil1.compatibility.SchemaComparisonConfiguration;
import com.linkedin.avroutil1.compatibility.SchemaParseConfiguration;
import com.linkedin.data.DataMap;
import com.linkedin.data.DataMapBuilder;
import com.linkedin.data.schema.DataSchema;
import com.linkedin.data.schema.DataSchemaResolver;
import com.linkedin.data.schema.DataSchemaTraverse;
import com.linkedin.data.schema.NamedDataSchema;
import com.linkedin.data.schema.RecordDataSchema;
import com.linkedin.data.schema.SchemaFormatType;
import com.linkedin.data.schema.SchemaParser;
Expand All @@ -36,6 +39,7 @@
import com.linkedin.data.template.DataTemplateUtil;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
Expand All @@ -53,6 +57,7 @@ public class SchemaTranslator
private static final Logger log = LoggerFactory.getLogger(SchemaTranslator.class);

public static final String DATA_PROPERTY = "com.linkedin.data";
public static final String TRANSLATED_FROM_SOURCE_OPTION = "li.data.translated.from";
public static final String SCHEMA_PROPERTY = "schema";
public static final String OPTIONAL_DEFAULT_MODE_PROPERTY = "optionalDefaultMode";
public static final String AVRO_FILE_EXTENSION = ".avsc";
Expand Down Expand Up @@ -166,9 +171,12 @@ public static DataSchema avroToDataSchema(String avroSchemaInJson, AvroToDataSch
{
avroSchemaFromEmbedded.addProp(DATA_PROPERTY, embededSchemaPropertyVal);
}
if (!avroSchemaFromEmbedded.equals(avroSchemaFromJson))
{
throw new IllegalArgumentException("Embedded schema does not translate to input Avro schema: " + avroSchemaInJson);
// Compare using configuration equivalent to STRICT, except ignore TRANSLATED_FROM_SOURCE_OPTION
if (!ConfigurableSchemaComparator.equals(avroSchemaFromEmbedded, avroSchemaFromJson,
new SchemaComparisonConfiguration(true, true, true, false, true, true,
Collections.singleton((TRANSLATED_FROM_SOURCE_OPTION))))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit hard to read. Why can you provide a constructor in ConfigurableSchemaComparator to allow configure what options to be excluded? Also why DATA_PROPERTY above is not handled simiarly?

Copy link
Member Author

@li-ukumar li-ukumar Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check will never fail for DATA_PROPERTY because the code above this check adds it just before the check.

Object embededSchemaPropertyVal = avroSchemaFromJson.getObjectProp(DATA_PROPERTY);
            if (embededSchemaPropertyVal != null)
            {
              avroSchemaFromEmbedded.addProp(DATA_PROPERTY, embededSchemaPropertyVal);
            }

Updated the SchemaComparisonConfiguration call

throw new IllegalArgumentException(
"Embedded schema does not translate to input Avro schema: " + avroSchemaInJson);
}
}
}
Expand All @@ -186,6 +194,9 @@ public static DataSchema avroToDataSchema(String avroSchemaInJson, AvroToDataSch
String dataSchemaJson = dataSchema.toString();
resultDataSchema = DataTemplateUtil.parseSchema(dataSchemaJson);
}

// add translated from annotation if this is a named dataSchema
resultDataSchema = addTranslatedPropToNamedDataSchema(resultDataSchema);
return resultDataSchema;
}

Expand Down Expand Up @@ -317,6 +328,8 @@ public static String dataToAvroSchemaJson(DataSchema dataSchema)
*/
public static String dataToAvroSchemaJson(DataSchema dataSchema, DataToAvroSchemaTranslationOptions options) throws IllegalArgumentException
{
dataSchema = addTranslatedPropToNamedDataSchema(dataSchema);

// Create a copy of the schema before the actual translation, since the translation process ends up modifying the
// schema for unions with aliases, and we don't want to disturb the original schema. Use PDL to preserve annotations.
final DataSchema translatedDataSchema = DataTemplateUtil.parseSchema(
Expand All @@ -341,6 +354,26 @@ public static String dataToAvroSchemaJson(DataSchema dataSchema, DataToAvroSchem
return SchemaToAvroJsonEncoder.schemaToAvro(translatedDataSchema, dataSchema, defaultValueOverrides, options);
}

/**
* Adds TRANSLATED_FROM_SOURCE_OPTION property to named data schemas if not already present.
* @param dataSchema the data schema to add the property to
* @return the data schema with the property added if applicable.
*/
private static DataSchema addTranslatedPropToNamedDataSchema(DataSchema dataSchema) {
// Add translated from annotation if this is a named dataSchema
if (dataSchema instanceof NamedDataSchema) {
NamedDataSchema namedDataSchema = (NamedDataSchema) dataSchema;
// Add annotation if not already present
if (!namedDataSchema.getProperties().containsKey(TRANSLATED_FROM_SOURCE_OPTION)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what should you do if existing translated_from prop is different from updated one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't update the source in that case. So, if schema A translates to B, and B is translated to C,
the translatedFrom option in C will be A

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My original question is talking about an error case, I am guessing that should never happen and we should throw error?
But your response provides me something different from my understanding, I thought that annotation is just for direct source, seems not?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to stop users from translating a translated schema again, as it is allowed right now in SchemaTranslator right now, and stopping it might fail users.

However, we can use this annotation to fail other flows like PDL -> Proto or Avro -> proto.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on this logic, translated.from annotation is used to indicate the root source, not directly translated source, is this clearly communicated?

Map<String, Object> properties = new HashMap<>(namedDataSchema.getProperties());
properties.put(TRANSLATED_FROM_SOURCE_OPTION, namedDataSchema.getFullName());
namedDataSchema.setProperties(properties);
}
dataSchema = namedDataSchema;
}
return dataSchema;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will have side effect of changing passed DataSchema?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be none, except one additional json property in the result avro schema, which is the requirement of this change. This new property will identify the avro schemas as a translated schema.

}

/**
* Allows caller to specify a file path for schema resolution.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,16 @@
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.json.JSONException;
import org.skyscreamer.jsonassert.Customization;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.comparator.CustomComparator;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import static com.linkedin.data.avro.SchemaTranslator.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
Expand Down Expand Up @@ -834,9 +840,11 @@ public void testToAvroSchemaTestTypeRefAnnotationPropagationUnionWithAlias(Strin
transOptions.setTyperefPropertiesExcludeSet(new HashSet<>(Arrays.asList("validate", "java")));

String avroSchemaText = SchemaTranslator.dataToAvroSchemaJson(schema, transOptions);
DataMap avroSchemaAsDataMap = TestUtil.dataMapFromString(avroSchemaText);
DataMap fieldsPropertiesMap = TestUtil.dataMapFromString(expectedAvroSchemaAsString);
assertEquals(avroSchemaAsDataMap, fieldsPropertiesMap);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(expectedAvroSchemaAsString, avroSchemaText,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));
}

@Test(dataProvider = "toAvroSchemaDataTestTypeRefAnnotationPropagation")
Expand All @@ -848,9 +856,11 @@ public void testToAvroSchemaTestTypeRefAnnotationPropagation(String schemaBefore
transOptions.setTyperefPropertiesExcludeSet(new HashSet<>(Arrays.asList("validate", "java")));

String avroSchemaText = SchemaTranslator.dataToAvroSchemaJson(schema, transOptions);
DataMap avroSchemaAsDataMap = TestUtil.dataMapFromString(avroSchemaText);
DataMap fieldsPropertiesMap = TestUtil.dataMapFromString(expectedAvroSchemaAsString);
assertEquals(fieldsPropertiesMap, avroSchemaAsDataMap);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(expectedAvroSchemaAsString, avroSchemaText,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));
}


Expand Down Expand Up @@ -2271,8 +2281,7 @@ public void testToAvroSchema(String schemaText,
String expected,
String writerSchemaText,
String avroValueJson,
String expectedGenericRecordJson) throws IOException
{
String expectedGenericRecordJson) throws IOException, JSONException {
// test generating Avro schema from Pegasus schema
if (schemaText.contains("##T_START"))
{
Expand Down Expand Up @@ -2300,8 +2309,7 @@ private void testToAvroSchemaInternal(String schemaText,
String expected,
String writerSchemaText,
String avroValueJson,
String expectedGenericRecordJson) throws IOException
{
String expectedGenericRecordJson) throws IOException, JSONException {
for (EmbedSchemaMode embedSchemaMode : EmbedSchemaMode.values())
{
for (OptionalDefaultMode optionalDefaultMode : optionalDefaultModes)
Expand All @@ -2321,7 +2329,11 @@ private void testToAvroSchemaInternal(String schemaText,
DataMap expectedAvroDataMap = TestUtil.dataMapFromString(expected);
DataMap resultAvroDataMap = TestUtil.dataMapFromString(avroTextFromSchema);
Object dataProperty = resultAvroDataMap.remove(SchemaTranslator.DATA_PROPERTY);
assertEquals(resultAvroDataMap, expectedAvroDataMap);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(expected, avroTextFromSchema,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));

// look for embedded schema
assertNotNull(dataProperty);
Expand Down Expand Up @@ -2350,11 +2362,21 @@ private void testToAvroSchemaInternal(String schemaText,
DataMap resultAvroDataMap = TestUtil.dataMapFromString(avroTextFromSchema);
assertFalse(resultAvroDataMap.containsKey(SchemaTranslator.DATA_PROPERTY));
}
assertEquals(avroTextFromSchema, expected);
// assertEquals(avroTextFromSchema, expected);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(expected, avroTextFromSchema,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));
}

String postTranslateSchemaText = schema.toString();
assertEquals(postTranslateSchemaText, preTranslateSchemaText);
// assertEquals(postTranslateSchemaText, preTranslateSchemaText);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(preTranslateSchemaText, postTranslateSchemaText,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));

// make sure Avro accepts it
Schema avroSchema = AvroCompatibilityHelper.parse(avroTextFromSchema);
Expand Down Expand Up @@ -2405,7 +2427,12 @@ private void testToAvroSchemaInternal(String schemaText,
// taking into account typeref.
AvroToDataSchemaTranslationOptions avroToDataSchemaMode = new AvroToDataSchemaTranslationOptions(AvroToDataSchemaTranslationMode.VERIFY_EMBEDDED_SCHEMA);
DataSchema embeddedSchema = SchemaTranslator.avroToDataSchema(avroTextFromSchema, avroToDataSchemaMode);
assertEquals(embeddedSchema, schema.getDereferencedDataSchema());
// assertEquals(embeddedSchema, schema.getDereferencedDataSchema());

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(schema.getDereferencedDataSchema().toString(), embeddedSchema.toString(),
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));
}
}
}
Expand Down Expand Up @@ -2445,8 +2472,8 @@ public Object[][] pegasusDefaultToAvroOptionalSchemaTranslationProvider() {

@Test(dataProvider = "pegasusDefaultToAvroOptionalSchemaTranslationProvider",
description = "Test schemaTranslator for default fields to optional fields translation, in different schema translation modes")
public void testPegasusDefaultToAvroOptionalSchemaTranslation(String... testSchemaTextAndExpected) throws IOException
{
public void testPegasusDefaultToAvroOptionalSchemaTranslation(String... testSchemaTextAndExpected)
throws IOException, JSONException {

String schemaText = null;
String expectedAvroSchema = null;
Expand Down Expand Up @@ -2474,9 +2501,14 @@ public void testPegasusDefaultToAvroOptionalSchemaTranslation(String... testSche
schema,
new DataToAvroSchemaTranslationOptions(PegasusToAvroDefaultFieldTranslationMode.DO_NOT_TRANSLATE)
);
resultAvroDataMap = TestUtil.dataMapFromString(avroTextFromSchema);
expectedAvroDataMap = TestUtil.dataMapFromString(expectedAvroSchema);
assertEquals(resultAvroDataMap, expectedAvroDataMap);
// resultAvroDataMap = TestUtil.dataMapFromString(avroTextFromSchema);
// expectedAvroDataMap = TestUtil.dataMapFromString(expectedAvroSchema);
// assertEquals(resultAvroDataMap, expectedAvroDataMap);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(expectedAvroSchema, avroTextFromSchema,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));

// Test avro Schema
Schema avroSchema = AvroCompatibilityHelper.parse(avroTextFromSchema);
Expand Down Expand Up @@ -2806,13 +2838,15 @@ public Object[][] embeddingSchemaWithDataPropertyData()
}

@Test(dataProvider = "embeddingSchemaWithDataPropertyData")
public void testEmbeddingSchemaWithDataProperty(String schemaText, String expected) throws IOException
{
public void testEmbeddingSchemaWithDataProperty(String schemaText, String expected) throws IOException,
JSONException {
DataToAvroSchemaTranslationOptions options = new DataToAvroSchemaTranslationOptions(JsonBuilder.Pretty.SPACES, EmbedSchemaMode.ROOT_ONLY);
String avroSchemaJson = SchemaTranslator.dataToAvroSchemaJson(TestUtil.dataSchemaFromString(schemaText), options);
DataMap avroSchemaDataMap = TestUtil.dataMapFromString(avroSchemaJson);
DataMap expectedDataMap = TestUtil.dataMapFromString(expected);
assertEquals(avroSchemaDataMap, expectedDataMap);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(expected, avroSchemaJson.toString(),
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));
}

@DataProvider
Expand Down Expand Up @@ -2887,11 +2921,16 @@ public Object[][] schemaWithNamespaceOverride()
}

@Test(dataProvider = "schemaWithNamespaceOverride")
public void testSchemaWithNamespaceOverride(String schemaText, String expected) throws IOException
{
public void testSchemaWithNamespaceOverride(String schemaText, String expected) throws IOException, JSONException {
DataToAvroSchemaTranslationOptions options = new DataToAvroSchemaTranslationOptions(JsonBuilder.Pretty.SPACES).setOverrideNamespace(true);
String avroSchemaJson = SchemaTranslator.dataToAvroSchemaJson(TestUtil.dataSchemaFromString(schemaText), options);
assertEquals(avroSchemaJson, expected);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(expected, avroSchemaJson,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));

// assertEquals(avroSchemaJson, expected);
}

@DataProvider
Expand Down Expand Up @@ -3090,7 +3129,13 @@ public void testFromAvroSchema(String avroText, String schemaText) throws Except
{
DataSchema schema = SchemaTranslator.avroToDataSchema(avroText, option);
String schemaTextFromAvro = SchemaToJsonEncoder.schemaToJson(schema, JsonBuilder.Pretty.SPACES);
assertEquals(TestUtil.dataMapFromString(schemaTextFromAvro), TestUtil.dataMapFromString(schemaText));

// assertEquals(TestUtil.dataMapFromString(schemaTextFromAvro), TestUtil.dataMapFromString(schemaText));

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(schemaTextFromAvro, schema.toString(),
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));

Schema avroSchema = AvroCompatibilityHelper.parse(avroText,
new SchemaParseConfiguration(false,
Expand All @@ -3100,9 +3145,21 @@ public void testFromAvroSchema(String avroText, String schemaText) throws Except
String preTranslateAvroSchema = avroSchema.toString();
schema = SchemaTranslator.avroToDataSchema(avroSchema, option);
schemaTextFromAvro = SchemaToJsonEncoder.schemaToJson(schema, JsonBuilder.Pretty.SPACES);
assertEquals(TestUtil.dataMapFromString(schemaTextFromAvro), TestUtil.dataMapFromString(schemaText));
// assertEquals(TestUtil.dataMapFromString(schemaTextFromAvro), TestUtil.dataMapFromString(schemaText));

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(schemaText.toString(), schemaTextFromAvro,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));

String postTranslateAvroSchema = avroSchema.toString();

assertEquals(preTranslateAvroSchema, postTranslateAvroSchema);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(schemaText.toString(), schemaTextFromAvro,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));
}
}

Expand Down Expand Up @@ -3155,11 +3212,14 @@ public Object[][] avroToDataSchemaTranslationModeData()

@Test(dataProvider = "avroToDataSchemaTranslationModeData")
public void testAvroToDataSchemaTranslationMode(AvroToDataSchemaTranslationMode translationMode, String avroSchemaText, String expected)
throws IOException
{
throws JSONException {
AvroToDataSchemaTranslationOptions options = new AvroToDataSchemaTranslationOptions(translationMode);
DataSchema translatedDataSchema = SchemaTranslator.avroToDataSchema(avroSchemaText, options);
assertEquals(TestUtil.dataMapFromString(translatedDataSchema.toString()), TestUtil.dataMapFromString(expected));

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(expected, translatedDataSchema.toString(),
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));
}

@DataProvider
Expand Down Expand Up @@ -3275,8 +3335,7 @@ public void testUnionDefaultValues(String readerSchemaText) throws IOException
}

@Test
public void testAvroUnionModeChaining() throws IOException
{
public void testAvroUnionModeChaining() throws IOException, JSONException {
String expectedSchema = "{ " +
" \"type\" : \"record\", " +
" \"name\" : \"A\", " +
Expand Down Expand Up @@ -3324,9 +3383,11 @@ public void testAvroUnionModeChaining() throws IOException
AvroToDataSchemaTranslationOptions options =
new AvroToDataSchemaTranslationOptions(AvroToDataSchemaTranslationMode.TRANSLATE).setFileResolutionPaths(avroRootDir);
DataSchema pdscSchema = SchemaTranslator.avroToDataSchema(schema, options);
DataMap actual = TestUtil.dataMapFromString(pdscSchema.toString());
DataMap expected = TestUtil.dataMapFromString(expectedSchema);
assertEquals(actual, expected);

// JSON compare except TRANSLATED_FROM_SOURCE_OPTION in root
JSONAssert.assertEquals(expectedSchema, pdscSchema.toString(),
new CustomComparator(JSONCompareMode.LENIENT,
new Customization(TRANSLATED_FROM_SOURCE_OPTION, (o1, o2) -> true)));
}

@Test
Expand Down