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

net.java.ao.Query Maven / Gradle / Ivy

Go to download

This is the full Active Objects library, if you don't know which one to use, you probably want this one.

The newest version!
/*
 * Copyright 2007 Daniel Spiewak
 *
 * 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 net.java.ao;

import net.java.ao.schema.TableNameConverter;
import net.java.ao.schema.info.EntityInfo;
import net.java.ao.schema.info.FieldInfo;
import net.java.ao.types.TypeInfo;
import net.java.ao.types.TypeManager;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.google.common.collect.Maps.newHashMap;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

/**
 * A cross-database representation of a SELECT query, independent of database provider type.
 * 

* Use with {@link net.java.ao.DatabaseProvider#renderQuery(Query, net.java.ao.schema.TableNameConverter, boolean)} * to produce the database-specific SQL. * * @author Daniel Spiewak */ public class Query implements Serializable { public enum QueryType { SELECT } private final static String PRIMARY_KEY_FIELD = "''''primary_key_field''''"; protected static final Pattern ALIAS_PATTERN = Pattern.compile("(.+?)\\s+(?:[aA][sS]\\s+)?(\\w+)$"); protected static final Pattern AGGREGATE_FUNCTION_PATTERN = Pattern.compile("(\\S+)\\((\\S+)\\)"); private final QueryType type; private String fields; private boolean distinct = false; private Class> tableType; private String table; private String whereClause; private Object[] whereParams; private String orderClause; private String groupClause; private String havingClause; private int limit = -1; private int offset = -1; private Map>, String> joins; private Map>, String> aliases = newHashMap(); /** * Create a {@link Query} and set the field list in the {@code SELECT} clause. * * @param fields The fields to select, as comma-delimited field names. Spaces are OK. Must not contain {@code "*"}. * @throws java.lang.IllegalArgumentException if fields contains {@code "*"} */ public Query(QueryType type, String fields) { validateSelectFields(fields); this.type = type; this.fields = fields; joins = new LinkedHashMap>, String>(); } public Iterable getFields() { return getFields(Function.identity()); } public List getFieldMetadata() { return getFields(this::getSingleFieldMetadata); } private List getFields(Function resultMapper) { if (fields.contains(PRIMARY_KEY_FIELD)) { return Collections.emptyList(); } else { return Arrays.stream(fields.split(",")) .map(String::trim) .map(resultMapper) .collect(toList()); } } void setFields(String[] fields) { if (fields.length == 0) { return; } StringBuilder builder = new StringBuilder(); for (String field : fields) { builder.append(field).append(','); } if (fields.length > 1) { builder.setLength(builder.length() - 1); } this.fields = builder.toString(); } void resolvePrimaryKey(FieldInfo fieldInfo) { fields = fields.replaceAll(PRIMARY_KEY_FIELD, fieldInfo.getName()); } public Query distinct() { distinct = true; return this; } public Query from(Class> tableType) { table = null; this.tableType = tableType; return this; } public Query from(String table) { tableType = null; this.table = table; return this; } public Query where(String clause, Object... params) { whereClause = clause; setWhereParams(params); return this; } public Query order(String clause) { orderClause = clause; return this; } public Query group(String clause) { groupClause = clause; return this; } public Query having(String clause) { havingClause = clause; return this; } public Query limit(int limit) { this.limit = limit; return this; } public Query offset(int offset) { this.offset = offset; return this; } public Query alias(Class> table, String alias) { if (aliases.containsValue(alias)) { throw new ActiveObjectsException("There is already a table aliased '" + alias + "' for this query!"); } aliases.put(table, alias); return this; } public String getAlias(Class> table) { return aliases.get(table); } public Query join(Class> join, String on) { joins.put(join, on); return this; } public Query join(Class> join) { joins.put(join, null); return this; } public boolean isDistinct() { return distinct; } public void setDistinct(boolean distinct) { this.distinct = distinct; } public Class> getTableType() { return tableType; } public void setTableType(Class> tableType) { this.tableType = tableType; } public String getTable() { return table; } public void setTable(String table) { this.table = table; } public String getWhereClause() { return whereClause; } public void setWhereClause(String whereClause) { this.whereClause = whereClause; } public Object[] getWhereParams() { return whereParams; } public void setWhereParams(Object[] whereParams) { this.whereParams = whereParams; if (whereParams != null) { for (int i = 0; i < whereParams.length; i++) { if (whereParams[i] instanceof RawEntity) { whereParams[i] = Common.getPrimaryKeyValue((RawEntity) whereParams[i]); } } } } public String getOrderClause() { return orderClause; } public void setOrderClause(String orderClause) { this.orderClause = orderClause; } public String getGroupClause() { return groupClause; } public void setGroupClause(String groupClause) { this.groupClause = groupClause; } public String getHavingClause() { return havingClause; } public void setHavingClause(String havingClause) { this.havingClause = havingClause; } public int getLimit() { return limit; } public void setLimit(int limit) { this.limit = limit; } public int getOffset() { return offset; } public void setOffset(int offset) { this.offset = offset; } public Map>, String> getJoins() { return Collections.unmodifiableMap(joins); } public void setJoins(Map>, String> joins) { this.joins = joins; } public QueryType getType() { return type; } public String[] getCanonicalFields(EntityInfo entityInfo) { String[] back = fields.split(","); List result = new ArrayList(); for (String fieldName : back) { final String trimmedFieldName = fieldName.trim(); Optional alias = getAlias(trimmedFieldName); if (alias.isPresent()) { result.add(alias.get()); } else if (trimmedFieldName.equals("*")) { for (FieldInfo fieldInfo : entityInfo.getFields()) { result.add(fieldInfo.getName()); } } else { result.add(trimmedFieldName); } } return result.toArray(new String[result.size()]); } protected Optional getAlias(final String field) { Matcher matcher = ALIAS_PATTERN.matcher(field); if (matcher.find()) { return Optional.of(matcher.group(2)); } return Optional.empty(); } protected FieldMetadata getSingleFieldMetadata(final String field) { final Matcher aliasMatcher = ALIAS_PATTERN.matcher(field); final String maybeAlias; final String actualFieldAndMaybeAggregate; if (aliasMatcher.find()) { maybeAlias = aliasMatcher.group(2); actualFieldAndMaybeAggregate = aliasMatcher.group(1); } else { maybeAlias = null; actualFieldAndMaybeAggregate = field; } final Matcher aggregateMatcher = AGGREGATE_FUNCTION_PATTERN.matcher(actualFieldAndMaybeAggregate); final String maybeAggregateFunction; final String actualField; if (aggregateMatcher.find()) { actualField = aggregateMatcher.group(2); maybeAggregateFunction = aggregateMatcher.group(1); } else { actualField = actualFieldAndMaybeAggregate; maybeAggregateFunction = null; } return new FieldMetadata(actualField, maybeAlias, maybeAggregateFunction); } protected String toSQL(EntityInfo, K> entityInfo, DatabaseProvider provider, TableNameConverter converter, boolean count) { if (this.tableType == null && table == null) { this.tableType = entityInfo.getEntityType(); } resolvePrimaryKey(entityInfo.getPrimaryKey()); return provider.renderQuery(this, converter, count); } @SuppressWarnings("unchecked") protected void setParameters(EntityManager manager, PreparedStatement stmt) throws SQLException { if (whereParams != null) { final TypeManager typeManager = manager.getProvider().getTypeManager(); for (int i = 0; i < whereParams.length; i++) { if (whereParams[i] == null) { manager.getProvider().putNull(stmt, i + 1); } else { Class javaType = whereParams[i].getClass(); if (whereParams[i] instanceof RawEntity) { javaType = ((RawEntity) whereParams[i]).getEntityType(); } TypeInfo typeInfo = typeManager.getType(javaType); typeInfo.getLogicalType().putToDatabase(manager, stmt, i + 1, whereParams[i], typeInfo.getJdbcWriteType()); } } } } /** * Create a {@link Query} which will select the primary key field of the entity. * * @return non-null Query */ public static Query select() { return select(PRIMARY_KEY_FIELD); } /** * Create a {@link Query} and set the field list in the {@code SELECT} clause. * * @param fields The fields to select, as comma-delimited field names. Spaces are OK. Must not contain {@code "*"}. * @return non-null Query * @throws java.lang.IllegalArgumentException if fields contains {@code "*"} */ public static Query select(String fields) { return new Query(QueryType.SELECT, fields); } /* * @throws java.lang.IllegalArgumentException if fields contains {@code "*"} */ private static void validateSelectFields(String fields) { if (fields == null) { return; } // Assuming we won't have a "legitimate" '*' in a quoted field name if (fields.contains("*")) { throw new IllegalArgumentException("fields must not contain '*' - got '" + fields + "'"); } } public static class FieldMetadata { private final String columnName; private final String alias; private final String aggregateFunction; public FieldMetadata(@Nonnull String columnName, @Nullable String alias, @Nullable String aggregateFunction) { this.columnName = requireNonNull(columnName); this.alias = alias; this.aggregateFunction = aggregateFunction; } public String getColumnName() { return columnName; } public Optional getAlias() { return Optional.ofNullable(alias); } public Optional getAggregateFunction() { return Optional.ofNullable(aggregateFunction); } public String renderField(boolean quoteColumnName, boolean quoteAlias, String quoteCharacter) { final StringBuilder builder = new StringBuilder(); if (aggregateFunction != null) { builder.append(aggregateFunction); builder.append("("); }; if (quoteColumnName) { builder.append(quoteCharacter); } builder.append(Common.shorten(columnName, Integer.MAX_VALUE)); if (quoteColumnName) { builder.append(quoteCharacter); } if (aggregateFunction != null) { builder.append(")"); }; if (alias != null) { builder.append(" as "); if (quoteAlias) { builder.append(quoteCharacter); } builder.append(alias); if (quoteAlias) { builder.append(quoteCharacter); } }; return builder.toString(); } } }