com.jaregu.database.queries.compiling.EntityFieldsFeature Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of queries Show documentation
Show all versions of queries Show documentation
Java based SQL templating project. Store your queries in *.sql files and build queries for execution. Supports simple expressions and conditional clauses and interface proxying for java-sql bridge.
package com.jaregu.database.queries.compiling;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.jaregu.database.queries.building.ParametersResolver;
import com.jaregu.database.queries.common.StringSplitter;
import com.jaregu.database.queries.compiling.expr.Expression;
import com.jaregu.database.queries.compiling.expr.Expression.ExpressionResult;
import com.jaregu.database.queries.compiling.expr.ExpressionParser;
import com.jaregu.database.queries.parsing.CommentType;
import com.jaregu.database.queries.parsing.ParsedQueryPart;
/**
* Entity fields builder feature, used like part in SQL:
*
*
*
* Example SQL:
*
*
* [SQL] SELECT -- entityFieldGenerator(template = 'column'; entityClass = 'some.package.SomeTable'; alias = 'st')
* FROM some_table st [SQL]
*
*/
final class EntityFieldsFeature implements QueryCompilerFeature {
enum ParameterType {
template(true), entityClass(true), excludeColumns(false), alias(false);
private final boolean required;
private ParameterType(boolean required) {
this.required = required;
}
public boolean isRequired() {
return required;
}
}
enum TemplateType {
column(cf -> {
return Collections.singletonList(ParsedQueryPart.create(cf.getColumn()));
}, 1),
value((cf) -> {
return Collections.singletonList(ParsedQueryPart.create(":" + cf.getField()));
}, 1),
columnAndValue((cf) -> {
return Arrays.asList(ParsedQueryPart.create(cf.getColumn() + " = "),
ParsedQueryPart.create(":" + cf.getField()));
}, 2);
private final Function> template;
private final int size;
private TemplateType(Function> template, int size) {
this.template = template;
this.size = size;
}
public List eval(ColumnField columnField) {
return template.apply(columnField);
}
public int getSeedMultiplicator() {
return size;
}
}
interface ColumnField {
String getColumn();
String getField();
}
private static final String FEATURE_NAME = "entityFieldGenerator";
final private ExpressionParser expressionParser;
final private Map> entities;
EntityFieldsFeature(ExpressionParser expressionParser, Map> entities) {
this.expressionParser = expressionParser;
this.entities = entities;
}
@Override
public boolean isCompilable(Source source) {
List parts = source.getParts();
return parts.size() == 1 && isJpaFunctionPart(parts.get(0));
}
private boolean isJpaFunctionPart(ParsedQueryPart testPart) {
return testPart.isComment() && testPart.getCommentContent().startsWith(FEATURE_NAME)
&& expressionParser.isLikeExpression(getParametersExpression(testPart));
}
private String getParametersExpression(ParsedQueryPart testPart) {
return testPart.getCommentContent().substring(FEATURE_NAME.length());
}
@Override
public Result compile(Source source, Compiler compiler) {
List sourceParts = source.getParts();
ParsedQueryPart generatorCommentPart = sourceParts.get(0);
Map parameterByType = extractParameters(
expressionParser.parse(getParametersExpression(generatorCommentPart)));
checkRequiredParameters(parameterByType);
Class> entityClass = extractEntity(parameterByType);
TemplateType templateType = parseTemplateType(parameterByType.get(ParameterType.template));
Optional alias = Optional.ofNullable(parameterByType.get(ParameterType.alias));
Set excludedColumns = getExcludedColumns(parameterByType);
List fields = new EntityFieldsBuilder(entityClass, excludedColumns, alias)
.build();
checkHasAtLeastOneField(fields);
List fieldParts = new ArrayList<>(
fields.size() * templateType.getSeedMultiplicator() + fields.size() + 1);
Iterator fieldsIterator = fields.iterator();
fieldParts.addAll(templateType.eval(fieldsIterator.next()));
while (fieldsIterator.hasNext()) {
fieldParts.add(ParsedQueryPart.create(", "));
fieldParts.addAll(templateType.eval(fieldsIterator.next()));
}
if (generatorCommentPart.getCommentType() == CommentType.HYPHENS) {
fieldParts.add(ParsedQueryPart.create("\n"));
}
List children = compiler.compile(Source.of(fieldParts)).getParts();
return new Result() {
@Override
public List getParts() {
return Collections.singletonList(new EnityFieldsPart(children));
}
};
}
private Map extractParameters(List parameters) {
Map parameterByType = new HashMap<>(ParameterType.values().length);
for (Expression expression : parameters) {
// first implementations is static, params must be constants
ExpressionResult result = expression.eval(ParametersResolver.empty());
for (Entry paramEntry : result.getOutputVariables().entrySet()) {
if (paramEntry.getValue() == null || !(paramEntry.getValue() instanceof String)) {
throw new QueryCompileException(
"Entity fields generation feature expects all parameters to be String constants!");
}
String value = (String) paramEntry.getValue();
ParameterType parameterType = parseParameterType(paramEntry.getKey());
if (parameterByType.containsKey(parameterType)) {
throw new QueryCompileException(
"Entity fields generation feature syntax error: Multiple '" + parameterType
+ "' parameters!");
}
parameterByType.put(parameterType, value);
}
}
return parameterByType;
}
private ParameterType parseParameterType(String value) {
try {
return ParameterType.valueOf(value);
} catch (RuntimeException e) {
throw new QueryCompileException(
"Entity fields generation feature template expects only parameters named: "
+ Arrays.asList(ParameterType.values()) + "!");
}
}
private void checkRequiredParameters(Map parameterByType) {
for (ParameterType parameterType : ParameterType.values()) {
if (parameterType.isRequired() && !parameterByType.containsKey(parameterType)) {
throw new QueryCompileException(
"Entity fields generation feature syntax error: Rquired parameter '" + parameterType
+ "' is not supplied!");
}
}
}
private Class> extractEntity(Map parameterByType) {
try {
String entityAliasOrClass = parameterByType.get(ParameterType.entityClass);
if (this.entities.containsKey(entityAliasOrClass)) {
return entities.get(entityAliasOrClass);
} else {
return Class.forName(entityAliasOrClass);
}
} catch (ClassNotFoundException e) {
throw new QueryCompileException(
"Entity fields generation feature syntax error: Entity class '"
+ parameterByType.get(ParameterType.entityClass)
+ "' not found!");
}
}
private TemplateType parseTemplateType(String value) {
try {
return TemplateType.valueOf(value);
} catch (RuntimeException e) {
throw new QueryCompileException(
"Entity fields generation feature template parameter expected values are one of: "
+ Arrays.asList(TemplateType.values()) + "!");
}
}
private Set getExcludedColumns(Map parameterByType) {
List excluded = Optional
.ofNullable(parameterByType.get(ParameterType.excludeColumns))
.map(cols -> StringSplitter.on(',').split(cols))
.orElse(Collections.emptyList());
Set excludedColumns = excluded.stream()
.map(String::trim)
.collect(Collectors.toSet());
return excludedColumns;
}
private void checkHasAtLeastOneField(List fields) {
if (fields.isEmpty())
throw new QueryCompileException(
"Entity fields generation feature - entity doesn't contain any fields marked with @Column annotation!");
}
private static final class EnityFieldsPart implements PreparedQueryPart {
final private List children;
public EnityFieldsPart(List children) {
this.children = children;
}
@Override
public Result build(ParametersResolver resolver) {
StringBuilder sql = new StringBuilder();
List