All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.sql.pretty.SqlPrettyWriter Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * 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 com.hazelcast.org.apache.calcite.sql.pretty;

import com.hazelcast.org.apache.calcite.avatica.util.Spaces;
import com.hazelcast.org.apache.calcite.sql.SqlBinaryOperator;
import com.hazelcast.org.apache.calcite.sql.SqlDialect;
import com.hazelcast.org.apache.calcite.sql.SqlNode;
import com.hazelcast.org.apache.calcite.sql.SqlNodeList;
import com.hazelcast.org.apache.calcite.sql.SqlWriter;
import com.hazelcast.org.apache.calcite.sql.SqlWriterConfig;
import com.hazelcast.org.apache.calcite.sql.dialect.AnsiSqlDialect;
import com.hazelcast.org.apache.calcite.sql.dialect.CalciteSqlDialect;
import com.hazelcast.org.apache.calcite.sql.util.SqlString;
import com.hazelcast.org.apache.calcite.util.Util;
import com.hazelcast.org.apache.calcite.util.trace.CalciteLogger;

import com.hazelcast.com.google.common.base.Preconditions;
import com.hazelcast.com.google.common.collect.ImmutableList;

import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;
import com.hazelcast.org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;

import static java.util.Objects.requireNonNull;

/**
 * Pretty printer for SQL statements.
 *
 * 

There are several options to control the format. * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Formatting options
OptionDescriptionDefault
{@link SqlWriterConfig#clauseStartsLine()} ClauseStartsLine}Whether a clause ({@code FROM}, {@code WHERE}, {@code GROUP BY}, * {@code HAVING}, {@code WINDOW}, {@code ORDER BY}) starts a new line. * {@code SELECT} is always at the start of a line.true
{@link SqlWriterConfig#clauseEndsLine ClauseEndsLine}Whether a clause ({@code SELECT}, {@code FROM}, {@code WHERE}, * {@code GROUP BY}, {@code HAVING}, {@code WINDOW}, {@code ORDER BY}) is * followed by a new line.false
{@link SqlWriterConfig#caseClausesOnNewLines CaseClausesOnNewLines}Whether the WHEN, THEN and ELSE clauses of a CASE expression appear at * the start of a new line.false
{@link SqlWriterConfig#indentation Indentation}Number of spaces to indent4
{@link SqlWriterConfig#keywordsLowerCase KeywordsLowerCase}Whether to print keywords (SELECT, AS, etc.) in lower-case.false
{@link SqlWriterConfig#alwaysUseParentheses AlwaysUseParentheses}

Whether to enclose all expressions in parentheses, even if the * operator has high enough precedence that the parentheses are not required. * *

For example, the parentheses are required in the expression * {@code (a + b) * c} because the '*' operator has higher precedence than the * '+' operator, and so without the parentheses, the expression would be * equivalent to {@code a + (b * c)}. The fully-parenthesized expression, * {@code ((a + b) * c)} is unambiguous even if you don't know the precedence * of every operator.

false
{@link SqlWriterConfig#quoteAllIdentifiers QuoteAllIdentifiers}Whether to quote all identifiers, even those which would be correct * according to the rules of the {@link SqlDialect} if quotation marks were * omitted.true
{@link SqlWriterConfig#subQueryStyle SubQueryStyle}Style for formatting sub-queries. Values are: * {@link com.hazelcast.org.apache.calcite.sql.SqlWriter.SubQueryStyle#HYDE Hyde}, * {@link com.hazelcast.org.apache.calcite.sql.SqlWriter.SubQueryStyle#BLACK Black}.{@link com.hazelcast.org.apache.calcite.sql.SqlWriter.SubQueryStyle#HYDE Hyde}
{@link SqlWriterConfig#lineLength LineLength}The desired maximum length for lines (to look nice in editors, * printouts, etc.).-1 (no maximum)
{@link SqlWriterConfig#foldLength FoldLength}The line length at which lines are folded or chopped down * (see {@code LineFolding}). Only has an effect if clauses are marked * {@link SqlWriterConfig.LineFolding#CHOP CHOP} or * {@link SqlWriterConfig.LineFolding#FOLD FOLD}.80
{@link SqlWriterConfig#lineFolding LineFolding}How long lines are to be handled. Options are lines are * WIDE (do not wrap), * FOLD (wrap if long), * CHOP (chop down if long), * and TALL (wrap always).WIDE
{@link SqlWriterConfig#selectFolding() SelectFolding}How the {@code SELECT} clause is to be folded.{@code LineFolding}
{@link SqlWriterConfig#fromFolding FromFolding}How the {@code FROM} clause and nested {@code JOIN} clauses are to be * folded.{@code LineFolding}
{@link SqlWriterConfig#whereFolding WhereFolding}How the {@code WHERE} clause is to be folded.{@code LineFolding}
{@link SqlWriterConfig#groupByFolding GroupByFolding}How the {@code GROUP BY} clause is to be folded.{@code LineFolding}
{@link SqlWriterConfig#havingFolding HavingFolding}How the {@code HAVING} clause is to be folded.{@code LineFolding}
{@link SqlWriterConfig#orderByFolding OrderByFolding}How the {@code ORDER BY} clause is to be folded.{@code LineFolding}
{@link SqlWriterConfig#windowFolding WindowFolding}How the {@code WINDOW} clause is to be folded.{@code LineFolding}
{@link SqlWriterConfig#overFolding OverFolding}How window declarations in the {@code WINDOW} clause * and in the {@code OVER} clause of aggregate functions are to be folded.{@code LineFolding}
{@link SqlWriterConfig#valuesFolding ValuesFolding}How lists of values in the {@code VALUES} clause are to be folded.{@code LineFolding}
{@link SqlWriterConfig#updateSetFolding UpdateSetFolding}How assignments in the {@code SET} clause of an {@code UPDATE} statement * are to be folded.{@code LineFolding}
* *

The following options exist for backwards compatibility. They are * used if {@link SqlWriterConfig#lineFolding LineFolding} and clause-specific * options such as {@link SqlWriterConfig#selectFolding SelectFolding} are not * specified: * *

    * *
  • {@link SqlWriterConfig#selectListItemsOnSeparateLines SelectListItemsOnSeparateLines} * replaced by {@link SqlWriterConfig#selectFolding SelectFolding}, * {@link SqlWriterConfig#groupByFolding GroupByFolding}, and * {@link SqlWriterConfig#orderByFolding OrderByFolding}; * *
  • {@link SqlWriterConfig#updateSetListNewline UpdateSetListNewline} * replaced by {@link SqlWriterConfig#updateSetFolding UpdateSetFolding}; * *
  • {@link SqlWriterConfig#windowDeclListNewline WindowDeclListNewline} * replaced by {@link SqlWriterConfig#windowFolding WindowFolding}; * *
  • {@link SqlWriterConfig#windowNewline WindowNewline} * replaced by {@link SqlWriterConfig#overFolding OverFolding}; * *
  • {@link SqlWriterConfig#valuesListNewline ValuesListNewline} * replaced by {@link SqlWriterConfig#valuesFolding ValuesFolding}. * *
*/ public class SqlPrettyWriter implements SqlWriter { //~ Static fields/initializers --------------------------------------------- protected static final CalciteLogger LOGGER = new CalciteLogger( LoggerFactory.getLogger("com.hazelcast.org.apache.calcite.sql.pretty.SqlPrettyWriter")); /** * Default SqlWriterConfig, reduce the overhead of "ImmutableBeans.create" */ private static final SqlWriterConfig CONFIG = SqlWriterConfig.of() .withDialect(CalciteSqlDialect.DEFAULT); /** * Bean holding the default property values. */ private static final Bean DEFAULT_BEAN = new SqlPrettyWriter(SqlPrettyWriter.config() .withDialect(AnsiSqlDialect.DEFAULT)).getBean(); protected static final String NL = System.getProperty("line.separator"); //~ Instance fields -------------------------------------------------------- private final SqlDialect dialect; private final StringBuilder buf; private final Deque listStack = new ArrayDeque<>(); private ImmutableList.@Nullable Builder dynamicParameters; protected @Nullable FrameImpl frame; private boolean needWhitespace; protected @Nullable String nextWhitespace; private SqlWriterConfig config; private @Nullable Bean bean; private int currentIndent; private int lineStart; //~ Constructors ----------------------------------------------------------- @SuppressWarnings("method.invocation.invalid") private SqlPrettyWriter(SqlWriterConfig config, StringBuilder buf, @SuppressWarnings("unused") boolean ignore) { this.buf = requireNonNull(buf, "buf"); this.dialect = requireNonNull(config.dialect()); this.config = requireNonNull(config, "config"); lineStart = 0; reset(); } /** Creates a writer with the given configuration * and a given buffer to write to. */ public SqlPrettyWriter(SqlWriterConfig config, StringBuilder buf) { this(config, requireNonNull(buf, "buf"), false); } /** Creates a writer with the given configuration and dialect, * and a given print writer (or a private print writer if it is null). */ public SqlPrettyWriter( SqlDialect dialect, SqlWriterConfig config, StringBuilder buf) { this(config.withDialect(requireNonNull(dialect, "dialect")), buf); } /** Creates a writer with the given configuration * and a private print writer. */ @Deprecated public SqlPrettyWriter(SqlDialect dialect, SqlWriterConfig config) { this(config.withDialect(requireNonNull(dialect, "dialect"))); } @Deprecated public SqlPrettyWriter( SqlDialect dialect, boolean alwaysUseParentheses, PrintWriter pw) { // NOTE that 'pw' is ignored; there is no place for it in the new API this(config().withDialect(requireNonNull(dialect, "dialect")) .withAlwaysUseParentheses(alwaysUseParentheses)); } @Deprecated public SqlPrettyWriter( SqlDialect dialect, boolean alwaysUseParentheses) { this(config().withDialect(requireNonNull(dialect, "dialect")) .withAlwaysUseParentheses(alwaysUseParentheses)); } /** Creates a writer with a given dialect, the default configuration * and a private print writer. */ @Deprecated public SqlPrettyWriter(SqlDialect dialect) { this(config().withDialect(requireNonNull(dialect, "dialect"))); } /** Creates a writer with the given configuration, * and a private builder. */ public SqlPrettyWriter(SqlWriterConfig config) { this(config, new StringBuilder(), true); } /** Creates a writer with the default configuration. * * @see #config() */ public SqlPrettyWriter() { this(config()); } /** Creates a {@link SqlWriterConfig} with Calcite's SQL dialect. */ public static SqlWriterConfig config() { return CONFIG; } //~ Methods ---------------------------------------------------------------- @Deprecated public void setCaseClausesOnNewLines(boolean caseClausesOnNewLines) { this.config = config.withCaseClausesOnNewLines(caseClausesOnNewLines); } @Deprecated public void setSubQueryStyle(SubQueryStyle subQueryStyle) { this.config = config.withSubQueryStyle(subQueryStyle); } @Deprecated public void setWindowNewline(boolean windowNewline) { this.config = config.withWindowNewline(windowNewline); } @Deprecated public void setWindowDeclListNewline(boolean windowDeclListNewline) { this.config = config.withWindowDeclListNewline(windowDeclListNewline); } @Deprecated @Override public int getIndentation() { return config.indentation(); } @Deprecated @Override public boolean isAlwaysUseParentheses() { return config.alwaysUseParentheses(); } @Override public boolean inQuery() { return (frame == null) || (frame.frameType == FrameTypeEnum.ORDER_BY) || (frame.frameType == FrameTypeEnum.WITH) || (frame.frameType == FrameTypeEnum.SETOP); } @Deprecated @Override public boolean isQuoteAllIdentifiers() { return config.quoteAllIdentifiers(); } @Deprecated @Override public boolean isClauseStartsLine() { return config.clauseStartsLine(); } @Deprecated @Override public boolean isSelectListItemsOnSeparateLines() { return config.selectListItemsOnSeparateLines(); } @Deprecated public boolean isWhereListItemsOnSeparateLines() { return config.whereListItemsOnSeparateLines(); } @Deprecated public boolean isSelectListExtraIndentFlag() { return config.selectListExtraIndentFlag(); } @Deprecated @Override public boolean isKeywordsLowerCase() { return config.keywordsLowerCase(); } @Deprecated public int getLineLength() { return config.lineLength(); } @Override public void resetSettings() { reset(); config = config(); } @Override public void reset() { buf.setLength(0); lineStart = 0; dynamicParameters = null; setNeedWhitespace(false); nextWhitespace = " "; } /** * Returns an object which encapsulates each property as a get/set method. */ private Bean getBean() { if (bean == null) { bean = new Bean(this); } return bean; } @Deprecated public void setIndentation(int indentation) { this.config = config.withIndentation(indentation); } /** * Prints the property settings of this pretty-writer to a writer. * * @param pw Writer * @param omitDefaults Whether to omit properties whose value is the same as * the default */ public void describe(PrintWriter pw, boolean omitDefaults) { final Bean properties = getBean(); final String[] propertyNames = properties.getPropertyNames(); int count = 0; for (String key : propertyNames) { final Object value = properties.get(key); final Object defaultValue = DEFAULT_BEAN.get(key); if (Objects.equals(value, defaultValue)) { continue; } if (count++ > 0) { pw.print(","); } pw.print(key + "=" + value); } } /** * Sets settings from a properties object. */ public void setSettings(Properties properties) { resetSettings(); final Bean bean = getBean(); final String[] propertyNames = bean.getPropertyNames(); for (String propertyName : propertyNames) { final String value = properties.getProperty(propertyName); if (value != null) { bean.set(propertyName, value); } } } @Deprecated public void setClauseStartsLine(boolean clauseStartsLine) { this.config = config.withClauseStartsLine(clauseStartsLine); } @Deprecated public void setSelectListItemsOnSeparateLines(boolean b) { this.config = config.withSelectListItemsOnSeparateLines(b); } @Deprecated public void setSelectListExtraIndentFlag(boolean selectListExtraIndentFlag) { this.config = config.withSelectListExtraIndentFlag(selectListExtraIndentFlag); } @Deprecated public void setKeywordsLowerCase(boolean keywordsLowerCase) { this.config = config.withKeywordsLowerCase(keywordsLowerCase); } @Deprecated public void setWhereListItemsOnSeparateLines( boolean whereListItemsOnSeparateLines) { this.config = config.withWhereListItemsOnSeparateLines(whereListItemsOnSeparateLines); } @Deprecated public void setAlwaysUseParentheses(boolean alwaysUseParentheses) { this.config = config.withAlwaysUseParentheses(alwaysUseParentheses); } @Override public void newlineAndIndent() { newlineAndIndent(currentIndent); } public void newlineAndIndent(int indent) { buf.append(NL); lineStart = buf.length(); indent(indent); setNeedWhitespace(false); // no further whitespace necessary } void indent(int indent) { if (indent < 0) { throw new IllegalArgumentException("negative indent " + indent); } Spaces.append(buf, indent); } @Deprecated public void setQuoteAllIdentifiers(boolean quoteAllIdentifiers) { this.config = config.withQuoteAllIdentifiers(quoteAllIdentifiers); } /** * Creates a list frame. * *

Derived classes should override this method to specify the indentation * of the list. * * @param frameType What type of list * @param keyword The keyword to be printed at the start of the list * @param open The string to print at the start of the list * @param close The string to print at the end of the list * @return A frame */ protected FrameImpl createListFrame( FrameType frameType, @Nullable String keyword, String open, String close) { final FrameTypeEnum frameTypeEnum = frameType instanceof FrameTypeEnum ? (FrameTypeEnum) frameType : FrameTypeEnum.OTHER; final int indentation = config.indentation(); boolean newlineAfterOpen = false; boolean newlineBeforeSep = false; boolean newlineAfterSep = false; boolean newlineBeforeClose = false; int left = column(); int sepIndent = indentation; int extraIndent = 0; final SqlWriterConfig.LineFolding fold = fold(frameTypeEnum); final boolean newline = fold == SqlWriterConfig.LineFolding.TALL; switch (frameTypeEnum) { case SELECT: extraIndent = indentation; newlineAfterOpen = false; newlineBeforeSep = config.clauseStartsLine(); // newline before FROM, WHERE etc. newlineAfterSep = false; sepIndent = 0; // all clauses appear below SELECT break; case SETOP: extraIndent = 0; newlineAfterOpen = false; newlineBeforeSep = config.clauseStartsLine(); // newline before UNION, EXCEPT newlineAfterSep = config.clauseStartsLine(); // newline after UNION, EXCEPT sepIndent = 0; // all clauses appear below SELECT break; case SELECT_LIST: case FROM_LIST: case JOIN: case GROUP_BY_LIST: case ORDER_BY_LIST: case WINDOW_DECL_LIST: case VALUES: if (config.selectListExtraIndentFlag()) { extraIndent = indentation; } left = frame == null ? 0 : frame.left; newlineAfterOpen = config.clauseEndsLine() && (fold == SqlWriterConfig.LineFolding.TALL || fold == SqlWriterConfig.LineFolding.STEP); newlineBeforeSep = false; newlineAfterSep = newline; if (config.leadingComma() && newline) { newlineBeforeSep = true; newlineAfterSep = false; sepIndent = -", ".length(); } break; case WHERE_LIST: case WINDOW: extraIndent = indentation; newlineAfterOpen = newline && config.clauseEndsLine(); newlineBeforeSep = newline; sepIndent = 0; newlineAfterSep = false; break; case ORDER_BY: case OFFSET: case FETCH: newlineAfterOpen = false; newlineBeforeSep = true; sepIndent = 0; newlineAfterSep = false; break; case UPDATE_SET_LIST: extraIndent = indentation; newlineAfterOpen = newline; newlineBeforeSep = false; sepIndent = 0; newlineAfterSep = newline; break; case CASE: newlineAfterOpen = newline; newlineBeforeSep = newline; newlineBeforeClose = newline; sepIndent = 0; break; default: break; } final int chopColumn; SqlWriterConfig.LineFolding lineFolding = config.lineFolding(); if (lineFolding == null) { lineFolding = SqlWriterConfig.LineFolding.WIDE; chopColumn = -1; } else { if (config.foldLength() > 0 && (lineFolding == SqlWriterConfig.LineFolding.CHOP || lineFolding == SqlWriterConfig.LineFolding.FOLD || lineFolding == SqlWriterConfig.LineFolding.STEP)) { chopColumn = left + config.foldLength(); } else { chopColumn = -1; } } switch (frameTypeEnum) { case UPDATE_SET_LIST: case WINDOW_DECL_LIST: case VALUES: case SELECT: case SETOP: case SELECT_LIST: case WHERE_LIST: case ORDER_BY_LIST: case GROUP_BY_LIST: case WINDOW: case ORDER_BY: case OFFSET: case FETCH: return new FrameImpl(frameType, keyword, open, close, left, extraIndent, chopColumn, lineFolding, newlineAfterOpen, newlineBeforeSep, sepIndent, newlineAfterSep, false, false); case SUB_QUERY: switch (config.subQueryStyle()) { case BLACK: // Generate, e.g.: // // WHERE foo = bar IN // ( SELECT ... open = Spaces.padRight("(", indentation - 1); return new FrameImpl(frameType, keyword, open, close, left, 0, chopColumn, lineFolding, false, true, indentation, false, false, false) { protected void _before() { newlineAndIndent(); } }; case HYDE: // Generate, e.g.: // // WHERE foo IN ( // SELECT ... return new FrameImpl(frameType, keyword, open, close, left, 0, chopColumn, lineFolding, false, true, 0, false, false, false) { protected void _before() { nextWhitespace = NL; } }; default: throw Util.unexpected(config.subQueryStyle()); } case FUN_CALL: setNeedWhitespace(false); return new FrameImpl(frameType, keyword, open, close, left, indentation, chopColumn, lineFolding, false, false, indentation, false, false, false); case PARENTHESES: open = "("; close = ")"; // fall through case IDENTIFIER: case SIMPLE: return new FrameImpl(frameType, keyword, open, close, left, indentation, chopColumn, lineFolding, false, false, indentation, false, false, false); case FROM_LIST: case JOIN: newlineBeforeSep = newline; sepIndent = 0; // all clauses appear below SELECT newlineAfterSep = false; final boolean newlineBeforeComma = config.leadingComma() && newline; final boolean newlineAfterComma = !config.leadingComma() && newline; return new FrameImpl(frameType, keyword, open, close, left, indentation, chopColumn, lineFolding, newlineAfterOpen, newlineBeforeSep, sepIndent, newlineAfterSep, false, false) { @Override protected void sep(boolean printFirst, String sep) { final boolean newlineBeforeSep; final boolean newlineAfterSep; if (sep.equals(",")) { newlineBeforeSep = newlineBeforeComma; newlineAfterSep = newlineAfterComma; } else { newlineBeforeSep = this.newlineBeforeSep; newlineAfterSep = this.newlineAfterSep; } if (itemCount == 0) { if (newlineAfterOpen) { newlineAndIndent(currentIndent); } } else { if (newlineBeforeSep) { newlineAndIndent(currentIndent + sepIndent); } } if ((itemCount > 0) || printFirst) { keyword(sep); nextWhitespace = newlineAfterSep ? NL : " "; } ++itemCount; } }; default: case OTHER: return new FrameImpl(frameType, keyword, open, close, left, indentation, chopColumn, lineFolding, newlineAfterOpen, newlineBeforeSep, sepIndent, newlineAfterSep, newlineBeforeClose, false); } } private SqlWriterConfig.LineFolding fold(FrameTypeEnum frameType) { switch (frameType) { case SELECT_LIST: return f3(config.selectFolding(), config.lineFolding(), config.selectListItemsOnSeparateLines()); case GROUP_BY_LIST: return f3(config.groupByFolding(), config.lineFolding(), config.selectListItemsOnSeparateLines()); case ORDER_BY_LIST: return f3(config.orderByFolding(), config.lineFolding(), config.selectListItemsOnSeparateLines()); case UPDATE_SET_LIST: return f3(config.updateSetFolding(), config.lineFolding(), config.updateSetListNewline()); case WHERE_LIST: return f3(config.whereFolding(), config.lineFolding(), config.whereListItemsOnSeparateLines()); case WINDOW_DECL_LIST: return f3(config.windowFolding(), config.lineFolding(), config.clauseStartsLine() && config.windowDeclListNewline()); case WINDOW: return f3(config.overFolding(), config.lineFolding(), config.windowNewline()); case VALUES: return f3(config.valuesFolding(), config.lineFolding(), config.valuesListNewline()); case FROM_LIST: case JOIN: return f3(config.fromFolding(), config.lineFolding(), config.caseClausesOnNewLines()); case CASE: return f3(null, null, config.caseClausesOnNewLines()); default: return SqlWriterConfig.LineFolding.WIDE; } } private static SqlWriterConfig.LineFolding f3(SqlWriterConfig.@Nullable LineFolding folding0, SqlWriterConfig.@Nullable LineFolding folding1, boolean opt) { return folding0 != null ? folding0 : folding1 != null ? folding1 : opt ? SqlWriterConfig.LineFolding.TALL : SqlWriterConfig.LineFolding.WIDE; } /** * Starts a list. * * @param frameType Type of list. For example, a SELECT list will be * governed according to SELECT-list formatting preferences. * @param open String to print at the start of the list; typically "(" or * the empty string. * @param close String to print at the end of the list. */ protected Frame startList( FrameType frameType, @Nullable String keyword, String open, String close) { assert frameType != null; FrameImpl frame = this.frame; if (frame != null) { if (frame.itemCount++ == 0 && frame.newlineAfterOpen) { newlineAndIndent(); } else if (frameType == FrameTypeEnum.SUB_QUERY && config.subQueryStyle() == SubQueryStyle.BLACK) { newlineAndIndent(currentIndent - frame.extraIndent); } // REVIEW jvs 9-June-2006: This is part of the fix for FRG-149 // (extra frame for identifier was leading to extra indentation, // causing select list to come out raggedy with identifiers // deeper than literals); are there other frame types // for which extra indent should be suppressed? currentIndent += frame.extraIndent(frameType); assert !listStack.contains(frame); listStack.push(frame); } frame = createListFrame(frameType, keyword, open, close); this.frame = frame; frame.before(); return frame; } @Override public void endList(@Nullable Frame frame) { FrameImpl endedFrame = (FrameImpl) frame; Preconditions.checkArgument(frame == this.frame, "Frame does not match current frame"); if (endedFrame == null) { throw new RuntimeException("No list started"); } if (endedFrame.open.equals("(")) { if (!endedFrame.close.equals(")")) { throw new RuntimeException("Expected ')'"); } } if (endedFrame.newlineBeforeClose) { newlineAndIndent(); } keyword(endedFrame.close); if (endedFrame.newlineAfterClose) { newlineAndIndent(); } // Pop the frame, and move to the previous indentation level. if (listStack.isEmpty()) { this.frame = null; assert currentIndent == 0 : currentIndent; } else { this.frame = listStack.pop(); currentIndent -= this.frame.extraIndent(endedFrame.frameType); } } public String format(SqlNode node) { assert frame == null; node.unparse(this, 0, 0); assert frame == null; return toString(); } @Override public String toString() { return buf.toString(); } @Override public SqlString toSqlString() { ImmutableList dynamicParameters = this.dynamicParameters == null ? null : this.dynamicParameters.build(); return new SqlString(dialect, toString(), dynamicParameters); } @Override public SqlDialect getDialect() { return dialect; } @Override public void literal(String s) { print(s); setNeedWhitespace(true); } @Override public void keyword(String s) { maybeWhitespace(s); buf.append( isKeywordsLowerCase() ? s.toLowerCase(Locale.ROOT) : s.toUpperCase(Locale.ROOT)); if (!s.equals("")) { setNeedWhitespace(needWhitespaceAfter(s)); } } private void maybeWhitespace(String s) { if (tooLong(s) || (needWhitespace && needWhitespaceBefore(s))) { whiteSpace(); } } private static boolean needWhitespaceBefore(String s) { return !(s.equals(",") || s.equals(".") || s.equals(")") || s.equals("[") || s.equals("]") || s.equals("")); } private static boolean needWhitespaceAfter(String s) { return !(s.equals("(") || s.equals("[") || s.equals(".")); } protected void whiteSpace() { if (needWhitespace) { if (NL.equals(nextWhitespace)) { newlineAndIndent(); } else { buf.append(nextWhitespace); } nextWhitespace = " "; setNeedWhitespace(false); } } /** Returns the number of characters appended since the last newline. */ private int column() { return buf.length() - lineStart; } protected boolean tooLong(String s) { final int lineLength = config.lineLength(); boolean result = lineLength > 0 && (column() > currentIndent) && ((column() + s.length()) >= lineLength); if (result) { nextWhitespace = NL; } LOGGER.trace("Token is '{}'; result is {}", s, result); return result; } @Override public void print(String s) { maybeWhitespace(s); buf.append(s); } @Override public void print(int x) { maybeWhitespace("0"); buf.append(x); } @Override public void identifier(String name, boolean quoted) { // If configured globally or the original identifier is quoted, // then quotes the identifier. maybeWhitespace(name); if (isQuoteAllIdentifiers() || quoted) { dialect.quoteIdentifier(buf, name); } else { buf.append(name); } setNeedWhitespace(true); } @Override public void dynamicParam(int index) { if (dynamicParameters == null) { dynamicParameters = ImmutableList.builder(); } dynamicParameters.add(index); print("?"); setNeedWhitespace(true); } @Override public void fetchOffset(@Nullable SqlNode fetch, @Nullable SqlNode offset) { if (fetch == null && offset == null) { return; } dialect.unparseOffsetFetch(this, offset, fetch); } @Override public void topN(@Nullable SqlNode fetch, @Nullable SqlNode offset) { if (fetch == null && offset == null) { return; } dialect.unparseTopN(this, offset, fetch); } @Override public Frame startFunCall(String funName) { keyword(funName); setNeedWhitespace(false); return startList(FrameTypeEnum.FUN_CALL, "(", ")"); } @Override public void endFunCall(Frame frame) { endList(this.frame); } @Override public Frame startList(String open, String close) { return startList(FrameTypeEnum.SIMPLE, null, open, close); } @Override public Frame startList(FrameTypeEnum frameType) { assert frameType != null; return startList(frameType, null, "", ""); } @Override public Frame startList(FrameType frameType, String open, String close) { assert frameType != null; return startList(frameType, null, open, close); } @Override public SqlWriter list(FrameTypeEnum frameType, Consumer action) { final SqlWriter.Frame selectListFrame = startList(SqlWriter.FrameTypeEnum.SELECT_LIST); final SqlWriter w = this; action.accept(w); endList(selectListFrame); return this; } @Override public SqlWriter list(FrameTypeEnum frameType, SqlBinaryOperator sepOp, SqlNodeList list) { final SqlWriter.Frame frame = startList(frameType); ((FrameImpl) frame).list(list, sepOp); endList(frame); return this; } @Override public void sep(String sep) { sep(sep, !(sep.equals(",") || sep.equals("."))); } @Override public void sep(String sep, boolean printFirst) { if (frame == null) { throw new RuntimeException("No list started"); } if (sep.startsWith(" ") || sep.endsWith(" ")) { throw new RuntimeException("Separator must not contain whitespace"); } frame.sep(printFirst, sep); } @Override public void setNeedWhitespace(boolean needWhitespace) { this.needWhitespace = needWhitespace; } @Deprecated public void setLineLength(int lineLength) { this.config = config.withLineLength(lineLength); } public void setFormatOptions(@Nullable SqlFormatOptions options) { if (options == null) { return; } setAlwaysUseParentheses(options.isAlwaysUseParentheses()); setCaseClausesOnNewLines(options.isCaseClausesOnNewLines()); setClauseStartsLine(options.isClauseStartsLine()); setKeywordsLowerCase(options.isKeywordsLowercase()); setQuoteAllIdentifiers(options.isQuoteAllIdentifiers()); setSelectListItemsOnSeparateLines( options.isSelectListItemsOnSeparateLines()); setWhereListItemsOnSeparateLines( options.isWhereListItemsOnSeparateLines()); setWindowNewline(options.isWindowDeclarationStartsLine()); setWindowDeclListNewline(options.isWindowListItemsOnSeparateLines()); setIndentation(options.getIndentation()); setLineLength(options.getLineLength()); } //~ Inner Classes ---------------------------------------------------------- /** * Implementation of {@link com.hazelcast.org.apache.calcite.sql.SqlWriter.Frame}. */ protected class FrameImpl implements Frame { final FrameType frameType; final @Nullable String keyword; final String open; final String close; private final int left; /** * Indent of sub-frame with respect to this one. */ final int extraIndent; /** * Indent of separators with respect to this frame's indent. Typically * zero. */ final int sepIndent; /** * Number of items which have been printed in this list so far. */ int itemCount; /** * Whether to print a newline before each separator. */ public final boolean newlineBeforeSep; /** * Whether to print a newline after each separator. */ public final boolean newlineAfterSep; protected final boolean newlineBeforeClose; protected final boolean newlineAfterClose; protected final boolean newlineAfterOpen; /** Character count after which we should move an item to the * next line. Or {@link Integer#MAX_VALUE} if we are not chopping. */ private final int chopLimit; /** How lines are to be folded. */ private final SqlWriterConfig.LineFolding lineFolding; FrameImpl(FrameType frameType, @Nullable String keyword, String open, String close, int left, int extraIndent, int chopLimit, SqlWriterConfig.LineFolding lineFolding, boolean newlineAfterOpen, boolean newlineBeforeSep, int sepIndent, boolean newlineAfterSep, boolean newlineBeforeClose, boolean newlineAfterClose) { this.frameType = frameType; this.keyword = keyword; this.open = open; this.close = close; this.left = left; this.extraIndent = extraIndent; this.chopLimit = chopLimit; this.lineFolding = lineFolding; this.newlineAfterOpen = newlineAfterOpen; this.newlineBeforeSep = newlineBeforeSep; this.newlineAfterSep = newlineAfterSep; this.newlineBeforeClose = newlineBeforeClose; this.newlineAfterClose = newlineAfterClose; this.sepIndent = sepIndent; assert chopLimit >= 0 == (lineFolding == SqlWriterConfig.LineFolding.CHOP || lineFolding == SqlWriterConfig.LineFolding.FOLD || lineFolding == SqlWriterConfig.LineFolding.STEP); } protected void before() { if ((open != null) && !open.equals("")) { keyword(open); } } protected void after() { } protected void sep(boolean printFirst, String sep) { if (itemCount == 0) { if (newlineAfterOpen) { newlineAndIndent(currentIndent); } } else { if (newlineBeforeSep) { newlineAndIndent(currentIndent + sepIndent); } } if ((itemCount > 0) || printFirst) { keyword(sep); nextWhitespace = newlineAfterSep ? NL : " "; } ++itemCount; } /** Returns the extra indent required for a given type of sub-frame. */ int extraIndent(FrameType subFrameType) { if (this.frameType == FrameTypeEnum.ORDER_BY && subFrameType == FrameTypeEnum.ORDER_BY_LIST) { return config.indentation(); } if (subFrameType.needsIndent()) { return extraIndent; } return 0; } void list(SqlNodeList list, SqlBinaryOperator sepOp) { final Save save; switch (lineFolding) { case CHOP: case FOLD: save = new Save(); if (!list2(list, sepOp)) { save.restore(); final boolean newlineAfterOpen = config.clauseEndsLine(); final SqlWriterConfig.LineFolding lineFolding; final int chopLimit; if (this.lineFolding == SqlWriterConfig.LineFolding.CHOP) { lineFolding = SqlWriterConfig.LineFolding.TALL; chopLimit = -1; } else { lineFolding = SqlWriterConfig.LineFolding.FOLD; chopLimit = this.chopLimit; } final boolean newline = lineFolding == SqlWriterConfig.LineFolding.TALL; final boolean newlineBeforeSep; final boolean newlineAfterSep; final int sepIndent; if (config.leadingComma() && newline) { newlineBeforeSep = true; newlineAfterSep = false; sepIndent = -", ".length(); } else if (newline) { newlineBeforeSep = false; newlineAfterSep = true; sepIndent = this.sepIndent; } else { newlineBeforeSep = false; newlineAfterSep = false; sepIndent = this.sepIndent; } final FrameImpl frame2 = new FrameImpl(frameType, keyword, open, close, left, extraIndent, chopLimit, lineFolding, newlineAfterOpen, newlineBeforeSep, sepIndent, newlineAfterSep, newlineBeforeClose, newlineAfterClose); frame2.list2(list, sepOp); } break; default: list2(list, sepOp); } } /** Tries to write a list. If the line runs too long, returns false, * indicating to retry. */ private boolean list2(SqlNodeList list, SqlBinaryOperator sepOp) { // The precedence pulling on the LHS of a node is the // right-precedence of the separator operator. Similarly RHS. // // At the start and end of the list precedence should be 0, but non-zero // precedence is useful, because it forces parentheses around // sub-queries and empty lists, e.g. "SELECT a, (SELECT * FROM t), b", // "GROUP BY ()". final int lprec = sepOp.getRightPrec(); final int rprec = sepOp.getLeftPrec(); if (chopLimit < 0) { for (int i = 0; i < list.size(); i++) { SqlNode node = list.get(i); sep(false, sepOp.getName()); node.unparse(SqlPrettyWriter.this, lprec, rprec); } } else if (newlineBeforeSep) { for (int i = 0; i < list.size(); i++) { SqlNode node = list.get(i); sep(false, sepOp.getName()); final Save prevSize = new Save(); node.unparse(SqlPrettyWriter.this, lprec, rprec); if (column() > chopLimit) { if (lineFolding == SqlWriterConfig.LineFolding.CHOP || lineFolding == SqlWriterConfig.LineFolding.TALL) { return false; } prevSize.restore(); newlineAndIndent(); node.unparse(SqlPrettyWriter.this, lprec, rprec); } } } else { for (int i = 0; i < list.size(); i++) { SqlNode node = list.get(i); if (i == 0) { sep(false, sepOp.getName()); } final Save save = new Save(); node.unparse(SqlPrettyWriter.this, lprec, rprec); if (i + 1 < list.size()) { sep(false, sepOp.getName()); } if (column() > chopLimit) { switch (lineFolding) { case CHOP: return false; case FOLD: if (newlineAfterOpen != config.clauseEndsLine()) { return false; } break; default: break; } save.restore(); newlineAndIndent(); node.unparse(SqlPrettyWriter.this, lprec, rprec); if (i + 1 < list.size()) { sep(false, sepOp.getName()); } } } } return true; } /** Remembers the state of the current frame and writer. * *

You can call {@link #restore} to restore to that state, or just * continue. It is useful if you wish to re-try with different options * (for example, with lines wrapped). */ class Save { final int bufLength; Save() { this.bufLength = buf.length(); } void restore() { buf.setLength(bufLength); } } } /** * Helper class which exposes the get/set methods of an object as * properties. */ private static class Bean { private final SqlPrettyWriter o; private final Map getterMethods = new HashMap<>(); private final Map setterMethods = new HashMap<>(); Bean(SqlPrettyWriter o) { this.o = o; // Figure out the getter/setter methods for each attribute. for (Method method : o.getClass().getMethods()) { if (method.getName().startsWith("set") && (method.getReturnType() == Void.class) && (method.getParameterTypes().length == 1)) { String attributeName = stripPrefix( method.getName(), 3); setterMethods.put(attributeName, method); } if (method.getName().startsWith("get") && (method.getReturnType() != Void.class) && (method.getParameterTypes().length == 0)) { String attributeName = stripPrefix( method.getName(), 3); getterMethods.put(attributeName, method); } if (method.getName().startsWith("is") && (method.getReturnType() == Boolean.class) && (method.getParameterTypes().length == 0)) { String attributeName = stripPrefix( method.getName(), 2); getterMethods.put(attributeName, method); } } } private static String stripPrefix(String name, int offset) { return name.substring(offset, offset + 1).toLowerCase(Locale.ROOT) + name.substring(offset + 1); } public void set(String name, String value) { final Method method = requireNonNull( setterMethods.get(name), () -> "setter method " + name + " not found" ); try { method.invoke(o, value); } catch (IllegalAccessException | InvocationTargetException e) { throw Util.throwAsRuntime(Util.causeOrSelf(e)); } } public @Nullable Object get(String name) { final Method method = requireNonNull( getterMethods.get(name), () -> "getter method " + name + " not found" ); try { return method.invoke(o); } catch (IllegalAccessException | InvocationTargetException e) { throw Util.throwAsRuntime(Util.causeOrSelf(e)); } } public String[] getPropertyNames() { final Set names = new HashSet<>(); names.addAll(getterMethods.keySet()); names.addAll(setterMethods.keySet()); return names.toArray(new String[0]); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy