#presto #sql-parser
#presto #sql-парсер
Вопрос:
Я сталкиваюсь с тем, что кажется довольно простой проблемой, но я не могу разобраться в проблеме, чтобы найти подходящее решение.
Проблема:
мне нужно добавить схему в мой оператор SQL «странным» (со схемой в двойных кавычках) способом.
FROM "SCHEMA".tableB tableB
LEFT JOIN "SCHEMA".tableC tableC
Контекст
По сути, мы размещаем и предоставляем инструмент метабазы, который будет подключаться и выполнять запросы к нашей базе данных Hive с использованием Presto SQL.
Метабаза позволяет заказчику писать операторы SQL, а некоторые клиенты просто не вводят схему в операторах. Сегодня мы выдаем и ошибка для этих запросов, но я мог бы легко получить значение схемы из заголовка авторизации, поскольку в нашем многопользовательском продукте схемой является идентификатор клиента, в который зарегистрирован этот пользователь, и с этой информацией в руках я мог бы добавить к инструкции SQL клиента и избежатьошибка.
Представьте, что клиент ввел оператор follow:
SELECT tableA.*
, (tableA.valorfaturado tableA.valorcortado) valorpedido
FROM (SELECT from_unixtime(tableB.datacorte / 1000) datacorte
, COALESCE((tableB.quantidadecortada * tableC.preco), 0) valorcortado
, COALESCE((tableB.quantidade * tableC.preco), 0) valorfaturado
, tableB.quantidadecortada
FROM tableB tableB
LEFT JOIN tableC tableC
ON tableC.numeropedido = tableB.numeropedido
AND tableC.codigoproduto = tableB.codigoproduto
AND tableC.codigofilial = tableB.codigofilial
LEFT JOIN tableD tableD
ON tableD.numero = tableB.numeropedido
WHERE (CASE
WHEN COALESCE(tableB.codigofilial, '') = '' THEN
tableD.codigofilial
ELSE
tableB.codigofilial
END) = '10'
AND from_unixtime(tableB.datacorte / 1000) BETWEEN from_iso8601_timestamp('2020-07-01T03:00:00.000Z') AND from_iso8601_timestamp('2020-08-01T02:59:59.999Z')) tableA
ORDER BY datacorte
Я должен преобразовать это в (добавив «СХЕМУ»):
SELECT tableA.*
, (tableA.valorfaturado tableA.valorcortado) valorpedido
FROM (SELECT from_unixtime(tableB.datacorte / 1000) datacorte
, COALESCE((tableB.quantidadecortada * tableC.preco), 0) valorcortado
, COALESCE((tableB.quantidade * tableC.preco), 0) valorfaturado
, tableB.quantidadecortada
FROM "SCHEMA".tableB tableB
LEFT JOIN "SCHEMA".tableC tableC
ON tableC.numeropedido = tableB.numeropedido
AND tableC.codigoproduto = tableB.codigoproduto
AND tableC.codigofilial = tableB.codigofilial
LEFT JOIN "SCHEMA".tableD tableD
ON tableD.numero = tableB.numeropedido
WHERE (CASE
WHEN COALESCE(tableB.codigofilial, '') = '' THEN
tableD.codigofilial
ELSE
tableB.codigofilial
END) = '10'
AND from_unixtime(tableB.datacorte / 1000) BETWEEN from_iso8601_timestamp('2020-07-01T03:00:00.000Z') AND from_iso8601_timestamp('2020-08-01T02:59:59.999Z')) tableA
ORDER BY datacorte
Все еще пытаюсь найти решение, которое использует только presto-parser
и Посетитель Инструментальное решение.
Кроме того, я знаю о JSqlParser, и я пытался, но я всегда возвращаюсь, чтобы попытаться найти «простое» решение, напуганное тем, что JSqlParser не сможет поддерживать все запросы Presto / Hive, которые немного отличаются от стандартного SQL;
Я создаю небольшой проект на GitHub с тестовым примером для проверки..
https://github.com/genyherrera/prestosqlerror
Но для тех, кто не хочет клонировать репозиторий, вот классы и зависимости:
import java.util.Optional;
import com.facebook.presto.sql.SqlFormatter;
import com.facebook.presto.sql.parser.ParsingOptions;
import com.facebook.presto.sql.parser.SqlParser;
public class SchemaAwareQueryAdapter {
// Inspired from
// https://github.com/prestodb/presto/tree/master/presto-parser/src/test/java/com/facebook/presto/sql/parser
private static final SqlParser SQL_PARSER = new SqlParser();
public String rewriteSql(String sqlStatement, String schemaId) {
com.facebook.presto.sql.tree.Statement statement = SQL_PARSER.createStatement(sqlStatement, ParsingOptions.builder().build());
SchemaAwareQueryVisitor visitor = new SchemaAwareQueryVisitor(schemaId);
statement.accept(visitor, null);
return SqlFormatter.formatSql(statement, Optional.empty());
}
}
public class SchemaAwareQueryVisitor extends DefaultTraversalVisitor<Void, Void> {
private String schemaId;
public SchemaAwareQueryVisitor(String schemaId) {
super();
this.schemaId = schemaId;
}
/**
* The customer can type:
* [table name]
* [schema].[table name]
* [catalog].[schema].[table name]
*/
@Override
protected Void visitTable(Table node, Void context) {
List<String> parts = node.getName().getParts();
// [table name] -> is the only one we need to modify, so let's check by parts.size() ==1
if (parts.size() == 1) {
try {
Field privateStringField = Table.class.getDeclaredField("name");
privateStringField.setAccessible(true);
QualifiedName qualifiedName = QualifiedName.of(""" schemaId """,node.getName().getParts().get(0));
privateStringField.set(node, qualifiedName);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new SecurityException("Unable to execute query");
}
}
return null;
}
}
import static org.testng.Assert.assertEquals;
import org.gherrera.prestosqlparser.SchemaAwareQueryAdapter;
import org.testng.annotations.Test;
public class SchemaAwareTest {
private static final String schemaId = "SCHEMA";
private SchemaAwareQueryAdapter adapter = new SchemaAwareQueryAdapter();
@Test
public void testAppendSchemaA() {
String sql = "select * from tableA";
String bound = adapter.rewriteSql(sql, schemaId);
assertEqualsFormattingStripped(bound,
"select * from "SCHEMA".tableA");
}
private void assertEqualsFormattingStripped(String sql1, String sql2) {
assertEquals(sql1.replace("n", " ").toLowerCase().replace("r", " ").replaceAll(" ", " ").trim(),
sql2.replace("n", " ").toLowerCase().replace("r", " ").replaceAll(" ", " ").trim());
}
}
<dependencies>
<dependency>
<groupId>com.facebook.presto</groupId>
<artifactId>presto-parser</artifactId>
<version>0.229</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.10</version>
<scope>test</scope>
</dependency>
</dependencies>
PS: Я смог добавить схему без двойных кавычек, но они привели к identifiers must not start with a digit; surround the identifier with double quotes
ошибке. В основном эта ошибка возникает из SqlParser$PostProcessor.exitDigitIdentifier(...)
-за метода..
Спасибо
Комментарии:
1. Рассматривали ли вы возможность настройки каталога сеансов и схемы при выполнении запроса Presto? Это приведет к разрешению неквалифицированных имен таблиц в соответствии с этой схемой.
2. Вы также можете обратиться за помощью к Presto Slack: prestosql.io/slack.html
3. Спасибо @DavidPhillips, присоединюсь к их slack…. Что касается вашего первого вопроса, поскольку мы используем один и тот же пользовательский интерфейс метабазы для клиента и администратора, в основном при открытии сеанса мы подключаемся к каталогу по умолчанию «hive», особенно потому, что пользователь может присутствовать в более чем одной схеме (арендаторе), поэтому мы разрешаем ему доступ к метабазе източка зрения по умолчанию… Я обнаружил, что именно этот метод
SqlParser$PostProcessor.exitDigitIdentifier(...)
блокирует добавление схемы без кавычек… Только если я смог вставить схему без двойных кавычек, моя проблема была решена!
Ответ №1:
Я смог найти решение для своего случая, в любом случае поделюсь на Presto Slack своим открытием, чтобы узнать, является ли это ожидаемым поведением.
Итак, если вы хотите добавить к своей схеме двойные кавычки, вам нужно будет создать свой собственный класс Vistor, и вам нужно будет переопределить метод, visitTable
и когда вы укажете имя своей таблицы с помощью schema (вот галочка), передайте схему в ВЕРХНЕМ РЕГИСТРЕ, чтобы она не совпадалашаблон регулярных выражений для класса SqlFormatter
в методе formatName
, и он добавит двойную кавычку..
public class SchemaAwareQueryVisitor extends DefaultTraversalVisitor<Void, Void> {
private String schemaId;
public SchemaAwareQueryVisitor(String schemaId) {
super();
this.schemaId = schemaId;
}
@Override
protected Void visitTable(Table node, Void context) {
try {
Field privateStringField = Table.class.getDeclaredField("name");
privateStringField.setAccessible(true);
QualifiedName qualifiedName = QualifiedName.of(schemaId, node.getName().getParts().get(0));
privateStringField.set(node, qualifiedName);
} catch (NoSuchFieldException
| SecurityException
| IllegalArgumentException
| IllegalAccessException e) {
throw new SecurityException("Unable to execute query");
}
return null;
}
}