Посетитель анализатора SQL Метабаза Presto

#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;
  }
}