// Copyright 2012 Google Inc. All Rights Reserved. // // 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.google.api.ads.dfp.lib.utils; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Maps; import java.util.Collections; import java.util.Map; /** * {@code QueryBuilder} allows for queries to be constructed in parts. * * @param <V> the type of the API Value */ public class QueryBuilder<V> implements QueryBuilderInterface<V> { protected static final String SELECT = "SELECT"; protected static final String FROM = "FROM"; protected static final String WHERE = "WHERE"; protected static final String LIMIT = "LIMIT"; protected static final String OFFSET = "OFFSET"; protected static final String ORDER_BY = "ORDER BY"; protected String select; protected String from; protected String where; protected Integer limit = null; protected Integer offset = null; protected String orderBy; protected Map<String, V> valueMap; /** * Constructs a query builder. */ public QueryBuilder() { valueMap = Maps.newHashMap(); } /** * Removes the {@code keyword} from the {@code clause} if present. Will * remove {@code keyword + " "}. * @param clause the clause to remove from * @param keyword the keyword to remove * @return a new string with the keyword + " " removed */ static String removeKeyword(String clause, String keyword) { keyword = keyword + " "; if (clause.regionMatches(true, 0, keyword, 0, keyword.length())) { return clause.substring(keyword.length()); } return clause; } /** * Sets the statement SELECT clause in the form of "a,b" or "*". * Only necessary for statements being sent to the * {@code PublisherQueryLanguageService}. The "SELECT " keyword will be * ignored. * * @param columns the statement select clause without "SELECT" * @return a reference to this object */ @Override public QueryBuilder<V> select(String columns) { Preconditions.checkNotNull(columns, "SELECT clause cannot be null"); columns = removeKeyword(columns, SELECT); this.select = columns; return this; } /** * Sets the statement FROM clause in the form of "table". * Only necessary for statements being sent to the * {@code PublisherQueryLanguageService}. The "FROM " keyword will be * ignored. * * @param table the statement from clause without "FROM" * @return a reference to this object */ @Override public QueryBuilder<V> from(String table) { Preconditions.checkNotNull(table, "FROM clause cannot be null"); table = removeKeyword(table, FROM); this.from = table; return this; } /** * Sets the statement WHERE clause in the form of<br><br> * <code>"WHERE <condition> {[AND | OR] <condition> ...}"</code> * <br><br> * e.g. "a = b OR b = c". The "WHERE " keyword will be ignored. * @param conditions the statement query without "WHERE" * @return a reference to this object */ @Override public QueryBuilder<V> where(String conditions) { Preconditions.checkNotNull(conditions, "WHERE clause cannot be null"); conditions = removeKeyword(conditions, WHERE); this.where = conditions; return this; } /** * Sets the statement LIMIT clause in the form of<br><br> * <code>"LIMIT <count>"</code> * <br><br> * e.g. 1000. * @param count the statement limit * @return a reference to this object */ @Override public QueryBuilder<V> limit(Integer count) { this.limit = count; return this; } /** * Sets the statement OFFSET clause in the form of<br><br> * <code>"OFFSET <count>"</code> * <br><br> * e.g. 200. * @param count the statement offset * @return a reference to this object */ @Override public QueryBuilder<V> offset(Integer count) { this.offset = count; return this; } /** * Increases the offset by the {@code amount}. * @param amount the amount to increase the offset * @return a reference to this object */ @Override public QueryBuilder<V> increaseOffsetBy(Integer amount) { if (offset == null) { offset = 0; } this.offset += amount; return this; } /** * Gets the current offset. * @return the current offset */ @Override public Integer getOffset() { return this.offset; } /** * Removes the limit and offset from the query. * @return a reference to this object */ @Override public QueryBuilder<V> removeLimitAndOffset() { offset = null; limit = null; return this; } /** * Sets the statement ORDER BY clause in the form of<br><br> * <code>"ORDER BY <property> [ASC | DESC]"</code> * <br><br> * e.g. "type ASC, lastModifiedDateTime DESC". The "ORDER BY " keyword will be * ignored. * @param orderBy the statement order by without "ORDER BY" * @return a reference to this object */ @Override public QueryBuilder<V> orderBy(String orderBy) { Preconditions.checkNotNull(orderBy, "ORDER BY clause cannot be null"); orderBy = removeKeyword(orderBy, ORDER_BY); this.orderBy = orderBy; return this; } /** * Adds a value to the statement in the form of a {@code Value}. * * @param key the value key * @param value the value * @return a reference to this object */ @Override public QueryBuilder<V> withBindVariableValue(String key, V value) { valueMap.put(key, value); return this; } /** * Adds all key value mappings. * * @param values the mappings of key to value of type <V> * @return a reference to this object */ public QueryBuilder<V> withBindVariableValues(Map<String, V> values) { valueMap.putAll(values); return this; } /** * Returns an unmodifiable form of the key to value map. */ @Override public Map<String, V> getBindVariableMap() { return Collections.unmodifiableMap(valueMap); } /** * Checks that the query is valid. */ protected void validateQuery() { Preconditions.checkState(limit != null || (limit == null && offset == null), "OFFSET cannot be set if LIMIT is not set."); } /** * Builds the query from the clauses. * @return the c query */ @Override public String buildQuery() { validateQuery(); StringBuilder stringBuilder = new StringBuilder(); if (!Strings.isNullOrEmpty(select)) { stringBuilder = stringBuilder.append(SELECT).append(" ").append(select).append(" "); } if (!Strings.isNullOrEmpty(from)) { stringBuilder = stringBuilder.append(FROM).append(" ").append(from).append(" "); } if (!Strings.isNullOrEmpty(where)) { stringBuilder = stringBuilder.append(WHERE).append(" ").append(where).append(" "); } if (!Strings.isNullOrEmpty(orderBy)) { stringBuilder = stringBuilder.append(ORDER_BY).append(" ").append(orderBy).append(" "); } if (limit != null) { stringBuilder = stringBuilder.append(LIMIT).append(" ").append(limit).append(" "); } if (offset != null) { stringBuilder = stringBuilder.append(OFFSET).append(" ").append(offset).append(" "); } return stringBuilder.toString().trim(); } }