Skip to content

Commit 78b7109

Browse files
committed
WIP: Got stats working.
1 parent 003cda6 commit 78b7109

File tree

3 files changed

+125
-3
lines changed

3 files changed

+125
-3
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<dep.aws-sdk.version>1.12.782</dep.aws-sdk.version>
5252
<dep.okhttp.version>4.12.0</dep.okhttp.version>
5353
<dep.jdbi3.version>3.4.0</dep.jdbi3.version>
54-
<dep.oracle.version>19.3.0.0</dep.oracle.version>
54+
<dep.oracle.version>23.8.0.25.04</dep.oracle.version>
5555
<dep.drift.version>${dep.airlift.version}</dep.drift.version>
5656
<!-- Changing joda version changes tzdata which must match deployed JVM tzdata
5757
Do not change this without also making sure it matches -->

presto-oracle/pom.xml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020

2121
<dependencies>
2222
<dependency>
23-
<groupId>com.oracle.ojdbc</groupId>
24-
<artifactId>ojdbc8</artifactId>
23+
<groupId>com.oracle.database.jdbc</groupId>
24+
<artifactId>ojdbc11</artifactId>
2525
<version>${dep.oracle.version}</version>
2626
</dependency>
2727

@@ -211,6 +211,14 @@
211211
</ignoredNonTestScopedDependencies>
212212
</configuration>
213213
</plugin>
214+
<plugin>
215+
<groupId>org.apache.maven.plugins</groupId>
216+
<artifactId>maven-compiler-plugin</artifactId>
217+
<configuration>
218+
<source>15</source>
219+
<target>15</target>
220+
</configuration>
221+
</plugin>
214222
</plugins>
215223
</build>
216224

presto-oracle/src/main/java/com/facebook/presto/plugin/oracle/OracleClient.java

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,41 @@
1313
*/
1414
package com.facebook.presto.plugin.oracle;
1515

16+
import com.facebook.airlift.log.Logger;
17+
import com.facebook.presto.common.predicate.TupleDomain;
1618
import com.facebook.presto.common.type.Decimals;
1719
import com.facebook.presto.common.type.VarcharType;
1820
import com.facebook.presto.plugin.jdbc.BaseJdbcClient;
1921
import com.facebook.presto.plugin.jdbc.BaseJdbcConfig;
2022
import com.facebook.presto.plugin.jdbc.ConnectionFactory;
23+
import com.facebook.presto.plugin.jdbc.JdbcColumnHandle;
2124
import com.facebook.presto.plugin.jdbc.JdbcConnectorId;
2225
import com.facebook.presto.plugin.jdbc.JdbcIdentity;
26+
import com.facebook.presto.plugin.jdbc.JdbcTableHandle;
2327
import com.facebook.presto.plugin.jdbc.JdbcTypeHandle;
2428
import com.facebook.presto.plugin.jdbc.mapping.ReadMapping;
29+
import com.facebook.presto.spi.ColumnHandle;
2530
import com.facebook.presto.spi.ConnectorSession;
2631
import com.facebook.presto.spi.PrestoException;
2732
import com.facebook.presto.spi.SchemaTableName;
33+
import com.facebook.presto.spi.function.table.Preconditions;
34+
import com.facebook.presto.spi.statistics.ColumnStatistics;
35+
import com.facebook.presto.spi.statistics.DoubleRange;
36+
import com.facebook.presto.spi.statistics.Estimate;
37+
import com.facebook.presto.spi.statistics.TableStatistics;
38+
import com.google.common.collect.Maps;
2839
import jakarta.inject.Inject;
2940

3041
import java.sql.Connection;
3142
import java.sql.DatabaseMetaData;
43+
import java.sql.Date;
3244
import java.sql.PreparedStatement;
3345
import java.sql.ResultSet;
3446
import java.sql.SQLException;
3547
import java.sql.Types;
48+
import java.util.HashMap;
49+
import java.util.List;
50+
import java.util.Map;
3651
import java.util.Optional;
3752

3853
import static com.facebook.presto.common.type.DecimalType.createDecimalType;
@@ -46,13 +61,16 @@
4661
import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.smallintReadMapping;
4762
import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varcharReadMapping;
4863
import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED;
64+
import static com.google.common.base.Verify.verify;
65+
import static java.lang.Double.NaN;
4966
import static java.lang.String.format;
5067
import static java.util.Locale.ENGLISH;
5168
import static java.util.Objects.requireNonNull;
5269

5370
public class OracleClient
5471
extends BaseJdbcClient
5572
{
73+
private static final Logger LOG = Logger.get(OracleClient.class);
5674
private static final int FETCH_SIZE = 1000;
5775

5876
private final boolean synonymsEnabled;
@@ -92,6 +110,7 @@ protected ResultSet getTables(Connection connection, Optional<String> schemaName
92110
escapeNamePattern(tableName, Optional.of(escape)).orElse(null),
93111
getTableTypes());
94112
}
113+
95114
@Override
96115
public PreparedStatement getPreparedStatement(ConnectorSession session, Connection connection, String sql)
97116
throws SQLException
@@ -129,6 +148,101 @@ protected void renameTable(JdbcIdentity identity, String catalogName, SchemaTabl
129148
}
130149
}
131150

