|
| 1 | +package io.substrait.extension; |
| 2 | + |
| 3 | +import static io.substrait.extension.DefaultExtensionCatalog.DEFAULT_COLLECTION; |
| 4 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
| 5 | + |
| 6 | +import com.fasterxml.jackson.databind.JsonNode; |
| 7 | +import com.fasterxml.jackson.databind.ObjectMapper; |
| 8 | +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; |
| 9 | +import java.io.File; |
| 10 | +import java.io.IOException; |
| 11 | +import java.util.List; |
| 12 | +import java.util.Set; |
| 13 | +import org.junit.jupiter.api.Test; |
| 14 | + |
| 15 | +/** |
| 16 | + * Verifies that every extension YAML in substrait/extensions is loaded by {@link |
| 17 | + * DefaultExtensionCatalog}. |
| 18 | + */ |
| 19 | +class DefaultExtensionCatalogTest { |
| 20 | + |
| 21 | + private static final Set<String> UNSUPPORTED_FILES = |
| 22 | + Set.of( |
| 23 | + // TODO: aggregate_decimal_output defines count and approx_count_distinct with |
| 24 | + // decimal<38,0> return types instead of i64. When loaded alongside aggregate_generic, |
| 25 | + // the same function key (e.g. count:any) maps to the same Calcite operator twice, |
| 26 | + // which breaks the reverse lookup in FunctionConverter.getSqlOperatorFromSubstraitFunc. |
| 27 | + // Fixing this requires either deduplicating the operator map or adding type-based |
| 28 | + // disambiguation for aggregate functions. |
| 29 | + "functions_aggregate_decimal_output.yaml", |
| 30 | + "functions_geometry.yaml", // user-defined types not supported in Calcite type conversion |
| 31 | + "functions_list.yaml", // TODO(#688): remove once lambda types are supported |
| 32 | + "type_variations.yaml", // type variations not yet supported by extension loader |
| 33 | + "unknown.yaml" // unknown type extension not yet loaded |
| 34 | + ); |
| 35 | + |
| 36 | + private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); |
| 37 | + |
| 38 | + @Test |
| 39 | + void allExtensionYamlFilesAreLoaded() throws IOException { |
| 40 | + List<File> yamlFiles = getExtensionYamlFiles(); |
| 41 | + |
| 42 | + for (File file : yamlFiles) { |
| 43 | + if (UNSUPPORTED_FILES.contains(file.getName())) { |
| 44 | + continue; |
| 45 | + } |
| 46 | + String urn = parseUrn(file); |
| 47 | + assertTrue( |
| 48 | + DEFAULT_COLLECTION.containsUrn(urn), |
| 49 | + file.getName() + " not loaded by DefaultExtensionCatalog (urn: " + urn + ")"); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + private static String parseUrn(File yamlFile) throws IOException { |
| 54 | + JsonNode doc = YAML_MAPPER.readTree(yamlFile); |
| 55 | + JsonNode urnNode = doc.get("urn"); |
| 56 | + return urnNode == null ? null : urnNode.asText(); |
| 57 | + } |
| 58 | + |
| 59 | + private static List<File> getExtensionYamlFiles() { |
| 60 | + File extensionsDir = new File("../substrait/extensions"); |
| 61 | + assertTrue(extensionsDir.isDirectory(), "substrait/extensions directory not found"); |
| 62 | + File[] files = extensionsDir.listFiles((dir, name) -> name.endsWith(".yaml")); |
| 63 | + assertTrue(files != null && files.length > 0, "No YAML files found"); |
| 64 | + return List.of(files); |
| 65 | + } |
| 66 | +} |
0 commit comments