All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netease.arctic.trace.AmsTableTracer Maven / Gradle / Ivy

/*
 * 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 com.netease.arctic.trace;

import com.netease.arctic.AmsClient;
import com.netease.arctic.ams.api.CommitMetaProducer;
import com.netease.arctic.ams.api.Constants;
import com.netease.arctic.ams.api.SchemaUpdateMeta;
import com.netease.arctic.ams.api.TableChange;
import com.netease.arctic.ams.api.TableCommitMeta;
import com.netease.arctic.table.ArcticTable;
import com.netease.arctic.table.ChangeTable;
import com.netease.arctic.table.UnkeyedTable;
import com.netease.arctic.utils.ConvertStructUtil;
import com.netease.arctic.utils.SnapshotFileUtil;
import com.netease.arctic.shade.org.apache.iceberg.DataFile;
import com.netease.arctic.shade.org.apache.iceberg.DeleteFile;
import com.netease.arctic.shade.org.apache.iceberg.Snapshot;
import com.netease.arctic.shade.org.apache.iceberg.SnapshotSummary;
import com.netease.arctic.shade.org.apache.iceberg.Table;
import com.netease.arctic.shade.org.apache.iceberg.relocated.com.google.common.collect.Lists;
import com.netease.arctic.shade.org.apache.iceberg.util.PropertyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Implementation of {@link TableTracer}, trace table changes and report changes to ams when committing ignore errors.
 */
public class AmsTableTracer implements TableTracer {

  private static final Logger LOG = LoggerFactory.getLogger(AmsTableTracer.class);

  private final ArcticTable table;
  private final String innerTable;
  private final AmsClient client;

  private final Map snapshotSummary = new HashMap<>();
  private final Map transactionSnapshotTableChanges = new LinkedHashMap<>();
  private final List updateColumns = new ArrayList<>();

  private String action;
  private Map properties;
  private InternalTableChange defaultTableChange;

  /**
   * Construct a new AmsTableTracer
   *
   * @param table table object
   * @param action Table change operation name
   * @param client AMS client
   * @param commitNewSnapshot Whether to submit a new snapshot(but not a transaction)
   */
  public AmsTableTracer(UnkeyedTable table, String action, AmsClient client, boolean commitNewSnapshot) {
    this.innerTable = table instanceof ChangeTable ?
        Constants.INNER_TABLE_CHANGE : Constants.INNER_TABLE_BASE;
    this.table = table;
    this.client = client;
    if (commitNewSnapshot) {
      this.defaultTableChange = new InternalTableChange();
    }
    setAction(action);
  }

  public AmsTableTracer(UnkeyedTable table, AmsClient client, boolean commitNewSnapshot) {
    this(table, null, client, commitNewSnapshot);
  }

  @Override
  public void addDataFile(DataFile dataFile) {
    getDefaultChange().addDataFile(dataFile);
  }

  @Override
  public void deleteDataFile(DataFile dataFile) {
    getDefaultChange().deleteDataFile(dataFile);
  }

  @Override
  public void addDeleteFile(DeleteFile deleteFile) {
    getDefaultChange().addDeleteFile(deleteFile);
  }

  @Override
  public void deleteDeleteFile(DeleteFile deleteFile) {
    getDefaultChange().deleteDeleteFile(deleteFile);
  }

  private InternalTableChange getDefaultChange() {
    if (defaultTableChange == null) {
      throw new RuntimeException("This operation should not result in changes to files or snapshots");
    }
    return defaultTableChange;
  }

  public void addTransactionTableSnapshot(Long snapshotId, AmsTableTracer.InternalTableChange internalTableChange) {
    transactionSnapshotTableChanges.putIfAbsent(snapshotId, internalTableChange);
  }

  public ArcticTable table() {
    return table;
  }

  public String innerTable() {
    return innerTable;
  }

  @Override
  public void commit() {
    TableCommitMeta commitMeta = new TableCommitMeta();
    commitMeta.setTableIdentifier(table.id().buildTableIdentifier());
    commitMeta.setAction(action);
    String commitMetaSource = PropertyUtil.propertyAsString(
        snapshotSummary,
        com.netease.arctic.trace.SnapshotSummary.SNAPSHOT_PRODUCER,
        com.netease.arctic.trace.SnapshotSummary.SNAPSHOT_PRODUCER_DEFAULT);
    commitMeta.setCommitMetaProducer(CommitMetaProducer.valueOf(commitMetaSource));
    commitMeta.setCommitTime(System.currentTimeMillis());
    boolean update = false;


    if (transactionSnapshotTableChanges.size() > 0) {
      transactionSnapshotTableChanges.forEach((snapshotId, internalTableChange) -> {
        if (table.isUnkeyedTable()) {
          Snapshot snapshot = table.asUnkeyedTable().snapshot(snapshotId);
          TableChange tableChange = internalTableChange.toTableChange(table, snapshot, innerTable);
          commitMeta.addToChanges(tableChange);
        }
      });
      update = true;
    } else if (defaultTableChange != null) {
      Table traceTable;
      if (table.isUnkeyedTable()) {
        traceTable = table.asUnkeyedTable();
      } else {
        throw new IllegalStateException("can't apply table change on keyed table.");
      }

      TableChange tableChange =
          defaultTableChange.toTableChange(table, traceTable.currentSnapshot(), innerTable);
      commitMeta.addToChanges(tableChange);
      update = true;
    }

    if (updateColumns.size() > 0 && Constants.INNER_TABLE_BASE.equals(innerTable)) {
      int schemaId = table.schema().schemaId();
      SchemaUpdateMeta ddlCommitMeta = new SchemaUpdateMeta();
      ddlCommitMeta.setSchemaId(schemaId);
      List commitUpdateColumns =
          updateColumns.stream().map(AmsTableTracer::covert).collect(Collectors.toList());
      ddlCommitMeta.setUpdateColumns(commitUpdateColumns);
      commitMeta.setSchemaUpdateMeta(ddlCommitMeta);
      update = true;
    }
    if (this.properties != null && Constants.INNER_TABLE_BASE.equals(innerTable)) {
      commitMeta.setProperties(this.properties);
      update = true;
    }
    if (!update) {
      return;
    }

    try {
      client.tableCommit(commitMeta);
    } catch (Throwable t) {
      LOG.warn("trace table commit failed", t);
    }
  }

  @Override
  public void replaceProperties(Map newProperties) {
    this.properties = newProperties;
  }

  @Override
  public void updateColumn(UpdateColumn updateColumn) {
    updateColumns.add(updateColumn);
  }

  @Override
  public void setSnapshotSummary(String key, String value) {
    snapshotSummary.put(key, value);
  }

  public void setAction(String action) {
    this.action = action;
  }

  public static class InternalTableChange {
    private final List addedFiles = Lists.newArrayList();
    private final List deletedFiles = Lists.newArrayList();
    private final List addedDeleteFiles = Lists.newArrayList();
    private final List deletedDeleteFiles = Lists.newArrayList();

    public InternalTableChange() {
    }

    public void addDataFile(DataFile dataFile) {
      addedFiles.add(dataFile);
    }

    public void deleteDataFile(DataFile dataFile) {
      deletedFiles.add(dataFile);
    }

    public void addDeleteFile(DeleteFile deleteFile) {
      addedDeleteFiles.add(deleteFile);
    }

    public void deleteDeleteFile(DeleteFile deleteFile) {
      deletedDeleteFiles.add(deleteFile);
    }

    /**
     * Build {@link TableChange} to report to ams.
     *
     * @param arcticTable arctic table which table change belongs
     * @param snapshot    the snapshot produced in this operation
     * @param innerTable  inner table name
     * @return table change
     */
    public TableChange toTableChange(ArcticTable arcticTable, Snapshot snapshot, String innerTable) {

      long currentSnapshotId = snapshot.snapshotId();
      long parentSnapshotId =
          snapshot.parentId() == null ? -1 : snapshot.parentId();
      Map summary = snapshot.summary();
      long realAddedDataFiles = summary.get(SnapshotSummary.ADDED_FILES_PROP) == null ?
          0 : Long.parseLong(summary.get(SnapshotSummary.ADDED_FILES_PROP));
      long realDeletedDataFiles = summary.get(SnapshotSummary.DELETED_FILES_PROP) == null ?
          0 : Long.parseLong(summary.get(SnapshotSummary.DELETED_FILES_PROP));
      long realAddedDeleteFiles = summary.get(SnapshotSummary.ADDED_DELETE_FILES_PROP) == null ?
          0 : Long.parseLong(summary.get(SnapshotSummary.ADDED_DELETE_FILES_PROP));
      long readRemovedDeleteFiles = summary.get(SnapshotSummary.REMOVED_DELETE_FILES_PROP) == null ?
          0 : Long.parseLong(summary.get(SnapshotSummary.REMOVED_DELETE_FILES_PROP));

      List addFiles = new ArrayList<>();
      List deleteFiles = new ArrayList<>();
      if (realAddedDataFiles == addedFiles.size() && realDeletedDataFiles == deletedFiles.size() &&
          realAddedDeleteFiles == addedDeleteFiles.size() && readRemovedDeleteFiles == deletedDeleteFiles.size()) {
        addFiles =
            addedFiles.stream().map(file -> ConvertStructUtil.convertToAmsDatafile(file, arcticTable, innerTable))
                .collect(Collectors.toList());
        deleteFiles =
            deletedFiles.stream().map(file -> ConvertStructUtil.convertToAmsDatafile(file, arcticTable, innerTable))
                .collect(Collectors.toList());
        addFiles.addAll(addedDeleteFiles.stream()
            .map(file -> ConvertStructUtil.convertToAmsDatafile(file, arcticTable, innerTable))
            .collect(Collectors.toList()));
        deleteFiles.addAll(deletedDeleteFiles.stream()
            .map(file -> ConvertStructUtil.convertToAmsDatafile(file, arcticTable, innerTable))
            .collect(Collectors.toList()));
      } else {
        // tracer file change info is different from iceberg snapshot, should get iceberg real file change info
        SnapshotFileUtil.getSnapshotFiles(arcticTable, innerTable, snapshot, addFiles, deleteFiles);
      }

      return new TableChange(innerTable, addFiles, deleteFiles, currentSnapshotId,
          parentSnapshotId, snapshot.sequenceNumber());
    }
  }

  private static com.netease.arctic.ams.api.UpdateColumn covert(UpdateColumn updateColumn) {
    com.netease.arctic.ams.api.UpdateColumn commit = new com.netease.arctic.ams.api.UpdateColumn();
    commit.setName(updateColumn.getName());
    commit.setParent(updateColumn.getParent());
    commit.setType(updateColumn.getType() == null ? null : updateColumn.getType().toString());
    commit.setDoc(updateColumn.getDoc());
    commit.setOperate(updateColumn.getOperate().name());
    commit.setIsOptional(updateColumn.getOptional() == null ? null : updateColumn.getOptional().toString());
    commit.setNewName(updateColumn.getNewName());
    return commit;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy