@Override
  public ConnectorTableProperties getTableProperties(
      ConnectorSession session, ConnectorTableHandle tableHandle) {
    IcebergTableHandle table = (IcebergTableHandle) tableHandle;

    if (table.getSnapshotId().isEmpty()) {
      // A table with missing snapshot id produces no splits, so we optimize here by returning
      // TupleDomain.none() as the predicate
      return new ConnectorTableProperties(
          TupleDomain.none(),
          Optional.empty(),
          Optional.empty(),
          Optional.empty(),
          ImmutableList.of());
    }

    Table icebergTable = loadTable(session, table.getSchemaTableName());

    // Extract identity partition fields that are present in all partition specs, for creating the
    // discrete predicates.
    Set<Integer> partitionSourceIds = identityPartitionColumnsInAllSpecs(icebergTable);

    TupleDomain<IcebergColumnHandle> enforcedPredicate = table.getEnforcedPredicate();

    DiscretePredicates discretePredicates = null;
    if (!partitionSourceIds.isEmpty()) {
      // Extract identity partition columns
      Map<Integer, IcebergColumnHandle> columns =
          getColumns(icebergTable.schema(), typeManager).stream()
              .filter(column -> partitionSourceIds.contains(column.getId()))
              .collect(toImmutableMap(IcebergColumnHandle::getId, identity()));

      Supplier<List<FileScanTask>> lazyFiles =
          Suppliers.memoize(
              () -> {
                TableScan tableScan =
                    icebergTable
                        .newScan()
                        .useSnapshot(table.getSnapshotId().get())
                        .filter(toIcebergExpression(enforcedPredicate));

                try (CloseableIterable<FileScanTask> iterator = tableScan.planFiles()) {
                  return ImmutableList.copyOf(iterator);
                } catch (IOException e) {
                  throw new UncheckedIOException(e);
                }
              });

      Iterable<FileScanTask> files = () -> lazyFiles.get().iterator();

      Iterable<TupleDomain<ColumnHandle>> discreteTupleDomain =
          Iterables.transform(
              files,
              fileScan -> {
                // Extract partition values in the data file
                Map<Integer, Optional<String>> partitionColumnValueStrings =
                    getPartitionKeys(fileScan);
                Map<ColumnHandle, NullableValue> partitionValues =
                    partitionSourceIds.stream()
                        .filter(partitionColumnValueStrings::containsKey)
                        .collect(
                            toImmutableMap(
                                columns::get,
                                columnId -> {
                                  IcebergColumnHandle column = columns.get(columnId);
                                  Object prestoValue =
                                      deserializePartitionValue(
                                          column.getType(),
                                          partitionColumnValueStrings.get(columnId).orElse(null),
                                          column.getName());

                                  return NullableValue.of(column.getType(), prestoValue);
                                }));

                return TupleDomain.fromFixedValues(partitionValues);
              });

      discretePredicates =
          new DiscretePredicates(
              columns.values().stream().map(ColumnHandle.class::cast).collect(toImmutableList()),
              discreteTupleDomain);
    }

    return new ConnectorTableProperties(
        // Using the predicate here directly avoids eagerly loading all partition values. Logically,
        // this
        // still keeps predicate and discretePredicates evaluation the same on every row of the
        // table. This
        // can be further optimized by intersecting with partition values at the cost of iterating
        // over all tableScan.planFiles() and caching partition values in table handle.
        enforcedPredicate.transformKeys(ColumnHandle.class::cast),
        // TODO: implement table partitioning
        Optional.empty(),
        Optional.empty(),
        Optional.ofNullable(discretePredicates),
        ImmutableList.of());
  }
Iceberg original metadata has some problems for mixed-format table, such as iceberg version,
table type. So copy from IcebergMetadata and made some change
amoro-format-mixed/amoro-mixed-trino/src/main/java/org/apache/amoro/trino/unkeyed/IcebergMetadata.java