Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 3.9k
[fix](iceberg) Project Iceberg system table scans#65262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
e227d18abbe29f5e7ee11141bc9bFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -28,15 +28,11 @@ | ||
| import org.apache.iceberg.FileScanTask; | ||
| import org.apache.iceberg.StructLike; | ||
| import org.apache.iceberg.io.CloseableIterator; | ||
| import org.apache.iceberg.types.Types.NestedField; | ||
| import org.apache.iceberg.types.Types.StructType; | ||
| import org.apache.iceberg.util.SerializationUtil; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import java.io.IOException; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.TimeZone; | ||
| import java.util.stream.Collectors; | ||
| @@ -50,7 +46,7 @@ public class IcebergSysTableJniScanner extends JniScanner { | ||
| private final ClassLoader classLoader; | ||
| private final PreExecutionAuthenticator preExecutionAuthenticator; | ||
| private final FileScanTask scanTask; | ||
| private final List<SelectedField> fields; | ||
| private final int requiredFieldCount; | ||
| private final String timezone; | ||
| private CloseableIterator<StructLike> reader; | ||
| @@ -60,15 +56,22 @@ public IcebergSysTableJniScanner(int batchSize, Map<String, String> params) { | ||
| Preconditions.checkArgument(serializedSplitParams != null && !serializedSplitParams.isEmpty(), | ||
| "serialized_split should not be empty"); | ||
| this.scanTask = SerializationUtil.deserializeFromBase64(serializedSplitParams); | ||
| String[] requiredFields = params.get("required_fields").split(","); | ||
| this.fields = selectSchema(scanTask.schema().asStruct(), requiredFields); | ||
| String requiredFieldsParam = params.get("required_fields"); | ||
| Preconditions.checkArgument(requiredFieldsParam != null && !requiredFieldsParam.isEmpty(), | ||
| "required_fields should not be empty"); | ||
| String[] requiredFields = requiredFieldsParam.split(","); | ||
| this.requiredFieldCount = requiredFields.length; | ||
| this.timezone = params.getOrDefault("time_zone", TimeZone.getDefault().getID()); | ||
| Map<String, String> hadoopOptionParams = params.entrySet().stream() | ||
| .filter(kv -> kv.getKey().startsWith(HADOOP_OPTION_PREFIX)) | ||
| .collect(Collectors | ||
| .toMap(kv1 -> kv1.getKey().substring(HADOOP_OPTION_PREFIX.length()), kv1 -> kv1.getValue())); | ||
| this.preExecutionAuthenticator = PreExecutionAuthenticatorCache.getAuthenticator(hadoopOptionParams); | ||
| ColumnType[] requiredTypes = parseRequiredTypes(params.get("required_types").split("#"), requiredFields); | ||
| String requiredTypesParam = params.get("required_types"); | ||
| Preconditions.checkArgument(requiredTypesParam != null && !requiredTypesParam.isEmpty(), | ||
| "required_types should not be empty"); | ||
| String[] requiredTypeStrings = requiredTypesParam.split("#"); | ||
| ColumnType[] requiredTypes = parseRequiredTypes(requiredTypeStrings, requiredFields); | ||
| initTableInfo(requiredTypes, requiredFields, batchSize); | ||
| } | ||
| @@ -106,9 +109,10 @@ protected int getNext() throws IOException { | ||
| break; | ||
| } | ||
| StructLike row = reader.next(); | ||
| for (int i = 0; i < fields.size(); i++) { | ||
| SelectedField field = fields.get(i); | ||
| Object value = row.get(field.sourceIndex, field.field.type().typeId().javaClass()); | ||
| for (int i = 0; i < requiredFieldCount; i++) { | ||
| // FE keeps the fields requested by BE at the start of the Iceberg projection. | ||
| // FileScanTask.schema() is not the row schema for every DataTask implementation. | ||
| Object value = row.get(i, Object.class); | ||
| ColumnValue columnValue = new IcebergSysTableColumnValue(value, timezone); | ||
suxiaogang223 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| appendData(i, columnValue); | ||
| } | ||
| @@ -129,35 +133,10 @@ public void close() throws IOException { | ||
| } | ||
| } | ||
| private static List<SelectedField> selectSchema(StructType schema, String[] requiredFields) { | ||
| List<NestedField> schemaFields = schema.fields(); | ||
| List<SelectedField> selectedFields = new ArrayList<>(); | ||
| for (String requiredField : requiredFields) { | ||
| NestedField field = schema.field(requiredField); | ||
| if (field == null) { | ||
| throw new IllegalArgumentException("RequiredField " + requiredField + " not found in schema"); | ||
| } | ||
| int sourceIndex = schemaFields.indexOf(field); | ||
| if (sourceIndex < 0) { | ||
| throw new IllegalArgumentException( | ||
| "RequiredField " + requiredField + " not found in source schema fields"); | ||
| } | ||
| selectedFields.add(new SelectedField(sourceIndex, field)); | ||
| } | ||
| return selectedFields; | ||
| } | ||
| private static final class SelectedField { | ||
| private final int sourceIndex; | ||
| private final NestedField field; | ||
| private SelectedField(int sourceIndex, NestedField field) { | ||
| this.sourceIndex = sourceIndex; | ||
| this.field = field; | ||
| } | ||
| } | ||
| private static ColumnType[] parseRequiredTypes(String[] typeStrings, String[] requiredFields) { | ||
| Preconditions.checkArgument(typeStrings.length == requiredFields.length, | ||
| "required_types size %s does not match required_fields size %s", | ||
| typeStrings.length, requiredFields.length); | ||
| ColumnType[] requiredTypes = new ColumnType[typeStrings.length]; | ||
| for (int i = 0; i < typeStrings.length; i++) { | ||
| String type = typeStrings[i]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -96,6 +96,7 @@ | ||
| import org.apache.iceberg.Table; | ||
| import org.apache.iceberg.TableProperties; | ||
| import org.apache.iceberg.TableScan; | ||
| import org.apache.iceberg.expressions.Binder; | ||
| import org.apache.iceberg.expressions.Expression; | ||
| import org.apache.iceberg.expressions.Expressions; | ||
| import org.apache.iceberg.expressions.InclusiveMetricsEvaluator; | ||
| @@ -108,6 +109,7 @@ | ||
| import org.apache.iceberg.mapping.NameMapping; | ||
| import org.apache.iceberg.mapping.NameMappingParser; | ||
| import org.apache.iceberg.types.Type; | ||
| import org.apache.iceberg.types.TypeUtil; | ||
| import org.apache.iceberg.types.Types.NestedField; | ||
| import org.apache.iceberg.util.ScanTaskUtil; | ||
| import org.apache.iceberg.util.SerializationUtil; | ||
| @@ -121,11 +123,13 @@ | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import java.util.OptionalLong; | ||
| import java.util.Set; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.atomic.AtomicReference; | ||
| @@ -674,11 +678,70 @@ public TableScan createTableScan() throws UserException { | ||
| this.pushdownIcebergPredicates.add(predicate.toString()); | ||
| } | ||
| // Doris reads normal Iceberg table files in BE and applies column pruning through scan range params. | ||
| // System tables are different: Iceberg SDK DataTask materializes rows using the projected scan | ||
| // schema. Keep Doris file slots in the same order as the JNI reader's required fields. | ||
| if (isSystemTable) { | ||
| Schema projectedSchema = getSystemTableProjectedSchema(expressions, scan.isCaseSensitive()); | ||
| Preconditions.checkState(!projectedSchema.columns().isEmpty(), | ||
| "Iceberg system table scan must materialize at least one file slot"); | ||
| scan = scan.project(projectedSchema); | ||
| } | ||
| icebergTableScan = scan.planWith(source.getCatalog().getThreadPoolWithPreAuth()); | ||
| return icebergTableScan; | ||
| } | ||
| @VisibleForTesting | ||
| Schema getSystemTableProjectedSchema(List<Expression> expressions, boolean caseSensitive) | ||
| throws UserException { | ||
| List<NestedField> projectedFields = new ArrayList<>(); | ||
| Set<Integer> projectedFieldIds = new HashSet<>(); | ||
| List<String> partitionKeys = getPathPartitionKeys(); | ||
| for (SlotDescriptor slot : desc.getSlots()) { | ||
| Column column = slot.getColumn(); | ||
| String columnName = column.getName(); | ||
| if (!isFileSlot(classifyColumn(slot, partitionKeys))) { | ||
| continue; | ||
| } | ||
| NestedField field = caseSensitive | ||
| ? icebergTable.schema().findField(columnName) | ||
| : icebergTable.schema().caseInsensitiveFindField(columnName); | ||
| if (field == null) { | ||
| throw new UserException("Column " + columnName + " not found in Iceberg system table schema"); | ||
| } | ||
| if (projectedFieldIds.add(field.fieldId())) { | ||
| projectedFields.add(field); | ||
| } | ||
suxiaogang223 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| Set<Integer> filterFieldIds = Binder.boundReferences( | ||
| icebergTable.schema().asStruct(), expressions, caseSensitive); | ||
| for (Integer fieldId : filterFieldIds) { | ||
| NestedField field = getTopLevelSystemTableField(fieldId); | ||
| if (field == null) { | ||
| throw new UserException( | ||
| "Column with field id " + fieldId + " not found in Iceberg system table schema"); | ||
| } | ||
| if (!projectedFieldIds.contains(field.fieldId())) { | ||
| throw new UserException("Iceberg system table filter column " + field.name() | ||
| + " is not materialized by the planner"); | ||
| } | ||
| } | ||
| return new Schema(projectedFields); | ||
| } | ||
| private NestedField getTopLevelSystemTableField(int fieldId) { | ||
| for (NestedField field : icebergTable.schema().columns()) { | ||
| if (field.fieldId() == fieldId || TypeUtil.getProjectedIds(field.type()).contains(fieldId)) { | ||
| return field; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| private CloseableIterable<FileScanTask> planFileScanTask(TableScan scan) { | ||
| if (!IcebergUtils.isManifestCacheEnabled(source.getCatalog())) { | ||
| return splitFiles(scan); | ||
| @@ -1199,7 +1262,7 @@ private List<Split> doGetSystemTableSplits() throws UserException { | ||
| private boolean isPositionDeletesSystemTable() { | ||
| TableIf targetTable = source.getTargetTable(); | ||
| return targetTable instanceof IcebergSysExternalTable | ||
| && "position_deletes".equalsIgnoreCase(((IcebergSysExternalTable) targetTable).getSysTableType()); | ||
| && ((IcebergSysExternalTable) targetTable).isPositionDeletesTable(); | ||
| } | ||
| private List<Split> doGetPositionDeletesSystemTableSplits() throws UserException { | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.