Compare commits

...

1 Commits

Author SHA1 Message Date
luodan000 13c679e063 !576 pushdown feature
* pushdown feature
2021-02-05 10:30:56 +08:00
1084 changed files with 24437 additions and 13814 deletions

View File

@ -16,7 +16,6 @@
package io.hetu.core.plugin.carbondata.integrationtest;
import com.google.gson.Gson;
import io.hetu.core.plugin.carbondata.CarbondataMetadata;
import io.hetu.core.plugin.carbondata.server.HetuTestServer;
import io.prestosql.hive.$internal.au.com.bytecode.opencsv.CSVReader;
import org.apache.carbondata.common.logging.LogServiceFactory;
@ -33,7 +32,6 @@ import org.apache.carbondata.core.metadata.schema.table.TableInfo;
import org.apache.carbondata.core.mutate.SegmentUpdateDetails;
import org.apache.carbondata.core.reader.ThriftReader;
import org.apache.carbondata.core.statusmanager.LoadMetadataDetails;
import org.apache.carbondata.core.statusmanager.SegmentStatusManager;
import org.apache.carbondata.core.util.CarbonProperties;
import org.apache.carbondata.core.util.CarbonUtil;
import org.apache.carbondata.core.util.path.CarbonTablePath;
@ -51,10 +49,7 @@ import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.text.ParseException;
@ -68,8 +63,8 @@ import java.util.Map;
import java.util.TreeMap;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
@Test(singleThreaded = true)
public class TestCarbonAllDataType
@ -107,8 +102,8 @@ public class TestCarbonAllDataType
map.put("carbondata.minor-vacuum-seg-count", "4");
map.put("carbondata.major-vacuum-seg-size", "1");
if (!FileFactory.isFileExist( storePath + "/carbon.store")) {
FileFactory.mkdirs( storePath + "/carbon.store");
if (!FileFactory.isFileExist(storePath + "/carbon.store")) {
FileFactory.mkdirs(storePath + "/carbon.store");
}
hetuServer.startServer("testdb", map);
@ -146,7 +141,7 @@ public class TestCarbonAllDataType
{
List<Map<String, Object>> actualResult = hetuServer.executeQuery("SELECT COUNT(*) AS RESULT FROM testdb.testtable");
List<Map<String, Object>> expectedResult = new ArrayList<Map<String, Object>>() {{
add(new HashMap<String, Object>() {{ put("RESULT", 11); }});
add(new HashMap<String, Object>() {{put("RESULT", 11); }});
}};
assertEquals(actualResult.toString(), expectedResult.toString());
@ -812,7 +807,8 @@ public class TestCarbonAllDataType
}
@Test
public void testSegmentDelete() throws SQLException {
public void testSegmentDelete() throws SQLException
{
hetuServer.execute("CREATE TABLE testdb.segmentdelete(a int, b tinyint)");
hetuServer.execute("INSERT INTO testdb.segmentdelete VALUES (10, tinyint '1'),(11, tinyint '2'),(12, tinyint '3')");
hetuServer.execute("INSERT INTO testdb.segmentdelete VALUES (13, tinyint '1'),(14, tinyint '2'),(15, tinyint '3')");
@ -852,7 +848,8 @@ public class TestCarbonAllDataType
/*
Returns true if "Marked for Delete" is present in both tableupdatestatus and tablestatus file
*/
private boolean checkStatusFileForDeleteMarked(String tableName, int updateNumber, int segmentNumber) throws SQLException {
private boolean checkStatusFileForDeleteMarked(String tableName, int updateNumber, int segmentNumber) throws SQLException
{
try {
File dir = new File(storePath + "/carbon.store/testdb/" + tableName + "/Metadata");
File[] tableUpdateStatusFiles = dir.listFiles((d, name) -> name.startsWith("tableupdatestatus"));
@ -909,7 +906,8 @@ public class TestCarbonAllDataType
hetuServer.execute("VACUUM TABLE testdb.mytesttable2");
assertEquals(FileFactory.isFileExist(storePath +
"/carbon.store/testdb/mytesttable2/Fact/Part0/Segment_0.1", false), true);
} catch (IOException e) {
}
catch (IOException e) {
hetuServer.execute("DROP TABLE if exists testdb.mytesttable2");
e.printStackTrace();
}

View File

@ -42,8 +42,8 @@ import java.util.Map;
import static org.testng.Assert.assertEquals;
public class TestCarbondataAutoCleanup {
public class TestCarbondataAutoCleanup
{
private final Logger logger = LogServiceFactory.getLogService(TestCarbondataAutoCleanup.class.getCanonicalName());
private String rootPath = new File(this.getClass().getResource("/").getPath() + "../..")
@ -77,8 +77,8 @@ public class TestCarbondataAutoCleanup {
map.put("carbondata.minor-vacuum-seg-count", "4");
map.put("carbondata.major-vacuum-seg-size", "1");
if (!FileFactory.isFileExist( storePath + "/carbon.store")) {
FileFactory.mkdirs( storePath + "/carbon.store");
if (!FileFactory.isFileExist(storePath + "/carbon.store")) {
FileFactory.mkdirs(storePath + "/carbon.store");
}
hetuServer.startServer("testdb", map);

View File

@ -111,7 +111,8 @@ public class HetuTestServer
boolean result = false;
try {
result = statement.execute(query);
} catch (SQLException e) {
}
catch (SQLException e) {
logger.error("Exception Occured: " + e.getMessage() + "\n Failed Query: " + query);
throw e;
}
@ -125,7 +126,8 @@ public class HetuTestServer
try {
ResultSet rs = statement.executeQuery(query);
return convertResultSetToList(rs);
} catch (SQLException e) {
}
catch (SQLException e) {
logger.error("Exception Occured: " + e.getMessage() + "\n Failed Query: " + query);
throw e;
}
@ -167,7 +169,8 @@ public class HetuTestServer
if (StringUtils.isEmpty(dbName)) {
url = "jdbc:presto://localhost:" + port + "/carbondata/default";
} else {
}
else {
url = "jdbc:presto://localhost:" + port + "/carbondata/" + dbName;
}
@ -190,13 +193,14 @@ public class HetuTestServer
Map<String, String> carbonPropertiesLocationDisabled = ImmutableMap.<String, String>builder()
.putAll(this.carbonProperties)
.put("carbon.unsafe.working.memory.in.mb", "512")
.put("hive.table-creates-with-location-allowed","false")
.put("hive.table-creates-with-location-allowed", "false")
.build();
// CreateCatalog will create a catalog for CarbonData in etc/catalog.
queryRunner.createCatalog(carbonDataCatalog, carbonDataConnector, carbonProperties);
queryRunner.createCatalog(carbonDataCatalogLocationDisabled, carbonDataConnector, carbonPropertiesLocationDisabled);
} catch (RuntimeException e) {
}
catch (RuntimeException e) {
queryRunner.close();
throw e;
}
@ -210,7 +214,8 @@ public class HetuTestServer
queryRunner.createCatalog("hive", "hive", hiveProperties);
}
public CatalogManager getCatalog() {
public CatalogManager getCatalog()
{
return queryRunner.getCatalogManager();
}
}

View File

@ -20,6 +20,7 @@ import io.airlift.configuration.ConfigDescription;
import io.airlift.configuration.ConfigSecuritySensitive;
import io.airlift.units.DataSize;
import io.airlift.units.Duration;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
@ -102,6 +103,8 @@ public class DataCenterConfig
private boolean isQueryPushDownEnabled = true;
private JdbcPushDownModule queryPushDownModule = JdbcPushDownModule.DEFAULT;
private Duration metadataCacheTtl = new Duration(1, TimeUnit.SECONDS); // DataCenter metadata cache eviction time
private long metadataCacheMaximumSize = DEFAULT_METADATA_CACHE_MAX_SIZE; // DataCenter metadata cache max size
@ -682,6 +685,25 @@ public class DataCenterConfig
return this;
}
public JdbcPushDownModule getQueryPushDownModule()
{
return queryPushDownModule;
}
/**
* set queryPushDownEnabled
*
* @param queryPushDownModule Push Down Module
* @return DataCenterConfig object
*/
@Config("dc.query.pushdown.module")
@ConfigDescription("query push down module [FULL_PUSDOWN/BASE_PUSHDOWN]")
public DataCenterConfig setQueryPushDownModule(JdbcPushDownModule queryPushDownModule)
{
this.queryPushDownModule = queryPushDownModule;
return this;
}
public DataSize getRemoteHttpServerMaxRequestHeaderSize()
{
return remoteHeaderSize;

View File

@ -15,15 +15,19 @@
package io.hetu.core.plugin.datacenter;
import com.google.common.collect.ImmutableSet;
import io.airlift.bootstrap.LifeCycleManager;
import io.airlift.log.Logger;
import io.hetu.core.plugin.datacenter.client.DataCenterClient;
import io.hetu.core.plugin.datacenter.client.DataCenterStatementClientFactory;
import io.hetu.core.plugin.datacenter.optimization.DataCenterPlanOptimizer;
import io.hetu.core.plugin.datacenter.pagesource.DataCenterPageSourceProvider;
import io.prestosql.spi.ConnectorPlanOptimizer;
import io.prestosql.spi.connector.CachedConnectorMetadata;
import io.prestosql.spi.connector.Connector;
import io.prestosql.spi.connector.ConnectorMetadata;
import io.prestosql.spi.connector.ConnectorPageSourceProvider;
import io.prestosql.spi.connector.ConnectorPlanOptimizerProvider;
import io.prestosql.spi.connector.ConnectorSplitManager;
import io.prestosql.spi.connector.ConnectorTransactionHandle;
import io.prestosql.spi.transaction.IsolationLevel;
@ -34,6 +38,7 @@ import javax.inject.Inject;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import static io.hetu.core.plugin.datacenter.DataCenterTransactionHandle.INSTANCE;
import static java.util.Objects.requireNonNull;
@ -60,6 +65,8 @@ public class DataCenterConnector
private final OkHttpClient httpClient;
private final ConnectorPlanOptimizer planOptimizer;
/**
* Constructor of data center connector.
*
@ -68,14 +75,18 @@ public class DataCenterConnector
* @param typeManager the type manager.
*/
@Inject
public DataCenterConnector(LifeCycleManager lifeCycleManager, DataCenterConfig dataCenterConfig,
TypeManager typeManager)
public DataCenterConnector(
LifeCycleManager lifeCycleManager,
DataCenterConfig dataCenterConfig,
TypeManager typeManager,
DataCenterPlanOptimizer planOptimizer)
{
this.lifeCycleManager = requireNonNull(lifeCycleManager, "lifeCycleManager is null");
this.httpClient = DataCenterStatementClientFactory.newHttpClient(dataCenterConfig);
this.dataCenterClient = new DataCenterClient(dataCenterConfig, this.httpClient, typeManager);
this.splitManager = new DataCenterSplitManager(dataCenterConfig, this.dataCenterClient);
this.pageSourceProvider = new DataCenterPageSourceProvider(dataCenterConfig, this.httpClient, typeManager);
this.planOptimizer = planOptimizer;
if (dataCenterConfig.isMetadataCacheEnabled()) {
this.metadata = new CachedConnectorMetadata(new DataCenterMetadata(dataCenterClient, dataCenterConfig),
dataCenterConfig.getMetadataCacheTtl(), dataCenterConfig.getMetadataCacheMaximumSize());
@ -85,6 +96,25 @@ public class DataCenterConnector
}
}
@Override
public ConnectorPlanOptimizerProvider getConnectorPlanOptimizerProvider()
{
return new ConnectorPlanOptimizerProvider()
{
@Override
public Set<ConnectorPlanOptimizer> getLogicalPlanOptimizers()
{
return ImmutableSet.of(planOptimizer);
}
@Override
public Set<ConnectorPlanOptimizer> getPhysicalPlanOptimizers()
{
return ImmutableSet.of();
}
};
}
@Override
public ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLevel, boolean isReadOnly)
{

View File

@ -22,6 +22,7 @@ import io.prestosql.spi.connector.Connector;
import io.prestosql.spi.connector.ConnectorContext;
import io.prestosql.spi.connector.ConnectorFactory;
import io.prestosql.spi.connector.ConnectorHandleResolver;
import io.prestosql.spi.relation.RowExpressionService;
import java.util.Map;
@ -54,7 +55,10 @@ public class DataCenterConnectorFactory
requireNonNull(requiredConfig, "requiredConfig is null");
try {
// A plugin is not required to use Guice; it is just very convenient
Bootstrap app = new Bootstrap(new JsonModule(), new DataCenterModule(context.getTypeManager()));
Bootstrap app = new Bootstrap(
binder -> binder.bind(RowExpressionService.class).toInstance(context.getRowExpressionService()),
new JsonModule(),
new DataCenterModule(context.getTypeManager()));
Injector injector = app.strictConfig()
.doNotInitializeLogging()

View File

@ -32,13 +32,9 @@ import io.prestosql.spi.connector.LimitApplicationResult;
import io.prestosql.spi.connector.SchemaNotFoundException;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.connector.SchemaTablePrefix;
import io.prestosql.spi.connector.SubQueryApplicationResult;
import io.prestosql.spi.connector.TableNotFoundException;
import io.prestosql.spi.sql.SqlQueryWriter;
import io.prestosql.spi.statistics.TableStatistics;
import io.prestosql.spi.type.Type;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -245,49 +241,6 @@ public class DataCenterMetadata
return Optional.of(new LimitApplicationResult<>(handle, true));
}
@Override
public Optional<SubQueryApplicationResult<ConnectorTableHandle>> applySubQuery(ConnectorSession session,
ConnectorTableHandle handle, String subQuery, Map<String, Type> types)
{
if (!isQueryPushDownEnabled || subQuery.getBytes(StandardCharsets.ISO_8859_1).length >= maxRemoteHeaderSize) {
return Optional.empty();
}
// If the subQuery pushed down to the connector, table name, limit or predicate push downs are not necessary
// Therefore, either of the table name can be used for the new TableHandle as long as the subQuery is valid
requireNonNull(subQuery, "cannot apply null sub-query");
DataCenterTableHandle tableHandle = (DataCenterTableHandle) handle;
// If we can get the columns from the sub-query, it should be able to push sub-query down
List<DataCenterColumn> columns = dataCenterClient.getColumns(subQuery);
if (columns.isEmpty()) {
return Optional.empty();
}
DataCenterTableHandle newTableHandle = new DataCenterTableHandle(tableHandle.getCatalogName(),
tableHandle.getSchemaName(), tableHandle.getTableName(), OptionalLong.empty(), subQuery);
ImmutableMap.Builder<String, ColumnHandle> columnHandleBuilder = new ImmutableMap.Builder<>();
ImmutableMap.Builder<String, Type> typesBuilder = new ImmutableMap.Builder<>();
columns.forEach(column -> {
columnHandleBuilder.put(column.getName(),
new DataCenterColumnHandle(column.getName(), column.getType(), 0));
typesBuilder.put(column.getName(), column.getType());
});
return Optional.of(
new SubQueryApplicationResult<>(newTableHandle, columnHandleBuilder.build(), typesBuilder.build()));
}
@Override
public Optional<SqlQueryWriter> getSqlQueryWriter()
{
if (!isQueryPushDownEnabled) {
return Optional.empty();
}
return Optional.of(new DataCenterSqlQueryWriter());
}
@Override
public TableStatistics getTableStatistics(ConnectorSession session, ConnectorTableHandle tableHandle, Constraint constraint)
{

View File

@ -18,6 +18,8 @@ package io.hetu.core.plugin.datacenter;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Scopes;
import io.hetu.core.plugin.datacenter.optimization.DataCenterPlanOptimizer;
import io.hetu.core.plugin.datacenter.optimization.DataCenterQueryGenerator;
import io.prestosql.spi.type.TypeManager;
import static io.airlift.configuration.ConfigBinder.configBinder;
@ -48,6 +50,8 @@ public class DataCenterModule
{
binder.bind(TypeManager.class).toInstance(typeManager);
binder.bind(DataCenterConnector.class).in(Scopes.SINGLETON);
binder.bind(DataCenterPlanOptimizer.class).in(Scopes.SINGLETON);
binder.bind(DataCenterQueryGenerator.class).in(Scopes.SINGLETON);
configBinder(binder).bindConfig(DataCenterConfig.class);
}
}

View File

@ -17,6 +17,7 @@ package io.hetu.core.plugin.datacenter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Joiner;
import io.prestosql.spi.connector.ConnectorTableHandle;
import io.prestosql.spi.connector.SchemaTableName;
@ -43,7 +44,7 @@ public final class DataCenterTableHandle
private final OptionalLong limit;
private final String subQuery;
private final String pushDownSql;
/**
* Constructor of data center table handle.
@ -59,7 +60,7 @@ public final class DataCenterTableHandle
this.schemaName = requireNonNull(schemaName, "schemaName is null");
this.tableName = requireNonNull(tableName, "tableName is null");
this.limit = requireNonNull(limit, "limit is null");
this.subQuery = "";
this.pushDownSql = "";
}
/**
@ -69,25 +70,25 @@ public final class DataCenterTableHandle
* @param schemaName schema name.
* @param tableName table name.
* @param limit the limit number of this query need.
* @param subQuery the sub query statement that want to be pushed down to remote data center.
* @param pushDownSql the sub query statement that want to be pushed down to remote data center.
*/
@JsonCreator
public DataCenterTableHandle(@JsonProperty("catalogName") String catalogName,
@JsonProperty("schemaName") String schemaName, @JsonProperty("tableName") String tableName,
@JsonProperty("limit") OptionalLong limit, @JsonProperty("subQuery") String subQuery)
@JsonProperty("limit") OptionalLong limit, @JsonProperty("subQuery") String pushDownSql)
{
this.catalogName = catalogName;
this.schemaName = requireNonNull(schemaName, "schemaName is null");
this.tableName = requireNonNull(tableName, "tableName is null");
this.limit = requireNonNull(limit, "limit is null");
this.subQuery = subQuery;
this.pushDownSql = pushDownSql;
}
@Override
public ConnectorTableHandle createFrom(ConnectorTableHandle connectorTableHandle)
{
DataCenterTableHandle dataCenterTableHandle = (DataCenterTableHandle) connectorTableHandle;
return new DataCenterTableHandle(catalogName, schemaName, dataCenterTableHandle.tableName, dataCenterTableHandle.getLimit(), dataCenterTableHandle.getSubQuery());
return new DataCenterTableHandle(catalogName, schemaName, dataCenterTableHandle.tableName, dataCenterTableHandle.getLimit(), dataCenterTableHandle.getPushDownSql());
}
@JsonProperty
@ -130,9 +131,9 @@ public final class DataCenterTableHandle
}
@JsonProperty
public String getSubQuery()
public String getPushDownSql()
{
return subQuery;
return pushDownSql;
}
@Override
@ -159,6 +160,14 @@ public final class DataCenterTableHandle
@Override
public String toString()
{
return catalogName + SPLIT_DOT + schemaName + SPLIT_DOT + tableName;
StringBuilder builder = new StringBuilder();
if (!pushDownSql.isEmpty()) {
Joiner.on(SPLIT_DOT).skipNulls().appendTo(builder, catalogName, "{" + pushDownSql + "}");
}
else {
Joiner.on(SPLIT_DOT).skipNulls().appendTo(builder, catalogName, schemaName, tableName);
}
limit.ifPresent(value -> builder.append(" limit=").append(value));
return builder.toString();
}
}

View File

@ -0,0 +1,307 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.datacenter.optimization;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.airlift.log.Logger;
import io.hetu.core.plugin.datacenter.DataCenterColumn;
import io.hetu.core.plugin.datacenter.DataCenterColumnHandle;
import io.hetu.core.plugin.datacenter.DataCenterConfig;
import io.hetu.core.plugin.datacenter.DataCenterTableHandle;
import io.hetu.core.plugin.datacenter.client.DataCenterClient;
import io.hetu.core.plugin.datacenter.client.DataCenterStatementClientFactory;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorContext;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorResult;
import io.prestosql.spi.ConnectorPlanOptimizer;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.SymbolAllocator;
import io.prestosql.spi.connector.CatalogName;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.ConnectorSession;
import io.prestosql.spi.function.OperatorType;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.metadata.TableHandle;
import io.prestosql.spi.operator.ReuseExchangeOperator;
import io.prestosql.spi.plan.Assignments;
import io.prestosql.spi.plan.FilterNode;
import io.prestosql.spi.plan.GroupIdNode;
import io.prestosql.spi.plan.MarkDistinctNode;
import io.prestosql.spi.plan.PlanNode;
import io.prestosql.spi.plan.PlanNodeIdAllocator;
import io.prestosql.spi.plan.PlanVisitor;
import io.prestosql.spi.plan.ProjectNode;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.plan.TableScanNode;
import io.prestosql.spi.predicate.TupleDomain;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.relation.VariableReferenceExpression;
import io.prestosql.spi.sql.RowExpressionUtils;
import io.prestosql.spi.type.Type;
import io.prestosql.spi.type.TypeManager;
import io.prestosql.spi.type.UnknownType;
import okhttp3.OkHttpClient;
import javax.inject.Inject;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
import java.util.stream.IntStream;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.getGroupingSetColumn;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.replaceGroupingSetColumns;
public class DataCenterPlanOptimizer
implements ConnectorPlanOptimizer
{
private static final Logger log = Logger.get(DataCenterPlanOptimizer.class);
private static final String DATACENTER_CATALOG_PREFIX = "dc.";
private static final Set<Class<? extends PlanNode>> UNSUPPORTED_ROOT_NODE = ImmutableSet.of(GroupIdNode.class, MarkDistinctNode.class);
private final DataCenterClient client;
private final DataCenterConfig config;
private final TypeManager typeManager;
private final DataCenterQueryGenerator queryGenerator;
@Inject
public DataCenterPlanOptimizer(
TypeManager typeManager,
DataCenterConfig config,
DataCenterQueryGenerator query)
{
OkHttpClient httpClient = DataCenterStatementClientFactory.newHttpClient(config);
this.client = new DataCenterClient(config, httpClient, typeManager);
this.config = config;
this.typeManager = typeManager;
this.queryGenerator = query;
}
@Override
public PlanNode optimize(PlanNode maxSubPlan, ConnectorSession session, Map<String, Type> types, SymbolAllocator symbolAllocator, PlanNodeIdAllocator idAllocator)
{
if (!config.isQueryPushDownEnabled()) {
return maxSubPlan;
}
// Some node cannot be push down root node.
if (UNSUPPORTED_ROOT_NODE.contains(maxSubPlan.getClass())) {
return maxSubPlan;
}
return maxSubPlan.accept(new Visitor(idAllocator, types, session, symbolAllocator), null);
}
private static PlanNode replaceChildren(PlanNode node, List<PlanNode> children)
{
for (int i = 0; i < node.getSources().size(); i++) {
if (children.get(i) != node.getSources().get(i)) {
return node.replaceChildren(children);
}
}
return node;
}
private class Visitor
extends PlanVisitor<PlanNode, Void>
{
private final PlanNodeIdAllocator idAllocator;
private final ConnectorSession session;
private final Map<String, Type> types;
private final SymbolAllocator symbolAllocator;
private final IdentityHashMap<FilterNode, Void> filtersSplitUp = new IdentityHashMap<>();
public Visitor(
PlanNodeIdAllocator idAllocator,
Map<String, Type> types,
ConnectorSession session,
SymbolAllocator symbolAllocator)
{
this.idAllocator = idAllocator;
this.types = types;
this.session = session;
this.symbolAllocator = symbolAllocator;
}
@Override
public PlanNode visitPlan(PlanNode node, Void context)
{
Optional<PlanNode> pushDownPlan = tryCreatingNewScanNode(node);
return pushDownPlan.orElseGet(() -> replaceChildren(
node, node.getSources().stream().map(source -> source.accept(this, null)).collect(toImmutableList())));
}
@Override
public PlanNode visitFilter(FilterNode node, Void context)
{
if (filtersSplitUp.containsKey(node)) {
return this.visitPlan(node, context);
}
filtersSplitUp.put(node, null);
FilterNode nodeToRecurseInto = node;
List<RowExpression> pushable = new ArrayList<>();
List<RowExpression> nonPushable = new ArrayList<>();
for (RowExpression conjunct : RowExpressionUtils.extractConjuncts(node.getPredicate())) {
try {
conjunct.accept(queryGenerator.getConverter(), null);
pushable.add(conjunct);
}
catch (PrestoException pe) {
nonPushable.add(conjunct);
}
}
if (!pushable.isEmpty()) {
FilterNode pushableFilter = new FilterNode(idAllocator.getNextId(), node.getSource(), RowExpressionUtils.combineConjuncts(pushable));
Optional<FilterNode> nonPushableFilter = nonPushable.isEmpty() ? Optional.empty() : Optional.of(new FilterNode(idAllocator.getNextId(), pushableFilter, RowExpressionUtils.combineConjuncts(nonPushable)));
filtersSplitUp.put(pushableFilter, null);
if (nonPushableFilter.isPresent()) {
FilterNode nonPushableFilterNode = nonPushableFilter.get();
filtersSplitUp.put(nonPushableFilterNode, null);
nodeToRecurseInto = nonPushableFilterNode;
}
else {
nodeToRecurseInto = pushableFilter;
}
}
return this.visitFilter(nodeToRecurseInto, context);
}
private Optional<PlanNode> tryCreatingNewScanNode(PlanNode node)
{
Optional<JdbcQueryGeneratorResult> result = queryGenerator.generate(node, typeManager);
if (!result.isPresent()) {
return Optional.empty();
}
JdbcQueryGeneratorContext context = result.get().getContext();
JdbcQueryGeneratorResult.GeneratedSql generatedSql = result.get().getGeneratedSql();
if (!generatedSql.isPushDown()) {
return Optional.empty();
}
JdbcQueryGeneratorContext.GroupIdNodeInfo groupIdNodeInfo = context.getGroupIdNodeInfo();
String sql = generatedSql.getSql();
// replace grouping sets column
if (groupIdNodeInfo.isGroupByComplexOperation()) {
sql = replaceGroupingSetColumns(sql);
}
if (sql.getBytes(StandardCharsets.ISO_8859_1).length >= config.getRemoteHttpServerMaxRequestHeaderSize().toBytes()) {
log.debug("Generated sql is too long, push down failed.");
return Optional.empty();
}
List<DataCenterColumn> columnsList;
try {
columnsList = client.getColumns(sql);
}
catch (PrestoException e) {
log.warn("query push down failed for [%s]", e.getMessage());
return Optional.empty();
}
if (columnsList.isEmpty()) {
log.debug("Get columns from generated sql failed.");
return Optional.empty();
}
Map<String, ColumnHandle> columns = new HashMap<>();
IntStream.range(0, columnsList.size()).forEach(i -> {
DataCenterColumn column = columnsList.get(i);
columns.put(column.getName(), new DataCenterColumnHandle(column.getName(), column.getType(), i));
});
ImmutableList.Builder<Symbol> scanOutputs = new ImmutableList.Builder<>();
ImmutableMap.Builder<Symbol, ColumnHandle> columnHandles = new ImmutableMap.Builder<>();
ImmutableMap.Builder<Symbol, RowExpression> assignments = new ImmutableMap.Builder<>();
for (Symbol symbol : node.getOutputSymbols()) {
String name = symbol.getName().toLowerCase(Locale.ENGLISH);
String aliasName = groupIdNodeInfo.isGroupByComplexOperation()
? getGroupingSetColumn(name)
: name;
if (!types.containsKey(name) || !columns.containsKey(aliasName)) {
log.debug("Get type of column [%s] failed", name);
return Optional.empty();
}
Type prestoType = types.get(name);
Type dcType = ((DataCenterColumnHandle) columns.get(aliasName)).getColumnType();
if (prestoType.equals(dcType)) {
scanOutputs.add(symbol);
columnHandles.put(symbol, columns.get(aliasName));
assignments.put(symbol, new VariableReferenceExpression(symbol.getName(), prestoType));
}
else {
if (prestoType instanceof UnknownType) {
log.debug("Can't cast from type[%s] to type[%s]", dcType.getDisplayName(), prestoType.getDisplayName());
return Optional.empty();
}
// If Jdbc return a different type from Presto's expected type, add a CAST expression
Symbol scanSymbol = symbolAllocator.newSymbol(symbol.getName(), dcType);
scanOutputs.add(scanSymbol);
columnHandles.put(scanSymbol, columns.get(aliasName));
assignments.put(symbol, new CallExpression(
Signature.internalOperator(OperatorType.CAST, prestoType.getTypeSignature(), ImmutableList.of(dcType.getTypeSignature())),
prestoType,
ImmutableList.of(new VariableReferenceExpression(scanSymbol.getName(), dcType))));
}
}
checkState(context.getCatalogName().isPresent(), "CatalogName is null");
checkState(context.getSchemaTableName().isPresent(), "schemaTableName is null");
checkState(context.getTransaction().isPresent(), "transaction is null");
CatalogName catalogName = context.getCatalogName().get();
String tableCatalogName = catalogName.getCatalogName().startsWith(DATACENTER_CATALOG_PREFIX)
? catalogName.getCatalogName().substring(DATACENTER_CATALOG_PREFIX.length())
: catalogName.getCatalogName();
TableHandle newTableHandle = new TableHandle(
catalogName,
new DataCenterTableHandle(
tableCatalogName,
context.getSchemaTableName().get().getSchemaName(),
context.getSchemaTableName().get().getTableName(),
OptionalLong.empty(),
sql),
context.getTransaction().get(),
Optional.empty());
return Optional.of(
new ProjectNode(
this.idAllocator.getNextId(),
new TableScanNode(
idAllocator.getNextId(),
newTableHandle,
scanOutputs.build(),
columnHandles.build(),
TupleDomain.all(),
Optional.empty(),
ReuseExchangeOperator.STRATEGY.REUSE_STRATEGY_DEFAULT,
0,
0),
new Assignments(assignments.build())));
}
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.datacenter.optimization;
import io.hetu.core.plugin.datacenter.DataCenterColumnHandle;
import io.hetu.core.plugin.datacenter.DataCenterConfig;
import io.hetu.core.plugin.datacenter.DataCenterTableHandle;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcQueryGenerator;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcRowExpressionConverter;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcSqlStatementWriter;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownParameter;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorContext;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.metadata.TableHandle;
import io.prestosql.spi.plan.PlanNode;
import io.prestosql.spi.plan.PlanVisitor;
import io.prestosql.spi.plan.TableScanNode;
import io.prestosql.spi.predicate.TupleDomain;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.sql.expression.Selection;
import io.prestosql.spi.type.TypeManager;
import javax.inject.Inject;
import java.util.LinkedHashMap;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_QUERY_GENERATOR_FAILURE;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.quote;
public class DataCenterQueryGenerator
extends BaseJdbcQueryGenerator
{
@Inject
public DataCenterQueryGenerator(DataCenterConfig config, RowExpressionService rowExpressionService)
{
super(new JdbcPushDownParameter("\"", false, config.getQueryPushDownModule()),
new BaseJdbcRowExpressionConverter(rowExpressionService),
new BaseJdbcSqlStatementWriter(new JdbcPushDownParameter("\"", false, config.getQueryPushDownModule())));
}
@Override
protected PlanVisitor<Optional<JdbcQueryGeneratorContext>, Void> getVisitor(TypeManager typeManager)
{
return new DataCenterPlanVisitor(typeManager);
}
protected class DataCenterPlanVisitor
extends BaseJdbcPlanVisitor
{
public DataCenterPlanVisitor(TypeManager typeManager)
{
super(typeManager);
}
@Override
public Optional<JdbcQueryGeneratorContext> visitPlan(PlanNode node, Void contextIn)
{
log.debug(GENERATE_FAILED_LOG, "Don't know how to handle plan node of type " + node);
return Optional.empty();
}
@Override
public Optional<JdbcQueryGeneratorContext> visitTableScan(TableScanNode node, Void contextIn)
{
checkAvailable(node);
checkArgument(node.getTable().getConnectorHandle() instanceof DataCenterTableHandle,
"Expected to find Data Center table handle for the scan node");
TupleDomain<ColumnHandle> constraint = node.getEnforcedConstraint();
if (constraint != null && constraint.getDomains().isPresent()) {
if (!constraint.getDomains().get().isEmpty()) {
// Predicate is pushed down
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "Cannot push down table scan with predicates pushed down");
}
}
TableHandle tableHandle = node.getTable();
DataCenterTableHandle dcTableHandle = (DataCenterTableHandle) node.getTable().getConnectorHandle();
checkArgument(dcTableHandle.getPushDownSql().isEmpty(), "Data center should not have sql before pushdown");
LinkedHashMap<String, Selection> selections = new LinkedHashMap<>();
node.getOutputSymbols().forEach(outputColumn -> {
DataCenterColumnHandle dcColumn = (DataCenterColumnHandle) node.getAssignments().get(outputColumn);
selections.put(outputColumn.getName(), new Selection(dcColumn.getColumnName(), outputColumn.getName()));
});
StringBuilder table = new StringBuilder();
if (!isNullOrEmpty(dcTableHandle.getCatalogName())) {
table.append(quote(quote, dcTableHandle.getCatalogName())).append('.');
}
if (!isNullOrEmpty(dcTableHandle.getSchemaName())) {
table.append(quote(quote, dcTableHandle.getSchemaName())).append('.');
}
table.append(quote(quote, dcTableHandle.getTableName()));
JdbcQueryGeneratorContext.Builder contextBuilder = new JdbcQueryGeneratorContext.Builder()
.setCatalogName(Optional.of(tableHandle.getCatalogName()))
.setTransaction(Optional.of(tableHandle.getTransaction()))
.setSchemaTableName(Optional.of(new SchemaTableName(dcTableHandle.getSchemaName(), dcTableHandle.getTableName())))
.setSelections(selections)
.setFrom(Optional.of(table.toString()));
// If LIMIT has been push down, add it to context
if (dcTableHandle.getLimit().isPresent()) {
contextBuilder.setLimit(dcTableHandle.getLimit());
contextBuilder.setHasPushDown(true);
}
return Optional.of(contextBuilder.build());
}
}
}

View File

@ -57,7 +57,7 @@ public class DataCenterPageSourceProvider
private final OkHttpClient httpClient;
private TypeManager typeManager;
private final TypeManager typeManager;
/**
* Constructor of data center page source provider.
@ -91,7 +91,7 @@ public class DataCenterPageSourceProvider
sql.append(" FROM ");
if (tableHandler.getSubQuery() == null || "".equals(tableHandler.getSubQuery())) {
if (tableHandler.getPushDownSql() == null || "".equals(tableHandler.getPushDownSql())) {
if (!isNullOrEmpty(catalog)) {
sql.append(catalog).append('.');
}
@ -102,7 +102,7 @@ public class DataCenterPageSourceProvider
sql.append(table);
}
else {
sql.append(tableHandler.getSubQuery());
sql.append("(").append(tableHandler.getPushDownSql()).append(") pushdown");
}
if (limit.isPresent()) {

View File

@ -365,7 +365,6 @@ public class TestCrossRegionDynamicFilter
assertQuery("SELECT COUNT(*) FROM dc.tpch.tiny.lineitem JOIN orders ON dc.tpch.tiny.lineitem.orderkey = orders.orderkey AND NOT (orders.comment LIKE '%forges%')");
assertQuery("SELECT COUNT(*) FROM dc.tpch.tiny.lineitem JOIN orders ON dc.tpch.tiny.lineitem.orderkey = orders.orderkey AND NOT (orders.comment LIKE dc.tpch.tiny.lineitem.comment)");
assertQuery("SELECT COUNT(*) FROM dc.tpch.tiny.lineitem JOIN orders ON dc.tpch.tiny.lineitem.orderkey = orders.orderkey AND dc.tpch.tiny.lineitem.quantity + length(orders.comment) > 7");
assertQuery("SELECT COUNT(*) FROM dc.tpch.tiny.lineitem JOIN orders ON dc.tpch.tiny.lineitem.orderkey = orders.orderkey AND NULL");
}
@Test

View File

@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableMap;
import io.airlift.configuration.testing.ConfigAssertions;
import io.airlift.units.DataSize;
import io.airlift.units.Duration;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule;
import org.testng.annotations.Test;
import java.net.URI;
@ -57,6 +58,7 @@ public class TestDataCenterConfig
.setKerberosUseCanonicalHostname(false)
.setExtraCredentials(null)
.setQueryPushDownEnabled(true)
.setQueryPushDownModule(JdbcPushDownModule.DEFAULT)
.setHttpRequestReadTimeout(READ_TIMEOUT)
.setHttpRequestConnectTimeout(CONNECT_TIMEOUT)
.setClientTimeout(new Duration(10, TimeUnit.MINUTES))
@ -96,6 +98,7 @@ public class TestDataCenterConfig
.put("dc.ssl.truststore.password", "ssl.truststore.password")
.put("dc.ssl.truststore.path", "ssl.truststore.path")
.put("dc.query.pushdown.enabled", "false")
.put("dc.query.pushdown.module", "FULL_PUSHDOWN")
.put("dc.http-request-readTimeout", "5m")
.put("dc.http-request-connectTimeout", "5m")
.put("dc.http-client-timeout", "5m")
@ -131,6 +134,7 @@ public class TestDataCenterConfig
.setKerberosUseCanonicalHostname(true)
.setExtraCredentials("extra.credentials")
.setQueryPushDownEnabled(false)
.setQueryPushDownModule(JdbcPushDownModule.FULL_PUSHDOWN)
.setHttpRequestReadTimeout(new Duration(5, TimeUnit.MINUTES))
.setHttpRequestConnectTimeout(new Duration(5, TimeUnit.MINUTES))
.setClientTimeout(new Duration(5, TimeUnit.MINUTES))

View File

@ -18,12 +18,6 @@
</properties>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>

View File

@ -17,7 +17,8 @@ package io.hetu.core.plugin.hana;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.airlift.log.Logger;
import io.hetu.core.plugin.hana.rewrite.HanaSqlQueryWriter;
import io.hetu.core.plugin.hana.optimization.HanaPushDownParameter;
import io.hetu.core.plugin.hana.optimization.HanaQueryGenerator;
import io.prestosql.plugin.jdbc.BaseJdbcClient;
import io.prestosql.plugin.jdbc.BaseJdbcConfig;
import io.prestosql.plugin.jdbc.ColumnMapping;
@ -29,13 +30,16 @@ import io.prestosql.plugin.jdbc.JdbcSplit;
import io.prestosql.plugin.jdbc.JdbcTableHandle;
import io.prestosql.plugin.jdbc.JdbcTypeHandle;
import io.prestosql.plugin.jdbc.StatsCollecting;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorResult;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.SuppressFBWarnings;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.ColumnMetadata;
import io.prestosql.spi.connector.ConnectorSession;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.sql.SqlQueryWriter;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.sql.QueryGenerator;
import io.prestosql.spi.type.DecimalType;
import io.prestosql.spi.type.Decimals;
import io.prestosql.spi.type.Type;
@ -87,7 +91,7 @@ public class HanaClient
/**
* If disabled, do not accept sub-query push down.
*/
private final boolean isQueryPushDownEnabled;
private final JdbcPushDownModule pushDownModule;
/**
* constructor
@ -102,7 +106,7 @@ public class HanaClient
super(config, "", connectionFactory);
tableTypes = hanaConfig.getTableTypes().split(",");
schemaPattern = hanaConfig.getSchemaPattern();
isQueryPushDownEnabled = hanaConfig.isQueryPushDownEnabled();
this.pushDownModule = config.getPushDownModule();
this.hanaConfig = hanaConfig;
}
@ -240,22 +244,16 @@ public class HanaClient
}
@Override
public Optional<SqlQueryWriter> getSqlQueryWriter()
public Optional<QueryGenerator<JdbcQueryGeneratorResult>> getQueryGenerator(RowExpressionService rowExpressionService)
{
if (!isQueryPushDownEnabled) {
return Optional.empty();
}
return Optional.of(new HanaSqlQueryWriter(hanaConfig));
HanaPushDownParameter pushDownParameter = new HanaPushDownParameter(getIdentifierQuote(), this.caseInsensitiveNameMatching, pushDownModule, hanaConfig);
return Optional.of(new HanaQueryGenerator(rowExpressionService, pushDownParameter));
}
@SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
@Override
public Map<String, ColumnHandle> getColumns(ConnectorSession session, String sql, Map<String, Type> types)
{
if (!isQueryPushDownEnabled) {
return Collections.emptyMap();
}
try (Connection connection = connectionFactory.openConnection(JdbcIdentity.from(session));
PreparedStatement statement = connection.prepareStatement(sql)) {
ResultSetMetaData metadata = statement.getMetaData();

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.hana.optimization;
import io.hetu.core.plugin.hana.HanaConfig;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownParameter;
/**
* Push Down parameter module
*/
public class HanaPushDownParameter
extends JdbcPushDownParameter
{
private final HanaConfig hanaConfig;
public HanaPushDownParameter(String identifierQuote, boolean nameCaseInsensitive, JdbcPushDownModule pushDownModule, HanaConfig hanaConfig)
{
super(identifierQuote, nameCaseInsensitive, pushDownModule);
this.hanaConfig = hanaConfig;
}
public HanaConfig getHanaConfig()
{
return hanaConfig;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.hana.optimization;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcQueryGenerator;
import io.prestosql.spi.relation.RowExpressionService;
public class HanaQueryGenerator
extends BaseJdbcQueryGenerator
{
public HanaQueryGenerator(RowExpressionService rowExpressionService, HanaPushDownParameter pushDownParameter)
{
super(pushDownParameter, new HanaRowExpressionConverter(rowExpressionService, pushDownParameter), new HanaSqlStatementWriter(pushDownParameter));
}
}

View File

@ -0,0 +1,312 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.hana.optimization;
import com.google.common.base.Joiner;
import io.airlift.slice.Slice;
import io.hetu.core.plugin.hana.HanaConfig;
import io.hetu.core.plugin.hana.HanaConstants;
import io.hetu.core.plugin.hana.rewrite.UdfFunctionRewriteConstants;
import io.hetu.core.plugin.hana.rewrite.functioncall.ArrayConstructorCallRewriter;
import io.hetu.core.plugin.hana.rewrite.functioncall.BuildInDirectMapFunctionCallRewriter;
import io.hetu.core.plugin.hana.rewrite.functioncall.DateAddFunctionCallRewrite;
import io.hetu.core.plugin.hana.rewrite.functioncall.DateTimeFunctionCallRewriter;
import io.hetu.core.plugin.hana.rewrite.functioncall.HanaUnsupportedFunctionCallRewriter;
import io.hetu.core.plugin.hana.rewrite.functioncall.VarbinaryLiteralFunctionCallRewriter;
import io.prestosql.configmanager.ConfigSupplier;
import io.prestosql.configmanager.DefaultUdfRewriteConfigSupplier;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcRowExpressionConverter;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.function.OperatorType;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.ConstantExpression;
import io.prestosql.spi.relation.LambdaDefinitionExpression;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.relation.SpecialForm;
import io.prestosql.spi.sql.expression.QualifiedName;
import io.prestosql.spi.type.StandardTypes;
import io.prestosql.spi.type.Type;
import io.prestosql.spi.type.VarcharType;
import io.prestosql.spi.util.DateTimeUtils;
import io.prestosql.sql.ExpressionFormatter;
import io.prestosql.sql.builder.functioncall.FunctionWriterManager;
import io.prestosql.sql.builder.functioncall.FunctionWriterManagerGroup;
import io.prestosql.sql.builder.functioncall.functions.FunctionCallRewriter;
import io.prestosql.sql.builder.functioncall.functions.base.FromBase64CallRewriter;
import io.prestosql.sql.builder.functioncall.functions.config.DefaultConnectorConfigFunctionRewriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static io.prestosql.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT;
import static io.prestosql.spi.StandardErrorCode.NOT_SUPPORTED;
import static io.prestosql.spi.function.Signature.unmangleOperator;
import static io.prestosql.spi.function.StandardFunctionUtils.isArrayConstructor;
import static io.prestosql.spi.function.StandardFunctionUtils.isLikeFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isNotFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isOperator;
import static io.prestosql.spi.type.DateType.DATE;
import static io.prestosql.spi.type.TimeType.TIME;
import static io.prestosql.spi.type.TimestampType.TIMESTAMP;
import static io.prestosql.spi.type.VarbinaryType.VARBINARY;
import static io.prestosql.spi.util.DateTimeUtils.printDate;
import static java.lang.String.format;
import static java.util.Locale.ENGLISH;
public class HanaRowExpressionConverter
extends BaseJdbcRowExpressionConverter
{
private static final Set<String> hanaNotSupportFunctions =
Stream.of("try", "try_cast", "at_timezone", "current_user", "current_path", "current_time").collect(toImmutableSet());
private static FunctionWriterManager hanaFunctionManager;
/**
* Hana sql query writer
*
* @param hanaConfig hana config
*/
public HanaRowExpressionConverter(RowExpressionService rowExpressionService, HanaPushDownParameter hanaConfig)
{
super(rowExpressionService);
hanaFunctionManager = initFunctionManager(hanaConfig.getHanaConfig());
}
private FunctionWriterManager initFunctionManager(HanaConfig hanaConfig)
{
// add inner config udf, use the default function result string builder in the HanaConfigUdfRewriter
ConfigSupplier configSupplier = new DefaultUdfRewriteConfigSupplier(UdfFunctionRewriteConstants.DEFAULT_VERSION_UDF_REWRITE_PATTERNS);
DefaultConnectorConfigFunctionRewriter connectorConfigFunctionRewriter =
new DefaultConnectorConfigFunctionRewriter(HanaConstants.CONNECTOR_NAME, configSupplier);
// use the default function Signature Builder in the HanaFunctionRewriterManager
return FunctionWriterManagerGroup.newFunctionWriterManagerInstance(HanaConstants.CONNECTOR_NAME,
hanaConfig.getHanaSqlVersion(), getInjectFunctionCallRewritersDefault(hanaConfig), connectorConfigFunctionRewriter);
}
private Map<String, FunctionCallRewriter> getInjectFunctionCallRewritersDefault(HanaConfig hanaConfig)
{
// add the user define function re-writer
Map<String, FunctionCallRewriter> functionCallRewriters = new HashMap<>(Collections.emptyMap());
// 1. the base function re-writers all connector can use
FromBase64CallRewriter fromBase64CallRewriter = new FromBase64CallRewriter();
functionCallRewriters.put(FromBase64CallRewriter.INNER_FUNC_FROM_BASE64, fromBase64CallRewriter);
// 2. the specific user define function re-writers
FunctionCallRewriter varbinaryLiteralFunctionCallRewriter = new VarbinaryLiteralFunctionCallRewriter();
functionCallRewriters.put(VarbinaryLiteralFunctionCallRewriter.INNER_FUNC_VARBINARY_LITERAL, varbinaryLiteralFunctionCallRewriter);
FunctionCallRewriter unSupportedFunctionCallRewriter = new HanaUnsupportedFunctionCallRewriter(HanaConstants.CONNECTOR_NAME);
functionCallRewriters.put(HanaUnsupportedFunctionCallRewriter.INNER_FUNC_INTERVAL_LITERAL_DAY2SEC, unSupportedFunctionCallRewriter);
functionCallRewriters.put(HanaUnsupportedFunctionCallRewriter.INNER_FUNC_INTERVAL_LITERAL_YEAR2MONTH, unSupportedFunctionCallRewriter);
functionCallRewriters.put(HanaUnsupportedFunctionCallRewriter.INNER_FUNC_TIME_WITH_TZ_LITERAL, unSupportedFunctionCallRewriter);
FunctionCallRewriter dateTimeFunctionCallRewriter = new DateTimeFunctionCallRewriter(hanaConfig);
functionCallRewriters.put(DateTimeFunctionCallRewriter.INNER_FUNC_TIME_LITERAL, dateTimeFunctionCallRewriter);
functionCallRewriters.put(DateTimeFunctionCallRewriter.INNER_FUNC_TIMESTAMP_LITERAL, dateTimeFunctionCallRewriter);
FunctionCallRewriter dateAddFunctionCallRewrite = new DateAddFunctionCallRewrite();
functionCallRewriters.put(DateAddFunctionCallRewrite.BUILD_IN_FUNC_DATE_ADD, dateAddFunctionCallRewrite);
FunctionCallRewriter buildInDirectMapFunctionCallRewriter = new BuildInDirectMapFunctionCallRewriter();
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUIDLIN_AGGR_FUNC_SUM, buildInDirectMapFunctionCallRewriter);
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUILDIN_AGGR_FUNC_AVG, buildInDirectMapFunctionCallRewriter);
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUILDIN_AGGR_FUNC_COUNT, buildInDirectMapFunctionCallRewriter);
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUILDIN_AGGR_FUNC_MAX, buildInDirectMapFunctionCallRewriter);
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUILDIN_AGGR_FUNC_MIN, buildInDirectMapFunctionCallRewriter);
FunctionCallRewriter arrayConstructorCallRewriter = new ArrayConstructorCallRewriter();
functionCallRewriters.put(ArrayConstructorCallRewriter.INNER_FUNC_ARRAY_CONSTRUCTOR, arrayConstructorCallRewriter);
return functionCallRewriters;
}
protected static String functionCall(QualifiedName name, boolean isDistinct, List<String> argumentsList, Optional<String> orderBy, Optional<String> filter, Optional<String> window)
{
if (hanaFunctionManager == null) {
throw new PrestoException(NOT_SUPPORTED, "Function manager is uninitialized");
}
try {
return hanaFunctionManager.getFunctionRewriteResult(name, isDistinct, argumentsList, orderBy, filter, window);
}
catch (UnsupportedOperationException e) {
throw new PrestoException(NOT_SUPPORTED, e.getMessage());
}
}
private String handleCastOperator(RowExpression expression, Type dstType)
{
/*
* In SqlToRowExpressionTranslator, it will translate GenericLiteral expression to a 'CONSTANT' rowExpression,
* so the 'CAST' operator is not needed.
* */
String value = expression.accept(this, null);
if (expression instanceof ConstantExpression && expression.getType() instanceof VarcharType) {
return value;
}
if (dstType.getDisplayName().equals(LIKE_PATTERN_NAME)) {
return value;
}
return format("CAST(%s AS %s)", value, dstType.getDisplayName().toLowerCase(ENGLISH));
}
private String handleOperatorFunction(CallExpression call)
{
OperatorType type = unmangleOperator(call.getSignature().getName());
if (type.equals(OperatorType.CAST)) {
return handleCastOperator(call.getArguments().get(0), call.getType());
}
List<String> argumentList = call.getArguments().stream().map(expr -> expr.accept(this, null)).collect(Collectors.toList());
if (type.isArithmeticOperator()) {
if (type.equals(OperatorType.MODULUS)) {
return format("MOD(%s, %s)", argumentList.get(0), argumentList.get(1));
}
else {
return format("(%s %s %s)", argumentList.get(0), type.getOperator(), argumentList.get(1));
}
}
if (type.isComparisonOperator()) {
final String[] hanaCompareOperators = new String[]{"=", ">", "<", ">=", "<=", "!=", "<>"};
if (Arrays.asList(hanaCompareOperators).contains(type.getOperator())) {
return format("(%s %s %s)", argumentList.get(0), type.getOperator(), argumentList.get(1));
}
else {
String exceptionInfo = "Hana Connector does not support comparison operator " + type.getOperator();
throw new PrestoException(NOT_SUPPORTED, exceptionInfo);
}
}
if (type.equals(OperatorType.SUBSCRIPT)) {
if (call.getArguments().size() == 2) {
return format("MEMBER_AT(%s, %s)", argumentList.get(0), argumentList.get(1));
}
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Illegal argument num of function " + type.getOperator());
}
if (call.getArguments().size() == 1 && type.equals(OperatorType.NEGATION)) {
String value = argumentList.get(0);
String separator = value.startsWith("-") ? " " : "";
return format("-%s%s", separator, value);
}
throw new PrestoException(NOT_SUPPORTED, String.format("Unknown operator %s in push down", type.getOperator()));
}
@Override
public String visitCall(CallExpression call, Void context)
{
Signature signature = call.getSignature();
String functionName = call.getSignature().getName().toLowerCase(ENGLISH);
if (hanaNotSupportFunctions.contains(functionName)) {
throw new PrestoException(NOT_SUPPORTED, "Hana connector does not support " + functionName);
}
if (isOperator(signature)) {
return handleOperatorFunction(call);
}
List<String> argumentList = call.getArguments().stream().map(expr -> expr.accept(this, null)).collect(Collectors.toList());
if (isNotFunction(signature)) {
return format("(NOT %s)", argumentList.get(0));
}
if (isLikeFunction(signature)) {
return format("(%s LIKE %s)", argumentList.get(0), argumentList.get(1));
}
if (isArrayConstructor(signature)) {
return format("ARRAY(%s)", Joiner.on(", ").join(argumentList));
}
return functionCall(new QualifiedName(Collections.singletonList(functionName)), false, argumentList, Optional.empty(), Optional.empty(), Optional.empty());
}
@Override
public String visitLambda(LambdaDefinitionExpression lambda, Void context)
{
throw new PrestoException(NOT_SUPPORTED, "Hana connector does not support Lambda expression");
}
private String visitIfExpression(SpecialForm specialForm)
{
String condition = specialForm.getArguments().get(0).accept(this, null);
String trueValue = specialForm.getArguments().get(1).accept(this, null);
RowExpression falseExpression = specialForm.getArguments().get(2);
Optional<String> falseValue = ((falseExpression instanceof ConstantExpression) && ((ConstantExpression) falseExpression).isNull())
? Optional.empty() : Optional.of(falseExpression.accept(this, null));
StringBuilder stringBuilder = new StringBuilder(HanaConstants.DEAFULT_STRINGBUFFER_CAPACITY);
stringBuilder.append("CASE WHEN " + condition + " THEN " + trueValue);
falseValue.ifPresent(value -> stringBuilder.append(" ELSE ").append(value));
stringBuilder.append(" END");
return stringBuilder.toString();
}
@Override
public String visitSpecialForm(SpecialForm specialForm, Void context)
{
if (specialForm.getForm().equals(SpecialForm.Form.DEREFERENCE)
|| specialForm.getForm().equals(SpecialForm.Form.ROW_CONSTRUCTOR)
|| specialForm.getForm().equals(SpecialForm.Form.BIND)) {
throw new PrestoException(NOT_SUPPORTED, "Hana connector does not support" + specialForm.getForm().toString());
}
if (specialForm.getForm().equals(SpecialForm.Form.IF)) {
return visitIfExpression(specialForm);
}
return super.visitSpecialForm(specialForm, context);
}
@Override
public String visitConstant(ConstantExpression literal, Void context)
{
Type type = literal.getType();
if (type.equals(DATE)) {
String date = printDate((int) literal.getValue());
return StandardTypes.DATE + " " + ExpressionFormatter.formatStringLiteral(date);
}
if (type.equals(VARBINARY)) {
String hexValue = ((Slice) literal.getValue()).toStringUtf8();
return format("X'%s'", hexValue);
}
if (type.equals(TIME)) {
String time = DateTimeUtils.printTimeWithoutTimeZone((long) literal.getValue());
return format("time'%s'", time);
}
if (type.equals(TIMESTAMP)) {
String timestamp = DateTimeUtils.printTimeWithoutTimeZone((long) literal.getValue());
return format("timestamp'%s'", timestamp);
}
return super.visitConstant(literal, context);
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.hana.optimization;
import com.google.common.base.Joiner;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcSqlStatementWriter;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownParameter;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.sql.expression.QualifiedName;
import io.prestosql.spi.sql.expression.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import static io.prestosql.spi.StandardErrorCode.NOT_SUPPORTED;
public class HanaSqlStatementWriter
extends BaseJdbcSqlStatementWriter
{
public HanaSqlStatementWriter(JdbcPushDownParameter pushDownParameter)
{
super(pushDownParameter);
}
@Override
public String aggregation(String functionName, List<String> arguments, boolean isDistinct)
{
if (functionName.toUpperCase(Locale.ENGLISH).equals("VARIANCE")) {
functionName = "VAR";
}
return super.aggregation(functionName, arguments, isDistinct);
}
@Override
public String windowFrame(Types.WindowFrameType type, String start, Optional<String> end)
{
String frameString = super.windowFrame(type, start, end);
if (type.name().toLowerCase(Locale.ENGLISH).equals("range")) {
// should verify the hana default frame and the HeTu default range
if (frameString.toLowerCase(Locale.ENGLISH).contains("range between unbounded preceding and current row")) {
return "";
}
else {
throw new PrestoException(NOT_SUPPORTED, "Hana Connector does not support window frame: " + frameString);
}
}
return frameString;
}
@Override
public String window(String functionName, List<String> functionArgs, List<String> partitionBy, Optional<String> orderBy, Optional<String> frame)
{
// the window frame has limit to rows in the windowFrame method
// in hana grammar, ROWS requires a ORDER BY clause to be specified.
if (frame.isPresent() && frame.get().toLowerCase(Locale.ENGLISH).contains("rows") && !orderBy.isPresent()) {
throw new PrestoException(NOT_SUPPORTED, "Hana Connector does not support rows window frame without a " + "specified ORDER BY clause");
}
// the window frame has limit to rows in the windowFrame method
if (functionArgs.size() == 0 && frame.isPresent() && frame.get().toLowerCase(Locale.ENGLISH).contains("rows")) {
throw new PrestoException(NOT_SUPPORTED, "Hana Connector does not support function " + functionName + " with rows, only aggregation support this!");
}
List<String> parts = new ArrayList<>();
if (!partitionBy.isEmpty()) {
parts.add("PARTITION BY " + Joiner.on(", ").join(partitionBy));
}
orderBy.ifPresent(parts::add);
frame.ifPresent(parts::add);
String windows = '(' + Joiner.on(' ').join(parts) + ')';
// Window aggregation does not support DISTINCT, the same as HeTu, do not need to verify here
String signatureStr = HanaRowExpressionConverter.functionCall(new QualifiedName(Collections.singletonList(functionName)), false, functionArgs, Optional.empty(), Optional.empty(), Optional.empty());
return " " + signatureStr + " OVER " + windows;
}
}

View File

@ -1,402 +0,0 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.hana.rewrite;
import com.google.common.base.Joiner;
import io.hetu.core.plugin.hana.HanaConfig;
import io.hetu.core.plugin.hana.HanaConstants;
import io.hetu.core.plugin.hana.rewrite.functioncall.ArrayConstructorCallRewriter;
import io.hetu.core.plugin.hana.rewrite.functioncall.BuildInDirectMapFunctionCallRewriter;
import io.hetu.core.plugin.hana.rewrite.functioncall.DateAddFunctionCallRewrite;
import io.hetu.core.plugin.hana.rewrite.functioncall.DateTimeFunctionCallRewriter;
import io.hetu.core.plugin.hana.rewrite.functioncall.HanaUnsupportedFunctionCallRewriter;
import io.hetu.core.plugin.hana.rewrite.functioncall.VarbinaryLiteralFunctionCallRewriter;
import io.prestosql.configmanager.ConfigSupplier;
import io.prestosql.configmanager.DefaultUdfRewriteConfigSupplier;
import io.prestosql.spi.sql.expression.Operators;
import io.prestosql.spi.sql.expression.QualifiedName;
import io.prestosql.spi.sql.expression.Time;
import io.prestosql.spi.sql.expression.Types;
import io.prestosql.sql.builder.BaseSqlQueryWriter;
import io.prestosql.sql.builder.functioncall.FunctionWriterManager;
import io.prestosql.sql.builder.functioncall.FunctionWriterManagerGroup;
import io.prestosql.sql.builder.functioncall.functions.FunctionCallRewriter;
import io.prestosql.sql.builder.functioncall.functions.base.FromBase64CallRewriter;
import io.prestosql.sql.builder.functioncall.functions.config.DefaultConnectorConfigFunctionRewriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import static io.prestosql.spi.type.StandardTypes.BIGINT;
import static io.prestosql.spi.type.StandardTypes.BOOLEAN;
import static io.prestosql.spi.type.StandardTypes.CHAR;
import static io.prestosql.spi.type.StandardTypes.DATE;
import static io.prestosql.spi.type.StandardTypes.DECIMAL;
import static io.prestosql.spi.type.StandardTypes.DOUBLE;
import static io.prestosql.spi.type.StandardTypes.INTEGER;
import static io.prestosql.spi.type.StandardTypes.REAL;
import static io.prestosql.spi.type.StandardTypes.SMALLINT;
import static io.prestosql.spi.type.StandardTypes.TINYINT;
import static io.prestosql.spi.type.StandardTypes.VARCHAR;
import static java.lang.String.format;
/**
* Implementation of BaseSqlQueryWriter. It knows how to write Hana SQL for the Hetu's logical plan.
*
* @since 2019-09-10
*/
public class HanaSqlQueryWriter
extends BaseSqlQueryWriter
{
// The Hana connector extract function's support fields
private static final List<Time.ExtractField> HANA_SUPPORT_EXTRACT_FIELDS_LIST = Arrays.asList(Time.ExtractField.YEAR, Time.ExtractField.MONTH, Time.ExtractField.DAY, Time.ExtractField.HOUR, Time.ExtractField.MINUTE, Time.ExtractField.SECOND);
private FunctionWriterManager hanaFunctionRewriterManager;
/**
* Hana sql query writer
*
* @param hanaConfig hana config
*/
public HanaSqlQueryWriter(HanaConfig hanaConfig)
{
super();
functionCallManagerHandle(hanaConfig);
}
private void functionCallManagerHandle(HanaConfig hanaConfig)
{
// add inner config udf, use the default function result string builder in the HanaConfigUdfRewriter
ConfigSupplier configSupplier = new DefaultUdfRewriteConfigSupplier(UdfFunctionRewriteConstants.DEFAULT_VERSION_UDF_REWRITE_PATTERNS);
DefaultConnectorConfigFunctionRewriter connectorConfigFunctionRewriter =
new DefaultConnectorConfigFunctionRewriter(HanaConstants.CONNECTOR_NAME, configSupplier);
// use the default function Signature Builder in the HanaFunctionRewriterManager
hanaFunctionRewriterManager = FunctionWriterManagerGroup.newFunctionWriterManagerInstance(HanaConstants.CONNECTOR_NAME,
hanaConfig.getHanaSqlVersion(), getInjectFunctionCallRewritersDefault(hanaConfig), connectorConfigFunctionRewriter);
}
private Map<String, FunctionCallRewriter> getInjectFunctionCallRewritersDefault(HanaConfig hanaConfig)
{
// add the user define function re-writer
Map<String, FunctionCallRewriter> functionCallRewriters = new HashMap<>(Collections.emptyMap());
// 1. the base function re-writers all connector can use
FromBase64CallRewriter fromBase64CallRewriter = new FromBase64CallRewriter();
functionCallRewriters.put(FromBase64CallRewriter.INNER_FUNC_FROM_BASE64, fromBase64CallRewriter);
// 2. the specific user define function re-writers
FunctionCallRewriter varbinaryLiteralFunctionCallRewriter = new VarbinaryLiteralFunctionCallRewriter();
functionCallRewriters.put(VarbinaryLiteralFunctionCallRewriter.INNER_FUNC_VARBINARY_LITERAL, varbinaryLiteralFunctionCallRewriter);
FunctionCallRewriter unSupportedFunctionCallRewriter = new HanaUnsupportedFunctionCallRewriter(HanaConstants.CONNECTOR_NAME);
functionCallRewriters.put(HanaUnsupportedFunctionCallRewriter.INNER_FUNC_INTERVAL_LITERAL_DAY2SEC, unSupportedFunctionCallRewriter);
functionCallRewriters.put(HanaUnsupportedFunctionCallRewriter.INNER_FUNC_INTERVAL_LITERAL_YEAR2MONTH, unSupportedFunctionCallRewriter);
functionCallRewriters.put(HanaUnsupportedFunctionCallRewriter.INNER_FUNC_TIME_WITH_TZ_LITERAL, unSupportedFunctionCallRewriter);
FunctionCallRewriter dateTimeFunctionCallRewriter = new DateTimeFunctionCallRewriter(hanaConfig);
functionCallRewriters.put(DateTimeFunctionCallRewriter.INNER_FUNC_TIME_LITERAL, dateTimeFunctionCallRewriter);
functionCallRewriters.put(DateTimeFunctionCallRewriter.INNER_FUNC_TIMESTAMP_LITERAL, dateTimeFunctionCallRewriter);
FunctionCallRewriter dateAddFunctionCallRewrite = new DateAddFunctionCallRewrite();
functionCallRewriters.put(DateAddFunctionCallRewrite.BUILD_IN_FUNC_DATE_ADD, dateAddFunctionCallRewrite);
FunctionCallRewriter buildInDirectMapFunctionCallRewriter = new BuildInDirectMapFunctionCallRewriter();
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUIDLIN_AGGR_FUNC_SUM, buildInDirectMapFunctionCallRewriter);
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUILDIN_AGGR_FUNC_AVG, buildInDirectMapFunctionCallRewriter);
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUILDIN_AGGR_FUNC_COUNT, buildInDirectMapFunctionCallRewriter);
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUILDIN_AGGR_FUNC_MAX, buildInDirectMapFunctionCallRewriter);
functionCallRewriters.put(BuildInDirectMapFunctionCallRewriter.BUILDIN_AGGR_FUNC_MIN, buildInDirectMapFunctionCallRewriter);
FunctionCallRewriter arrayConstructorCallRewriter = new ArrayConstructorCallRewriter();
functionCallRewriters.put(ArrayConstructorCallRewriter.INNER_FUNC_ARRAY_CONSTRUCTOR, arrayConstructorCallRewriter);
return functionCallRewriters;
}
@Override
public String cast(String expression, String type, boolean isSafe, boolean isTypeOnly)
{
if (isSafe) {
throw new UnsupportedOperationException("Hana Connector does not support try_cast");
}
return format("CAST(%s AS %s)", expression, type);
}
@Override
public String comparisonExpression(Operators.ComparisonOperator operator, String left, String right)
{
final String[] hanaCompareOperators = {"=", ">", "<", ">=", "<=", "!=", "<>"};
String operatorString = operator.getValue();
if (Arrays.asList(hanaCompareOperators).contains(operatorString)) {
return format("(%s %s %s)", left, operatorString, right);
}
else {
String exceptionInfo = "Hana Connector does not support comparison operator " + operatorString;
throw new UnsupportedOperationException(exceptionInfo);
}
}
@Override
public String exists(String subquery)
{
return format("(EXISTS %s)", subquery);
}
@Override
public String extract(String expression, Time.ExtractField field)
{
if (HANA_SUPPORT_EXTRACT_FIELDS_LIST.contains(field)) {
return format("EXTRACT(%s FROM %s)", field, expression);
}
else {
throw new UnsupportedOperationException("Hana Connector does not support extract field: " + field);
}
}
/**
* arrayConstructor should be call by the function call
*
* @param values array values
*/
@Override
public String arrayConstructor(List<String> values)
{
return format("ARRAY(%s)", Joiner.on(", ").join(values));
}
@Override
public String subscriptExpression(String base, String index)
{
return format("MEMBER_AT(%s, %s)", base, index);
}
@Override
public String arithmeticBinary(Operators.ArithmeticOperator operator, String left, String right)
{
if (operator.equals(Operators.ArithmeticOperator.MODULUS)) {
return format("MOD(%s, %s)", left, right);
}
else {
return format("(%s %s %s)", left, operator.getValue(), right);
}
}
@Override
public String atTimeZone(String value, String timezone)
{
throw new UnsupportedOperationException("Hana Connector does not support at time zone");
}
@Override
public String lambdaArgumentDeclaration(String identifier)
{
throw new UnsupportedOperationException("Hana Connector does not support lambda argument declaration");
}
@Override
public String currentUser()
{
throw new UnsupportedOperationException("Hana Connector does not support current user");
}
@Override
public String currentPath()
{
throw new UnsupportedOperationException("Hana Connector does not support current path");
}
@Override
// CHECKSTYLE:OFF:RegexpSinglelineCheck => inherit api, can't change it
public String currentTime(Time.Function function, Integer precision)
{
throw new UnsupportedOperationException("Hana Connector does not support current time");
}
// CHECKSTYLE:ON:RegexpSinglelineCheck
/**
* intervalLiteral should be call by the function call
*
* @param signLiteral config
* @param value hanaConfig
* @param startField connectionFactory object
*/
@Override
public String intervalLiteral(Time.IntervalSign signLiteral, String value, Time.IntervalField startField, Optional<Time.IntervalField> endField)
{
throw new UnsupportedOperationException("Hana Connector does not support interval literal");
}
@Override
public String dereferenceExpression(String base, String field)
{
throw new UnsupportedOperationException("Hana Connector does not support dereference expression");
}
@Override
public String ifExpression(String condition, String trueValue, Optional<String> falseValue)
{
StringBuilder stringBuilder = new StringBuilder(HanaConstants.DEAFULT_STRINGBUFFER_CAPACITY);
stringBuilder.append("CASE WHEN " + condition + " THEN " + trueValue);
falseValue.ifPresent(value -> stringBuilder.append(" ELSE ").append(value));
stringBuilder.append(" END");
return stringBuilder.toString();
}
@Override
public String filter(String value)
{
throw new UnsupportedOperationException("Hana Connector does not support filter");
}
/**
* binaryLiteral should be call by the function call
*
* @param hexValue values
*/
@Override
public String binaryLiteral(String hexValue)
{
return format("X'%s'", hexValue);
}
@Override
public String bindExpression(List<String> values, String function)
{
throw new UnsupportedOperationException("Hana Connector does not support bind expression");
}
@Override
public String lambdaExpression(List<String> arguments, String body)
{
throw new UnsupportedOperationException("Hana Connector does not support lambda expression");
}
@Override
public String tryExpression(String innerExpression)
{
throw new UnsupportedOperationException("Hana Connector does not support try expression");
}
@Override
public String row(List<String> expressions)
{
throw new UnsupportedOperationException("Hana Connector does not support row");
}
@Override
// CHECKSTYLE:OFF:ParameterNumber
// => inherit api(io.prestosql.spi.sql.SqlQueryWriter.functionCall), can't change it
public String functionCall(QualifiedName name, boolean isDistinct, List<String> argumentsList, Optional<String> orderBy, Optional<String> filter, Optional<String> window)
{
// CHECKSTYLE:ON:ParameterNumber
return this.hanaFunctionRewriterManager.getFunctionRewriteResult(name, isDistinct, argumentsList, orderBy, filter, window);
}
@Override
public String timeLiteral(String value)
{
return format("time'%s'", value);
}
@Override
public String timestampLiteral(String value)
{
return format("timestamp'%s'", value);
}
@Override
public String genericLiteral(String type, String value)
{
// https://help.sap.com/viewer/
// 4fe29514fd584807ac9f2a04f6754767/2.0.03/en-US/20a1569875191014b507cf392724b7eb.html
// -- Type Constants Section
String lowerType = type.toLowerCase(Locale.ENGLISH);
switch (lowerType) {
case BIGINT:
case SMALLINT:
case TINYINT:
case REAL:
case INTEGER:
return value;
case DATE:
return type + " " + this.formatStringLiteral(value);
case BOOLEAN:
return booleanLiteral(Boolean.parseBoolean(value));
case DECIMAL:
return decimalLiteral(value);
case DOUBLE:
return doubleLiteral(Double.parseDouble(value));
case VARCHAR:
case CHAR:
return stringLiteral(value);
default:
String exceptionInfo = "Hana Connector does not support data type " + type;
throw new UnsupportedOperationException(exceptionInfo);
}
}
@Override
public String decimalLiteral(String value)
{
return "'" + value + "'";
}
@Override
public String formatWindowColumn(String functionName, List<String> args, String windows)
{
// the window frame has limit to rows in the windowFrame method
if (args.size() == 0 && windows.toLowerCase(Locale.ENGLISH).contains("rows")) {
throw new UnsupportedOperationException("Hana Connector does not support function " + functionName + " with rows, only aggregation support this!");
}
// Window aggregation does not support DISTINCT, the same as HeTu, do not need to verify here
String signatureStr = this.functionCall(new QualifiedName(Collections.singletonList(functionName)), false, args, Optional.empty(), Optional.empty(), Optional.empty());
return " " + signatureStr + " OVER " + windows;
}
@Override
public String window(List<String> partitionBy, Optional<String> orderBy, Optional<String> frame)
{
// the window frame has limit to rows in the windowFrame method
// in hana grammar, ROWS requires a ORDER BY clause to be specified.
if (frame.isPresent() && frame.get().toLowerCase(Locale.ENGLISH).contains("rows") && !orderBy.isPresent()) {
throw new UnsupportedOperationException("Hana Connector does not support rows window frame without a " + "specified ORDER BY clause");
}
return super.window(partitionBy, orderBy, frame);
}
@Override
public String windowFrame(Types.WindowFrameType type, String start, Optional<String> end)
{
String frameString = super.windowFrame(type, start, end);
if (type.name().toLowerCase(Locale.ENGLISH).equals("range")) {
// should verify the hana default frame and the HeTu default range
if (frameString.toLowerCase(Locale.ENGLISH).contains("range between unbounded preceding and current row")) {
return "";
}
else {
throw new UnsupportedOperationException("Hana Connector does not support window frame: " + frameString);
}
}
return frameString;
}
}

View File

@ -55,7 +55,7 @@ public class UdfFunctionRewriteConstants
.put("LOG2($1)", "LOG(2, $1)")
.put("LOG($1,$2)", "LOG($1, $2)")
.put("MOD($1,$2)", "MOD($1, $2)")
.put("POW($1,$2)", "POW($1, $2)")
.put("POW($1,$2)", "POWER($1, $2)")
.put("POWER($1,$2)", "POWER($1, $2)")
.put("RAND()", "RAND()")
.put("RANDOM()", "RAND()")
@ -77,6 +77,9 @@ public class UdfFunctionRewriteConstants
.put("RTRIM($1)", "RTRIM($1)")
.put("STRPOS($1,$2)", "LOCATE($1, $2)")
.put("SUBSTR($1,$2,$3)", "SUBSTR($1, $2, $3)")
.put("SUBSTR($1,$2)", "SUBSTR($1, $2)")
.put("SUBSTRING($1,$2,$3)", "SUBSTRING($1, $2, $3)")
.put("SUBSTRING($1,$2)", "SUBSTRING($1, $2)")
.put("POSITION($1,$2)", "LOCATE($2, $1)")
.put("TRIM($1)", "TRIM($1)")
.put("UPPER($1)", "UPPER($1)")

View File

@ -1,979 +0,0 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.hana;
import com.google.common.collect.ImmutableList;
import io.airlift.log.Logger;
import io.hetu.core.plugin.hana.rewrite.HanaSqlQueryWriter;
import io.hetu.core.plugin.hana.rewrite.UdfFunctionRewriteConstants;
import io.prestosql.plugin.jdbc.BaseJdbcConfig;
import io.prestosql.plugin.jdbc.ConnectionFactory;
import io.prestosql.plugin.jdbc.DriverConnectionFactory;
import io.prestosql.plugin.jdbc.JdbcClient;
import io.prestosql.plugin.jdbc.JdbcHandleResolver;
import io.prestosql.plugin.jdbc.JdbcMetadata;
import io.prestosql.plugin.jdbc.JdbcRecordSetProvider;
import io.prestosql.plugin.jdbc.JdbcSplitManager;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.connector.Connector;
import io.prestosql.spi.connector.ConnectorContext;
import io.prestosql.spi.connector.ConnectorFactory;
import io.prestosql.spi.connector.ConnectorHandleResolver;
import io.prestosql.spi.connector.ConnectorMetadata;
import io.prestosql.spi.connector.ConnectorRecordSetProvider;
import io.prestosql.spi.connector.ConnectorSplitManager;
import io.prestosql.spi.connector.ConnectorTransactionHandle;
import io.prestosql.spi.transaction.IsolationLevel;
import io.prestosql.spi.type.DateTimeEncoding;
import io.prestosql.spi.type.TimeZoneKey;
import io.prestosql.sql.builder.functioncall.ConfigFunctionParser;
import io.prestosql.sql.builder.functioncall.FunctionCallArgsPackage;
import io.prestosql.sql.tree.ArithmeticBinaryExpression;
import io.prestosql.sql.tree.AtTimeZone;
import io.prestosql.sql.tree.BetweenPredicate;
import io.prestosql.sql.tree.BinaryLiteral;
import io.prestosql.sql.tree.BindExpression;
import io.prestosql.sql.tree.BooleanLiteral;
import io.prestosql.sql.tree.Cast;
import io.prestosql.sql.tree.CharLiteral;
import io.prestosql.sql.tree.ComparisonExpression;
import io.prestosql.sql.tree.CurrentPath;
import io.prestosql.sql.tree.CurrentTime;
import io.prestosql.sql.tree.CurrentUser;
import io.prestosql.sql.tree.DecimalLiteral;
import io.prestosql.sql.tree.DereferenceExpression;
import io.prestosql.sql.tree.ExistsPredicate;
import io.prestosql.sql.tree.Expression;
import io.prestosql.sql.tree.FunctionCall;
import io.prestosql.sql.tree.GenericLiteral;
import io.prestosql.sql.tree.GroupingOperation;
import io.prestosql.sql.tree.IfExpression;
import io.prestosql.sql.tree.InListExpression;
import io.prestosql.sql.tree.InPredicate;
import io.prestosql.sql.tree.IntervalLiteral;
import io.prestosql.sql.tree.IsNotNullPredicate;
import io.prestosql.sql.tree.IsNullPredicate;
import io.prestosql.sql.tree.LambdaArgumentDeclaration;
import io.prestosql.sql.tree.LambdaExpression;
import io.prestosql.sql.tree.LongLiteral;
import io.prestosql.sql.tree.Node;
import io.prestosql.sql.tree.NodeLocation;
import io.prestosql.sql.tree.NotExpression;
import io.prestosql.sql.tree.NullLiteral;
import io.prestosql.sql.tree.Parameter;
import io.prestosql.sql.tree.QualifiedName;
import io.prestosql.sql.tree.QuantifiedComparisonExpression;
import io.prestosql.sql.tree.SingleColumn;
import io.prestosql.sql.tree.StringLiteral;
import io.prestosql.sql.tree.SubqueryExpression;
import io.prestosql.sql.tree.SubscriptExpression;
import io.prestosql.sql.tree.SymbolReference;
import io.prestosql.sql.tree.TimeLiteral;
import io.prestosql.sql.tree.TimestampLiteral;
import io.prestosql.sql.tree.TryExpression;
import io.prestosql.sql.tree.Window;
import io.prestosql.tests.AbstractTestSqlQueryWriter;
import io.prestosql.util.DateTimeUtils;
import org.codehaus.plexus.util.StringUtils;
import org.intellij.lang.annotations.Language;
import org.testng.SkipException;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static io.hetu.core.plugin.hana.TestHanaSqlUtil.getHandledSql;
import static io.prestosql.plugin.jdbc.DriverConnectionFactory.basicConnectionProperties;
import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_ERROR;
import static io.prestosql.sql.QueryUtil.identifier;
import static io.prestosql.sql.QueryUtil.query;
import static io.prestosql.sql.QueryUtil.row;
import static io.prestosql.sql.QueryUtil.selectList;
import static io.prestosql.sql.QueryUtil.simpleQuery;
import static io.prestosql.sql.QueryUtil.table;
import static io.prestosql.sql.QueryUtil.values;
import static io.prestosql.sql.tree.ArithmeticUnaryExpression.negative;
import static io.prestosql.sql.tree.ComparisonExpression.Operator.LESS_THAN;
import static org.testng.Assert.assertEquals;
/**
* This is testing HanaSqlQueryWriter
*
* @since 2019-09-25
*/
public class TestHanaSqlQueryWriter
extends AbstractTestSqlQueryWriter
{
private static final Logger LOGGER = Logger.get(TestHanaSqlQueryWriter.class);
private Connection connection;
private TestingHanaServer testingHanaServer;
private ConnectorFactory connectorFactory;
private List<String> tables = new ArrayList<>();
private HanaConfig hanaConfig = new HanaConfig();
/**
* Create TestHanaSqlQueryWriter
*/
protected TestHanaSqlQueryWriter()
{
super(new HanaSqlQueryWriter(new HanaConfig()), "hana", "datahub");
}
//tools for test
protected void assertExpression(Node expression, String expected, UnsupportedOperationException expectedExp)
{
assertExpression(expression, expected, Optional.empty(), expectedExp);
}
protected void assertExpression(Node expression, String expected, Optional<List<Expression>> params, UnsupportedOperationException expectedExp)
{
try {
assertExpression(expression, expected, params);
}
catch (Exception rtmExp) {
if ((expectedExp != null) && (rtmExp instanceof UnsupportedOperationException)) {
assertEquals(rtmExp.getMessage(), expectedExp.getMessage());
return;
}
throw rtmExp;
}
}
protected void assertStatement(@Language("SQL") String query, AssertionError assertionError, String... keywords)
{
try {
assertStatement(query, keywords);
}
catch (Error er) {
if ((assertionError != null) && (er instanceof AssertionError)) {
return;
}
throw er;
}
}
/**
* Setup the database
*/
@BeforeClass
public void setup()
{
this.testingHanaServer = TestingHanaServer.getInstance();
if (!this.testingHanaServer.isHanaServerAvailable()) {
LOGGER.info("please set correct hana data base info!");
throw new SkipException("skip the test");
}
LOGGER.info("running TestHanaSqlQueryWriter...");
try {
createTables(this.testingHanaServer);
}
catch (SQLException e) {
throw new RuntimeException(e);
}
super.setup();
}
/**
* Clean the resources
*/
@AfterClass(alwaysRun = true)
public void clean()
{
try {
if (this.testingHanaServer.isHanaServerAvailable()) {
if (!testingHanaServer.isTpchLoaded()) {
for (String table : tables) {
String sql = "DROP TABLE " + testingHanaServer.getSchema() + "." + table;
connection.createStatement().execute(sql);
}
}
TestingHanaServer.shutDown();
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
super.clean();
}
private void createTables(TestingHanaServer hanaServer) throws SQLException
{
BaseJdbcConfig jdbcConfig = new BaseJdbcConfig();
HanaConfig hanaConfig = new HanaConfig();
jdbcConfig.setConnectionUrl(hanaServer.getJdbcUrl());
jdbcConfig.setConnectionUser(hanaServer.getUser());
jdbcConfig.setConnectionPassword(hanaServer.getPassword());
hanaConfig.setTableTypes("TABLE,VIEW");
hanaConfig.setSchemaPattern(hanaServer.getSchema());
Driver driver = null;
try {
driver = (Driver) Class.forName(HanaConstants.SAP_HANA_JDBC_DRIVER_CLASS_NAME).getConstructor(((Class<?>[]) null)).newInstance();
}
catch (InstantiationException e) {
throw new PrestoException(JDBC_ERROR, e);
}
catch (IllegalAccessException e) {
throw new PrestoException(JDBC_ERROR, e);
}
catch (ClassNotFoundException e) {
throw new PrestoException(JDBC_ERROR, e);
}
catch (InvocationTargetException e) {
throw new PrestoException(JDBC_ERROR, e);
}
catch (NoSuchMethodException e) {
throw new PrestoException(JDBC_ERROR, e);
}
ConnectionFactory connectionFactory = new DriverConnectionFactory(driver, jdbcConfig.getConnectionUrl(),
Optional.ofNullable(jdbcConfig.getUserCredentialName()),
Optional.ofNullable(jdbcConfig.getPasswordCredentialName()), basicConnectionProperties(jdbcConfig));
HanaClient hanaClient = new HanaClient(jdbcConfig, hanaConfig, connectionFactory);
connectorFactory = new HanaJdbcConnectorFactory(hanaClient, "hana");
connection = DriverManager.getConnection(hanaServer.getJdbcUrl(), hanaServer.getUser(), hanaServer.getPassword());
if (!hanaServer.isTpchLoaded()) {
connection.createStatement().execute(buildCreateTableSql("orders", "(orderkey bigint NOT NULL, custkey bigint NOT NULL, orderstatus varchar(1) NOT NULL, totalprice DOUBLE NOT NULL, orderdate date NOT NULL, orderpriority varchar(15) NOT NULL, clerk varchar(15) NOT NULL, shippriority integer NOT NULL, COMMENT varchar(79) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("customer", "(custkey bigint NOT NULL, name varchar(25) NOT NULL, address varchar(40) NOT NULL, nationkey bigint NOT NULL, phone varchar(15) NOT NULL, acctbal DOUBLE NOT NULL, mktsegment varchar(10) NOT NULL, COMMENT varchar(117) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("supplier", "(suppkey bigint NOT NULL, name varchar(25) NOT NULL, address varchar(40) NOT NULL, nationkey bigint NOT NULL, phone varchar(15) NOT NULL, acctbal DOUBLE NOT NULL, COMMENT varchar(101) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("region", "(regionkey bigint NOT NULL, name varchar(25) NOT NULL, COMMENT varchar(152) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("lineitem", "(orderkey bigint NOT NULL, partkey bigint NOT NULL, suppkey bigint NOT NULL, linenumber integer NOT NULL, quantity DOUBLE NOT NULL, extendedprice DOUBLE NOT NULL, discount DOUBLE NOT NULL, tax DOUBLE NOT NULL, returnflag varchar(1) NOT NULL, linestatus varchar(1) NOT NULL, shipdate date NOT NULL, commitdate date NOT NULL, receiptdate date NOT NULL, shipinstruct varchar(25) NOT NULL, shipmode varchar(10) NOT NULL, COMMENT varchar(44) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("nation", "(nationkey bigint NOT NULL, name varchar(25) NOT NULL, regionkey bigint NOT NULL, COMMENT varchar(152) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("part", "(partkey bigint NOT NULL, name varchar(55) NOT NULL, mfgr varchar(25) NOT NULL, brand varchar(10) NOT NULL, TYPE varchar(25) NOT NULL, SIZE integer NOT NULL, container varchar(10) NOT NULL, retailprice DOUBLE NOT NULL, COMMENT varchar(23) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("partsupp", "(partkey bigint NOT NULL, suppkey bigint NOT NULL, availqty integer NOT NULL, supplycost DOUBLE NOT NULL, COMMENT varchar(199) NOT NULL)"));
}
}
private String buildCreateTableSql(String tableName, String columnInfo)
{
String newTableName = TestingHanaServer.getActualTable(tableName);
tables.add(newTableName);
return "CREATE TABLE " + testingHanaServer.getSchema() + "." + newTableName + " " + columnInfo;
}
@Override
protected void assertStatement(@Language("SQL") String query, String... keywords)
{
String newQuery = getHandledSql(query);
super.assertStatement(newQuery, keywords);
}
/**
* getConnectorFactory
*
* @return connection factory
*/
@Override
protected Optional<ConnectorFactory> getConnectorFactory()
{
return Optional.of(this.connectorFactory);
}
@Test
public void testCast()
{
assertExpression(new Cast(new NullLiteral(), "date", false), "CAST(null AS date)", new UnsupportedOperationException("Hana Connector does not support try_cast"));
assertExpression(new Cast(new NullLiteral(), "date", true), "CAST(null AS date)", new UnsupportedOperationException("Hana Connector does not support try_cast"));
}
@Test
public void testQuantifiedComparisonExpression()
{
LOGGER.info("Testing comparison expressions");
assertExpression(new QuantifiedComparisonExpression(
LESS_THAN,
QuantifiedComparisonExpression.Quantifier.ANY,
identifier("col1"),
new SubqueryExpression(simpleQuery(selectList(new SingleColumn(identifier("col2"))), table(QualifiedName.of("table1"))))),
"(col1 < ANY (SELECT col2\n" +
"FROM\n" +
" table1\n" +
"))");
assertExpression(new QuantifiedComparisonExpression(
ComparisonExpression.Operator.EQUAL,
QuantifiedComparisonExpression.Quantifier.ALL,
identifier("col1"),
new SubqueryExpression(query(values(row(longLiteral("1")), row(longLiteral("2")))))),
"(col1 = ALL ( VALUES \n" + " ROW (1)\n" + ", ROW (2)\n" + "))",
new UnsupportedOperationException("Hana Connector does not support row"));
assertExpression(new QuantifiedComparisonExpression(
ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL,
QuantifiedComparisonExpression.Quantifier.SOME,
identifier("col1"),
new SubqueryExpression(simpleQuery(selectList(longLiteral("10"))))),
"(col1 >= SOME (SELECT 10\n" +
"\n" +
"))");
}
@Test
public void testComparisonExpression()
{
assertExpression(new ComparisonExpression(ComparisonExpression.Operator.EQUAL, new SymbolReference("a"), new StringLiteral("hello")), "(a = 'hello')");
assertExpression(new ComparisonExpression(ComparisonExpression.Operator.NOT_EQUAL, new SymbolReference("a"), new StringLiteral("hello")), "(a <> 'hello')");
assertExpression(new ComparisonExpression(ComparisonExpression.Operator.LESS_THAN, new SymbolReference("a"), new StringLiteral("hello")), "(a < 'hello')");
assertExpression(new ComparisonExpression(ComparisonExpression.Operator.LESS_THAN_OR_EQUAL, new SymbolReference("a"), new StringLiteral("hello")), "(a <= 'hello')");
assertExpression(new ComparisonExpression(ComparisonExpression.Operator.GREATER_THAN, new SymbolReference("a"), new StringLiteral("hello")), "(a > 'hello')");
assertExpression(new ComparisonExpression(ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL, new SymbolReference("a"), new StringLiteral("hello")), "(a >= 'hello')");
assertExpression(new ComparisonExpression(ComparisonExpression.Operator.IS_DISTINCT_FROM, new SymbolReference("a"), new StringLiteral("hello")), "NA", new UnsupportedOperationException("Hana Connector does not support comparison operator IS DISTINCT FROM"));
}
@Test
public void testWindowFunction()
{
// Hetu SQL grammar functions window.html
String tableCustomer = TestingHanaServer.getActualTable("customer");
String tableLineitem = TestingHanaServer.getActualTable("lineitem");
LOGGER.info("Testing window function in a statement");
@Language("SQL")
String query = "select quantity , max(quantity) " +
"over(order by returnflag) as ranking" +
" from " + tableLineitem + " limit 100";
assertStatement(query, "SELECT", "MAX", "over", "order", "by",
"from", "lineitem", "LIMIT");
query = "select quantity , max(quantity) over(partition by linestatus " +
"order by returnflag desc nulls first rows 2 preceding) as ranking from " +
tableLineitem + " order by quantity limit 100";
assertStatement(query, "SELECT", "MAX", "over", "partition", "BY", "order", "by",
"returnflag", "DESC", "NULLS", "FIRST", "ROWS", "2", "PRECEDING", "lineitem", "order", "BY", "quantity", "LIMIT");
query = "select quantity , max(quantity) over(partition by linestatus order by " +
"returnflag desc nulls first rows between 2 preceding and 1 following) as ranking " +
" from " + tableLineitem + " limit 100";
assertStatement(query, "SELECT", "MAX", "over", "partition", "BY", "order", "by",
"returnflag", "DESC", "NULLS", "FIRST", "ROWS", "between", "2", "PRECEDING", "and", "1", "following", "lineitem", "LIMIT");
query = "select rank() over(partition by name order by acctbal) as ranking, " +
" sum(quantity) over(partition by linestatus order by returnflag rows 2 preceding) as ranking2 " +
"from " + tableLineitem + ", " + tableCustomer + " limit 100";
assertStatement(query, "SELECT", "sum", "quantity", "OVER", "PARTITION", "ORDER", "returnflag", "ROWS", "2",
"PRECEDING", "rank", "over", "partition", "by", "name", "ORDER", "by", "acctbal", "ASC", "NULLS", "LAST",
"lineitem", "CROSS", "JOIN ", "customer", "LIMIT");
// Hana(grammar) Connector does not support function rank with rows, only aggregation support this! Assert error
query = "select rank() over(partition by name order by acctbal rows 2 preceding) as ranking from " + tableCustomer + " limit 10";
assertStatement(query, new AssertionError(), "SELECT", "rank", "over", "partition", "by", "ranking", "sum", "over", "partition", "BY",
"order", "by", "returnflag", "DESC", "NULLS", "FIRST", "ROWS", "2", "PRECEDING", "lineitem", "customer", "LIMIT");
query = "select rank() over(partition by name order by acctbal) as ranking from " + tableCustomer + " limit 10";
assertStatement(query, new AssertionError(), "SELECT", "rank", "over", "partition", "by", "customer", "LIMIT");
}
@Test
public void testGroupByWithComplexGroupingOperations()
{
// Hetu SQL grammar select#group-by-clause
LOGGER.info("Testing Group By Clause with Complex Grouping Operations");
String tableCustomer = TestingHanaServer.getActualTable("customer");
String tableOrders = TestingHanaServer.getActualTable("orders");
@Language("SQL")
String query = "SELECT name, address, sum(acctbal) FROM " + tableCustomer + " GROUP BY rollup(name, address)";
assertStatement(query, "SELECT", "sum", "acctbal", "GROUP", "BY", "GROUPING", "SETS", "name", "address", "name", "()");
query = "SELECT name, address, sum(acctbal) FROM " + tableCustomer + " GROUP BY GROUPING SETS (name, address)";
assertStatement(query, "SELECT", "sum", "acctbal", "GROUP", "BY", "GROUPING", "SETS", "((", "address", "name", "))");
query = "SELECT name, address, sum(acctbal) FROM " + tableCustomer + " GROUP BY cube(name, address)";
assertStatement(query, "SELECT", "sum", "acctbal", "GROUP", "BY", "GROUPING", "SETS", "((", "address", "name", "))");
query = "SELECT name, address, sum(acctbal) FROM " + tableCustomer + " GROUP BY all cube(name, address), rollup(name, address)";
assertStatement(query, "SELECT", "sum", "acctbal", "GROUP", "BY", "GROUPING", "SETS", "((", "address", "name", "))");
query = "SELECT name, address, sum(acctbal) FROM " + tableCustomer + " GROUP BY name, rollup(name, address)";
assertStatement(query, "SELECT", "sum", "acctbal", "GROUP", "BY", "GROUPING", "SETS", "((", "address", "name", "))");
// group by with having clause
query = "SELECT name, address, sum(acctbal) FROM " + tableCustomer + " GROUP BY name, rollup(name, address) having sum(acctbal) > 1000 order by sum(acctbal)";
assertStatement(query, "SELECT", "sum", "acctbal", "GROUP", "BY", "GROUPING", "SETS", "((", "address", "name", "))");
// group by clause with window function
query = "select name, acctbal, sum(acctbal) over(partition by name order by name rows 2 preceding) as ranking, sum(acctbal) from " + tableCustomer + " group by cube(name, acctbal) having sum(acctbal) > 1000 order by name";
assertStatement(query, "SELECT", "sum", "acctbal", "partition", "by", "ORDER BY", "ROWS", "2", "PRECEDING", "customer", "GROUP", "BY", "GROUPING", "SETS", "((", "name", "acctbal", "name", "()))", "where", "sum", ">", "1E3", "ORDER BY");
// join with group by complex grouping operations
query = "select o.custkey, sum(o.totalprice) from " + tableOrders + " o, " + tableCustomer + " c where c.custkey = o.custkey group by rollup( o.custkey, o.totalprice)";
assertStatement(query, "SELECT", "sum", "totalprice", "orders", "INNER JOIN", "customer", "GROUP", "BY", "GROUPING", "SETS", ",", ",", ",");
}
@Test
public void testJoinStatements()
{
LOGGER.info("Testing join statements");
@Language("SQL") String query = "SELECT c.name FROM customer c LEFT JOIN orders o ON c.custkey=o.custkey";
assertStatement(query, "SELECT", "FROM", "customer", "LEFT JOIN", "orders", "ON", "table0.custkey = table1.custkey_0");
}
@Test
public void testAggregationStatements()
{
LOGGER.info("Testing aggregation statements");
String tableCustomer = TestingHanaServer.getActualTable("customer");
String tableOrders = TestingHanaServer.getActualTable("orders");
String tableLineitem = TestingHanaServer.getActualTable("lineitem");
/*@Language("SQL") String query = "SELECT * FROM " +
" (SELECT max(totalprice) AS price, o.orderkey AS orderkey FROM " +
" customer c JOIN orders o ON c.custkey=o.custkey GROUP BY orderpriority, orderkey) t1 " +
" LEFT JOIN lineitem l ON substr(cast(t1.orderkey AS VARCHAR), 0, 2)=cast(t1.orderkey AS VARCHAR) LIMIT 20";*/
@Language("SQL") String query = "SELECT * FROM " +
" (SELECT max(totalprice) AS price, o.orderkey AS orderkey FROM " +
" " + tableCustomer + " c JOIN " + tableOrders + " o ON c.custkey=o.custkey GROUP BY orderpriority, orderkey) t1 " +
" LEFT JOIN " + tableLineitem + " l ON substr(cast(t1.orderkey AS VARCHAR), 0, 2)=cast(t1.orderkey AS VARCHAR) LIMIT 20";
assertStatement(query, "SELECT", "FROM", "customer", "INNER JOIN", "orders", "GROUP BY", "LEFT JOIN", "lineitem", "LIMIT 20");
/*query = "SELECT * FROM " + " (SELECT max(totalprice) AS price, o.orderkey AS orderkey FROM " +
" customer c join orders o ON c.custkey=o.custkey GROUP BY orderpriority, orderkey HAVING orderkey>100) t1 "
+
" LEFT JOIN lineitem l ON substr(cast(t1.orderkey AS VARCHAR), 0, 2)=cast(t1.orderkey AS VARCHAR) LIMIT 10"; */
query = "SELECT * FROM " +
" (SELECT max(totalprice) AS price, o.orderkey AS orderkey FROM " +
" " + tableCustomer + " c join " + tableOrders + " o ON c.custkey=o.custkey GROUP BY orderpriority, orderkey HAVING orderkey>100) t1 " +
" LEFT JOIN " + tableLineitem + " l ON substr(cast(t1.orderkey AS VARCHAR), 0, 2)=cast(t1.orderkey AS VARCHAR) LIMIT 10";
assertStatement(query, "SELECT", "FROM", "customer", "INNER JOIN", "orders", "WHERE", ">", "100", "GROUP BY", "LEFT JOIN", "lineitem", "LIMIT 10");
}
@Test
public void testTpchSql3()
{
LOGGER.info("Testing TPCH Sql 3");
// @Language("SQL") String query = "SELECT l.orderkey, sum(l.extendedprice * (1 - l.discount)) AS revenue, o.orderdate, o.shippriority FROM customer c, orders o, lineitem l WHERE c.mktsegment = 'BUILDING' and c.custkey = o.custkey and l.orderkey = o.orderkey and o.orderdate < date '1995-03-22' and l.shipdate > date '1995-03-22' GROUP BY l.orderkey, o.orderdate, o.shippriority ORDER BY revenue desc, o.orderdate LIMIT 10";
@Language("SQL") String query = "SELECT l.orderkey, sum(l.extendedprice * (1 - l.discount)) AS revenue, o.orderdate, o.shippriority FROM " +
TestingHanaServer.getActualTable("customer") + " c, " +
TestingHanaServer.getActualTable("orders") + " o, " +
TestingHanaServer.getActualTable("lineitem") + " l " +
"WHERE c.mktsegment = 'BUILDING' and c.custkey = o.custkey and l.orderkey = o.orderkey and o.orderdate < date '1995-03-22' and l.shipdate > date '1995-03-22' GROUP BY l.orderkey, o.orderdate, o.shippriority ORDER BY revenue desc, o.orderdate LIMIT 10";
assertStatement(query, "sum", "FROM", "customer", "INNER JOIN", "orders", "INNER JOIN", "lineitem", "GROUP BY", "ORDER BY", "desc");
}
@Test
public void testTpchSql5()
{
LOGGER.info("Testing TPCH Sql 5");
// @Language("SQL") String query = "SELECT n.name, sum(l.extendedprice * (1 - l.discount)) AS revenue FROM customer c, orders o, lineitem l, supplier s, nation n, region r WHERE c.custkey = o.custkey and l.orderkey = o.orderkey and l.suppkey = s.suppkey and c.nationkey = s.nationkey and s.nationkey = n.nationkey and n.regionkey = r.regionkey and r.name = 'AFRICA' and o.orderdate >= date '1993-01-01' and o.orderdate < date '1994-01-01' GROUP BY n.name ORDER BY revenue desc";
@Language("SQL") String query = "SELECT n.name, sum(l.extendedprice * (1 - l.discount)) AS revenue FROM " +
TestingHanaServer.getActualTable("customer") + " c, " +
TestingHanaServer.getActualTable("orders") + " o, " +
TestingHanaServer.getActualTable("lineitem") + " l, " +
TestingHanaServer.getActualTable("supplier") + " s, " +
TestingHanaServer.getActualTable("nation") + " n, " +
TestingHanaServer.getActualTable("region") + " r " +
"WHERE c.custkey = o.custkey and l.orderkey = o.orderkey and l.suppkey = s.suppkey and c.nationkey = s.nationkey and s.nationkey = n.nationkey and n.regionkey = r.regionkey and r.name = 'AFRICA' and o.orderdate >= date '1993-01-01' and o.orderdate < date '1994-01-01' GROUP BY n.name ORDER BY revenue desc";
assertStatement(query, "sum", "FROM", "customer", "INNER JOIN", "orders", "INNER JOIN", "lineitem", "INNER JOIN", "supplier", "INNER JOIN", "nation", "INNER JOIN", "region", "GROUP BY", "ORDER BY", "desc");
}
private static class HanaJdbcConnectorFactory
implements ConnectorFactory
{
private final JdbcClient jdbcClient;
private final String name;
private HanaJdbcConnectorFactory(JdbcClient jdbcClient, String name)
{
this.jdbcClient = jdbcClient;
this.name = name;
}
@Override
public String getName()
{
return this.name;
}
@Override
public ConnectorHandleResolver getHandleResolver()
{
return new JdbcHandleResolver();
}
@Override
public Connector create(String catalogName, Map<String, String> config, ConnectorContext context)
{
return new Connector() {
@Override
public ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLevel, boolean readOnly)
{
return new ConnectorTransactionHandle() {
};
}
@Override
public ConnectorMetadata getMetadata(ConnectorTransactionHandle transactionHandle)
{
return new JdbcMetadata(jdbcClient, false);
}
@Override
public ConnectorSplitManager getSplitManager()
{
return new JdbcSplitManager(jdbcClient);
}
@Override
public ConnectorRecordSetProvider getRecordSetProvider()
{
return new JdbcRecordSetProvider(jdbcClient);
}
};
}
}
@Override
@Test
public void testFunctionCallAndTryExpression()
{
LOGGER.info("Testing function call and try expressions");
List<Expression> literals = list(longLiteral("10"), longLiteral("20"), longLiteral("30"));
FunctionCall functionCall = new FunctionCall(Optional.empty(),
QualifiedName.of("test"),
Optional.empty(),
Optional.of(new InPredicate(new SymbolReference("age"), array(literals))),
Optional.empty(),
true, literals);
TryExpression tryExpression = new TryExpression(functionCall);
assertExpression(functionCall, "test(DISTINCT 10, 20, 30) FILTER (WHERE (age IN ARRAY[10,20,30]))", new UnsupportedOperationException("Hana Connector does not support filter"));
assertExpression(tryExpression, "TRY(test(DISTINCT 10, 20, 30) FILTER (WHERE (age IN ARRAY[10,20,30])))", new UnsupportedOperationException("Hana Connector does not support filter"));
}
@Override
@Test
public void testMiscellaneousExpression()
{
LOGGER.info("Testing HeTu miscellaneous expressions");
assertExpression(new GroupingOperation(Optional.empty(), ImmutableList.of(QualifiedName.of("a"), QualifiedName.of("b"))), "GROUPING (a, b)");
assertExpression(new DereferenceExpression(new SymbolReference("b"), identifier("x")), "b.x", new UnsupportedOperationException("Hana Connector does not support dereference expression"));
assertExpression(new Window(ImmutableList.of(new SymbolReference("a")), Optional.empty(), Optional.empty()), "(PARTITION BY a)");
}
@Override
@Test
public void testMiscellaneousLiteralExpression()
{
LOGGER.info("Testing HeTu miscellaneous literal expressions");
//time literal invoke by HanaSqlQueryWriter.timeLiteral(just for functional coverage)
assertExpression(new TimeLiteral("12:10:59"), "time'12:10:59'");
assertExpression(new TimeLiteral("03:04:05"), "time'03:04:05'");
//time literal first handle by optimizer and end invoke by HanaSqlQueryWriter.functionCall(realword implement)
long epochTime = DateTimeUtils.parseTimeLiteral("12:12:59.999");
List<Expression> timefunCallParamliterals = list(longLiteral(String.valueOf(epochTime)));
FunctionCall timeFunctionCall = new FunctionCall(Optional.empty(),
QualifiedName.of("$literal$time"),
Optional.empty(),
Optional.empty(),
Optional.empty(),
true, timefunCallParamliterals);
assertExpression(timeFunctionCall, "time'12:12:59.999'");
//timestamp literal invoke by HanaSqlQueryWriter.timestampLiteral(just for functional coverage)
assertExpression(new TimestampLiteral("2011-05-10 23:12:59.999"), "timestamp'2011-05-10 23:12:59.999'");
//parseTimestampLiteral will encodeing with HeTu epoch time ms.
long hetuTimeStampWchicagoTZ = DateTimeUtils.parseTimestampLiteral("2011-05-10 10:12:59.999 America/Chicago");
long epochTimeStampWchicagoTZ = DateTimeEncoding.unpackMillisUtc(hetuTimeStampWchicagoTZ);
LOGGER.info("America/Chicago zoneKey" + DateTimeEncoding.unpackZoneKey(hetuTimeStampWchicagoTZ));
assertEquals(TimeZoneKey.getTimeZoneKey("America/Chicago"), DateTimeEncoding.unpackZoneKey(hetuTimeStampWchicagoTZ));
List<Expression> timeStampWtzfunCallParamliterals = list(longLiteral(String.valueOf(epochTimeStampWchicagoTZ)));
FunctionCall timestampWtzFunctionCall = new FunctionCall(Optional.empty(),
QualifiedName.of("$literal$timestamp"),
Optional.empty(),
Optional.empty(),
Optional.empty(),
true, timeStampWtzfunCallParamliterals);
assertExpression(timestampWtzFunctionCall, "timestamp'2011-05-10 15:12:59.999'");
//timestamp literal first handle by optimizer and end invoke by HanaSqlQueryWriter.functionCall(realword implement)
long epochTimeStampWutcTZ = DateTimeUtils.parseTimestampLiteral("2011-05-10 23:12:59.999");
List<Expression> timeStampfunCallParamliterals = list(longLiteral(String.valueOf(epochTimeStampWutcTZ)));
FunctionCall timestampFunctionCall = new FunctionCall(Optional.empty(),
QualifiedName.of("$literal$timestamp"),
Optional.empty(),
Optional.empty(),
Optional.empty(),
true, timeStampfunCallParamliterals);
assertExpression(timestampFunctionCall, "timestamp'2011-05-10 23:12:59.999'");
assertExpression(new IntervalLiteral("33", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.DAY, Optional.empty()), "INTERVAL '33' DAY", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new IntervalLiteral("33", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.DAY, Optional.of(IntervalLiteral.IntervalField.SECOND)), "INTERVAL '33' DAY TO SECOND", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new CharLiteral("abc"), "CHAR 'abc'");
}
@Override
@Test
public void testPredicateExpression()
{
LOGGER.info("Testing predicate expressions");
List<Expression> literals = list(longLiteral("10"), longLiteral("20"), longLiteral("30"));
assertExpression(new InPredicate(new SymbolReference("age"), array(literals)), "(age IN ARRAY(10, 20, 30))");
assertExpression(new InListExpression(literals), "(10, 20, 30)");
assertExpression(new IsNullPredicate(new SymbolReference("age")), "(age IS NULL)");
assertExpression(new IsNotNullPredicate(new SymbolReference("age")), "(age IS NOT NULL)");
assertExpression(new BetweenPredicate(longLiteral("1"), longLiteral("2"), longLiteral("3")), "(1 BETWEEN 2 AND 3)");
assertExpression(new NotExpression(new BetweenPredicate(longLiteral("1"), longLiteral("2"), longLiteral("3"))), "(NOT (1 BETWEEN 2 AND 3))");
}
@Test
public void testArithmeticBinary()
{
LOGGER.info("Testing ArithmeticBinary expressions");
assertExpression(new ArithmeticBinaryExpression(ArithmeticBinaryExpression.Operator.ADD, negative(longLiteral("23")), longLiteral("2")), "(-23 + 2)");
assertExpression(new ArithmeticBinaryExpression(ArithmeticBinaryExpression.Operator.SUBTRACT, new ArithmeticBinaryExpression(ArithmeticBinaryExpression.Operator.SUBTRACT, longLiteral("233"), longLiteral("2")), longLiteral("3")), "((233 - 2) - 3)");
assertExpression(new ArithmeticBinaryExpression(ArithmeticBinaryExpression.Operator.DIVIDE, new ArithmeticBinaryExpression(ArithmeticBinaryExpression.Operator.DIVIDE, longLiteral("1"), longLiteral("233")), longLiteral("3")), "((1 / 233) / 3)");
assertExpression(new ArithmeticBinaryExpression(ArithmeticBinaryExpression.Operator.ADD, longLiteral("1"), new ArithmeticBinaryExpression(ArithmeticBinaryExpression.Operator.MULTIPLY, longLiteral("2"), longLiteral("233"))), "(1 + (2 * 233))");
assertExpression(new ArithmeticBinaryExpression(ArithmeticBinaryExpression.Operator.MODULUS, longLiteral("233"), new ArithmeticBinaryExpression(ArithmeticBinaryExpression.Operator.MULTIPLY, longLiteral("2"), longLiteral("3"))), "MOD(233, (2 * 3))");
}
@Override
@Test
public void testArrayExpression()
{
LOGGER.info("Testing ArrayConstructor expressions");
assertExpression(array(list()), "ARRAY()");
assertExpression(array(list(longLiteral("1"), longLiteral("233"))), "ARRAY(1, 233)");
assertExpression(array(list(doubleLiteral("1.0"), doubleLiteral("233.5"))), "ARRAY(1E0, 2.335E2)");
assertExpression(array(list(stringLiteral("hi233"))), "ARRAY('hi233')");
assertExpression(array(list(stringLiteral("hi233"), stringLiteral("hello world"))), "ARRAY('hi233', 'hello world')");
}
@Test
public void testSubscriptExpression()
{
assertExpression(new SubscriptExpression(array(list(longLiteral("1"), longLiteral("233"))), longLiteral("1")), "MEMBER_AT(ARRAY(1, 233), 1)");
}
@Override
@Test
public void testAtTimeZoneExpression()
{
LOGGER.info("Testing at timezone expression");
assertExpression(new AtTimeZone(stringLiteral("2012-10-31 01:00 UTC"), stringLiteral("Asia/Shanghai")), "'2012-10-31 01:00 UTC' AT TIME ZONE 'Asia/Shanghai'", new UnsupportedOperationException("Hana Connector does not support at time zone"));
}
@Test
public void testBinaryLiteral()
{
LOGGER.info("Testing binary literal expressions");
assertExpression(new BinaryLiteral(""), "X''");
assertExpression(new BinaryLiteral("abcdef1234567890ABCDEF"), "X'ABCDEF1234567890ABCDEF'");
}
@Test
public void testCurrentPathExpression()
{
LOGGER.info("Testing HeTu current path expression");
assertExpression(new CurrentPath(new NodeLocation(0, 0)), "CURRENT_PATH", new UnsupportedOperationException("Hana Connector does not support current path"));
}
@Test
public void testCurrentTimestampExpression()
{
LOGGER.info("Testing HeTu current timestamp expression");
assertExpression(new CurrentTime(CurrentTime.Function.TIME, 2), "current_time(2)", new UnsupportedOperationException("Hana Connector does not support current time"));
}
@Test
public void testCurrentUserExpression()
{
LOGGER.info("Testing HeTu current user expression");
assertExpression(new CurrentUser(new NodeLocation(0, 0)), "CURRENT_USER", new UnsupportedOperationException("Hana Connector does not support current user"));
}
@Test
public void testDereferenceExpression()
{
LOGGER.info("Testing Dereference Expression expression");
assertExpression(new DereferenceExpression(new SymbolReference("b"), identifier("x")), "b.x", new UnsupportedOperationException("Hana Connector does not support dereference expression"));
}
@Test
public void testExistsAndSubqueryExpression()
{
LOGGER.info("Testing exists expression");
// TODO test sub query Expression independently
assertExpression(new SubqueryExpression(simpleQuery(selectList(new LongLiteral("1")))), "(SELECT 1\n" + "\n" + ")");
assertExpression(new ExistsPredicate(new SubqueryExpression(simpleQuery(selectList(new LongLiteral("1"))))), "(EXISTS (SELECT 1\n" + "\n" + "))");
}
@Override
@Test
public void testIfExpression()
{
LOGGER.info("Testing if and nullif expressions");
assertExpression(new IfExpression(new BooleanLiteral("true"), longLiteral("1"), longLiteral("0")), "CASE WHEN true THEN 1 ELSE 0 END");
assertExpression(new IfExpression(new BooleanLiteral("true"), longLiteral("3"), new NullLiteral()), "CASE WHEN true THEN 3 ELSE null END");
assertExpression(new IfExpression(new BooleanLiteral("false"), new NullLiteral(), longLiteral("4")), "CASE WHEN false THEN null ELSE 4 END");
assertExpression(new IfExpression(new BooleanLiteral("false"), new NullLiteral(), new NullLiteral()), "CASE WHEN false THEN null ELSE null END");
assertExpression(new IfExpression(new BooleanLiteral("true"), longLiteral("3"), null), "CASE WHEN true THEN 3 END");
// TODO: VERIFY THE NULLIF
}
@Override
@Test
public void testIntervalLiteralExpression()
{
LOGGER.info("Testing interval literal expressions");
assertExpression(new IntervalLiteral("1234", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.YEAR), "INTERVAL '1234' YEAR", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new IntervalLiteral("123-4", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.YEAR, Optional.of(IntervalLiteral.IntervalField.MONTH)), "INTERVAL '123-4' YEAR TO MONTH", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new IntervalLiteral("4", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.MONTH), "INTERVAL '4' MONTH", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new IntervalLiteral("12", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.DAY), "INTERVAL '12' DAY", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new IntervalLiteral("1234 23:58:53.456", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.DAY, Optional.of(IntervalLiteral.IntervalField.SECOND)), "INTERVAL '1234 23:58:53.456' DAY TO SECOND", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new IntervalLiteral("12", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.HOUR), "INTERVAL '12' HOUR", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new IntervalLiteral("00:59", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.HOUR, Optional.of(IntervalLiteral.IntervalField.MINUTE)), "INTERVAL '00:59' HOUR TO MINUTE", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new IntervalLiteral("59", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.MINUTE), "INTERVAL '59' MINUTE", new UnsupportedOperationException("Hana Connector does not support interval literal"));
assertExpression(new IntervalLiteral("59", IntervalLiteral.Sign.POSITIVE, IntervalLiteral.IntervalField.SECOND), "INTERVAL '59' SECOND", new UnsupportedOperationException("Hana Connector does not support interval literal"));
}
@Override
@Test
public void testLambdaExpression()
{
// TODO: test identifier independently
LOGGER.info("Testing Lambda Argument Declaration Expression");
assertExpression(new LambdaExpression(list(), identifier("x1")), "() -> x1", new UnsupportedOperationException("Hana Connector does not support lambda expression"));
assertExpression(new LambdaExpression(list(new LambdaArgumentDeclaration(identifier("x1"))), new FunctionCall(QualifiedName.of("sin"), list(identifier("x1")))), "(x1) -> sin(x1)", new UnsupportedOperationException("Hana Connector does not support lambda argument declaration"));
assertExpression(new LambdaExpression(list(new LambdaArgumentDeclaration(identifier("x1")), new LambdaArgumentDeclaration(identifier("y1"))), new FunctionCall(QualifiedName.of("mod"), list(identifier("x1"), identifier("y1")))), "(x1, y1) -> mod(x1, y1)", new UnsupportedOperationException("Hana Connector does not support lambda argument declaration"));
assertExpression(new LambdaArgumentDeclaration(identifier("x1")), "", new UnsupportedOperationException("Hana Connector does not support lambda argument declaration"));
}
@Override
@Test
public void testParameterExpression()
{
LOGGER.info("Testing parameter expressions");
Optional<List<Expression>> params = Optional.of(list(new SymbolReference("tpch.tiny.item"), longLiteral("1")));
assertExpression(new Parameter(0), "tpch.tiny.item", params);
assertExpression(new Parameter(1), "1", params);
assertExpression(new Parameter(2), "?");
}
@Override
@Test
public void testLambdaStatement()
{
LOGGER.info("Testing lambda in a statement");
@Language("SQL") String query = "SELECT filter(split(comment, ' '), x -> length(x) > 2) FROM customer LIMIT 10";
assertStatement(query, new AssertionError("Failed to rewrite the query "), "SELECT", "filter", "split", "comment", "' '", "expr", "->", "length", ">", "2", "LIMIT 10");
}
@Override
@Test
public void testExtractStatement()
{
LOGGER.info("Testing extract statement");
@Language("SQL") String queryYear1 = "SELECT extract(YEAR FROM orderdate) AS year FROM orders LIMIT 10";
@Language("SQL") String queryMonth1 = "SELECT extract(MONTH FROM orderdate) AS year FROM orders LIMIT 10";
@Language("SQL") String queryDay1 = "SELECT extract(DAY FROM orderdate) AS year FROM orders LIMIT 10";
@Language("SQL") String queryHour1 = "SELECT extract(HOUR FROM orderdate) AS year FROM orders LIMIT 10";
@Language("SQL") String queryMinute1 = "SELECT extract(MINUTE FROM orderdate) AS year FROM orders LIMIT 10";
@Language("SQL") String querySecond1 = "SELECT extract(SECOND FROM orderdate) AS year FROM orders LIMIT 10";
assertStatement(queryYear1, "SELECT", "year", "orderdate", "FROM", "orders", "LIMIT 10");
assertStatement(queryMonth1, "SELECT", "month", "orderdate", "FROM", "orders", "LIMIT 10");
assertStatement(queryDay1, "SELECT", "day", "orderdate", "FROM", "orders", "LIMIT 10");
assertStatement(queryHour1, "SELECT", "hour", "orderdate", "FROM", "orders", "LIMIT 10");
assertStatement(queryMinute1, "SELECT", "minute", "orderdate", "FROM", "orders", "LIMIT 10");
assertStatement(querySecond1, "SELECT", "second", "orderdate", "FROM", "orders", "LIMIT 10");
@Language("SQL") String queryYear2 = "select year(cast(web_rec_start_date as date)) as year from web_site order by year limit 2";
@Language("SQL") String queryMonth2 = "select month(cast(web_rec_start_date as date)) as month from web_site order by month limit 2";
@Language("SQL") String queryDay2 = "select day(cast(web_rec_start_date as date)) as day from web_site order by day limit 2";
@Language("SQL") String queryHour2 = "select hour(cast(web_rec_start_date as date)) as hour from web_site order by hour limit 2";
@Language("SQL") String queryMinute2 = "select minute(cast(web_rec_start_date as date)) as minute from web_site order by minute limit 2";
@Language("SQL") String querySecond2 = "select second(cast(web_rec_start_date as date)) as second from web_site order by second limit 2";
assertStatement(queryYear2, "SELECT", "year", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
assertStatement(queryMonth2, "SELECT", "month", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
assertStatement(queryDay2, "SELECT", "day", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
assertStatement(queryHour2, "SELECT", "hour", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
assertStatement(queryMinute2, "SELECT", "minute", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
assertStatement(querySecond2, "SELECT", "second", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
@Language("SQL") String queryYear11 = "SELECT extract(YEAR_OF_WEEK FROM orderdate) AS year FROM orders LIMIT 10";
@Language("SQL") String queryMonth12 = "SELECT extract(DAY_OF_MONTH FROM orderdate) AS year FROM orders LIMIT 10";
@Language("SQL") String queryYear21 = "select YEAR_OF_WEEK(cast(web_rec_start_date as date)) as year from web_site order by year limit 2";
@Language("SQL") String queryMonth22 = "select DAY_OF_MONTH(cast(web_rec_start_date as date)) as month from web_site order by month limit 2";
assertStatement(queryYear21, new AssertionError(), "SELECT", "YEAR_OF_WEEK", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
assertStatement(queryMonth22, new AssertionError(), "SELECT", "DAY_OF_MONTH", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
assertStatement(queryYear11, new AssertionError(), "SELECT", "YEAR_OF_WEEK", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
assertStatement(queryMonth12, new AssertionError(), "SELECT", "DAY_OF_MONTH", "web_rec_start_date", "FROM", "web_site", "LIMIT 2");
}
// TODO: testFilter
@Test
public void testRowExpression()
{
assertExpression(row(longLiteral("1")), "row(1)", new UnsupportedOperationException("Hana Connector does not support row"));
assertExpression(row(longLiteral("1"), longLiteral("1")), "row(1, 1)", new UnsupportedOperationException("Hana Connector does not support row"));
}
@Test
public void testBindExpression()
{
assertExpression(new BindExpression(list(new StringLiteral("value")), new StringLiteral("targetFunction")), "$INTERNAL$BIND(value, targetFunction)", new UnsupportedOperationException("Hana Connector does not support bind expression"));
}
@Test
public void testTryExpression()
{
LOGGER.info("Testing function call and try expressions");
List<Expression> literals = list(longLiteral("10"), longLiteral("20"), longLiteral("30"));
FunctionCall functionCall = new FunctionCall(Optional.empty(),
QualifiedName.of("test"),
Optional.empty(),
Optional.empty(),
Optional.empty(),
true, literals);
TryExpression tryExpression = new TryExpression(functionCall);
assertExpression(functionCall, "test(DISTINCT 10, 20, 30)",
new UnsupportedOperationException("Hana Connector does not support function call of test"));
assertExpression(tryExpression, "TRY(test(DISTINCT 10, 20, 30))", new UnsupportedOperationException("Hana Connector does not support function call of test"));
}
@Test
public void testAggregationWithOrderByExpression()
{
// ignore this functioncall support TODO: add new ut case
assertEquals(true, true);
}
@Test
public void testDecimalLiteralExpression()
{
LOGGER.info("Testing HeTu decimal literal expressions");
assertExpression(new DecimalLiteral("12.34"), "'12.34'");
assertExpression(new DecimalLiteral("12."), "'12.'");
assertExpression(new DecimalLiteral("12"), "'12'");
assertExpression(new DecimalLiteral(".34"), "'.34'");
assertExpression(new DecimalLiteral("+12.34"), "'+12.34'");
assertExpression(new DecimalLiteral("+12"), "'+12'");
assertExpression(new DecimalLiteral("-12.34"), "'-12.34'");
assertExpression(new DecimalLiteral("-12"), "'-12'");
assertExpression(new DecimalLiteral("+.34"), "'+.34'");
assertExpression(new DecimalLiteral("-.34"), "'-.34'");
}
@Test
public void testGenericLiteralExpression()
{
LOGGER.info("Testing Hana Connector generic literal expressions");
assertExpression(new GenericLiteral("VARCHAR", "abc"), "'abc'");
assertExpression(new GenericLiteral("CHAR", "abc"), "'abc'");
assertExpression(new GenericLiteral("BIGINT", "abc"), "abc");
assertExpression(new GenericLiteral("SMALLINT", "abc"), "abc");
assertExpression(new GenericLiteral("TINYINT", "abc"), "abc");
assertExpression(new GenericLiteral("REAL", "abc"), "abc");
assertExpression(new GenericLiteral("INTEGER", "abc"), "abc");
assertExpression(new GenericLiteral("DOUBLE", "3141592"), "3.141592E6");
assertExpression(new GenericLiteral("BOOLEAN", "true"), "true");
assertExpression(new GenericLiteral("DECIMAL", "3.141592"), "'3.141592'");
assertExpression(new GenericLiteral("DATE", "abc"), "DATE 'abc'");
}
@Test
public void testVarbinaryLiteralExpression()
{
LOGGER.info("Testing HeTu varbinary literal expressions");
assertExpression(new FunctionCall(QualifiedName.of("from_base64"), list(stringLiteral("c2VsZWN0"))), "73656C656374");
assertExpression(new FunctionCall(QualifiedName.of("$literal$varbinary"),
list(stringLiteral("73656C656374"))), "X'73656C656374'");
assertExpression(new FunctionCall(QualifiedName.of("$literal$varbinary"),
list(new FunctionCall(QualifiedName.of("from_base64"), list(stringLiteral("c2VsZWN0"))))), "X'73656C656374'");
}
@Test
public void testArrayConstructorExpression()
{
LOGGER.info("Testing HeTu array constructor expressions");
assertExpression(new FunctionCall(QualifiedName.of("array_constructor"), list()), "ARRAY()");
assertExpression(new FunctionCall(QualifiedName.of("array_constructor"), list(longLiteral("1"), longLiteral("233"))), "ARRAY(1, 233)");
assertExpression(new FunctionCall(QualifiedName.of("array_constructor"), list(doubleLiteral("1.0"), doubleLiteral("233.5"))), "ARRAY(1E0, 2.335E2)");
assertExpression(new FunctionCall(QualifiedName.of("array_constructor"), list(stringLiteral("hi233"))), "ARRAY('hi233')");
assertExpression(new FunctionCall(QualifiedName.of("array_constructor"), list(stringLiteral("hi233"), stringLiteral("hello world"))), "ARRAY('hi233', 'hello world')");
}
@Test
public void testConfigFunctionCallDefault()
{
LOGGER.info("Testing config function call rewrite");
Map<String, String> propertiesMap = UdfFunctionRewriteConstants.DEFAULT_VERSION_UDF_REWRITE_PATTERNS;
// config functions
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String key = entry.getKey();
String regex = "\\(.*\\)";
int argsCount = StringUtils.countMatches(key, "$");
String functionName = key.replaceAll(regex, "");
List<String> funcNameList = new ArrayList<>(Collections.emptyList());
funcNameList.add(functionName);
List<Expression> argsListExp = new ArrayList<>();
List<String> argsListStr = new ArrayList<>();
for (int i = 0; i < argsCount; i++) {
argsListExp.add(stringLiteral("arg" + i));
argsListStr.add("arg" + i);
}
LOGGER.info(functionName + " " + argsListStr.toString());
FunctionCallArgsPackage functionCallArgsPackage = new FunctionCallArgsPackage(new io.prestosql.spi.sql.expression.QualifiedName(funcNameList), false, argsListStr, Optional.empty(), Optional.empty(), Optional.empty());
String propertyName = ConfigFunctionParser.baseFunctionArgsToConfigPropertyName(functionCallArgsPackage);
String rewriteResult = ConfigFunctionParser.baseConfigPropertyValueToFunctionPushDownString(functionCallArgsPackage, propertiesMap.get(propertyName));
LOGGER.info(rewriteResult);
if (rewriteResult == null) {
throw new AssertionError("found null from FunctionCallRewriteUtil");
}
for (int i = 0; i < argsCount; i++) {
String argsC = "arg" + i;
rewriteResult = rewriteResult.replace(argsC, "'" + argsC + "'");
}
assertExpression(new FunctionCall(QualifiedName.of(functionName), argsListExp), rewriteResult);
}
}
@Test
public void testDataAddFunctions()
{
LOGGER.info("Testing Data Add Function call rewrite");
assertExpression(new FunctionCall(QualifiedName.of("date_add"), list(stringLiteral("second"), longLiteral("233"), stringLiteral("date233"))), "ADD_SECONDS('date233', 233)");
assertExpression(new FunctionCall(QualifiedName.of("date_add"), list(stringLiteral("minute"), longLiteral("233"), stringLiteral("date233"))), "ADD_SECONDS('date233', 233 * 60)");
assertExpression(new FunctionCall(QualifiedName.of("date_add"), list(stringLiteral("hour"), longLiteral("233"), stringLiteral("date233"))), "ADD_SECONDS('date233', 233 * 3600)");
assertExpression(new FunctionCall(QualifiedName.of("date_add"), list(stringLiteral("day"), longLiteral("233"), stringLiteral("date233"))), "ADD_DAYS('date233', 233)");
assertExpression(new FunctionCall(QualifiedName.of("date_add"), list(stringLiteral("week"), longLiteral("233"), stringLiteral("date233"))), "ADD_DAYS('date233', 233 * 7)");
assertExpression(new FunctionCall(QualifiedName.of("date_add"), list(stringLiteral("month"), longLiteral("233"), stringLiteral("date233"))), "ADD_MONTHS('date233', 233)");
assertExpression(new FunctionCall(QualifiedName.of("date_add"), list(stringLiteral("quarter"), longLiteral("233"), stringLiteral("date233"))), "ADD_MONTHS('date233', 233 * 3)");
assertExpression(new FunctionCall(QualifiedName.of("date_add"), list(stringLiteral("year"), longLiteral("233"), stringLiteral("date233"))), "ADD_YEARS('date233', 233)");
}
}

View File

@ -174,11 +174,6 @@
<artifactId>log</artifactId>
</dependency>
<dependency>
<groupId>io.hetu.core</groupId>
<artifactId>presto-parser</artifactId>
</dependency>
<dependency>
<groupId>io.airlift</groupId>
<artifactId>stats</artifactId>

View File

@ -22,6 +22,7 @@ import io.airlift.log.Logger;
import io.airlift.slice.Slice;
import io.hetu.core.plugin.oracle.config.RoundingMode;
import io.hetu.core.plugin.oracle.config.UnsupportedTypeHandling;
import io.hetu.core.plugin.oracle.optimization.OracleQueryGenerator;
import io.prestosql.plugin.jdbc.BaseJdbcClient;
import io.prestosql.plugin.jdbc.BaseJdbcConfig;
import io.prestosql.plugin.jdbc.ColumnMapping;
@ -34,12 +35,16 @@ import io.prestosql.plugin.jdbc.LongWriteFunction;
import io.prestosql.plugin.jdbc.SliceWriteFunction;
import io.prestosql.plugin.jdbc.StatsCollecting;
import io.prestosql.plugin.jdbc.WriteMapping;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownParameter;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorResult;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.SuppressFBWarnings;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.ConnectorSession;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.sql.SqlQueryWriter;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.sql.QueryGenerator;
import io.prestosql.spi.type.AbstractType;
import io.prestosql.spi.type.CharType;
import io.prestosql.spi.type.DateTimeEncoding;
@ -190,7 +195,7 @@ public class OracleClient
/**
* If disabled, do not accept sub-query push down.
*/
private final boolean isQueryPushDownEnabled;
private final JdbcPushDownModule pushDownModule;
/**
* enable to user oracle synonyms
@ -210,7 +215,7 @@ public class OracleClient
{
// the empty "" is to not use a quote to create queries
super(config, "\"", connectionFactory);
this.isQueryPushDownEnabled = oracleConfig.isQueryPushDownEnabled();
this.pushDownModule = config.getPushDownModule();
this.numberDefaultScale = oracleConfig.getNumberDefaultScale();
this.roundingMode = requireNonNull(oracleConfig.getRoundingMode(), "oracle rounding mode cannot be null");
this.unsupportedTypeHandling = requireNonNull(oracleConfig.getUnsupportedTypeHandling(),
@ -385,22 +390,10 @@ public class OracleClient
}
}
@Override
public Optional<SqlQueryWriter> getSqlQueryWriter()
{
if (!isQueryPushDownEnabled) {
return Optional.empty();
}
return Optional.of(new OracleSqlQueryWriter());
}
@SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
@Override
public Map<String, ColumnHandle> getColumns(ConnectorSession session, String sql, Map<String, Type> types)
{
if (!isQueryPushDownEnabled) {
return Collections.emptyMap();
}
try (Connection connection = connectionFactory.openConnection(JdbcIdentity.from(session));
PreparedStatement statement = connection.prepareStatement(sql)) {
ResultSetMetaData metadata = statement.getMetaData();
@ -697,6 +690,13 @@ public class OracleClient
}
}
@Override
public Optional<QueryGenerator<JdbcQueryGeneratorResult>> getQueryGenerator(RowExpressionService rowExpressionService)
{
JdbcPushDownParameter pushDownParameter = new JdbcPushDownParameter(getIdentifierQuote(), this.caseInsensitiveNameMatching, pushDownModule);
return Optional.of(new OracleQueryGenerator(rowExpressionService, pushDownParameter));
}
private ColumnMapping decimalColumnMapping(DecimalType decimalType)
{
// JDBC driver can return BigDecimal with lower scale than column's scale when there are trailing zeroes

View File

@ -37,8 +37,6 @@ public class OracleConfig
private static final int DEFAULT_SCALE = 0;
private boolean isQueryPushDownEnabled = true;
private UnsupportedTypeHandling unsupportedTypeHandling = UnsupportedTypeHandling.FAIL;
private RoundingMode roundingMode = RoundingMode.UNNECESSARY;
@ -47,25 +45,6 @@ public class OracleConfig
private boolean synonymsEnabled;
public boolean isQueryPushDownEnabled()
{
return isQueryPushDownEnabled;
}
/**
* set Query Push Down Enabled
*
* @param isQueryPushDownEnabledParameter config from properties
* @return oracle config object
*/
@Config("hetu.query.pushdown.enabled")
@ConfigDescription("Enable sub-query push down to this data center. It's set by default")
public OracleConfig setQueryPushDownEnabled(boolean isQueryPushDownEnabledParameter)
{
this.isQueryPushDownEnabled = isQueryPushDownEnabledParameter;
return this;
}
public UnsupportedTypeHandling getUnsupportedTypeHandling()
{
return unsupportedTypeHandling;

View File

@ -1,294 +0,0 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.oracle;
import com.google.common.collect.ImmutableMap;
import io.prestosql.spi.sql.expression.OrderBy;
import io.prestosql.spi.sql.expression.QualifiedName;
import io.prestosql.spi.sql.expression.Selection;
import io.prestosql.spi.sql.expression.Time;
import io.prestosql.sql.builder.BaseSqlQueryWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import static io.prestosql.spi.type.StandardTypes.BIGINT;
import static io.prestosql.spi.type.StandardTypes.CHAR;
import static io.prestosql.spi.type.StandardTypes.DATE;
import static io.prestosql.spi.type.StandardTypes.DOUBLE;
import static io.prestosql.spi.type.StandardTypes.INTEGER;
import static io.prestosql.spi.type.StandardTypes.REAL;
import static io.prestosql.spi.type.StandardTypes.SMALLINT;
import static io.prestosql.spi.type.StandardTypes.TIMESTAMP;
import static io.prestosql.spi.type.StandardTypes.TIMESTAMP_WITH_TIME_ZONE;
import static io.prestosql.spi.type.StandardTypes.TINYINT;
import static io.prestosql.spi.type.StandardTypes.VARBINARY;
import static io.prestosql.spi.type.StandardTypes.VARCHAR;
/**
* Implementation of BaseSqlQueryWriter. It knows how to write
* Oracle SQL for the Hetu's logical plan.
*
* @since 2019-07-18
*/
public class OracleSqlQueryWriter
extends BaseSqlQueryWriter
{
private static final char SINGLE_QUOTE = '\'';
private static final int VARIABLE_ARGUMENTS = -1;
private static final String CHAR_TYPE_PREFIX = "char(";
private static final String DECIMAL_TYPE_PREFIX = "decimal(";
private static final String VARCHAR_TYPE_PREFIX = "varchar(";
private static final Map<String, Integer> BLACKLISTED_FUNCTIONS;
OracleSqlQueryWriter()
{
super(BLACKLISTED_FUNCTIONS);
}
private static boolean isStringLiteral(String expression)
{
char first = expression.charAt(0);
char last = expression.charAt(expression.length() - 1);
// In Hetu, identifier names can be surrounded byt double quotes
return first == SINGLE_QUOTE && last == SINGLE_QUOTE;
}
@Override
public String lambdaArgumentDeclaration(String identifier)
{
throw new UnsupportedOperationException("Oracle Connector does not support lambda");
}
@Override
public String lambdaExpression(List<String> arguments, String body)
{
throw new UnsupportedOperationException("Oracle Connector does not support lambda");
}
@Override
public String decimalLiteral(String value)
{
return "'" + value + "'";
}
@Override
public String arrayConstructor(List<String> values)
{
throw new UnsupportedOperationException("Oracle connector does not support array constructor");
}
@Override
public String subscriptExpression(String base, String index)
{
throw new UnsupportedOperationException("Oracle connector does not support subscript expression");
}
@Override
public String genericLiteral(String type, String value)
{
// https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements003.htm
String lowerType = type.toLowerCase(Locale.ENGLISH);
switch (lowerType) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case REAL:
case DOUBLE:
return value;
case VARCHAR:
case CHAR:
case VARBINARY:
return stringLiteral(value);
case DATE:
return lowerType + " " + stringLiteral(value);
default:
if (lowerType.startsWith(DECIMAL_TYPE_PREFIX)) {
return value;
}
else if (lowerType.startsWith(VARCHAR_TYPE_PREFIX)) {
return stringLiteral(value);
}
else if (lowerType.startsWith(CHAR_TYPE_PREFIX)) {
return stringLiteral(value);
}
// TIMESTAMP, and TIMESTAMP WITH TIME ZONE requires time format which is not available
throw new UnsupportedOperationException("Oracle does not support the type " + type);
}
}
@Override
public String toNativeType(String type)
{
String lowerType = type.toLowerCase(Locale.ENGLISH);
switch (lowerType) {
case TINYINT:
return "number(3)";
case SMALLINT:
return "number(5)";
case INTEGER:
return "number(10)";
case BIGINT:
return "number(19)";
case REAL:
return "binary_float";
case DOUBLE:
return "binary_double";
case VARCHAR:
return "nclob";
case VARBINARY:
return "blob";
case TIMESTAMP:
return "timestamp(3)";
case TIMESTAMP_WITH_TIME_ZONE:
return "timestamp(3) with time zone";
case CHAR:
case DATE:
return lowerType;
default:
if (lowerType.startsWith(DECIMAL_TYPE_PREFIX)) {
return lowerType.replace("decimal", "number");
}
else if (lowerType.startsWith(VARCHAR_TYPE_PREFIX)) {
return lowerType.replace("varchar", "varchar2");
}
else if (lowerType.startsWith(CHAR_TYPE_PREFIX)) {
return lowerType;
}
throw new UnsupportedOperationException("Oracle does not support the type " + type);
}
}
@Override
public String cast(String expression, String type, boolean isSafe, boolean isTypeOnly)
{
String newType = type;
if (type.toLowerCase(Locale.ENGLISH).startsWith(CHAR_TYPE_PREFIX) && isStringLiteral(expression)) {
// CAST('57834' AS char(10)) returns '57834 ' which cause to equality mismatch in Hetu
// If the data type is char(<fixed-length>), the following logic makes sure that the <fixed-length>
// is equal to the length of the input
final int lengthOfQuotes = 2;
newType = CHAR_TYPE_PREFIX + (expression.length() - lengthOfQuotes) + ")";
}
return super.cast(expression, newType, isSafe, isTypeOnly);
}
@Override
public String functionCall(QualifiedName name, boolean isDistinct, List<String> argumentsList,
Optional<String> orderBy, Optional<String> filter, Optional<String> window)
{
String functionName = name.toString();
final int noOfArgs = argumentsList.size();
final boolean isDecorated = window.isPresent() || filter.isPresent() || orderBy.isPresent();
if (noOfArgs == 1 && !isDecorated && !isDistinct && getExtractFieldMap().contains(functionName.toUpperCase(Locale.ENGLISH))) {
try {
Time.ExtractField field = Time.ExtractField.valueOf(functionName.toUpperCase(Locale.ENGLISH));
return extract(argumentsList.get(0), field);
}
catch (IllegalArgumentException ignored) {
throw new IllegalArgumentException("Illegal argument: ", ignored);
}
}
if ("at_timezone".equals(functionName) && noOfArgs == 2) {
return this.atTimeZone(argumentsList.get(0), argumentsList.get(1));
}
return super.functionCall(name, isDistinct, argumentsList, orderBy, filter, window);
}
private Set<String> getExtractFieldMap()
{
Set<String> set = new HashSet<>();
Time.ExtractField[] fields = Time.ExtractField.values();
for (Time.ExtractField field : fields) {
set.add(field.name());
}
return set;
}
/**
* isBlacklistedFunction
*
* @param qualifiedName qualifiedName
* @param noOfArgs noOfArgs
* @return
*/
@Override
public boolean isBlacklistedFunction(String qualifiedName, int noOfArgs)
{
return false;
}
@Override
public String select(List<Selection> symbols, String from)
{
StringJoiner selection = new StringJoiner(", ");
for (Selection symbol : symbols) {
if (symbol.getAlias()
.toLowerCase(Locale.ENGLISH)
.equals(symbol.getExpression().toLowerCase(Locale.ENGLISH))) {
selection.add(symbol.getExpression());
}
else {
selection.add(symbol.getExpression() + " AS " + symbol.getAlias());
}
}
return "(SELECT " + selection.toString() + " FROM " + from + ")";
}
@Override
public String limit(List<Selection> symbols, long count, String from)
{
return select(symbols, from + " WHERE ROWNUM <= " + count);
}
@Override
public String topN(List<Selection> symbols, List<OrderBy> orderings, long count, String from)
{
return limit(symbols, count, sort(symbols, orderings, from));
}
static {
ImmutableMap.Builder<String, Integer> builder = new ImmutableMap.Builder<>();
builder.put("concat", VARIABLE_ARGUMENTS);
BLACKLISTED_FUNCTIONS = builder.build();
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.oracle.optimization;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.type.Type;
import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_QUERY_GENERATOR_FAILURE;
import static io.prestosql.spi.type.StandardTypes.BIGINT;
import static io.prestosql.spi.type.StandardTypes.CHAR;
import static io.prestosql.spi.type.StandardTypes.DATE;
import static io.prestosql.spi.type.StandardTypes.DOUBLE;
import static io.prestosql.spi.type.StandardTypes.INTEGER;
import static io.prestosql.spi.type.StandardTypes.REAL;
import static io.prestosql.spi.type.StandardTypes.SMALLINT;
import static io.prestosql.spi.type.StandardTypes.TIMESTAMP;
import static io.prestosql.spi.type.StandardTypes.TIMESTAMP_WITH_TIME_ZONE;
import static io.prestosql.spi.type.StandardTypes.TINYINT;
import static io.prestosql.spi.type.StandardTypes.VARBINARY;
import static io.prestosql.spi.type.StandardTypes.VARCHAR;
import static java.lang.String.format;
import static java.util.Locale.ENGLISH;
public class OraclePushDownUtils
{
private static final char SINGLE_QUOTE = '\'';
private static final String CHAR_TYPE_PREFIX = "char(";
private static final String DECIMAL_TYPE_PREFIX = "decimal(";
private static final String VARCHAR_TYPE_PREFIX = "varchar(";
private OraclePushDownUtils() {}
public static String getCastExpression(String expression, Type type)
{
String typeName = type.getDisplayName().toLowerCase(ENGLISH);
if (typeName.startsWith(CHAR_TYPE_PREFIX) && isStringLiteral(expression)) {
// CAST('57834' AS char(10)) returns '57834 ' which cause to equality mismatch in Hetu
// If the data type is char(<fixed-length>), the following logic makes sure that the <fixed-length>
// is equal to the length of the input
final int lengthOfQuotes = 2;
typeName = CHAR_TYPE_PREFIX + (expression.length() - lengthOfQuotes) + ")";
}
return format("CAST(%s AS %s)", expression, toNativeType(typeName));
}
public static String toNativeType(String type)
{
String lowerType = type.toLowerCase(ENGLISH);
switch (lowerType) {
case TINYINT:
return "number(3)";
case SMALLINT:
return "number(5)";
case INTEGER:
return "number(10)";
case BIGINT:
return "number(19)";
case REAL:
return "binary_float";
case DOUBLE:
return "binary_double";
case VARCHAR:
return "nclob";
case VARBINARY:
return "blob";
case TIMESTAMP:
return "timestamp(3)";
case TIMESTAMP_WITH_TIME_ZONE:
return "timestamp(3) with time zone";
case CHAR:
case DATE:
return lowerType;
default:
if (lowerType.startsWith(DECIMAL_TYPE_PREFIX)) {
return lowerType.replace("decimal", "number");
}
else if (lowerType.startsWith(VARCHAR_TYPE_PREFIX)) {
return lowerType.replace("varchar", "varchar2");
}
else if (lowerType.startsWith(CHAR_TYPE_PREFIX)) {
return lowerType;
}
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "Oracle does not support the type " + type);
}
}
private static boolean isStringLiteral(String expression)
{
char first = expression.charAt(0);
char last = expression.charAt(expression.length() - 1);
// In Hetu, identifier names can be surrounded byt double quotes
return first == SINGLE_QUOTE && last == SINGLE_QUOTE;
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.oracle.optimization;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcQueryGenerator;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownParameter;
import io.prestosql.spi.relation.RowExpressionService;
public class OracleQueryGenerator
extends BaseJdbcQueryGenerator
{
public OracleQueryGenerator(RowExpressionService rowExpressionService, JdbcPushDownParameter pushDownParameter)
{
super(pushDownParameter, new OracleRowExpressionConverter(rowExpressionService), new OracleSqlStatementWriter(pushDownParameter));
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.oracle.optimization;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcRowExpressionConverter;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.ConstantExpression;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.relation.SpecialForm;
import io.prestosql.spi.sql.expression.Time;
import io.prestosql.spi.type.CharType;
import io.prestosql.spi.type.DateType;
import io.prestosql.spi.type.DecimalType;
import io.prestosql.spi.type.DoubleType;
import io.prestosql.spi.type.RealType;
import io.prestosql.spi.type.Type;
import io.prestosql.spi.type.VarbinaryType;
import io.prestosql.spi.type.VarcharType;
import java.util.Arrays;
import java.util.Set;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static io.hetu.core.plugin.oracle.optimization.OraclePushDownUtils.getCastExpression;
import static io.prestosql.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT;
import static io.prestosql.spi.StandardErrorCode.NOT_SUPPORTED;
import static io.prestosql.spi.function.StandardFunctionUtils.isArrayConstructor;
import static io.prestosql.spi.function.StandardFunctionUtils.isCastFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isSubscriptFunction;
import static io.prestosql.spi.relation.SpecialForm.Form.IF;
import static java.lang.String.format;
import static java.util.Locale.ENGLISH;
public class OracleRowExpressionConverter
extends BaseJdbcRowExpressionConverter
{
private static final String AT_TIMEZONE_FUNCTION_NAME = "at_timezone";
private static final Set<String> timeExtractFields = Arrays.stream(Time.ExtractField.values())
.map(Time.ExtractField::name)
.map(String::toLowerCase)
.collect(toImmutableSet());
public OracleRowExpressionConverter(RowExpressionService rowExpressionService)
{
super(rowExpressionService);
}
@Override
public String visitCall(CallExpression call, Void context)
{
Signature signature = call.getSignature();
String functionName = call.getSignature().getName().toLowerCase(ENGLISH);
if (timeExtractFields.contains(functionName)) {
if (call.getArguments().size() == 1) {
try {
Time.ExtractField field = Time.ExtractField.valueOf(functionName.toUpperCase(ENGLISH));
return format("EXTRACT(%s FROM %s)", field, call.getArguments().get(0).accept(this, null));
}
catch (IllegalArgumentException e) {
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Illegal argument: " + e);
}
}
else {
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Illegal argument num of function " + functionName);
}
}
if (functionName.equals(AT_TIMEZONE_FUNCTION_NAME)) {
if (call.getArguments().size() == 2) {
return format("%s AT TIME ZONE %s",
call.getArguments().get(0).accept(this, null),
call.getArguments().get(1).accept(this, null));
}
else {
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Illegal argument num of function " + functionName);
}
}
if (isArrayConstructor(signature)) {
throw new PrestoException(NOT_SUPPORTED, "Oracle connector does not support array constructor");
}
if (isSubscriptFunction(signature)) {
throw new PrestoException(NOT_SUPPORTED, "Oracle connector does not support subscript expression");
}
if (isCastFunction(signature)) {
// deal with literal, when generic literal expression translate to rowExpression, it will be
// translated to a 'CAST' rowExpression with a varchar type 'CONSTANT' rowExpression, in some
// case, 'CAST' is superfluous
RowExpression argument = call.getArguments().get(0);
Type type = call.getType();
if (argument instanceof ConstantExpression && argument.getType() instanceof VarcharType) {
String value = argument.accept(this, null);
if (type instanceof DateType) {
return format("date %s", value);
}
if (type instanceof VarcharType
|| type instanceof CharType
|| type instanceof VarbinaryType
|| type instanceof DecimalType
|| type instanceof RealType
|| type instanceof DoubleType) {
return value;
}
}
if (call.getType().getDisplayName().equals(LIKE_PATTERN_NAME)) {
return call.getArguments().get(0).accept(this, null);
}
return getCastExpression(call.getArguments().get(0).accept(this, null), call.getType());
}
return super.visitCall(call, context);
}
@Override
public String visitSpecialForm(SpecialForm specialForm, Void context)
{
// Oracle sql does not support if, convert IF to [case ... when ... else] expression
if (specialForm.getForm().equals(IF)) {
return format("(CASE WHEN %s THEN %s ELSE %s END)",
specialForm.getArguments().get(0).accept(this, null),
specialForm.getArguments().get(1).accept(this, null),
specialForm.getArguments().get(2).accept(this, null));
}
return super.visitSpecialForm(specialForm, context);
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.oracle.optimization;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcSqlStatementWriter;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownParameter;
public class OracleSqlStatementWriter
extends BaseJdbcSqlStatementWriter
{
public OracleSqlStatementWriter(JdbcPushDownParameter pushDownParameter)
{
super(pushDownParameter);
}
/**
* Oracle doesn't support limit, use [select * from table where rownum <= count],
* this must add at last of sql expression
*
* @param table table
* @param count limit count
* @return limit statement
*/
@Override
public String limit(String table, long count)
{
return "SELECT * FROM (" + table + ") WHERE ROWNUM <= " + count;
}
}

View File

@ -43,15 +43,14 @@ public class TestOracleConfig
@Test
public void testOraclePropertyMappings()
{
Map<String, String> properties = new ImmutableMap.Builder<String, String>().put(
"hetu.query.pushdown.enabled", "false")
Map<String, String> properties = new ImmutableMap.Builder<String, String>()
.put("oracle.number.default-scale", "2")
.put("oracle.number.rounding-mode", "DOWN")
.put("unsupported-type.handling-strategy", "CONVERT_TO_VARCHAR")
.put("oracle.synonyms.enabled", "true")
.build();
OracleConfig expected = new OracleConfig().setQueryPushDownEnabled(false)
OracleConfig expected = new OracleConfig()
.setNumberDefaultScale(NUMBER_DEFAULT_SCALE)
.setRoundingMode(RoundingMode.DOWN)
.setUnsupportedTypeHandling(UnsupportedTypeHandling.CONVERT_TO_VARCHAR)

View File

@ -1,807 +0,0 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.hetu.core.plugin.oracle;
import com.google.common.collect.ImmutableSet;
import io.airlift.log.Logger;
import io.prestosql.plugin.jdbc.BaseJdbcConfig;
import io.prestosql.plugin.jdbc.ColumnMapping;
import io.prestosql.plugin.jdbc.ConnectionFactory;
import io.prestosql.plugin.jdbc.DriverConnectionFactory;
import io.prestosql.plugin.jdbc.JdbcClient;
import io.prestosql.plugin.jdbc.JdbcHandleResolver;
import io.prestosql.plugin.jdbc.JdbcIdentity;
import io.prestosql.plugin.jdbc.JdbcMetadata;
import io.prestosql.plugin.jdbc.JdbcRecordSetProvider;
import io.prestosql.plugin.jdbc.JdbcSplitManager;
import io.prestosql.plugin.jdbc.JdbcTableHandle;
import io.prestosql.plugin.jdbc.JdbcTypeHandle;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.connector.Connector;
import io.prestosql.spi.connector.ConnectorContext;
import io.prestosql.spi.connector.ConnectorFactory;
import io.prestosql.spi.connector.ConnectorHandleResolver;
import io.prestosql.spi.connector.ConnectorMetadata;
import io.prestosql.spi.connector.ConnectorRecordSetProvider;
import io.prestosql.spi.connector.ConnectorSession;
import io.prestosql.spi.connector.ConnectorSplitManager;
import io.prestosql.spi.connector.ConnectorTransactionHandle;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.transaction.IsolationLevel;
import io.prestosql.spi.type.BooleanType;
import io.prestosql.spi.type.CharType;
import io.prestosql.spi.type.DecimalType;
import io.prestosql.spi.type.VarcharType;
import io.prestosql.sql.tree.BetweenPredicate;
import io.prestosql.sql.tree.Cast;
import io.prestosql.sql.tree.DecimalLiteral;
import io.prestosql.sql.tree.ExistsPredicate;
import io.prestosql.sql.tree.Expression;
import io.prestosql.sql.tree.FunctionCall;
import io.prestosql.sql.tree.GenericLiteral;
import io.prestosql.sql.tree.InListExpression;
import io.prestosql.sql.tree.IsNotNullPredicate;
import io.prestosql.sql.tree.IsNullPredicate;
import io.prestosql.sql.tree.LongLiteral;
import io.prestosql.sql.tree.NotExpression;
import io.prestosql.sql.tree.NullLiteral;
import io.prestosql.sql.tree.QualifiedName;
import io.prestosql.sql.tree.SubqueryExpression;
import io.prestosql.sql.tree.SymbolReference;
import io.prestosql.sql.tree.TryExpression;
import io.prestosql.tests.AbstractTestSqlQueryWriter;
import org.intellij.lang.annotations.Language;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static io.prestosql.plugin.jdbc.DriverConnectionFactory.basicConnectionProperties;
import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_ERROR;
import static io.prestosql.spi.type.BigintType.BIGINT;
import static io.prestosql.spi.type.DateType.DATE;
import static io.prestosql.spi.type.DoubleType.DOUBLE;
import static io.prestosql.spi.type.IntegerType.INTEGER;
import static io.prestosql.spi.type.RealType.REAL;
import static io.prestosql.spi.type.SmallintType.SMALLINT;
import static io.prestosql.spi.type.TimestampType.TIMESTAMP;
import static io.prestosql.spi.type.TimestampWithTimeZoneType.TIMESTAMP_WITH_TIME_ZONE;
import static io.prestosql.spi.type.TinyintType.TINYINT;
import static io.prestosql.spi.type.VarbinaryType.VARBINARY;
import static io.prestosql.sql.QueryUtil.selectList;
import static io.prestosql.sql.QueryUtil.simpleQuery;
import static io.prestosql.testing.TestingSession.testSessionBuilder;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
/**
* TestOracleSqlQueryWriter
*
* @since 2019-07-08
*/
public class TestOracleSqlQueryWriter
extends AbstractTestSqlQueryWriter
{
private static final Logger LOGGER = Logger.get(TestOracleSqlQueryWriter.class);
private static final ConnectorSession SESSION = testSessionBuilder().build().toConnectorSession();
private static final String TEST = "test";
private static final String ORDERS = "orders";
private static final String CUSTOMER = "customer";
private static final String LINEITEM = "lineitem";
private static final String TIMESTAMPSTR = "timestamp";
private static final String INSERT = "INSERT INTO test.numbers(text, text_short, value) VALUES ";
private static final String STR_DECIMAL = "decimal";
private static final int NUMBER_4 = 4;
private static final int NUMBER_2 = 2;
private static final int NUMBER_13 = 13;
private static final JdbcTypeHandle JDBC_BOOLEAN = new JdbcTypeHandle(Types.BOOLEAN,
Optional.of("boolean"), 1, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_SMALLINT = new JdbcTypeHandle(Types.SMALLINT,
Optional.of("smallint"), 1, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_TINYINT = new JdbcTypeHandle(Types.TINYINT,
Optional.of("tinyint"), 2, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_INTEGER = new JdbcTypeHandle(Types.INTEGER,
Optional.of("integer"), 4, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_BIGINT = new JdbcTypeHandle(Types.BIGINT,
Optional.of("bigint"), 8, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_REAL = new JdbcTypeHandle(Types.REAL,
Optional.of("real"), 8, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_DOUBLE = new JdbcTypeHandle(Types.DOUBLE,
Optional.of("double precision"), 8, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_CHAR = new JdbcTypeHandle(Types.CHAR,
Optional.of("char"), 10, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_VARCHAR = new JdbcTypeHandle(Types.VARCHAR,
Optional.of("varchar"), 10, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_DATE = new JdbcTypeHandle(Types.DATE,
Optional.of("date"), 8, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_TIME = new JdbcTypeHandle(Types.TIME,
Optional.of("time"), 4, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_TIMESTAMP = new JdbcTypeHandle(Types.TIMESTAMP,
Optional.of(TIMESTAMPSTR), 8, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_DECIMAL_30 = new JdbcTypeHandle(Types.DECIMAL,
Optional.of(STR_DECIMAL), 3, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_DECIMAL_50 = new JdbcTypeHandle(Types.DECIMAL,
Optional.of(STR_DECIMAL), 5, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_DECIMAL_100 = new JdbcTypeHandle(Types.DECIMAL,
Optional.of(STR_DECIMAL), 10, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_DECIMAL_190 = new JdbcTypeHandle(Types.DECIMAL,
Optional.of(STR_DECIMAL), 19, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_DECIMAL_0127 = new JdbcTypeHandle(Types.DECIMAL,
Optional.of(STR_DECIMAL), 0, -3, Optional.empty());
private static final JdbcTypeHandle JDBC_DECIMAL_384 = new JdbcTypeHandle(Types.DECIMAL,
Optional.of(STR_DECIMAL), 12, -4, Optional.empty());
private static final JdbcTypeHandle JDBC_CLOB_OR_NCLOB = new JdbcTypeHandle(OracleTypes.CLOB_OR_NCLOB,
Optional.of("clob"), 12, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_LONG_RAW = new JdbcTypeHandle(OracleTypes.LONG_RAW,
Optional.of("long_rwa"), 12, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_LONG = new JdbcTypeHandle(OracleTypes.LONG,
Optional.of("long"), 1, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_TIMESTAMP_STRING = new JdbcTypeHandle(
OracleTypes.TIMESTAMP_WITH_TIMEZONE_OR_NCLOB_OR_NVARCHAR2,
Optional.of(TIMESTAMPSTR), 12, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_TIMESTAMP_NCLOB_STRING = new JdbcTypeHandle(
OracleTypes.TIMESTAMP_WITH_TIMEZONE_OR_NCLOB_OR_NVARCHAR2,
Optional.of("NCLOB"), 12, 0, Optional.empty());
private static final JdbcTypeHandle JDBC_FLOAT = new JdbcTypeHandle(OracleTypes.NUMBER_OR_FLOAT,
Optional.of("float"), 127, -127, Optional.empty());
private static final JdbcTypeHandle JDBC_TIMESTAMP6_WITH_TIMEZONE = new JdbcTypeHandle(
OracleTypes.TIMESTAMP6_WITH_TIMEZONE,
Optional.of(TIMESTAMPSTR), 12, 0, Optional.empty());
private OracleClient oracleClient;
private Connection connection;
private TestingOracleServer oracleServer;
private ConnectorFactory connectorFactory;
private List<String> tables = new ArrayList<>(1);
/**
* Create TestOracleSqlQueryWriter
*/
protected TestOracleSqlQueryWriter()
{
super(new OracleSqlQueryWriter(), "oracle", TEST);
}
/**
* Setup the database
*/
@BeforeClass
public void setup()
{
try {
oracleServer = new TestingOracleServer();
BaseJdbcConfig jdbcConfig = new BaseJdbcConfig();
jdbcConfig.setConnectionUrl(oracleServer.getJdbcUrl());
jdbcConfig.setConnectionUser(TEST);
jdbcConfig.setConnectionPassword(TEST);
Driver driver;
try {
driver = (Driver) Class.forName(Constants.ORACLE_JDBC_DRIVER_CLASS_NAME).getConstructor(((Class<?>[]) null)).newInstance();
}
catch (InstantiationException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new PrestoException(JDBC_ERROR, e);
}
ConnectionFactory connectionFactory = new DriverConnectionFactory(driver,
jdbcConfig.getConnectionUrl(),
Optional.ofNullable(jdbcConfig.getConnectionUser()),
Optional.ofNullable(jdbcConfig.getConnectionPassword()),
basicConnectionProperties(jdbcConfig));
OracleConfig oracleConfig = new OracleConfig();
oracleClient = new OracleClient(jdbcConfig, oracleConfig, connectionFactory);
this.connectorFactory = new OracleJdbcConnectorFactory(oracleClient, "oracle");
this.connection = connectionFactory.openConnection(JdbcIdentity.from(SESSION));
createTables();
}
catch (SQLException e) {
throw new RuntimeException(e);
}
super.setup();
}
private void createTables()
throws SQLException
{
connection.createStatement().execute(buildCreateTableSql(ORDERS,
"(orderkey int NOT NULL primary key, "
+ "custkey int NOT NULL, orderstatus varchar(1) NOT NULL, totalprice number(10) NOT NULL, "
+ "orderdate date NOT NULL, orderpriority varchar(15) NOT NULL, clerk varchar(15) NOT NULL, "
+ "shippriority int NOT NULL, \"COMMENT\" varchar(79) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql(CUSTOMER,
"(custkey int NOT NULL primary key, "
+ "name varchar(25) NOT NULL, address varchar(40) NOT NULL, nationkey int NOT NULL, "
+ "phone varchar(15) NOT NULL, acctbal number(10) NOT NULL, mktsegment varchar(10) NOT NULL, "
+ "\"COMMENT\" varchar(117) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("supplier",
"(suppkey int NOT NULL primary key, "
+ "name varchar(25) NOT NULL, address varchar(40) NOT NULL, "
+ "nationkey int NOT NULL, phone varchar(15) NOT NULL, acctbal number(10) NOT NULL, "
+ "\"COMMENT\" varchar(101) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("region",
"(regionkey int NOT NULL primary key, name varchar(25) NOT NULL, "
+ "\"COMMENT\" varchar(152) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql(LINEITEM,
"(orderkey int NOT NULL primary key, "
+ "partkey int NOT NULL, suppkey int NOT NULL, linenumber int NOT NULL, quantity number(10) NOT NULL,"
+ " extendedprice number(10) NOT NULL, discount number(10) NOT NULL, tax number(10) NOT NULL, "
+ "returnflag varchar(1) NOT NULL, linestatus varchar(1) NOT NULL, shipdate date NOT NULL, "
+ "commitdate date NOT NULL, receiptdate date NOT NULL, shipinstruct varchar(25) NOT NULL, "
+ "shipmode varchar(10) NOT NULL, \"COMMENT\" varchar(44) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("nation",
"(nationkey int NOT NULL primary key, name varchar(25) NOT NULL, regionkey int NOT NULL,"
+ "\"COMMENT\" varchar(152) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("part",
"(partkey int NOT NULL primary key, name varchar(55) NOT NULL, mfgr varchar(25) NOT NULL, "
+ "brand varchar(10) NOT NULL, TYPE varchar(25) NOT NULL, \"SIZE\" int NOT NULL, "
+ "container varchar(10) NOT NULL,"
+ " retailprice number(10) NOT NULL, \"COMMENT\" varchar(23) NOT NULL)"));
connection.createStatement().execute(buildCreateTableSql("partsupp",
"(partkey int NOT NULL primary key, suppkey int NOT NULL, availqty int NOT NULL, "
+ "supplycost number(10) NOT NULL, \"COMMENT\" varchar(199) NOT NULL)"));
createTablesForClient();
}
private void createTablesForClient()
throws SQLException
{
connection.createStatement().execute("CREATE TABLE test.numbers(text varchar(20) primary key, "
+ "text_short varchar(32), value int)");
connection.createStatement().execute(INSERT + "('one', 'one', 1)");
connection.createStatement().execute(INSERT + "('two', 'two', 2)");
connection.createStatement().execute(INSERT + "('three', 'three', 3)");
connection.createStatement().execute(INSERT + "('ten', 'ten', 10)");
connection.createStatement().execute(INSERT + "('eleven', 'eleven', 11)");
connection.createStatement().execute(INSERT + "('twelve', 'twelve', 12)");
connection.createStatement().execute("CREATE TABLE test.student(id varchar(20) primary key)");
connection.createStatement().execute("CREATE TABLE test.num_ers(te_t varchar(20) primary key,"
+ " \"VA%UE\" int)");
connection.createStatement().execute("CREATE TABLE test.table_with_float_col(col1 int primary key,"
+ " col2 int, col3 int, col4 int)");
connection.createStatement().execute("CREATE TABLE test.number2(text varchar(20) primary key, "
+ "text_short varchar(32), value int)");
}
private String buildCreateTableSql(String tableName, String columnInfo)
{
tables.add(tableName);
return "CREATE TABLE " + TEST + "." + tableName + " " + columnInfo;
}
/**
* Clean the resources
*/
@AfterClass(alwaysRun = true)
public void clean()
{
oracleServer.close();
super.clean();
}
/**
* testMetadata
*/
@Test
public void testMetadata()
{
JdbcIdentity identity = JdbcIdentity.from(SESSION);
assertTrue(oracleClient.getSchemaNames(identity).contains(TEST));
}
/**
* testListSchema
*/
@Test
public void testListSchema()
{
assertEquals(ImmutableSet.copyOf(oracleClient.listSchemas(connection)).contains(TEST), true);
}
/**
* testGetTableHandle
*/
@Test
public void testGetTableHandle()
{
JdbcIdentity identity = JdbcIdentity.from(SESSION);
Optional<JdbcTableHandle> tableHandle = oracleClient.getTableHandle(identity,
new SchemaTableName(Constants.ORACLE, Constants.NUMBERS));
assertEquals(oracleClient.getTableHandle(identity,
new SchemaTableName(Constants.ORACLE, Constants.NUMBERS)), tableHandle);
assertEquals(oracleClient.getTableHandle(identity,
new SchemaTableName(Constants.ORACLE, "dept")), Optional.empty());
assertEquals(oracleClient.getTableHandle(identity,
new SchemaTableName(Constants.ORACLE, "darren")), Optional.empty());
assertEquals(oracleClient.getTableHandle(identity,
new SchemaTableName("mysql", "dept")), Optional.empty());
}
/**
* testGetTableNames
*/
@Test
public void testGetTableNames()
{
JdbcIdentity identity = JdbcIdentity.from(SESSION);
assertEquals(oracleClient.getTableNames(identity, Optional.of(Constants.ORACLE)).size(), NUMBER_13);
}
/**
* testGetTableHandleException
*/
@Test
public void testGetTableHandleException()
{
JdbcIdentity identity = JdbcIdentity.from(SESSION);
assertEquals(oracleClient.getTableHandle(identity,
new SchemaTableName(Constants.ORACLE, "notexist_table")), Optional.empty());
}
/**
* testGetTableHandleGetConnectionException
*/
@Test
public void testGetTableHandleGetConnectionException()
{
JdbcIdentity identity = JdbcIdentity.from(SESSION);
oracleClient.getTableHandle(identity, new SchemaTableName(Constants.ORACLE, Constants.NUMBERS));
}
/**
* testRenameTableException
*/
@Test(expectedExceptions = RuntimeException.class)
public void testRenameTableException()
{
JdbcIdentity identity = JdbcIdentity.from(SESSION);
SchemaTableName newTableName = new SchemaTableName("schema_test", "new_student");
Optional<JdbcTableHandle> tableHandle = oracleClient.getTableHandle(identity,
new SchemaTableName(Constants.ORACLE, Constants.NUMBERS));
try {
oracleClient.renameTable(identity, tableHandle.get(), newTableName);
}
// CHECKSTYLE:OFF:IllegalCatch
catch (Exception e) {
// CHECKSTYLE:ON:IllegalCatch
throw new RuntimeException(e);
}
assertEquals(oracleClient.getTableNames(identity, Optional.of(Constants.ORACLE)).size(), NUMBER_13);
}
/**
* testGenerateTempTableName
*/
@Test
public void testGenerateTempTableName()
{
String tmpName = oracleClient.generateTemporaryTableName();
assertNotNull(tmpName);
}
/**
* Test disabled support for LONG oracle type
*/
@Test(expectedExceptions = UnsupportedOperationException.class)
public void testLongTypeDisabledSupport()
{
oracleClient.toPrestoType(SESSION, connection, JDBC_LONG);
}
/**
* testtoHetuType
*/
@Test
public void testToHetuType()
{
Optional<ColumnMapping> columnMapping = Optional.empty();
columnMapping = oracleClient.toPrestoType(SESSION, connection, JDBC_BIGINT);
oracleClient.toPrestoType(SESSION, connection, JDBC_SMALLINT);
oracleClient.toPrestoType(SESSION, connection, JDBC_BOOLEAN);
oracleClient.toPrestoType(SESSION, connection, JDBC_TINYINT);
oracleClient.toPrestoType(SESSION, connection, JDBC_INTEGER);
oracleClient.toPrestoType(SESSION, connection, JDBC_REAL);
oracleClient.toPrestoType(SESSION, connection, JDBC_DOUBLE);
oracleClient.toPrestoType(SESSION, connection, JDBC_CHAR);
oracleClient.toPrestoType(SESSION, connection, JDBC_VARCHAR);
oracleClient.toPrestoType(SESSION, connection, JDBC_DATE);
oracleClient.toPrestoType(SESSION, connection, JDBC_TIME);
oracleClient.toPrestoType(SESSION, connection, JDBC_TIMESTAMP);
oracleClient.toPrestoType(SESSION, connection, JDBC_DECIMAL_30);
oracleClient.toPrestoType(SESSION, connection, JDBC_DECIMAL_50);
oracleClient.toPrestoType(SESSION, connection, JDBC_DECIMAL_100);
oracleClient.toPrestoType(SESSION, connection, JDBC_DECIMAL_190);
oracleClient.toPrestoType(SESSION, connection, JDBC_DECIMAL_0127);
oracleClient.toPrestoType(SESSION, connection, JDBC_DECIMAL_384);
oracleClient.toPrestoType(SESSION, connection, JDBC_CLOB_OR_NCLOB);
oracleClient.toPrestoType(SESSION, connection, JDBC_LONG_RAW);
oracleClient.toPrestoType(SESSION, connection, JDBC_TIMESTAMP_NCLOB_STRING);
oracleClient.toPrestoType(SESSION, connection, JDBC_FLOAT);
// we do not support these following type for we remove oracle.sql.TIMESTAMPTZ;
// JDBC_TIMESTAMP_STRING, JDBC_TIMESTAMP6_WITH_TIMEZONE
}
/**
* testToWriteMapping
*/
@Test
public void testToWriteMapping()
{
oracleClient.toWriteMapping(SESSION, VarcharType.VARCHAR);
oracleClient.toWriteMapping(SESSION, CharType.createCharType(NUMBER_4));
oracleClient.toWriteMapping(SESSION, DecimalType.createDecimalType(NUMBER_4, NUMBER_2));
oracleClient.toWriteMapping(SESSION, BooleanType.BOOLEAN);
oracleClient.toWriteMapping(SESSION, INTEGER);
oracleClient.toWriteMapping(SESSION, SMALLINT);
oracleClient.toWriteMapping(SESSION, BIGINT);
oracleClient.toWriteMapping(SESSION, TINYINT);
oracleClient.toWriteMapping(SESSION, REAL);
oracleClient.toWriteMapping(SESSION, DOUBLE);
oracleClient.toWriteMapping(SESSION, VARBINARY);
oracleClient.toWriteMapping(SESSION, TIMESTAMP);
oracleClient.toWriteMapping(SESSION, TIMESTAMP_WITH_TIME_ZONE);
oracleClient.toWriteMapping(SESSION, DATE);
}
/**
* getConnectorFactory
*
* @return connection factory
*/
@Override
protected Optional<ConnectorFactory> getConnectorFactory()
{
return Optional.of(this.connectorFactory);
}
@Override
protected void assertStatement(@Language("SQL") String query, String... keywords)
{
super.assertStatement(query, keywords);
}
/**
* testLambdaExpression
*/
@Test(expectedExceptions = UnsupportedOperationException.class)
@Override
public void testLambdaExpression()
{
super.testLambdaExpression();
}
/**
* testDecimalLiteralExpression
*/
@Override
public void testDecimalLiteralExpression()
{
LOGGER.info("Testing Hetu decimal literal expressions");
assertExpression(new DecimalLiteral("12.34"), "'12.34'");
assertExpression(new DecimalLiteral("12."), "'12.'");
assertExpression(new DecimalLiteral("12"), "'12'");
assertExpression(new DecimalLiteral(".34"), "'.34'");
assertExpression(new DecimalLiteral("+12.34"), "'+12.34'");
assertExpression(new DecimalLiteral("+12"), "'+12'");
assertExpression(new DecimalLiteral("-12.34"), "'-12.34'");
assertExpression(new DecimalLiteral("-12"), "'-12'");
assertExpression(new DecimalLiteral("+.34"), "'+.34'");
assertExpression(new DecimalLiteral("-.34"), "'-.34'");
}
/**
* testSelectStatement
*/
@Test
public void testSelectStatement()
{
LOGGER.info("Testing select statement");
@Language("SQL")
String query = "SELECT (totalprice + 2) AS new_price FROM orders";
assertStatement(query, "SELECT", "totalprice", "+", "2", "FROM", "orders");
}
/**
* testIntermediateFunctions
*/
@Test
public void testIntermediateFunctions()
{
LOGGER.info("Testing Hetu current time in a statement");
// current_time is converted to $literal$time with time zone
@Language("SQL")
String query = "SELECT current_time FROM customer";
assertStatement(query, "SELECT", "FROM", CUSTOMER);
// interval '29' day is converted to $literal$interval day to second
// timestamp is converted to $literal$timestamp
query = "SELECT * FROM orders WHERE orderdate - interval '29' day > timestamp '2012-10-31 01:00 UTC'";
assertStatement(query, "SELECT", "FROM", ORDERS, "WHERE");
}
/**
* testAggregationStatements
*/
@Test
public void testAggregationStatements()
{
LOGGER.info("Testing aggregation statements");
String query = "SELECT * FROM " + " (SELECT max(totalprice) AS price, o.orderkey AS orderkey FROM "
+ " customer c JOIN orders o ON c.custkey=o.custkey GROUP BY orderpriority, orderkey) t1 "
+ " LEFT JOIN lineitem l ON substr(cast(t1.orderkey AS VARCHAR), 0, 2)=cast(t1.orderkey AS VARCHAR) LIMIT 20";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "INNER JOIN", ORDERS, "GROUP BY", "LEFT JOIN",
LINEITEM, "WHERE ROWNUM <= 20");
query = "SELECT * FROM " + " (SELECT max(totalprice) AS price, o.orderkey AS orderkey FROM"
+ " customer c join orders o ON c.custkey=o.custkey GROUP BY orderpriority, orderkey HAVING orderkey>100) t1"
+ " LEFT JOIN lineitem l ON substr(cast(t1.orderkey AS VARCHAR), 0, 2)=cast(t1.orderkey AS VARCHAR) LIMIT 10";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "INNER JOIN", ORDERS, "WHERE", ">", "100", "GROUP BY",
"LEFT JOIN", LINEITEM, "WHERE ROWNUM <= 10");
}
/**
* testExtractStatement
*/
@Test
public void testExtractStatement()
{
LOGGER.info("Testing extract statement");
String query = "SELECT extract(YEAR FROM orderdate) AS year FROM orders LIMIT 10";
assertStatement(query, "SELECT", "EXTRACT", "YEAR FROM orderdate", "FROM", ORDERS, "WHERE ROWNUM <= 10");
}
/**
* testLambdaStatement
*/
@Test
public void testLambdaStatement()
{
LOGGER.info("Testing lambda in a statement");
String query = "SELECT filter(split(comment, ' '), x -> length(x) > 2) FROM customer LIMIT 10";
assertStatement(query);
}
/**
* testJoinStatements
*/
@Override
public void testJoinStatements()
{
LOGGER.info("Testing join statements");
String query = "SELECT c.name FROM customer c LEFT JOIN orders o ON c.custkey=o.custkey";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "LEFT JOIN", ORDERS, "ON",
"table0.custkey = table1.custkey_0");
query = "SELECT c.name FROM customer c RIGHT JOIN orders o ON c.custkey=o.custkey";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "RIGHT JOIN", ORDERS, "ON",
"table0.custkey = table1.custkey_0");
query = "SELECT c.name FROM customer c JOIN orders o ON c.custkey=o.custkey";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "INNER JOIN", ORDERS, "ON",
"table0.custkey = table1.custkey_0");
query = "SELECT c.name FROM customer c FULL JOIN orders o ON c.custkey=o.custkey";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "FULL JOIN", ORDERS, "ON",
"table0.custkey = table1.custkey_0");
query = "SELECT c.name FROM customer c JOIN orders o USING (custkey)";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "INNER JOIN", ORDERS, "ON",
"table0.custkey = table1.custkey_0");
query = "SELECT c.name FROM customer c CROSS JOIN orders LIMIT 10";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "CROSS JOIN", ORDERS, "WHERE ROWNUM <= 10");
// Predicate push down changes left join to inner join
query = "SELECT c.name FROM customer c LEFT JOIN orders o ON c.custkey=o.custkey WHERE o.totalprice > 10";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "INNER JOIN", ORDERS, "WHERE", ">", "10");
query
= "SELECT c.name FROM customer c RIGHT JOIN orders o ON c.custkey=o.custkey WHERE o.totalprice > 10 AND o.orderstatus='F'";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "RIGHT JOIN", ORDERS, "WHERE", ">", "10", "AND", "'f'");
query
= "SELECT c.name FROM customer c RIGHT JOIN orders o ON c.custkey=o.custkey WHERE o.totalprice > 10 AND o.orderstatus='F' ORDER BY cast(c.name AS VARCHAR) LIMIT 10";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "RIGHT JOIN", ORDERS, "WHERE", ">", "10", "AND", "'f'",
"ORDER BY", "WHERE ROWNUM <= 10");
query = "SELECT * " + "FROM (SELECT max(totalprice) AS price, o.orderkey AS orderkey "
+ "FROM customer c JOIN orders o ON c.custkey=o.custkey GROUP BY orderpriority, orderkey LIMIT 10) t1 LEFT JOIN lineitem l ON t1.orderkey=l.orderkey";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "INNER JOIN", ORDERS, "GROUP BY", "WHERE ROWNUM <= 10",
"LEFT JOIN", LINEITEM);
query
= "SELECT c.name FROM customer c RIGHT JOIN orders o ON c.custkey=o.custkey WHERE o.totalprice > 10 AND o.orderstatus='F'";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "RIGHT JOIN", ORDERS, "WHERE", ">", "10", "AND", "'f'");
query =
"SELECT t1.custkey1, t2.custkey, t2.name FROM (SELECT c.custkey AS custkey1, o.custkey AS custkey2 FROM "
+ " customer c INNER JOIN orders o ON c.custkey = o.custkey) t1 "
+ " LEFT JOIN customer t2 ON t1.custkey1=t2.custkey LIMIT 10";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "INNER JOIN", ORDERS, "LEFT JOIN", CUSTOMER);
query = "SELECT * FROM orders o LEFT JOIN lineitem l USING (orderkey) LEFT JOIN "
+ " customer c using (custkey) LEFT JOIN supplier s USING (nationkey) LEFT JOIN "
+ " partsupp ps ON ps.suppkey=s.suppkey JOIN part pt ON pt.partkey=ps.partkey LIMIT 20";
assertStatement(query, "SELECT", "FROM", ORDERS, "LEFT JOIN", LINEITEM, "LEFT JOIN", CUSTOMER,
"LEFT JOIN", "supplier", "LEFT JOIN", "partsupp", "INNER JOIN", "part", "WHERE ROWNUM <= 20");
query = "SELECT c_count, count(*) AS custdist FROM " + " (SELECT c.custkey, count(o.orderkey) FROM "
+ " customer c LEFT OUTER JOIN orders o ON c.custkey = o.custkey AND o.comment NOT LIKE '%[WORD1]%[WORD2]%' GROUP BY c.custkey) AS c_orders(c_custkey, c_count) "
+ " GROUP BY c_count ORDER BY custdist DESC, c_count DESC";
assertStatement(query, "SELECT", "FROM", CUSTOMER, "LEFT JOIN", ORDERS, "NOT", "LIKE", "%", "WORD1", "%",
"WORD2", "GROUP BY", "ORDER BY", "DESC");
}
@Override
public void testCastExpression()
{
LOGGER.info("Testing cast expressions");
assertExpression(new Cast(new NullLiteral(), "varchar(42)"), "CAST(null AS varchar2(42))");
assertExpression(new Cast(new NullLiteral(), "varchar"), "CAST(null AS nclob)");
assertExpression(new Cast(new NullLiteral(), "BIGINT"), "CAST(null AS number(19))");
assertExpression(new Cast(new NullLiteral(), "double"), "CAST(null AS binary_double)");
assertExpression(new Cast(new NullLiteral(), "DOUBLE"), "CAST(null AS binary_double)");
assertExpression(new Cast(new NullLiteral(), "date"), "CAST(null AS date)");
assertExpression(new Cast(new NullLiteral(), TIMESTAMPSTR), "CAST(null AS timestamp(3))");
assertExpression(new Cast(new NullLiteral(), "timestamp with time zone"),
"CAST(null AS timestamp(3) with time zone)");
}
@Override
public void testFunctionCallAndTryExpression()
{
LOGGER.info("Testing function call and try expressions");
FunctionCall functionCall = new FunctionCall(QualifiedName.of("strpos"),
list(stringLiteral("b"), stringLiteral("a")));
TryExpression tryExpression = new TryExpression(functionCall);
assertExpression(functionCall, "strpos('b', 'a')");
assertExpression(tryExpression, "TRY(strpos('b', 'a'))");
}
@Override
public void testPredicateExpression()
{
LOGGER.info("Testing predicate expressions");
List<Expression> literals = list(longLiteral("10"), longLiteral("20"), longLiteral("30"));
assertExpression(new InListExpression(literals), "(10, 20, 30)");
assertExpression(new IsNullPredicate(new SymbolReference("age")), "(age IS NULL)");
assertExpression(new IsNotNullPredicate(new SymbolReference("age")), "(age IS NOT NULL)");
assertExpression(new BetweenPredicate(longLiteral("1"), longLiteral("2"), longLiteral("3")),
"(1 BETWEEN 2 AND 3)");
assertExpression(new NotExpression(new BetweenPredicate(longLiteral("1"), longLiteral("2"), longLiteral("3"))),
"(NOT (1 BETWEEN 2 AND 3))");
assertExpression(new ExistsPredicate(new SubqueryExpression(simpleQuery(selectList(new LongLiteral("1"))))),
"(EXISTS (SELECT 1\n" + "\n" + "))");
}
@Override
@Test(expectedExceptions = UnsupportedOperationException.class)
public void testArrayExpression()
{
super.testArrayExpression();
}
@Override
public void testGenericLiteralExpression()
{
LOGGER.info("Testing Hetu generic literal expressions");
assertExpression(new GenericLiteral("VARCHAR", "abc"), "'abc'");
assertExpression(new GenericLiteral("BIGINT", "abc"), "abc");
assertExpression(new GenericLiteral("DOUBLE", "abc"), "abc");
assertExpression(new GenericLiteral("DATE", "abc"), "date 'abc'");
}
/**
* testTpchSql1
*/
@Test
public void testTpchSql1()
{
LOGGER.info("Testing TPCH Sql 1");
@Language("SQL")
String query = "SELECT returnflag, linestatus, sum(quantity) AS sum_qty, sum(extendedprice) AS sum_base_price, sum(extendedprice * (1 - discount)) AS sum_disc_price, sum(extendedprice * (1 - discount) * (1 + tax)) AS sum_charge, avg(quantity) AS avg_qty, avg(extendedprice) AS avg_price, avg(discount) AS avg_disc, count(*) AS count_order FROM lineitem WHERE shipdate <= date '1998-09-16' GROUP BY returnflag, linestatus ORDER BY returnflag, linestatus";
assertStatement(query, "sum", "sum", "sum", "sum", "avg", "avg", "avg", "count", "(", "*", ")", "WHERE", "\\<=", TIMESTAMPSTR, "GROUP BY", "ORDER BY");
}
/**
* Oracle does not support BOOLEAN literal.
*/
@Test(expectedExceptions = UnsupportedOperationException.class)
public void testBooleanLiteralExpression()
{
LOGGER.info("Testing Hetu generic date expressions");
assertExpression(new GenericLiteral("BOOLEAN", "abc"), "abc");
}
/**
* OracleJdbcConnectorFactory
*
* @since 2019-10-12
*/
private static class OracleJdbcConnectorFactory
implements ConnectorFactory
{
private final JdbcClient jdbcClient;
private final String name;
private OracleJdbcConnectorFactory(JdbcClient jdbcClient, String name)
{
this.jdbcClient = jdbcClient;
this.name = name;
}
@Override
public String getName()
{
return this.name;
}
@Override
public ConnectorHandleResolver getHandleResolver()
{
return new JdbcHandleResolver();
}
@Override
public Connector create(String catalogName, Map<String, String> config, ConnectorContext context)
{
return new Connector()
{
@Override
public ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLevel, boolean isReadOnly)
{
return new ConnectorTransactionHandle()
{
};
}
@Override
public ConnectorMetadata getMetadata(ConnectorTransactionHandle transactionHandle)
{
return new JdbcMetadata(jdbcClient, false);
}
@Override
public ConnectorSplitManager getSplitManager()
{
return new JdbcSplitManager(jdbcClient);
}
@Override
public ConnectorRecordSetProvider getRecordSetProvider()
{
return new JdbcRecordSetProvider(jdbcClient);
}
};
}
}
}

View File

@ -22,6 +22,12 @@
<groupId>io.hetu.core</groupId>
<artifactId>presto-parser</artifactId>
</dependency>
<dependency>
<groupId>io.hetu.core</groupId>
<artifactId>presto-spi</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>

View File

@ -20,6 +20,8 @@ import com.google.common.collect.Lists;
import io.hetu.core.migration.source.hive.HiveSqlBaseVisitor;
import io.hetu.core.migration.source.hive.HiveSqlLexer;
import io.hetu.core.migration.source.hive.HiveSqlParser;
import io.prestosql.spi.sql.expression.Types;
import io.prestosql.spi.sql.expression.Types.FrameBoundType;
import io.prestosql.sql.parser.ParsingException;
import io.prestosql.sql.parser.ParsingOptions;
import io.prestosql.sql.tree.AddColumn;
@ -597,8 +599,6 @@ public class HiveAstBuilder
Identifier name = new Identifier("location");
Expression value = (StringLiteral) visit(context.location);
properties.add(new Property(name, value));
addDiff(DiffType.MODIFIED, context.LOCATION().getText(), LOCATION + " = " + value, "[LOCATION] is formatted");
}
if (context.TBLPROPERTIES() != null) {
List<Property> tableProperties = visit(context.properties().property(), Property.class);
@ -2288,7 +2288,7 @@ public class HiveAstBuilder
@Override
public Node visitCurrentRowBound(HiveSqlParser.CurrentRowBoundContext context)
{
return new FrameBound(getLocation(context), FrameBound.Type.CURRENT_ROW);
return new FrameBound(getLocation(context), FrameBoundType.CURRENT_ROW);
}
@Override
@ -2602,37 +2602,37 @@ public class HiveAstBuilder
throw new IllegalArgumentException("Unsupported interval field: " + token.getText());
}
private static WindowFrame.Type getFrameType(Token type)
private static Types.WindowFrameType getFrameType(Token type)
{
switch (type.getType()) {
case HiveSqlLexer.RANGE:
return WindowFrame.Type.RANGE;
return Types.WindowFrameType.RANGE;
case HiveSqlLexer.ROWS:
return WindowFrame.Type.ROWS;
return Types.WindowFrameType.ROWS;
}
throw new IllegalArgumentException("Unsupported frame type: " + type.getText());
}
private static FrameBound.Type getBoundedFrameBoundType(Token token)
private static Types.FrameBoundType getBoundedFrameBoundType(Token token)
{
switch (token.getType()) {
case HiveSqlLexer.PRECEDING:
return FrameBound.Type.PRECEDING;
return Types.FrameBoundType.PRECEDING;
case HiveSqlLexer.FOLLOWING:
return FrameBound.Type.FOLLOWING;
return Types.FrameBoundType.FOLLOWING;
}
throw new IllegalArgumentException("Unsupported bound type: " + token.getText());
}
private static FrameBound.Type getUnboundedFrameBoundType(Token token)
private static Types.FrameBoundType getUnboundedFrameBoundType(Token token)
{
switch (token.getType()) {
case HiveSqlLexer.PRECEDING:
return FrameBound.Type.UNBOUNDED_PRECEDING;
return Types.FrameBoundType.UNBOUNDED_PRECEDING;
case HiveSqlLexer.FOLLOWING:
return FrameBound.Type.UNBOUNDED_FOLLOWING;
return Types.FrameBoundType.UNBOUNDED_FOLLOWING;
}
throw new IllegalArgumentException("Unsupported bound type: " + token.getText());

View File

@ -19,6 +19,8 @@ import com.google.common.collect.Lists;
import io.hetu.core.migration.source.impala.ImpalaSqlBaseVisitor;
import io.hetu.core.migration.source.impala.ImpalaSqlLexer;
import io.hetu.core.migration.source.impala.ImpalaSqlParser;
import io.prestosql.spi.sql.expression.Types.FrameBoundType;
import io.prestosql.spi.sql.expression.Types.WindowFrameType;
import io.prestosql.sql.parser.ParsingException;
import io.prestosql.sql.parser.ParsingOptions;
import io.prestosql.sql.tree.AddColumn;
@ -2096,7 +2098,7 @@ public class ImpalaAstBuilder
@Override
public Node visitCurrentRowBound(ImpalaSqlParser.CurrentRowBoundContext context)
{
return new FrameBound(getLocation(context), FrameBound.Type.CURRENT_ROW);
return new FrameBound(getLocation(context), FrameBoundType.CURRENT_ROW);
}
@Override
@ -2340,37 +2342,37 @@ public class ImpalaAstBuilder
throw new IllegalArgumentException("Unsupported interval field: " + token.getText());
}
private static WindowFrame.Type getFrameType(Token type)
private static WindowFrameType getFrameType(Token type)
{
switch (type.getType()) {
case ImpalaSqlLexer.RANGE:
return WindowFrame.Type.RANGE;
return WindowFrameType.RANGE;
case ImpalaSqlLexer.ROWS:
return WindowFrame.Type.ROWS;
return WindowFrameType.ROWS;
}
throw new IllegalArgumentException("Unsupported frame type: " + type.getText());
}
private static FrameBound.Type getBoundedFrameBoundType(Token token)
private static FrameBoundType getBoundedFrameBoundType(Token token)
{
switch (token.getType()) {
case ImpalaSqlLexer.PRECEDING:
return FrameBound.Type.PRECEDING;
return FrameBoundType.PRECEDING;
case ImpalaSqlLexer.FOLLOWING:
return FrameBound.Type.FOLLOWING;
return FrameBoundType.FOLLOWING;
}
throw new IllegalArgumentException("Unsupported bound type: " + token.getText());
}
private static FrameBound.Type getUnboundedFrameBoundType(Token token)
private static FrameBoundType getUnboundedFrameBoundType(Token token)
{
switch (token.getType()) {
case ImpalaSqlLexer.PRECEDING:
return FrameBound.Type.UNBOUNDED_PRECEDING;
return FrameBoundType.UNBOUNDED_PRECEDING;
case ImpalaSqlLexer.FOLLOWING:
return FrameBound.Type.UNBOUNDED_FOLLOWING;
return FrameBoundType.UNBOUNDED_FOLLOWING;
}
throw new IllegalArgumentException("Unsupported bound type: " + token.getText());

View File

@ -78,6 +78,7 @@
<module>presto-array</module>
<module>presto-jmx</module>
<module>presto-record-decoder</module>
<module>presto-expressions</module>
<module>presto-kafka</module>
<module>presto-memory</module>
<module>presto-orc</module>
@ -191,6 +192,12 @@
<type>test-jar</type>
</dependency>
<dependency>
<groupId>io.hetu.core</groupId>
<artifactId>presto-expressions</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.hetu.core</groupId>
<artifactId>presto-resource-group-managers</artifactId>

View File

@ -120,6 +120,11 @@
<artifactId>presto-spi</artifactId>
</dependency>
<dependency>
<groupId>io.hetu.core</groupId>
<artifactId>presto-parser</artifactId>
</dependency>
<dependency>
<groupId>io.airlift</groupId>
<artifactId>slice</artifactId>
@ -183,7 +188,9 @@
<dependency>
<groupId>io.hetu.core</groupId>
<artifactId>presto-parser</artifactId>
<artifactId>presto-main</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -177,6 +177,12 @@ public class BaseJdbcClient
connectionFactory.close();
}
@Override
public String getIdentifierQuote()
{
return identifierQuote;
}
@Override
public final Set<String> getSchemaNames(JdbcIdentity identity)
{
@ -321,15 +327,15 @@ public class BaseJdbcClient
public PreparedStatement buildSql(ConnectorSession session, Connection connection, JdbcSplit split, JdbcTableHandle table, List<JdbcColumnHandle> columns)
throws SQLException
{
if (table.getSubQuery() != null) {
// Hetu: If the sub-query is pushed down, use it as the table
if (table.getGeneratedSql().isPresent()) {
// Hetu: If the query is pushed down, use it as the table
return new QueryBuilder(identifierQuote, true).buildSql(
this,
session,
connection,
null,
null,
table.getSubQuery(),
table.getGeneratedSql().get().getSql(),
columns,
table.getConstraint(),
split.getAdditionalPredicate(),

View File

@ -18,6 +18,7 @@ import io.airlift.configuration.ConfigDescription;
import io.airlift.configuration.ConfigSecuritySensitive;
import io.airlift.units.Duration;
import io.airlift.units.MinDuration;
import io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule;
import javax.annotation.Nullable;
import javax.validation.constraints.Min;
@ -52,6 +53,10 @@ public class BaseJdbcConfig
private boolean jmxEnabled = true;
// Hetu: JDBC fetch size configuration
private int fetchSize;
// Hetu: JDBC query push down enable
private boolean pushDownEnable = true;
// Hetu: JDBC push down module
private JdbcPushDownModule pushDownModule = JdbcPushDownModule.DEFAULT;
public boolean isLifo()
{
@ -364,4 +369,30 @@ public class BaseJdbcConfig
this.fetchSize = fetchSize;
return this;
}
public boolean isPushDownEnable()
{
return pushDownEnable;
}
@Config("jdbc.pushdown-enabled")
@ConfigDescription("Allow jdbc pushDown")
public BaseJdbcConfig setPushDownEnable(boolean pushDownEnable)
{
this.pushDownEnable = pushDownEnable;
return this;
}
public JdbcPushDownModule getPushDownModule()
{
return this.pushDownModule;
}
@Config("jdbc.pushdown-module")
@ConfigDescription("jdbc query push down module in [FULL_PUSHDOWN, BASE_PUSHDOWN]")
public BaseJdbcConfig setPushDownModule(JdbcPushDownModule pushDownModule)
{
this.pushDownModule = pushDownModule;
return this;
}
}

View File

@ -13,6 +13,8 @@
*/
package io.prestosql.plugin.jdbc;
import io.prestosql.plugin.jdbc.optimization.BaseJdbcQueryGenerator;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorResult;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.ColumnMetadata;
import io.prestosql.spi.connector.ConnectorSession;
@ -20,10 +22,10 @@ import io.prestosql.spi.connector.ConnectorSplitSource;
import io.prestosql.spi.connector.ConnectorTableMetadata;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.predicate.TupleDomain;
import io.prestosql.spi.sql.SqlQueryWriter;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.sql.QueryGenerator;
import io.prestosql.spi.statistics.TableStatistics;
import io.prestosql.spi.type.Type;
import io.prestosql.sql.builder.BaseSqlQueryWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
@ -57,6 +59,12 @@ public abstract class ForwardingJdbcClient
return getDelegate().getTableNames(identity, schema);
}
@Override
public String getIdentifierQuote()
{
return getDelegate().getIdentifierQuote();
}
@Override
public Optional<JdbcTableHandle> getTableHandle(JdbcIdentity identity, SchemaTableName schemaTableName)
{
@ -219,7 +227,7 @@ public abstract class ForwardingJdbcClient
}
/**
* Hetu's sub-query push down requires to get output columns of the given sql query.
* Hetu's push down requires to get output columns of the given sql query.
* The returned list of columns does not necessarily match with the underlying table schema.
* It interprets all the selected values as a separate column.
* For example `SELECT CAST(MAX(price) AS varchar) as max_price FORM orders GROUP BY customer`
@ -236,26 +244,26 @@ public abstract class ForwardingJdbcClient
return getDelegate().getColumns(session, sql, types);
}
/**
* Hetu's sub-query push down expects the JDBC connectors to provide a {@link SqlQueryWriter}
* to write SQL queries for the respective databases. By default, this method provides the
* {@link BaseSqlQueryWriter} which writes Presto ANSI SQL queries.
* <p>
* Override this method in the JDBC client of supporting database and return a {@link SqlQueryWriter}
* object which knows how to write database specific SQL queries.
*
* @return the optional SQL query writer which can write database specific SQL queries
*/
@Override
public Optional<SqlQueryWriter> getSqlQueryWriter()
{
return getDelegate().getSqlQueryWriter();
}
// default method to check if execution plan caching is supported by this connector
@Override
public boolean isExecutionPlanCacheSupported()
{
return getDelegate().isExecutionPlanCacheSupported();
}
/**
* Hetu's query push down expects the JDBC connectors to provide a {@link QueryGenerator}
* to write SQL queries for the respective databases. By default, this method provides the
* {@link BaseJdbcQueryGenerator} which writes Presto ANSI SQL queries.
* <p>
* Override this method in the JDBC client of supporting database and return a {@link QueryGenerator}
* object which knows how to write database specific SQL queries.
*
* @return the optional SQL query writer which can write database specific SQL queries
*/
@Override
public Optional<QueryGenerator<JdbcQueryGeneratorResult>> getQueryGenerator(RowExpressionService rowExpressionService)
{
return getDelegate().getQueryGenerator(rowExpressionService);
}
}

View File

@ -13,6 +13,7 @@
*/
package io.prestosql.plugin.jdbc;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorResult;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.ColumnMetadata;
import io.prestosql.spi.connector.ConnectorSession;
@ -20,7 +21,8 @@ import io.prestosql.spi.connector.ConnectorSplitSource;
import io.prestosql.spi.connector.ConnectorTableMetadata;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.predicate.TupleDomain;
import io.prestosql.spi.sql.SqlQueryWriter;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.sql.QueryGenerator;
import io.prestosql.spi.statistics.TableStatistics;
import io.prestosql.spi.type.Type;
@ -41,6 +43,8 @@ public interface JdbcClient
return getSchemaNames(identity).contains(schema);
}
String getIdentifierQuote();
Set<String> getSchemaNames(JdbcIdentity identity);
List<SchemaTableName> getTableNames(JdbcIdentity identity, Optional<String> schema);
@ -115,7 +119,7 @@ public interface JdbcClient
}
/**
* Hetu's sub-query push down requires to get output columns of the given sql query.
* Hetu's query push down requires to get output columns of the given sql query.
* The returned list of columns does not necessarily match with the underlying table schema.
* It interprets all the selected values as a separate column.
* For example `SELECT CAST(MAX(price) AS varchar) as max_price FORM orders GROUP BY customer`
@ -132,15 +136,11 @@ public interface JdbcClient
}
/**
* Hetu's sub-query push down expects the JDBC connectors to provide a {@link SqlQueryWriter}
* Hetu's query push down expects the JDBC connectors to provide a {@link QueryGenerator}
* to write SQL queries for the respective databases.
* <p>
* Override this method in the JDBC client of supporting database and return a {@link SqlQueryWriter}
* object which knows how to write database specific SQL queries.
*
* @return the optional SQL query writer which can write database specific SQL queries
* @return the optional SQL query writer which can write database specific SQL Queries
*/
default Optional<SqlQueryWriter> getSqlQueryWriter()
default Optional<QueryGenerator<JdbcQueryGeneratorResult>> getQueryGenerator(RowExpressionService rowExpressionService)
{
return Optional.empty();
}

View File

@ -16,12 +16,16 @@ package io.prestosql.plugin.jdbc;
import com.google.common.collect.ImmutableSet;
import io.airlift.bootstrap.LifeCycleManager;
import io.airlift.log.Logger;
import io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizer;
import io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerProvider;
import io.prestosql.spi.ConnectorPlanOptimizer;
import io.prestosql.spi.connector.CachedConnectorMetadata;
import io.prestosql.spi.connector.Connector;
import io.prestosql.spi.connector.ConnectorAccessControl;
import io.prestosql.spi.connector.ConnectorCapabilities;
import io.prestosql.spi.connector.ConnectorMetadata;
import io.prestosql.spi.connector.ConnectorPageSinkProvider;
import io.prestosql.spi.connector.ConnectorPlanOptimizerProvider;
import io.prestosql.spi.connector.ConnectorRecordSetProvider;
import io.prestosql.spi.connector.ConnectorSplitManager;
import io.prestosql.spi.connector.ConnectorTransactionHandle;
@ -57,6 +61,7 @@ public class JdbcConnector
private final Optional<ConnectorAccessControl> accessControl;
private final Set<Procedure> procedures;
private final JdbcMetadataConfig config;
private final ConnectorPlanOptimizer planOptimizer;
private final ConcurrentMap<ConnectorTransactionHandle, JdbcMetadata> transactions = new ConcurrentHashMap<>();
@ -69,7 +74,8 @@ public class JdbcConnector
JdbcPageSinkProvider jdbcPageSinkProvider,
Optional<ConnectorAccessControl> accessControl,
Set<Procedure> procedures,
JdbcMetadataConfig config)
JdbcMetadataConfig config,
JdbcPlanOptimizer planOptimizer)
{
this.lifeCycleManager = requireNonNull(lifeCycleManager, "lifeCycleManager is null");
this.jdbcMetadataFactory = requireNonNull(jdbcMetadataFactory, "jdbcMetadataFactory is null");
@ -79,6 +85,13 @@ public class JdbcConnector
this.accessControl = requireNonNull(accessControl, "accessControl is null");
this.procedures = ImmutableSet.copyOf(requireNonNull(procedures, "procedures is null"));
this.config = config;
this.planOptimizer = planOptimizer;
}
@Override
public ConnectorPlanOptimizerProvider getConnectorPlanOptimizerProvider()
{
return new JdbcPlanOptimizerProvider(planOptimizer);
}
@Override

View File

@ -22,6 +22,7 @@ import io.prestosql.spi.connector.Connector;
import io.prestosql.spi.connector.ConnectorContext;
import io.prestosql.spi.connector.ConnectorFactory;
import io.prestosql.spi.connector.ConnectorHandleResolver;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.type.TypeManager;
import org.weakref.jmx.guice.MBeanModule;
@ -67,6 +68,7 @@ public class JdbcConnectorFactory
try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) {
Bootstrap app = new Bootstrap(
binder -> binder.bind(TypeManager.class).toInstance(context.getTypeManager()),
binder -> binder.bind(RowExpressionService.class).toInstance(context.getRowExpressionService()),
new JdbcModule(catalogName),
new MBeanServerModule(),
new MBeanModule(),

View File

@ -18,12 +18,16 @@ import io.prestosql.spi.ErrorCodeSupplier;
import io.prestosql.spi.ErrorType;
import static io.prestosql.spi.ErrorType.EXTERNAL;
import static io.prestosql.spi.ErrorType.INTERNAL_ERROR;
public enum JdbcErrorCode
implements ErrorCodeSupplier
{
JDBC_ERROR(0, EXTERNAL),
JDBC_NON_TRANSIENT_ERROR(1, EXTERNAL);
JDBC_NON_TRANSIENT_ERROR(1, EXTERNAL),
JDBC_UNSUPPORTED_EXPRESSION(2, EXTERNAL),
JDBC_UNCLASSIFIED_ERROR(3, EXTERNAL),
JDBC_QUERY_GENERATOR_FAILURE(4, INTERNAL_ERROR);
private final ErrorCode errorCode;

View File

@ -33,13 +33,10 @@ import io.prestosql.spi.connector.ConstraintApplicationResult;
import io.prestosql.spi.connector.LimitApplicationResult;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.connector.SchemaTablePrefix;
import io.prestosql.spi.connector.SubQueryApplicationResult;
import io.prestosql.spi.connector.TableNotFoundException;
import io.prestosql.spi.predicate.TupleDomain;
import io.prestosql.spi.sql.SqlQueryWriter;
import io.prestosql.spi.statistics.ComputedStatistics;
import io.prestosql.spi.statistics.TableStatistics;
import io.prestosql.spi.type.Type;
import java.util.Collection;
import java.util.List;
@ -145,69 +142,6 @@ public class JdbcMetadata
return jdbcClient.isExecutionPlanCacheSupported();
}
/**
* Hetu supports pushing sub-query with join down to the connector.
* This method decides if the sub-query can be pushed down to the connector based on the connector.
* <p>
* Connectors can indicate whether they don't support predicate push down or that the action had no effect
* by returning {@link Optional#empty()}. Connectors should expect this method to be called multiple times
* </p>
* during the optimization of a given query.
* <p>
* <b>Note</b>: it's critical for connectors to return Optional.empty() if calling this method has no effect for that
* invocation, even if the connector generally supports push down. Doing otherwise can cause the optimizer
* to loop indefinitely.
* </p>
*
* @param session Presto session
* @param table randomly selected connector handle from the sub-query
* @param subQuery the actual sub-query to be pushed down
* @param types Presto types of intermediate symbols
* @return optional SubQueryApplicationResult which has the new TableHandle if the connector supports this feature
*/
@Override
public Optional<SubQueryApplicationResult<ConnectorTableHandle>> applySubQuery(ConnectorSession session, ConnectorTableHandle table, String subQuery, Map<String, Type> types)
{
// If the subQuery pushed down to the connector, table name, limit or predicate push downs are not necessary
// Therefore, either of the table name can be used for the new TableHandle as long as the subQuery is valid
requireNonNull(subQuery, "cannot apply null sub-query");
JdbcTableHandle tableHandle = (JdbcTableHandle) table;
// If the JDBC Client can get the columns from the sub-query, it should be able to push sub-query down
Map<String, ColumnHandle> assignments = jdbcClient.getColumns(session, subQuery, types);
if (assignments.isEmpty()) {
return Optional.empty();
}
// Extract the types returned by the database
ImmutableMap.Builder<String, Type> typesBuilder = new ImmutableMap.Builder<>();
for (Map.Entry<String, ColumnHandle> entry : assignments.entrySet()) {
typesBuilder.put(entry.getKey(), ((JdbcColumnHandle) entry.getValue()).getColumnType());
}
JdbcTableHandle handle = new JdbcTableHandle(
tableHandle.getSchemaTableName(),
tableHandle.getCatalogName(),
tableHandle.getSchemaName(),
tableHandle.getTableName(),
tableHandle.getConstraint(),
OptionalLong.empty(),
subQuery);
return Optional.of(new SubQueryApplicationResult<>(handle, assignments, typesBuilder.build()));
}
/**
* Hetu's sub-query push down expects supporting connectors to provide a {@link SqlQueryWriter}
* to write SQL queries for the respective databases.
*
* @return the optional SQL query writer which can write database specific SQL queries
*/
@Override
public Optional<SqlQueryWriter> getSqlQueryWriter()
{
return jdbcClient.getSqlQueryWriter();
}
@Override
public boolean usesLegacyTableLayouts()
{

View File

@ -26,7 +26,6 @@ import java.util.concurrent.TimeUnit;
public class JdbcMetadataConfig
{
private boolean allowDropTable;
// added by Hetu for metadata caching
private Duration metadataCacheTtl = new Duration(1, TimeUnit.SECONDS); // metadata cache eviction time
private long metadataCacheMaximumSize = 10000; // metadata cache max size

View File

@ -21,6 +21,7 @@ import com.google.inject.Scopes;
import com.google.inject.Singleton;
import io.prestosql.plugin.jdbc.jmx.StatisticsAwareConnectionFactory;
import io.prestosql.plugin.jdbc.jmx.StatisticsAwareJdbcClient;
import io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizer;
import io.prestosql.spi.connector.ConnectorAccessControl;
import io.prestosql.spi.procedure.Procedure;
@ -47,6 +48,7 @@ public class JdbcModule
newSetBinder(binder, Procedure.class);
binder.bind(JdbcMetadataFactory.class).in(Scopes.SINGLETON);
binder.bind(JdbcSplitManager.class).in(Scopes.SINGLETON);
binder.bind(JdbcPlanOptimizer.class).in(Scopes.SINGLETON);
binder.bind(JdbcRecordSetProvider.class).in(Scopes.SINGLETON);
binder.bind(JdbcPageSinkProvider.class).in(Scopes.SINGLETON);
binder.bind(JdbcConnector.class).in(Scopes.SINGLETON);

View File

@ -16,6 +16,7 @@ package io.prestosql.plugin.jdbc;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Joiner;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorResult.GeneratedSql;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.ConnectorTableHandle;
import io.prestosql.spi.connector.SchemaTableName;
@ -24,6 +25,7 @@ import io.prestosql.spi.predicate.TupleDomain;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
import static java.util.Objects.requireNonNull;
@ -39,8 +41,8 @@ public class JdbcTableHandle
private final String tableName;
private final TupleDomain<ColumnHandle> constraint;
private final OptionalLong limit;
// Hetu: If subQuery is not null, it will be used by the DC Connector to build the sql
private final String subQuery;
// Hetu: If query is push down use pushDown sql to build sql and use columnHandles directly
private final Optional<GeneratedSql> generatedSql;
public JdbcTableHandle(SchemaTableName schemaTableName, @Nullable String catalogName, @Nullable String schemaName, String tableName)
{
@ -56,7 +58,6 @@ public class JdbcTableHandle
* @param schemaName
* @param tableName
* @param constraint
* @param limit
*/
public JdbcTableHandle(
SchemaTableName schemaTableName,
@ -66,7 +67,7 @@ public class JdbcTableHandle
TupleDomain<ColumnHandle> constraint,
OptionalLong limit)
{
this(schemaTableName, catalogName, schemaName, tableName, constraint, limit, null);
this(schemaTableName, catalogName, schemaName, tableName, constraint, limit, Optional.empty());
}
@JsonCreator
@ -77,7 +78,7 @@ public class JdbcTableHandle
@JsonProperty("tableName") String tableName,
@JsonProperty("constraint") TupleDomain<ColumnHandle> constraint,
@JsonProperty("limit") OptionalLong limit,
@JsonProperty("subQuery") String subQuery)
@JsonProperty("sql") Optional<GeneratedSql> generatedSql)
{
this.schemaTableName = requireNonNull(schemaTableName, "schemaTableName is null");
this.catalogName = catalogName;
@ -85,7 +86,7 @@ public class JdbcTableHandle
this.tableName = requireNonNull(tableName, "tableName is null");
this.constraint = requireNonNull(constraint, "constraint is null");
this.limit = requireNonNull(limit, "limit is null");
this.subQuery = subQuery;
this.generatedSql = generatedSql;
}
@JsonProperty
@ -120,24 +121,18 @@ public class JdbcTableHandle
return constraint;
}
@JsonProperty
public Optional<GeneratedSql> getGeneratedSql()
{
return generatedSql;
}
@JsonProperty
public OptionalLong getLimit()
{
return limit;
}
/**
* Return the sub-query.
*
* @return sub-query if it was assigned otherwise, null
*/
@JsonProperty
@Nullable
public String getSubQuery()
{
return subQuery;
}
/**
* Hetu DC Connector uses {@link JdbcTableHandle}.
* Overriding this method makes all JdbcConnectors using {@link JdbcTableHandle}
@ -169,7 +164,7 @@ public class JdbcTableHandle
{
JdbcTableHandle oldJdbcTableHandle = (JdbcTableHandle) oldConnectorTableHandle;
return new JdbcTableHandle(schemaTableName, catalogName, schemaName, tableName, oldJdbcTableHandle.getConstraint(),
oldJdbcTableHandle.getLimit(), oldJdbcTableHandle.getSubQuery());
oldJdbcTableHandle.getLimit(), oldJdbcTableHandle.getGeneratedSql());
}
@Override
@ -195,8 +190,12 @@ public class JdbcTableHandle
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(schemaTableName).append(" ");
Joiner.on(".").skipNulls().appendTo(builder, catalogName, schemaName, tableName, subQuery);
if (generatedSql.isPresent()) {
Joiner.on(".").skipNulls().appendTo(builder, catalogName, generatedSql.get());
}
else {
Joiner.on(".").skipNulls().appendTo(builder, catalogName, schemaName, tableName);
}
limit.ifPresent(value -> builder.append(" limit=").append(value));
return builder.toString();
}

View File

@ -50,7 +50,7 @@ public class QueryBuilder
private static final String ALWAYS_FALSE = "1=0";
private final String identifierQuote;
private boolean isPushSubQueryDown;
private boolean isPushDown;
private static class TypeAndValue
{
@ -86,10 +86,10 @@ public class QueryBuilder
this.identifierQuote = requireNonNull(identifierQuote, "identifierQuote is null");
}
public QueryBuilder(String identifierQuote, boolean isPushSubQueryDown)
public QueryBuilder(String identifierQuote, boolean isPushDown)
{
this(identifierQuote);
this.isPushSubQueryDown = isPushSubQueryDown;
this.isPushDown = isPushDown;
}
public PreparedStatement buildSql(
@ -125,8 +125,8 @@ public class QueryBuilder
if (!isNullOrEmpty(schema)) {
sql.append(quote(schema)).append('.');
}
if (isPushSubQueryDown) {
sql.append(table);
if (isPushDown) {
sql.append("(").append(table).append(") pushdown");
}
else {
sql.append(quote(table));

View File

@ -40,6 +40,12 @@ public class TransactionScopeCachingJdbcClient
return delegate;
}
@Override
public String getIdentifierQuote()
{
return delegate.getIdentifierQuote();
}
@Override
public List<JdbcColumnHandle> getColumns(ConnectorSession session, JdbcTableHandle tableHandle)
{

View File

@ -62,6 +62,12 @@ public class StatisticsAwareJdbcClient
return delegate;
}
@Override
public String getIdentifierQuote()
{
return delegate.getIdentifierQuote();
}
@Managed
@Flatten
public JdbcClientStats getStats()

View File

@ -0,0 +1,589 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import io.airlift.log.Logger;
import io.prestosql.plugin.jdbc.JdbcColumnHandle;
import io.prestosql.plugin.jdbc.JdbcTableHandle;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorResult.GeneratedSql;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.metadata.TableHandle;
import io.prestosql.spi.plan.AggregationNode;
import io.prestosql.spi.plan.FilterNode;
import io.prestosql.spi.plan.GroupIdNode;
import io.prestosql.spi.plan.JoinNode;
import io.prestosql.spi.plan.LimitNode;
import io.prestosql.spi.plan.MarkDistinctNode;
import io.prestosql.spi.plan.OrderingScheme;
import io.prestosql.spi.plan.PlanNode;
import io.prestosql.spi.plan.PlanVisitor;
import io.prestosql.spi.plan.ProjectNode;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.plan.TableScanNode;
import io.prestosql.spi.plan.TopNNode;
import io.prestosql.spi.plan.UnionNode;
import io.prestosql.spi.plan.WindowNode;
import io.prestosql.spi.predicate.TupleDomain;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.sql.QueryGenerator;
import io.prestosql.spi.sql.RowExpressionConverter;
import io.prestosql.spi.sql.SqlStatementWriter;
import io.prestosql.spi.sql.expression.OrderBy;
import io.prestosql.spi.sql.expression.Selection;
import io.prestosql.spi.sql.expression.Types;
import io.prestosql.spi.type.Type;
import io.prestosql.spi.type.TypeManager;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.stream.IntStream;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_QUERY_GENERATOR_FAILURE;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.frameBound;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.getDerivedTable;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.getProjectSelections;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.getSelectionsFromSymbolsMap;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.isAggregationDistinct;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.isSameCatalog;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.quote;
import static io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule.DEFAULT;
import static io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule.FULL_PUSHDOWN;
import static io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorContext.buildAsNewTable;
import static io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorContext.buildFrom;
import static io.prestosql.spi.StandardErrorCode.NOT_SUPPORTED;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
public class BaseJdbcQueryGenerator
implements QueryGenerator<JdbcQueryGeneratorResult>
{
protected static final Logger log = Logger.get(BaseJdbcQueryGenerator.class);
protected static final String GENERATE_FAILED_LOG = "JDBC query generator failed for [%s]";
protected final String quote;
protected final JdbcPushDownModule pushDownModule;
protected final RowExpressionConverter converter;
protected final SqlStatementWriter statementWriter;
public BaseJdbcQueryGenerator(
JdbcPushDownParameter pushDownParameter,
RowExpressionConverter converter,
SqlStatementWriter statementWriter)
{
this.quote = pushDownParameter.getIdentifierQuote();
this.pushDownModule = pushDownParameter.getPushDownModuleParameter() == DEFAULT ? FULL_PUSHDOWN : pushDownParameter.getPushDownModuleParameter();
this.converter = converter;
this.statementWriter = statementWriter;
}
@Override
public RowExpressionConverter getConverter()
{
return converter;
}
@Override
public Optional<JdbcQueryGeneratorResult> generate(PlanNode plan, TypeManager typeManager)
{
try {
Optional<JdbcQueryGeneratorContext> context = requireNonNull(plan.accept(getVisitor(typeManager), null),
"Resulting context is null");
return context.map(jdbcQueryGeneratorContext -> new JdbcQueryGeneratorResult(buildSql(jdbcQueryGeneratorContext), jdbcQueryGeneratorContext));
}
catch (PrestoException e) {
log.debug(e, "Possibly benign error when pushing plan into scan node %s", plan);
return Optional.empty();
}
}
protected PlanVisitor<Optional<JdbcQueryGeneratorContext>, Void> getVisitor(TypeManager typeManager)
{
return new BaseJdbcPlanVisitor(typeManager);
}
protected GeneratedSql buildSql(JdbcQueryGeneratorContext context)
{
String sql = statementWriter.select(ImmutableList.copyOf(context.getSelections().values()));
checkArgument(context.getFrom().isPresent(), "From expression must not be empty");
sql = statementWriter.from(sql, context.getFrom().get());
if (context.getFilter().isPresent()) {
sql = statementWriter.filter(sql, context.getFilter().get());
}
if (!context.getGroupByColumns().isEmpty()) {
sql = statementWriter.groupBy(sql, context.getGroupByColumns());
}
if (context.getOrderBy().isPresent()) {
sql = statementWriter.orderBy(sql, context.getOrderBy().get());
}
if (context.getLimit().isPresent()) {
sql = statementWriter.limit(sql, context.getLimit().getAsLong());
}
boolean isPushDown = context.isHasPushDown();
return new GeneratedSql(sql, isPushDown);
}
protected class BaseJdbcPlanVisitor
extends PlanVisitor<Optional<JdbcQueryGeneratorContext>, Void>
{
protected int derivedTableIdentifier = 1;
protected TypeManager typeManager;
public BaseJdbcPlanVisitor(TypeManager typeManager)
{
this.typeManager = typeManager;
}
@Override
public Optional<JdbcQueryGeneratorContext> visitPlan(PlanNode node, Void contextIn)
{
log.debug(GENERATE_FAILED_LOG, "Don't know how to handle plan node of type " + node);
return Optional.empty();
}
@Override
public Optional<JdbcQueryGeneratorContext> visitMarkDistinct(MarkDistinctNode node, Void contextIn)
{
return node.getSource().accept(this, contextIn);
}
@Override
public Optional<JdbcQueryGeneratorContext> visitFilter(FilterNode node, Void contextIn)
{
checkAvailable(node);
Optional<JdbcQueryGeneratorContext> sourceContext = node.getSource().accept(this, contextIn);
if (!sourceContext.isPresent()) {
return Optional.empty();
}
JdbcQueryGeneratorContext context = sourceContext.get();
String filter = node.getPredicate().accept(converter, null);
return Optional.of(buildAsNewTable(context)
.setSelections(getProjectSelections(context.getSelections()))
.setFrom(getDerivedTable(buildSql(context).getSql(), derivedTableIdentifier++))
.setFilter(Optional.of(filter))
.setOutputColumns(node.getOutputSymbols())
.setHasPushDown(true)
.build());
}
@Override
public Optional<JdbcQueryGeneratorContext> visitJoin(JoinNode node, Void contextIn)
{
checkAvailable(node);
Optional<JdbcQueryGeneratorContext> leftSourceContext = node.getLeft().accept(this, contextIn);
if (!leftSourceContext.isPresent()) {
return Optional.empty();
}
Optional<JdbcQueryGeneratorContext> rightSourceContext = node.getRight().accept(this, contextIn);
if (!rightSourceContext.isPresent()) {
return Optional.empty();
}
JdbcQueryGeneratorContext leftContext = leftSourceContext.get();
JdbcQueryGeneratorContext rightContext = rightSourceContext.get();
if (!leftContext.getCatalogName().isPresent()
|| !rightContext.getCatalogName().isPresent()
|| !leftContext.getCatalogName().equals(rightContext.getCatalogName())) {
log.debug(GENERATE_FAILED_LOG, "Jdbc Generator can only push down join node with same catalog");
return Optional.empty();
}
LinkedHashMap<String, Selection> newSelections = new LinkedHashMap<>();
newSelections.putAll(getProjectSelections(leftContext.getSelections()));
newSelections.putAll(getProjectSelections(rightContext.getSelections()));
// create a derived table as from
String from = statementWriter.join((node.isCrossJoin()
? Types.JoinType.CROSS
: Types.JoinType.valueOf(node.getType().toString())).getJoinLabel(),
buildSql(leftContext).getSql(),
buildSql(rightContext).getSql(),
node.getCriteria().stream().map(JoinNode.EquiJoinClause::toString).collect(toList()),
node.getFilter().map(filter -> filter.accept(converter, null)),
derivedTableIdentifier++);
JdbcQueryGeneratorContext.Builder contextBuilder = buildAsNewTable(leftContext)
.setSelections(newSelections)
.setFrom(Optional.of(from))
.setHasPushDown(true)
.setOutputColumns(node.getOutputSymbols());
return Optional.of(contextBuilder.build());
}
@Override
public Optional<JdbcQueryGeneratorContext> visitUnion(UnionNode node, Void contextIn)
{
checkAvailable(node);
List<PlanNode> sources = node.getSources();
if (sources == null || sources.size() < 2) {
log.debug(GENERATE_FAILED_LOG, "Does not support tables' num smaller than 2 in union node");
return Optional.empty();
}
List<Optional<JdbcQueryGeneratorContext>> sourceContexts = sources.stream()
.map(planNode -> planNode.accept(this, contextIn))
.collect(toList());
if (!sourceContexts.stream().allMatch(Optional::isPresent)) {
return Optional.empty();
}
List<JdbcQueryGeneratorContext> contexts = sourceContexts.stream()
.map(Optional::get)
.collect(toList());
if (!isSameCatalog(contexts)) {
log.debug(GENERATE_FAILED_LOG, "Union push down just support all sources in same catalog");
return Optional.empty();
}
// sort sources' selection
String from = statementWriter.union(IntStream.range(0, sources.size())
.mapToObj(i -> statementWriter.from(
statementWriter.select(getSelectionsFromSymbolsMap(node.sourceSymbolMap(i))),
getDerivedTable(buildSql(contexts.get(i)).getSql(), derivedTableIdentifier++).get()))
.collect(toList()), derivedTableIdentifier++);
LinkedHashMap<String, Selection> newSelections = new LinkedHashMap<>();
node.getOutputSymbols().forEach(symbol -> newSelections.put(symbol.getName(), new Selection(symbol.getName(), symbol.getName())));
// select first source as base context
JdbcQueryGeneratorContext baseContext = contexts.get(0);
return Optional.of(buildAsNewTable(baseContext)
.setSelections(newSelections)
.setFrom(Optional.of(from))
.setHasPushDown(true)
.build());
}
@Override
public Optional<JdbcQueryGeneratorContext> visitProject(ProjectNode node, Void contextIn)
{
checkAvailable(node);
Optional<JdbcQueryGeneratorContext> sourceContext = node.getSource().accept(this, contextIn);
if (!sourceContext.isPresent()) {
return Optional.empty();
}
JdbcQueryGeneratorContext context = sourceContext.get();
Map<Symbol, RowExpression> assignments = node.getAssignments().getMap();
LinkedHashMap<String, Selection> newSelections = new LinkedHashMap<>(getProjectSelections(context.getSelections()));
for (Map.Entry<Symbol, RowExpression> entry : assignments.entrySet()) {
newSelections.put(entry.getKey().getName(), new Selection(entry.getValue().accept(converter, null), entry.getKey().getName()));
}
return Optional.of(buildAsNewTable(context)
.setHasPushDown(true)
.setFrom(getDerivedTable(buildSql(context).getSql(), derivedTableIdentifier++))
.setSelections(newSelections)
.setOutputColumns(node.getOutputSymbols())
.build());
}
@Override
public Optional<JdbcQueryGeneratorContext> visitAggregation(AggregationNode node, Void contextIn)
{
checkAvailable(node);
// visit the child project node
Optional<JdbcQueryGeneratorContext> sourceContext = node.getSource().accept(this, contextIn);
if (!sourceContext.isPresent()) {
return Optional.empty();
}
checkArgument(!node.getStep().isOutputPartial(), "partial aggregations are not support in Jdbc pushdown framework");
LinkedHashMap<String, Selection> newSelections = new LinkedHashMap<>();
LinkedHashSet<String> groupByColumns = new LinkedHashSet<>();
for (Symbol outputColumn : node.getOutputSymbols()) {
AggregationNode.Aggregation aggregation = node.getAggregations().get(outputColumn);
if (aggregation != null) {
if (aggregation.getFilter().isPresent() || aggregation.getOrderingScheme().isPresent()) {
log.debug(GENERATE_FAILED_LOG, "Not support aggregation node " + node);
return Optional.empty();
}
Type returnType = typeManager.getType(aggregation.getSignature().getReturnType());
String aggExpression = statementWriter.aggregation(
aggregation.getSignature().getName(),
aggregation.getArguments().stream()
.map(rowExpression -> rowExpression.accept(converter, null))
.collect(toList()),
isAggregationDistinct(aggregation));
String castAggExpression = statementWriter.castAggregationType(aggExpression, converter, returnType);
newSelections.put(outputColumn.getName(), new Selection(castAggExpression, outputColumn.getName()));
}
else {
// group by output
newSelections.put(outputColumn.getName(), new Selection(outputColumn.getName()));
groupByColumns.add(outputColumn.getName());
}
}
// If groupIdSymbol is not empty, remove groupId column and add GROUPING SETS
Optional<Symbol> groupIdSymbol = node.getGroupIdSymbol();
if (groupIdSymbol.isPresent() && sourceContext.get().getGroupIdNodeInfo().getGroupingElementStore().containsKey(groupIdSymbol.get())) {
JdbcQueryGeneratorContext.GroupIdNodeInfo groupIdNodeInfo = sourceContext.get().getGroupIdNodeInfo();
String idElementString = groupIdNodeInfo.getGroupingElementStore().get(groupIdSymbol.get());
Optional<String> eleStr = Optional.of(idElementString);
Optional<Selection> selectionOptional = Optional.empty();
for (Map.Entry<String, Selection> entry : newSelections.entrySet()) {
String selectStr = entry.getValue().getExpression();
if (selectStr.equals(groupIdSymbol.get().getName())) {
selectionOptional = Optional.of(entry.getValue());
}
}
selectionOptional.ifPresent(selection -> newSelections.remove(selection.getAlias()));
groupIdNodeInfo.setGroupByComplexOperation(true);
return Optional.of(buildAsNewTable(sourceContext.get())
.setFrom(getDerivedTable(buildSql(sourceContext.get()).getSql(), derivedTableIdentifier++))
.setSelections(newSelections)
.setGroupIdNodeInfo(groupIdNodeInfo)
.setGroupByColumns(ImmutableSet.of(eleStr.get()))
.build());
}
JdbcQueryGeneratorContext context = sourceContext.get();
return Optional.of(buildAsNewTable(context)
.setFrom(getDerivedTable(buildSql(context).getSql(), derivedTableIdentifier++))
.setSelections(newSelections)
.setGroupByColumns(groupByColumns)
.setHasPushDown(true)
.build());
}
@Override
public Optional<JdbcQueryGeneratorContext> visitTableScan(TableScanNode node, Void contextIn)
{
checkAvailable(node);
checkArgument(node.getTable().getConnectorHandle() instanceof JdbcTableHandle,
"Expected to find jdbc table handle for the scan node");
TupleDomain<ColumnHandle> constraint = node.getEnforcedConstraint();
if (constraint != null && constraint.getDomains().isPresent()) {
if (!constraint.getDomains().get().isEmpty()) {
// Predicate is pushed down
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "Cannot push down table scan with predicates pushed down");
}
}
TableHandle tableHandle = node.getTable();
JdbcTableHandle jdbcTableHandle = (JdbcTableHandle) node.getTable().getConnectorHandle();
checkArgument(!jdbcTableHandle.getGeneratedSql().isPresent(), "Jdbc tableHandle should not have sql before pushdown");
LinkedHashMap<String, Selection> selections = new LinkedHashMap<>();
node.getOutputSymbols().forEach(outputColumn -> {
JdbcColumnHandle jdbcColumn = (JdbcColumnHandle) node.getAssignments().get(outputColumn);
selections.put(outputColumn.getName(), new Selection(jdbcColumn.getColumnName(), outputColumn.getName()));
});
StringBuilder table = new StringBuilder();
if (!isNullOrEmpty(jdbcTableHandle.getCatalogName())) {
table.append(quote(quote, jdbcTableHandle.getCatalogName())).append('.');
}
if (!isNullOrEmpty(jdbcTableHandle.getSchemaName())) {
table.append(quote(quote, jdbcTableHandle.getSchemaName())).append('.');
}
table.append(quote(quote, jdbcTableHandle.getTableName()));
JdbcQueryGeneratorContext.Builder contextBuilder = new JdbcQueryGeneratorContext.Builder()
.setCatalogName(Optional.of(tableHandle.getCatalogName()))
.setTransaction(Optional.of(tableHandle.getTransaction()))
.setSchemaTableName(Optional.of(jdbcTableHandle.getSchemaTableName()))
.setSelections(selections)
.setFrom(Optional.of(table.toString()));
// If LIMIT has been push down, add it to context
if (jdbcTableHandle.getLimit().isPresent()) {
contextBuilder.setLimit(jdbcTableHandle.getLimit());
contextBuilder.setHasPushDown(true);
}
return Optional.of(contextBuilder.build());
}
@Override
public Optional<JdbcQueryGeneratorContext> visitWindow(WindowNode node, Void contextIn)
{
checkAvailable(node);
Optional<JdbcQueryGeneratorContext> sourceContext = node.getSource().accept(this, contextIn);
if (!sourceContext.isPresent()) {
return Optional.empty();
}
JdbcQueryGeneratorContext context = sourceContext.get();
List<String> partitionBy = node.getPartitionBy().stream().map(Symbol::getName).collect(toList());
Optional<String> orderBy = Optional.empty();
if (node.getOrderingScheme().isPresent()) {
OrderingScheme scheme = node.getOrderingScheme().get();
orderBy = Optional.of(statementWriter.orderBy("", scheme.getOrderBy().stream()
.map(symbol -> new OrderBy(symbol.getName(), scheme.getOrdering(symbol)))
.collect(toList())));
}
LinkedHashMap<String, Selection> newSelections = new LinkedHashMap<>(getProjectSelections(context.getSelections()));
for (Map.Entry<Symbol, WindowNode.Function> functionEntry : node.getWindowFunctions().entrySet()) {
Symbol windowFunctionColumnName = functionEntry.getKey();
WindowNode.Function windowFunction = functionEntry.getValue();
WindowNode.Frame frame = windowFunction.getFrame();
io.prestosql.spi.sql.expression.Types.WindowFrameType windowFrameType;
if (frame.getType() == Types.WindowFrameType.RANGE) {
windowFrameType = io.prestosql.spi.sql.expression.Types.WindowFrameType.RANGE;
}
else if (frame.getType() == Types.WindowFrameType.ROWS) {
windowFrameType = io.prestosql.spi.sql.expression.Types.WindowFrameType.ROWS;
}
else {
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "Does not support unknown frame type in " + node.getClass().getName());
}
Optional<String> startBound;
Optional<String> endBound;
Types.FrameBoundType startType = frame.getStartType();
Types.FrameBoundType endType = frame.getEndType();
if (frame.getStartValue().isPresent() && frame.getOriginalEndValue().isPresent()) {
if (!frame.getOriginalStartValue().isPresent() || !frame.getOriginalEndValue().isPresent()) {
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "Does not support unknown 2 frame bound value in " + node.getClass().getName());
}
Optional<String> startValue = Optional.of(frame.getOriginalStartValue().get());
startBound = Optional.of(frameBound(startType, startValue));
Optional<String> endValue = Optional.of(frame.getOriginalEndValue().get());
endBound = Optional.of(frameBound(endType, endValue));
}
else if (frame.getStartValue().isPresent() && !frame.getEndValue().isPresent()) {
if (!frame.getOriginalStartValue().isPresent()) {
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "Does not support start frame bound value in " + node.getClass().getName());
}
Optional<String> startValue = Optional.of(frame.getOriginalStartValue().get());
startBound = Optional.of(frameBound(startType, startValue));
endBound = Optional.of(frameBound(endType, Optional.empty()));
}
else if (!frame.getStartValue().isPresent() && !frame.getEndValue().isPresent()) {
startBound = Optional.of(frameBound(startType, Optional.empty()));
endBound = Optional.of(frameBound(endType, Optional.empty()));
}
else {
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "Does not support unknown frame start and end value in " + node.getClass().getName());
}
Optional<String> frameStr = startBound.map(s -> statementWriter.windowFrame(windowFrameType, s, endBound));
List<RowExpression> expArgs = windowFunction.getArguments();
List<String> functionArgs = expArgs.stream().map(expression -> expression.accept(converter, null)).collect(toList());
String columnStr = statementWriter.window(windowFunction.getSignature().getName(), functionArgs, partitionBy, orderBy, frameStr);
newSelections.put(windowFunctionColumnName.toString(), new Selection(columnStr, windowFunctionColumnName.toString()));
}
return Optional.of(buildAsNewTable(context)
.setFrom(getDerivedTable(buildSql(context).getSql(), derivedTableIdentifier++))
.setHasPushDown(true)
.setSelections(newSelections)
.setOutputColumns(node.getOutputSymbols())
.build());
}
@Override
public Optional<JdbcQueryGeneratorContext> visitLimit(LimitNode node, Void contextIn)
{
checkAvailable(node);
if (node.isPartial()) {
throw new PrestoException(NOT_SUPPORTED, "Jdbc query generator cannot handle partial limit");
}
Optional<JdbcQueryGeneratorContext> sourceContext = node.getSource().accept(this, contextIn);
if (!sourceContext.isPresent()) {
return Optional.empty();
}
JdbcQueryGeneratorContext context = sourceContext.get();
return Optional.of(buildFrom(context)
.setHasPushDown(true)
.setLimit(OptionalLong.of(node.getCount()))
.setOutputColumns(node.getOutputSymbols())
.build());
}
@Override
public Optional<JdbcQueryGeneratorContext> visitTopN(TopNNode node, Void contextIn)
{
checkAvailable(node);
Optional<JdbcQueryGeneratorContext> sourceContext = node.getSource().accept(this, contextIn);
if (!sourceContext.isPresent()) {
return Optional.empty();
}
if (!node.getStep().equals(TopNNode.Step.SINGLE)) {
throw new PrestoException(NOT_SUPPORTED, "JDBC query generator can only push single logical topN");
}
JdbcQueryGeneratorContext context = sourceContext.get();
OrderingScheme scheme = node.getOrderingScheme();
return Optional.of(buildAsNewTable(context)
.setFrom(getDerivedTable(buildSql(context).getSql(), derivedTableIdentifier++))
.setHasPushDown(true)
.setSelections(getProjectSelections(context.getSelections()))
.setLimit(OptionalLong.of(node.getCount()))
.setOrderBy(Optional.of(scheme.getOrderBy().stream()
.map(symbol -> new OrderBy(symbol.getName(), scheme.getOrdering(symbol)))
.collect(toList())))
.setOutputColumns(node.getOutputSymbols())
.build());
}
@Override
public Optional<JdbcQueryGeneratorContext> visitGroupId(GroupIdNode node, Void contextIn)
{
checkAvailable(node);
Optional<JdbcQueryGeneratorContext> sourceContext = node.getSource().accept(this, contextIn);
if (!sourceContext.isPresent()) {
return Optional.empty();
}
JdbcQueryGeneratorContext.GroupIdNodeInfo groupIdNodeInfo = sourceContext.get().getGroupIdNodeInfo();
String groupingsSets = statementWriter.groupingsSets(node.getGroupingSets().stream()
.map(list -> list.stream().map(Symbol::getName).collect(toList()))
.collect(toList()));
Symbol groupIdSymbol = node.getGroupIdSymbol();
groupIdNodeInfo.getGroupingElementStore().put(groupIdSymbol, groupingsSets);
LinkedHashMap<String, Selection> newSelections = new LinkedHashMap<>();
node.getGroupingColumns().forEach((key, value) -> newSelections.put(key.getName(), new Selection(value.getName(), key.getName())));
node.getAggregationArguments().forEach(symbol -> newSelections.put(symbol.getName(), new Selection(symbol.getName())));
return Optional.of(buildAsNewTable(sourceContext.get())
.setFrom(getDerivedTable(buildSql(sourceContext.get()).getSql(), derivedTableIdentifier++))
.setGroupIdNodeInfo(groupIdNodeInfo)
.setSelections(newSelections)
.build());
}
protected void checkAvailable(PlanNode node)
{
if (!pushDownModule.isAvailable(node)) {
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, format("The node [%s] is not support to push down in mode [%s]", node.getClass().getSimpleName(), pushDownModule));
}
}
}
}

View File

@ -0,0 +1,310 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.google.common.base.Joiner;
import io.airlift.slice.Slice;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.ConstantExpression;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.relation.SpecialForm;
import io.prestosql.spi.relation.VariableReferenceExpression;
import io.prestosql.spi.sql.RowExpressionConverter;
import io.prestosql.spi.type.BigintType;
import io.prestosql.spi.type.BooleanType;
import io.prestosql.spi.type.CharType;
import io.prestosql.spi.type.DecimalType;
import io.prestosql.spi.type.DoubleType;
import io.prestosql.spi.type.IntegerType;
import io.prestosql.spi.type.RealType;
import io.prestosql.spi.type.SmallintType;
import io.prestosql.spi.type.TimestampType;
import io.prestosql.spi.type.TinyintType;
import io.prestosql.spi.type.Type;
import io.prestosql.spi.type.VarcharType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.IntStream;
import static com.google.common.base.Preconditions.checkState;
import static io.prestosql.spi.StandardErrorCode.NOT_SUPPORTED;
import static io.prestosql.spi.function.Signature.unmangleOperator;
import static io.prestosql.spi.function.StandardFunctionUtils.isArithmeticFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isArrayConstructor;
import static io.prestosql.spi.function.StandardFunctionUtils.isCastFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isComparisonFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isLikeFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isNegateFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isNotFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isOperator;
import static io.prestosql.spi.function.StandardFunctionUtils.isSubscriptFunction;
import static io.prestosql.spi.function.StandardFunctionUtils.isTryFunction;
import static io.prestosql.spi.sql.RowExpressionUtils.isDeterministic;
import static io.prestosql.spi.type.Decimals.decodeUnscaledValue;
import static java.lang.Float.intBitsToFloat;
import static java.lang.String.format;
import static java.util.Locale.ENGLISH;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
public class BaseJdbcRowExpressionConverter
implements RowExpressionConverter
{
public static final String COUNT_FUNCTION_NAME = "count";
protected static final String LIKE_PATTERN_NAME = "LikePattern";
private static final String INTERNAL_FUNCTION_PREFIX = "$";
private static final String TIMESTAMP_LITERAL = "$literal$timestamp";
private static final String DYNAMIC_FILTER_FUNCTION_NAME = "$internal$dynamic_filter_function";
private final Map<String, Integer> blacklistFunctions;
private final RowExpressionService rowExpressionService;
public BaseJdbcRowExpressionConverter(RowExpressionService rowExpressionService)
{
this(rowExpressionService, Collections.emptyMap());
}
public BaseJdbcRowExpressionConverter(RowExpressionService rowExpressionService, Map<String, Integer> blacklistFunctions)
{
this.rowExpressionService = rowExpressionService;
requireNonNull(blacklistFunctions, "BlackListFunctions cannot be null");
this.blacklistFunctions = blacklistFunctions;
}
@Override
public String visitCall(CallExpression call, Void context)
{
if (!isDeterministic(rowExpressionService.getDeterminismEvaluator(), call)) {
throw new PrestoException(NOT_SUPPORTED, format("Unsupported not deterministic function [%s] push down.", call.getSignature().getName()));
}
Signature signature = call.getSignature();
String functionName = call.getSignature().getName().toLowerCase(ENGLISH);
if (isNotFunction(signature)) {
return format("(NOT %s)", call.getArguments().get(0).accept(this, null));
}
if (isTryFunction(signature)) {
return format("TRY(%s)", call.getArguments().get(0).accept(this, null));
}
if (isLikeFunction(signature)) {
return format("(%s LIKE %s)",
call.getArguments().get(0).accept(this, null),
call.getArguments().get(1).accept(this, null));
}
if (isArrayConstructor(signature)) {
String arguments = Joiner.on(",").join(call.getArguments().stream().map(expression -> expression.accept(this, null)).collect(toList()));
return format("ARRAY[%s]", arguments);
}
if (isOperator(signature)) {
return handleOperator(call);
}
if (functionName.equals(TIMESTAMP_LITERAL)) {
long time = (long) ((ConstantExpression) call.getArguments().get(0)).getValue();
return format("TIMESTAMP '%s'", new Timestamp(time));
}
return handleFunction(call);
}
@Override
public String visitSpecialForm(SpecialForm specialForm, Void context)
{
switch (specialForm.getForm()) {
case AND:
case OR:
return format("(%s %s %s)",
specialForm.getArguments().get(0).accept(this, null),
specialForm.getForm().toString(),
specialForm.getArguments().get(1).accept(this, null));
case IS_NULL:
return format("(%s IS NULL)", specialForm.getArguments().get(0).accept(this, null));
case NULL_IF:
return format("NULLIF(%s, %s)",
specialForm.getArguments().get(0).accept(this, null),
specialForm.getArguments().get(1).accept(this, null));
case IN:
String value = specialForm.getArguments().get(0).accept(this, null);
String valueList = Joiner.on(", ").join(IntStream.range(1, specialForm.getArguments().size())
.mapToObj(i -> specialForm.getArguments().get(i))
.map(expression -> expression.accept(this, null))
.collect(toList()));
return format("(%s IN (%s))", value, valueList);
case BETWEEN:
return format("(%s BETWEEN %s AND %s)",
specialForm.getArguments().get(0).accept(this, null),
specialForm.getArguments().get(1).accept(this, null),
specialForm.getArguments().get(2).accept(this, null));
case ROW_CONSTRUCTOR:
return format("ROW (%s)", Joiner.on(", ").join(specialForm.getArguments().stream()
.map(expression -> expression.accept(this, null))
.collect(toList())));
case COALESCE:
String argument = Joiner.on(",")
.join(specialForm.getArguments().stream()
.map(expression -> expression.accept(this, null))
.collect(toList()));
return format("COALESCE(%s)", argument);
case IF:
// convert IF to [case ... when ... else] expression
return format("IF (%s, %s, %s)",
specialForm.getArguments().get(0).accept(this, null),
specialForm.getArguments().get(1).accept(this, null),
specialForm.getArguments().get(2).accept(this, null));
case SWITCH:
int size = specialForm.getArguments().size();
return format("(CASE %s %s ELSE %s END)",
specialForm.getArguments().get(0).accept(this, null),
Joiner.on(' ').join(IntStream.range(1, size - 1)
.mapToObj(i -> specialForm.getArguments().get(i).accept(this, null))
.collect(toList())),
specialForm.getArguments().get(size - 1).accept(this, null));
case WHEN:
return format("WHEN %s THEN %s",
specialForm.getArguments().get(0).accept(this, null),
specialForm.getArguments().get(1).accept(this, null));
case DEREFERENCE:
return format("%s.%s",
specialForm.getArguments().get(0).accept(this, null),
specialForm.getArguments().get(1).accept(this, null));
default:
throw new PrestoException(NOT_SUPPORTED, String.format("specialForm %s not supported in filter", specialForm.getForm()));
}
}
@Override
public String visitConstant(ConstantExpression literal, Void context)
{
Type type = literal.getType();
if (literal.getValue() == null) {
return "null";
}
if (type instanceof BooleanType) {
return String.valueOf(((Boolean) literal.getValue()).booleanValue());
}
if (type instanceof BigintType || type instanceof TinyintType || type instanceof SmallintType || type instanceof IntegerType) {
Number number = (Number) literal.getValue();
return format("%d", number.longValue());
}
if (type instanceof DoubleType) {
return literal.getValue().toString();
}
if (type instanceof RealType) {
Long number = (Long) literal.getValue();
return format("%f", intBitsToFloat(number.intValue()));
}
if (type instanceof DecimalType) {
DecimalType decimalType = (DecimalType) type;
if (decimalType.isShort()) {
checkState(literal.getValue() instanceof Long);
return decodeDecimal(BigInteger.valueOf((long) literal.getValue()), decimalType).toString();
}
checkState(literal.getValue() instanceof Slice);
Slice value = (Slice) literal.getValue();
return decodeDecimal(decodeUnscaledValue(value), decimalType).toString();
}
if (type instanceof VarcharType || type instanceof CharType) {
return "'" + ((Slice) literal.getValue()).toStringUtf8() + "'";
}
if (type instanceof TimestampType) {
Long time = (Long) literal.getValue();
return format("TIMESTAMP '%s'", new Timestamp(time));
}
throw new PrestoException(NOT_SUPPORTED, String.format("Cannot handle the constant expression %s with value of type %s", literal.getValue(), type));
}
@Override
public String visitVariableReference(VariableReferenceExpression reference, Void context)
{
return reference.getName();
}
private String handleOperator(CallExpression call)
{
Signature signature = call.getSignature();
List<RowExpression> arguments = call.getArguments();
if (isCastFunction(signature)) {
if (call.getType().getDisplayName().equals(LIKE_PATTERN_NAME)) {
return arguments.get(0).accept(this, null);
}
return format("CAST(%s AS %s)", arguments.get(0).accept(this, null), call.getType().getDisplayName());
}
if (call.getArguments().size() == 1 && isNegateFunction(signature)) {
String value = call.getArguments().get(0).accept(this, null);
String separator = value.startsWith("-") ? " " : "";
return format("-%s%s", separator, value);
}
if (arguments.size() == 2 && (isComparisonFunction(signature) || isArithmeticFunction(signature))) {
return format(
"(%s %s %s)",
arguments.get(0).accept(this, null),
unmangleOperator(signature.getName()).getOperator(),
arguments.get(1).accept(this, null));
}
if (isSubscriptFunction(signature)) {
String base = call.getArguments().get(0).accept(this, null);
String index = call.getArguments().get(1).accept(this, null);
return format("%s[%s]", base, index);
}
throw new PrestoException(NOT_SUPPORTED, String.format("Unknown operator %s in push down", signature));
}
private String handleFunction(CallExpression callExpression)
{
Signature function = callExpression.getSignature();
List<RowExpression> arguments = callExpression.getArguments();
if (isBlackListFunction(function)) {
if (DYNAMIC_FILTER_FUNCTION_NAME.equals(function.getName())) {
return "true";
}
throw new PrestoException(NOT_SUPPORTED, String.format("Unsupported function in push down %s", function.getName()));
}
if (function.getName().equals(COUNT_FUNCTION_NAME) && callExpression.getArguments().size() == 0) {
return "count(*)";
}
else {
StringBuilder builder = new StringBuilder(function.getName());
StringJoiner joiner = new StringJoiner(", ", "(", ")");
for (RowExpression expression : arguments) {
joiner.add(expression.accept(this, null));
}
builder.append(joiner);
return builder.toString();
}
}
private boolean isBlackListFunction(Signature signature)
{
if (signature.getName().contains(INTERNAL_FUNCTION_PREFIX)) {
return true;
}
Integer args = blacklistFunctions.get(signature.getName());
return args != null && (args < 0 || signature.getArgumentTypes().size() == args);
}
private static Number decodeDecimal(BigInteger unscaledValue, DecimalType type)
{
return new BigDecimal(unscaledValue, type.getScale(), new MathContext(type.getPrecision()));
}
}

View File

@ -0,0 +1,208 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import io.prestosql.spi.block.SortOrder;
import io.prestosql.spi.function.OperatorType;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.VariableReferenceExpression;
import io.prestosql.spi.sql.RowExpressionConverter;
import io.prestosql.spi.sql.SqlStatementWriter;
import io.prestosql.spi.sql.expression.OrderBy;
import io.prestosql.spi.sql.expression.Selection;
import io.prestosql.spi.sql.expression.Types;
import io.prestosql.spi.type.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.DERIVED_TABLE_PREFIX;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.JOIN_LEFT_TABLE_PREFIX;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.JOIN_RIGHT_TABLE_PREFIX;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.parentheses;
public class BaseJdbcSqlStatementWriter
implements SqlStatementWriter
{
private static final String COUNT_FUNCTION_NAME = "count";
private final boolean nameCaseInsensitive;
public BaseJdbcSqlStatementWriter(JdbcPushDownParameter pushDownParameter)
{
this.nameCaseInsensitive = pushDownParameter.getCaseInsensitiveParameter();
}
@Override
public String select(List<Selection> selections)
{
StringBuilder builder = new StringBuilder("SELECT ");
if (selections == null || selections.size() == 0) {
builder.append("null");
}
else {
StringJoiner joiner = new StringJoiner(", ");
for (Selection selection : selections) {
if (selection.isAliased(nameCaseInsensitive)) {
joiner.add(selection.getExpression() + " AS " + selection.getAlias());
}
else {
joiner.add(selection.getExpression());
}
}
builder.append(joiner);
}
return builder.toString();
}
@Override
public String from(String selections, String from)
{
return selections + " FROM " + from;
}
@Override
public String filter(String table, String predicate)
{
return table + " WHERE " + predicate;
}
@Override
public String groupBy(String table, Set<String> groupBy)
{
StringJoiner joiner = new StringJoiner(", ");
for (String symbol : groupBy) {
joiner.add(symbol);
}
return table + " GROUP BY " + joiner.toString();
}
@Override
public String orderBy(String table, List<OrderBy> orderings)
{
StringJoiner joiner = new StringJoiner(", ");
for (OrderBy orderBy : orderings) {
StringJoiner orderItem = new StringJoiner(" ");
orderItem.add(orderBy.getSymbol());
SortOrder sortOrder = orderBy.getType();
orderItem.add(sortOrder.isAscending() ? "ASC" : "DESC");
orderItem.add(sortOrder.isNullsFirst() ? "NULLS FIRST" : "NULLS LAST");
joiner.merge(orderItem);
}
return table + " ORDER BY " + joiner.toString();
}
@Override
public String limit(String table, long count)
{
return table + " LIMIT " + count;
}
@Override
public String windowFrame(Types.WindowFrameType type, String start, Optional<String> end)
{
StringBuilder builder = new StringBuilder();
builder.append(type.toString()).append(' ');
if (end.isPresent()) {
builder.append("BETWEEN ")
.append(start)
.append(" AND ")
.append(end.get());
}
else {
builder.append(start);
}
return builder.toString();
}
@Override
public String window(String functionName, List<String> functionArgs, List<String> partitionBy, Optional<String> orderBy, Optional<String> frame)
{
List<String> parts = new ArrayList<>();
if (!partitionBy.isEmpty()) {
parts.add("PARTITION BY " + Joiner.on(", ").join(partitionBy));
}
orderBy.ifPresent(parts::add);
frame.ifPresent(parts::add);
String windows = '(' + Joiner.on(' ').join(parts) + ')';
String arguments = (functionArgs.size() == 0 && functionName.equals(COUNT_FUNCTION_NAME)) ? "*" : Joiner.on(", ").join(functionArgs);
return functionName + '(' + arguments + ')' + " OVER " + windows;
}
@Override
public String aggregation(String functionName, List<String> arguments, boolean isDistinct)
{
String params = (arguments.size() == 0 && functionName.equals(COUNT_FUNCTION_NAME)) ? "*" : Joiner.on(", ").join(arguments);
if (isDistinct) {
params = "DISTINCT " + params;
}
return functionName + parentheses(params);
}
@Override
public String castAggregationType(String aggregationExpression, RowExpressionConverter converter, Type returnType)
{
VariableReferenceExpression aggVariable = new VariableReferenceExpression(aggregationExpression, returnType);
Signature castSignature = Signature.internalOperator(OperatorType.CAST, returnType.getTypeSignature(), returnType.getTypeSignature());
return converter.visitCall(new CallExpression(castSignature, returnType, ImmutableList.of(aggVariable)), null);
}
@Override
public String join(String joinType, String leftTable, String rightTable, List<String> criteria, Optional<String> filter, int identifier)
{
StringBuilder builder = new StringBuilder();
builder.append(parentheses(leftTable)).append(' ').append(JOIN_LEFT_TABLE_PREFIX).append(identifier)
.append(' ').append(joinType).append(' ')
.append(parentheses(rightTable)).append(' ').append(JOIN_RIGHT_TABLE_PREFIX).append(identifier);
if (!criteria.isEmpty() || filter.isPresent()) {
builder.append(" ON ");
StringJoiner joiner = new StringJoiner(" AND ");
criteria.forEach(joiner::add);
filter.ifPresent(joiner::add);
builder.append(joiner);
}
return parentheses(builder.toString());
}
@Override
public String union(List<String> relations, int identifier)
{
StringJoiner joiner = new StringJoiner(" UNION ALL ", "(", ") " + DERIVED_TABLE_PREFIX + identifier);
for (String relation : relations) {
joiner.add(parentheses(relation));
}
return joiner.toString();
}
@Override
public String groupingsSets(List<List<String>> groupSets)
{
StringJoiner joiner = new StringJoiner(", ", "(", ")");
for (List<String> group : groupSets) {
joiner.add(parentheses(Joiner.on(", ").join(group)));
}
return " GROUPING SETS " + joiner.toString();
}
}

View File

@ -0,0 +1,291 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.airlift.log.Logger;
import io.prestosql.plugin.jdbc.BaseJdbcConfig;
import io.prestosql.plugin.jdbc.JdbcClient;
import io.prestosql.plugin.jdbc.JdbcColumnHandle;
import io.prestosql.plugin.jdbc.JdbcTableHandle;
import io.prestosql.plugin.jdbc.optimization.JdbcQueryGeneratorResult.GeneratedSql;
import io.prestosql.spi.ConnectorPlanOptimizer;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.SymbolAllocator;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.ConnectorSession;
import io.prestosql.spi.function.OperatorType;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.metadata.TableHandle;
import io.prestosql.spi.operator.ReuseExchangeOperator;
import io.prestosql.spi.plan.Assignments;
import io.prestosql.spi.plan.FilterNode;
import io.prestosql.spi.plan.GroupIdNode;
import io.prestosql.spi.plan.MarkDistinctNode;
import io.prestosql.spi.plan.PlanNode;
import io.prestosql.spi.plan.PlanNodeIdAllocator;
import io.prestosql.spi.plan.PlanVisitor;
import io.prestosql.spi.plan.ProjectNode;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.plan.TableScanNode;
import io.prestosql.spi.predicate.TupleDomain;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.relation.VariableReferenceExpression;
import io.prestosql.spi.sql.QueryGenerator;
import io.prestosql.spi.sql.RowExpressionUtils;
import io.prestosql.spi.type.Type;
import io.prestosql.spi.type.TypeManager;
import io.prestosql.spi.type.UnknownType;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.getGroupingSetColumn;
import static io.prestosql.plugin.jdbc.optimization.JdbcPlanOptimizerUtils.replaceGroupingSetColumns;
public class JdbcPlanOptimizer
implements ConnectorPlanOptimizer
{
private static final Logger log = Logger.get(JdbcPlanOptimizer.class);
private static final Set<Class<? extends PlanNode>> UNSUPPORTED_ROOT_NODE = ImmutableSet.of(GroupIdNode.class, MarkDistinctNode.class);
private final JdbcClient client;
private final BaseJdbcConfig config;
private final TypeManager typeManager;
private final Optional<QueryGenerator<JdbcQueryGeneratorResult>> queryGenerator;
@Inject
public JdbcPlanOptimizer(
JdbcClient client,
TypeManager typeManager,
BaseJdbcConfig config,
RowExpressionService rowExpressionService)
{
this.client = client;
this.config = config;
this.typeManager = typeManager;
this.queryGenerator = client.getQueryGenerator(rowExpressionService);
}
@Override
public PlanNode optimize(
PlanNode maxSubPlan,
ConnectorSession session,
Map<String, Type> types,
SymbolAllocator symbolAllocator,
PlanNodeIdAllocator idAllocator)
{
if (!config.isPushDownEnable() || !queryGenerator.isPresent()) {
return maxSubPlan;
}
// Some node cannot be push down root node.
if (UNSUPPORTED_ROOT_NODE.contains(maxSubPlan.getClass())) {
return maxSubPlan;
}
return maxSubPlan.accept(new Visitor(idAllocator, types, session, symbolAllocator), null);
}
private static PlanNode replaceChildren(PlanNode node, List<PlanNode> children)
{
for (int i = 0; i < node.getSources().size(); i++) {
if (children.get(i) != node.getSources().get(i)) {
return node.replaceChildren(children);
}
}
return node;
}
private class Visitor
extends PlanVisitor<PlanNode, Void>
{
private final PlanNodeIdAllocator idAllocator;
private final ConnectorSession session;
private final Map<String, Type> types;
private final SymbolAllocator symbolAllocator;
private final IdentityHashMap<FilterNode, Void> filtersSplitUp = new IdentityHashMap<>();
public Visitor(
PlanNodeIdAllocator idAllocator,
Map<String, Type> types,
ConnectorSession session,
SymbolAllocator symbolAllocator)
{
this.idAllocator = idAllocator;
this.types = types;
this.session = session;
this.symbolAllocator = symbolAllocator;
}
@Override
public PlanNode visitPlan(PlanNode node, Void context)
{
Optional<PlanNode> pushDownPlan = tryCreatingNewScanNode(node);
return pushDownPlan.orElseGet(() -> replaceChildren(
node, node.getSources().stream().map(source -> source.accept(this, null)).collect(toImmutableList())));
}
@Override
public PlanNode visitFilter(FilterNode node, Void context)
{
if (filtersSplitUp.containsKey(node)) {
return this.visitPlan(node, context);
}
filtersSplitUp.put(node, null);
FilterNode nodeToRecurseInto = node;
List<RowExpression> pushable = new ArrayList<>();
List<RowExpression> nonPushable = new ArrayList<>();
for (RowExpression conjunct : RowExpressionUtils.extractConjuncts(node.getPredicate())) {
try {
conjunct.accept(queryGenerator.get().getConverter(), null);
pushable.add(conjunct);
}
catch (PrestoException pe) {
nonPushable.add(conjunct);
}
}
if (!pushable.isEmpty()) {
FilterNode pushableFilter = new FilterNode(idAllocator.getNextId(), node.getSource(), RowExpressionUtils.combineConjuncts(pushable));
Optional<FilterNode> nonPushableFilter = nonPushable.isEmpty() ? Optional.empty() : Optional.of(new FilterNode(idAllocator.getNextId(), pushableFilter, RowExpressionUtils.combineConjuncts(nonPushable)));
filtersSplitUp.put(pushableFilter, null);
if (nonPushableFilter.isPresent()) {
FilterNode nonPushableFilterNode = nonPushableFilter.get();
filtersSplitUp.put(nonPushableFilterNode, null);
nodeToRecurseInto = nonPushableFilterNode;
}
else {
nodeToRecurseInto = pushableFilter;
}
}
return this.visitFilter(nodeToRecurseInto, context);
}
private Optional<PlanNode> tryCreatingNewScanNode(PlanNode node)
{
Optional<JdbcQueryGeneratorResult> result = queryGenerator.get().generate(node, typeManager);
if (!result.isPresent()) {
return Optional.empty();
}
Map<String, ColumnHandle> columns;
JdbcQueryGeneratorContext context = result.get().getContext();
GeneratedSql generatedSql = result.get().getGeneratedSql();
if (!generatedSql.isPushDown()) {
return Optional.empty();
}
JdbcQueryGeneratorContext.GroupIdNodeInfo groupIdNodeInfo = context.getGroupIdNodeInfo();
String sql = generatedSql.getSql();
// replace grouping sets column
if (groupIdNodeInfo.isGroupByComplexOperation()) {
sql = replaceGroupingSetColumns(sql);
}
try {
columns = client.getColumns(session, sql, types);
}
catch (PrestoException e) {
log.warn("query push down failed for [%s]", e.getMessage());
return Optional.empty();
}
if (columns.isEmpty()) {
log.debug("Get columns from generated sql failed.");
return Optional.empty();
}
ImmutableList.Builder<Symbol> scanOutputs = new ImmutableList.Builder<>();
ImmutableMap.Builder<Symbol, ColumnHandle> columnHandles = new ImmutableMap.Builder<>();
ImmutableMap.Builder<Symbol, RowExpression> assignments = new ImmutableMap.Builder<>();
for (Symbol symbol : node.getOutputSymbols()) {
String name = symbol.getName().toLowerCase(Locale.ENGLISH);
String aliasName = groupIdNodeInfo.isGroupByComplexOperation()
? getGroupingSetColumn(name)
: name;
if (!types.containsKey(name) || !columns.containsKey(aliasName)) {
log.debug("Get type of column [%s] failed", name);
return Optional.empty();
}
Type prestoType = types.get(name);
Type jdbcType = ((JdbcColumnHandle) columns.get(aliasName)).getColumnType();
if (prestoType.equals(jdbcType)) {
scanOutputs.add(symbol);
columnHandles.put(symbol, columns.get(aliasName));
assignments.put(symbol, new VariableReferenceExpression(symbol.getName(), prestoType));
}
else {
if (prestoType instanceof UnknownType) {
log.debug("Can't cast from type[%s] to type[%s]", jdbcType.getDisplayName(), prestoType.getDisplayName());
return Optional.empty();
}
// If Jdbc return a different type from Presto's expected type, add a CAST expression
Symbol scanSymbol = symbolAllocator.newSymbol(symbol.getName(), jdbcType);
scanOutputs.add(scanSymbol);
columnHandles.put(scanSymbol, columns.get(aliasName));
assignments.put(symbol, new CallExpression(
Signature.internalOperator(OperatorType.CAST, prestoType.getTypeSignature(), ImmutableList.of(jdbcType.getTypeSignature())),
prestoType,
ImmutableList.of(new VariableReferenceExpression(scanSymbol.getName(), jdbcType))));
}
}
checkState(context.getCatalogName().isPresent(), "CatalogName is null");
checkState(context.getSchemaTableName().isPresent(), "schemaTableName is null");
checkState(context.getTransaction().isPresent(), "transaction is null");
TableHandle newTableHandle = new TableHandle(
context.getCatalogName().get(),
new JdbcTableHandle(
context.getSchemaTableName().get(),
context.getCatalogName().get().getCatalogName(),
context.getSchemaTableName().get().getSchemaName(),
context.getSchemaTableName().get().getTableName(),
TupleDomain.all(),
OptionalLong.empty(),
Optional.of(new GeneratedSql(sql, true))),
context.getTransaction().get(),
Optional.empty());
return Optional.of(
new ProjectNode(
this.idAllocator.getNextId(),
new TableScanNode(
idAllocator.getNextId(),
newTableHandle,
scanOutputs.build(),
columnHandles.build(),
TupleDomain.all(),
Optional.empty(),
ReuseExchangeOperator.STRATEGY.REUSE_STRATEGY_DEFAULT,
0,
0),
new Assignments(assignments.build())));
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.google.common.collect.ImmutableSet;
import io.prestosql.spi.ConnectorPlanOptimizer;
import io.prestosql.spi.connector.ConnectorPlanOptimizerProvider;
import java.util.Set;
public class JdbcPlanOptimizerProvider
implements ConnectorPlanOptimizerProvider
{
private final ConnectorPlanOptimizer planOptimizer;
public JdbcPlanOptimizerProvider(ConnectorPlanOptimizer planOptimizer)
{
this.planOptimizer = planOptimizer;
}
@Override
public Set<ConnectorPlanOptimizer> getLogicalPlanOptimizers()
{
return ImmutableSet.of(planOptimizer);
}
@Override
public Set<ConnectorPlanOptimizer> getPhysicalPlanOptimizers()
{
return ImmutableSet.of();
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.connector.CatalogName;
import io.prestosql.spi.plan.AggregationNode;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.sql.expression.Selection;
import io.prestosql.spi.sql.expression.Types;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_QUERY_GENERATOR_FAILURE;
import static io.prestosql.spi.StandardErrorCode.NOT_SUPPORTED;
import static java.util.stream.Collectors.toList;
public class JdbcPlanOptimizerUtils
{
public static final String GROUPING_COLUMN_SUFFIX = "$gid";
public static final String REPLACED_GROUPING_COLUMN_SUFFIX = "_gid";
public static final String DISTINCT_SUFFIX = "$distinct";
public static final String DERIVED_TABLE_PREFIX = "hetu_table_";
public static final String JOIN_LEFT_TABLE_PREFIX = "hetu_left_";
public static final String JOIN_RIGHT_TABLE_PREFIX = "hetu_right_";
private JdbcPlanOptimizerUtils() {}
public static List<Selection> getSelectionsFromSymbolsMap(Map<Symbol, Symbol> symbols)
{
return symbols.entrySet().stream().map(entry -> new Selection(entry.getValue().getName(), entry.getKey().getName())).collect(toList());
}
public static boolean isSameCatalog(List<JdbcQueryGeneratorContext> contexts)
{
if (contexts == null || contexts.isEmpty()) {
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "context is null or empty");
}
CatalogName catalog = contexts.get(0).getCatalogName().get();
for (JdbcQueryGeneratorContext context : contexts) {
if (!context.getCatalogName().get().equals(catalog)) {
return false;
}
}
return true;
}
public static String parentheses(String inputString)
{
return "(" + inputString + ")";
}
public static Optional<String> getDerivedTable(String tableExpression, int identifier)
{
return Optional.of(parentheses(tableExpression) + " " + DERIVED_TABLE_PREFIX + identifier);
}
public static String frameBound(Types.FrameBoundType type, Optional<String> value)
{
switch (type) {
case UNBOUNDED_PRECEDING:
return "UNBOUNDED PRECEDING";
case PRECEDING:
if (!value.isPresent()) {
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "Unsupported empty value in " + type);
}
return value.get() + " PRECEDING";
case CURRENT_ROW:
return "CURRENT ROW";
case FOLLOWING:
if (!value.isPresent()) {
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "Unsupported empty value in " + type);
}
return value.get() + " FOLLOWING";
case UNBOUNDED_FOLLOWING:
return "UNBOUNDED FOLLOWING";
}
throw new PrestoException(JDBC_QUERY_GENERATOR_FAILURE, "unhandled type: " + type);
}
public static String quote(String quote, String name)
{
name = name.replace(quote, quote + quote);
return quote + name + quote;
}
public static boolean isAggregationDistinct(AggregationNode.Aggregation aggregation)
{
if (aggregation.isDistinct()) {
return true;
}
if (aggregation.getMask().isPresent()) {
if (aggregation.getMask().get().getName().contains(DISTINCT_SUFFIX)) {
return true;
}
throw new PrestoException(NOT_SUPPORTED, "Unsupported mask in push down: " + aggregation.getMask().get());
}
return false;
}
public static LinkedHashMap<String, Selection> getProjectSelections(LinkedHashMap<String, Selection> oldSelection)
{
LinkedHashMap<String, Selection> newSelection = new LinkedHashMap<>();
oldSelection.forEach((name, selection) -> newSelection.put(name, new Selection(name)));
return newSelection;
}
public static String replaceGroupingSetColumns(String sql)
{
return sql.replace(GROUPING_COLUMN_SUFFIX, REPLACED_GROUPING_COLUMN_SUFFIX);
}
public static String getGroupingSetColumn(String column)
{
if (column.endsWith(GROUPING_COLUMN_SUFFIX)) {
return column.replace(GROUPING_COLUMN_SUFFIX, REPLACED_GROUPING_COLUMN_SUFFIX);
}
return column;
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.google.common.collect.ImmutableSet;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.plan.AggregationNode;
import io.prestosql.spi.plan.FilterNode;
import io.prestosql.spi.plan.LimitNode;
import io.prestosql.spi.plan.PlanNode;
import io.prestosql.spi.plan.ProjectNode;
import io.prestosql.spi.plan.TableScanNode;
import io.prestosql.spi.plan.TopNNode;
import java.util.Set;
import static io.prestosql.spi.StandardErrorCode.NOT_SUPPORTED;
/**
* Jdbc Query Push Down Module
*/
public enum JdbcPushDownModule
{
/**
* Default Module
*/
DEFAULT,
/**
* Push down all supported PlanNodes to TableScan
*/
FULL_PUSHDOWN,
/**
* Only push down filter, aggregation, limit, topN, project to tableScan
*/
BASE_PUSHDOWN;
private static final Set<Class<? extends PlanNode>> BASE_PUSH_DOWN_NODE = ImmutableSet.of(
FilterNode.class,
AggregationNode.class,
LimitNode.class,
TopNNode.class,
ProjectNode.class,
TableScanNode.class);
public boolean isAvailable(PlanNode node)
{
switch (this) {
case FULL_PUSHDOWN:
return true;
case BASE_PUSHDOWN:
return BASE_PUSH_DOWN_NODE.contains(node.getClass());
default:
throw new PrestoException(NOT_SUPPORTED, "Unsupported push down module");
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
/**
* Push Down parameter module
*/
public class JdbcPushDownParameter
{
private final boolean nameCaseInsensitive;
private final JdbcPushDownModule pushDownModule;
private final String identifierQuote;
public JdbcPushDownParameter(String identifierQuote, boolean nameCaseInsensitive, JdbcPushDownModule pushDownModule)
{
this.identifierQuote = identifierQuote;
this.nameCaseInsensitive = nameCaseInsensitive;
this.pushDownModule = pushDownModule;
}
public String getIdentifierQuote()
{
return identifierQuote;
}
public boolean getCaseInsensitiveParameter()
{
return nameCaseInsensitive;
}
public JdbcPushDownModule getPushDownModuleParameter()
{
return pushDownModule;
}
}

View File

@ -0,0 +1,330 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import io.prestosql.spi.connector.CatalogName;
import io.prestosql.spi.connector.ConnectorTransactionHandle;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.sql.expression.OrderBy;
import io.prestosql.spi.sql.expression.Selection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
import static java.util.Objects.requireNonNull;
public final class JdbcQueryGeneratorContext
{
private final Optional<CatalogName> catalogName;
private final Optional<SchemaTableName> schemaTableName;
private final Optional<ConnectorTransactionHandle> transaction;
private final LinkedHashMap<String, Selection> selections;
private final Set<String> groupByColumns;
private final Optional<String> from;
private final Optional<String> filter;
private final OptionalLong limit;
private final Optional<List<OrderBy>> orderBy;
private final boolean hasPushDown;
private final GroupIdNodeInfo groupIdNodeInfo;
private JdbcQueryGeneratorContext(
Optional<CatalogName> catalogName,
Optional<SchemaTableName> schemaTableName,
Optional<ConnectorTransactionHandle> transaction,
Map<String, Selection> selections,
Optional<String> from,
Set<String> groupByColumns,
Optional<String> filter,
OptionalLong limit,
Optional<List<OrderBy>> orderBy,
GroupIdNodeInfo groupIdNodeInfo,
boolean hasPushDown)
{
this.catalogName = catalogName;
this.schemaTableName = schemaTableName;
this.transaction = transaction;
this.selections = new LinkedHashMap<>(requireNonNull(selections, "selections can't be null"));
this.from = requireNonNull(from, "from can't be null");
this.groupByColumns = new HashSet<>(requireNonNull(groupByColumns, "groupByColumns can't be null. It could be empty if not available."));
this.filter = requireNonNull(filter);
this.limit = requireNonNull(limit, "limit is null");
this.orderBy = orderBy;
this.groupIdNodeInfo = groupIdNodeInfo;
this.hasPushDown = hasPushDown;
}
public Optional<CatalogName> getCatalogName()
{
return catalogName;
}
public Optional<SchemaTableName> getSchemaTableName()
{
return schemaTableName;
}
public Optional<ConnectorTransactionHandle> getTransaction()
{
return transaction;
}
public LinkedHashMap<String, Selection> getSelections()
{
return selections;
}
public Optional<String> getFrom()
{
return from;
}
public Set<String> getGroupByColumns()
{
return groupByColumns;
}
public Optional<String> getFilter()
{
return filter;
}
public OptionalLong getLimit()
{
return limit;
}
public Optional<List<OrderBy>> getOrderBy()
{
return orderBy;
}
public GroupIdNodeInfo getGroupIdNodeInfo()
{
return groupIdNodeInfo;
}
public boolean isHasPushDown()
{
return hasPushDown;
}
@Override
public String toString()
{
return toStringHelper(this)
.add("selections", selections)
.add("from", from)
.add("filter", filter)
.add("limit", limit)
.add("groupByColumns", groupByColumns)
.add("orderingSchema", orderBy)
.toString();
}
public static class GroupIdNodeInfo
{
private boolean isGroupByComplexOperation;
private Map<Symbol, String> groupingElementStore;
GroupIdNodeInfo()
{
this.groupingElementStore = new HashMap<>();
}
public boolean isGroupByComplexOperation()
{
return isGroupByComplexOperation;
}
public void setGroupByComplexOperation(boolean groupByComplexOperation)
{
isGroupByComplexOperation = groupByComplexOperation;
}
public Map<Symbol, String> getGroupingElementStore()
{
return groupingElementStore;
}
public void setGroupingElementStore(Map<Symbol, String> groupingElementStore)
{
this.groupingElementStore = groupingElementStore;
}
}
public static Builder builder()
{
return new Builder();
}
public static Builder buildFrom(JdbcQueryGeneratorContext context)
{
return new Builder(context);
}
public static Builder buildAsNewTable(JdbcQueryGeneratorContext context)
{
return new Builder(context.getCatalogName(), context.getSchemaTableName(), context.getTransaction(), context.getGroupIdNodeInfo());
}
public static final class Builder
{
private Optional<CatalogName> catalogName;
private Optional<SchemaTableName> schemaTableName;
private Optional<ConnectorTransactionHandle> transaction;
private LinkedHashMap<String, Selection> selections = new LinkedHashMap<>();
private Set<String> groupByColumns = new HashSet<>();
private Optional<String> from = Optional.empty();
private Optional<String> filter = Optional.empty();
private OptionalLong limit = OptionalLong.empty();
private Optional<List<OrderBy>> orderBy = Optional.empty();
private GroupIdNodeInfo groupIdNodeInfo = new GroupIdNodeInfo();
private boolean hasPushDown;
public Builder() {}
private Builder(JdbcQueryGeneratorContext context)
{
this.catalogName = context.getCatalogName();
this.schemaTableName = context.getSchemaTableName();
this.transaction = context.getTransaction();
this.selections = context.getSelections();
this.groupByColumns = context.getGroupByColumns();
this.from = context.getFrom();
this.filter = context.getFilter();
this.limit = context.getLimit();
this.orderBy = context.getOrderBy();
this.hasPushDown = context.isHasPushDown();
this.groupIdNodeInfo = context.getGroupIdNodeInfo();
}
private Builder(
Optional<CatalogName> catalogName,
Optional<SchemaTableName> schemaTableName,
Optional<ConnectorTransactionHandle> transaction,
GroupIdNodeInfo groupIdNodeInfo)
{
this.catalogName = catalogName;
this.schemaTableName = schemaTableName;
this.transaction = transaction;
this.groupIdNodeInfo = groupIdNodeInfo;
}
public Builder setCatalogName(Optional<CatalogName> catalogName)
{
this.catalogName = catalogName;
return this;
}
public Builder setSchemaTableName(Optional<SchemaTableName> schemaTableName)
{
this.schemaTableName = schemaTableName;
return this;
}
public Builder setTransaction(Optional<ConnectorTransactionHandle> transaction)
{
this.transaction = transaction;
return this;
}
public Builder setSelections(LinkedHashMap<String, Selection> selections)
{
this.selections = selections;
return this;
}
public Builder setGroupByColumns(Set<String> groupByColumns)
{
this.groupByColumns = groupByColumns;
return this;
}
public Builder setFrom(Optional<String> from)
{
this.from = from;
return this;
}
public Builder setFilter(Optional<String> filter)
{
this.filter = filter;
return this;
}
public Builder setLimit(OptionalLong limit)
{
this.limit = limit;
return this;
}
public Builder setOrderBy(Optional<List<OrderBy>> orderBy)
{
this.orderBy = orderBy;
return this;
}
public Builder setHasPushDown(boolean hasPushDown)
{
this.hasPushDown = hasPushDown;
return this;
}
public Builder setOutputColumns(List<Symbol> outputColumns)
{
LinkedHashMap<String, Selection> newSelections = new LinkedHashMap<>();
for (Symbol out : outputColumns) {
// If column is group id column, skip it
if (groupIdNodeInfo.getGroupingElementStore().containsKey(out)) {
continue;
}
newSelections.put(out.getName(), requireNonNull(selections.get(out.getName()),
"Cannot find the selection " + out.getName() + " in the original context."));
}
this.selections = newSelections;
return this;
}
public Builder setGroupIdNodeInfo(GroupIdNodeInfo groupIdNodeInfo)
{
this.groupIdNodeInfo = groupIdNodeInfo;
return this;
}
public JdbcQueryGeneratorContext build()
{
return new JdbcQueryGeneratorContext(
catalogName,
schemaTableName,
transaction,
selections,
from,
groupByColumns,
filter,
limit,
orderBy,
groupIdNodeInfo,
hasPushDown);
}
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import static com.google.common.base.MoreObjects.toStringHelper;
public class JdbcQueryGeneratorResult
{
private final GeneratedSql generatedSql;
private final JdbcQueryGeneratorContext context;
public JdbcQueryGeneratorResult(
GeneratedSql generatedSql,
JdbcQueryGeneratorContext context)
{
this.generatedSql = generatedSql;
this.context = context;
}
public GeneratedSql getGeneratedSql()
{
return generatedSql;
}
public JdbcQueryGeneratorContext getContext()
{
return context;
}
public static class GeneratedSql
{
private final String sql;
private final boolean isPushDown;
@JsonCreator
public GeneratedSql(
@JsonProperty("sql") String sql,
@JsonProperty("isPushDown") boolean isPushDown)
{
this.sql = sql;
this.isPushDown = isPushDown;
}
@JsonProperty("sql")
public String getSql()
{
return sql;
}
@JsonProperty("isPushDown")
public boolean isPushDown()
{
return isPushDown;
}
@Override
public String toString()
{
return toStringHelper(this)
.add("sql", sql)
.add("isPushDown", isPushDown)
.toString();
}
}
}

View File

@ -1,762 +0,0 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.sql.builder;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import io.prestosql.spi.block.SortOrder;
import io.prestosql.spi.sql.SqlQueryWriter;
import io.prestosql.spi.sql.expression.Operators;
import io.prestosql.spi.sql.expression.OrderBy;
import io.prestosql.spi.sql.expression.QualifiedName;
import io.prestosql.spi.sql.expression.Selection;
import io.prestosql.spi.sql.expression.Time;
import io.prestosql.spi.sql.expression.Types;
import io.prestosql.sql.ExpressionFormatter;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.StringJoiner;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
public class BaseSqlQueryWriter
implements SqlQueryWriter
{
private static final String INTERNAL_FUNCTION_PREFIX = "$";
private static final String DYNAMIC_FILTER_FUNCTION_NAME = "$internal$dynamic_filter_function";
private final ThreadLocal<DecimalFormat> doubleFormatter = ThreadLocal.withInitial(
() -> new DecimalFormat("0.###################E0###", new DecimalFormatSymbols(Locale.US)));
private final Map<String, Integer> blacklistedFunctions;
public BaseSqlQueryWriter()
{
this(Collections.emptyMap());
}
/**
* Create SqlQueryWriter with the blacklisted functions. The blacklisted functions map
* should have the function name in lower case as the key and the expected number of
* parameters as the value. If the function is a variable argument function (can take
* any number of arguments), use a negative number (preferably -1) as the value.
*
* @param blacklistedFunctions the map of blacklisted functions
*/
public BaseSqlQueryWriter(Map<String, Integer> blacklistedFunctions)
{
requireNonNull(blacklistedFunctions, "supportingFunctions cannot be null");
this.blacklistedFunctions = blacklistedFunctions;
}
@Override
public String row(List<String> expressions)
{
return "ROW (" + Joiner.on(", ").join(expressions) + ")";
}
@Override
public String atTimeZone(String value, String timezone)
{
return value + " AT TIME ZONE " + timezone;
}
@Override
public String currentUser()
{
throw new UnsupportedOperationException("Cannot push CURRENT_USER to remote database");
}
@Override
public String currentPath()
{
throw new UnsupportedOperationException("Cannot push CURRENT_PATH to remote database");
}
@Override
public String currentTime(Time.Function function, Integer precision)
{
throw new UnsupportedOperationException("Cannot push current time functions to remote database");
}
@Override
public String extract(String expression, Time.ExtractField field)
{
return "EXTRACT(" + field + " FROM " + expression + ")";
}
@Override
public String booleanLiteral(boolean value)
{
return String.valueOf(value);
}
@Override
public String stringLiteral(String value)
{
return formatStringLiteral(value);
}
@Override
public String charLiteral(String value)
{
return "CHAR " + formatStringLiteral(value);
}
@Override
public String binaryLiteral(String hexValue)
{
return "X'" + hexValue + "'";
}
@Override
public String parameter(Optional<List<String>> parameters, int position)
{
if (parameters.isPresent()) {
checkArgument(position < parameters.get().size(), "Invalid parameter number %s. Max value is %s", position, parameters.get().size() - 1);
return parameters.get().get(position);
}
return "?";
}
@Override
public String arrayConstructor(List<String> values)
{
return "ARRAY[" + Joiner.on(",").join(values) + "]";
}
@Override
public String subscriptExpression(String base, String index)
{
return base + "[" + index + "]";
}
@Override
public String longLiteral(long value)
{
return Long.toString(value);
}
@Override
public String doubleLiteral(double value)
{
return doubleFormatter.get().format(value);
}
@Override
public String decimalLiteral(String value)
{
// TODO return node value without "DECIMAL '..'" when FeaturesConfig#parseDecimalLiteralsAsDouble switch is removed
return "DECIMAL '" + value + "'";
}
@Override
public String genericLiteral(String type, String value)
{
return type + " " + formatStringLiteral(value);
}
@Override
public String timeLiteral(String value)
{
return "TIME '" + value + "'";
}
@Override
public String timestampLiteral(String value)
{
return "TIMESTAMP '" + value + "'";
}
@Override
public String nullLiteral()
{
return "null";
}
@Override
public String intervalLiteral(Time.IntervalSign signLiteral, String value, Time.IntervalField startField, Optional<Time.IntervalField> endField)
{
String sign = (signLiteral == Time.IntervalSign.NEGATIVE) ? " - " : " ";
StringBuilder builder = new StringBuilder()
.append("INTERVAL")
.append(sign)
.append("'").append(value).append("' ")
.append(startField);
endField.ifPresent(field -> builder.append(" TO ").append(field));
return builder.toString();
}
@Override
public String subqueryExpression(String query)
{
return "(" + query + ")";
}
@Override
public String exists(String subquery)
{
return "(EXISTS " + subquery + ")";
}
@Override
public String identifier(String value, boolean delimited)
{
if (!delimited) {
return value;
}
else {
return '"' + value.replace("\"", "\"\"") + '"';
}
}
@Override
public String lambdaArgumentDeclaration(String identifier)
{
return identifier;
}
@Override
public String dereferenceExpression(String base, String field)
{
return base + "." + field;
}
@Override
public String fieldReference(int fieldIndex)
{
// add colon so this won't parse
return ":input(" + fieldIndex + ")";
}
@Override
public String functionCall(QualifiedName name, boolean distinct, List<String> argumentsList, Optional<String> orderBy, Optional<String> filter, Optional<String> window)
{
String functionName = formatQualifiedName(name);
if (this.isBlacklistedFunction(functionName, argumentsList.size())) {
// Replace dynamic filter function name with true for sub-query pushdown
if (DYNAMIC_FILTER_FUNCTION_NAME.equals(name.toString())) {
return "true";
}
throw new UnsupportedOperationException("The connector does not support the function " + functionName);
}
StringBuilder builder = new StringBuilder();
String arguments = joinExpressions(argumentsList);
if (argumentsList.isEmpty() && "count".equalsIgnoreCase(name.getSuffix())) {
arguments = "*";
}
if (distinct) {
arguments = "DISTINCT " + arguments;
}
builder.append(formatQualifiedName(name))
.append('(').append(arguments);
orderBy.ifPresent(exp -> builder.append(' ').append(exp));
builder.append(')');
filter.ifPresent(exp -> builder.append(" FILTER ").append(exp));
window.ifPresent(exp -> builder.append(" OVER ").append(exp));
return builder.toString();
}
@Override
public String lambdaExpression(List<String> arguments, String body)
{
StringBuilder builder = new StringBuilder();
builder.append('(');
Joiner.on(", ").appendTo(builder, arguments);
builder.append(") -> ");
builder.append(body);
return builder.toString();
}
@Override
public String bindExpression(List<String> values, String function)
{
return "\"$INTERNAL$BIND\"(" +
Joiner.on(", ").join(values) +
function +
'(';
}
@Override
public String logicalBinaryExpression(Operators.LogicalOperator operator, String left, String right)
{
return formatBinaryExpression(operator.toString(), left, right);
}
@Override
public String notExpression(String value)
{
return "(NOT " + value + ")";
}
@Override
public String comparisonExpression(Operators.ComparisonOperator operator, String left, String right)
{
return formatBinaryExpression(operator.getValue(), left, right);
}
@Override
public String isNullPredicate(String value)
{
return "(" + value + " IS NULL)";
}
@Override
public String isNotNullPredicate(String value)
{
return "(" + value + " IS NOT NULL)";
}
@Override
public String nullIfExpression(String first, String second)
{
return "NULLIF(" + first + ", " + second + ')';
}
@Override
public String ifExpression(String condition, String trueValue, Optional<String> falseValue)
{
StringBuilder builder = new StringBuilder();
builder.append("IF(")
.append(condition)
.append(", ")
.append(trueValue);
falseValue.ifPresent(value -> builder.append(", ").append(value));
builder.append(")");
return builder.toString();
}
@Override
public String tryExpression(String innerExpression)
{
return "TRY(" + innerExpression + ")";
}
@Override
public String coalesceExpression(List<String> operands)
{
return "COALESCE(" + joinExpressions(operands) + ")";
}
@Override
public String arithmeticUnary(Operators.Sign sign, String value)
{
switch (sign) {
case MINUS:
// this is to avoid turning a sequence of "-" into a comment (i.e., "-- comment")
String separator = value.startsWith("-") ? " " : "";
return "-" + separator + value;
case PLUS:
return "+" + value;
default:
throw new UnsupportedOperationException("Unsupported sign: " + sign);
}
}
@Override
public String arithmeticBinary(Operators.ArithmeticOperator operator, String left, String right)
{
return formatBinaryExpression(operator.getValue(), left, right);
}
@Override
public String likePredicate(String value, String pattern, Optional<String> escape)
{
StringBuilder builder = new StringBuilder();
builder.append('(')
.append(value)
.append(" LIKE ")
.append(pattern);
escape.ifPresent(val -> builder.append(" ESCAPE ")
.append(val));
builder.append(')');
return builder.toString();
}
@Override
public String allColumns(Optional<QualifiedName> prefix)
{
return prefix.map(name -> name + ".*").orElse("*");
}
@Override
public String cast(String expression, String type, boolean safe, boolean typeOnly)
{
return (safe ? "TRY_CAST" : "CAST") +
"(" + expression + " AS " + toNativeType(type) + ")";
}
@Override
public String searchedCaseExpression(List<String> whenCaluses, Optional<String> defaultValue)
{
ImmutableList.Builder<String> parts = ImmutableList.builder();
parts.add("CASE");
parts.addAll(whenCaluses);
defaultValue.ifPresent((value) -> parts.add("ELSE").add(value));
parts.add("END");
return "(" + Joiner.on(' ').join(parts.build()) + ")";
}
@Override
public String simpleCaseExpression(String operand, List<String> whenCaluses, Optional<String> defaultValue)
{
ImmutableList.Builder<String> parts = ImmutableList.builder();
parts.add("CASE").add(operand);
parts.addAll(whenCaluses);
defaultValue.ifPresent((value) -> parts.add("ELSE").add(value));
parts.add("END");
return "(" + Joiner.on(' ').join(parts.build()) + ")";
}
@Override
public String whenClause(String operand, String result)
{
return "WHEN " + operand + " THEN " + result;
}
@Override
public String betweenPredicate(String value, String min, String max)
{
return "(" + value + " BETWEEN " + min + " AND " + max + ")";
}
@Override
public String inPredicate(String value, String valueList)
{
return "(" + value + " IN " + valueList + ")";
}
@Override
public String inListExpression(List<String> values)
{
return "(" + joinExpressions(values) + ")";
}
@Override
public String filter(String value)
{
if ("false".equals(value)) {
return "(WHERE 1=0)";
}
else if ("true".equals(value)) {
return "(WHERE 1=1)";
}
return "(WHERE " + value + ')';
}
@Override
public String groupByIdElement(List<List<String>> groSets)
{
// default impl will write it as Hetu grammar
List<List<String>> bewGroSet = new ArrayList<>();
for (int i = groSets.size() - 1; i >= 0; i--) {
bewGroSet.add(groSets.get(i));
}
return bewGroSet.toString().replace('[', '(').replace(']', ')');
}
@Override
public String formatWindowColumn(String functionName, List<String> args, String windows)
{
String signatureStr = this.functionCall(new QualifiedName(Collections.singletonList(functionName)),
false, args, Optional.empty(), Optional.empty(), Optional.empty());
return " " + signatureStr + " OVER " + windows;
}
@Override
public String window(List<String> partitionBy, Optional<String> orderBy, Optional<String> frame)
{
List<String> parts = new ArrayList<>();
if (!partitionBy.isEmpty()) {
parts.add("PARTITION BY " + joinExpressions(partitionBy));
}
orderBy.ifPresent(parts::add);
frame.ifPresent(parts::add);
return '(' + Joiner.on(' ').join(parts) + ')';
}
@Override
public String windowFrame(Types.WindowFrameType type, String start, Optional<String> end)
{
StringBuilder builder = new StringBuilder();
builder.append(type.toString()).append(' ');
if (end.isPresent()) {
builder.append("BETWEEN ")
.append(start)
.append(" AND ")
.append(end.get());
}
else {
builder.append(start);
}
return builder.toString();
}
@Override
public String frameBound(Types.FrameBoundType type, Optional<String> value)
{
switch (type) {
case UNBOUNDED_PRECEDING:
return "UNBOUNDED PRECEDING";
case PRECEDING:
if (!value.isPresent()) {
throw new UnsupportedOperationException("Unsupported empty value in " + type);
}
return value.get() + " PRECEDING";
case CURRENT_ROW:
return "CURRENT ROW";
case FOLLOWING:
if (!value.isPresent()) {
throw new UnsupportedOperationException("Unsupported empty value in " + type);
}
return value.get() + " FOLLOWING";
case UNBOUNDED_FOLLOWING:
return "UNBOUNDED FOLLOWING";
}
throw new IllegalArgumentException("unhandled type: " + type);
}
@Override
public String quantifiedComparisonExpression(Operators.ComparisonOperator operator, Types.Quantifier quantifier, String value, String subquery)
{
return "(" + value + ' ' + operator.getValue() + ' ' + quantifier + ' ' + subquery + ")";
}
@Override
public String groupingOperation(List<String> groupingColumns)
{
return "GROUPING (" + joinExpressions(groupingColumns) + ")";
}
@Override
public String formatStringLiteral(String literal)
{
return ExpressionFormatter.formatStringLiteral(literal);
}
@Override
public String joinExpressions(List<String> expressions)
{
return Joiner.on(", ").join(expressions);
}
@Override
public boolean isBlacklistedFunction(String qualifiedName, int noOfArgs)
{
if (qualifiedName.contains(INTERNAL_FUNCTION_PREFIX)) {
// Internal functions such as `$literal$time with time zone` cannot be pushed down
return true;
}
Integer args = this.blacklistedFunctions.get(qualifiedName.toLowerCase(Locale.ENGLISH));
return args != null && (args < 0 || args == noOfArgs);
}
@Override
public String orderBy(List<OrderBy> orders)
{
StringJoiner joiner = new StringJoiner(", ");
for (OrderBy orderBy : orders) {
StringJoiner orderItem = new StringJoiner(" ");
orderItem.add(orderBy.getSymbol());
SortOrder sortOrder = orderBy.getType();
orderItem.add(sortOrder.isAscending() ? "ASC" : "DESC");
orderItem.add(sortOrder.isNullsFirst() ? "NULLS FIRST" : "NULLS LAST");
joiner.merge(orderItem);
}
return " ORDER BY " + joiner.toString();
}
@Override
public String qualifiedName(String tableName, String symbolName)
{
return tableName + "." + symbolName;
}
@Override
public String queryAlias(String id)
{
return "table" + id;
}
@Override
public String formatIdentifier(Optional<Map<String, Selection>> qualifiedNames, String identifier)
{
if (qualifiedNames.isPresent()) {
identifier = qualifiedNames.get().get(identifier).getExpression();
}
return identifier;
}
@Override
public String formatQualifiedName(QualifiedName name)
{
return name.getParts().stream()
.map(identifier -> formatIdentifier(Optional.empty(), identifier))
.collect(joining("."));
}
@Override
public String formatBinaryExpression(String operator, String left, String right)
{
return '(' + left + ' ' + operator + ' ' + right + ')';
}
@Override
public String toNativeType(String type)
{
return type;
}
@Override
public String select(List<Selection> symbols, String from)
{
if (symbols.size() == 0) {
return "(SELECT * FROM " + from + ")";
}
StringJoiner selection = new StringJoiner(", ");
for (Selection symbol : symbols) {
if (symbol.isAliased()) {
selection.add(symbol.getExpression() + " AS " + symbol.getAlias());
}
else {
selection.add(symbol.getExpression());
}
}
return "(SELECT " + selection.toString() + " FROM " + from + ")";
}
@Override
public String join(List<Selection> symbols, Types.JoinType type, String left, String leftId, String right, String rightId, List<String> criteria, Optional<String> filter)
{
StringBuilder builder = new StringBuilder();
builder.append(left);
builder.append(' ');
builder.append(leftId);
builder.append(' ');
builder.append(type.getJoinLabel());
builder.append(' ');
builder.append(right);
builder.append(' ');
builder.append(rightId);
builder.append(' ');
// Cross Join does not have criteria
if (!criteria.isEmpty() || filter.isPresent()) {
// Filter requires ON
builder.append(" ON ");
StringJoiner joiner = new StringJoiner(" AND ");
criteria.forEach(joiner::add);
filter.ifPresent(joiner::add);
builder.append(joiner.toString());
}
return select(symbols, builder.toString());
}
@Override
public String aggregation(List<Selection> symbols, Optional<List<String>> groupingKeysOp, Optional<String> groupIdElementOP, String from)
{
StringBuilder builder = new StringBuilder();
builder.append(from);
if (groupingKeysOp.isPresent()) {
List<String> groupingKeys = groupingKeysOp.get();
if (!groupingKeys.isEmpty()) {
builder.append(" GROUP BY ");
builder.append(Joiner.on(", ").join(groupingKeys));
}
}
else if (groupIdElementOP.isPresent()) {
String groupEleStr = groupIdElementOP.get();
builder.append(" GROUP BY GROUPING SETS ");
builder.append(groupEleStr);
}
return select(symbols, builder.toString());
}
@Override
public String limit(List<Selection> symbols, long count, String from)
{
return select(symbols, from + " LIMIT " + count);
}
@Override
public String filter(List<Selection> symbols, String predicate, String from)
{
if ("false".equals(predicate)) {
return select(symbols, from + " WHERE 1=0 ");
}
else if ("true".equals(predicate)) {
return select(symbols, from + " WHERE 1=1 ");
}
else {
return select(symbols, from + " WHERE " + predicate);
}
}
@Override
public String sort(List<Selection> symbols, List<OrderBy> orderings, String from)
{
return select(symbols, from + orderBy(orderings));
}
@Override
public String topN(List<Selection> symbols, List<OrderBy> orderings, long count, String from)
{
return select(symbols, from + orderBy(orderings) + " LIMIT " + count);
}
@Override
public String setOperator(List<Selection> symbols, Types.SetOperator type, List<String> relations)
{
StringBuilder builder = new StringBuilder();
builder.append(" (");
boolean first = true;
for (String relation : relations) {
builder.append(" ");
builder.append(first ? "" : type.getLabel());
builder.append(" ");
builder.append(relation);
first = false;
}
builder.append(") ");
return select(symbols, builder.toString());
}
private static boolean isAsciiPrintable(int codePoint)
{
return codePoint < 0x7F && codePoint >= 0x20;
}
}

View File

@ -37,7 +37,6 @@ import io.prestosql.spi.connector.ConnectorSplitManager;
import io.prestosql.spi.connector.ConnectorSplitSource;
import io.prestosql.spi.connector.ConnectorTransactionHandle;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.sql.SqlQueryWriter;
import io.prestosql.spi.transaction.IsolationLevel;
import java.sql.Connection;
@ -63,17 +62,17 @@ public final class InMemoryJdbcDatabase
private final String schemaName;
private final ConnectorFactory connectorFactory;
public InMemoryJdbcDatabase(Driver driver, String connectionUrl, String connectorName, String schemaName, SqlQueryWriter queryWriter)
public InMemoryJdbcDatabase(Driver driver, String connectionUrl, String connectorName, String schemaName)
throws SQLException
{
this(driver, connectionUrl, connectorName, schemaName, queryWriter, new BaseJdbcConfig(), new Properties());
this(driver, connectionUrl, connectorName, schemaName, new BaseJdbcConfig(), new Properties());
}
public InMemoryJdbcDatabase(Driver driver, String connectionUrl, String connectorName, String schemaName, SqlQueryWriter queryWriter, BaseJdbcConfig baseJdbcConfig, Properties connectionProperties)
public InMemoryJdbcDatabase(Driver driver, String connectionUrl, String connectorName, String schemaName, BaseJdbcConfig baseJdbcConfig, Properties connectionProperties)
throws SQLException
{
this.schemaName = schemaName;
jdbcClient = new InMemoryJdbcClient(baseJdbcConfig, driver, connectionUrl, queryWriter, connectionProperties);
jdbcClient = new InMemoryJdbcClient(baseJdbcConfig, driver, connectionUrl, connectionProperties);
connection = DriverManager.getConnection(connectionUrl, connectionProperties);
this.connectorFactory = new InMemoryJdbcConnectorFactory(this.jdbcClient, connectorName);
}
@ -194,19 +193,9 @@ public final class InMemoryJdbcDatabase
private static class InMemoryJdbcClient
extends BaseJdbcClient
{
private final SqlQueryWriter sqlQueryWriter;
public InMemoryJdbcClient(BaseJdbcConfig baseJdbcConfig, Driver driver, String connectionUrl, SqlQueryWriter sqlQueryWriter,
Properties properties)
public InMemoryJdbcClient(BaseJdbcConfig baseJdbcConfig, Driver driver, String connectionUrl, Properties properties)
{
super(baseJdbcConfig, "\"", new DriverConnectionFactory(driver, connectionUrl, Optional.empty(), Optional.empty(), properties));
this.sqlQueryWriter = sqlQueryWriter;
}
@Override
public Optional<SqlQueryWriter> getSqlQueryWriter()
{
return Optional.ofNullable(this.sqlQueryWriter);
}
}
}

View File

@ -20,6 +20,8 @@ import org.testng.annotations.Test;
import java.util.Map;
import static io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule.BASE_PUSHDOWN;
import static io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule.DEFAULT;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
@ -53,7 +55,9 @@ public class TestBaseJdbcConfig
.setNumTestsPerEvictionRun(3)
.setTimeBetweenEvictionRunsMillis(-1L)
.setMaxWaitMillis(-1L)
.setCaseInsensitiveNameMatchingCacheTtl(new Duration(1, MINUTES)));
.setCaseInsensitiveNameMatchingCacheTtl(new Duration(1, MINUTES))
.setPushDownEnable(true)
.setPushDownModule(DEFAULT));
}
@Test
@ -83,7 +87,9 @@ public class TestBaseJdbcConfig
.put("jdbc.connection.pool.maxTotal", "200")
.put("jdbc.connection.pool.maxIdle", "20")
.put("jdbc.connection.pool.minIdle", "12")
.put("jdbc.pushdown-enabled", "false")
.put("use-connection-pool", "true")
.put("jdbc.pushdown-module", "BASE_PUSHDOWN")
.build();
BaseJdbcConfig expected = new BaseJdbcConfig()
@ -110,7 +116,9 @@ public class TestBaseJdbcConfig
.setNumTestsPerEvictionRun(100)
.setTimeBetweenEvictionRunsMillis(1000)
.setMaxWaitMillis(1000)
.setCaseInsensitiveNameMatchingCacheTtl(new Duration(1, SECONDS));
.setCaseInsensitiveNameMatchingCacheTtl(new Duration(1, SECONDS))
.setPushDownEnable(false)
.setPushDownModule(BASE_PUSHDOWN);
ConfigAssertions.assertFullMapping(properties, expected);
}

View File

@ -0,0 +1,369 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import io.prestosql.plugin.jdbc.JdbcTableHandle;
import io.prestosql.spi.block.SortOrder;
import io.prestosql.spi.function.FunctionKind;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.plan.Assignments;
import io.prestosql.spi.plan.JoinNode;
import io.prestosql.spi.plan.OrderingScheme;
import io.prestosql.spi.plan.PlanNode;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.plan.WindowNode;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.relation.VariableReferenceExpression;
import io.prestosql.spi.sql.expression.Types;
import io.prestosql.sql.planner.iterative.rule.test.PlanBuilder;
import io.prestosql.sql.relational.ConnectorRowExpressionService;
import io.prestosql.sql.relational.RowExpressionDeterminismEvaluator;
import io.prestosql.sql.relational.RowExpressionDomainTranslator;
import org.testng.annotations.Test;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import static io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule.FULL_PUSHDOWN;
import static io.prestosql.spi.type.BigintType.BIGINT;
import static org.testng.Assert.assertEquals;
public class TestBaseBaseJdbcQueryGenerator
extends TestBaseJdbcPushDownBase
{
private static final SessionHolder defaultSessionHolder = new SessionHolder();
private static final JdbcTableHandle jdbcTable = testTable;
private void testJQL(
Function<PlanBuilder, PlanNode> planBuilderConsumer,
String expectedJQL)
{
PlanNode planNode = planBuilderConsumer.apply(createPlanBuilder());
testJQL(planNode, expectedJQL);
}
private void testJQL(
PlanNode planNode,
String expectedJQL)
{
JdbcPushDownParameter pushDownParameter = new JdbcPushDownParameter("'", false, FULL_PUSHDOWN);
RowExpressionService rowExpressionService = new ConnectorRowExpressionService(new RowExpressionDomainTranslator(metadata), new RowExpressionDeterminismEvaluator(metadata));
JdbcQueryGeneratorResult jdbcQueryGeneratorResult = (new BaseJdbcQueryGenerator(pushDownParameter, new BaseJdbcRowExpressionConverter(rowExpressionService), new BaseJdbcSqlStatementWriter(pushDownParameter))).generate(planNode, new TestTypeManager()).get();
String generatedJQL = jdbcQueryGeneratorResult.getGeneratedSql().getSql();
assertEquals(generatedJQL, expectedJQL);
}
private PlanNode buildPlan(Function<PlanBuilder, PlanNode> consumer)
{
PlanBuilder planBuilder = createPlanBuilder();
return consumer.apply(planBuilder);
}
@Test
public void testSimpleSelectStar()
{
testJQL(planBuilder -> tableScan(planBuilder, jdbcTable, regionId, city, fare, amount),
"SELECT regionid, city, fare, amount FROM 'table'");
testJQL(planBuilder -> limit(planBuilder, 10L, tableScan(planBuilder, jdbcTable, regionId, city, fare, amount)),
"SELECT regionid, city, fare, amount FROM 'table' LIMIT 10");
}
@Test
public void testSimpleOperatorFilter()
{
testJQL(planBuilder -> filter(
planBuilder,
tableScan(planBuilder, jdbcTable, regionId, city, fare, amount),
getRowExpression("amount = 20", defaultSessionHolder)),
"SELECT regionid, city, fare, amount FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 WHERE (amount = 20)");
}
@Test
public void testFilterWithLike()
{
testJQL(planBuilder -> filter(
planBuilder,
tableScan(planBuilder, jdbcTable, regionId, city, fare, amount),
getRowExpression("city like 'city'", defaultSessionHolder)),
"SELECT regionid, city, fare, amount FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 WHERE (city LIKE 'city')");
testJQL(planBuilder -> filter(
planBuilder,
tableScan(planBuilder, jdbcTable, regionId, city, fare, amount),
getRowExpression("city not like 'city'", defaultSessionHolder)),
"SELECT regionid, city, fare, amount FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 WHERE (NOT (city LIKE 'city'))");
}
@Test
public void testFilterWithIsNull()
{
testJQL(planBuilder -> filter(
planBuilder,
tableScan(planBuilder, jdbcTable, regionId, city, fare, amount),
getRowExpression("amount is null", defaultSessionHolder)),
"SELECT regionid, city, fare, amount FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 WHERE (amount IS NULL)");
testJQL(planBuilder -> filter(
planBuilder,
tableScan(planBuilder, jdbcTable, regionId, city, fare, amount),
getRowExpression("amount is not null", defaultSessionHolder)),
"SELECT regionid, city, fare, amount FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 WHERE (NOT (amount IS NULL))");
}
@Test
public void testFilterWithIn()
{
testJQL(planBuilder -> filter(
planBuilder,
tableScan(planBuilder, jdbcTable, regionId, city, fare, amount),
getRowExpression("amount in (100, 200)", defaultSessionHolder)),
"SELECT regionid, city, fare, amount FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 WHERE (amount IN (100, 200))");
testJQL(planBuilder -> filter(
planBuilder,
tableScan(planBuilder, jdbcTable, regionId, city, fare, amount),
getRowExpression("amount not in (100, 200)", defaultSessionHolder)),
"SELECT regionid, city, fare, amount FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 WHERE (NOT (amount IN (100, 200)))");
}
@Test
public void testSimpleSelectWithFilterLimit()
{
testJQL(planBuilder -> limit(
planBuilder,
30L,
project(
planBuilder,
filter(
planBuilder,
tableScan(planBuilder, jdbcTable, regionId, city, fare, amount),
getRowExpression("amount > 20", defaultSessionHolder)),
ImmutableList.of("city", "fare"))),
"SELECT city, fare FROM (SELECT regionid, city, fare, amount FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 WHERE (amount > 20)) hetu_table_2 LIMIT 30");
}
@Test
public void testCountStar()
{
BiConsumer<PlanBuilder, PlanBuilder.AggregationBuilder> aggregationFunctionBuilder = ((planBuilder, aggregationBuilder) ->
aggregationBuilder.addAggregation(planBuilder.symbol("agg"), getRowExpression("count(*)", defaultSessionHolder)));
PlanNode justScan = buildPlan(planBuilder -> tableScan(planBuilder, jdbcTable, regionId, city, fare, amount));
testJQL(planBuilder -> planBuilder.aggregation(aggBuilder -> aggregationFunctionBuilder.accept(planBuilder, aggBuilder.source(justScan).globalGrouping())),
"SELECT CAST(count(*) AS bigint) AS agg FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1");
}
@Test
public void testDistinctSelection()
{
PlanNode justScan = buildPlan(planBuilder -> tableScan(planBuilder, jdbcTable, regionId, city, fare, amount));
testJQL(planBuilder -> planBuilder.aggregation(aggBuilder -> aggBuilder.source(justScan).singleGroupingSet(symbol("regionid"))),
"SELECT regionid FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 GROUP BY regionid");
}
@Test
public void testSimpleJoin()
{
testJQL(planBuilder -> planBuilder.join(
JoinNode.Type.INNER,
tableScan(planBuilder, testLeftTable, leftId, leftValue),
tableScan(planBuilder, testRightTable, rightId, rightValue),
new JoinNode.EquiJoinClause(symbol("leftid"),
symbol("rightid"))),
"SELECT leftid, leftvalue, rightid, rightvalue FROM ((SELECT leftid, leftvalue FROM 'left_table') hetu_left_1 INNER JOIN (SELECT rightid, rightvalue FROM 'right_table') hetu_right_1 ON leftid = rightid)");
testJQL(planBuilder -> planBuilder.join(
JoinNode.Type.LEFT,
tableScan(planBuilder, testLeftTable, leftId, leftValue),
tableScan(planBuilder, testRightTable, rightId, rightValue),
new JoinNode.EquiJoinClause(symbol("leftid"), symbol("rightid"))),
"SELECT leftid, leftvalue, rightid, rightvalue FROM ((SELECT leftid, leftvalue FROM 'left_table') hetu_left_1 LEFT JOIN (SELECT rightid, rightvalue FROM 'right_table') hetu_right_1 ON leftid = rightid)");
testJQL(planBuilder -> planBuilder.join(
JoinNode.Type.RIGHT,
tableScan(planBuilder, testLeftTable, leftId, leftValue),
tableScan(planBuilder, testRightTable, rightId, rightValue),
new JoinNode.EquiJoinClause(symbol("leftid"), symbol("rightid"))),
"SELECT leftid, leftvalue, rightid, rightvalue FROM ((SELECT leftid, leftvalue FROM 'left_table') hetu_left_1 RIGHT JOIN (SELECT rightid, rightvalue FROM 'right_table') hetu_right_1 ON leftid = rightid)");
testJQL(planBuilder -> planBuilder.join(
JoinNode.Type.FULL,
tableScan(planBuilder, testLeftTable, leftId, leftValue),
tableScan(planBuilder, testRightTable, rightId, rightValue),
new JoinNode.EquiJoinClause(symbol("leftid"), symbol("rightid"))),
"SELECT leftid, leftvalue, rightid, rightvalue FROM ((SELECT leftid, leftvalue FROM 'left_table') hetu_left_1 FULL JOIN (SELECT rightid, rightvalue FROM 'right_table') hetu_right_1 ON leftid = rightid)");
}
@Test
public void testMultiLayeredJoin()
{
testJQL(planBuilder -> planBuilder.join(
JoinNode.Type.INNER,
planBuilder.join(
JoinNode.Type.INNER,
tableScan(planBuilder, testLeftTable, leftId, leftValue),
tableScan(planBuilder, testRightTable, rightId, rightValue),
new JoinNode.EquiJoinClause(symbol("leftid"), symbol("rightid"))),
tableScan(planBuilder, jdbcTable, regionId), new JoinNode.EquiJoinClause(symbol("leftid"), symbol("regoinid"))),
"SELECT leftid, leftvalue, rightid, rightvalue, regionid FROM ((SELECT leftid, leftvalue, rightid, rightvalue FROM ((SELECT leftid, leftvalue FROM 'left_table') hetu_left_1 INNER JOIN (SELECT rightid, rightvalue FROM 'right_table') hetu_right_1 ON leftid = rightid)) hetu_left_2 INNER JOIN (SELECT regionid FROM 'table') hetu_right_2 ON leftid = regoinid)");
}
@Test
public void testJoinWithFilter()
{
testJQL(planBuilder -> planBuilder.join(
JoinNode.Type.INNER,
tableScan(planBuilder, testLeftTable, leftId, leftValue),
tableScan(planBuilder, testRightTable, rightId, rightValue),
getRowExpression("leftvalue > rightvalue", defaultSessionHolder),
new JoinNode.EquiJoinClause(symbol("leftid"), symbol("rightid"))),
"SELECT leftid, leftvalue, rightid, rightvalue FROM ((SELECT leftid, leftvalue FROM 'left_table') hetu_left_1 INNER JOIN (SELECT rightid, rightvalue FROM 'right_table') hetu_right_1 ON leftid = rightid AND (leftvalue > rightvalue))");
}
@Test
public void testTopN()
{
testJQL(planBuilder -> planBuilder.topN(
10L,
ImmutableList.of(symbol("regionid")),
tableScan(planBuilder, jdbcTable, regionId, city, fare, amount)),
"SELECT regionid, city, fare, amount FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1 ORDER BY regionid ASC NULLS FIRST LIMIT 10");
}
@Test
public void testUnion()
{
PlanNode leftScanNode = buildPlan(planBuilder -> tableScan(planBuilder, testLeftTable, leftId, leftValue));
PlanNode rightScanNode = buildPlan(planBuilder -> tableScan(planBuilder, testRightTable, rightId, rightValue));
testJQL(planBuilder -> planBuilder.union(
ImmutableListMultimap.<Symbol, Symbol>builder()
.put(symbol("id"), symbol("leftid"))
.put(symbol("value"), symbol("leftvalue"))
.put(symbol("id"), symbol("rightid"))
.put(symbol("value"), symbol("rightvalue"))
.build(),
ImmutableList.of(leftScanNode, rightScanNode)
), "SELECT id, value FROM ((SELECT leftid AS id, leftvalue AS value FROM (SELECT leftid, leftvalue FROM 'left_table') hetu_table_1) UNION ALL (SELECT rightid AS id, rightvalue AS value FROM (SELECT rightid, rightvalue FROM 'right_table') hetu_table_2)) hetu_table_3");
}
@Test
public void testWindowFunctionWithOrderBy()
{
PlanNode scanNode = buildPlan(planBuilder -> tableScan(planBuilder, jdbcTable, regionId, city, fare, amount));
testJQL(planBuilder -> planBuilder.window(
new WindowNode.Specification(
ImmutableList.of(),
Optional.of(new OrderingScheme(
ImmutableList.of(symbol("fare")),
ImmutableMap.of(symbol("fare"), SortOrder.ASC_NULLS_FIRST)))),
ImmutableMap.of(
symbol("amount_out"),
new WindowNode.Function(
new Signature(
"min",
FunctionKind.WINDOW,
ImmutableList.of(),
ImmutableList.of(),
BIGINT.getTypeSignature(),
ImmutableList.of(BIGINT.getTypeSignature()),
false),
ImmutableList.of(new VariableReferenceExpression("amount", types.get(symbol("amount")))),
new WindowNode.Frame(
Types.WindowFrameType.RANGE,
Types.FrameBoundType.UNBOUNDED_PRECEDING,
Optional.empty(),
Types.FrameBoundType.CURRENT_ROW,
Optional.empty(),
Optional.empty(),
Optional.empty()))),
symbol("city"),
scanNode),
"SELECT regionid, city, fare, amount, min(amount) OVER ( ORDER BY fare ASC NULLS FIRST RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS amount_out FROM (SELECT regionid, city, fare, amount FROM 'table') hetu_table_1");
}
@Test
public void testWindowFunctionWithProjectAndRange()
{
PlanNode scanNode = buildPlan(planBuilder -> tableScan(planBuilder, jdbcTable, regionId, city, fare, amount, startValueColumn, endValueColumn));
testJQL(planBuilder -> planBuilder.project(
Assignments.builder()
.put(symbol("regionid"), variable("regionid"))
.put(symbol("city"), variable("city"))
.put(symbol("amount_out"), variable("amount_out", BIGINT))
.build(),
planBuilder.window(
new WindowNode.Specification(
ImmutableList.of(),
Optional.empty()),
ImmutableMap.of(
symbol("amount_out"),
new WindowNode.Function(
new Signature(
"min",
FunctionKind.WINDOW,
ImmutableList.of(),
ImmutableList.of(),
BIGINT.getTypeSignature(),
ImmutableList.of(BIGINT.getTypeSignature()),
false),
ImmutableList.of(new VariableReferenceExpression("amount", types.get(symbol("amount")))),
new WindowNode.Frame(
Types.WindowFrameType.RANGE,
Types.FrameBoundType.PRECEDING,
Optional.of(startValue),
Types.FrameBoundType.FOLLOWING,
Optional.of(endValue),
Optional.of(startValue.getName()),
Optional.of(endValue.getName())))),
symbol("city"),
scanNode)),
"SELECT regionid, city, amount_out FROM (SELECT regionid, city, fare, amount, startvalue, endvalue, min(amount) OVER (RANGE BETWEEN startValue PRECEDING AND endValue FOLLOWING) AS amount_out FROM (SELECT regionid, city, fare, amount, startValue, endValue FROM 'table') hetu_table_1) hetu_table_2");
testJQL(planBuilder -> planBuilder.project(
Assignments.builder()
.put(symbol("regionid"), variable("regionid"))
.put(symbol("city"), variable("city"))
.put(symbol("amount_out"), variable("amount_out", BIGINT))
.build(),
planBuilder.window(
new WindowNode.Specification(
ImmutableList.of(),
Optional.of(new OrderingScheme(
ImmutableList.of(symbol("fare")),
ImmutableMap.of(symbol("fare"), SortOrder.ASC_NULLS_FIRST)))),
ImmutableMap.of(
symbol("amount_out"),
new WindowNode.Function(
new Signature(
"min",
FunctionKind.WINDOW,
ImmutableList.of(),
ImmutableList.of(),
BIGINT.getTypeSignature(),
ImmutableList.of(BIGINT.getTypeSignature()),
false),
ImmutableList.of(new VariableReferenceExpression("amount", types.get(symbol("amount")))),
new WindowNode.Frame(
Types.WindowFrameType.ROWS,
Types.FrameBoundType.PRECEDING,
Optional.of(startValue),
Types.FrameBoundType.UNBOUNDED_FOLLOWING,
Optional.empty(),
Optional.of(startValue.getName()),
Optional.empty()))),
symbol("city"),
scanNode)),
"SELECT regionid, city, amount_out FROM (SELECT regionid, city, fare, amount, startvalue, endvalue, min(amount) OVER ( ORDER BY fare ASC NULLS FIRST ROWS BETWEEN startValue PRECEDING AND UNBOUNDED FOLLOWING) AS amount_out FROM (SELECT regionid, city, fare, amount, startValue, endValue FROM 'table') hetu_table_1) hetu_table_2");
}
}

View File

@ -0,0 +1,422 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.prestosql.Session;
import io.prestosql.SystemSessionProperties;
import io.prestosql.execution.warnings.WarningCollector;
import io.prestosql.metadata.Metadata;
import io.prestosql.metadata.MetadataManager;
import io.prestosql.metadata.SessionPropertyManager;
import io.prestosql.plugin.jdbc.BaseJdbcClient;
import io.prestosql.plugin.jdbc.BaseJdbcConfig;
import io.prestosql.plugin.jdbc.DriverConnectionFactory;
import io.prestosql.plugin.jdbc.JdbcClient;
import io.prestosql.plugin.jdbc.JdbcColumnHandle;
import io.prestosql.plugin.jdbc.JdbcTableHandle;
import io.prestosql.plugin.jdbc.JdbcTypeHandle;
import io.prestosql.spi.connector.CatalogName;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.ConnectorSession;
import io.prestosql.spi.connector.SchemaTableName;
import io.prestosql.spi.function.FunctionKind;
import io.prestosql.spi.function.OperatorType;
import io.prestosql.spi.metadata.TableHandle;
import io.prestosql.spi.plan.Assignments;
import io.prestosql.spi.plan.FilterNode;
import io.prestosql.spi.plan.LimitNode;
import io.prestosql.spi.plan.PlanNode;
import io.prestosql.spi.plan.PlanNodeIdAllocator;
import io.prestosql.spi.plan.ProjectNode;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.plan.TableScanNode;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.relation.VariableReferenceExpression;
import io.prestosql.spi.sql.QueryGenerator;
import io.prestosql.spi.type.BigintType;
import io.prestosql.spi.type.BooleanType;
import io.prestosql.spi.type.DecimalType;
import io.prestosql.spi.type.DoubleType;
import io.prestosql.spi.type.IntegerType;
import io.prestosql.spi.type.ParametricType;
import io.prestosql.spi.type.RealType;
import io.prestosql.spi.type.TimestampType;
import io.prestosql.spi.type.Type;
import io.prestosql.spi.type.TypeManager;
import io.prestosql.spi.type.TypeNotFoundException;
import io.prestosql.spi.type.TypeSignature;
import io.prestosql.spi.type.TypeSignatureParameter;
import io.prestosql.spi.type.VarcharType;
import io.prestosql.sql.ExpressionUtils;
import io.prestosql.sql.analyzer.ExpressionAnalyzer;
import io.prestosql.sql.parser.ParsingOptions;
import io.prestosql.sql.parser.SqlParser;
import io.prestosql.sql.planner.TypeProvider;
import io.prestosql.sql.planner.iterative.rule.test.PlanBuilder;
import io.prestosql.sql.relational.SqlToRowExpressionTranslator;
import io.prestosql.sql.tree.Expression;
import io.prestosql.sql.tree.NodeRef;
import io.prestosql.testing.TestingSession;
import io.prestosql.testing.TestingTransactionHandle;
import io.prestosql.utils.HetuConfig;
import org.h2.Driver;
import java.lang.invoke.MethodHandle;
import java.sql.Types;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static io.prestosql.plugin.jdbc.TestingJdbcTypeHandle.JDBC_BIGINT;
import static io.prestosql.plugin.jdbc.TestingJdbcTypeHandle.JDBC_BOOLEAN;
import static io.prestosql.plugin.jdbc.TestingJdbcTypeHandle.JDBC_DOUBLE;
import static io.prestosql.plugin.jdbc.TestingJdbcTypeHandle.JDBC_INTEGER;
import static io.prestosql.plugin.jdbc.TestingJdbcTypeHandle.JDBC_REAL;
import static io.prestosql.plugin.jdbc.TestingJdbcTypeHandle.JDBC_VARCHAR;
import static io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule.FULL_PUSHDOWN;
import static io.prestosql.spi.type.BigintType.BIGINT;
import static io.prestosql.spi.type.BooleanType.BOOLEAN;
import static io.prestosql.spi.type.DateType.DATE;
import static io.prestosql.spi.type.DoubleType.DOUBLE;
import static io.prestosql.spi.type.HyperLogLogType.HYPER_LOG_LOG;
import static io.prestosql.spi.type.TestingIdType.ID;
import static io.prestosql.spi.type.TimestampType.TIMESTAMP;
import static io.prestosql.spi.type.VarbinaryType.VARBINARY;
import static io.prestosql.spi.type.VarcharType.VARCHAR;
import static io.prestosql.testing.TestingConnectorSession.SESSION;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
public class TestBaseJdbcPushDownBase
{
protected static final String connectionUrl = "testUrl";
protected static final JdbcClient testClient = new TestPushwonClient();
protected static final CatalogName catalogName = new CatalogName("catalog");
protected static final Metadata metadata = MetadataManager.createTestMetadataManager();
protected static final JdbcTableHandle testTable = new JdbcTableHandle(
new SchemaTableName("schema", "table"), null, null, "table");
protected static final JdbcTableHandle testLeftTable = new JdbcTableHandle(
new SchemaTableName("schema", "left_table"), null, null, "left_table");
protected static final JdbcTableHandle testRightTable = new JdbcTableHandle(
new SchemaTableName("schema", "right_table"), null, null, "right_table");
protected static final JdbcColumnHandle leftId = bigintColumn("leftid");
protected static final JdbcColumnHandle leftValue = varcharColumn("leftvalue");
protected static final JdbcColumnHandle rightId = bigintColumn("rightid");
protected static final JdbcColumnHandle rightValue = varcharColumn("rightvalue");
protected static final JdbcColumnHandle booleanCol = booleanColumn("booleanCol");
protected static final JdbcColumnHandle intCol = integerColumn("intCol");
protected static final JdbcColumnHandle realCol = realColumn("realCol");
protected static final JdbcColumnHandle doubleCol = doubleColumn("doubleCol");
protected static final JdbcColumnHandle decimalCol = decimalColumn("decimalCol");
protected static final JdbcColumnHandle regionId = integerColumn("regionid");
protected static final JdbcColumnHandle city = varcharColumn("city");
protected static final JdbcColumnHandle fare = doubleColumn("fare");
protected static final JdbcColumnHandle amount = bigintColumn("amount");
protected static final JdbcColumnHandle startValueColumn = bigintColumn("startValue");
protected static final JdbcColumnHandle endValueColumn = bigintColumn("endValue");
protected static final Symbol startValue = symbol("startValue");
protected static final Symbol endValue = symbol("endValue");
protected static final Map<Symbol, Type> types = ImmutableMap.<Symbol, Type>builder()
.put(new Symbol("regionid"), IntegerType.INTEGER)
.put(new Symbol("city"), VarcharType.VARCHAR)
.put(new Symbol("fare"), DoubleType.DOUBLE)
.put(new Symbol("amount"), BigintType.BIGINT)
.put(new Symbol("booleanCol"), BooleanType.BOOLEAN)
.put(new Symbol("intCol"), IntegerType.INTEGER)
.put(new Symbol("realCol"), RealType.REAL)
.put(new Symbol("doubleCol"), DoubleType.DOUBLE)
.put(new Symbol("decimalCol"), DecimalType.createDecimalType(10, 2))
.put(new Symbol("timeCol"), TimestampType.TIMESTAMP)
.put(new Symbol("leftid"), BigintType.BIGINT)
.put(new Symbol("leftvalue"), VarcharType.VARCHAR)
.put(new Symbol("rightid"), BigintType.BIGINT)
.put(new Symbol("rightvalue"), VarcharType.VARCHAR)
.put(new Symbol("startValue"), BigintType.BIGINT)
.put(new Symbol("endValue"), BigintType.BIGINT)
.build();
protected final TypeProvider typeProvider = TypeProvider.copyOf(types);
protected static class SessionHolder
{
private final ConnectorSession connectorSession;
private final Session session;
public SessionHolder()
{
connectorSession = SESSION;
session = TestingSession.testSessionBuilder(new SessionPropertyManager(new SystemSessionProperties().getSessionProperties(), new HetuConfig())).build();
}
public ConnectorSession getConnectorSession()
{
return connectorSession;
}
public Session getSession()
{
return session;
}
}
protected static Symbol symbol(String name)
{
return new Symbol(name);
}
protected static VariableReferenceExpression variable(String name)
{
return new VariableReferenceExpression(name, types.get(symbol(name)));
}
protected static VariableReferenceExpression variable(String name, Type type)
{
return new VariableReferenceExpression(name, type);
}
public static Expression expression(String sql)
{
return ExpressionUtils.rewriteIdentifiersToSymbolReferences(new SqlParser().createExpression(sql,
new ParsingOptions(ParsingOptions.DecimalLiteralTreatment.AS_DECIMAL)));
}
protected RowExpression toRowExpression(Expression expression, Session session)
{
Map<NodeRef<Expression>, Type> expressionTypes = ExpressionAnalyzer.analyzeExpressions(
session,
metadata,
new SqlParser(),
typeProvider,
ImmutableList.of(expression),
ImmutableList.of(),
WarningCollector.NOOP,
false
).getExpressionTypes();
return SqlToRowExpressionTranslator.translate(expression, FunctionKind.SCALAR, expressionTypes, ImmutableMap.of(), metadata, session, false);
}
protected TableScanNode tableScan(PlanBuilder planBuilder, JdbcTableHandle connectorTableHandle, JdbcColumnHandle... columnHandles)
{
List<Symbol> symbols = Arrays.stream(columnHandles).map(column -> new Symbol(column.getColumnName().toLowerCase(Locale.ENGLISH))).collect(toImmutableList());
ImmutableMap.Builder<Symbol, ColumnHandle> assignments = ImmutableMap.builder();
for (int i = 0; i < symbols.size(); i++) {
assignments.put(symbols.get(i), columnHandles[i]);
}
TableHandle tableHandle = new TableHandle(
catalogName,
connectorTableHandle,
TestingTransactionHandle.create(),
Optional.empty());
return planBuilder.tableScan(
tableHandle,
symbols,
assignments.build());
}
protected FilterNode filter(PlanBuilder planBuilder, PlanNode source, RowExpression predicate)
{
return planBuilder.filter(predicate, source);
}
protected ProjectNode project(PlanBuilder planBuilder, PlanNode source, List<String> columnNames)
{
Map<String, Symbol> incomingColumns = source.getOutputSymbols().stream().collect(toMap(Symbol::getName, identity()));
Assignments.Builder assignmentBuilder = Assignments.builder();
columnNames.forEach(columnName -> {
Symbol symbol = requireNonNull(incomingColumns.get(columnName), "Couldn't find the incoming column " + columnName);
assignmentBuilder.put(symbol, new VariableReferenceExpression(columnName, types.get(new Symbol(columnName))));
});
return planBuilder.project(assignmentBuilder.build(), source);
}
protected LimitNode limit(PlanBuilder pb, long count, PlanNode source)
{
return new LimitNode(pb.getIdAllocator().getNextId(), source, count, false);
}
protected RowExpression getRowExpression(String sqlExpression, SessionHolder sessionHolder)
{
return toRowExpression(expression(sqlExpression), sessionHolder.getSession());
}
protected PlanBuilder createPlanBuilder()
{
return new PlanBuilder(new PlanNodeIdAllocator(), metadata);
}
protected static JdbcColumnHandle booleanColumn(String name)
{
return new JdbcColumnHandle(name, JDBC_BOOLEAN, BooleanType.BOOLEAN, true);
}
protected static JdbcColumnHandle integerColumn(String name)
{
return new JdbcColumnHandle(name, JDBC_INTEGER, IntegerType.INTEGER, true);
}
protected static JdbcColumnHandle bigintColumn(String name)
{
return new JdbcColumnHandle(name, JDBC_BIGINT, BigintType.BIGINT, true);
}
private static JdbcColumnHandle realColumn(String name)
{
return new JdbcColumnHandle(name, JDBC_REAL, RealType.REAL, true);
}
private static JdbcColumnHandle doubleColumn(String name)
{
return new JdbcColumnHandle(name, JDBC_DOUBLE, DoubleType.DOUBLE, true);
}
private static JdbcColumnHandle varcharColumn(String name)
{
return new JdbcColumnHandle(name, JDBC_VARCHAR, VarcharType.VARCHAR, true);
}
private static JdbcColumnHandle decimalColumn(String name)
{
return new JdbcColumnHandle(
name,
new JdbcTypeHandle(Types.DECIMAL, Optional.of("decimal(10,2)"), 10, 2, Optional.empty()),
DecimalType.createDecimalType(10, 2),
true);
}
protected static class TestPushwonClient
extends BaseJdbcClient
{
public TestPushwonClient()
{
super(new BaseJdbcConfig(), "`", new DriverConnectionFactory(new Driver(), connectionUrl, Optional.empty(), Optional.empty(), new Properties()));
}
@Override
public Map<String, ColumnHandle> getColumns(ConnectorSession session, String sql, Map<String, Type> types)
{
Map<String, ColumnHandle> columns = new HashMap<>();
for (Map.Entry<String, Type> entry : types.entrySet()) {
ColumnHandle columnHandle;
String name = entry.getKey();
Type type = entry.getValue();
if (type instanceof BigintType) {
columnHandle = bigintColumn(name);
}
else if (type instanceof IntegerType) {
columnHandle = integerColumn(name);
}
else if (type instanceof DoubleType) {
columnHandle = doubleColumn(name);
}
else if (type instanceof VarcharType) {
columnHandle = varcharColumn(name);
}
else {
throw new RuntimeException(format("Unknown column type [%s]", type));
}
columns.put(name, columnHandle);
}
return columns;
}
@Override
public Optional<QueryGenerator<JdbcQueryGeneratorResult>> getQueryGenerator(RowExpressionService rowExpressionService)
{
JdbcPushDownParameter pushDownParameter = new JdbcPushDownParameter("'", false, FULL_PUSHDOWN);
return Optional.of(new BaseJdbcQueryGenerator(pushDownParameter, new BaseJdbcRowExpressionConverter(rowExpressionService), new BaseJdbcSqlStatementWriter(pushDownParameter)));
}
}
protected static class TestTypeManager
implements TypeManager
{
@Override
public Type getType(TypeSignature signature)
{
for (Type type : getTypes()) {
if (signature.getBase().equals(type.getTypeSignature().getBase())) {
return type;
}
}
throw new TypeNotFoundException(signature);
}
@Override
public Type getParameterizedType(String baseTypeName, List<TypeSignatureParameter> typeParameters)
{
return getType(new TypeSignature(baseTypeName, typeParameters));
}
@Override
public List<Type> getTypes()
{
return ImmutableList.of(BOOLEAN, BIGINT, DOUBLE, VARCHAR, VARBINARY, TIMESTAMP, DATE, ID, HYPER_LOG_LOG);
}
@Override
public Collection<ParametricType> getParametricTypes()
{
return ImmutableList.of();
}
@Override
public Optional<Type> getCommonSuperType(Type firstType, Type secondType)
{
throw new UnsupportedOperationException();
}
@Override
public boolean canCoerce(Type actualType, Type expectedType)
{
throw new UnsupportedOperationException();
}
@Override
public boolean isTypeOnlyCoercion(Type actualType, Type expectedType)
{
return false;
}
@Override
public Optional<Type> coerceTypeBase(Type sourceType, String resultTypeBase)
{
throw new UnsupportedOperationException();
}
@Override
public MethodHandle resolveOperator(OperatorType operatorType, List<? extends Type> argumentTypes)
{
throw new UnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.plugin.jdbc.optimization;
import com.google.common.collect.ImmutableMap;
import io.prestosql.metadata.Metadata;
import io.prestosql.metadata.MetadataManager;
import io.prestosql.plugin.jdbc.BaseJdbcConfig;
import io.prestosql.plugin.jdbc.JdbcClient;
import io.prestosql.plugin.jdbc.JdbcTableHandle;
import io.prestosql.spi.plan.PlanNode;
import io.prestosql.spi.plan.ProjectNode;
import io.prestosql.spi.plan.TableScanNode;
import io.prestosql.spi.relation.RowExpressionService;
import io.prestosql.spi.type.Type;
import io.prestosql.sql.planner.PlanSymbolAllocator;
import io.prestosql.sql.planner.iterative.rule.test.PlanBuilder;
import io.prestosql.sql.relational.ConnectorRowExpressionService;
import io.prestosql.sql.relational.RowExpressionDeterminismEvaluator;
import io.prestosql.sql.relational.RowExpressionDomainTranslator;
import org.testng.annotations.Test;
import static io.prestosql.spi.type.BigintType.BIGINT;
import static io.prestosql.spi.type.DoubleType.DOUBLE;
import static io.prestosql.spi.type.IntegerType.INTEGER;
import static io.prestosql.spi.type.VarcharType.VARCHAR;
import static org.testng.Assert.assertTrue;
public class TestJdbcPlanOptimizer
extends TestBaseJdbcPushDownBase
{
private static final SessionHolder defaultSessionHolder = new SessionHolder();
private static final JdbcTableHandle jdbcTable = testTable;
private void matchPlan(PlanNode optimizedPlan, String exceptedSql)
{
assertTrue(optimizedPlan instanceof ProjectNode
&& ((ProjectNode) optimizedPlan).getSource() instanceof TableScanNode
&& ((JdbcTableHandle) ((TableScanNode) ((ProjectNode) optimizedPlan).getSource()).getTable().getConnectorHandle()).getGeneratedSql().map(JdbcQueryGeneratorResult.GeneratedSql::getSql).get().equals(exceptedSql));
}
@Test
public void testLimitPushDownWithStarSelection()
{
PlanBuilder pb = createPlanBuilder();
PlanNode originalPlan = limit(pb, 50L, tableScan(pb, jdbcTable, regionId, city, fare, amount));
PlanNode optimized = getOptimizedPlan(pb, originalPlan);
matchPlan(optimized, "SELECT regionid, city, fare, amount FROM 'table' LIMIT 50");
}
private PlanNode getOptimizedPlan(PlanBuilder planBuilder, PlanNode originalPlan)
{
BaseJdbcConfig config = new BaseJdbcConfig();
JdbcClient client = new TestPushwonClient();
Metadata metadata = MetadataManager.createTestMetadataManager();
RowExpressionService rowExpressionService = new ConnectorRowExpressionService(new RowExpressionDomainTranslator(metadata), new RowExpressionDeterminismEvaluator(metadata));
JdbcPlanOptimizer optimizer = new JdbcPlanOptimizer(client, new TestTypeManager(), config, rowExpressionService);
return optimizer.optimize(
originalPlan,
defaultSessionHolder.getConnectorSession(),
ImmutableMap.<String, Type>builder()
.put("regionid", INTEGER)
.put("city", VARCHAR)
.put("fare", DOUBLE)
.put("amount", BIGINT)
.build(),
new PlanSymbolAllocator(),
planBuilder.getIdAllocator());
}
}

View File

@ -1,140 +0,0 @@
/*
* Copyright (C) 2018-2020. Huawei Technologies Co., Ltd. All rights reserved.
* Licensed 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 io.prestosql.sql.builder;
import com.google.common.collect.ImmutableMap;
import io.airlift.log.Logger;
import io.prestosql.spi.connector.ConnectorFactory;
import io.prestosql.sql.builder.test.InMemoryJdbcDatabase;
import io.prestosql.testing.LocalQueryRunner;
import io.prestosql.testing.MaterializedResult;
import io.prestosql.tests.AbstractTestSqlQueryWriter;
import org.h2.Driver;
import org.intellij.lang.annotations.Language;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.sql.SQLException;
import java.util.Optional;
import static io.prestosql.testing.TestingSession.testSessionBuilder;
import static org.testng.Assert.assertEquals;
public class TestBaseSqlQueryWriter
extends AbstractTestSqlQueryWriter
{
private static final Logger LOGGER = Logger.get(TestBaseSqlQueryWriter.class);
private InMemoryJdbcDatabase database;
private LocalQueryRunner queryRunner;
protected TestBaseSqlQueryWriter()
{
super(new BaseSqlQueryWriter());
this.queryRunner = new LocalQueryRunner(testSessionBuilder()
.setCatalog(CONNECTOR_NAME)
.setSchema(SCHEMA_NAME)
.setSystemProperty("task_concurrency", "1").build());
}
@BeforeClass
public void setup()
{
try {
this.database = new InMemoryJdbcDatabase(new Driver(), "jdbc:h2:mem:tpch", CONNECTOR_NAME, SCHEMA_NAME, new BaseSqlQueryWriter());
this.database.createTables();
this.queryRunner.createCatalog(CONNECTOR_NAME, this.database.getConnectorFactory(), ImmutableMap.of());
super.setup();
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
@AfterClass
public void clean()
{
try {
if (this.database != null) {
this.database.close();
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
super.clean();
}
@Override
protected Optional<ConnectorFactory> getConnectorFactory()
{
return Optional.of(this.database.getConnectorFactory());
}
@Override
protected void compare(String original, String rewritten)
{
MaterializedResult actualQueryResults = this.queryRunner.execute(original);
MaterializedResult subQueryResults = this.queryRunner.execute(rewritten);
assertEquals(subQueryResults.getMaterializedRows(), actualQueryResults.getMaterializedRows(),
"result mismatch");
}
@Test
public void testWindowFunction()
{
// the hetu sql grammar of functions window
@Language("SQL")
String query = "select quantity , max(quantity) over(partition by linestatus " +
"order by returnflag desc nulls first rows 2 preceding) as ranking from lineitem order by quantity limit 100";
assertStatement(query, "SELECT", "MAX", "over", "partition", "BY", "order", "by",
"returnflag", "DESC", "NULLS", "FIRST", "ROWS", "2", "PRECEDING", "lineitem", "order", "BY", "quantity", "LIMIT");
}
@Test
public void testGroupByWithComplexGroupingOperations()
{
// the hetu sql grammar of select#group-by-clause
@Language("SQL")
String query = "SELECT name, address, sum(acctbal) FROM customer GROUP BY rollup(name,address)";
assertStatement(query, "SELECT", "sum", "acctbal", "GROUP", "BY", "GROUPING", "SETS", "name", "address", "name", "()");
}
@Test
public void testIntersectStatement()
{
// the hetu sql grammar of select#union-intersect-except-clause
LOGGER.info("Testing intersect statements");
// For io.prestosql.sql.planner.iterative.rule.ImplementIntersectAsUnion change intersect operator to union all operater so addtion add "marker", "count" key words
@Language("SQL") String queryIntersectDefault = "SELECT nationkey FROM nation INTERSECT SELECT regionkey FROM nation";
assertStatement(queryIntersectDefault, "SELECT", "FROM", "nationkey", "nation", "UNION", "ALL", "marker", "count");
@Language("SQL") String queryIntersectDistinct = "SELECT nationkey FROM nation INTERSECT DISTINCT SELECT regionkey FROM nation";
assertStatement(queryIntersectDistinct, "SELECT", "FROM", "nationkey", "nation", "UNION", "ALL", "marker", "count");
}
@Test
public void testExceptStatement()
{
// the hetu sql grammar of select#union-intersect-except-clause
LOGGER.info("Testing except statements");
// For io.prestosql.sql.planner.iterative.rule.ImplementIntersectAsUnion change intersect operator to union all operater so addtion add "marker", "count" key words
@Language("SQL") String queryExceptDefault = "SELECT nationkey FROM nation EXCEPT SELECT regionkey FROM nation";
assertStatement(queryExceptDefault, "SELECT", "FROM", "nationkey", "nation", "UNION", "ALL", "marker", "count");
@Language("SQL") String queryExceptDistinct = "SELECT nationkey FROM nation EXCEPT DISTINCT SELECT regionkey FROM nation";
assertStatement(queryExceptDistinct, "SELECT", "FROM", "nationkey", "nation", "UNION", "ALL", "marker", "count");
}
}

View File

@ -29,7 +29,6 @@ import io.prestosql.memory.QueryContext;
import io.prestosql.metadata.Metadata;
import io.prestosql.metadata.QualifiedObjectName;
import io.prestosql.metadata.Split;
import io.prestosql.metadata.TableHandle;
import io.prestosql.operator.Driver;
import io.prestosql.operator.DriverContext;
import io.prestosql.operator.FilterAndProjectOperator;
@ -47,17 +46,18 @@ import io.prestosql.spi.QueryId;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.connector.ConnectorPageSource;
import io.prestosql.spi.memory.MemoryPoolId;
import io.prestosql.spi.metadata.TableHandle;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.type.Type;
import io.prestosql.spiller.SpillSpaceTracker;
import io.prestosql.split.SplitSource;
import io.prestosql.sql.gen.PageFunctionCompiler;
import io.prestosql.sql.planner.Symbol;
import io.prestosql.sql.planner.SymbolAllocator;
import io.prestosql.sql.planner.PlanSymbolAllocator;
import io.prestosql.sql.planner.TypeAnalyzer;
import io.prestosql.sql.planner.TypeProvider;
import io.prestosql.sql.planner.optimizations.HashGenerationOptimizer;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.sql.relational.RowExpression;
import io.prestosql.sql.tree.Expression;
import io.prestosql.sql.tree.NodeRef;
import io.prestosql.testing.LocalQueryRunner;
@ -218,19 +218,19 @@ public abstract class AbstractOperatorBenchmark
protected final OperatorFactory createHashProjectOperator(int operatorId, PlanNodeId planNodeId, List<Type> types)
{
SymbolAllocator symbolAllocator = new SymbolAllocator();
PlanSymbolAllocator planSymbolAllocator = new PlanSymbolAllocator();
ImmutableMap.Builder<Symbol, Integer> symbolToInputMapping = ImmutableMap.builder();
ImmutableList.Builder<PageProjection> projections = ImmutableList.builder();
for (int channel = 0; channel < types.size(); channel++) {
Symbol symbol = symbolAllocator.newSymbol("h" + channel, types.get(channel));
Symbol symbol = planSymbolAllocator.newSymbol("h" + channel, types.get(channel));
symbolToInputMapping.put(symbol, channel);
projections.add(new InputPageProjection(channel, types.get(channel)));
}
Map<Symbol, Type> symbolTypes = symbolAllocator.getTypes().allTypes();
Map<Symbol, Type> symbolTypes = planSymbolAllocator.getTypes().allTypes();
Optional<Expression> hashExpression = HashGenerationOptimizer.getHashExpression(
localQueryRunner.getMetadata(),
symbolAllocator,
planSymbolAllocator,
ImmutableList.copyOf(symbolTypes.keySet()));
verify(hashExpression.isPresent());

View File

@ -19,8 +19,8 @@ import io.prestosql.operator.DriverContext;
import io.prestosql.operator.DriverFactory;
import io.prestosql.operator.OperatorFactory;
import io.prestosql.operator.TaskContext;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.sql.gen.JoinCompiler;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import io.prestosql.testing.NullOutputOperator.NullOutputOperatorFactory;

View File

@ -18,8 +18,8 @@ import io.prestosql.operator.AggregationOperator.AggregationOperatorFactory;
import io.prestosql.operator.OperatorFactory;
import io.prestosql.operator.aggregation.InternalAggregationFunction;
import io.prestosql.spi.function.Signature;
import io.prestosql.sql.planner.plan.AggregationNode.Step;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.spi.plan.AggregationNode.Step;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import java.util.List;

View File

@ -18,8 +18,8 @@ import io.prestosql.operator.AggregationOperator.AggregationOperatorFactory;
import io.prestosql.operator.OperatorFactory;
import io.prestosql.operator.aggregation.InternalAggregationFunction;
import io.prestosql.spi.function.Signature;
import io.prestosql.sql.planner.plan.AggregationNode.Step;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.spi.plan.AggregationNode.Step;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import java.util.List;

View File

@ -27,11 +27,10 @@ import io.prestosql.spi.Page;
import io.prestosql.spi.PageBuilder;
import io.prestosql.spi.block.Block;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.plan.AggregationNode.Step;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.type.Type;
import io.prestosql.sql.planner.plan.AggregationNode.Step;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import io.prestosql.util.DateTimeUtils;
import java.util.List;
import java.util.Optional;
@ -44,6 +43,7 @@ import static io.prestosql.spi.type.BigintType.BIGINT;
import static io.prestosql.spi.type.DateType.DATE;
import static io.prestosql.spi.type.DoubleType.DOUBLE;
import static io.prestosql.spi.type.VarcharType.VARCHAR;
import static io.prestosql.spi.util.DateTimeUtils.parseDate;
import static java.util.Objects.requireNonNull;
public class HandTpchQuery1
@ -235,7 +235,7 @@ public class HandTpchQuery1
return null;
}
private static final int MAX_SHIP_DATE = DateTimeUtils.parseDate("1998-09-02");
private static final int MAX_SHIP_DATE = parseDate("1998-09-02");
private static void filterAndProjectRowOriented(PageBuilder pageBuilder,
Block returnFlagBlock,

View File

@ -28,11 +28,10 @@ import io.prestosql.spi.Page;
import io.prestosql.spi.block.Block;
import io.prestosql.spi.connector.ConnectorSession;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.plan.AggregationNode.Step;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.sql.gen.PageFunctionCompiler;
import io.prestosql.sql.planner.plan.AggregationNode.Step;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import io.prestosql.util.DateTimeUtils;
import java.util.List;
import java.util.Optional;
@ -44,6 +43,7 @@ import static io.prestosql.spi.function.FunctionKind.AGGREGATE;
import static io.prestosql.spi.type.BigintType.BIGINT;
import static io.prestosql.spi.type.DateType.DATE;
import static io.prestosql.spi.type.DoubleType.DOUBLE;
import static io.prestosql.spi.util.DateTimeUtils.parseDate;
import static io.prestosql.sql.relational.Expressions.field;
public class HandTpchQuery6
@ -95,8 +95,8 @@ public class HandTpchQuery6
public static class TpchQuery6Filter
implements PageFilter
{
private static final int MIN_SHIP_DATE = DateTimeUtils.parseDate("1994-01-01");
private static final int MAX_SHIP_DATE = DateTimeUtils.parseDate("1995-01-01");
private static final int MIN_SHIP_DATE = parseDate("1994-01-01");
private static final int MAX_SHIP_DATE = parseDate("1995-01-01");
private static final InputChannels INPUT_CHANNELS = new InputChannels(1, 2, 3);
private boolean[] selectedPositions = new boolean[0];

View File

@ -20,9 +20,9 @@ import io.prestosql.operator.HashAggregationOperator.HashAggregationOperatorFact
import io.prestosql.operator.OperatorFactory;
import io.prestosql.operator.aggregation.InternalAggregationFunction;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.plan.AggregationNode.Step;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.type.Type;
import io.prestosql.sql.planner.plan.AggregationNode.Step;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import java.util.List;

View File

@ -27,9 +27,9 @@ import io.prestosql.operator.OperatorFactory;
import io.prestosql.operator.PagesIndex;
import io.prestosql.operator.PartitionedLookupSourceFactory;
import io.prestosql.operator.TaskContext;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.type.Type;
import io.prestosql.spiller.SingleStreamSpillerFactory;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import io.prestosql.testing.NullOutputOperator.NullOutputOperatorFactory;

View File

@ -26,9 +26,9 @@ import io.prestosql.operator.PagesIndex;
import io.prestosql.operator.PartitionedLookupSourceFactory;
import io.prestosql.operator.TaskContext;
import io.prestosql.operator.ValuesOperator.ValuesOperatorFactory;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.type.Type;
import io.prestosql.spiller.SingleStreamSpillerFactory;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import io.prestosql.testing.NullOutputOperator.NullOutputOperatorFactory;

View File

@ -28,9 +28,9 @@ import io.prestosql.operator.OperatorFactory;
import io.prestosql.operator.PagesIndex;
import io.prestosql.operator.PartitionedLookupSourceFactory;
import io.prestosql.operator.TaskContext;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.type.Type;
import io.prestosql.spiller.SingleStreamSpillerFactory;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import io.prestosql.testing.NullOutputOperator.NullOutputOperatorFactory;

View File

@ -18,9 +18,9 @@ import io.prestosql.operator.LimitOperator.LimitOperatorFactory;
import io.prestosql.operator.OperatorFactory;
import io.prestosql.operator.OrderByOperator.OrderByOperatorFactory;
import io.prestosql.operator.PagesIndex;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.type.Type;
import io.prestosql.sql.gen.OrderingCompiler;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import java.util.List;

View File

@ -18,10 +18,10 @@ import io.airlift.units.DataSize;
import io.prestosql.operator.FilterAndProjectOperator;
import io.prestosql.operator.OperatorFactory;
import io.prestosql.operator.project.PageProcessor;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.sql.gen.ExpressionCompiler;
import io.prestosql.sql.gen.PageFunctionCompiler;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.sql.relational.RowExpression;
import io.prestosql.testing.LocalQueryRunner;
import java.util.List;

View File

@ -15,7 +15,7 @@ package io.prestosql.benchmark;
import com.google.common.collect.ImmutableList;
import io.prestosql.operator.OperatorFactory;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import java.util.List;

View File

@ -16,8 +16,8 @@ package io.prestosql.benchmark;
import com.google.common.collect.ImmutableList;
import io.prestosql.operator.OperatorFactory;
import io.prestosql.operator.TopNOperator.TopNOperatorFactory;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.type.Type;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.testing.LocalQueryRunner;
import java.util.List;

View File

@ -24,7 +24,6 @@ import io.prestosql.memory.MemoryPool;
import io.prestosql.memory.QueryContext;
import io.prestosql.metadata.Metadata;
import io.prestosql.metadata.QualifiedObjectName;
import io.prestosql.metadata.TableHandle;
import io.prestosql.operator.Driver;
import io.prestosql.operator.TaskContext;
import io.prestosql.plugin.memory.MemoryConnectorFactory;
@ -33,6 +32,7 @@ import io.prestosql.spi.Page;
import io.prestosql.spi.Plugin;
import io.prestosql.spi.QueryId;
import io.prestosql.spi.memory.MemoryPoolId;
import io.prestosql.spi.metadata.TableHandle;
import io.prestosql.spiller.SpillSpaceTracker;
import io.prestosql.testing.LocalQueryRunner;
import io.prestosql.testing.PageConsumerOperator;

View File

@ -21,13 +21,13 @@ import io.prestosql.plugin.hive.HiveTableHandle;
import io.prestosql.plugin.tpcds.TpcdsTableHandle;
import io.prestosql.plugin.tpch.TpchTableHandle;
import io.prestosql.spi.connector.ConnectorTableHandle;
import io.prestosql.spi.plan.AggregationNode;
import io.prestosql.spi.plan.JoinNode;
import io.prestosql.spi.plan.TableScanNode;
import io.prestosql.spi.plan.ValuesNode;
import io.prestosql.sql.planner.assertions.BasePlanTest;
import io.prestosql.sql.planner.plan.AggregationNode;
import io.prestosql.sql.planner.plan.ExchangeNode;
import io.prestosql.sql.planner.plan.JoinNode;
import io.prestosql.sql.planner.plan.SemiJoinNode;
import io.prestosql.sql.planner.plan.TableScanNode;
import io.prestosql.sql.planner.plan.ValuesNode;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@ -42,8 +42,8 @@ import static com.google.common.base.Verify.verify;
import static com.google.common.io.Files.createParentDirs;
import static com.google.common.io.Files.write;
import static com.google.common.io.Resources.getResource;
import static io.prestosql.sql.planner.plan.JoinNode.DistributionType.REPLICATED;
import static io.prestosql.sql.planner.plan.JoinNode.Type.INNER;
import static io.prestosql.spi.plan.JoinNode.DistributionType.REPLICATED;
import static io.prestosql.spi.plan.JoinNode.Type.INNER;
import static io.prestosql.testing.TestngUtils.toDataProvider;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.hetu.core</groupId>
<artifactId>presto-root</artifactId>
<version>1.2.0-SNAPSHOT</version>
</parent>
<artifactId>presto-expressions</artifactId>
<name>presto-expressions</name>
<properties>
<air.main.basedir>${project.parent.basedir}</air.main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>io.hetu.core</groupId>
<artifactId>presto-spi</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,68 @@
/*
* Licensed 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 io.prestosql.expressions;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.ConstantExpression;
import io.prestosql.spi.relation.InputReferenceExpression;
import io.prestosql.spi.relation.LambdaDefinitionExpression;
import io.prestosql.spi.relation.RowExpressionVisitor;
import io.prestosql.spi.relation.SpecialForm;
import io.prestosql.spi.relation.VariableReferenceExpression;
/**
* The default visitor serves as a template for "consumer-like" tree traversal.
* {@param context} is the consumer to apply customized actions on the visiting RowExpression.
*/
public class DefaultRowExpressionTraversalVisitor<C>
implements RowExpressionVisitor<Void, C>
{
@Override
public Void visitInputReference(InputReferenceExpression input, C context)
{
return null;
}
@Override
public Void visitCall(CallExpression call, C context)
{
call.getArguments().forEach(argument -> argument.accept(this, context));
return null;
}
@Override
public Void visitConstant(ConstantExpression literal, C context)
{
return null;
}
@Override
public Void visitLambda(LambdaDefinitionExpression lambda, C context)
{
return null;
}
@Override
public Void visitVariableReference(VariableReferenceExpression reference, C context)
{
return null;
}
@Override
public Void visitSpecialForm(SpecialForm specialForm, C context)
{
specialForm.getArguments().forEach(argument -> argument.accept(this, context));
return null;
}
}

View File

@ -0,0 +1,691 @@
/*
* Licensed 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 io.prestosql.expressions;
import com.google.common.collect.ImmutableList;
import io.prestosql.spi.function.OperatorType;
import io.prestosql.spi.function.Signature;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.ConstantExpression;
import io.prestosql.spi.relation.DeterminismEvaluator;
import io.prestosql.spi.relation.InputReferenceExpression;
import io.prestosql.spi.relation.LambdaDefinitionExpression;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.relation.RowExpressionVisitor;
import io.prestosql.spi.relation.SpecialForm;
import io.prestosql.spi.relation.VariableReferenceExpression;
import io.prestosql.spi.sql.RowExpressionUtils;
import io.prestosql.spi.type.StandardTypes;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static io.prestosql.spi.function.FunctionKind.SCALAR;
import static io.prestosql.spi.function.OperatorType.EQUAL;
import static io.prestosql.spi.function.OperatorType.GREATER_THAN;
import static io.prestosql.spi.function.OperatorType.GREATER_THAN_OR_EQUAL;
import static io.prestosql.spi.function.OperatorType.IS_DISTINCT_FROM;
import static io.prestosql.spi.function.OperatorType.LESS_THAN;
import static io.prestosql.spi.function.OperatorType.LESS_THAN_OR_EQUAL;
import static io.prestosql.spi.function.OperatorType.NOT_EQUAL;
import static io.prestosql.spi.relation.SpecialForm.Form.AND;
import static io.prestosql.spi.relation.SpecialForm.Form.OR;
import static io.prestosql.spi.sql.RowExpressionUtils.FALSE_CONSTANT;
import static io.prestosql.spi.sql.RowExpressionUtils.TRUE_CONSTANT;
import static io.prestosql.spi.sql.RowExpressionUtils.combinePredicates;
import static io.prestosql.spi.sql.RowExpressionUtils.extractPredicates;
import static io.prestosql.spi.sql.RowExpressionUtils.filterConjuncts;
import static io.prestosql.spi.sql.RowExpressionUtils.isConjunctionOrDisjunction;
import static io.prestosql.spi.sql.RowExpressionUtils.or;
import static io.prestosql.spi.type.BooleanType.BOOLEAN;
import static io.prestosql.spi.type.TypeSignature.parseTypeSignature;
import static java.lang.Math.min;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
public final class LogicalRowExpressions
{
// 10000 is very conservative estimation
private static final int ELIMINATE_COMMON_SIZE_LIMIT = 10000;
private final DeterminismEvaluator determinismEvaluator;
public LogicalRowExpressions(DeterminismEvaluator determinismEvaluator)
{
this.determinismEvaluator = determinismEvaluator;
}
/**
* Given a logical expression, the goal is to push negation to the leaf nodes.
* This only applies to propositional logic and comparison. this utility cannot be applied to high-order logic.
* Examples of non-applicable cases could be f(a AND b) > 5
*
* An applicable example:
*
* NOT
* |
* ___OR_ AND
* / \ / \
* NOT OR ==> AND AND
* | / \ / \ / \
* AND c NOT a b NOT d
* / \ | |
* a b d c
*/
public RowExpression pushNegationToLeaves(RowExpression expression)
{
return expression.accept(new PushNegationVisitor(), null);
}
/**
* Given a logical expression, the goal is to convert to conjuctive normal form (CNF).
* This requires making a call to `pushNegationToLeaves`. There is no guarantee as to
* the balance of the resulting expression tree.
*
* This only applies to propositional logic. this utility cannot be applied to high-order logic.
* Examples of non-applicable cases could be f(a AND b) > 5
*
* NOTE: This may exponentially increase the number of RowExpressions in the expression.
*
* An applicable example:
*
* NOT
* |
* ___OR_ AND
* / \ / \
* NOT OR ==> OR AND
* | / \ / \ / \
* OR c NOT a b NOT d
* / \ | |
* a b d c
*/
public RowExpression convertToConjunctiveNormalForm(RowExpression expression)
{
return convertToNormalForm(expression, AND);
}
/**
* Given a logical expression, the goal is to convert to disjunctive normal form (DNF).
* The same limitations, format, and risks apply as for converting to conjunctive normal form (CNF).
*
* An applicable example:
*
* NOT OR
* | / \
* ___OR_ AND AND
* / \ / \ / \
* NOT OR ==> a AND b AND
* | / \ / \ / \
* OR c NOT NOT d NOT d
* / \ | | |
* a b d c c
*/
public RowExpression convertToDisjunctiveNormalForm(RowExpression expression)
{
return convertToNormalForm(expression, OR);
}
public RowExpression minimalNormalForm(RowExpression expression)
{
RowExpression conjunctiveNormalForm = convertToConjunctiveNormalForm(expression);
RowExpression disjunctiveNormalForm = convertToDisjunctiveNormalForm(expression);
return numOfClauses(conjunctiveNormalForm) > numOfClauses(disjunctiveNormalForm) ? disjunctiveNormalForm : conjunctiveNormalForm;
}
public RowExpression convertToNormalForm(RowExpression expression, SpecialForm.Form clauseJoiner)
{
return pushNegationToLeaves(expression).accept(new ConvertNormalFormVisitor(), rootContext(clauseJoiner));
}
public RowExpression filterDeterministicConjuncts(RowExpression expression)
{
return filterConjuncts(expression, this.determinismEvaluator::isDeterministic);
}
public RowExpression filterNonDeterministicConjuncts(RowExpression expression)
{
return filterConjuncts(expression, predicate -> !this.determinismEvaluator.isDeterministic(predicate));
}
private final class PushNegationVisitor
implements RowExpressionVisitor<RowExpression, Void>
{
@Override
public RowExpression visitCall(CallExpression call, Void context)
{
if (!isNegationExpression(call)) {
return call;
}
checkArgument(call.getArguments().size() == 1, "Not expression should have exactly one argument");
RowExpression argument = call.getArguments().get(0);
// eliminate two consecutive negations
if (isNegationExpression(argument)) {
return ((CallExpression) argument).getArguments().get(0).accept(new PushNegationVisitor(), null);
}
if (isComparisonExpression(argument)) {
return negateComparison((CallExpression) argument);
}
if (!isConjunctionOrDisjunction(argument)) {
return call;
}
// push negation through conjunction or disjunction
SpecialForm specialForm = ((SpecialForm) argument);
RowExpression left = specialForm.getArguments().get(0);
RowExpression right = specialForm.getArguments().get(1);
if (specialForm.getForm() == AND) {
// !(a AND b) ==> !a OR !b
return or(notCallExpression(left).accept(new PushNegationVisitor(), null), notCallExpression(right).accept(this, null));
}
// !(a OR b) ==> !a AND !b
return and(notCallExpression(left).accept(new PushNegationVisitor(), null), notCallExpression(right).accept(this, null));
}
private RowExpression negateComparison(CallExpression expression)
{
OperatorType newOperator = negate(getOperator(expression).orElse(null));
if (newOperator == null) {
return new CallExpression(new Signature("not",
SCALAR,
parseTypeSignature(StandardTypes.BOOLEAN),
ImmutableList.of(parseTypeSignature(StandardTypes.BOOLEAN))),
BOOLEAN,
singletonList(expression));
}
checkArgument(expression.getArguments().size() == 2, "Comparison expression must have exactly two arguments");
RowExpression left = expression.getArguments().get(0).accept(this, null);
RowExpression right = expression.getArguments().get(1).accept(this, null);
return new CallExpression(
Signature.internalOperator(newOperator, BOOLEAN, asList(left.getType(), right.getType())),
BOOLEAN,
asList(left, right));
}
@Override
public RowExpression visitSpecialForm(SpecialForm specialForm, Void context)
{
if (!isConjunctionOrDisjunction(specialForm)) {
return specialForm;
}
RowExpression left = specialForm.getArguments().get(0);
RowExpression right = specialForm.getArguments().get(1);
if (specialForm.getForm() == AND) {
return and(left.accept(this, null), right.accept(this, null));
}
return or(left.accept(this, null), right.accept(this, null));
}
@Override
public RowExpression visitInputReference(InputReferenceExpression reference, Void context)
{
return reference;
}
@Override
public RowExpression visitConstant(ConstantExpression literal, Void context)
{
return literal;
}
@Override
public RowExpression visitLambda(LambdaDefinitionExpression lambda, Void context)
{
return lambda;
}
@Override
public RowExpression visitVariableReference(VariableReferenceExpression reference, Void context)
{
return reference;
}
}
private static ConvertNormalFormVisitorContext rootContext(SpecialForm.Form clauseJoiner)
{
return new ConvertNormalFormVisitorContext(clauseJoiner, 0);
}
private static class ConvertNormalFormVisitorContext
{
private final SpecialForm.Form expectedClauseJoiner;
private final int depth;
public ConvertNormalFormVisitorContext(SpecialForm.Form expectedClauseJoiner, int depth)
{
this.expectedClauseJoiner = expectedClauseJoiner;
this.depth = depth;
}
public ConvertNormalFormVisitorContext childContext()
{
return new ConvertNormalFormVisitorContext(expectedClauseJoiner, depth + 1);
}
}
private class ConvertNormalFormVisitor
implements RowExpressionVisitor<RowExpression, ConvertNormalFormVisitorContext>
{
@Override
public RowExpression visitSpecialForm(SpecialForm specialForm, ConvertNormalFormVisitorContext context)
{
if (!isConjunctionOrDisjunction(specialForm)) {
return specialForm;
}
// Attempt to convert sub expression to expected normal form, deduplicate and fold constants.
RowExpression rewritten = combinePredicates(
specialForm.getForm(),
extractPredicates(specialForm.getForm(), specialForm).stream()
.map(subPredicate -> subPredicate.accept(this, context.childContext()))
.collect(toList()));
if (!isConjunctionOrDisjunction(rewritten)) {
return rewritten;
}
SpecialForm rewrittenSpecialForm = (SpecialForm) rewritten;
io.prestosql.spi.relation.SpecialForm.Form expressionClauseJoiner = rewrittenSpecialForm.getForm();
List<List<RowExpression>> groupedClauses = getGroupedClauses(rewrittenSpecialForm);
if (groupedClauses.stream().mapToInt(List::size).sum() > ELIMINATE_COMMON_SIZE_LIMIT) {
return rewritten;
}
groupedClauses = eliminateCommonPredicates(groupedClauses);
// extractCommonPredicates can produce opposite expectedClauseJoiner
List<List<RowExpression>> groupedClausesWithFlippedJoiner = extractCommonPredicates(expressionClauseJoiner, groupedClauses);
if (groupedClausesWithFlippedJoiner != null) {
groupedClauses = groupedClausesWithFlippedJoiner;
expressionClauseJoiner = flip(expressionClauseJoiner);
}
int numClauses = groupedClauses.stream().mapToInt(List::size).sum();
int numClausesProducedByDistributiveLaw = groupedClauses.size();
for (List<RowExpression> group : groupedClauses) {
numClausesProducedByDistributiveLaw *= group.size();
// If distributive rule will produce too many sub expressions, return what we have instead.
if (context.depth > 0 || numClausesProducedByDistributiveLaw > numClauses * 2) {
return combineGroupedClauses(expressionClauseJoiner, groupedClauses);
}
}
// size unchanged means distributive law will not apply, we can save an unnecessary crossProduct call.
// For example, distributive law cannot apply to (a || b || c).
if (numClausesProducedByDistributiveLaw == numClauses) {
return combineGroupedClauses(expressionClauseJoiner, groupedClauses);
}
// TODO if the non-deterministic operation only appears in the only sub-predicates that has size >1, we can still expand it.
// For example: a && b && c && (d || e) can still be expanded if d or e is non-deterministic.
boolean deterministic = groupedClauses.stream()
.flatMap(List::stream)
.allMatch(determinismEvaluator::isDeterministic);
// Do not apply distributive law if there is non-deterministic element or we have already got expected expectedClauseJoiner.
if (expressionClauseJoiner == context.expectedClauseJoiner || !deterministic) {
return combineGroupedClauses(expressionClauseJoiner, groupedClauses);
}
// else, we apply distributive law and rewrite based on distributive property of Boolean algebra, for example
// (l1 OR l2) AND (r1 OR r2) <=> (l1 AND r1) OR (l1 AND r2) OR (l2 AND r1) OR (l2 AND r2)
groupedClauses = crossProduct(groupedClauses);
return combineGroupedClauses(context.expectedClauseJoiner, groupedClauses);
}
@Override
public RowExpression visitCall(CallExpression call, ConvertNormalFormVisitorContext context)
{
return call;
}
@Override
public RowExpression visitInputReference(InputReferenceExpression reference, ConvertNormalFormVisitorContext context)
{
return reference;
}
@Override
public RowExpression visitConstant(ConstantExpression literal, ConvertNormalFormVisitorContext context)
{
return literal;
}
@Override
public RowExpression visitLambda(LambdaDefinitionExpression lambda, ConvertNormalFormVisitorContext context)
{
return lambda;
}
@Override
public RowExpression visitVariableReference(VariableReferenceExpression reference, ConvertNormalFormVisitorContext context)
{
return reference;
}
}
private boolean isNegationExpression(RowExpression expression)
{
return expression instanceof CallExpression && ((CallExpression) expression).getSignature().getName().equals("not");
}
private boolean isComparisonExpression(RowExpression expression)
{
if (expression instanceof CallExpression) {
Signature signature = ((CallExpression) expression).getSignature();
try {
OperatorType operatorType = signature.unmangleOperator(signature.getName());
return operatorType.equals(EQUAL) ||
operatorType.equals(NOT_EQUAL) ||
operatorType.equals(LESS_THAN) ||
operatorType.equals(LESS_THAN_OR_EQUAL) ||
operatorType.equals(GREATER_THAN) ||
operatorType.equals(GREATER_THAN_OR_EQUAL) ||
operatorType.equals(IS_DISTINCT_FROM);
}
catch (IllegalArgumentException e) {
return false;
}
}
return false;
}
/**
* Extract the component predicates as a list of list in which is grouped so that the outer level has same conjunctive/disjunctive joiner as original predicate and
* inner level has opposite joiner.
* For example, (a or b) and (a or c) or ( a or c) returns [[a,b], [a,c], [a,c]]
*/
private List<List<RowExpression>> getGroupedClauses(SpecialForm expression)
{
return extractPredicates(expression.getForm(), expression).stream()
.map(RowExpressionUtils::extractPredicates)
.collect(toList());
}
private int numOfClauses(RowExpression expression)
{
if (expression instanceof SpecialForm) {
return getGroupedClauses((SpecialForm) expression).stream().mapToInt(List::size).sum();
}
return 1;
}
/**
* Eliminate a sub predicate if its sub predicates contain its peer.
* For example: (a || b) && a = a, (a && b) || b = b
*/
private List<List<RowExpression>> eliminateCommonPredicates(List<List<RowExpression>> groupedClauses)
{
if (groupedClauses.size() < 2) {
return groupedClauses;
}
// initialize to self
int[] reduceTo = IntStream.range(0, groupedClauses.size()).toArray();
for (int i = 0; i < groupedClauses.size(); i++) {
// Do not eliminate predicates contain non-deterministic value
// (a || b) && a should be kept same if a is non-deterministic.
// TODO We can eliminate (a || b) && a if a is deterministic even b is not.
if (groupedClauses.get(i).stream().allMatch(determinismEvaluator::isDeterministic)) {
for (int j = 0; j < groupedClauses.size(); j++) {
if (isSuperSet(groupedClauses.get(reduceTo[i]), groupedClauses.get(j))) {
reduceTo[i] = j; //prefer smaller set
}
else if (isSameSet(groupedClauses.get(reduceTo[i]), groupedClauses.get(j))) {
reduceTo[i] = min(reduceTo[i], j); //prefer predicates that appears earlier.
}
}
}
}
return unmodifiableList(stream(reduceTo)
.distinct()
.boxed()
.map(groupedClauses::get)
.collect(toList()));
}
/**
* Eliminate a sub predicate if its component predicates contain its peer. Will return null if cannot extract common predicates otherwise return a nested list with flipped form
* For example:
* (a || b || c || d) && (a || b || e || f) -> a || b || ((c || d) && (e || f))
* (a || b) && (c || d) -> null
*/
private List<List<RowExpression>> extractCommonPredicates(SpecialForm.Form rootClauseJoiner, List<List<RowExpression>> groupedPredicates)
{
if (groupedPredicates.isEmpty()) {
return null;
}
Set<RowExpression> commonPredicates = new LinkedHashSet<>(groupedPredicates.get(0));
for (int i = 1; i < groupedPredicates.size(); i++) {
// remove all non-common predicates
commonPredicates.retainAll(groupedPredicates.get(i));
}
if (commonPredicates.isEmpty()) {
return null;
}
// extract the component predicates that are not in common predicates: [(c || d), (e || f)]
List<RowExpression> remainingPredicates = new ArrayList<>();
for (List<RowExpression> group : groupedPredicates) {
List<RowExpression> remaining = group.stream()
.filter(predicate -> !commonPredicates.contains(predicate))
.collect(toList());
remainingPredicates.add(combinePredicates(flip(rootClauseJoiner), remaining));
}
// combine common predicates and remaining predicates to flipped nested form. For example: [[a], [b], [ (c || d), (e || f)]
return Stream.concat(commonPredicates.stream().map(predicate -> singletonList(predicate)), Stream.of(remainingPredicates))
.collect(toList());
}
private RowExpression combineGroupedClauses(SpecialForm.Form clauseJoiner, List<List<RowExpression>> nestedPredicates)
{
return combinePredicates(clauseJoiner, nestedPredicates.stream()
.map(predicate -> combinePredicates(flip(clauseJoiner), predicate))
.collect(toList()));
}
/**
* Cartesian cross product of List of List.
* For example, [[a], [b, c], [d]] becomes [[a,b,d], [a,c,d]]
*/
private static List<List<RowExpression>> crossProduct(List<List<RowExpression>> groupedPredicates)
{
checkArgument(groupedPredicates.size() > 0, "Must contains more than one child");
List<List<RowExpression>> result = groupedPredicates.get(0).stream().map(Collections::singletonList).collect(toList());
for (int i = 1; i < groupedPredicates.size(); i++) {
result = crossProduct(result, groupedPredicates.get(i));
}
return result;
}
private static List<List<RowExpression>> crossProduct(List<List<RowExpression>> previousCrossProduct, List<RowExpression> clauses)
{
List<List<RowExpression>> result = new ArrayList<>();
for (List<RowExpression> previousClauses : previousCrossProduct) {
for (RowExpression newClause : clauses) {
List<RowExpression> newClauses = new ArrayList<>(previousClauses);
newClauses.add(newClause);
result.add(newClauses);
}
}
return result;
}
private static SpecialForm.Form flip(SpecialForm.Form binaryLogicalOperation)
{
switch (binaryLogicalOperation) {
case AND:
return OR;
case OR:
return AND;
}
throw new UnsupportedOperationException("Invalid binary logical operation: " + binaryLogicalOperation);
}
private Optional<OperatorType> getOperator(RowExpression expression)
{
try {
if (expression instanceof CallExpression) {
Signature signature = ((CallExpression) expression).getSignature();
return Optional.of(signature.unmangleOperator(signature.getName()));
}
}
catch (IllegalArgumentException e) {
return Optional.empty();
}
return Optional.empty();
}
private RowExpression notCallExpression(RowExpression argument)
{
return new CallExpression(new Signature("not",
SCALAR,
parseTypeSignature(StandardTypes.BOOLEAN),
ImmutableList.of(parseTypeSignature(StandardTypes.BOOLEAN))),
BOOLEAN,
singletonList(argument));
}
private static OperatorType negate(OperatorType operator)
{
switch (operator) {
case EQUAL:
return NOT_EQUAL;
case NOT_EQUAL:
return EQUAL;
case GREATER_THAN:
return LESS_THAN_OR_EQUAL;
case LESS_THAN:
return GREATER_THAN_OR_EQUAL;
case LESS_THAN_OR_EQUAL:
return GREATER_THAN;
case GREATER_THAN_OR_EQUAL:
return LESS_THAN;
}
return null;
}
private static void checkArgument(boolean condition, String message, Object... arguments)
{
if (!condition) {
throw new IllegalArgumentException(String.format(message, arguments));
}
}
private static <T> boolean isSuperSet(Collection<T> a, Collection<T> b)
{
// We assumes a, b both are de-duplicated collections.
return a.size() > b.size() && a.containsAll(b);
}
private static <T> boolean isSameSet(Collection<T> a, Collection<T> b)
{
// We assumes a, b both are de-duplicated collections.
return a.size() == b.size() && a.containsAll(b) && b.containsAll(a);
}
public static RowExpression and(RowExpression... expressions)
{
return and(asList(expressions));
}
public static RowExpression and(Collection<RowExpression> expressions)
{
return binaryExpression(AND, expressions);
}
public static RowExpression binaryExpression(SpecialForm.Form form, Collection<RowExpression> expressions)
{
requireNonNull(form, "operator is null");
requireNonNull(expressions, "expressions is null");
if (expressions.isEmpty()) {
switch (form) {
case AND:
return TRUE_CONSTANT;
case OR:
return FALSE_CONSTANT;
default:
throw new IllegalArgumentException("Unsupported binary expression operator");
}
}
// Build balanced tree for efficient recursive processing that
// preserves the evaluation order of the input expressions.
//
// The tree is built bottom up by combining pairs of elements into
// binary AND expressions.
//
// Example:
//
// Initial state:
// a b c d e
//
// First iteration:
//
// /\ /\ e
// a b c d
//
// Second iteration:
//
// / \ e
// /\ /\
// a b c d
//
//
// Last iteration:
//
// / \
// / \ e
// /\ /\
// a b c d
Queue<RowExpression> queue = new ArrayDeque<>(expressions);
while (queue.size() > 1) {
Queue<RowExpression> buffer = new ArrayDeque<>();
// combine pairs of elements
while (queue.size() >= 2) {
List<RowExpression> arguments = asList(queue.remove(), queue.remove());
buffer.add(new SpecialForm(form, BOOLEAN, arguments));
}
// if there's and odd number of elements, just append the last one
if (!queue.isEmpty()) {
buffer.add(queue.remove());
}
// continue processing the pairs that were just built
queue = buffer;
}
return queue.remove();
}
}

View File

@ -0,0 +1,40 @@
/*
* Licensed 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 io.prestosql.expressions;
import io.prestosql.spi.relation.RowExpression;
import java.util.Map;
public class RowExpressionNodeInliner
extends RowExpressionRewriter<Void>
{
private final Map<? extends RowExpression, ? extends RowExpression> mappings;
public static RowExpression replaceExpression(RowExpression expression, Map<? extends RowExpression, ? extends RowExpression> mappings)
{
return RowExpressionTreeRewriter.rewriteWith(new RowExpressionNodeInliner(mappings), expression);
}
public RowExpressionNodeInliner(Map<? extends RowExpression, ? extends RowExpression> mappings)
{
this.mappings = mappings;
}
@Override
public RowExpression rewriteRowExpression(RowExpression node, Void context, RowExpressionTreeRewriter<Void> treeRewriter)
{
return mappings.get(node);
}
}

View File

@ -0,0 +1,60 @@
/*
* Licensed 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 io.prestosql.expressions;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.ConstantExpression;
import io.prestosql.spi.relation.InputReferenceExpression;
import io.prestosql.spi.relation.LambdaDefinitionExpression;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.relation.SpecialForm;
import io.prestosql.spi.relation.VariableReferenceExpression;
public class RowExpressionRewriter<C>
{
public RowExpression rewriteRowExpression(RowExpression node, C context, RowExpressionTreeRewriter<C> treeRewriter)
{
return null;
}
public RowExpression rewriteInputReference(InputReferenceExpression node, C context, RowExpressionTreeRewriter<C> treeRewriter)
{
return rewriteRowExpression(node, context, treeRewriter);
}
public RowExpression rewriteCall(CallExpression node, C context, RowExpressionTreeRewriter<C> treeRewriter)
{
return rewriteRowExpression(node, context, treeRewriter);
}
public RowExpression rewriteConstant(ConstantExpression node, C context, RowExpressionTreeRewriter<C> treeRewriter)
{
return rewriteRowExpression(node, context, treeRewriter);
}
public RowExpression rewriteLambda(LambdaDefinitionExpression node, C context, RowExpressionTreeRewriter<C> treeRewriter)
{
return rewriteRowExpression(node, context, treeRewriter);
}
public RowExpression rewriteVariableReference(VariableReferenceExpression node, C context, RowExpressionTreeRewriter<C> treeRewriter)
{
return rewriteRowExpression(node, context, treeRewriter);
}
public RowExpression rewriteSpecialForm(SpecialForm node, C context, RowExpressionTreeRewriter<C> treeRewriter)
{
return rewriteRowExpression(node, context, treeRewriter);
}
}

View File

@ -0,0 +1,213 @@
/*
* Licensed 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 io.prestosql.expressions;
import io.prestosql.spi.relation.CallExpression;
import io.prestosql.spi.relation.ConstantExpression;
import io.prestosql.spi.relation.InputReferenceExpression;
import io.prestosql.spi.relation.LambdaDefinitionExpression;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.relation.RowExpressionVisitor;
import io.prestosql.spi.relation.SpecialForm;
import io.prestosql.spi.relation.VariableReferenceExpression;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public final class RowExpressionTreeRewriter<C>
{
private final RowExpressionRewriter<C> rewriter;
private final RowExpressionVisitor<RowExpression, Context<C>> visitor;
public static <C, T extends RowExpression> T rewriteWith(RowExpressionRewriter<C> rewriter, T node)
{
return new RowExpressionTreeRewriter<>(rewriter).rewrite(node, null);
}
public static <C, T extends RowExpression> T rewriteWith(RowExpressionRewriter<C> rewriter, T node, C context)
{
return new RowExpressionTreeRewriter<>(rewriter).rewrite(node, context);
}
public RowExpressionTreeRewriter(RowExpressionRewriter<C> rewriter)
{
this.rewriter = rewriter;
this.visitor = new RewritingVisitor();
}
private List<RowExpression> rewrite(List<RowExpression> items, Context<C> context)
{
List<RowExpression> rewritenExpressions = new ArrayList<>();
for (RowExpression expression : items) {
rewritenExpressions.add(rewrite(expression, context.get()));
}
return Collections.unmodifiableList(rewritenExpressions);
}
@SuppressWarnings("unchecked")
public <T extends RowExpression> T rewrite(T node, C context)
{
return (T) node.accept(visitor, new Context<>(context, false));
}
/**
* Invoke the default rewrite logic explicitly. Specifically, it skips the invocation of the expression rewriter for the provided node.
*/
@SuppressWarnings("unchecked")
public <T extends RowExpression> T defaultRewrite(T node, C context)
{
return (T) node.accept(visitor, new Context<>(context, true));
}
private class RewritingVisitor
implements RowExpressionVisitor<RowExpression, Context<C>>
{
@Override
public RowExpression visitInputReference(InputReferenceExpression input, Context<C> context)
{
if (!context.isDefaultRewrite()) {
RowExpression result = rewriter.rewriteInputReference(input, context.get(), RowExpressionTreeRewriter.this);
if (result != null) {
return result;
}
}
return input;
}
@Override
public RowExpression visitCall(CallExpression call, Context<C> context)
{
if (!context.isDefaultRewrite()) {
RowExpression result = rewriter.rewriteCall(call, context.get(), RowExpressionTreeRewriter.this);
if (result != null) {
return result;
}
}
List<RowExpression> arguments = rewrite(call.getArguments(), context);
if (!sameElements(call.getArguments(), arguments)) {
return new CallExpression(call.getSignature(), call.getType(), arguments);
}
return call;
}
@Override
public RowExpression visitConstant(ConstantExpression literal, Context<C> context)
{
if (!context.isDefaultRewrite()) {
RowExpression result = rewriter.rewriteConstant(literal, context.get(), RowExpressionTreeRewriter.this);
if (result != null) {
return result;
}
}
return literal;
}
@Override
public RowExpression visitLambda(LambdaDefinitionExpression lambda, Context<C> context)
{
if (!context.isDefaultRewrite()) {
RowExpression result = rewriter.rewriteLambda(lambda, context.get(), RowExpressionTreeRewriter.this);
if (result != null) {
return result;
}
}
RowExpression body = rewrite(lambda.getBody(), context.get());
if (body != lambda.getBody()) {
return new LambdaDefinitionExpression(lambda.getArgumentTypes(), lambda.getArguments(), body);
}
return lambda;
}
@Override
public RowExpression visitVariableReference(VariableReferenceExpression variable, Context<C> context)
{
if (!context.isDefaultRewrite()) {
RowExpression result = rewriter.rewriteVariableReference(variable, context.get(), RowExpressionTreeRewriter.this);
if (result != null) {
return result;
}
}
return variable;
}
@Override
public RowExpression visitSpecialForm(SpecialForm specialForm, Context<C> context)
{
if (!context.isDefaultRewrite()) {
RowExpression result = rewriter.rewriteSpecialForm(specialForm, context.get(), RowExpressionTreeRewriter.this);
if (result != null) {
return result;
}
}
List<RowExpression> arguments = rewrite(specialForm.getArguments(), context);
if (!sameElements(specialForm.getArguments(), arguments)) {
return new SpecialForm(specialForm.getForm(), specialForm.getType(), arguments);
}
return specialForm;
}
}
public static class Context<C>
{
private final boolean defaultRewrite;
private final C context;
private Context(C context, boolean defaultRewrite)
{
this.context = context;
this.defaultRewrite = defaultRewrite;
}
public C get()
{
return context;
}
public boolean isDefaultRewrite()
{
return defaultRewrite;
}
}
@SuppressWarnings("ObjectEquality")
private static <T> boolean sameElements(Collection<? extends T> a, Collection<? extends T> b)
{
if (a.size() != b.size()) {
return false;
}
Iterator<? extends T> first = a.iterator();
Iterator<? extends T> second = b.iterator();
while (first.hasNext() && second.hasNext()) {
if (first.next() != second.next()) {
return false;
}
}
return true;
}
}

View File

@ -16,8 +16,8 @@ package io.prestosql.plugin.geospatial;
import com.google.common.collect.ImmutableMap;
import io.prestosql.metadata.Metadata;
import io.prestosql.metadata.QualifiedObjectName;
import io.prestosql.metadata.TableHandle;
import io.prestosql.plugin.memory.MemoryConnectorFactory;
import io.prestosql.spi.metadata.TableHandle;
import io.prestosql.testing.LocalQueryRunner;
import io.prestosql.testing.MaterializedResult;
import org.openjdk.jmh.annotations.Benchmark;

View File

@ -14,37 +14,59 @@
package io.prestosql.plugin.geospatial;
import com.google.common.collect.ImmutableMap;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.type.Type;
import io.prestosql.sql.TestingRowExpressionTranslator;
import io.prestosql.sql.planner.TypeProvider;
import io.prestosql.sql.planner.iterative.rule.ExtractSpatialJoins.ExtractSpatialInnerJoin;
import io.prestosql.sql.planner.iterative.rule.test.BaseRuleTest;
import io.prestosql.sql.planner.iterative.rule.test.PlanBuilder;
import io.prestosql.sql.planner.iterative.rule.test.RuleAssert;
import io.prestosql.sql.planner.iterative.rule.test.RuleTester;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import static io.prestosql.plugin.geospatial.GeometryType.GEOMETRY;
import static io.prestosql.plugin.geospatial.SphericalGeographyType.SPHERICAL_GEOGRAPHY;
import static io.prestosql.spi.plan.JoinNode.Type.INNER;
import static io.prestosql.spi.type.BigintType.BIGINT;
import static io.prestosql.spi.type.VarcharType.VARCHAR;
import static io.prestosql.sql.planner.assertions.PlanMatchPattern.expression;
import static io.prestosql.sql.planner.assertions.PlanMatchPattern.project;
import static io.prestosql.sql.planner.assertions.PlanMatchPattern.spatialJoin;
import static io.prestosql.sql.planner.assertions.PlanMatchPattern.values;
import static io.prestosql.sql.planner.plan.JoinNode.Type.INNER;
public class TestExtractSpatialInnerJoin
extends BaseRuleTest
{
private TestingRowExpressionTranslator sqlToRowExpressionTranslator;
public TestExtractSpatialInnerJoin()
{
super(new GeoPlugin());
}
@BeforeClass
public void setupTranslator()
{
this.sqlToRowExpressionTranslator = new TestingRowExpressionTranslator(tester().getMetadata());
}
@Test
public void testDoesNotFire()
{
// scalar expression
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(ST_GeometryFromText('POLYGON ...'), b)"),
p.filter(
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText('POLYGON ((0 0, 0 0, 0 0, 0 0))'), b)",
ImmutableMap.of("b", GEOMETRY)),
p.join(INNER,
p.values(),
p.values(p.symbol("b")))))
@ -53,7 +75,10 @@ public class TestExtractSpatialInnerJoin
// OR operand
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(ST_GeometryFromText(wkt), point) OR name_1 != name_2"),
p.filter(
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt), point) OR name_1 != name_2",
ImmutableMap.of("wkt", VARCHAR, "point", GEOMETRY, "name_1", BIGINT, "name_2", BIGINT)),
p.join(INNER,
p.values(p.symbol("wkt", VARCHAR), p.symbol("name_1")),
p.values(p.symbol("point", GEOMETRY), p.symbol("name_2")))))
@ -62,7 +87,10 @@ public class TestExtractSpatialInnerJoin
// NOT operator
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("NOT ST_Contains(ST_GeometryFromText(wkt), point)"),
p.filter(
sqlToRowExpression(
"NOT ST_Contains(ST_GeometryFromText(wkt), point)",
ImmutableMap.of("wkt", VARCHAR, "point", GEOMETRY, "name_1", BIGINT, "name_2", BIGINT)),
p.join(INNER,
p.values(p.symbol("wkt", VARCHAR), p.symbol("name_1")),
p.values(p.symbol("point", GEOMETRY), p.symbol("name_2")))))
@ -71,7 +99,10 @@ public class TestExtractSpatialInnerJoin
// ST_Distance(...) > r
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Distance(a, b) > 5"),
p.filter(
sqlToRowExpression(
"ST_Distance(a, b) > 5",
ImmutableMap.of("a", GEOMETRY, "b", GEOMETRY)),
p.join(INNER,
p.values(p.symbol("a", GEOMETRY)),
p.values(p.symbol("b", GEOMETRY)))))
@ -185,7 +216,7 @@ public class TestExtractSpatialInnerJoin
{
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression(filter),
p.filter(sqlToRowExpression(filter, ImmutableMap.of("a", GEOMETRY, "b", GEOMETRY, "name_a", BIGINT, "name_b", BIGINT, "r", BIGINT)),
p.join(INNER,
p.values(p.symbol("a", GEOMETRY), p.symbol("name_a")),
p.values(p.symbol("b", GEOMETRY), p.symbol("name_b"), p.symbol("r")))))
@ -199,7 +230,7 @@ public class TestExtractSpatialInnerJoin
{
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression(filter),
p.filter(sqlToRowExpression(filter, ImmutableMap.of("a", GEOMETRY, "b", GEOMETRY, "name_a", BIGINT, "name_b", BIGINT, "r", BIGINT)),
p.join(INNER,
p.values(p.symbol("a", GEOMETRY), p.symbol("name_a")),
p.values(p.symbol("b", GEOMETRY), p.symbol("name_b"), p.symbol("r")))))
@ -214,7 +245,7 @@ public class TestExtractSpatialInnerJoin
{
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression(filter),
p.filter(sqlToRowExpression(filter, buildBigIntTypeProviderMap("lat_a", "lng_a", "lat_b", "lng_b", "name_a", "name_b")),
p.join(INNER,
p.values(p.symbol("lat_a"), p.symbol("lng_a"), p.symbol("name_a")),
p.values(p.symbol("lat_b"), p.symbol("lng_b"), p.symbol("name_b")))))
@ -230,7 +261,8 @@ public class TestExtractSpatialInnerJoin
{
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression(filter),
p.filter(
sqlToRowExpression(filter, buildBigIntTypeProviderMap("lat_a", "lng_a", "lat_b", "lng_b", "name_a", "name_b")),
p.join(INNER,
p.values(p.symbol("lat_a"), p.symbol("lng_a"), p.symbol("name_a")),
p.values(p.symbol("lat_b"), p.symbol("lng_b"), p.symbol("name_b")))))
@ -249,7 +281,10 @@ public class TestExtractSpatialInnerJoin
// symbols
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(a, b)"),
p.filter(
sqlToRowExpression(
"ST_Contains(a, b)",
ImmutableMap.of("a", GEOMETRY, "b", GEOMETRY)),
p.join(INNER,
p.values(p.symbol("a")),
p.values(p.symbol("b")))))
@ -261,7 +296,10 @@ public class TestExtractSpatialInnerJoin
// AND
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("name_1 != name_2 AND ST_Contains(a, b)"),
p.filter(
sqlToRowExpression(
"name_1 != name_2 AND ST_Contains(a, b)",
ImmutableMap.of("a", GEOMETRY, "b", GEOMETRY, "name_1", BIGINT, "name_2", BIGINT)),
p.join(INNER,
p.values(p.symbol("a"), p.symbol("name_1")),
p.values(p.symbol("b"), p.symbol("name_2")))))
@ -273,7 +311,10 @@ public class TestExtractSpatialInnerJoin
// AND
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(a1, b1) AND ST_Contains(a2, b2)"),
p.filter(
sqlToRowExpression(
"ST_Contains(a1, b1) AND ST_Contains(a2, b2)",
ImmutableMap.of("a1", GEOMETRY, "a2", GEOMETRY, "b1", GEOMETRY, "b2", GEOMETRY)),
p.join(INNER,
p.values(p.symbol("a1"), p.symbol("a2")),
p.values(p.symbol("b1"), p.symbol("b2")))))
@ -288,7 +329,9 @@ public class TestExtractSpatialInnerJoin
{
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(ST_GeometryFromText(wkt), point)"),
p.filter(sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt), point)",
ImmutableMap.of("wkt", VARCHAR, "point", GEOMETRY)),
p.join(INNER,
p.values(p.symbol("wkt", VARCHAR)),
p.values(p.symbol("point", GEOMETRY)))))
@ -299,7 +342,10 @@ public class TestExtractSpatialInnerJoin
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(ST_GeometryFromText(wkt), ST_Point(0, 0))"),
p.filter(
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt), ST_Point(0, 0))",
ImmutableMap.of("wkt", VARCHAR)),
p.join(INNER,
p.values(p.symbol("wkt", VARCHAR)),
p.values())))
@ -311,7 +357,10 @@ public class TestExtractSpatialInnerJoin
{
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(polygon, ST_Point(lng, lat))"),
p.filter(
sqlToRowExpression(
"ST_Contains(polygon, ST_Point(lng, lat))",
ImmutableMap.of("polygon", GEOMETRY, "lat", BIGINT, "lng", BIGINT)),
p.join(INNER,
p.values(p.symbol("polygon", GEOMETRY)),
p.values(p.symbol("lat"), p.symbol("lng")))))
@ -322,7 +371,10 @@ public class TestExtractSpatialInnerJoin
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(ST_GeometryFromText('POLYGON ...'), ST_Point(lng, lat))"),
p.filter(
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText('POLYGON ((0 0, 0 0, 0 0, 0 0))'), ST_Point(lng, lat))",
ImmutableMap.of("lat", BIGINT, "lng", BIGINT)),
p.join(INNER,
p.values(),
p.values(p.symbol("lat"), p.symbol("lng")))))
@ -334,7 +386,10 @@ public class TestExtractSpatialInnerJoin
{
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))"),
p.filter(
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))",
ImmutableMap.of("wkt", VARCHAR, "lat", BIGINT, "lng", BIGINT)),
p.join(INNER,
p.values(p.symbol("wkt", VARCHAR)),
p.values(p.symbol("lat"), p.symbol("lng")))))
@ -349,7 +404,7 @@ public class TestExtractSpatialInnerJoin
{
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))"),
p.filter(sqlToRowExpression("ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))", ImmutableMap.of("wkt", VARCHAR, "lat", BIGINT, "lng", BIGINT)),
p.join(INNER,
p.values(p.symbol("lat"), p.symbol("lng")),
p.values(p.symbol("wkt", VARCHAR)))))
@ -364,7 +419,10 @@ public class TestExtractSpatialInnerJoin
{
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("name_1 != name_2 AND ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))"),
p.filter(
sqlToRowExpression(
"name_1 != name_2 AND ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))",
ImmutableMap.of("wkt", VARCHAR, "lat", BIGINT, "lng", BIGINT, "name_1", BIGINT, "name_2", BIGINT)),
p.join(INNER,
p.values(p.symbol("wkt", VARCHAR), p.symbol("name_1")),
p.values(p.symbol("lat"), p.symbol("lng"), p.symbol("name_2")))))
@ -376,7 +434,10 @@ public class TestExtractSpatialInnerJoin
// Multiple spatial functions - only the first one is being processed
assertRuleApplication()
.on(p ->
p.filter(PlanBuilder.expression("ST_Contains(ST_GeometryFromText(wkt1), geometry1) AND ST_Contains(ST_GeometryFromText(wkt2), geometry2)"),
p.filter(
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt1), geometry1) AND ST_Contains(ST_GeometryFromText(wkt2), geometry2)",
ImmutableMap.of("wkt1", VARCHAR, "wkt2", VARCHAR, "geometry1", GEOMETRY, "geometry2", GEOMETRY)),
p.join(INNER,
p.values(p.symbol("wkt1", VARCHAR), p.symbol("wkt2", VARCHAR)),
p.values(p.symbol("geometry1"), p.symbol("geometry2")))))
@ -391,4 +452,17 @@ public class TestExtractSpatialInnerJoin
RuleTester tester = tester();
return tester.assertThat(new ExtractSpatialInnerJoin(tester.getMetadata(), tester.getSplitManager(), tester.getPageSourceManager(), tester.getTypeAnalyzer()));
}
private RowExpression sqlToRowExpression(String sql, Map<String, Type> typeMap)
{
Map<Symbol, Type> types = typeMap.entrySet().stream().collect(Collectors.toMap(e -> new Symbol(e.getKey()), e -> e.getValue()));
return sqlToRowExpressionTranslator.translateAndOptimize(PlanBuilder.expression(sql), TypeProvider.copyOf(types));
}
private static Map<String, Type> buildBigIntTypeProviderMap(String... variables)
{
ImmutableMap.Builder<String, Type> builder = ImmutableMap.builder();
Arrays.stream(variables).forEach(variable -> builder.put(variable, BIGINT));
return builder.build();
}
}

View File

@ -14,30 +14,48 @@
package io.prestosql.plugin.geospatial;
import com.google.common.collect.ImmutableMap;
import io.prestosql.spi.plan.Symbol;
import io.prestosql.spi.relation.RowExpression;
import io.prestosql.spi.type.Type;
import io.prestosql.sql.TestingRowExpressionTranslator;
import io.prestosql.sql.planner.TypeProvider;
import io.prestosql.sql.planner.assertions.PlanMatchPattern;
import io.prestosql.sql.planner.iterative.rule.ExtractSpatialJoins.ExtractSpatialLeftJoin;
import io.prestosql.sql.planner.iterative.rule.test.BaseRuleTest;
import io.prestosql.sql.planner.iterative.rule.test.PlanBuilder;
import io.prestosql.sql.planner.iterative.rule.test.RuleAssert;
import io.prestosql.sql.planner.iterative.rule.test.RuleTester;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.util.Map;
import java.util.stream.Collectors;
import static io.prestosql.plugin.geospatial.GeometryType.GEOMETRY;
import static io.prestosql.plugin.geospatial.SphericalGeographyType.SPHERICAL_GEOGRAPHY;
import static io.prestosql.spi.plan.JoinNode.Type.LEFT;
import static io.prestosql.spi.type.BigintType.BIGINT;
import static io.prestosql.spi.type.VarcharType.VARCHAR;
import static io.prestosql.sql.planner.assertions.PlanMatchPattern.project;
import static io.prestosql.sql.planner.assertions.PlanMatchPattern.spatialLeftJoin;
import static io.prestosql.sql.planner.assertions.PlanMatchPattern.values;
import static io.prestosql.sql.planner.iterative.rule.test.PlanBuilder.expression;
import static io.prestosql.sql.planner.plan.JoinNode.Type.LEFT;
public class TestExtractSpatialLeftJoin
extends BaseRuleTest
{
private TestingRowExpressionTranslator sqlToRowExpressionTranslator;
public TestExtractSpatialLeftJoin()
{
super(new GeoPlugin());
}
@BeforeClass
public void setupTranslator()
{
this.sqlToRowExpressionTranslator = new TestingRowExpressionTranslator(tester().getMetadata());
}
@Test
public void testDoesNotFire()
{
@ -46,8 +64,10 @@ public class TestExtractSpatialLeftJoin
.on(p ->
p.join(LEFT,
p.values(),
p.values(p.symbol("b")),
expression("ST_Contains(ST_GeometryFromText('POLYGON ...'), b)")))
p.values(p.symbol("b", GEOMETRY)),
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText('POLYGON ((0 0, 0 0, 0 0, 0 0))'), b)",
ImmutableMap.of("b", GEOMETRY))))
.doesNotFire();
// OR operand
@ -56,7 +76,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("wkt", VARCHAR), p.symbol("name_1")),
p.values(p.symbol("point", GEOMETRY), p.symbol("name_2")),
expression("ST_Contains(ST_GeometryFromText(wkt), point) OR name_1 != name_2")))
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt), point) OR name_1 != name_2",
ImmutableMap.of("wkt", VARCHAR, "point", GEOMETRY, "name_1", BIGINT, "name_2", BIGINT))))
.doesNotFire();
// NOT operator
@ -65,7 +87,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("wkt", VARCHAR), p.symbol("name_1")),
p.values(p.symbol("point", GEOMETRY), p.symbol("name_2")),
expression("NOT ST_Contains(ST_GeometryFromText(wkt), point)")))
sqlToRowExpression(
"NOT ST_Contains(ST_GeometryFromText(wkt), point)",
ImmutableMap.of("wkt", VARCHAR, "point", GEOMETRY, "name_1", BIGINT, "name_2", BIGINT))))
.doesNotFire();
// ST_Distance(...) > r
@ -74,7 +98,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("a", GEOMETRY)),
p.values(p.symbol("b", GEOMETRY)),
expression("ST_Distance(a, b) > 5")))
sqlToRowExpression(
"ST_Distance(a, b) > 5",
ImmutableMap.of("a", GEOMETRY, "b", GEOMETRY))))
.doesNotFire();
// SphericalGeography operand
@ -83,24 +109,34 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("a", SPHERICAL_GEOGRAPHY)),
p.values(p.symbol("b", SPHERICAL_GEOGRAPHY)),
expression("ST_Distance(a, b) < 5")))
sqlToRowExpression(
"ST_Distance(a, b) < 5",
ImmutableMap.of("a", SPHERICAL_GEOGRAPHY, "b", SPHERICAL_GEOGRAPHY))))
.doesNotFire();
assertRuleApplication()
.on(p ->
p.join(LEFT,
p.values(p.symbol("wkt", VARCHAR)),
p.values(p.symbol("point", SPHERICAL_GEOGRAPHY)),
sqlToRowExpression(
"ST_Distance(to_spherical_geography(ST_GeometryFromText(wkt)), point) < 5",
ImmutableMap.of("wkt", VARCHAR, "point", SPHERICAL_GEOGRAPHY))))
.doesNotFire();
}
@Test(enabled = false)
public void testSphericalGeographiesDoesNotFire()
{
// TODO enable once #13133 is merged
assertRuleApplication()
.on(p ->
p.join(LEFT,
p.values(p.symbol("polygon", SPHERICAL_GEOGRAPHY)),
p.values(p.symbol("point", SPHERICAL_GEOGRAPHY)),
expression("ST_Contains(polygon, point)")))
.doesNotFire();
// to_spherical_geography() operand
assertRuleApplication()
.on(p ->
p.join(LEFT,
p.values(p.symbol("wkt", VARCHAR)),
p.values(p.symbol("point", SPHERICAL_GEOGRAPHY)),
expression("ST_Distance(to_spherical_geography(ST_GeometryFromText(wkt)), point) < 5")))
sqlToRowExpression(
"ST_Contains(polygon, point)",
ImmutableMap.of("polygon", SPHERICAL_GEOGRAPHY, "point", SPHERICAL_GEOGRAPHY))))
.doesNotFire();
assertRuleApplication()
@ -108,7 +144,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("wkt", VARCHAR)),
p.values(p.symbol("point", SPHERICAL_GEOGRAPHY)),
expression("ST_Contains(to_spherical_geography(ST_GeometryFromText(wkt)), point)")))
sqlToRowExpression(
"ST_Contains(to_spherical_geography(ST_GeometryFromText(wkt)), point)",
ImmutableMap.of("wkt", VARCHAR, "point", SPHERICAL_GEOGRAPHY))))
.doesNotFire();
}
@ -119,9 +157,9 @@ public class TestExtractSpatialLeftJoin
assertRuleApplication()
.on(p ->
p.join(LEFT,
p.values(p.symbol("a")),
p.values(p.symbol("b")),
p.expression("ST_Contains(a, b)")))
p.values(p.symbol("a", GEOMETRY)),
p.values(p.symbol("b", GEOMETRY)),
sqlToRowExpression("ST_Contains(a, b)", ImmutableMap.of("a", GEOMETRY, "b", GEOMETRY))))
.matches(
spatialLeftJoin("ST_Contains(a, b)",
values(ImmutableMap.of("a", 0)),
@ -131,9 +169,9 @@ public class TestExtractSpatialLeftJoin
assertRuleApplication()
.on(p ->
p.join(LEFT,
p.values(p.symbol("a"), p.symbol("name_1")),
p.values(p.symbol("b"), p.symbol("name_2")),
p.expression("name_1 != name_2 AND ST_Contains(a, b)")))
p.values(p.symbol("a", GEOMETRY), p.symbol("name_1")),
p.values(p.symbol("b", GEOMETRY), p.symbol("name_2")),
sqlToRowExpression("name_1 != name_2 AND ST_Contains(a, b)", ImmutableMap.of("a", GEOMETRY, "b", GEOMETRY, "name_1", BIGINT, "name_2", BIGINT))))
.matches(
spatialLeftJoin("name_1 != name_2 AND ST_Contains(a, b)",
values(ImmutableMap.of("a", 0, "name_1", 1)),
@ -143,9 +181,9 @@ public class TestExtractSpatialLeftJoin
assertRuleApplication()
.on(p ->
p.join(LEFT,
p.values(p.symbol("a1"), p.symbol("a2")),
p.values(p.symbol("b1"), p.symbol("b2")),
p.expression("ST_Contains(a1, b1) AND ST_Contains(a2, b2)")))
p.values(p.symbol("a1", GEOMETRY), p.symbol("a2", GEOMETRY)),
p.values(p.symbol("b1", GEOMETRY), p.symbol("b2", GEOMETRY)),
sqlToRowExpression("ST_Contains(a1, b1) AND ST_Contains(a2, b2)", ImmutableMap.of("a1", GEOMETRY, "b1", GEOMETRY, "a2", GEOMETRY, "b2", GEOMETRY))))
.matches(
spatialLeftJoin("ST_Contains(a1, b1) AND ST_Contains(a2, b2)",
values(ImmutableMap.of("a1", 0, "a2", 1)),
@ -160,7 +198,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("wkt", VARCHAR)),
p.values(p.symbol("point", GEOMETRY)),
expression("ST_Contains(ST_GeometryFromText(wkt), point)")))
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt), point)",
ImmutableMap.of("wkt", VARCHAR, "point", GEOMETRY))))
.matches(
spatialLeftJoin("ST_Contains(st_geometryfromtext, point)",
project(ImmutableMap.of("st_geometryfromtext", PlanMatchPattern.expression("ST_GeometryFromText(wkt)")), values(ImmutableMap.of("wkt", 0))),
@ -171,7 +211,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("wkt", VARCHAR)),
p.values(),
expression("ST_Contains(ST_GeometryFromText(wkt), ST_Point(0, 0))")))
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt), ST_Point(0, 0))",
ImmutableMap.of("wkt", VARCHAR))))
.doesNotFire();
}
@ -183,7 +225,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("polygon", GEOMETRY)),
p.values(p.symbol("lat"), p.symbol("lng")),
expression("ST_Contains(polygon, ST_Point(lng, lat))")))
sqlToRowExpression(
"ST_Contains(polygon, ST_Point(lng, lat))",
ImmutableMap.of("polygon", GEOMETRY, "lat", BIGINT, "lng", BIGINT))))
.matches(
spatialLeftJoin("ST_Contains(polygon, st_point)",
values(ImmutableMap.of("polygon", 0)),
@ -194,7 +238,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(),
p.values(p.symbol("lat"), p.symbol("lng")),
expression("ST_Contains(ST_GeometryFromText('POLYGON ...'), ST_Point(lng, lat))")))
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText('POLYGON ((0 0, 0 0, 0 0, 0 0))'), ST_Point(lng, lat))",
ImmutableMap.of("polygon", GEOMETRY, "lat", BIGINT, "lng", BIGINT))))
.doesNotFire();
}
@ -206,7 +252,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("wkt", VARCHAR)),
p.values(p.symbol("lat"), p.symbol("lng")),
expression("ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))")))
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))",
ImmutableMap.of("wkt", VARCHAR, "lat", BIGINT, "lng", BIGINT))))
.matches(
spatialLeftJoin("ST_Contains(st_geometryfromtext, st_point)",
project(ImmutableMap.of("st_geometryfromtext", PlanMatchPattern.expression("ST_GeometryFromText(wkt)")), values(ImmutableMap.of("wkt", 0))),
@ -221,7 +269,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("lat"), p.symbol("lng")),
p.values(p.symbol("wkt", VARCHAR)),
expression("ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))")))
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))",
ImmutableMap.of("wkt", VARCHAR, "lat", BIGINT, "lng", BIGINT))))
.matches(
spatialLeftJoin("ST_Contains(st_geometryfromtext, st_point)",
project(ImmutableMap.of("st_point", PlanMatchPattern.expression("ST_Point(lng, lat)")), values(ImmutableMap.of("lat", 0, "lng", 1))),
@ -236,7 +286,9 @@ public class TestExtractSpatialLeftJoin
p.join(LEFT,
p.values(p.symbol("wkt", VARCHAR), p.symbol("name_1")),
p.values(p.symbol("lat"), p.symbol("lng"), p.symbol("name_2")),
expression("name_1 != name_2 AND ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))")))
sqlToRowExpression(
"name_1 != name_2 AND ST_Contains(ST_GeometryFromText(wkt), ST_Point(lng, lat))",
ImmutableMap.of("wkt", VARCHAR, "name_1", BIGINT, "name_2", BIGINT, "lat", BIGINT, "lng", BIGINT))))
.matches(
spatialLeftJoin("name_1 != name_2 AND ST_Contains(st_geometryfromtext, st_point)",
project(ImmutableMap.of("st_geometryfromtext", PlanMatchPattern.expression("ST_GeometryFromText(wkt)")), values(ImmutableMap.of("wkt", 0, "name_1", 1))),
@ -247,8 +299,10 @@ public class TestExtractSpatialLeftJoin
.on(p ->
p.join(LEFT,
p.values(p.symbol("wkt1", VARCHAR), p.symbol("wkt2", VARCHAR)),
p.values(p.symbol("geometry1"), p.symbol("geometry2")),
expression("ST_Contains(ST_GeometryFromText(wkt1), geometry1) AND ST_Contains(ST_GeometryFromText(wkt2), geometry2)")))
p.values(p.symbol("geometry1", GEOMETRY), p.symbol("geometry2", GEOMETRY)),
sqlToRowExpression(
"ST_Contains(ST_GeometryFromText(wkt1), geometry1) AND ST_Contains(ST_GeometryFromText(wkt2), geometry2)",
ImmutableMap.of("wkt1", VARCHAR, "wkt2", VARCHAR, "geometry1", GEOMETRY, "geometry2", GEOMETRY))))
.matches(
spatialLeftJoin("ST_Contains(st_geometryfromtext, geometry1) AND ST_Contains(ST_GeometryFromText(wkt2), geometry2)",
project(ImmutableMap.of("st_geometryfromtext", PlanMatchPattern.expression("ST_GeometryFromText(wkt1)")), values(ImmutableMap.of("wkt1", 0, "wkt2", 1))),
@ -260,4 +314,10 @@ public class TestExtractSpatialLeftJoin
RuleTester tester = tester();
return tester().assertThat(new ExtractSpatialLeftJoin(tester.getMetadata(), tester.getSplitManager(), tester.getPageSourceManager(), tester.getTypeAnalyzer()));
}
private RowExpression sqlToRowExpression(String sql, Map<String, Type> typeMap)
{
Map<Symbol, Type> types = typeMap.entrySet().stream().collect(Collectors.toMap(e -> new Symbol(e.getKey()), e -> e.getValue()));
return sqlToRowExpressionTranslator.translateAndOptimize(PlanBuilder.expression(sql), TypeProvider.copyOf(types));
}
}

View File

@ -15,11 +15,11 @@ package io.prestosql.plugin.geospatial;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.prestosql.spi.plan.AggregationNode;
import io.prestosql.sql.planner.iterative.rule.RewriteSpatialPartitioningAggregation;
import io.prestosql.sql.planner.iterative.rule.test.BaseRuleTest;
import io.prestosql.sql.planner.iterative.rule.test.PlanBuilder;
import io.prestosql.sql.planner.iterative.rule.test.RuleAssert;
import io.prestosql.sql.planner.plan.AggregationNode;
import org.testng.annotations.Test;
import static io.prestosql.plugin.geospatial.GeometryType.GEOMETRY;

View File

@ -39,8 +39,8 @@ import io.prestosql.operator.TaskContext;
import io.prestosql.operator.ValuesOperator;
import io.prestosql.spi.Page;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.sql.gen.JoinFilterFunctionCompiler;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.sql.planner.plan.SpatialJoinNode.Type;
import io.prestosql.testing.MaterializedResult;
import io.prestosql.testing.TestingTaskContext;

View File

@ -23,10 +23,10 @@ import io.prestosql.geospatial.Rectangle;
import io.prestosql.plugin.memory.MemoryConnectorFactory;
import io.prestosql.plugin.tpch.TpchConnectorFactory;
import io.prestosql.spi.PrestoException;
import io.prestosql.spi.plan.JoinNode;
import io.prestosql.sql.planner.LogicalPlanner;
import io.prestosql.sql.planner.assertions.BasePlanTest;
import io.prestosql.sql.planner.plan.ExchangeNode;
import io.prestosql.sql.planner.plan.JoinNode;
import io.prestosql.testing.LocalQueryRunner;
import org.testng.annotations.Test;

View File

@ -27,15 +27,15 @@ import io.prestosql.spi.connector.FixedPageSource;
import io.prestosql.spi.dynamicfilter.DynamicFilter;
import io.prestosql.spi.dynamicfilter.DynamicFilterFactory;
import io.prestosql.spi.dynamicfilter.DynamicFilterSupplier;
import io.prestosql.spi.plan.FilterNode;
import io.prestosql.spi.plan.PlanNodeId;
import io.prestosql.spi.plan.ProjectNode;
import io.prestosql.spi.plan.TableScanNode;
import io.prestosql.spi.type.StandardTypes;
import io.prestosql.spi.util.BloomFilter;
import io.prestosql.sql.analyzer.FeaturesConfig;
import io.prestosql.sql.planner.Plan;
import io.prestosql.sql.planner.optimizations.PlanNodeSearcher;
import io.prestosql.sql.planner.plan.FilterNode;
import io.prestosql.sql.planner.plan.PlanNodeId;
import io.prestosql.sql.planner.plan.ProjectNode;
import io.prestosql.sql.planner.plan.TableScanNode;
import io.prestosql.testing.MaterializedResult;
import io.prestosql.testing.TestingConnectorSession;
import io.prestosql.tests.AbstractTestQueryFramework;

View File

@ -17,17 +17,17 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.prestosql.Session;
import io.prestosql.connector.CatalogName;
import io.prestosql.cost.StatsAndCosts;
import io.prestosql.metadata.InsertTableHandle;
import io.prestosql.metadata.Metadata;
import io.prestosql.metadata.QualifiedObjectName;
import io.prestosql.metadata.TableHandle;
import io.prestosql.metadata.TableMetadata;
import io.prestosql.spi.connector.CatalogName;
import io.prestosql.spi.connector.CatalogSchemaTableName;
import io.prestosql.spi.connector.ColumnMetadata;
import io.prestosql.spi.connector.ConnectorSession;
import io.prestosql.spi.connector.Constraint;
import io.prestosql.spi.metadata.TableHandle;
import io.prestosql.spi.security.Identity;
import io.prestosql.spi.security.SelectedRole;
import io.prestosql.spi.type.BigintType;

View File

@ -23,7 +23,6 @@ import io.prestosql.spi.type.DecimalType;
import io.prestosql.spi.type.StandardTypes;
import io.prestosql.spi.type.TypeManager;
import io.prestosql.type.InternalTypeManager;
import io.prestosql.util.DateTimeUtils;
import org.testng.annotations.Test;
import java.util.List;
@ -46,6 +45,7 @@ import static io.prestosql.spi.type.StandardTypes.INTEGER;
import static io.prestosql.spi.type.StandardTypes.TIMESTAMP;
import static io.prestosql.spi.type.StandardTypes.VARCHAR;
import static io.prestosql.spi.type.TypeSignature.parseTypeSignature;
import static io.prestosql.spi.util.DateTimeUtils.parseDate;
import static org.testng.Assert.assertEquals;
public class TestIonSqlQueryBuilder
@ -105,7 +105,7 @@ public class TestIonSqlQueryBuilder
new HiveColumnHandle("t1", HIVE_TIMESTAMP, parseTypeSignature(TIMESTAMP), 0, REGULAR, Optional.empty()),
new HiveColumnHandle("t2", HIVE_DATE, parseTypeSignature(StandardTypes.DATE), 1, REGULAR, Optional.empty()));
TupleDomain<HiveColumnHandle> tupleDomain = withColumnDomains(ImmutableMap.of(
columns.get(1), Domain.create(SortedRangeSet.copyOf(DATE, ImmutableList.of(Range.equal(DATE, (long) DateTimeUtils.parseDate("2001-08-22")))), false)));
columns.get(1), Domain.create(SortedRangeSet.copyOf(DATE, ImmutableList.of(Range.equal(DATE, (long) parseDate("2001-08-22")))), false)));
assertEquals("SELECT s._1, s._2 FROM S3Object s WHERE (case s._2 when '' then null else CAST(s._2 AS TIMESTAMP) end = `2001-08-22`)", queryBuilder.buildSql(columns, tupleDomain));
}

Some files were not shown because too many files have changed in this diff Show More