listViews(SessionContext context, Namespace namespa
@Override
public boolean viewExists(SessionContext context, TableIdentifier identifier) {
- Endpoint.check(endpoints, Endpoint.V1_VIEW_EXISTS);
-
try {
checkViewIdentifierIsValid(identifier);
- client.head(paths.view(identifier), headers(context), ErrorHandlers.viewErrorHandler());
- return true;
+ if (endpoints.contains(Endpoint.V1_VIEW_EXISTS)) {
+ client.head(paths.view(identifier), headers(context), ErrorHandlers.viewErrorHandler());
+ return true;
+ } else {
+ // fallback in order to work with 1.7.x and older servers
+ return super.viewExists(context, identifier);
+ }
} catch (NoSuchViewException e) {
return false;
}
diff --git a/core/src/main/java/org/apache/iceberg/schema/SchemaWithPartnerVisitor.java b/core/src/main/java/org/apache/iceberg/schema/SchemaWithPartnerVisitor.java
index 9b2226f5714d..694bfb2f6242 100644
--- a/core/src/main/java/org/apache/iceberg/schema/SchemaWithPartnerVisitor.java
+++ b/core/src/main/java/org/apache/iceberg/schema/SchemaWithPartnerVisitor.java
@@ -107,6 +107,9 @@ public static T visit(
return visitor.map(map, partner, keyResult, valueResult);
+ case VARIANT:
+ return visitor.variant(type.asVariantType(), partner);
+
default:
return visitor.primitive(type.asPrimitiveType(), partner);
}
@@ -160,6 +163,10 @@ public R map(Types.MapType map, P partner, R keyResult, R valueResult) {
return null;
}
+ public R variant(Types.VariantType variant, P partner) {
+ throw new UnsupportedOperationException("Unsupported type: variant");
+ }
+
public R primitive(Type.PrimitiveType primitive, P partner) {
return null;
}
diff --git a/core/src/main/java/org/apache/iceberg/schema/UnionByNameVisitor.java b/core/src/main/java/org/apache/iceberg/schema/UnionByNameVisitor.java
index b7ac23816a02..c3b9a50b2081 100644
--- a/core/src/main/java/org/apache/iceberg/schema/UnionByNameVisitor.java
+++ b/core/src/main/java/org/apache/iceberg/schema/UnionByNameVisitor.java
@@ -142,6 +142,11 @@ public Boolean map(
return false;
}
+ @Override
+ public Boolean variant(Types.VariantType variant, Integer partnerId) {
+ return partnerId == null;
+ }
+
@Override
public Boolean primitive(Type.PrimitiveType primitive, Integer partnerId) {
return partnerId == null;
diff --git a/core/src/main/java/org/apache/iceberg/types/FixupTypes.java b/core/src/main/java/org/apache/iceberg/types/FixupTypes.java
index 23fccddda3d9..1e4c0b597a6a 100644
--- a/core/src/main/java/org/apache/iceberg/types/FixupTypes.java
+++ b/core/src/main/java/org/apache/iceberg/types/FixupTypes.java
@@ -147,6 +147,12 @@ public Type map(Types.MapType map, Supplier keyTypeFuture, Supplier
}
}
+ @Override
+ public Type variant(Types.VariantType variant) {
+ // nothing to fix up
+ return variant;
+ }
+
@Override
public Type primitive(Type.PrimitiveType primitive) {
if (sourceType.equals(primitive)) {
diff --git a/core/src/main/java/org/apache/iceberg/variants/Variants.java b/core/src/main/java/org/apache/iceberg/variants/Variants.java
index 7b14c4c38d0f..96ef0bbb5ba6 100644
--- a/core/src/main/java/org/apache/iceberg/variants/Variants.java
+++ b/core/src/main/java/org/apache/iceberg/variants/Variants.java
@@ -52,6 +52,10 @@ enum BasicType {
ARRAY
}
+ public static VariantMetadata emptyMetadata() {
+ return SerializedMetadata.EMPTY_V1_METADATA;
+ }
+
public static VariantMetadata metadata(ByteBuffer metadata) {
return SerializedMetadata.from(metadata);
}
@@ -89,59 +93,59 @@ public static VariantPrimitive ofNull() {
return new PrimitiveWrapper<>(PhysicalType.NULL, null);
}
- static VariantPrimitive of(boolean value) {
+ public static VariantPrimitive of(boolean value) {
return new PrimitiveWrapper<>(PhysicalType.BOOLEAN_TRUE, value);
}
- static VariantPrimitive of(byte value) {
+ public static VariantPrimitive of(byte value) {
return new PrimitiveWrapper<>(PhysicalType.INT8, value);
}
- static VariantPrimitive of(short value) {
+ public static VariantPrimitive of(short value) {
return new PrimitiveWrapper<>(PhysicalType.INT16, value);
}
- static VariantPrimitive of(int value) {
+ public static VariantPrimitive of(int value) {
return new PrimitiveWrapper<>(PhysicalType.INT32, value);
}
- static VariantPrimitive of(long value) {
+ public static VariantPrimitive of(long value) {
return new PrimitiveWrapper<>(PhysicalType.INT64, value);
}
- static VariantPrimitive of(float value) {
+ public static VariantPrimitive of(float value) {
return new PrimitiveWrapper<>(PhysicalType.FLOAT, value);
}
- static VariantPrimitive of(double value) {
+ public static VariantPrimitive of(double value) {
return new PrimitiveWrapper<>(PhysicalType.DOUBLE, value);
}
- static VariantPrimitive ofDate(int value) {
+ public static VariantPrimitive ofDate(int value) {
return new PrimitiveWrapper<>(PhysicalType.DATE, value);
}
- static VariantPrimitive ofIsoDate(String value) {
+ public static VariantPrimitive ofIsoDate(String value) {
return ofDate(DateTimeUtil.isoDateToDays(value));
}
- static VariantPrimitive ofTimestamptz(long value) {
+ public static VariantPrimitive ofTimestamptz(long value) {
return new PrimitiveWrapper<>(PhysicalType.TIMESTAMPTZ, value);
}
- static VariantPrimitive ofIsoTimestamptz(String value) {
+ public static VariantPrimitive ofIsoTimestamptz(String value) {
return ofTimestamptz(DateTimeUtil.isoTimestamptzToMicros(value));
}
- static VariantPrimitive ofTimestampntz(long value) {
+ public static VariantPrimitive ofTimestampntz(long value) {
return new PrimitiveWrapper<>(PhysicalType.TIMESTAMPNTZ, value);
}
- static VariantPrimitive ofIsoTimestampntz(String value) {
+ public static VariantPrimitive ofIsoTimestampntz(String value) {
return ofTimestampntz(DateTimeUtil.isoTimestampToMicros(value));
}
- static VariantPrimitive of(BigDecimal value) {
+ public static VariantPrimitive of(BigDecimal value) {
int bitLength = value.unscaledValue().bitLength();
if (bitLength < 32) {
return new PrimitiveWrapper<>(PhysicalType.DECIMAL4, value);
@@ -154,11 +158,11 @@ static VariantPrimitive of(BigDecimal value) {
throw new UnsupportedOperationException("Unsupported decimal precision: " + value.precision());
}
- static VariantPrimitive of(ByteBuffer value) {
+ public static VariantPrimitive of(ByteBuffer value) {
return new PrimitiveWrapper<>(PhysicalType.BINARY, value);
}
- static VariantPrimitive of(String value) {
+ public static VariantPrimitive of(String value) {
return new PrimitiveWrapper<>(PhysicalType.STRING, value);
}
}
diff --git a/core/src/test/java/org/apache/iceberg/TestRowLineageMetadata.java b/core/src/test/java/org/apache/iceberg/TestRowLineageMetadata.java
index 0fb9ee880f6b..3a72f654038a 100644
--- a/core/src/test/java/org/apache/iceberg/TestRowLineageMetadata.java
+++ b/core/src/test/java/org/apache/iceberg/TestRowLineageMetadata.java
@@ -26,6 +26,7 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.iceberg.exceptions.ValidationException;
+import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
import org.apache.iceberg.relocated.com.google.common.primitives.Ints;
import org.apache.iceberg.types.Types;
import org.junit.jupiter.api.AfterEach;
@@ -306,6 +307,20 @@ public void testEnableRowLineageViaProperty() {
assertThat(table.ops().current().rowLineageEnabled()).isTrue();
}
+ @TestTemplate
+ public void testEnableRowLineageViaPropertyAtTableCreation() {
+ assumeThat(formatVersion).isGreaterThanOrEqualTo(TableMetadata.MIN_FORMAT_VERSION_ROW_LINEAGE);
+
+ TestTables.TestTable table =
+ TestTables.create(
+ tableDir,
+ "test",
+ TEST_SCHEMA,
+ ImmutableMap.of(TableProperties.ROW_LINEAGE, "true"),
+ formatVersion);
+ assertThat(table.ops().current().rowLineageEnabled()).isTrue();
+ }
+
private final AtomicInteger fileNum = new AtomicInteger(0);
private DataFile fileWithRows(long numRows) {
diff --git a/core/src/test/java/org/apache/iceberg/TestSchemaParser.java b/core/src/test/java/org/apache/iceberg/TestSchemaParser.java
index 108371416b14..acdba85adf55 100644
--- a/core/src/test/java/org/apache/iceberg/TestSchemaParser.java
+++ b/core/src/test/java/org/apache/iceberg/TestSchemaParser.java
@@ -127,4 +127,14 @@ public void testPrimitiveTypeDefaultValues(Type.PrimitiveType type, Literal> d
assertThat(serialized.findField("col_with_default").writeDefault())
.isEqualTo(defaultValue.value());
}
+
+ @Test
+ public void testVariantType() throws IOException {
+ Schema schema =
+ new Schema(
+ Types.NestedField.required(1, "id", Types.IntegerType.get()),
+ Types.NestedField.optional(2, "data", Types.VariantType.get()));
+
+ writeAndValidate(schema);
+ }
}
diff --git a/core/src/test/java/org/apache/iceberg/TestSchemaUnionByFieldName.java b/core/src/test/java/org/apache/iceberg/TestSchemaUnionByFieldName.java
index 3a61ce8a1513..aa478f85260e 100644
--- a/core/src/test/java/org/apache/iceberg/TestSchemaUnionByFieldName.java
+++ b/core/src/test/java/org/apache/iceberg/TestSchemaUnionByFieldName.java
@@ -27,7 +27,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.iceberg.expressions.Literal;
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
-import org.apache.iceberg.types.Type.PrimitiveType;
+import org.apache.iceberg.types.Type;
import org.apache.iceberg.types.Types;
import org.apache.iceberg.types.Types.BinaryType;
import org.apache.iceberg.types.Types.BooleanType;
@@ -43,13 +43,16 @@
import org.apache.iceberg.types.Types.StringType;
import org.apache.iceberg.types.Types.StructType;
import org.apache.iceberg.types.Types.TimeType;
+import org.apache.iceberg.types.Types.TimestampNanoType;
import org.apache.iceberg.types.Types.TimestampType;
import org.apache.iceberg.types.Types.UUIDType;
+import org.apache.iceberg.types.Types.UnknownType;
+import org.apache.iceberg.types.Types.VariantType;
import org.junit.jupiter.api.Test;
public class TestSchemaUnionByFieldName {
- private static List extends PrimitiveType> primitiveTypes() {
+ private static List extends Type> primitiveTypes() {
return Lists.newArrayList(
StringType.get(),
TimeType.get(),
@@ -64,11 +67,15 @@ private static List extends PrimitiveType> primitiveTypes() {
FixedType.ofLength(10),
DecimalType.of(10, 2),
LongType.get(),
- FloatType.get());
+ FloatType.get(),
+ VariantType.get(),
+ UnknownType.get(),
+ TimestampNanoType.withoutZone(),
+ TimestampNanoType.withZone());
}
private static NestedField[] primitiveFields(
- Integer initialValue, List extends PrimitiveType> primitiveTypes) {
+ Integer initialValue, List extends Type> primitiveTypes) {
AtomicInteger atomicInteger = new AtomicInteger(initialValue);
return primitiveTypes.stream()
.map(
@@ -76,7 +83,7 @@ private static NestedField[] primitiveFields(
optional(
atomicInteger.incrementAndGet(),
type.toString(),
- Types.fromPrimitiveString(type.toString())))
+ Types.fromTypeName(type.toString())))
.toArray(NestedField[]::new);
}
@@ -104,7 +111,7 @@ public void testAddFieldWithDefault() {
@Test
public void testAddTopLevelListOfPrimitives() {
- for (PrimitiveType primitiveType : primitiveTypes()) {
+ for (Type primitiveType : primitiveTypes()) {
Schema newSchema =
new Schema(optional(1, "aList", Types.ListType.ofOptional(2, primitiveType)));
Schema applied = new SchemaUpdate(new Schema(), 0).unionByNameWith(newSchema).apply();
@@ -114,7 +121,13 @@ public void testAddTopLevelListOfPrimitives() {
@Test
public void testAddTopLevelMapOfPrimitives() {
- for (PrimitiveType primitiveType : primitiveTypes()) {
+ for (Type primitiveType : primitiveTypes()) {
+ if (primitiveType.equals(UnknownType.get())) {
+ // The UnknownType has to be optional, and this conflicts with the map key that must be
+ // required
+ continue;
+ }
+
Schema newSchema =
new Schema(
optional(1, "aMap", Types.MapType.ofOptional(2, 3, primitiveType, primitiveType)));
@@ -125,7 +138,7 @@ public void testAddTopLevelMapOfPrimitives() {
@Test
public void testAddTopLevelStructOfPrimitives() {
- for (PrimitiveType primitiveType : primitiveTypes()) {
+ for (Type primitiveType : primitiveTypes()) {
Schema currentSchema =
new Schema(
optional(1, "aStruct", Types.StructType.of(optional(2, "primitive", primitiveType))));
@@ -136,7 +149,7 @@ public void testAddTopLevelStructOfPrimitives() {
@Test
public void testAddNestedPrimitive() {
- for (PrimitiveType primitiveType : primitiveTypes()) {
+ for (Type primitiveType : primitiveTypes()) {
Schema currentSchema = new Schema(optional(1, "aStruct", Types.StructType.of()));
Schema newSchema =
new Schema(
diff --git a/core/src/test/java/org/apache/iceberg/TestSchemaUpdate.java b/core/src/test/java/org/apache/iceberg/TestSchemaUpdate.java
index 38cf6da18a3e..d1591f80d836 100644
--- a/core/src/test/java/org/apache/iceberg/TestSchemaUpdate.java
+++ b/core/src/test/java/org/apache/iceberg/TestSchemaUpdate.java
@@ -2439,4 +2439,47 @@ public void testMoveDeletedNestedStructFieldToFirst() {
assertThat(actual.asStruct()).isEqualTo(expected.asStruct());
}
+
+ @Test
+ public void testAddUnknown() {
+ Schema schema = new Schema(required(1, "id", Types.LongType.get()));
+ Schema expected =
+ new Schema(
+ required(1, "id", Types.LongType.get()), optional(2, "unk", Types.UnknownType.get()));
+
+ Schema actual =
+ new SchemaUpdate(schema, schema.highestFieldId())
+ .addColumn("unk", Types.UnknownType.get())
+ .apply();
+
+ assertThat(actual.asStruct()).isEqualTo(expected.asStruct());
+ }
+
+ @Test
+ public void testAddUnknownNonNullDefault() {
+ Schema schema = new Schema(required(1, "id", Types.LongType.get()));
+
+ assertThatThrownBy(
+ () ->
+ new SchemaUpdate(schema, schema.highestFieldId())
+ .allowIncompatibleChanges()
+ .addColumn("unk", Types.UnknownType.get(), Literal.of("string!"))
+ .apply())
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Cannot cast default value to unknown: \"string!\"");
+ }
+
+ @Test
+ public void testAddRequiredUnknown() {
+ Schema schema = new Schema(required(1, "id", Types.LongType.get()));
+
+ assertThatThrownBy(
+ () ->
+ new SchemaUpdate(schema, schema.highestFieldId())
+ .allowIncompatibleChanges()
+ .addRequiredColumn("unk", Types.UnknownType.get())
+ .apply())
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Cannot create required field with unknown type: unk");
+ }
}
diff --git a/core/src/test/java/org/apache/iceberg/TestTables.java b/core/src/test/java/org/apache/iceberg/TestTables.java
index eeff5db8e5a6..de4aff8142b9 100644
--- a/core/src/test/java/org/apache/iceberg/TestTables.java
+++ b/core/src/test/java/org/apache/iceberg/TestTables.java
@@ -52,6 +52,26 @@ public static TestTable create(
return create(temp, name, schema, spec, SortOrder.unsorted(), formatVersion);
}
+ public static TestTable create(
+ File temp, String name, Schema schema, Map properties, int formatVersion) {
+ TestTableOperations ops = new TestTableOperations(name, temp);
+ if (ops.current() != null) {
+ throw new AlreadyExistsException("Table %s already exists at location: %s", name, temp);
+ }
+
+ ops.commit(
+ null,
+ newTableMetadata(
+ schema,
+ PartitionSpec.unpartitioned(),
+ SortOrder.unsorted(),
+ temp.toString(),
+ properties,
+ formatVersion));
+
+ return new TestTable(ops, name);
+ }
+
public static TestTable create(
File temp,
String name,
diff --git a/core/src/test/java/org/apache/iceberg/mapping/TestNameMapping.java b/core/src/test/java/org/apache/iceberg/mapping/TestNameMapping.java
index d30a93d50d49..3ebf4d9242ab 100644
--- a/core/src/test/java/org/apache/iceberg/mapping/TestNameMapping.java
+++ b/core/src/test/java/org/apache/iceberg/mapping/TestNameMapping.java
@@ -289,4 +289,16 @@ public void testMappingFindByName() {
"location",
MappedFields.of(MappedField.of(11, "latitude"), MappedField.of(12, "longitude"))));
}
+
+ @Test
+ public void testMappingVariantType() {
+ Schema schema =
+ new Schema(
+ required(1, "id", Types.LongType.get()), required(2, "data", Types.VariantType.get()));
+
+ MappedFields expected = MappedFields.of(MappedField.of(1, "id"), MappedField.of(2, "data"));
+
+ NameMapping mapping = MappingUtil.create(schema);
+ assertThat(mapping.asMappedFields()).isEqualTo(expected);
+ }
}
diff --git a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
index 6a5f22075c6e..696240bb6da2 100644
--- a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
+++ b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
@@ -66,6 +66,7 @@
import org.apache.iceberg.exceptions.ServiceFailureException;
import org.apache.iceberg.expressions.Expressions;
import org.apache.iceberg.inmemory.InMemoryCatalog;
+import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
import org.apache.iceberg.relocated.com.google.common.collect.Maps;
@@ -2505,6 +2506,127 @@ public void testNamespaceExistsViaHEADRequest() {
any());
}
+ @Test
+ public void testNamespaceExistsFallbackToGETRequest() {
+ RESTCatalogAdapter adapter =
+ Mockito.spy(
+ new RESTCatalogAdapter(backendCatalog) {
+ @Override
+ public T execute(
+ HTTPRequest request,
+ Class responseType,
+ Consumer errorHandler,
+ Consumer