diff --git a/api/src/main/java/org/apache/iceberg/PartitionSpec.java b/api/src/main/java/org/apache/iceberg/PartitionSpec.java
index 3742f7d73b1b..9ccce0dadc34 100644
--- a/api/src/main/java/org/apache/iceberg/PartitionSpec.java
+++ b/api/src/main/java/org/apache/iceberg/PartitionSpec.java
@@ -456,6 +456,17 @@ public Builder truncate(String sourceName, int width) {
return truncate(sourceName, width, sourceName + "_trunc");
}
+ public Builder alwaysNull(String sourceName, String targetName) {
+ checkAndAddPartitionName(targetName);
+ Types.NestedField sourceColumn = findSourceColumn(sourceName);
+ fields.add(new PartitionField(sourceColumn.fieldId(), nextFieldId(), targetName, Transforms.alwaysNull()));
+ return this;
+ }
+
+ public Builder alwaysNull(String sourceName) {
+ return alwaysNull(sourceName, sourceName + "_null");
+ }
+
// add a partition field with an auto-increment partition field id starting from PARTITION_DATA_ID_START
Builder add(int sourceId, String name, String transform) {
return add(sourceId, nextFieldId(), name, transform);
diff --git a/api/src/main/java/org/apache/iceberg/transforms/SerializationProxies.java b/api/src/main/java/org/apache/iceberg/transforms/SerializationProxies.java
new file mode 100644
index 000000000000..db12159c4a7e
--- /dev/null
+++ b/api/src/main/java/org/apache/iceberg/transforms/SerializationProxies.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iceberg.transforms;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * Stand-in classes for expression classes in Java Serialization.
+ *
+ * These are used so that transform classes can be singletons and use identical equality.
+ */
+class SerializationProxies {
+ private SerializationProxies() {
+ }
+
+ static class VoidTransformProxy implements Serializable {
+ private static final VoidTransformProxy INSTANCE = new VoidTransformProxy();
+
+ static VoidTransformProxy get() {
+ return INSTANCE;
+ }
+
+ /**
+ * Constructor for Java serialization.
+ */
+ VoidTransformProxy() {
+ }
+
+ Object readResolve() throws ObjectStreamException {
+ return VoidTransform.get();
+ }
+ }
+}
diff --git a/api/src/main/java/org/apache/iceberg/transforms/Transforms.java b/api/src/main/java/org/apache/iceberg/transforms/Transforms.java
index ee92c291f3b3..cd370d481b30 100644
--- a/api/src/main/java/org/apache/iceberg/transforms/Transforms.java
+++ b/api/src/main/java/org/apache/iceberg/transforms/Transforms.java
@@ -67,6 +67,10 @@ private Transforms() {
// fall through to return unknown transform
}
+ if (transform.equalsIgnoreCase("void")) {
+ return VoidTransform.get();
+ }
+
return new UnknownTransform<>(type, transform);
}
@@ -178,4 +182,14 @@ public static Transform bucket(Type type, int numBuckets) {
public static Transform truncate(Type type, int width) {
return Truncate.get(type, width);
}
+
+ /**
+ * Returns a {@link Transform} that always produces null.
+ *
+ * @param Java type accepted by the transform.
+ * @return a transform that always produces null (the void transform).
+ */
+ public static Transform alwaysNull() {
+ return VoidTransform.get();
+ }
}
diff --git a/api/src/main/java/org/apache/iceberg/transforms/VoidTransform.java b/api/src/main/java/org/apache/iceberg/transforms/VoidTransform.java
new file mode 100644
index 000000000000..d2ecbda13d90
--- /dev/null
+++ b/api/src/main/java/org/apache/iceberg/transforms/VoidTransform.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iceberg.transforms;
+
+import java.io.ObjectStreamException;
+import org.apache.iceberg.expressions.BoundPredicate;
+import org.apache.iceberg.expressions.UnboundPredicate;
+import org.apache.iceberg.types.Type;
+
+class VoidTransform implements Transform {
+ private static final VoidTransform