/** * Copyright 2011-2017 Asakusa Framework Team. * * 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 com.asakusafw.directio.hive.syntax; import java.io.IOException; import java.util.Iterator; import java.util.Map; import com.asakusafw.directio.hive.info.BuiltinStorageFormatInfo; import com.asakusafw.directio.hive.info.ColumnInfo; import com.asakusafw.directio.hive.info.CustomStorageFormatInfo; import com.asakusafw.directio.hive.info.DelimitedRowFormatInfo; import com.asakusafw.directio.hive.info.RowFormatInfo; import com.asakusafw.directio.hive.info.SerdeRowFormatInfo; import com.asakusafw.directio.hive.info.StorageFormatInfo; import com.asakusafw.directio.hive.info.TableInfo; /** * Emits Hive QL. * @since 0.8.1 */ public final class HiveQlEmitter { private HiveQlEmitter() { return; } /** * Emits {@code CREATE TABLE} statement into the target writer. * @param statement the source statement * @param writer the target writer * @throws IOException if failed by I/O error */ public static void emit(HiveCreateTable statement, Appendable writer) throws IOException { TableInfo table = statement.getTableInfo(); Context c = new Context(writer); emitCreateTableHead(c, statement); // TODO PARTITIONED BY // TODO CLUSTERED BY // TODO SKEWED BY RowFormatInfo rowFormat = table.getRowFormat(); if (rowFormat != null) { c.tokens("ROW", "FORMAT"); switch (rowFormat.getFormatKind()) { case DELIMITED: emitRowFormat(c, (DelimitedRowFormatInfo) rowFormat); break; case SERDE: emitRowFormat(c, (SerdeRowFormatInfo) rowFormat); break; default: throw new AssertionError(rowFormat.getFormatKind()); } c.newLine(); } StorageFormatInfo storageFormat = table.getStorageFormat(); if (storageFormat != null) { c.tokens("STORED", "AS"); //$NON-NLS-1$ //$NON-NLS-2$ switch (storageFormat.getFormatKind().getCategory()) { case BUILTIN: emitStorageFormat(c, (BuiltinStorageFormatInfo) storageFormat); break; case CUSTOM: emitStorageFormat(c, (CustomStorageFormatInfo) storageFormat); break; default: throw new AssertionError(storageFormat); } c.newLine(); } // or TODO STORED BY if (statement.getLocation() != null) { c.token("LOCATION"); //$NON-NLS-1$ c.string(statement.getLocation()); c.newLine(); } if (table.getProperties().isEmpty() == false) { c.token("TBLPROPERTIES"); //$NON-NLS-1$ c.token("("); //$NON-NLS-1$ c.newLine(); c.indent(+1); emitProperties(c, table.getProperties()); c.indent(-1); c.token(")"); //$NON-NLS-1$ c.newLine(); } } private static void emitRowFormat(Context c, DelimitedRowFormatInfo info) throws IOException { c.token("DELIMITED"); c.newLine(); if (info.getFieldSeparator() != null) { c.indent(+1); c.tokens("FIELDS", "TERMINATED", "BY"); c.string(info.getFieldSeparator()); if (info.getFieldSeparatorEscape() != null) { c.tokens("ESCAPED", "BY"); c.string(info.getFieldSeparatorEscape()); } c.newLine(); c.indent(-1); } if (info.getCollectionItemSeparator() != null) { c.indent(+1); c.tokens("COLLECTION", "ITEMS", "TERMINATED", "BY"); c.string(info.getCollectionItemSeparator()); c.newLine(); c.indent(-1); } if (info.getMapPairSeparator() != null) { c.indent(+1); c.tokens("MAP", "KEYS", "TERMINATED", "BY"); c.string(info.getMapPairSeparator()); c.newLine(); c.indent(-1); } if (info.getLineSeparator() != null) { c.indent(+1); c.tokens("LINES", "TERMINATED", "BY"); c.string(info.getLineSeparator()); c.newLine(); c.indent(-1); } if (info.getNullSymbol() != null) { c.indent(+1); c.tokens("NULL", "DEFINED", "AS"); c.string(info.getNullSymbol()); c.newLine(); c.indent(-1); } } private static void emitRowFormat(Context c, SerdeRowFormatInfo info) throws IOException { c.token("SERDE"); c.string(info.getName()); if (info.getProperties().isEmpty() == false) { c.tokens("WITH", "SERDEPROPERTIES"); c.token("("); //$NON-NLS-1$ c.newLine(); c.indent(+1); emitProperties(c, info.getProperties()); c.indent(-1); c.token(")"); //$NON-NLS-1$ } } private static void emitStorageFormat(Context c, BuiltinStorageFormatInfo info) throws IOException { c.token(info.getFormatKind().name()); } private static void emitStorageFormat(Context c, CustomStorageFormatInfo info) throws IOException { c.token("INPUTFORMAT"); //$NON-NLS-1$ c.string(info.getInputFormatClass()); c.token("OUTPUTFORMAT"); //$NON-NLS-1$ c.string(info.getOutputFormatClass()); } private static void emitCreateTableHead(Context c, HiveCreateTable statement) throws IOException { c.token("CREATE"); //$NON-NLS-1$ if (statement.isExternal()) { c.token("EXTERNAL"); //$NON-NLS-1$ } c.token("TABLE"); //$NON-NLS-1$ if (statement.isSkipPresentTable()) { c.tokens("IF", "NOT", "EXISTS"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } TableInfo table = statement.getTableInfo(); if (statement.getDatabaseName() == null) { c.name(table.getName()); } else { c.token(String.format( "%s.%s", //$NON-NLS-1$ HiveSyntax.quoteIdentifier(statement.getDatabaseName()), HiveSyntax.quoteIdentifier(table.getName()))); } c.token("("); //$NON-NLS-1$ c.newLine(); c.indent(+1); ColumnInfo[] columns = table.getColumns().toArray(new ColumnInfo[table.getColumns().size()]); for (int i = 0; i < columns.length; i++) { ColumnInfo column = columns[i]; c.name(column.getName()); c.token(column.getType().getQualifiedName()); if (column.getComment() != null) { c.token("COMMENT"); //$NON-NLS-1$ c.string(column.getComment()); } if (i != columns.length - 1) { c.token(","); //$NON-NLS-1$ } c.newLine(); } c.indent(-1); c.token(")"); //$NON-NLS-1$ c.newLine(); if (table.getComment() != null) { c.token("COMMENT"); //$NON-NLS-1$ c.string(table.getComment()); c.newLine(); } } private static void emitProperties(Context c, Map<String, String> properties) throws IOException { if (properties.isEmpty()) { return; } Iterator<Map.Entry<String, String>> iter = properties.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, String> entry = iter.next(); c.string(entry.getKey()); c.token("="); //$NON-NLS-1$ c.string(entry.getValue()); if (iter.hasNext()) { c.token(","); //$NON-NLS-1$ } c.newLine(); } } private static final class Context { private static final int INDENT_WIDTH = 4; private final Appendable writer; private int indent = 0; private boolean headOfLine = true; private boolean newLine = false; /** * Creates a new instance. * @param writer the target writer */ Context(Appendable writer) { this.writer = writer; } public void name(String name) throws IOException { pad(); writer.append(HiveSyntax.quoteIdentifier(name)); } public void token(String token) throws IOException { pad(); writer.append(token); } public void tokens(String... tokens) throws IOException { for (String string : tokens) { token(string); } } public void string(String text) throws IOException { pad(); writer.append(HiveSyntax.quoteLiteral('\'', text)); } public void newLine() { newLine = true; } public void indent(int delta) { assert newLine || headOfLine; this.indent += delta; } private void pad() throws IOException { if (newLine) { newLine = false; writer.append('\n'); headOfLine = true; } if (headOfLine) { for (int i = 0, n = indent * INDENT_WIDTH; i < n; i++) { writer.append(' '); } headOfLine = false; } else { writer.append(' '); } } } }