| // 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.impala.analysis; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import org.antlr.runtime.ANTLRStringStream; |
| import org.antlr.runtime.Token; |
| import org.apache.commons.collections.CollectionUtils; |
| import org.apache.commons.lang.ObjectUtils; |
| import org.apache.commons.lang.StringEscapeUtils; |
| import org.apache.hadoop.hive.common.StatsSetupConst; |
| import org.apache.hadoop.hive.metastore.TableType; |
| import org.apache.hadoop.hive.ql.parse.HiveLexer; |
| import org.apache.impala.catalog.CatalogException; |
| import org.apache.impala.catalog.Column; |
| import org.apache.impala.catalog.FeFsTable; |
| import org.apache.impala.catalog.FeHBaseTable; |
| import org.apache.impala.catalog.FeKuduTable; |
| import org.apache.impala.catalog.FeTable; |
| import org.apache.impala.catalog.FeView; |
| import org.apache.impala.catalog.Function; |
| import org.apache.impala.catalog.HdfsCompression; |
| import org.apache.impala.catalog.HdfsFileFormat; |
| import org.apache.impala.catalog.HdfsTable; |
| import org.apache.impala.catalog.KuduColumn; |
| import org.apache.impala.catalog.KuduTable; |
| import org.apache.impala.catalog.RowFormat; |
| import org.apache.impala.catalog.Table; |
| import org.apache.impala.common.Pair; |
| import org.apache.impala.thrift.TSortingOrder; |
| import org.apache.impala.util.KuduUtil; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Splitter; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| |
| /** |
| * Contains utility methods for creating SQL strings, for example, |
| * for creating identifier strings that are compatible with Hive or Impala. |
| */ |
| public class ToSqlUtils { |
| // Table properties to hide when generating the toSql() statement |
| // EXTERNAL, SORT BY [order], and comment are hidden because they are part of the |
| // toSql result, e.g., |
| // "CREATE EXTERNAL TABLE <name> ... SORT BY ZORDER (...) ... COMMENT <comment> ..." |
| @VisibleForTesting |
| protected static final ImmutableSet<String> HIDDEN_TABLE_PROPERTIES = ImmutableSet.of( |
| "EXTERNAL", "comment", AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, |
| AlterTableSortByStmt.TBL_PROP_SORT_ORDER); |
| |
| /** |
| * Removes all hidden properties from the given 'tblProperties' map. |
| */ |
| @VisibleForTesting |
| protected static void removeHiddenTableProperties(Map<String, String> tblProperties) { |
| for (String key: HIDDEN_TABLE_PROPERTIES) tblProperties.remove(key); |
| } |
| |
| /** |
| * Removes all hidden Kudu from the given 'tblProperties' map. |
| */ |
| @VisibleForTesting |
| protected static void removeHiddenKuduTableProperties( |
| Map<String, String> tblProperties) { |
| tblProperties.remove(KuduTable.KEY_TABLE_NAME); |
| } |
| |
| /** |
| * Returns the list of sort columns from 'properties' or 'null' if 'properties' doesn't |
| * contain 'sort.columns'. |
| */ |
| @VisibleForTesting |
| protected static List<String> getSortColumns(Map<String, String> properties) { |
| String sortByKey = AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS; |
| if (!properties.containsKey(sortByKey)) return null; |
| return Lists.newArrayList(Splitter.on(",").trimResults().omitEmptyStrings().split( |
| properties.get(sortByKey))); |
| } |
| |
| /** |
| * Returns the sorting order from 'properties' or the default value (lexicographic |
| * ordering), if 'properties' doesn't contain 'sort.order'. |
| */ |
| @VisibleForTesting |
| protected static String getSortingOrder(Map<String, String> properties) { |
| String sortOrderKey = AlterTableSortByStmt.TBL_PROP_SORT_ORDER; |
| if (!properties.containsKey(sortOrderKey)) return TSortingOrder.LEXICAL.toString(); |
| return properties.get(sortOrderKey); |
| } |
| |
| /** |
| * Returns a comma-delimited string of Kudu 'partition by' parameters on a |
| * CreateTableStmt, or null if this isn't a CreateTableStmt for a Kudu table. |
| */ |
| private static String getKuduPartitionByParams(CreateTableStmt stmt) { |
| List<KuduPartitionParam> partitionParams = stmt.getKuduPartitionParams(); |
| Preconditions.checkNotNull(partitionParams); |
| if (partitionParams.isEmpty()) return null; |
| List<String> paramStrings = new ArrayList<>(); |
| for (KuduPartitionParam p : partitionParams) { |
| paramStrings.add(p.toSql()); |
| } |
| return Joiner.on(", ").join(paramStrings); |
| } |
| |
| /** |
| * Check if a column (or table) name will be parsed by Hive as an identifier. |
| * If not, then the identifier must be quoted. |
| * @param ident name to check |
| * @return true if the name must be quoted for Hive, false if the |
| * name is a valid identifier and so needs no quoting |
| */ |
| public static boolean hiveNeedsQuotes(String ident) { |
| // Lexer catches only upper-case keywords: "SELECT", but not "select". |
| // So, do the check on an upper-case version of the identifier. |
| // Hive uses ANTLRNoCaseStringStream to upper-case text, but that |
| // class is a non-static inner class so we can't use it here. |
| HiveLexer hiveLexer = new HiveLexer(new ANTLRStringStream(ident.toUpperCase())); |
| try { |
| Token t = hiveLexer.nextToken(); |
| // Check that the lexer recognizes an identifier and then EOF. |
| // Not an identifier? Needs quotes. |
| if (t.getType() != HiveLexer.Identifier) return true; |
| // Not a single identifier? Needs quotes. |
| t = hiveLexer.nextToken(); |
| return t.getType() != HiveLexer.EOF; |
| } catch (Exception e) { |
| // Ignore exception and just quote the identifier to be safe. |
| return true; |
| } |
| } |
| |
| /** |
| * Determines if an identifier must be quoted for Impala. This is a very |
| * weak test, it works only for simple identifiers. Use this in conjunction |
| * with {@link #hiveNeedsQuotes} for a complete check. |
| * @param ident the identifier to check |
| * @return true if the identifier is an Impala keyword, or if starts |
| * with a digit |
| */ |
| public static boolean impalaNeedsQuotes(String ident) { |
| return SqlScanner.isReserved(ident) || |
| // Quote numbers to avoid odd cases. |
| // SELECT id AS 3a3 FROM functional.alltypestiny |
| // is valid, but |
| // SELECT id AS 3e3 FROM functional.alltypestiny |
| // Is not. The "e" changes the meaning from identifier to number. |
| Character.isDigit(ident.charAt(0)) || |
| // The parser-based checks fail if the identifier contains a comment |
| // character: the parser ignores those characters and the rest of |
| // the identifier. Treat them specially. |
| ident.contains("#") || ident.contains("--"); |
| } |
| |
| /** |
| * Given an unquoted identifier string, returns an identifier lexable by |
| * Impala and Hive, possibly by enclosing the original identifier in "`" quotes. |
| * For example, Hive cannot parse its own auto-generated column |
| * names "_c0", "_c1" etc. unless they are quoted. Impala and Hive keywords |
| * must also be quoted. |
| * |
| * The Impala and Hive lexical analyzers recognize a mostly-overlapping, |
| * but sometimes distinct set of keywords. Impala further imposes certain |
| * syntactic rules around identifiers that start with digits. To ensure |
| * that views generated by Impala are readable both both Impala and Hive, |
| * we quote names which are either Hive keywords, Impala keywords, or |
| * are ambiguous in Impala. |
| * |
| * The wildcard ("*") is never quoted though it is not an identifier. |
| */ |
| public static String getIdentSql(String ident) { |
| // Don't quote the wildcard used in SELECT *. |
| if (ident.equals("*")) return ident; |
| return hiveNeedsQuotes(ident) || impalaNeedsQuotes(ident) |
| ? "`" + ident + "`" : ident; |
| } |
| |
| /** |
| * Test case version of {@link #getIdentSql(String)}, with |
| * special handling for the wildcard and multi-part names. |
| * For creating generic expected values in tests. |
| */ |
| public static String identSql(String ident) { |
| List<String> parts = new ArrayList<>(); |
| for (String part : Splitter.on('.').split(ident)) { |
| parts.add(ident.equals("*") ? part : getIdentSql(part)); |
| } |
| return Joiner.on('.').join(parts); |
| } |
| |
| public static List<String> getIdentSqlList(List<String> identList) { |
| List<String> identSqlList = new ArrayList<>(); |
| for (String ident: identList) { |
| identSqlList.add(getIdentSql(ident)); |
| } |
| return identSqlList; |
| } |
| |
| public static String getPathSql(List<String> path) { |
| StringBuilder result = new StringBuilder(); |
| for (String p: path) { |
| if (result.length() > 0) result.append("."); |
| result.append(getIdentSql(p)); |
| } |
| return result.toString(); |
| } |
| |
| /** |
| * Returns the "CREATE TABLE" SQL string corresponding to the given CreateTableStmt |
| * statement. |
| */ |
| public static String getCreateTableSql(CreateTableStmt stmt) { |
| List<String> colsSql = new ArrayList<>(); |
| for (ColumnDef col: stmt.getColumnDefs()) { |
| colsSql.add(col.toString()); |
| } |
| List<String> partitionColsSql = new ArrayList<>(); |
| for (ColumnDef col: stmt.getPartitionColumnDefs()) { |
| partitionColsSql.add(col.toString()); |
| } |
| Map<String, String> properties = Maps.newLinkedHashMap( |
| stmt.getTblProperties()); |
| Map<String, String> generatedProperties = Maps.newLinkedHashMap( |
| stmt.getGeneratedKuduProperties()); |
| removeHiddenTableProperties(properties); |
| removeHiddenKuduTableProperties(generatedProperties); |
| properties.putAll(generatedProperties); |
| String kuduParamsSql = getKuduPartitionByParams(stmt); |
| // TODO: Pass the correct compression, if applicable. |
| return getCreateTableSql(stmt.getDb(), stmt.getTbl(), stmt.getComment(), colsSql, |
| partitionColsSql, stmt.getTblPrimaryKeyColumnNames(), stmt.getForeignKeysSql(), |
| kuduParamsSql, new Pair<>(stmt.getSortColumns(), stmt.getSortingOrder()), |
| properties, stmt.getSerdeProperties(), stmt.isExternal(), stmt.getIfNotExists(), |
| stmt.getRowFormat(), HdfsFileFormat.fromThrift(stmt.getFileFormat()), |
| HdfsCompression.NONE, null, stmt.getLocation()); |
| } |
| |
| /** |
| * Returns the "CREATE TABLE" SQL string corresponding to the given |
| * CreateTableAsSelectStmt statement. If rewritten is true, returns the rewritten SQL |
| * only if the statement was rewritten. Otherwise, the original SQL will be returned |
| * instead. It is the caller's responsibility to know if/when the statement was indeed |
| * rewritten. |
| */ |
| public static String getCreateTableSql( |
| CreateTableAsSelectStmt stmt, ToSqlOptions options) { |
| CreateTableStmt innerStmt = stmt.getCreateStmt(); |
| // Only add partition column labels to output. Table columns must not be specified as |
| // they are deduced from the select statement. |
| List<String> partitionColsSql = new ArrayList<>(); |
| for (ColumnDef col: innerStmt.getPartitionColumnDefs()) { |
| partitionColsSql.add(col.getColName()); |
| } |
| // Use a LinkedHashMap to preserve the ordering of the table properties. |
| Map<String, String> properties = |
| Maps.newLinkedHashMap(innerStmt.getTblProperties()); |
| Map<String, String> generatedProperties = Maps.newLinkedHashMap( |
| stmt.getCreateStmt().getGeneratedKuduProperties()); |
| removeHiddenTableProperties(properties); |
| removeHiddenKuduTableProperties(generatedProperties); |
| properties.putAll(generatedProperties); |
| String kuduParamsSql = getKuduPartitionByParams(innerStmt); |
| // TODO: Pass the correct compression, if applicable. |
| String createTableSql = getCreateTableSql(innerStmt.getDb(), innerStmt.getTbl(), |
| innerStmt.getComment(), null, partitionColsSql, |
| innerStmt.getTblPrimaryKeyColumnNames(), innerStmt.getForeignKeysSql(), |
| kuduParamsSql, new Pair<>(innerStmt.getSortColumns(), |
| innerStmt.getSortingOrder()), properties, innerStmt.getSerdeProperties(), |
| innerStmt.isExternal(), innerStmt.getIfNotExists(), innerStmt.getRowFormat(), |
| HdfsFileFormat.fromThrift(innerStmt.getFileFormat()), HdfsCompression.NONE, null, |
| innerStmt.getLocation()); |
| return createTableSql + " AS " + stmt.getQueryStmt().toSql(options); |
| } |
| |
| /** |
| * Returns a "CREATE TABLE" or "CREATE VIEW" statement that creates the specified |
| * table. |
| */ |
| public static String getCreateTableSql(FeTable table) throws CatalogException { |
| Preconditions.checkNotNull(table); |
| if (table instanceof FeView) return getCreateViewSql((FeView)table); |
| org.apache.hadoop.hive.metastore.api.Table msTable = table.getMetaStoreTable(); |
| // Use a LinkedHashMap to preserve the ordering of the table properties. |
| Map<String, String> properties = Maps.newLinkedHashMap(msTable.getParameters()); |
| if (properties.containsKey(Table.TBL_PROP_LAST_DDL_TIME)) { |
| properties.remove(Table.TBL_PROP_LAST_DDL_TIME); |
| } |
| boolean isExternal = msTable.getTableType() != null && |
| msTable.getTableType().equals(TableType.EXTERNAL_TABLE.toString()); |
| List<String> sortColsSql = getSortColumns(properties); |
| TSortingOrder sortingOrder = TSortingOrder.valueOf(getSortingOrder(properties)); |
| String comment = properties.get("comment"); |
| removeHiddenTableProperties(properties); |
| List<String> colsSql = new ArrayList<>(); |
| List<String> partitionColsSql = new ArrayList<>(); |
| boolean isHbaseTable = table instanceof FeHBaseTable; |
| for (int i = 0; i < table.getColumns().size(); i++) { |
| if (!isHbaseTable && i < table.getNumClusteringCols()) { |
| partitionColsSql.add(columnToSql(table.getColumns().get(i))); |
| } else { |
| colsSql.add(columnToSql(table.getColumns().get(i))); |
| } |
| } |
| RowFormat rowFormat = RowFormat.fromStorageDescriptor(msTable.getSd()); |
| HdfsFileFormat format = null; |
| HdfsCompression compression = null; |
| String location = isHbaseTable ? null : msTable.getSd().getLocation(); |
| Map<String, String> serdeParameters = msTable.getSd().getSerdeInfo().getParameters(); |
| |
| String storageHandlerClassName = table.getStorageHandlerClassName(); |
| List<String> primaryKeySql = new ArrayList<>(); |
| List<String> foreignKeySql = new ArrayList<>(); |
| String kuduPartitionByParams = null; |
| if (table instanceof FeKuduTable) { |
| FeKuduTable kuduTable = (FeKuduTable) table; |
| // Kudu tables don't use LOCATION syntax |
| location = null; |
| format = HdfsFileFormat.KUDU; |
| // Kudu tables cannot use the Hive DDL syntax for the storage handler |
| storageHandlerClassName = null; |
| properties.remove(KuduTable.KEY_STORAGE_HANDLER); |
| String kuduTableName = properties.get(KuduTable.KEY_TABLE_NAME); |
| // Remove the hidden table property 'kudu.table_name' for a managed Kudu table. |
| if (kuduTableName != null && |
| KuduUtil.isDefaultKuduTableName(kuduTableName, |
| table.getDb().getName(), table.getName())) { |
| properties.remove(KuduTable.KEY_TABLE_NAME); |
| } |
| // Remove the hidden table property 'kudu.table_id'. |
| properties.remove(KuduTable.KEY_TABLE_ID); |
| // Internal property, should not be exposed to the user. |
| properties.remove(StatsSetupConst.DO_NOT_UPDATE_STATS); |
| |
| if (!isExternal) { |
| primaryKeySql.addAll(kuduTable.getPrimaryKeyColumnNames()); |
| |
| List<String> paramsSql = new ArrayList<>(); |
| for (KuduPartitionParam param: kuduTable.getPartitionBy()) { |
| paramsSql.add(param.toSql()); |
| } |
| kuduPartitionByParams = Joiner.on(", ").join(paramsSql); |
| } else { |
| // We shouldn't output the columns for external tables |
| colsSql = null; |
| } |
| } else if (table instanceof FeFsTable) { |
| String inputFormat = msTable.getSd().getInputFormat(); |
| format = HdfsFileFormat.fromHdfsInputFormatClass(inputFormat); |
| compression = HdfsCompression.fromHdfsInputFormatClass(inputFormat); |
| if (table instanceof HdfsTable) { |
| primaryKeySql = ((HdfsTable) table).getPrimaryKeysSql(); |
| foreignKeySql = ((HdfsTable) table).getForeignKeysSql(); |
| } |
| } |
| HdfsUri tableLocation = location == null ? null : new HdfsUri(location); |
| return getCreateTableSql(table.getDb().getName(), table.getName(), comment, colsSql, |
| partitionColsSql, primaryKeySql, foreignKeySql, kuduPartitionByParams, |
| new Pair<>(sortColsSql, sortingOrder), properties, serdeParameters, |
| isExternal, false, rowFormat, format, compression, |
| storageHandlerClassName, tableLocation); |
| } |
| |
| /** |
| * Returns a "CREATE TABLE" string that creates the table with the specified properties. |
| * The tableName must not be null. If columnsSql is null, the schema syntax will |
| * not be generated. |
| */ |
| public static String getCreateTableSql(String dbName, String tableName, |
| String tableComment, List<String> columnsSql, List<String> partitionColumnsSql, |
| List<String> primaryKeysSql, List<String> foreignKeysSql, |
| String kuduPartitionByParams, Pair<List<String>, TSortingOrder> sortProperties, |
| Map<String, String> tblProperties, Map<String, String> serdeParameters, |
| boolean isExternal, boolean ifNotExists, RowFormat rowFormat, |
| HdfsFileFormat fileFormat, HdfsCompression compression, |
| String storageHandlerClass, HdfsUri location) { |
| Preconditions.checkNotNull(tableName); |
| StringBuilder sb = new StringBuilder("CREATE "); |
| if (isExternal) sb.append("EXTERNAL "); |
| sb.append("TABLE "); |
| if (ifNotExists) sb.append("IF NOT EXISTS "); |
| if (dbName != null) sb.append(dbName + "."); |
| sb.append(tableName); |
| if (columnsSql != null && !columnsSql.isEmpty()) { |
| sb.append(" (\n "); |
| sb.append(Joiner.on(",\n ").join(columnsSql)); |
| if (CollectionUtils.isNotEmpty(primaryKeysSql)) { |
| sb.append(",\n PRIMARY KEY ("); |
| Joiner.on(", ").appendTo(sb, primaryKeysSql).append(")"); |
| } |
| if (CollectionUtils.isNotEmpty(foreignKeysSql)) { |
| sb.append(",\n FOREIGN KEY"); |
| Joiner.on(",\n FOREIGN KEY").appendTo(sb, foreignKeysSql).append("\n"); |
| } |
| sb.append("\n)"); |
| } else { |
| // CTAS for Kudu tables still print the primary key |
| if (primaryKeysSql != null && !primaryKeysSql.isEmpty()) { |
| sb.append("\n PRIMARY KEY ("); |
| Joiner.on(", ").appendTo(sb, primaryKeysSql).append(")"); |
| } |
| } |
| sb.append("\n"); |
| |
| if (partitionColumnsSql != null && partitionColumnsSql.size() > 0) { |
| sb.append(String.format("PARTITIONED BY (\n %s\n)\n", |
| Joiner.on(", \n ").join(partitionColumnsSql))); |
| } |
| |
| if (kuduPartitionByParams != null && !kuduPartitionByParams.equals("")) { |
| sb.append("PARTITION BY " + kuduPartitionByParams + "\n"); |
| } |
| if (sortProperties.first != null) { |
| sb.append(String.format("SORT BY %s (\n %s\n)\n", sortProperties.second.toString(), |
| Joiner.on(", \n ").join(sortProperties.first))); |
| } |
| |
| if (tableComment != null) sb.append(" COMMENT '" + tableComment + "'\n"); |
| |
| if (rowFormat != null && !rowFormat.isDefault()) { |
| sb.append("ROW FORMAT DELIMITED"); |
| if (rowFormat.getFieldDelimiter() != null) { |
| String fieldDelim = StringEscapeUtils.escapeJava(rowFormat.getFieldDelimiter()); |
| sb.append(" FIELDS TERMINATED BY '" + fieldDelim + "'"); |
| } |
| if (rowFormat.getEscapeChar() != null) { |
| String escapeChar = StringEscapeUtils.escapeJava(rowFormat.getEscapeChar()); |
| sb.append(" ESCAPED BY '" + escapeChar + "'"); |
| } |
| if (rowFormat.getLineDelimiter() != null) { |
| String lineDelim = StringEscapeUtils.escapeJava(rowFormat.getLineDelimiter()); |
| sb.append(" LINES TERMINATED BY '" + lineDelim + "'"); |
| } |
| sb.append("\n"); |
| } |
| |
| if (storageHandlerClass == null) { |
| // TODO: Remove this special case when we have the LZO_TEXT writer |
| // We must handle LZO_TEXT specially because Impala does not yet support creating |
| // tables with this row format. In this case, we cannot output "WITH |
| // SERDEPROPERTIES" because Hive does not support it with "STORED AS". For any |
| // other HdfsFileFormat we want to output the serdeproperties because it is |
| // supported by Impala. |
| if (compression != HdfsCompression.LZO && |
| compression != HdfsCompression.LZO_INDEX && |
| serdeParameters != null && !serdeParameters.isEmpty()) { |
| sb.append( |
| "WITH SERDEPROPERTIES " + propertyMapToSql(serdeParameters) + "\n"); |
| } |
| |
| if (fileFormat != null) { |
| sb.append("STORED AS " + fileFormat.toSql(compression) + "\n"); |
| } |
| } else { |
| // If the storageHandlerClass is set, then we will generate the proper Hive DDL |
| // because we do not yet support creating HBase tables via Impala. |
| sb.append("STORED BY '" + storageHandlerClass + "'\n"); |
| if (serdeParameters != null && !serdeParameters.isEmpty()) { |
| sb.append( |
| "WITH SERDEPROPERTIES " + propertyMapToSql(serdeParameters) + "\n"); |
| } |
| } |
| if (location != null) { |
| sb.append("LOCATION '" + location.toString() + "'\n"); |
| } |
| if (tblProperties != null && !tblProperties.isEmpty()) { |
| sb.append("TBLPROPERTIES " + propertyMapToSql(tblProperties)); |
| } |
| return sb.toString(); |
| } |
| |
| public static String getCreateFunctionSql(List<Function> functions) { |
| Preconditions.checkNotNull(functions); |
| StringBuilder sb = new StringBuilder(); |
| for (Function fn: functions) { |
| if (sb.length() > 0) sb.append(";\n"); |
| sb.append(fn.toSql(false)); |
| } |
| return sb.append("\n").toString(); |
| } |
| |
| public static String getCreateViewSql(FeView view) { |
| StringBuffer sb = new StringBuffer(); |
| sb.append("CREATE VIEW "); |
| // Use toSql() to ensure that the table name and query statement are normalized |
| // and identifiers are quoted. |
| sb.append(view.getTableName().toSql()); |
| sb.append(" AS\n"); |
| sb.append(view.getQueryStmt().toSql()); |
| return sb.toString(); |
| } |
| |
| private static String columnToSql(Column col) { |
| StringBuilder sb = new StringBuilder(col.getName()); |
| if (col.getType() != null) sb.append(" " + col.getType().toSql()); |
| if (col instanceof KuduColumn) { |
| KuduColumn kuduCol = (KuduColumn) col; |
| Boolean isNullable = kuduCol.isNullable(); |
| if (isNullable != null) sb.append(isNullable ? " NULL" : " NOT NULL"); |
| if (kuduCol.getEncoding() != null) sb.append(" ENCODING " + kuduCol.getEncoding()); |
| if (kuduCol.getCompression() != null) { |
| sb.append(" COMPRESSION " + kuduCol.getCompression()); |
| } |
| if (kuduCol.hasDefaultValue()) { |
| sb.append(" DEFAULT " + kuduCol.getDefaultValueSql()); |
| } |
| if (kuduCol.getBlockSize() != 0) { |
| sb.append(String.format(" BLOCK_SIZE %d", kuduCol.getBlockSize())); |
| } |
| } |
| if (!Strings.isNullOrEmpty(col.getComment())) { |
| sb.append(String.format(" COMMENT '%s'", col.getComment())); |
| } |
| return sb.toString(); |
| } |
| |
| private static String propertyMapToSql(Map<String, String> propertyMap) { |
| // Sort entries on the key to ensure output is deterministic for tests (IMPALA-5757). |
| List<Entry<String, String>> mapEntries = Lists.newArrayList(propertyMap.entrySet()); |
| Collections.sort(mapEntries, new Comparator<Entry<String, String>>() { |
| @Override |
| public int compare(Entry<String, String> o1, Entry<String, String> o2) { |
| return ObjectUtils.compare(o1.getKey(), o2.getKey()); |
| } }); |
| |
| List<String> properties = new ArrayList<>(); |
| for (Map.Entry<String, String> entry: mapEntries) { |
| properties.add(String.format("'%s'='%s'", entry.getKey(), |
| // Properties may contain characters that need to be escaped. |
| // e.g. If the row format escape delimiter is '\', the map of serde properties |
| // from the metastore table will contain 'escape.delim' => '\', which is not |
| // properly escaped. |
| StringEscapeUtils.escapeJava(entry.getValue()))); |
| } |
| return "(" + Joiner.on(", ").join(properties) + ")"; |
| } |
| |
| /** |
| * Returns a SQL representation of the given list of hints. Uses the end-of-line |
| * commented plan hint style such that hinted views created by Impala are readable by |
| * Hive (parsed as a comment by Hive). |
| */ |
| public static String getPlanHintsSql(ToSqlOptions options, List<PlanHint> hints) { |
| Preconditions.checkNotNull(hints); |
| if (hints.isEmpty()) return ""; |
| StringBuilder sb = new StringBuilder(); |
| if (options.showRewritten()) { |
| sb.append("/* +"); |
| sb.append(Joiner.on(",").join(hints)); |
| sb.append(" */"); |
| } else { |
| sb.append("\n-- +"); |
| sb.append(Joiner.on(",").join(hints)); |
| sb.append("\n"); |
| } |
| return sb.toString(); |
| } |
| |
| public static String formatAlias(String alias) { |
| if (alias == null) return ""; |
| return " " + getIdentSql(alias); |
| } |
| } |