/* * Copyright 2017 requery.io * * 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 io.requery.sql; import io.requery.meta.Attribute; import io.requery.meta.Type; import io.requery.query.Expression; import io.requery.query.ExpressionType; import io.requery.util.function.Function; import javax.annotation.Nonnull; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Set; /** * String Builder for creating SQL statements. * * @author Nikhil Purushe */ public class QueryBuilder implements CharSequence { public interface Appender<T> { void append(QueryBuilder qb, T value); } public static class Options { private final String quotedIdentifier; private final Function<String, String> tableTransformer; private final Function<String, String> columnTransformer; private final boolean lowercaseKeywords; private final boolean quoteTableNames; private final boolean quoteColumnNames; public Options(String quotedIdentifier, boolean lowercaseKeywords, Function<String, String> tableTransformer, Function<String, String> columnTransformer, boolean quoteTableNames, boolean quoteColumnNames) { if (quotedIdentifier.equals(" ")) { quotedIdentifier = "\""; } this.quotedIdentifier = quotedIdentifier; this.tableTransformer = tableTransformer; this.columnTransformer = columnTransformer; this.lowercaseKeywords = lowercaseKeywords; this.quoteTableNames = quoteTableNames; this.quoteColumnNames = quoteColumnNames; } } private final StringBuilder sb; private final Options options; public QueryBuilder(Options options) { this.options = options; this.sb = new StringBuilder(32); } @Override @Nonnull public String toString() { return sb.toString(); } @Override public int length() { return sb.length(); } @Override public char charAt(int index) { return sb.charAt(index); } @Override public CharSequence subSequence(int start, int end) { return sb.subSequence(start, end); } public QueryBuilder keyword(Keyword... keywords) { for (Keyword keyword : keywords) { sb.append(options.lowercaseKeywords ? keyword.toString().toLowerCase(Locale.ROOT) : keyword); sb.append(" "); } return this; } public QueryBuilder appendIdentifier(String value, String identifier) { return append(identifier, false).append(value, false).append(identifier); } public QueryBuilder appendQuoted(String value) { return appendIdentifier(value, "\'"); } public QueryBuilder tableName(Object value) { String name = value.toString(); if (options.tableTransformer != null) { name = options.tableTransformer.apply(name); } if (options.quoteTableNames) { appendIdentifier(name, options.quotedIdentifier); } else { append(name); } return space(); } public QueryBuilder tableNames(Iterable<Expression<?>> values) { Set<Type<?>> types = new LinkedHashSet<>(); for (Expression<?> expression : values) { if (expression.getExpressionType() == ExpressionType.ATTRIBUTE) { Attribute attribute = (Attribute) expression; types.add(attribute.getDeclaringType()); } } return commaSeparated(types, new Appender<Type<?>>() { @Override public void append(QueryBuilder qb, Type<?> value) { tableName(value.getName()); } }); } public QueryBuilder attribute(Attribute value) { String name = options.columnTransformer == null ? value.getName() : options.columnTransformer.apply(value.getName()); if(options.quoteColumnNames) { appendIdentifier(name, options.quotedIdentifier); } else { append(name); } return space(); } public QueryBuilder aliasAttribute(String alias, Attribute value) { append(alias); append("."); return attribute(value); } public QueryBuilder append(Object value) { return append(value, false); } public QueryBuilder value(Object value) { return append(value, true); } public QueryBuilder append(Object value, boolean space) { if (value == null) { keyword(Keyword.NULL); } else if (value instanceof String[]) { commaSeparated(Arrays.asList((String[]) value)); } else { if (value instanceof Keyword) { sb.append(options.lowercaseKeywords ? value.toString().toLowerCase(Locale.ROOT) : value.toString()); } else { sb.append(value.toString()); } } if (space) { sb.append(" "); } return this; } public <T> QueryBuilder appendWhereConditions(Set<Attribute<T, ?>> attributes) { int index = 0; for (Attribute attribute : attributes) { if (index > 0) { keyword(Keyword.AND); space(); } attribute(attribute); space(); append("=?"); space(); index++; } return this; } public QueryBuilder commaSeparatedExpressions(Iterable<Expression<?>> values) { return commaSeparated(values, new QueryBuilder.Appender<Expression<?>>() { @Override public void append(QueryBuilder qb, Expression<?> value) { switch (value.getExpressionType()) { case ATTRIBUTE: qb.attribute((Attribute) value); break; default: qb.append(value.getName()).space(); break; } } }); } public QueryBuilder commaSeparatedAttributes(Iterable<? extends Attribute<?, ?>> values) { return commaSeparated(values, new QueryBuilder.Appender<Attribute<?, ?>>() { @Override public void append(QueryBuilder qb, Attribute<?, ?> value) { qb.attribute(value); } }); } public <T> QueryBuilder commaSeparated(Iterable<? extends T> values) { return commaSeparated(values, null); } public <T> QueryBuilder commaSeparated(Iterable<? extends T> values, Appender<T> appender) { return commaSeparated(values.iterator(), appender); } public <T> QueryBuilder commaSeparated(Iterator<? extends T> values, Appender<T> appender) { int i = 0; while (values.hasNext()) { T value = values.next(); if (i > 0) { comma(); } if (appender == null) { append(value); } else { appender.append(this, value); } i++; } return this; } public QueryBuilder openParenthesis() { sb.append("("); return this; } public QueryBuilder closeParenthesis() { if (sb.charAt(sb.length() - 1) == ' ') { sb.setCharAt(sb.length() - 1, ')'); } else { sb.append(')'); } return this; } public QueryBuilder space() { if (sb.charAt(sb.length() - 1) != ' ') { sb.append(" "); } return this; } public QueryBuilder comma() { if (sb.charAt(sb.length() - 1) == ' ') { sb.setCharAt(sb.length() - 1, ','); } else { sb.append(','); } space(); return this; } }