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

com.databricks.jdbc.api.impl.volume.DatabricksUCVolumeClient Maven / Gradle / Ivy

package com.databricks.jdbc.api.impl.volume;

import static com.databricks.jdbc.common.DatabricksJdbcConstants.VOLUME_OPERATION_STATUS_COLUMN_NAME;
import static com.databricks.jdbc.common.DatabricksJdbcConstants.VOLUME_OPERATION_STATUS_SUCCEEDED;
import static com.databricks.jdbc.common.util.StringUtil.escapeStringLiteral;

import com.databricks.jdbc.api.IDatabricksUCVolumeClient;
import com.databricks.jdbc.api.internal.IDatabricksResultSetInternal;
import com.databricks.jdbc.api.internal.IDatabricksStatementInternal;
import com.databricks.jdbc.log.JdbcLogger;
import com.databricks.jdbc.log.JdbcLoggerFactory;
import java.io.InputStream;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.entity.InputStreamEntity;

/** Implementation for DatabricksUCVolumeClient */
public class DatabricksUCVolumeClient implements IDatabricksUCVolumeClient {

  private static final JdbcLogger LOGGER =
      JdbcLoggerFactory.getLogger(DatabricksUCVolumeClient.class);
  private final Connection connection;

  private static final String UC_VOLUME_COLUMN_NAME =
      "name"; // Column name for the file names within a volume

  private static final String UC_VOLUME_COLUMN_VOLUME_NAME =
      "volume_name"; // Column name for the volume names within a schema

  private static class FilePath {
    public FilePath(String path) {
      int lastSlashIndex = path.lastIndexOf("/");
      folder = (lastSlashIndex >= 0) ? path.substring(0, lastSlashIndex) : "";
      basename = (lastSlashIndex >= 0) ? path.substring(lastSlashIndex + 1) : path;
    }

    public String folder;
    public String basename;
  }

  public DatabricksUCVolumeClient(Connection connection) {
    this.connection = connection;
  }

  private static String getVolumePath(String catalog, String schema, String volume) {
    // We need to escape '' to prevent SQL injection
    return escapeStringLiteral(String.format("/Volumes/%s/%s/%s/", catalog, schema, volume));
  }

  private static String getObjectFullPath(
      String catalog, String schema, String volume, String objectPath) {
    return getVolumePath(catalog, schema, volume) + escapeStringLiteral(objectPath);
  }

  private static String createListQuery(String catalog, String schema, String volume) {
    return String.format("LIST '%s'", getVolumePath(catalog, schema, volume));
  }

  private static String createListQuery(
      String catalog, String schema, String volume, String folder) {
    return (folder.isEmpty())
        ? createListQuery(catalog, schema, volume)
        : createListQuery(catalog, schema, volume + "/" + folder);
  }

  private static String createShowVolumesQuery(String catalog, String schema) {
    return String.format("SHOW VOLUMES IN %s.%s", catalog, schema);
  }

  private static String createGetObjectQuery(
      String catalog, String schema, String volume, String objectPath, String localPath) {
    return String.format(
        "GET '%s' TO '%s'",
        getObjectFullPath(catalog, schema, volume, objectPath), escapeStringLiteral(localPath));
  }

  private static String createGetObjectQueryForInputStream(
      String catalog, String schema, String volume, String objectPath) {
    return String.format(
        "GET '%s' TO '__input_stream__'", getObjectFullPath(catalog, schema, volume, objectPath));
  }

  private static String createPutObjectQuery(
      String catalog,
      String schema,
      String volume,
      String objectPath,
      String localPath,
      boolean toOverwrite) {
    return String.format(
        "PUT '%s' INTO '%s'%s",
        escapeStringLiteral(localPath),
        getObjectFullPath(catalog, schema, volume, objectPath),
        toOverwrite ? " OVERWRITE" : "");
  }

  private static String createPutObjectQueryForInputStream(
      String catalog, String schema, String volume, String objectPath, boolean toOverwrite) {
    return String.format(
        "PUT '__input_stream__' INTO '%s'%s",
        getObjectFullPath(catalog, schema, volume, objectPath), toOverwrite ? " OVERWRITE" : "");
  }

  private static String createDeleteObjectQuery(
      String catalog, String schema, String volume, String objectPath) {
    return String.format("REMOVE '%s'", getObjectFullPath(catalog, schema, volume, objectPath));
  }

