Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 60 additions & 7 deletions core/src/main/java/io/substrait/extension/SimpleExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,31 @@ public interface Option {
List<String> getValues();
}

/**
* Deprecation information for an extension entry (type, function, or function implementation).
*
* <p>Consumers of extension files are not required to understand or validate deprecation fields;
* the information is provided so tooling can surface deprecation warnings.
*/
@JsonDeserialize(as = ImmutableSimpleExtension.DeprecationStatus.class)
@JsonSerialize(as = ImmutableSimpleExtension.DeprecationStatus.class)
@JsonIgnoreProperties(ignoreUnknown = true)
@Value.Immutable
public interface DeprecationStatus {
/**
* The version at which the entry was deprecated, as a core semantic version string (e.g. {@code
* "1.2.0"}).
*/
@JsonProperty(required = true)
String since();

/** Optional human-readable description of why the entry was deprecated. */
Optional<String> reason();

/** Optional arbitrary data provided by the extension author. */
Optional<Map<String, Object>> metadata();
}

@JsonSerialize(as = ImmutableSimpleExtension.ValueArgument.class)
@JsonDeserialize(as = ImmutableSimpleExtension.ValueArgument.class)
@Value.Immutable
Expand Down Expand Up @@ -280,6 +305,8 @@ public String description() {

public abstract Optional<Map<String, Object>> metadata();

public abstract Optional<DeprecationStatus> deprecated();

public List<Argument> requiredArguments() {
return requiredArgsSupplier.get();
}
Expand Down Expand Up @@ -387,19 +414,26 @@ public abstract static class ScalarFunction {

public abstract Optional<Map<String, Object>> metadata();

public abstract Optional<DeprecationStatus> deprecated();

public abstract List<ScalarFunctionVariant> impls();

public Stream<ScalarFunctionVariant> resolve(String urn) {
return impls().stream().map(f -> f.resolve(urn, name(), description(), metadata()));
return impls().stream()
.map(f -> f.resolve(urn, name(), description(), metadata(), deprecated()));
}
}

@JsonDeserialize(as = ImmutableSimpleExtension.ScalarFunctionVariant.class)
@JsonSerialize(as = ImmutableSimpleExtension.ScalarFunctionVariant.class)
@Value.Immutable
public abstract static class ScalarFunctionVariant extends Function {
public ScalarFunctionVariant resolve(
String urn, String name, String description, Optional<Map<String, Object>> metadata) {
ScalarFunctionVariant resolve(
String urn,
String name,
String description,
Optional<Map<String, Object>> metadata,
Optional<DeprecationStatus> deprecated) {
return ImmutableSimpleExtension.ScalarFunctionVariant.builder()
.urn(urn)
.name(name)
Expand All @@ -408,6 +442,7 @@ public ScalarFunctionVariant resolve(
.args(args())
.options(options())
.metadata(metadata)
.deprecated(deprecated().isPresent() ? deprecated() : deprecated)
.ordered(ordered())
.variadic(variadic())
.returnType(returnType())
Expand All @@ -427,10 +462,13 @@ public abstract static class AggregateFunction {

public abstract Optional<Map<String, Object>> metadata();

public abstract Optional<DeprecationStatus> deprecated();

public abstract List<AggregateFunctionVariant> impls();

public Stream<AggregateFunctionVariant> resolve(String urn) {
return impls().stream().map(f -> f.resolve(urn, name(), description(), metadata()));
return impls().stream()
.map(f -> f.resolve(urn, name(), description(), metadata(), deprecated()));
}
}

Expand All @@ -446,10 +484,13 @@ public abstract static class WindowFunction {

public abstract Optional<Map<String, Object>> metadata();

public abstract Optional<DeprecationStatus> deprecated();

public abstract List<WindowFunctionVariant> impls();

public Stream<WindowFunctionVariant> resolve(String urn) {
return impls().stream().map(f -> f.resolve(urn, name(), description(), metadata()));
return impls().stream()
.map(f -> f.resolve(urn, name(), description(), metadata(), deprecated()));
}

public static ImmutableSimpleExtension.WindowFunction.Builder builder() {
Expand All @@ -476,7 +517,11 @@ public String toString() {
public abstract TypeExpression intermediate();

AggregateFunctionVariant resolve(
String urn, String name, String description, Optional<Map<String, Object>> metadata) {
String urn,
String name,
String description,
Optional<Map<String, Object>> metadata,
Optional<DeprecationStatus> deprecated) {
return ImmutableSimpleExtension.AggregateFunctionVariant.builder()
.urn(urn)
.name(name)
Expand All @@ -485,6 +530,7 @@ AggregateFunctionVariant resolve(
.args(args())
.options(options())
.metadata(metadata)
.deprecated(deprecated().isPresent() ? deprecated() : deprecated)
.ordered(ordered())
.variadic(variadic())
.decomposability(decomposability())
Expand Down Expand Up @@ -520,7 +566,11 @@ public String toString() {
}

WindowFunctionVariant resolve(
String urn, String name, String description, Optional<Map<String, Object>> metadata) {
String urn,
String name,
String description,
Optional<Map<String, Object>> metadata,
Optional<DeprecationStatus> deprecated) {
return ImmutableSimpleExtension.WindowFunctionVariant.builder()
.urn(urn)
.name(name)
Expand All @@ -529,6 +579,7 @@ WindowFunctionVariant resolve(
.args(args())
.options(options())
.metadata(metadata)
.deprecated(deprecated().isPresent() ? deprecated() : deprecated)
.ordered(ordered())
.variadic(variadic())
.decomposability(decomposability())
Expand Down Expand Up @@ -567,6 +618,8 @@ public abstract static class Type {

public abstract Optional<Map<String, Object>> metadata();

public abstract Optional<DeprecationStatus> deprecated();

public TypeAnchor getAnchor() {
return anchorSupplier.get();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package io.substrait.extension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.substrait.TestBase;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Map;
import org.junit.jupiter.api.Test;

/**
* Verifies that deprecation information can be read from extension YAML files at multiple levels:
*
* <ul>
* <li>Type-level deprecation
* <li>Function-level deprecation (scalar, aggregate, window)
* <li>Function-implementation-level deprecation (individual overloads)
* </ul>
*/
class DeprecationExtensionTest extends TestBase {

static final String URN = "extension:test:deprecation_extensions";
static final SimpleExtension.ExtensionCollection DEPRECATION_EXTENSION;

static {
try {
String extensionStr = asString("extensions/deprecation_extensions.yaml");
DEPRECATION_EXTENSION = SimpleExtension.load(extensionStr);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

DeprecationExtensionTest() {
super(DEPRECATION_EXTENSION);
}

@Test
void testTypeDeprecation() {
SimpleExtension.TypeAnchor anchor = SimpleExtension.TypeAnchor.of(URN, "deprecatedType");
SimpleExtension.DeprecationStatus deprecation =
extensions.getType(anchor).deprecated().orElseThrow();
assertEquals("0.50.0", deprecation.since());
assertEquals("Replaced by newType", deprecation.reason().orElseThrow());
}

@Test
void testScalarFunctionDeprecation() {
SimpleExtension.FunctionAnchor anchor =
SimpleExtension.FunctionAnchor.of(URN, "deprecatedScalar:i64");
SimpleExtension.DeprecationStatus deprecation =
extensions.getScalarFunction(anchor).deprecated().orElseThrow();
assertEquals("0.1.1", deprecation.since());
assertEquals("Use newScalar instead", deprecation.reason().orElseThrow());
}

@Test
void testScalarFunctionDeprecationWithMetadata() {
SimpleExtension.FunctionAnchor anchor =
SimpleExtension.FunctionAnchor.of(URN, "deprecatedScalarWithMetadata:i64");
SimpleExtension.DeprecationStatus deprecation =
extensions.getScalarFunction(anchor).deprecated().orElseThrow();
assertEquals("2.0.0", deprecation.since());
Map<String, Object> metadata = deprecation.metadata().orElseThrow();
assertEquals("newScalar", metadata.get("alternative"));
}

@Test
void testAggregateFunctionDeprecation() {
SimpleExtension.FunctionAnchor anchor =
SimpleExtension.FunctionAnchor.of(URN, "deprecatedAggregate:i64");
SimpleExtension.DeprecationStatus deprecation =
extensions.getAggregateFunction(anchor).deprecated().orElseThrow();
assertEquals("1.2.0", deprecation.since());
assertTrue(deprecation.reason().isEmpty());
}

@Test
void testWindowFunctionDeprecation() {
SimpleExtension.FunctionAnchor anchor =
SimpleExtension.FunctionAnchor.of(URN, "deprecatedWindow:i64");
SimpleExtension.DeprecationStatus deprecation =
extensions.getWindowFunction(anchor).deprecated().orElseThrow();
assertEquals("1.3.0", deprecation.since());
}

@Test
void testImplLevelDeprecation() {
// Only the i64 overload of evolvingScalar is deprecated; the fp32 overload is not.
SimpleExtension.DeprecationStatus deprecation =
extensions
.getScalarFunction(SimpleExtension.FunctionAnchor.of(URN, "evolvingScalar:i64"))
.deprecated()
.orElseThrow();
assertEquals("3.1.0", deprecation.since());
assertEquals("Use the fp32 overload instead", deprecation.reason().orElseThrow());

assertTrue(
extensions
.getScalarFunction(SimpleExtension.FunctionAnchor.of(URN, "evolvingScalar:fp32"))
.deprecated()
.isEmpty());
}

@Test
void testNonDeprecatedAggregateAsWindowFunction() {
// Aggregate functions are also registered as window functions; deprecation must carry over.
SimpleExtension.FunctionAnchor anchor =
SimpleExtension.FunctionAnchor.of(URN, "deprecatedAggregate:i64");
assertFalse(extensions.getWindowFunction(anchor).deprecated().isEmpty());
assertEquals("1.2.0", extensions.getWindowFunction(anchor).deprecated().orElseThrow().since());
}
}
57 changes: 57 additions & 0 deletions core/src/test/resources/extensions/deprecation_extensions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
%YAML 1.2
---
urn: extension:test:deprecation_extensions
types:
- name: "deprecatedType"
deprecated:
since: "0.50.0"
reason: "Replaced by newType"
scalar_functions:
# Function-level deprecation
- name: "deprecatedScalar"
deprecated:
since: "0.1.1"
reason: "Use newScalar instead"
impls:
- args:
- value: i64
return: i64
# Function-level deprecation with reason and metadata
- name: "deprecatedScalarWithMetadata"
deprecated:
since: "2.0.0"
reason: "This function is deprecated for removal in a future version"
metadata:
alternative: "newScalar"
impls:
- args:
- value: i64
return: i64
# Only one overload (impl) is deprecated
- name: "evolvingScalar"
impls:
- args:
- value: fp32
return: fp32
- args:
- value: i64
deprecated:
since: "3.1.0"
reason: "Use the fp32 overload instead"
return: i64
aggregate_functions:
- name: "deprecatedAggregate"
deprecated:
since: "1.2.0"
impls:
- args:
- value: i64
return: i64
window_functions:
- name: "deprecatedWindow"
deprecated:
since: "1.3.0"
impls:
- args:
- value: i64
return: i64
Loading