Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,40 @@
*/
package com.facebook.presto.plugin.oracle;

import com.facebook.airlift.log.Logger;
import com.facebook.presto.common.predicate.TupleDomain;
import com.facebook.presto.common.type.Decimals;
import com.facebook.presto.common.type.VarcharType;
import com.facebook.presto.plugin.jdbc.BaseJdbcClient;
import com.facebook.presto.plugin.jdbc.BaseJdbcConfig;
import com.facebook.presto.plugin.jdbc.ConnectionFactory;
import com.facebook.presto.plugin.jdbc.JdbcColumnHandle;
import com.facebook.presto.plugin.jdbc.JdbcConnectorId;
import com.facebook.presto.plugin.jdbc.JdbcIdentity;
import com.facebook.presto.plugin.jdbc.JdbcTableHandle;
import com.facebook.presto.plugin.jdbc.JdbcTypeHandle;
import com.facebook.presto.plugin.jdbc.mapping.ReadMapping;
import com.facebook.presto.spi.ColumnHandle;
import com.facebook.presto.spi.ConnectorSession;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.spi.SchemaTableName;
import com.facebook.presto.spi.statistics.ColumnStatistics;
import com.facebook.presto.spi.statistics.DoubleRange;
import com.facebook.presto.spi.statistics.Estimate;
import com.facebook.presto.spi.statistics.TableStatistics;
import com.google.common.collect.Maps;
import jakarta.inject.Inject;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static com.facebook.presto.common.type.DecimalType.createDecimalType;
Expand All @@ -47,13 +61,15 @@
import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varbinaryReadMapping;
import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varcharReadMapping;
import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED;
import static java.lang.Double.NaN;
import static java.lang.String.format;
import static java.util.Locale.ENGLISH;
import static java.util.Objects.requireNonNull;

public class OracleClient
extends BaseJdbcClient
{
private static final Logger LOG = Logger.get(OracleClient.class);
private static final int FETCH_SIZE = 1000;

private final boolean synonymsEnabled;
Expand Down Expand Up @@ -93,6 +109,7 @@ protected ResultSet getTables(Connection connection, Optional<String> schemaName
escapeNamePattern(tableName, Optional.of(escape)).orElse(null),
getTableTypes());
}

@Override
public PreparedStatement getPreparedStatement(ConnectorSession session, Connection connection, String sql)
throws SQLException
Expand Down Expand Up @@ -137,6 +154,99 @@ protected void renameTable(JdbcIdentity identity, String catalogName, SchemaTabl
}
}

@Override
public TableStatistics getTableStatistics(ConnectorSession session, JdbcTableHandle handle, List<JdbcColumnHandle> columnHandles, TupleDomain<ColumnHandle> tupleDomain)
{
try {
requireNonNull(handle.getSchemaName(), "schema name is null");
requireNonNull(handle.getTableName(), "table name is null");
String sql = format(
"SELECT NUM_ROWS, AVG_ROW_LEN, LAST_ANALYZED\n" +
"FROM DBA_TAB_STATISTICS\n" +
"WHERE OWNER='%s'\n" +
"AND TABLE_NAME='%s'",
handle.getSchemaName().toUpperCase(), handle.getTableName().toUpperCase());
try (Connection connection = connectionFactory.openConnection(JdbcIdentity.from(session));
PreparedStatement preparedStatement = getPreparedStatement(session, connection, sql);
PreparedStatement preparedStatementCol = getPreparedStatement(session, connection, getColumnStaticsSql(handle));
ResultSet resultSet = preparedStatement.executeQuery();
ResultSet resultSetColumnStats = preparedStatementCol.executeQuery()) {
if (!resultSet.next()) {
LOG.debug("Stats not found for table : %s.%s", handle.getSchemaName(), handle.getTableName());
return TableStatistics.empty();
}
double numRows = resultSet.getDouble("NUM_ROWS");
// double avgRowLen = resultSet.getDouble("AVG_ROW_LEN");
Date lastAnalyzed = resultSet.getDate("LAST_ANALYZED");

Map<ColumnHandle, ColumnStatistics> columnStatisticsMap = new HashMap<>();
Map<String, JdbcColumnHandle> columnHandleMap = Maps.uniqueIndex(columnHandles, JdbcColumnHandle::getColumnName);
while (resultSetColumnStats.next() && numRows > 0) {
String columnName = resultSetColumnStats.getString("COLUMN_NAME");
double nullsCount = resultSetColumnStats.getDouble("NUM_NULLS");
double ndv = resultSetColumnStats.getDouble("NUM_DISTINCT");
// Oracle stores low and high values as RAW(1000) i.e. a byte array. No way to unwrap it, without a clue about the underlying type
// So we use column type as a clue and parse to double by converting as string first.
double lowValue = toDouble(resultSetColumnStats.getString("LOW_VALUE"));
double highValue = toDouble(resultSetColumnStats.getString("HIGH_VALUE"));
ColumnStatistics.Builder columnStatisticsBuilder = ColumnStatistics.builder()
.setDataSize(Estimate.estimateFromDouble(resultSet.getDouble("DATA_LENGTH")))
.setNullsFraction(Estimate.estimateFromDouble(nullsCount / numRows))
.setDistinctValuesCount(Estimate.estimateFromDouble(ndv));
ColumnStatistics columnStatistics = columnStatisticsBuilder.build();
if (Double.isFinite(lowValue) && Double.isFinite(highValue)) {
columnStatistics = columnStatisticsBuilder.setRange(new DoubleRange(lowValue, highValue)).build();
}
columnStatisticsMap.put(columnHandleMap.get(columnName), columnStatistics);
}
LOG.info("getTableStatics for table: %s.%s.%s with last analyzed: %s",
handle.getCatalogName(), handle.getSchemaName(), handle.getTableName(), lastAnalyzed);
return TableStatistics.builder()
.setColumnStatistics(columnStatisticsMap)
.setRowCount(Estimate.estimateFromDouble(numRows)).build();
}
}
catch (SQLException | RuntimeException e) {
throw new PrestoException(JDBC_ERROR, "Failed fetching statistics for table: " + handle, e);
}
}

private String getColumnStaticsSql(JdbcTableHandle handle)
{
// UTL_RAW.CAST_TO_BINARY_X does not render correctly so those types are not supported.
return format(
"SELECT COLUMN_NAME,\n" +
"DATA_TYPE,\n" +
"DATA_LENGTH,\n" +
"NUM_NULLS,\n" +
"NUM_DISTINCT,\n" +
"DENSITY,\n" +
"CASE DATA_TYPE\n" +
" WHEN 'NUMBER' THEN TO_CHAR(UTL_RAW.CAST_TO_NUMBER(LOW_VALUE))\n" +
" ELSE NULL\n" +
"END AS LOW_VALUE,\n" +
"CASE DATA_TYPE\n" +
" WHEN 'NUMBER' THEN TO_CHAR(UTL_RAW.CAST_TO_NUMBER(HIGH_VALUE))\n" +
" ELSE NULL\n" +
"END AS HIGH_VALUE\n" +
"FROM ALL_TAB_COLUMNS\n" +
"WHERE OWNER = '%s'\n" +
" AND TABLE_NAME = '%s'", handle.getSchemaName().toUpperCase(), handle.getTableName().toUpperCase());
}

private double toDouble(String number)
{
try {
return Double.parseDouble(number);
}
catch (Exception e) {
// a string represented by number, may not even be a parseable number this is expected. e.g. if column type is
// varchar.
LOG.debug(e, "error while decoding : %s", number);
}
return NaN;
}

@Override
public Optional<ReadMapping> toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle)
{
Expand Down
Loading