Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature](sql-dialect)support convert hive view and presto view use sql convertor service #46308

Merged
merged 11 commits into from
Jan 24, 2025
Prev Previous commit
Next Next commit
fix test case
  • Loading branch information
liujiwen-up committed Jan 7, 2025
commit 6f00487c79adfc77762dc44327d48985b3abf20d
Original file line number Diff line number Diff line change
Expand Up @@ -456,37 +456,46 @@ public String getViewText() {
}

String originalText = getViewOriginalText();
/**
* Get the SQL text definition of the view.
* For Presto views, the definition is stored in the format:
* View Original Text: /* Presto View: <base64-encoded-json> * /
*
* The base64 encoded JSON contains the following fields:
* {
* "originalSql": "SELECT * FROM employees", // The original SQL statement
* "catalog": "hive", // The data catalog name
* "schema": "mmc_hive", // The schema name
* .....
* }
*/
if (originalText != null && originalText.contains("/* Presto View: ")) {
try {
String base64String = originalText.substring(
originalText.indexOf("/* Presto View: ") + "/* Presto View: ".length(),
originalText.lastIndexOf(" */")
).trim();
byte[] decodedBytes = Base64.getDecoder().decode(base64String);
String decodedString = new String(decodedBytes, StandardCharsets.UTF_8);
JsonObject jsonObject = new Gson().fromJson(decodedString, JsonObject.class);
if (jsonObject.has("originalSql")) {
return jsonObject.get("originalSql").getAsString();
}
} catch (Exception e) {
LOG.warn("Decoding Presto view definition failed", e);
}
String trinoViewSql = parseTrinoViewDefinition(originalText);
return trinoViewSql != null ? trinoViewSql : originalText;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not return originalText instead of return null when parse failed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. you can return originalText directly in parseTrinoViewDefinition's return value

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and better add a UT for this method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

/**
* Parse Trino/Presto view definition from the original text.
* The definition is stored in the format: /* Presto View: <base64-encoded-json> * /
*
* The base64 encoded JSON contains the following fields:
* {
* "originalSql": "SELECT * FROM employees", // The original SQL statement
* "catalog": "hive", // The data catalog name
* "schema": "mmc_hive", // The schema name
* ...
* }
*
* @param originalText The original view definition text
* @return The parsed SQL statement, or null if parsing fails
*/
private String parseTrinoViewDefinition(String originalText) {
if (originalText == null || !originalText.contains("/* Presto View: ")) {
return null;
}

return originalText;
try {
String base64String = originalText.substring(
originalText.indexOf("/* Presto View: ") + "/* Presto View: ".length(),
originalText.lastIndexOf(" */")
).trim();
byte[] decodedBytes = Base64.getDecoder().decode(base64String);
String decodedString = new String(decodedBytes, StandardCharsets.UTF_8);
JsonObject jsonObject = new Gson().fromJson(decodedString, JsonObject.class);

if (jsonObject.has("originalSql")) {
return jsonObject.get("originalSql").getAsString();
}
} catch (Exception e) {
LOG.warn("Decoding Presto view definition failed", e);
}
return null;
}

public String getViewExpandedText() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.nereids.parser;

import org.apache.doris.catalog.Env;
import org.apache.doris.plugin.DialectConverterPlugin;
import org.apache.doris.plugin.PluginMgr;
import org.apache.doris.qe.SessionVariable;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.List;
import javax.annotation.Nullable;

/**
* Helper class for SQL dialect conversion.
*/
public class SqlDialectHelper {
private static final Logger LOG = LogManager.getLogger(SqlDialectHelper.class);

/**
* Convert SQL statement based on current SQL dialect
*
* @param originStmt original SQL statement
* @param sessionVariable session variable containing dialect settings
* @return converted SQL statement, or original statement if conversion fails
*/
public static String convertSqlByDialect(String originStmt, SessionVariable sessionVariable) {
String convertedStmt = originStmt;
@Nullable Dialect sqlDialect = Dialect.getByName(sessionVariable.getSqlDialect());
if (sqlDialect != null && sqlDialect != Dialect.DORIS) {
PluginMgr pluginMgr = Env.getCurrentEnv().getPluginMgr();
List<DialectConverterPlugin> plugins = pluginMgr.getActiveDialectPluginList(sqlDialect);
for (DialectConverterPlugin plugin : plugins) {
try {
String convertedSql = plugin.convertSql(originStmt, sessionVariable);
if (StringUtils.isNotEmpty(convertedSql)) {
convertedStmt = convertedSql;
break;
}
} catch (Throwable throwable) {
LOG.warn("Convert sql with dialect {} failed, plugin: {}, sql: {}, use origin sql.",
sqlDialect, plugin.getClass().getSimpleName(), originStmt, throwable);
}
}
}
return convertedStmt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
import org.apache.doris.nereids.analyzer.UnboundResultSink;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.hint.LeadingHint;
import org.apache.doris.nereids.parser.Dialect;
import org.apache.doris.nereids.parser.NereidsParser;
import org.apache.doris.nereids.parser.SqlDialectHelper;
import org.apache.doris.nereids.pattern.MatchingContext;
import org.apache.doris.nereids.properties.LogicalProperties;
import org.apache.doris.nereids.properties.PhysicalProperties;
Expand Down Expand Up @@ -89,32 +89,23 @@
import org.apache.doris.nereids.trees.plans.logical.LogicalView;
import org.apache.doris.nereids.util.RelationUtil;
import org.apache.doris.nereids.util.Utils;
import org.apache.doris.plugin.DialectConverterPlugin;
import org.apache.doris.plugin.PluginMgr;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.ConnectProcessor;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;

/**
* Rule to bind relations in query plan.
*/
public class BindRelation extends OneAnalysisRuleFactory {

private static final Logger LOG = LogManager.getLogger(ConnectProcessor.class);

public BindRelation() {}

@Override
Expand Down Expand Up @@ -460,7 +451,7 @@ private Plan parseAndAnalyzeHiveView(
ConnectContext ctx = cascadesContext.getConnectContext();
String previousCatalog = ctx.getCurrentCatalog().getName();
String previousDb = ctx.getDatabase();
String convertedSql = convertViewDefinition(ddlSql, cascadesContext.getConnectContext());
String convertedSql = SqlDialectHelper.convertSqlByDialect(ddlSql, ctx.getSessionVariable());
// change catalog and db to hive catalog and db, so that we can parse and analyze the view sql in hive context.
ctx.changeDefaultCatalog(hiveCatalog);
ctx.setDatabase(hiveDb);
Expand Down Expand Up @@ -532,28 +523,4 @@ private List<Long> getPartitionIds(TableIf t, UnboundRelation unboundRelation, L
}).collect(ImmutableList.toImmutableList());
}

/**
* Convert view definition SQL based on current SQL dialect
*/
private String convertViewDefinition(String originSql, ConnectContext ctx) {
String convertedSql = originSql;
@Nullable Dialect sqlDialect = Dialect.getByName(ctx.getSessionVariable().getSqlDialect());
if (sqlDialect != null && sqlDialect != Dialect.DORIS) {
PluginMgr pluginMgr = Env.getCurrentEnv().getPluginMgr();
List<DialectConverterPlugin> plugins = pluginMgr.getActiveDialectPluginList(sqlDialect);
for (DialectConverterPlugin plugin : plugins) {
try {
String converted = plugin.convertSql(originSql, ctx.getSessionVariable());
if (StringUtils.isNotEmpty(converted)) {
convertedSql = converted;
break;
}
} catch (Throwable throwable) {
LOG.warn("Convert view definition with dialect {} failed, plugin: {}, sql: {}, use origin sql.",
sqlDialect, plugin.getClass().getSimpleName(), originSql, throwable);
}
}
}
return convertedSql;
}
}
29 changes: 2 additions & 27 deletions fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,12 @@
import org.apache.doris.nereids.exceptions.NotSupportedException;
import org.apache.doris.nereids.glue.LogicalPlanAdapter;
import org.apache.doris.nereids.minidump.MinidumpUtils;
import org.apache.doris.nereids.parser.Dialect;
import org.apache.doris.nereids.parser.NereidsParser;
import org.apache.doris.nereids.parser.SqlDialectHelper;
import org.apache.doris.nereids.stats.StatsErrorEstimator;
import org.apache.doris.nereids.trees.plans.commands.ExplainCommand;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.nereids.trees.plans.logical.LogicalSqlCache;
import org.apache.doris.plugin.DialectConverterPlugin;
import org.apache.doris.plugin.PluginMgr;
import org.apache.doris.proto.Data;
import org.apache.doris.qe.QueryState.MysqlStateType;
import org.apache.doris.qe.cache.CacheAnalyzer;
Expand All @@ -79,7 +77,6 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.thrift.TException;
Expand All @@ -91,7 +88,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.annotation.Nullable;

/**
* Process one connection, the life cycle is the same as connection
Expand Down Expand Up @@ -269,7 +265,7 @@ public void executeQuery(String originStmt) throws Exception {
}
}

String convertedStmt = convertOriginStmt(originStmt);
String convertedStmt = SqlDialectHelper.convertSqlByDialect(originStmt, ctx.getSessionVariable());
String sqlHash = DigestUtils.md5Hex(convertedStmt);
ctx.setSqlHash(sqlHash);

Expand Down Expand Up @@ -435,27 +431,6 @@ private List<StatementBase> parseFromSqlCache(String originStmt) {
return null;
}

private String convertOriginStmt(String originStmt) {
String convertedStmt = originStmt;
@Nullable Dialect sqlDialect = Dialect.getByName(ctx.getSessionVariable().getSqlDialect());
if (sqlDialect != null && sqlDialect != Dialect.DORIS) {
PluginMgr pluginMgr = Env.getCurrentEnv().getPluginMgr();
List<DialectConverterPlugin> plugins = pluginMgr.getActiveDialectPluginList(sqlDialect);
for (DialectConverterPlugin plugin : plugins) {
try {
String convertedSql = plugin.convertSql(originStmt, ctx.getSessionVariable());
if (StringUtils.isNotEmpty(convertedSql)) {
convertedStmt = convertedSql;
break;
}
} catch (Throwable throwable) {
LOG.warn("Convert sql with dialect {} failed, plugin: {}, sql: {}, use origin sql.",
sqlDialect, plugin.getClass().getSimpleName(), originStmt, throwable);
}
}
}
return convertedStmt;
}

// Use a handler for exception to avoid big try catch block which is a little hard to understand
protected void handleQueryException(Throwable throwable, String origStmt,
Expand Down
11 changes: 5 additions & 6 deletions regression-test/suites/dialect_compatible/sql/viewrewrite.sql
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
--create catalog
drop catalog if exists hive_view;
create catalog hive_view properties('type' = 'hms', 'hive.metastore.uris' = 'thrift://192.168.0.1:9083');
--dialect option
set sql_dialect = hive;

--create catalog
drop catalog if exists hive;
create catalog hive properties('type' = 'hms', 'hive.metastore.uris' = 'thrift://192.168.0.1:9083');
--test view rewrite
select * from hive.default.department_view;
select * from hive.default.department_nesting_view;
select * from hive_view.default.department_view;
select * from hive_view.default.department_nesting_view;



Loading