151+
@Override
152+
public TableStatistics getTableStatistics(ConnectorSession session, JdbcTableHandle handle, List<JdbcColumnHandle> columnHandles, TupleDomain<ColumnHandle> tupleDomain)
153+
{
154+
try {
155+
Preconditions.checkNotNullOrEmpty(handle.getSchemaName(), "schema name");
156+
Preconditions.checkNotNullOrEmpty(handle.getTableName(), "table name");
157+
String sql = format(
158+
"SELECT NUM_ROWS, AVG_ROW_LEN, LAST_ANALYZED\n" +
159+
"FROM DBA_TAB_STATISTICS\n" +
160+
"WHERE OWNER='%s'\n" +
161+
"AND TABLE_NAME='%s'",
162+
handle.getSchemaName().toUpperCase(), handle.getTableName().toUpperCase());
163+
try (Connection connection = connectionFactory.openConnection(JdbcIdentity.from(session))) {
164+
PreparedStatement preparedStatement = getPreparedStatement(session, connection, sql);
165+
ResultSet resultSet = preparedStatement.executeQuery();
166+
verify(resultSet.next(),
167+
format("Stats not found for table : %s.%s", handle.getSchemaName(), handle.getTableName()));
168+
double numRows = resultSet.getDouble("NUM_ROWS");
169+
double avgRowLen = resultSet.getDouble("AVG_ROW_LEN");
170+
Date lastAnalyzed = resultSet.getDate("LAST_ANALYZED");
171+
String sqlCol = getColumnStaticsSql(handle, columnHandles);
172+
PreparedStatement preparedStatementCol = getPreparedStatement(session, connection, sqlCol);
173+
resultSet = preparedStatementCol.executeQuery();
174+
Map<ColumnHandle, ColumnStatistics> columnStatisticsMap = new HashMap<>();
175+
Map<String, JdbcColumnHandle> columnHandleMap = Maps.uniqueIndex(columnHandles, JdbcColumnHandle::getColumnName);
176+
while (resultSet.next()) {
177+
String columnName = resultSet.getString("COLUMN_NAME");
178+
double nullsCount = resultSet.getDouble("NUM_NULLS");
179+
double ndv = resultSet.getDouble("NUM_DISTINCT");
180+
// 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
181+
// So we use column type as a clue and parse double by converting to string.
182+
double lowValue = toDouble(resultSet.getString("LOW_VALUE"));
183+
double highValue = toDouble(resultSet.getString("HIGH_VALUE"));
184+
ColumnStatistics.Builder columnStatisticsBuilder = ColumnStatistics.builder()
185+
.setDataSize(Estimate.estimateFromDouble(resultSet.getDouble("DATA_LENGTH")))
186+
.setNullsFraction(Estimate.estimateFromDouble(nullsCount / numRows))
187+
.setDistinctValuesCount(Estimate.estimateFromDouble(ndv));
188+
ColumnStatistics columnStatistics = columnStatisticsBuilder.build();
189+
if (Double.isFinite(lowValue) && Double.isFinite(highValue)) {
190+
columnStatistics = columnStatisticsBuilder.setRange(new DoubleRange(lowValue, highValue)).build();
191+
}
192+
columnStatisticsMap.put(columnHandleMap.get(columnName), columnStatistics);
193+
}
194+
LOG.info("getTableStatics for table: %s.%s.%s with last analyzed: %s",
195+
handle.getCatalogName(), handle.getSchemaName(), handle.getTableName(), lastAnalyzed);
196+
return TableStatistics.builder()
197+
.setColumnStatistics(columnStatisticsMap)
198+
.setRowCount(Estimate.estimateFromDouble(numRows))
199+
.setTotalSize(Estimate.estimateFromDouble(avgRowLen * numRows)).build();
200+
}
201+
}
202+
catch (SQLException | RuntimeException e) {
203+
throw new PrestoException(JDBC_ERROR, "Failed fetching statistics for table: " + handle, e);
204+
}
205+
}
206+
207+
private static String getColumnStaticsSql(JdbcTableHandle handle, List<JdbcColumnHandle> columnHandles)
208+
{
209+
String columnStatsSqlTemplate = (
210+
"(SELECT COLUMN_NAME,\n" +
211+
" DATA_TYPE,\n" +
212+
" DATA_LENGTH,\n" +
213+
" NUM_NULLS,\n" +
214+
" NUM_DISTINCT,\n" +
215+
" to_char(UTL_RAW.CAST_TO_%s(LOW_VALUE)) LOW_VALUE,\n" +
216+
" to_char(UTL_RAW.CAST_TO_%s(HIGH_VALUE)) HIGH_VALUE\n" +
217+
"FROM ALL_TAB_COLUMNS\n" +
218+
"WHERE OWNER = '%s'\n" +
219+
" AND TABLE_NAME = '%s' AND COLUMN_NAME = '%s') ");
220+
StringBuffer sqlQueryBuffer = new StringBuffer();
221+
for (JdbcColumnHandle columnHandle : columnHandles) {
222+
if (sqlQueryBuffer.length() != 0) {
223+
sqlQueryBuffer.append(" UNION ");
224+
}
225+
String targetOracleType = columnHandle.getJdbcTypeHandle().getJdbcTypeName();
226+
String columnStatsSql = format(columnStatsSqlTemplate, targetOracleType, targetOracleType, handle.getSchemaName(),
227+
handle.getTableName(), columnHandle.getColumnName().toUpperCase());
228+
sqlQueryBuffer.append(columnStatsSql);
229+
}
230+
return sqlQueryBuffer.toString();
231+
}
232+
233+
private double toDouble(String number)
234+
{
235+
try {
236+
return Double.parseDouble(number);
237+
}
238+
catch (Exception e) {
239+
// a string represented by number, may not even be a parseable number this is expected. e.g. if column type is
240+
// varchar.
241+
LOG.debug(e, "error while decoding : %s", number);
242+
}
243+
return NaN;
244+
}
245+
132246
@Override
133247
public Optional<ReadMapping> toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle)
134248
{

0 commit comments

Comments
 (0)