  public boolean prefixExists(String catalog, String schema, String volume, String prefix)
      throws SQLException {
    return prefixExists(catalog, schema, volume, prefix, true);
  }

  @Override
  public boolean prefixExists(
      String catalog, String schema, String volume, String prefix, boolean caseSensitive)
      throws SQLException {

    if (prefix.isEmpty()) {
      return false;
    }

    LOGGER.debug(
        String.format(
            "Entering prefixExists method with parameters: catalog={%s}, schema={%s}, volume={%s}, prefix={%s}, caseSensitive={%s}",
            catalog, schema, volume, prefix, caseSensitive));

    FilePath filePath = new FilePath(prefix);

    String listFilesSQLQuery = createListQuery(catalog, schema, volume, filePath.folder);

    try (Statement statement = connection.createStatement()) {
      try (ResultSet resultSet = statement.executeQuery(listFilesSQLQuery)) {
        LOGGER.debug("SQL query executed successfully");
        boolean exists = false;
        while (resultSet.next()) {
          String fileName = resultSet.getString(UC_VOLUME_COLUMN_NAME);
          if (fileName.regionMatches(
              /* ignoreCase= */ !caseSensitive,
              /* targetOffset= */ 0,
              /* StringToCheck= */ filePath.basename,
              /* sourceOffset= */ 0,
              /* lengthToMatch= */ filePath.basename.length())) {
            exists = true;
            break;
          }
        }
        return exists;
      }
    } catch (SQLException e) {
      LOGGER.error("SQL query execution failed " + e);
      throw e;
    }
  }

  @Override
  public boolean objectExists(
      String catalog, String schema, String volume, String objectPath, boolean caseSensitive)
      throws SQLException {

    if (objectPath.isEmpty()) {
      return false;
    }

    LOGGER.info(
        String.format(
            "Entering objectExists method with parameters: catalog={%s}, schema={%s}, volume={%s}, objectPath={%s}, caseSensitive={%s}",
            catalog, schema, volume, objectPath, caseSensitive));

    FilePath filePath = new FilePath(objectPath);

    String listFilesSQLQuery = createListQuery(catalog, schema, volume, filePath.folder);

    try (Statement statement = connection.createStatement()) {
      try (ResultSet resultSet = statement.executeQuery(listFilesSQLQuery)) {
        LOGGER.info("SQL query executed successfully");
        boolean exists = false;
        while (resultSet.next()) {
          String fileName = resultSet.getString(UC_VOLUME_COLUMN_NAME);
          if (fileName.regionMatches(
              /* ignoreCase= */ !caseSensitive,
              /* targetOffset= */ 0,
              /* StringToCheck= */ filePath.basename,
              /* sourceOffset= */ 0,
              /* lengthToMatch= */ filePath.basename.length())) {
            exists = true;
            break;
          }
        }
        return exists;
      }

    } catch (SQLException e) {
      LOGGER.error("SQL query execution failed " + e);
      throw e;
    }
  }

  public boolean objectExists(String catalog, String schema, String volume, String objectPath)
      throws SQLException {
    return objectExists(catalog, schema, volume, objectPath, true);
  }

  @Override
  public boolean volumeExists(
      String catalog, String schema, String volumeName, boolean caseSensitive) throws SQLException {

    LOGGER.info(
        String.format(
            "Entering volumeExists method with parameters: catalog={%s}, schema={%s}, volumeName={%s}, caseSensitive={%s}",
            catalog, schema, volumeName, caseSensitive));

    if (volumeName.isEmpty()) {
      return false;
    }

    String showVolumesSQLQuery = createShowVolumesQuery(catalog, schema);

    try (Statement statement = connection.createStatement()) {
      try (ResultSet resultSet = statement.executeQuery(showVolumesSQLQuery)) {
        LOGGER.info("SQL query executed successfully");
        boolean exists = false;
        while (resultSet.next()) {
          String volume = resultSet.getString(UC_VOLUME_COLUMN_VOLUME_NAME);
          if (volume.regionMatches(
              /* ignoreCase= */ !caseSensitive,
              /* targetOffset= */ 0,
              /* other= */ volumeName,
              /* sourceOffset= */ 0,
              /* len= */ volumeName.length())) {
            exists = true;
            break;
          }
        }
        return exists;
      }
    } catch (SQLException e) {
      LOGGER.error("SQL query execution failed " + e);
      throw e;
    }
  }

