|
112 | 112 | import org.apache.iceberg.Snapshot; |
113 | 113 | import org.apache.iceberg.SortOrder; |
114 | 114 | import org.apache.iceberg.Table; |
| 115 | +import org.apache.iceberg.TableMetadata; |
115 | 116 | import org.apache.iceberg.TableProperties; |
116 | 117 | import org.apache.iceberg.Transaction; |
117 | 118 | import org.apache.iceberg.UpdatePartitionSpec; |
|
188 | 189 | import static com.facebook.presto.iceberg.IcebergTableType.CHANGELOG; |
189 | 190 | import static com.facebook.presto.iceberg.IcebergTableType.DATA; |
190 | 191 | import static com.facebook.presto.iceberg.IcebergTableType.EQUALITY_DELETES; |
| 192 | +import static com.facebook.presto.iceberg.IcebergUtil.MAX_FORMAT_VERSION_FOR_ROW_LEVEL_OPERATIONS; |
191 | 193 | import static com.facebook.presto.iceberg.IcebergUtil.MIN_FORMAT_VERSION_FOR_DELETE; |
192 | 194 | import static com.facebook.presto.iceberg.IcebergUtil.getColumns; |
193 | 195 | import static com.facebook.presto.iceberg.IcebergUtil.getColumnsForWrite; |
@@ -357,6 +359,43 @@ public Optional<IcebergProcedureContext> getProcedureContext() |
357 | 359 | return this.procedureContext; |
358 | 360 | } |
359 | 361 |
|
| 362 | + /** |
| 363 | + * Validates that an Iceberg table does not use unsupported v3 features. |
| 364 | + * TODO: Remove when Iceberg v3 is fully supported |
| 365 | + */ |
| 366 | + protected static void validateTableForPresto(BaseTable table, Optional<Long> tableSnapshotId) |
| 367 | + { |
| 368 | + Snapshot snapshot = tableSnapshotId |
| 369 | + .map(table::snapshot) |
| 370 | + .orElse(table.currentSnapshot()); |
| 371 | + if (snapshot == null) { |
| 372 | + // empty table, nothing to validate |
| 373 | + return; |
| 374 | + } |
| 375 | + |
| 376 | + TableMetadata metadata = table.operations().current(); |
| 377 | + if (metadata.formatVersion() < 3) { |
| 378 | + return; |
| 379 | + } |
| 380 | + |
| 381 | + Schema schema = metadata.schemasById().get(snapshot.schemaId()); |
| 382 | + if (schema == null) { |
| 383 | + schema = metadata.schema(); |
| 384 | + } |
| 385 | + |
| 386 | + // Reject schema default values (initial-default / write-default) |
| 387 | + for (Types.NestedField field : schema.columns()) { |
| 388 | + if (field.initialDefault() != null || field.writeDefault() != null) { |
| 389 | + throw new PrestoException(NOT_SUPPORTED, "Iceberg v3 column default values are not supported"); |
| 390 | + } |
| 391 | + } |
| 392 | + |
| 393 | + // Reject Iceberg table encryption |
| 394 | + if (!metadata.encryptionKeys().isEmpty() || snapshot.keyId() != null || metadata.properties().containsKey("encryption.key-id")) { |
| 395 | + throw new PrestoException(NOT_SUPPORTED, "Iceberg table encryption is not supported"); |
| 396 | + } |
| 397 | + } |
| 398 | + |
360 | 399 | /** |
361 | 400 | * This class implements the default implementation for getTableLayoutForConstraint which will be used in the case of a Java Worker |
362 | 401 | */ |
@@ -832,10 +871,17 @@ public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorT |
832 | 871 | Table icebergTable = getIcebergTable(session, icebergTableHandle.getSchemaTableName()); |
833 | 872 | int formatVersion = ((BaseTable) icebergTable).operations().current().formatVersion(); |
834 | 873 |
|
835 | | - if (formatVersion < MIN_FORMAT_VERSION_FOR_DELETE || |
836 | | - !Optional.ofNullable(icebergTable.properties().get(TableProperties.UPDATE_MODE)) |
837 | | - .map(mode -> mode.equals(MERGE_ON_READ.modeName())) |
838 | | - .orElse(false)) { |
| 874 | + if (formatVersion < MIN_FORMAT_VERSION_FOR_DELETE) { |
| 875 | + throw new PrestoException(ICEBERG_INVALID_FORMAT_VERSION, |
| 876 | + "Iceberg table updates require at least format version 2 and update mode must be merge-on-read"); |
| 877 | + } |
| 878 | + if (formatVersion > MAX_FORMAT_VERSION_FOR_ROW_LEVEL_OPERATIONS) { |
| 879 | + throw new PrestoException(NOT_SUPPORTED, |
| 880 | + format("Iceberg table updates for format version %s are not supported yet", formatVersion)); |
| 881 | + } |
| 882 | + if (!Optional.ofNullable(icebergTable.properties().get(TableProperties.UPDATE_MODE)) |
| 883 | + .map(mode -> mode.equals(MERGE_ON_READ.modeName())) |
| 884 | + .orElse(false)) { |
839 | 885 | throw new PrestoException(ICEBERG_INVALID_FORMAT_VERSION, |
840 | 886 | "Iceberg table updates require at least format version 2 and update mode must be merge-on-read"); |
841 | 887 | } |
@@ -1174,6 +1220,8 @@ public ConnectorInsertTableHandle beginInsert(ConnectorSession session, Connecto |
1174 | 1220 | verify(table.getIcebergTableName().getTableType() == DATA, "only the data table can have data inserted"); |
1175 | 1221 | Table icebergTable = getIcebergTable(session, table.getSchemaTableName()); |
1176 | 1222 | validateTableMode(session, icebergTable); |
| 1223 | + BaseTable baseTable = (BaseTable) icebergTable; |
| 1224 | + validateTableForPresto(baseTable, Optional.ofNullable(baseTable.currentSnapshot()).map(Snapshot::snapshotId)); |
1177 | 1225 |
|
1178 | 1226 | return beginIcebergTableInsert(session, table, icebergTable); |
1179 | 1227 | } |
@@ -1364,6 +1412,10 @@ public ConnectorDeleteTableHandle beginDelete(ConnectorSession session, Connecto |
1364 | 1412 | if (formatVersion < MIN_FORMAT_VERSION_FOR_DELETE) { |
1365 | 1413 | throw new PrestoException(NOT_SUPPORTED, format("This connector only supports delete where one or more partitions are deleted entirely for table versions older than %d", MIN_FORMAT_VERSION_FOR_DELETE)); |
1366 | 1414 | } |
| 1415 | + if (formatVersion > MAX_FORMAT_VERSION_FOR_ROW_LEVEL_OPERATIONS) { |
| 1416 | + throw new PrestoException(NOT_SUPPORTED, |
| 1417 | + format("Iceberg table updates for format version %s are not supported yet", formatVersion)); |
| 1418 | + } |
1367 | 1419 | if (getDeleteMode(icebergTable) == RowLevelOperationMode.COPY_ON_WRITE) { |
1368 | 1420 | throw new PrestoException(NOT_SUPPORTED, "This connector only supports delete where one or more partitions are deleted entirely. Configure write.delete.mode table property to allow row level deletions."); |
1369 | 1421 | } |
@@ -1620,11 +1672,17 @@ public ConnectorTableHandle beginUpdate(ConnectorSession session, ConnectorTable |
1620 | 1672 | IcebergTableHandle handle = (IcebergTableHandle) tableHandle; |
1621 | 1673 | Table icebergTable = getIcebergTable(session, handle.getSchemaTableName()); |
1622 | 1674 | int formatVersion = ((BaseTable) icebergTable).operations().current().formatVersion(); |
1623 | | - if (formatVersion < MIN_FORMAT_VERSION_FOR_DELETE || |
1624 | | - !Optional.ofNullable(icebergTable.properties().get(TableProperties.UPDATE_MODE)) |
1625 | | - .map(mode -> mode.equals(MERGE_ON_READ.modeName())) |
1626 | | - .orElse(false)) { |
1627 | | - throw new RuntimeException("Iceberg table updates require at least format version 2 and update mode must be merge-on-read"); |
| 1675 | + if (formatVersion < MIN_FORMAT_VERSION_FOR_DELETE) { |
| 1676 | + throw new PrestoException(NOT_SUPPORTED, "Iceberg table updates require at least format version 2"); |
| 1677 | + } |
| 1678 | + if (formatVersion > MAX_FORMAT_VERSION_FOR_ROW_LEVEL_OPERATIONS) { |
| 1679 | + throw new PrestoException(NOT_SUPPORTED, |
| 1680 | + format("Iceberg table updates for format version %s are not supported yet", formatVersion)); |
| 1681 | + } |
| 1682 | + if (!Optional.ofNullable(icebergTable.properties().get(TableProperties.UPDATE_MODE)) |
| 1683 | + .map(mode -> mode.equals(MERGE_ON_READ.modeName())) |
| 1684 | + .orElse(false)) { |
| 1685 | + throw new PrestoException(NOT_SUPPORTED, "Iceberg table updates require update mode to be merge-on-read"); |
1628 | 1686 | } |
1629 | 1687 | validateTableMode(session, icebergTable); |
1630 | 1688 | transaction = icebergTable.newTransaction(); |
|
0 commit comments