  public boolean volumeExists(String catalog, String schema, String volumeName)
      throws SQLException {
    return volumeExists(catalog, schema, volumeName, true);
  }

  /**
   * This functions lists all the files that fall under the specified prefix within the target
   * folder in the specified volume. The prefix is checked with the word after the last / in the
   * input Ex - 1. foo/bar will list all the files within foo folder having bar as prefix | 2.
   * foo/bar/f will list all the files within the bar folder with prefix f | 3. foo/bar/ will list
   * all the files within the bar folder with all prefix
   *
   * @param catalog the catalog name of the cloud storage
   * @param schema the schema name of the cloud storage
   * @param volume the UC volume name of the cloud storage
   * @param prefix the prefix of the filenames to list. This includes the relative path from the
   *     volume as the root directory
   * @param caseSensitive a boolean indicating whether the check should be case-sensitive or not
   * @return List a list of strings indicating the filenames that start with the specified
   *     prefix
   */
  @Override
  public List listObjects(
      String catalog, String schema, String volume, String prefix, boolean caseSensitive)
      throws SQLException {

    LOGGER.info(
        String.format(
            "Entering listObjects method with parameters: catalog={%s}, schema={%s}, volume={%s}, prefix={%s}, caseSensitive={%s}",
            catalog, schema, volume, prefix, caseSensitive));

    FilePath filePath = new FilePath(prefix);

    String listFilesSQLQuery = createListQuery(catalog, schema, volume, filePath.folder);

    try (Statement statement = connection.createStatement()) {
      try (ResultSet resultSet = statement.executeQuery(listFilesSQLQuery)) {
        LOGGER.info("SQL query executed successfully");
        List filenames = new ArrayList<>();
        while (resultSet.next()) {
          String fileName = resultSet.getString("name");
          if (filePath.basename.isEmpty()
              || fileName.regionMatches(
                  /* ignoreCase= */ !caseSensitive,
                  /* targetOffset= */ 0,
                  /* StringToCheck= */ filePath.basename,
                  /* sourceOffset= */ 0,
                  /* lengthToMatch= */ filePath.basename.length())) {
            filenames.add(fileName);
          }
        }
        return filenames;
      }
    } catch (SQLException e) {
      LOGGER.error("SQL query execution failed" + e);
      throw e;
    }
  }

  public List listObjects(String catalog, String schema, String volume, String prefix)
      throws SQLException {
    return listObjects(catalog, schema, volume, prefix, true);
  }

  public boolean getObject(
      String catalog, String schema, String volume, String objectPath, String localPath)
      throws SQLException {
    LOGGER.debug(
        String.format(
            "Entering getObject method with parameters: catalog={%s}, schema={%s}, volume={%s}, objectPath={%s}, localPath={%s}",
            catalog, schema, volume, objectPath, localPath));

    String getObjectQuery = createGetObjectQuery(catalog, schema, volume, objectPath, localPath);

    boolean volumeOperationStatus = false;

    try (Statement statement = connection.createStatement()) {
      try (ResultSet resultSet = statement.executeQuery(getObjectQuery)) {
        LOGGER.info("GET query executed successfully");
        if (resultSet.next()) {
          String volumeOperationStatusString =
              resultSet.getString(VOLUME_OPERATION_STATUS_COLUMN_NAME);
          volumeOperationStatus =
              VOLUME_OPERATION_STATUS_SUCCEEDED.equals(volumeOperationStatusString);
        }
      }
    } catch (SQLException e) {
      LOGGER.error("GET query execution failed " + e);
      throw e;
    }

    return volumeOperationStatus;
  }

  @Override
  public InputStreamEntity getObject(
      String catalog, String schema, String volume, String objectPath) throws SQLException {

    LOGGER.debug(
        String.format(
            "Entering getObject method with parameters: catalog={%s}, schema={%s}, volume={%s}, objectPath={%s}",
            catalog, schema, volume, objectPath));

    String getObjectQuery = createGetObjectQueryForInputStream(catalog, schema, volume, objectPath);

    try (Statement statement = connection.createStatement()) {
      IDatabricksStatementInternal databricksStatement =
          statement.unwrap(IDatabricksStatementInternal.class);
      databricksStatement.allowInputStreamForVolumeOperation(true);

      try (ResultSet resultSet = statement.executeQuery(getObjectQuery)) {
        LOGGER.info("GET query executed successfully");
        if (resultSet.next()) {
          return resultSet
              .unwrap(IDatabricksResultSetInternal.class)
              .getVolumeOperationInputStream();
        } else {
          return null;
        }
      } catch (SQLException e) {
        LOGGER.error("GET query execution failed " + e);
        throw e;
      }
    }
  }

  public boolean putObject(
      String catalog,
      String schema,
      String volume,
      String objectPath,
      String localPath,
      boolean toOverwrite)
      throws SQLException {

    LOGGER.debug(
        String.format(
            "Entering putObject method with parameters: catalog={%s}, schema={%s}, volume={%s}, objectPath={%s}, localPath={%s}, toOverwrite={%s}",
            catalog, schema, volume, objectPath, localPath, toOverwrite));

    String putObjectQuery =
        createPutObjectQuery(catalog, schema, volume, objectPath, localPath, toOverwrite);

    boolean isOperationSucceeded = false;

    try (Statement statement = connection.createStatement()) {
      try (ResultSet resultSet = statement.executeQuery(putObjectQuery)) {
        LOGGER.info("PUT query executed successfully");
        if (resultSet.next()) {
          String volumeOperationStatusString =
              resultSet.getString(VOLUME_OPERATION_STATUS_COLUMN_NAME);
          isOperationSucceeded =
              VOLUME_OPERATION_STATUS_SUCCEEDED.equals(volumeOperationStatusString);
        }
      }
    } catch (SQLException e) {
      LOGGER.error("PUT query execution failed " + e);
      throw e;
    }

    return isOperationSucceeded;
  }

  @Override
  public boolean putObject(
      String catalog,
      String schema,
      String volume,
      String objectPath,
      InputStream inputStream,
      long contentLength,
      boolean toOverwrite)
      throws SQLException {

    LOGGER.debug(
        String.format(
            "Entering putObject method with parameters: catalog={%s}, schema={%s}, volume={%s}, objectPath={%s}, inputStream={%s}, toOverwrite={%s}",
            catalog, schema, volume, objectPath, inputStream, toOverwrite));

    String putObjectQueryForInputStream =
        createPutObjectQueryForInputStream(catalog, schema, volume, objectPath, toOverwrite);

    boolean isOperationSucceeded = false;

    try (Statement statement = connection.createStatement()) {
      IDatabricksStatementInternal databricksStatement =
          statement.unwrap(IDatabricksStatementInternal.class);
      databricksStatement.allowInputStreamForVolumeOperation(true);
      databricksStatement.setInputStreamForUCVolume(
          new InputStreamEntity(inputStream, contentLength));

      try (ResultSet resultSet = statement.executeQuery(putObjectQueryForInputStream)) {
        LOGGER.info("PUT query executed successfully");
        if (resultSet.next()) {
          String volumeOperationStatusString =
              resultSet.getString(VOLUME_OPERATION_STATUS_COLUMN_NAME);
          isOperationSucceeded =
              VOLUME_OPERATION_STATUS_SUCCEEDED.equals(volumeOperationStatusString);
        }
      }
    } catch (SQLException e) {
      LOGGER.error("PUT query execution failed " + e);
      throw e;
    }

    return isOperationSucceeded;
  }

  public boolean deleteObject(String catalog, String schema, String volume, String objectPath)
      throws SQLException {

    LOGGER.debug(
        String.format(
            "Entering deleteObject method with parameters: catalog={%s}, schema={%s}, volume={%s}, objectPath={%s}",
            catalog, schema, volume, objectPath));

    String deleteObjectQuery = createDeleteObjectQuery(catalog, schema, volume, objectPath);

    boolean isOperationSucceeded = false;

    try (Statement statement = connection.createStatement()) {
      try (ResultSet resultSet = statement.executeQuery(deleteObjectQuery)) {
        LOGGER.info("SQL query executed successfully");
        if (resultSet.next()) {
          String volumeOperationStatusString =
              resultSet.getString(VOLUME_OPERATION_STATUS_COLUMN_NAME);
          isOperationSucceeded =
              VOLUME_OPERATION_STATUS_SUCCEEDED.equals(volumeOperationStatusString);
        }
      }
    } catch (SQLException e) {
      LOGGER.error("SQL query execution failed " + e);
      throw e;
    }

    return isOperationSucceeded;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy