| /******************************************************************************* |
| * Copyright (c) 2010 BSI Business Systems Integration AG. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * BSI Business Systems Integration AG - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.scout.rt.server.services.common.jdbc.builder; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.scout.commons.ClassIdentifier; |
| import org.eclipse.scout.commons.ListUtility; |
| import org.eclipse.scout.commons.StringUtility; |
| import org.eclipse.scout.commons.StringUtility.ITagProcessor; |
| import org.eclipse.scout.commons.exception.ProcessingException; |
| import org.eclipse.scout.commons.holders.NVPair; |
| import org.eclipse.scout.commons.logger.IScoutLogger; |
| import org.eclipse.scout.commons.logger.ScoutLogManager; |
| import org.eclipse.scout.commons.parsers.BindModel; |
| import org.eclipse.scout.commons.parsers.BindParser; |
| import org.eclipse.scout.commons.parsers.token.IToken; |
| import org.eclipse.scout.commons.parsers.token.ValueInputToken; |
| import org.eclipse.scout.rt.server.services.common.jdbc.style.ISqlStyle; |
| import org.eclipse.scout.rt.shared.data.form.AbstractFormData; |
| import org.eclipse.scout.rt.shared.data.form.fields.AbstractFormFieldData; |
| import org.eclipse.scout.rt.shared.data.form.fields.composer.ComposerAttributeNodeData; |
| import org.eclipse.scout.rt.shared.data.form.fields.composer.ComposerEitherOrNodeData; |
| import org.eclipse.scout.rt.shared.data.form.fields.composer.ComposerEntityNodeData; |
| import org.eclipse.scout.rt.shared.data.form.fields.treefield.AbstractTreeFieldData; |
| import org.eclipse.scout.rt.shared.data.form.fields.treefield.TreeNodeData; |
| import org.eclipse.scout.rt.shared.data.model.AttributePath; |
| import org.eclipse.scout.rt.shared.data.model.DataModelConstants; |
| import org.eclipse.scout.rt.shared.data.model.DataModelUtility; |
| import org.eclipse.scout.rt.shared.data.model.EntityPath; |
| import org.eclipse.scout.rt.shared.data.model.IDataModel; |
| import org.eclipse.scout.rt.shared.data.model.IDataModelAttribute; |
| import org.eclipse.scout.rt.shared.data.model.IDataModelEntity; |
| |
| /** |
| * <pre> |
| * Usage: |
| * <ul> |
| * <li>call {@link #setDataModelEntityDefinition(Class, String, boolean)}, {@link #setDataModelAttributeDefinition(Class, String, boolean)} and {@link #addStatementMapping(Class, String, int, int, boolean)} |
| * for all member classes in the FormData</li> |
| * <li>call {@link #build(AbstractFormData)}</li> |
| * <li>add {@link #getWhereConstraints()} to the base sql statement (starts with an AND)</li> |
| * <li>add {@link #getBindMap()} to the sql bind bases</li> |
| * </pre> |
| * <p> |
| * The method {@link #buildComposerEntityNode(ComposerEntityNodeData)} corrects composer trees for correct handling of |
| * zero-traversing aggregation attributes and normal attributes using |
| * {@link #isZeroTraversingAttribute(ComposerAttributeNodeData)}.<br> |
| * An attribute is zero-traversing when it contains 0 and therefore null/non-existence together with the operator <, |
| * >, <=, >=, =, !=, <>, between. Only numeric attributes can be zero-traversing. Dates never are. |
| * <p> |
| * Examples of zero-traversing: |
| * <ul> |
| * <li>Count(Person) < 3</li> |
| * <li>priority between -10 and 10</li> |
| * <li>Sum(payment) <= 1'000'000</li> |
| * <li></li> |
| * </ul> |
| * <p> |
| * Examples of <b>not</b> zero-traversing: |
| * <ul> |
| * <li>Count(Person) between 2 and 4</li> |
| * <li>priority between 1 and 5</li> |
| * <li>Sum(payment) >= 1'000'000</li> |
| * <li></li> |
| * </ul> |
| * <p> |
| * When an entity e contains zero-traversing <b>aggregation</b> attributes (such as Count(.), Sum(.)) z1..zn and |
| * non-zero-traversing attributes a1..an it is splittet into 2 entities as follows:<br> |
| * <code> |
| * <pre>either ( |
| * e |
| * a1..an |
| * z1..zn |
| * ) |
| * or NOT ( |
| * e |
| * a1..an |
| * ) |
| * </pre> |
| * </code> |
| * <p> |
| * In sql this would be something like<br> |
| * <code> |
| * <pre>exists (select 1 from Person ... where a1 and z1 groupy by ... having a2 and z2) |
| * </pre> |
| * </code> will be transformed to <code> |
| * <pre> |
| * ( |
| * exists (select 1 from Person ... where a1 and z1 groupy by ... having a2 and z2) |
| * OR NOT |
| * exists (select 1 from Person ... where a1 groupy by ... having a2) |
| * ) |
| * </pre> |
| * </code> |
| * <p> |
| * Zero-traversing non aggregation attributes are simply wrapped using NLV(attribute). |
| * <p> |
| * That way non-existent matches are added to the result, which matches the expected behaviour. |
| * |
| * @author imo |
| * @deprecated. Will be removed in the M-Release. |
| */ |
| @Deprecated |
| public class FormDataStatementBuilder implements DataModelConstants { |
| private static final IScoutLogger LOG = ScoutLogManager.getLogger(FormDataStatementBuilder.class); |
| private static final Pattern PLAIN_ATTRIBUTE_PATTERN = Pattern.compile("(<attribute>)([a-zA-Z_][a-zA-Z0-9_]*)(</attribute>)"); |
| |
| /** |
| * Strategy used in |
| * {@link DataModelAttributePartDefinition#createInstance(FormDataStatementBuilder, ComposerAttributeNodeData, AttributeStrategy, String, List, List, Map)} |
| */ |
| public static enum AttributeStrategy { |
| /** |
| * Assuming the constraint "SALARY >= 1000" and the attribute statement |
| * |
| * <pre> |
| * <attribute>@Person@.SALARY</attribute> |
| * AND ACTIVE=1 |
| * </pre> |
| * |
| * this strategy only creates the contraint of the attribute part |
| * |
| * <pre> |
| * {@link EntityContribution#getWhereParts()} = SALARY>=1000 |
| * </pre> |
| */ |
| BuildConstraintOfAttribute, |
| /** |
| * Assuming the constraint "SALARY >= 1000" and the attribute statement |
| * |
| * <pre> |
| * <attribute>@Person@.SALARY</attribute> |
| * AND ACTIVE=1 |
| * </pre> |
| * |
| * this strategy only creates the contraint of the context (excluding the attribute) |
| * |
| * <pre> |
| * {@link EntityContribution#getWhereParts()} = ACTIVE=1 |
| * </pre> |
| */ |
| BuildConstraintOfContext, |
| /** |
| * Assuming the constraint "SALARY >= 1000" and the attribute statement |
| * |
| * <pre> |
| * <attribute>@Person@.SALARY</attribute> |
| * AND ACTIVE=1 |
| * </pre> |
| * |
| * this strategy creates the contraint of the context and the attribute |
| * |
| * <pre> |
| * {@link EntityContribution#getWhereParts()} = SALARY>=1000 AND ACTIVE=1 |
| * </pre> |
| */ |
| BuildConstraintOfAttributeWithContext, |
| /** |
| * Assuming the query "SALARY" and the attribute statement |
| * |
| * <pre> |
| * <attribute>@Person@.SALARY</attribute> |
| * AND ACTIVE=1 |
| * </pre> |
| * |
| * this strategy creates the select query part of the attribute and adds constraints for the context |
| * |
| * <pre> |
| * {@link EntityContribution#getSelectParts()} = SALARY |
| * {@link EntityContribution#getWhereParts()} = ACTIVE=1 |
| * </pre> |
| */ |
| BuildQueryOfAttributeAndConstraintOfContext, |
| } |
| |
| /** |
| * Strategy used in |
| * {@link DataModelEntityPartDefinition#createInstance(FormDataStatementBuilder, ComposerEntityNodeData, EntityStrategy, String, Map)} |
| */ |
| public static enum EntityStrategy { |
| BuildConstraints, |
| BuildQuery, |
| } |
| |
| public static enum AttributeKind { |
| /** |
| * no attribute node |
| */ |
| Undefined, |
| NonAggregation, |
| Aggregation, |
| NonAggregationNonZeroTraversing, |
| AggregationNonZeroTraversing, |
| } |
| |
| private ISqlStyle m_sqlStyle; |
| private IDataModel m_dataModel; |
| private AliasMapper m_aliasMapper; |
| private Map<Class<?>, DataModelAttributePartDefinition> m_dataModelAttMap; |
| private Map<Class<?>, DataModelEntityPartDefinition> m_dataModelEntMap; |
| private List<BasicPartDefinition> m_basicDefs; |
| private Map<String, Object> m_bindMap; |
| private AtomicInteger m_sequenceProvider; |
| private StringBuffer m_where; |
| private List<IFormDataStatementBuilderInjection> m_formDataStatementBuilderInjections; |
| |
| /** |
| * @param sqlStyle |
| */ |
| public FormDataStatementBuilder(ISqlStyle sqlStyle) { |
| m_sqlStyle = sqlStyle; |
| m_aliasMapper = new AliasMapper(); |
| m_bindMap = new HashMap<String, Object>(); |
| m_dataModelAttMap = new HashMap<Class<?>, DataModelAttributePartDefinition>(); |
| m_dataModelEntMap = new HashMap<Class<?>, DataModelEntityPartDefinition>(); |
| m_basicDefs = new ArrayList<BasicPartDefinition>(); |
| setSequenceProvider(new AtomicInteger(0)); |
| } |
| |
| public IDataModel getDataModel() { |
| return m_dataModel; |
| } |
| |
| /** |
| * @return true to consume child contributions by this entity. |
| * Default returns true. If the entity is a 1:1 or 1:0 relation to its base and its sql contribution is just a |
| * join clause or similar, this method must return false to let the parent entity colelct all parts. Use |
| * <code>return {@link IDataModelEntity#isOneToMany()}</code> when such behaviour is implemented. |
| */ |
| protected boolean isConsumeChildContributions(EntityPath ePath) { |
| return true; |
| //return ePath.lastElement().isOneToMany(); |
| } |
| |
| /** |
| * add an injection that allows to manipulate every call to |
| * {@link #buildComposerAttributeNode(ComposerAttributeNodeData, AttributeStrategy)} and |
| * {@link #buildComposerEntityNodeContribution(ComposerEntityNodeData, EntityStrategy)} |
| */ |
| public void addFormDataStatementBuilderInjection(IFormDataStatementBuilderInjection j) { |
| if (j == null) { |
| return; |
| } |
| if (m_formDataStatementBuilderInjections == null) { |
| m_formDataStatementBuilderInjections = new ArrayList<IFormDataStatementBuilderInjection>(1); |
| } |
| m_formDataStatementBuilderInjections.add(j); |
| } |
| |
| public void removeFormDataStatementBuilderInjection(IFormDataStatementBuilderInjection j) { |
| if (j == null) { |
| return; |
| } |
| if (m_formDataStatementBuilderInjections != null) { |
| m_formDataStatementBuilderInjections.remove(j); |
| if (m_formDataStatementBuilderInjections.isEmpty()) { |
| m_formDataStatementBuilderInjections = null; |
| } |
| } |
| } |
| |
| protected boolean hasInjections() { |
| return (m_formDataStatementBuilderInjections != null && !m_formDataStatementBuilderInjections.isEmpty()); |
| } |
| |
| protected void injectPreBuildEntity(ComposerEntityNodeData node, EntityStrategy entityStrategy, EntityContribution childContrib) { |
| if (m_formDataStatementBuilderInjections != null) { |
| for (IFormDataStatementBuilderInjection j : m_formDataStatementBuilderInjections) { |
| j.preBuildEntity(node, entityStrategy, childContrib); |
| } |
| } |
| } |
| |
| protected void injectPostBuildEntity(ComposerEntityNodeData node, EntityStrategy entityStrategy, EntityContribution parentContrib) { |
| if (m_formDataStatementBuilderInjections != null) { |
| for (IFormDataStatementBuilderInjection j : m_formDataStatementBuilderInjections) { |
| j.postBuildEntity(node, entityStrategy, parentContrib); |
| } |
| } |
| } |
| |
| protected void injectPostBuildAttribute(ComposerAttributeNodeData node, AttributeStrategy attributeStrategy, EntityContribution contrib) { |
| if (m_formDataStatementBuilderInjections != null) { |
| for (IFormDataStatementBuilderInjection j : m_formDataStatementBuilderInjections) { |
| j.postBuildAttribute(node, attributeStrategy, contrib); |
| } |
| } |
| } |
| |
| public void setDataModel(IDataModel dataModel) { |
| m_dataModel = dataModel; |
| } |
| |
| /** |
| * @returns the reference to the sequence provider to be used outside for additional sequenced items or sub statemet |
| * builders |
| */ |
| public AtomicInteger getSequenceProvider() { |
| return m_sequenceProvider; |
| } |
| |
| /** |
| * use another sequence provider (counts 0,1,2... for aliases) |
| */ |
| public void setSequenceProvider(AtomicInteger sequenceProvider) { |
| m_sequenceProvider = sequenceProvider; |
| m_aliasMapper.setSequenceProvider(m_sequenceProvider); |
| } |
| |
| /** |
| * Define the statement part for a sql part. For composer attributes and entites use |
| * {@link #setDataModelAttributeDefinition(DataModelAttributePartDefinition)} and |
| * {@link #setDataModelEntityDefinition(DataModelEntityPartDefinition)} |
| * <p> |
| * <b>Number, Date, String, Boolean field</b>:<br> |
| * The sqlAttribute is something like <code>@PERSON@.LAST_NAME</code><br> |
| * When multiple occurrences are simultaneously used, the sqlAttribute may be written as |
| * <code>(<attribute>@PERSON@.ORDER_STATUS</attribute> OR <attribute>@PERSON@.DELIVERY_STATUS</attribute>)</code> |
| * <p> |
| * The operator and aggregationType are required, unless a {@link BasicPartDefinition} is used. |
| */ |
| public void setBasicDefinition(Class<?> fieldType, String sqlAttribute, int operator) { |
| setBasicDefinition(new BasicPartDefinition(fieldType, sqlAttribute, operator)); |
| } |
| |
| /** |
| * see {@link #setBasicDefinition(Class, String, int)} |
| */ |
| public void setBasicDefinition(ClassIdentifier fieldTypeIdentifier, String sqlAttribute, int operator) { |
| setBasicDefinition(new BasicPartDefinition(fieldTypeIdentifier, sqlAttribute, operator)); |
| } |
| |
| /** |
| * see {@link #setBasicDefinition(Class, String, int)} |
| */ |
| public void setBasicDefinition(Class<?> fieldType, String sqlAttribute, int operator, boolean plainBind) { |
| setBasicDefinition(new BasicPartDefinition(fieldType, sqlAttribute, operator, plainBind)); |
| } |
| |
| /** |
| * see {@link #setBasicDefinition(Class, String, int)} |
| */ |
| public void setBasicDefinition(ClassIdentifier fieldTypeIdentifier, String sqlAttribute, int operator, boolean plainBind) { |
| setBasicDefinition(new BasicPartDefinition(fieldTypeIdentifier, sqlAttribute, operator, plainBind)); |
| } |
| |
| /** |
| * see {@link #setBasicDefinition(Class, String, int)} |
| */ |
| public void setBasicDefinition(Class<?>[] fieldTypes, String sqlAttribute, int operator) { |
| setBasicDefinition(new BasicPartDefinition(fieldTypes, sqlAttribute, operator, false)); |
| } |
| |
| /** |
| * see {@link #setBasicDefinition(Class, String, int)} |
| */ |
| public void setBasicDefinition(ClassIdentifier[] fieldTypeIdentifiers, String sqlAttribute, int operator) { |
| setBasicDefinition(new BasicPartDefinition(fieldTypeIdentifiers, sqlAttribute, operator, false)); |
| } |
| |
| /** |
| * see {@link #setBasicDefinition(Class, String, int)} |
| */ |
| public void setBasicDefinition(BasicPartDefinition def) { |
| m_basicDefs.add(def); |
| } |
| |
| /** |
| * @deprecated use setBasicDefinition instead |
| */ |
| @Deprecated |
| public void setValueDefinition(Class<?> fieldType, String sqlAttribute, int operator) { |
| setValueDefinition(new ValuePartDefinition(fieldType, sqlAttribute, operator)); |
| } |
| |
| /** |
| * @deprecated use setBasicDefinition instead |
| */ |
| @Deprecated |
| public void setValueDefinition(ClassIdentifier fieldTypeIdentifier, String sqlAttribute, int operator) { |
| setValueDefinition(new ValuePartDefinition(fieldTypeIdentifier, sqlAttribute, operator)); |
| } |
| |
| /** |
| * @deprecated use setBasicDefinition instead |
| */ |
| @Deprecated |
| public void setValueDefinition(Class<?> fieldType, String sqlAttribute, int operator, boolean plainBind) { |
| setValueDefinition(new ValuePartDefinition(fieldType, sqlAttribute, operator, plainBind)); |
| } |
| |
| /** |
| * @deprecated use setBasicDefinition instead |
| */ |
| @Deprecated |
| public void setValueDefinition(ClassIdentifier fieldTypeIdentifier, String sqlAttribute, int operator, boolean plainBind) { |
| setValueDefinition(new ValuePartDefinition(fieldTypeIdentifier, sqlAttribute, operator, plainBind)); |
| } |
| |
| /** |
| * @deprecated use setBasicDefinition instead |
| */ |
| @Deprecated |
| public void setValueDefinition(Class<?>[] fieldTypes, String sqlAttribute, int operator) { |
| setValueDefinition(new ValuePartDefinition(fieldTypes, sqlAttribute, operator, false)); |
| } |
| |
| /** |
| * @deprecated use setBasicDefinition instead |
| */ |
| @Deprecated |
| public void setValueDefinition(ClassIdentifier[] fieldTypeIdentifiers, String sqlAttribute, int operator) { |
| setValueDefinition(new ValuePartDefinition(fieldTypeIdentifiers, sqlAttribute, operator, false)); |
| } |
| |
| /** |
| * @deprecated use setBasicDefinition instead |
| */ |
| @Deprecated |
| public void setValueDefinition(ValuePartDefinition def) { |
| m_basicDefs.add(def); |
| } |
| |
| /** |
| * <b>Data model attribute</b>:<br> |
| * The sqlAttribute is something like LAST_NAME, STATUS or @PERSON@.LAST_NAME, @PERSON@.STATUS. |
| * |
| * @PERSON@ will be replaced by the parent entitie's generated alias. |
| * <p> |
| * The @PERSON@ prefix is added automatically if missing, but only if the entity where the attribute is |
| * contained has only <b>one</b> alias.<br> |
| * When multiple occurrences are simultaneously used, the sqlAttribute may be written as |
| * <code>(<attribute>ORDER_STATUS</attribute> OR <attribute>DELIVERY_STATUS</attribute>)</code> |
| */ |
| public void setDataModelAttributeDefinition(Class<? extends IDataModelAttribute> attributeType, String sqlAttribute) { |
| setDataModelAttributeDefinition(attributeType, sqlAttribute, false); |
| } |
| |
| /** |
| * see {@link #setDataModelAttributeDefinition(Class, String)} |
| */ |
| public void setDataModelAttributeDefinition(Class<? extends IDataModelAttribute> attributeType, String sqlAttribute, boolean plainBind) { |
| setDataModelAttributeDefinition(new DataModelAttributePartDefinition(attributeType, sqlAttribute, plainBind)); |
| } |
| |
| /** |
| * see {@link #setDataModelAttributeDefinition(Class, String)} |
| */ |
| public void setDataModelAttributeDefinition(Class<? extends IDataModelAttribute> attributeType, String whereClause, String selectClause, boolean plainBind) { |
| setDataModelAttributeDefinition(new DataModelAttributePartDefinition(attributeType, whereClause, selectClause, plainBind)); |
| } |
| |
| /** |
| * see {@link #setDataModelAttributeDefinition(Class, String)} |
| */ |
| public void setDataModelAttributeDefinition(DataModelAttributePartDefinition def) { |
| m_dataModelAttMap.put(def.getAttributeType(), def); |
| } |
| |
| /** |
| * see {@link #setDataModelEntityDefinition(Class, String, String)} |
| */ |
| public void setDataModelEntityDefinition(Class<? extends IDataModelEntity> entityType, String whereClause) { |
| setDataModelEntityDefinition(new DataModelEntityPartDefinition(entityType, whereClause)); |
| } |
| |
| /** |
| * <b>Data model entity</b>:<br> |
| * The whereClause is something like <code><pre> |
| * EXISTS ( |
| * SELECT 1 |
| * FROM PERSON @PERSON@ |
| * WHERE @PERSON@.PERSON_ID=@parent.PERSON@.PERSON_ID |
| * <whereParts/> |
| * <groupBy> |
| * GROUP BY @PERSON@.PERSON_ID |
| * HAVING 1=1 |
| * <havingParts/> |
| * </groupBy> |
| * ) |
| * </pre></code> <br> |
| * The selectClause is something like <code><pre> |
| * ( SELECT < selectParts/> |
| * FROM PERSON @PERSON@ |
| * WHERE @PERSON@.PERSON_ID=@parent.PERSON@.PERSON_ID |
| * <whereParts/> |
| * ) |
| * </pre></code> It is not allowed, that the selectClause contains a <i>UNION</i> because this part is needed for |
| * aggregation too.<br> |
| * The <i>selectParts</i> tag is replaced with all attributes which are selected. |
| * If there are more than one attributes, they are separated by a comma |
| * "<i>attribute1</i> <i>,</i> <i>attribute2</i>".<br> |
| * The <i>whereParts</i> tag is replaced with all attributes contained in the entity that have no aggregation type. |
| * Every attribute contributes a "AND <i>attribute</i> <i>op</i> <i>value</i>" line.<br> |
| * The <i>groupBy</i> tag is only used when there are attributes in the entity that have an aggregation type.<br> |
| * The <i>havingParts</i> tag is replaced with all attributes contained in the entity that have an aggregation type. |
| * Every aggregation attribute contributes a "AND <i>fun</i>(<i>attribute</i>) <i>op</i> <i>value</i>" line.<br> |
| */ |
| |
| public void setDataModelEntityDefinition(Class<? extends IDataModelEntity> entityType, String whereClause, String selectClause) { |
| setDataModelEntityDefinition(new DataModelEntityPartDefinition(entityType, whereClause, selectClause)); |
| } |
| |
| /** |
| * see {@link #setDataModelEntityDefinition(Class, String)} |
| */ |
| public void setDataModelEntityDefinition(DataModelEntityPartDefinition def) { |
| m_dataModelEntMap.put(def.getEntityType(), def); |
| } |
| |
| /** |
| * Convenience for {@link #getAliasMapper()} and {@link AliasMapper#setRootAlias(String, String)} |
| */ |
| public void setRootAlias(String entityName, String alias) { |
| getAliasMapper().setRootAlias(entityName, alias); |
| } |
| |
| protected FormDataStatementBuilderCheck createCheckInstance() { |
| return new FormDataStatementBuilderCheck(this); |
| } |
| |
| public void check(Object o) { |
| FormDataStatementBuilderCheck c = createCheckInstance(); |
| c.check(o); |
| System.out.println(c.toString()); |
| } |
| |
| @SuppressWarnings("cast") |
| public String build(AbstractFormData formData) throws ProcessingException { |
| m_where = new StringBuffer(); |
| // get all formData fields and properties defined directly and indirectly by extending template fields, respectively |
| //build constraints for fields |
| for (BasicPartDefinition def : m_basicDefs) { |
| if (def.accept(formData)) { |
| Map<String, String> parentAliasMap = getAliasMapper().getRootAliases(); |
| EntityContribution contrib = def.createInstance(this, formData, parentAliasMap); |
| String cons = createWhereConstraint(contrib); |
| if (cons != null) { |
| addWhere(" AND " + cons); |
| } |
| } |
| } |
| //build constraints for composer trees |
| Map<Integer, Map<String, AbstractFormFieldData>> fieldsBreathFirstMap = formData.getAllFieldsRec(); |
| for (Map<String, AbstractFormFieldData> map : fieldsBreathFirstMap.values()) { |
| for (AbstractFormFieldData f : map.values()) { |
| if (f.isValueSet()) { |
| if (f instanceof AbstractTreeFieldData) { |
| // composer tree with entity, attribute |
| EntityContribution contrib = buildTreeNodes(((AbstractTreeFieldData) f).getRoots(), EntityStrategy.BuildConstraints, AttributeStrategy.BuildConstraintOfAttributeWithContext); |
| String cons = createWhereConstraint(contrib); |
| if (cons != null) { |
| addWhere(" AND " + cons); |
| } |
| } |
| } |
| } |
| } |
| return getWhereConstraints(); |
| } |
| |
| /** |
| * Creates a select statement by merging the given entity contributions with the given base statement. This builder's |
| * {@link #getWhereConstraints()} are added as well. |
| * |
| * @param stm |
| * base statement with <selectParts/>, <fromParts/>, <whereParts/>, <groupByParts/> |
| * or <havingParts/> place holders. |
| * @param contributions |
| * entity contributions that are used to replace markers in the given base statement. |
| * @return Returns given base statement having all place holders replaced by the given entity contributions. |
| * @throws ProcessingException |
| * @since 3.8.1 |
| */ |
| public String createSelectStatement(String stm, EntityContribution... contributions) throws ProcessingException { |
| EntityContribution mergedContribution = new EntityContribution(); |
| if (contributions != null) { |
| for (EntityContribution c : contributions) { |
| mergedContribution.add(c); |
| } |
| } |
| String where = StringUtility.trim(getWhereConstraints()); |
| if (StringUtility.hasText(where)) { |
| if (where.toUpperCase().startsWith("AND")) { |
| where = where.substring(3); |
| } |
| mergedContribution.getWhereParts().add(where); |
| } |
| return createEntityPart(stm, false, mergedContribution); |
| } |
| |
| /** |
| * do not use or override this method, it is protected for unit test purposes |
| */ |
| protected boolean isZeroTraversingAttribute(int operation, Object[] values) { |
| Number value1 = values != null && values.length > 0 && values[0] instanceof Number ? (Number) values[0] : null; |
| Number value2 = values != null && values.length > 1 && values[1] instanceof Number ? (Number) values[1] : null; |
| switch (operation) { |
| case OPERATOR_EQ: { |
| if (value1 != null) { |
| return value1.longValue() == 0; |
| } |
| break; |
| } |
| case OPERATOR_GE: { |
| if (value1 != null) { |
| return value1.doubleValue() <= 0; |
| } |
| break; |
| } |
| case OPERATOR_GT: { |
| if (value1 != null) { |
| return value1.doubleValue() < 0; |
| } |
| break; |
| } |
| case OPERATOR_LE: { |
| if (value1 != null) { |
| return value1.doubleValue() >= 0; |
| } |
| break; |
| } |
| case OPERATOR_LT: { |
| if (value1 != null) { |
| return value1.doubleValue() > 0; |
| } |
| break; |
| } |
| case OPERATOR_NEQ: { |
| if (value1 != null) { |
| return value1.longValue() != 0; |
| } |
| break; |
| } |
| case OPERATOR_BETWEEN: { |
| if (value1 != null && value2 != null) { |
| return value1.doubleValue() <= 0 && value2.doubleValue() >= 0; |
| } |
| else if (value1 != null) { |
| return value1.doubleValue() <= 0; |
| } |
| else if (value2 != null) { |
| return value2.doubleValue() >= 0; |
| } |
| break; |
| } |
| } |
| return false; |
| } |
| |
| public AliasMapper getAliasMapper() { |
| return m_aliasMapper; |
| } |
| |
| /** |
| * @return the life bind map |
| */ |
| public Map<String, Object> getBindMap() { |
| return m_bindMap; |
| } |
| |
| public ISqlStyle getSqlStyle() { |
| return m_sqlStyle; |
| } |
| |
| /** |
| * Convenience for {@link #getBindMap()}.put(name,value) |
| */ |
| public void addBinds(String[] names, Object[] values) { |
| if (names != null) { |
| for (int i = 0; i < names.length; i++) { |
| addBind(names[i], values[i]); |
| } |
| } |
| } |
| |
| /** |
| * Convenience for {@link #getBindMap()}.put(name,value) |
| */ |
| public void addBind(String name, Object value) { |
| if (name != null && !name.startsWith(ISqlStyle.PLAIN_BIND_MARKER_PREFIX)) { |
| getBindMap().put(name, value); |
| } |
| } |
| |
| /** |
| * add sql part with custom binds the ADD keyword is NOT added (pre-pended) |
| * automatically |
| */ |
| public void addWhere(String sql, NVPair... customBinds) { |
| if (sql != null) { |
| m_where.append(" "); |
| m_where.append(sql); |
| for (NVPair p : customBinds) { |
| addBind(p.getName(), p.getValue()); |
| } |
| } |
| } |
| |
| /** |
| * @deprecated use {@link #getBasicPartDefinitions()} instead |
| */ |
| @Deprecated |
| public List<BasicPartDefinition> getValuePartDefinitions() { |
| return Collections.unmodifiableList(m_basicDefs); |
| } |
| |
| public List<BasicPartDefinition> getBasicPartDefinitions() { |
| return Collections.unmodifiableList(m_basicDefs); |
| } |
| |
| public Map<Class<?>, DataModelAttributePartDefinition> getDataModelAttributePartDefinitions() { |
| return Collections.unmodifiableMap(m_dataModelAttMap); |
| } |
| |
| public Map<Class<?>, DataModelEntityPartDefinition> getDataModelEntityPartDefinitions() { |
| return Collections.unmodifiableMap(m_dataModelEntMap); |
| } |
| |
| public String getWhereConstraints() { |
| return (m_where != null ? m_where.toString() : null); |
| } |
| |
| /** |
| * Replace bind name by unique bind name so that it is not |
| * conflicting with other parts that use the same statement |
| * part and bind name. For example S is replaces by __S123. |
| */ |
| public String localizeBindName(String bindName, String prefix) { |
| if (bindName != null) { |
| String locName = prefix + bindName + getNextBindSeqNo(); |
| return locName; |
| } |
| return null; |
| } |
| |
| /** |
| * Replace bind name in statement |
| */ |
| public String localizeStatement(String stm, String oldBindName, String newBindName) { |
| stm = stm.replaceAll("#" + oldBindName + "#", "#" + newBindName + "#"); |
| stm = stm.replaceAll("\\&" + oldBindName + "\\&", "&" + newBindName + "&"); |
| stm = stm.replaceAll(":" + oldBindName + "([^A-Za-z0-9_])", ":" + newBindName + "$1"); |
| stm = stm.replaceAll(":" + oldBindName + "$", ":" + newBindName); |
| return stm; |
| } |
| |
| protected long getNextBindSeqNo() { |
| return m_sequenceProvider.incrementAndGet(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public static <T extends TreeNodeData> T getParentNodeOfType(TreeNodeData node, Class<T> type) { |
| if (node == null) { |
| return null; |
| } |
| while (node != null) { |
| node = node.getParentNode(); |
| if (node != null && type.isAssignableFrom(node.getClass())) { |
| return (T) node; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * moved to {@link EntityContributionUtility#contributionToConstraintText(EntityContribution)} |
| */ |
| public static String createWhereConstraint(EntityContribution contrib) { |
| return EntityContributionUtility.contributionToConstraintText(contrib); |
| } |
| |
| public AttributeKind getAttributeKind(TreeNodeData node) { |
| if (!(node instanceof ComposerAttributeNodeData)) { |
| return AttributeKind.Undefined; |
| } |
| // |
| ComposerAttributeNodeData attributeNode = (ComposerAttributeNodeData) node; |
| Integer agg = attributeNode.getAggregationType(); |
| if (agg == null || agg == AGGREGATION_NONE) { |
| if (!isZeroTraversingAttribute(attributeNode.getOperator(), attributeNode.getValues())) { |
| return AttributeKind.NonAggregationNonZeroTraversing; |
| } |
| return AttributeKind.NonAggregation; |
| } |
| // |
| if (!isZeroTraversingAttribute(attributeNode.getOperator(), attributeNode.getValues())) { |
| return AttributeKind.AggregationNonZeroTraversing; |
| } |
| return AttributeKind.Aggregation; |
| } |
| |
| /** |
| * @param nodes |
| * @return the complete string of all attribute contributions |
| * @throws ProcessingException |
| */ |
| public EntityContribution buildTreeNodes(List<TreeNodeData> nodes, EntityStrategy entityStrategy, AttributeStrategy attributeStrategy) throws ProcessingException { |
| EntityContribution contrib = new EntityContribution(); |
| int i = 0; |
| while (i < nodes.size()) { |
| if (nodes.get(i) instanceof ComposerEntityNodeData) { |
| EntityContribution subContrib = buildComposerEntityNodeContribution((ComposerEntityNodeData) nodes.get(i), entityStrategy); |
| appendTreeSubContribution(contrib, subContrib, entityStrategy); |
| i++; |
| } |
| else if (nodes.get(i) instanceof ComposerAttributeNodeData) { |
| EntityContribution subContrib = buildComposerAttributeNode((ComposerAttributeNodeData) nodes.get(i), attributeStrategy); |
| appendTreeSubContribution(contrib, subContrib, entityStrategy); |
| i++; |
| } |
| else if (nodes.get(i) instanceof ComposerEitherOrNodeData) { |
| ArrayList<ComposerEitherOrNodeData> orNodes = new ArrayList<ComposerEitherOrNodeData>(); |
| orNodes.add((ComposerEitherOrNodeData) nodes.get(i)); |
| int k = i; |
| while (k + 1 < nodes.size() && (nodes.get(k + 1) instanceof ComposerEitherOrNodeData) && !((ComposerEitherOrNodeData) nodes.get(k + 1)).isBeginOfEitherOr()) { |
| orNodes.add((ComposerEitherOrNodeData) nodes.get(k + 1)); |
| k++; |
| } |
| EntityContribution subContrib = buildComposerOrNodes(orNodes, entityStrategy, attributeStrategy); |
| appendTreeSubContribution(contrib, subContrib, entityStrategy); |
| i = k + 1; |
| } |
| else { |
| EntityContribution subContrib = buildTreeNodes(nodes.get(i).getChildNodes(), entityStrategy, attributeStrategy); |
| appendTreeSubContribution(contrib, subContrib, entityStrategy); |
| } |
| } |
| return contrib; |
| } |
| |
| protected void appendTreeSubContribution(EntityContribution parent, EntityContribution child, EntityStrategy entityStrategy) { |
| switch (entityStrategy) { |
| case BuildConstraints: { |
| EntityContribution whereConstraints = EntityContributionUtility.createConstraintsContribution(child); |
| if (whereConstraints != null) { |
| parent.add(whereConstraints); |
| } |
| break; |
| } |
| default: { |
| if (child != null && !child.isEmpty()) { |
| parent.add(child); |
| } |
| } |
| } |
| } |
| |
| /** |
| * do not use or override this method, it is protected for unit test purposes |
| */ |
| @SuppressWarnings("unchecked") |
| protected EntityContribution buildComposerOrNodes(List<ComposerEitherOrNodeData> nodes, EntityStrategy entityStrategy, AttributeStrategy attributeStrategy) throws ProcessingException { |
| EntityContribution contrib = new EntityContribution(); |
| // check if only one condition |
| StringBuilder buf = new StringBuilder(); |
| int count = 0; |
| for (ComposerEitherOrNodeData node : nodes) { |
| EntityContribution subContrib = buildTreeNodes(node.getChildNodes(), entityStrategy, attributeStrategy); |
| contrib.getFromParts().addAll(subContrib.getFromParts()); |
| if (subContrib.getWhereParts().size() + subContrib.getHavingParts().size() > 0) { |
| if (count > 0) { |
| buf.append(" OR "); |
| if (node.isNegative()) { |
| buf.append(" NOT "); |
| } |
| } |
| buf.append("("); |
| // remove possible outer join signs (+) in where / having constraint |
| // this is necessary because outer joins are not allowed in OR clause |
| // the removal of outer joins does not influence the result set |
| buf.append(ListUtility.format(ListUtility.combine(subContrib.getWhereParts(), subContrib.getHavingParts()), " AND ").replaceAll("\\(\\+\\)", "")); |
| buf.append(")"); |
| count++; |
| } |
| } |
| if (count > 0) { |
| if (count > 1) { |
| buf.insert(0, "("); |
| buf.append(")"); |
| contrib.getWhereParts().add(buf.toString()); |
| } |
| else { |
| String s = buf.toString(); |
| if (s.matches("\\(.*\\)")) { |
| s = s.substring(1, s.length() - 1).trim(); |
| } |
| contrib.getWhereParts().add(s); |
| } |
| } |
| return contrib; |
| } |
| |
| /** |
| * @deprecated use {@link #buildComposerEntityNodeContribution(ComposerEntityNodeData, EntityStrategy)} instead |
| */ |
| @Deprecated |
| public String buildComposerEntityNode(ComposerEntityNodeData node, EntityStrategy entityStrategy) throws ProcessingException { |
| EntityContribution contrib = buildComposerEntityNodeContribution(node, entityStrategy); |
| switch (entityStrategy) { |
| case BuildConstraints: { |
| if (contrib.getWhereParts().size() > 0) { |
| return contrib.getWhereParts().get(0); |
| } |
| break; |
| } |
| case BuildQuery: { |
| if (contrib.getSelectParts().size() > 0) { |
| return contrib.getSelectParts().get(0); |
| } |
| break; |
| } |
| } |
| return null; |
| } |
| |
| public EntityContribution buildComposerEntityNodeContribution(ComposerEntityNodeData node, EntityStrategy entityStrategy) throws ProcessingException { |
| if (getDataModel() == null) { |
| throw new ProcessingException("there is no data model set, call FormDataStatementBuilder.setDataModel to set one"); |
| } |
| EntityPath entityPath = DataModelUtility.externalIdToEntityPath(getDataModel(), node.getEntityExternalId()); |
| IDataModelEntity entity = (entityPath != null ? entityPath.lastElement() : null); |
| if (entity == null) { |
| LOG.warn("no entity for external id: " + node.getEntityExternalId()); |
| return null; |
| } |
| DataModelEntityPartDefinition def = m_dataModelEntMap.get(entity.getClass()); |
| if (def == null) { |
| LOG.warn("no PartDefinition for entity: " + entity); |
| return null; |
| } |
| ComposerEntityNodeData parentEntityNode = getParentNodeOfType(node, ComposerEntityNodeData.class); |
| Map<String, String> parentAliasMap = (parentEntityNode != null ? m_aliasMapper.getNodeAliases(parentEntityNode) : m_aliasMapper.getRootAliases()); |
| String baseStm; |
| switch (entityStrategy) { |
| case BuildQuery: { |
| baseStm = def.getSelectClause(); |
| break; |
| } |
| case BuildConstraints: { |
| baseStm = def.getWhereClause(); |
| break; |
| } |
| default: { |
| baseStm = null; |
| } |
| } |
| String stm = null; |
| if (baseStm != null) { |
| stm = def.createInstance(this, node, entityStrategy, baseStm, parentAliasMap); |
| } |
| if (stm == null) { |
| return null; |
| } |
| m_aliasMapper.addAllNodeEntitiesFrom(node, stm); |
| stm = m_aliasMapper.replaceMarkersByAliases(stm, m_aliasMapper.getNodeAliases(node), parentAliasMap); |
| switch (entityStrategy) { |
| case BuildQuery: { |
| EntityContribution resultContrib = buildComposerEntityUnitContribution(node, entityStrategy, stm, node.getChildNodes(), isConsumeChildContributions(entityPath)); |
| return resultContrib; |
| } |
| case BuildConstraints: { |
| String s = buildComposerEntityEitherOrSplit(entityStrategy, stm, node.isNegative(), node.getChildNodes()); |
| EntityContribution resultContrib = (s != null ? EntityContribution.create(s) : new EntityContribution()); |
| return resultContrib; |
| } |
| default: { |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * only used with strategy {@link EntityStrategy#BuildConstraints} |
| * <p> |
| * do not use or override this method, it is protected for unit test purposes |
| */ |
| protected String buildComposerEntityEitherOrSplit(EntityStrategy entityStrategy, String baseStm, boolean negative, List<TreeNodeData> childParts) throws ProcessingException { |
| if (entityStrategy != EntityStrategy.BuildConstraints) { |
| return null; |
| } |
| List<List<ComposerEitherOrNodeData>> orBlocks = new ArrayList<List<ComposerEitherOrNodeData>>(); |
| List<TreeNodeData> otherParts = new ArrayList<TreeNodeData>(); |
| List<ComposerEitherOrNodeData> currentOrBlock = new ArrayList<ComposerEitherOrNodeData>(); |
| for (TreeNodeData ch : childParts) { |
| if (ch instanceof ComposerEitherOrNodeData) { |
| ComposerEitherOrNodeData orData = (ComposerEitherOrNodeData) ch; |
| if (orData.isBeginOfEitherOr()) { |
| if (currentOrBlock.size() > 0) { |
| orBlocks.add(new ArrayList<ComposerEitherOrNodeData>(currentOrBlock)); |
| } |
| currentOrBlock.clear(); |
| } |
| currentOrBlock.add(orData); |
| } |
| else { |
| otherParts.add(ch); |
| } |
| } |
| if (currentOrBlock.size() > 0) { |
| orBlocks.add(new ArrayList<ComposerEitherOrNodeData>(currentOrBlock)); |
| currentOrBlock.clear(); |
| } |
| // |
| if (orBlocks.size() > 0) { |
| StringBuilder blockBuf = new StringBuilder(); |
| int blockCount = 0; |
| for (List<ComposerEitherOrNodeData> list : orBlocks) { |
| int elemCount = 0; |
| StringBuilder elemBuf = new StringBuilder(); |
| for (ComposerEitherOrNodeData orData : list) { |
| ArrayList<TreeNodeData> subList = new ArrayList<TreeNodeData>(); |
| subList.addAll(otherParts); |
| subList.addAll(orData.getChildNodes()); |
| String s = buildComposerEntityEitherOrSplit(entityStrategy, baseStm, negative ^ orData.isNegative(), subList); |
| if (s != null) { |
| if (elemCount > 0) { |
| elemBuf.append(" OR "); |
| } |
| elemBuf.append(" ( "); |
| elemBuf.append(s); |
| elemBuf.append(" ) "); |
| elemCount++; |
| } |
| } |
| if (elemCount > 0) { |
| if (blockCount > 0) { |
| blockBuf.append(" AND "); |
| } |
| blockBuf.append(" ( "); |
| blockBuf.append(elemBuf.toString()); |
| blockBuf.append(" ) "); |
| blockCount++; |
| } |
| } |
| if (blockCount > 0) { |
| return blockBuf.toString(); |
| } |
| return null; |
| } |
| return buildComposerEntityZeroTraversingSplit(entityStrategy, baseStm, negative, childParts); |
| } |
| |
| /** |
| * only used with strategy {@link EntityStrategy#BuildConstraints} and one-to-many entity relation |
| * <p> |
| * do not use or override this method, it is protected for unit test purposes |
| */ |
| protected String buildComposerEntityZeroTraversingSplit(EntityStrategy entityStrategy, String baseStm, boolean negative, List<TreeNodeData> childParts) throws ProcessingException { |
| if (entityStrategy != EntityStrategy.BuildConstraints) { |
| return null; |
| } |
| ArrayList<TreeNodeData> nonZeroChildren = new ArrayList<TreeNodeData>(2); |
| for (TreeNodeData ch : childParts) { |
| switch (getAttributeKind(ch)) { |
| case Undefined: |
| case NonAggregation/*non-aggregations must not be handled as zero-traversal*/: |
| case NonAggregationNonZeroTraversing: |
| case AggregationNonZeroTraversing: { |
| nonZeroChildren.add(ch); |
| break; |
| } |
| } |
| } |
| // |
| //create entity part 1 |
| String entityPart1 = buildComposerEntityUnit(entityStrategy, baseStm, negative, childParts); |
| //create negated entity part 2 |
| String entityPart2 = null; |
| if (nonZeroChildren.size() < childParts.size()) { |
| // negated negation |
| entityPart2 = buildComposerEntityUnit(entityStrategy, baseStm, !negative, nonZeroChildren); |
| } |
| //combine parts |
| if (entityPart2 != null) { |
| return " ( " + entityPart1 + " OR " + entityPart2 + " ) "; |
| } |
| return entityPart1; |
| } |
| |
| /** |
| * do not use or override this method, it is protected for unit test purposes |
| */ |
| protected EntityContribution buildComposerEntityUnitContribution(ComposerEntityNodeData node, EntityStrategy entityStrategy, String baseStm, List<TreeNodeData> childParts, boolean consumeChildContributions) throws ProcessingException { |
| EntityContribution childContributions = new EntityContribution(); |
| switch (entityStrategy) { |
| case BuildConstraints: { |
| ArrayList<TreeNodeData> nonAggregationParts = new ArrayList<TreeNodeData>(childParts.size()); |
| ArrayList<TreeNodeData> aggregationParts = new ArrayList<TreeNodeData>(2); |
| for (TreeNodeData ch : childParts) { |
| switch (getAttributeKind(ch)) { |
| case Undefined: |
| case NonAggregation: |
| case NonAggregationNonZeroTraversing: { |
| nonAggregationParts.add(ch); |
| break; |
| } |
| case Aggregation: |
| case AggregationNonZeroTraversing: { |
| aggregationParts.add(ch); |
| break; |
| } |
| } |
| } |
| // |
| EntityContribution subContrib = buildTreeNodes(nonAggregationParts, entityStrategy, AttributeStrategy.BuildConstraintOfAttributeWithContext); |
| childContributions.add(subContrib); |
| // |
| subContrib = buildTreeNodes(aggregationParts, entityStrategy, AttributeStrategy.BuildConstraintOfContext); |
| childContributions.add(subContrib); |
| // |
| subContrib = buildTreeNodes(aggregationParts, entityStrategy, AttributeStrategy.BuildConstraintOfAttribute); |
| childContributions.add(subContrib); |
| break; |
| } |
| case BuildQuery: { |
| EntityContribution subContrib = buildTreeNodes(childParts, entityStrategy, AttributeStrategy.BuildQueryOfAttributeAndConstraintOfContext); |
| childContributions.add(subContrib); |
| break; |
| } |
| } |
| //legacy: node may be null from legacy calls |
| if (node != null && hasInjections()) { |
| injectPreBuildEntity(node, entityStrategy, childContributions); |
| } |
| EntityContribution parentContributions = createEntityPart(entityStrategy, baseStm, childContributions, consumeChildContributions); |
| if (node != null && hasInjections()) { |
| injectPostBuildEntity(node, entityStrategy, parentContributions); |
| } |
| return parentContributions; |
| } |
| |
| /** |
| * only used with strategy {@link EntityStrategy#BuildConstraints} and one-to-many entity relation |
| * <p> |
| * do not use or override this method, it is protected for unit test purposes |
| */ |
| protected String buildComposerEntityUnit(EntityStrategy entityStrategy, String baseStm, boolean negative, List<TreeNodeData> childParts) throws ProcessingException { |
| EntityContribution contrib = buildComposerEntityUnitContribution(null, entityStrategy, baseStm, childParts, true); |
| List<String> list = contrib.getWhereParts(); |
| if (list.isEmpty()) { |
| list = contrib.getFromParts(); |
| } |
| if (list.isEmpty()) { |
| list = contrib.getSelectParts(); |
| } |
| if (list.isEmpty()) { |
| return "1=1"; |
| } |
| String s = list.get(0); |
| // negation |
| if (negative) { |
| s = " NOT (" + s + ") "; |
| } |
| return s; |
| } |
| |
| @SuppressWarnings("cast") |
| public EntityContribution buildComposerAttributeNode(final ComposerAttributeNodeData node, AttributeStrategy attributeStrategy) throws ProcessingException { |
| if (getDataModel() == null) { |
| throw new ProcessingException("there is no data model set, call FormDataStatementBuilder.setDataModel to set one"); |
| } |
| AttributePath attPath = DataModelUtility.externalIdToAttributePath(getDataModel(), node.getAttributeExternalId()); |
| IDataModelAttribute attribute = (attPath != null ? attPath.getAttribute() : null); |
| if (attribute == null) { |
| LOG.warn("no attribute for external id: " + node.getAttributeExternalId()); |
| return new EntityContribution(); |
| } |
| DataModelAttributePartDefinition def = m_dataModelAttMap.get(attribute.getClass()); |
| if (def == null) { |
| Integer agg = node.getAggregationType(); |
| if (agg != null && agg == AGGREGATION_COUNT) { |
| def = new DataModelAttributePartDefinition(null, "1", false); |
| } |
| } |
| if (def == null) { |
| LOG.warn("no PartDefinition for attribute: " + attribute); |
| return new EntityContribution(); |
| } |
| List<Object> bindValues = new ArrayList<Object>(); |
| if (node.getValues() != null) { |
| bindValues.addAll(Arrays.asList(node.getValues())); |
| } |
| List<String> bindNames = new ArrayList<String>(bindValues.size()); |
| for (int i = 0; i < bindValues.size(); i++) { |
| bindNames.add("" + (char) (((int) 'a') + i)); |
| } |
| AliasMapper aliasMap = getAliasMapper(); |
| ComposerEntityNodeData parentEntityNode = FormDataStatementBuilder.getParentNodeOfType(node, ComposerEntityNodeData.class); |
| Map<String, String> parentAliasMap = parentEntityNode != null ? aliasMap.getNodeAliases(parentEntityNode) : aliasMap.getRootAliases(); |
| String stm = null; |
| switch (attributeStrategy) { |
| case BuildConstraintOfAttribute: |
| case BuildConstraintOfContext: |
| case BuildConstraintOfAttributeWithContext: { |
| stm = def.getWhereClause(); |
| break; |
| } |
| case BuildQueryOfAttributeAndConstraintOfContext: { |
| stm = def.getSelectClause(); |
| break; |
| } |
| } |
| EntityContribution contrib = null; |
| if (stm != null) { |
| contrib = def.createInstance(this, node, attributeStrategy, stm, bindNames, bindValues, parentAliasMap); |
| } |
| if (contrib == null) { |
| contrib = new EntityContribution(); |
| } |
| switch (attributeStrategy) { |
| case BuildQueryOfAttributeAndConstraintOfContext: { |
| if (contrib.getSelectParts().isEmpty()) { |
| contrib.getSelectParts().add("NULL"); |
| contrib.getGroupByParts().add("NULL"); |
| } |
| break; |
| } |
| } |
| if (hasInjections()) { |
| injectPostBuildAttribute(node, attributeStrategy, contrib); |
| } |
| return contrib; |
| } |
| |
| /** |
| * Evaluates the collecting tags in the entity statement and fills in the values of the {@link EntityContribution}. |
| * If the contributing tags are missing, the complete part is treated as 'select' on {@link EntityStrategy#BuildQuery} |
| * and as 'where' on {@link EntityStrategy#BuildConstraints} |
| * <p> |
| * Default calls {@link EntityContributionUtility#createEntityPart(String, EntityContribution, boolean)} |
| * |
| * @param entityStrategy |
| * @param entityPartWithTags |
| * may contain the collecting tags selectParts, fromParts, whereParts, groupBy, groupByParts, havingParts<br/> |
| * as well as the contributing selectPart, fromPart, wherePart, groupByPart, havingPart for the outer calling |
| * part. |
| * @param childContributions |
| * is the set of tags collected by all children |
| * @param consumeChildContributions |
| * true: consume the child tags inside the entity statement. The returned entity contributions will not |
| * contain any of these tags |
| * <p> |
| * false: don't consume the child tags inside the entity statement. The returned entity contribution contains |
| * its onw plus all of these child tags (proxy) |
| */ |
| public EntityContribution createEntityPart(EntityStrategy entityStrategy, String entityPartWithTags, EntityContribution childContributions, boolean consumeChildContributions) throws ProcessingException { |
| if (consumeChildContributions) { |
| entityPartWithTags = autoCompleteEntityPartTags(entityPartWithTags); |
| } |
| EntityContribution parentContrib = EntityContributionUtility.mergeContributions(entityStrategy, entityPartWithTags, childContributions, consumeChildContributions); |
| return parentContrib; |
| } |
| |
| /** |
| * only used with strategy {@link EntityStrategy#BuildConstraints} |
| * <p> |
| * |
| * @return the statement combined with the contributions |
| */ |
| public String createEntityPart(String stm, boolean negative, EntityContribution childContributions) throws ProcessingException { |
| EntityContribution contrib = createEntityPart(EntityStrategy.BuildConstraints, stm, childContributions, true); |
| List<String> list = contrib.getWhereParts(); |
| if (list.isEmpty()) { |
| list = contrib.getFromParts(); |
| } |
| if (list.isEmpty()) { |
| list = contrib.getSelectParts(); |
| } |
| if (list.isEmpty()) { |
| return "1=1"; |
| } |
| String s = list.get(0); |
| // negation |
| if (negative) { |
| s = " NOT (" + s + ") "; |
| } |
| return s; |
| } |
| |
| /** |
| * @deprecated moved to {@link EntityContributionUtility} |
| */ |
| @Deprecated |
| protected String autoBracketSelectPart(String s) { |
| if (s != null && !s.startsWith("(") && s.toLowerCase().contains("select")) { |
| return "(" + s + ")"; |
| } |
| return s; |
| } |
| |
| protected String autoCompleteEntityPartTags(String s) { |
| if (s == null) { |
| return null; |
| } |
| if (StringUtility.getTag(s, "whereParts") == null) { |
| s = s + " <whereParts/>"; |
| } |
| if (StringUtility.getTag(s, "groupBy") == null) { |
| s = s + " <groupBy/>"; |
| } |
| s = s.replace("<groupBy/>", "<groupBy>GROUP BY <groupByParts/> HAVING 1=1 <havingParts/></groupBy>"); |
| return s; |
| } |
| |
| public static final int STATUS_CODE_INVALID_GROUP_BY_PART = EntityContributionUtility.STATUS_CODE_INVALID_GROUP_BY_PART; |
| |
| /** |
| * Check if a group by part is valid, i.e. ist not a SELECT clause. |
| * default uses {@link EntityContributionUtility#checkGroupByPart(String)} |
| * |
| * @throws ProcessingException |
| * with {@link IStatus#getCode()} = X |
| * @since 3.8 |
| */ |
| protected void checkGroupByPart(String groupByPart) throws ProcessingException { |
| EntityContributionUtility.checkGroupByPart(groupByPart); |
| } |
| |
| /** |
| * adding an attribute as an entity contribution |
| * <p> |
| * Evaluates the tags in the attribute statement and creates an {@link EntityContribution} based on it. |
| * |
| * @param stm |
| * may contain attribute, fromPart and wherePart tags |
| */ |
| public EntityContribution createAttributePart(AttributeStrategy attributeStrategy, Integer aggregationType, String stm, int operation, List<String> bindNames, List<Object> bindValues, final boolean plainBind, Map<String, String> parentAliasMap) throws ProcessingException { |
| if (stm == null) { |
| return new EntityContribution(); |
| } |
| //convenience: automatically wrap attribute in attribute tags |
| if (stm.indexOf("<attribute>") < 0) { |
| stm = "<attribute>" + stm + "</attribute>"; |
| } |
| //convenience: automatically add missing alias on plain attributes, but only if the parent entity has at most 1 alias mapping |
| Matcher m = PLAIN_ATTRIBUTE_PATTERN.matcher(stm); |
| if (m.find()) { |
| if (parentAliasMap.size() == 0) { |
| //nop |
| } |
| else if (parentAliasMap.size() == 1) { |
| stm = m.replaceAll("$1@parent." + parentAliasMap.keySet().iterator().next() + "@.$2$3"); |
| } |
| else { |
| throw new ProcessingException("composer attribute " + stm + " uses no @...@ alias prefix, but parent has more than 1 alias: " + parentAliasMap); |
| } |
| } |
| boolean isAg = (aggregationType != null && aggregationType != AGGREGATION_NONE); |
| EntityContribution contrib = new EntityContribution(); |
| //special handling of NOT: wrap NOT around complete constraint text and not only in attribute operator |
| int positiveOperation; |
| boolean negation; |
| switch (operation) { |
| case OPERATOR_DATE_IS_NOT_TODAY: { |
| positiveOperation = OPERATOR_DATE_IS_TODAY; |
| negation = true; |
| break; |
| } |
| case OPERATOR_DATE_NEQ: { |
| positiveOperation = OPERATOR_DATE_EQ; |
| negation = true; |
| break; |
| } |
| case OPERATOR_DATE_TIME_IS_NOT_NOW: { |
| positiveOperation = OPERATOR_DATE_TIME_IS_NOW; |
| negation = true; |
| break; |
| } |
| case OPERATOR_DATE_TIME_NEQ: { |
| positiveOperation = OPERATOR_DATE_TIME_EQ; |
| negation = true; |
| break; |
| } |
| case OPERATOR_NEQ: { |
| positiveOperation = OPERATOR_EQ; |
| negation = true; |
| break; |
| } |
| case OPERATOR_NOT_CONTAINS: { |
| positiveOperation = OPERATOR_CONTAINS; |
| negation = true; |
| break; |
| } |
| case OPERATOR_NOT_ENDS_WITH: { |
| positiveOperation = OPERATOR_ENDS_WITH; |
| negation = true; |
| break; |
| } |
| case OPERATOR_NOT_IN: { |
| positiveOperation = OPERATOR_IN; |
| negation = true; |
| break; |
| } |
| case OPERATOR_NOT_NULL: { |
| positiveOperation = OPERATOR_NULL; |
| negation = true; |
| break; |
| } |
| case OPERATOR_NOT_STARTS_WITH: { |
| positiveOperation = OPERATOR_STARTS_WITH; |
| negation = true; |
| break; |
| } |
| case OPERATOR_NUMBER_NOT_NULL: { |
| positiveOperation = OPERATOR_NUMBER_NULL; |
| negation = true; |
| break; |
| } |
| case OPERATOR_TIME_IS_NOT_NOW: { |
| positiveOperation = OPERATOR_TIME_IS_NOW; |
| negation = true; |
| break; |
| } |
| default: { |
| positiveOperation = operation; |
| negation = false; |
| } |
| } |
| // |
| String fromPart = StringUtility.getTag(stm, "fromPart"); |
| stm = StringUtility.removeTag(stm, "fromPart").trim(); |
| String wherePart = StringUtility.getTag(stm, "wherePart"); |
| if (wherePart == null) { |
| String tmp = StringUtility.removeTag(stm, "attribute").trim(); |
| if (tmp.length() > 0) { |
| wherePart = stm; |
| stm = ""; |
| } |
| } |
| stm = StringUtility.removeTag(stm, "wherePart").trim(); |
| String attPart = StringUtility.getTag(stm, "attribute"); |
| stm = StringUtility.removeTag(stm, "attribute").trim(); |
| if (stm.length() > 0) { |
| LOG.warn("attribute part is not well-formed; contains wherePart tag and also other sql text: " + stm); |
| } |
| // |
| //from |
| if (fromPart != null) { |
| //resolve aliases in from |
| // mis-using 'contrib' as a "node" because real node is not accessible |
| m_aliasMapper.addMissingNodeEntitiesFrom(contrib, fromPart); |
| Map<String, String> aliasMap = m_aliasMapper.getNodeAliases(contrib); |
| parentAliasMap.putAll(aliasMap); |
| fromPart = m_aliasMapper.replaceMarkersByAliases(fromPart, parentAliasMap, parentAliasMap); |
| contrib.getFromParts().add(fromPart); |
| } |
| switch (attributeStrategy) { |
| //select ... where |
| case BuildQueryOfAttributeAndConstraintOfContext: { |
| //select |
| if (attPart != null) { |
| String sql = createSqlPart(aggregationType, attPart, OPERATOR_NONE, bindNames, bindValues, plainBind, parentAliasMap); |
| if (sql != null) { |
| contrib.getSelectParts().add(sql); |
| if (!isAg) { |
| contrib.getGroupByParts().add(sql); |
| } |
| } |
| } |
| //where |
| if (wherePart != null) { |
| wherePart = StringUtility.replaceTags(wherePart, "attribute", "1=1").trim(); |
| String sql = createSqlPart(wherePart, bindNames, bindValues, plainBind, parentAliasMap); |
| if (sql != null) { |
| contrib.getWhereParts().add(sql); |
| } |
| } |
| break; |
| } |
| //where / having |
| case BuildConstraintOfAttribute: { |
| if (attPart != null) { |
| String sql = createSqlPart(aggregationType, attPart, positiveOperation, bindNames, bindValues, plainBind, parentAliasMap); |
| if (sql != null) { |
| if (negation) { |
| sql = "NOT(" + sql + ")"; |
| } |
| if (isAg) { |
| contrib.getHavingParts().add(sql); |
| } |
| else { |
| contrib.getWhereParts().add(sql); |
| } |
| } |
| } |
| break; |
| } |
| case BuildConstraintOfContext: { |
| if (wherePart != null) { |
| wherePart = StringUtility.replaceTags(wherePart, "attribute", "1=1").trim(); |
| String sql = createSqlPart(wherePart, bindNames, bindValues, plainBind, parentAliasMap); |
| if (sql != null) { |
| contrib.getWhereParts().add(sql); |
| } |
| } |
| break; |
| } |
| case BuildConstraintOfAttributeWithContext: { |
| String whereAndAttPart = (wherePart != null ? wherePart : "") + (wherePart != null && attPart != null ? " AND " : "") + (attPart != null ? "<attribute>" + attPart + "</attribute>" : ""); |
| if (whereAndAttPart.length() > 0) { |
| String sql = createSqlPart(aggregationType, whereAndAttPart, positiveOperation, bindNames, bindValues, plainBind, parentAliasMap); |
| if (sql != null) { |
| if (negation) { |
| sql = "NOT(" + sql + ")"; |
| } |
| contrib.getWhereParts().add(sql); |
| } |
| } |
| break; |
| } |
| } |
| return contrib; |
| } |
| |
| /** |
| * adding an attribute as an entity contribution |
| * <p> |
| * |
| * @param stm |
| * may contain attribute, fromPart and wherePart tags |
| */ |
| public String createAttributePartSimple(AttributeStrategy attributeStrategy, Integer aggregationType, String stm, int operation, List<String> bindNames, List<Object> bindValues, boolean plainBind, Map<String, String> parentAliasMap) throws ProcessingException { |
| EntityContribution contrib = createAttributePart(attributeStrategy, aggregationType, stm, operation, bindNames, bindValues, plainBind, parentAliasMap); |
| if (contrib.isEmpty()) { |
| return null; |
| } |
| return ListUtility.format(contrib.getWhereParts(), " AND "); |
| } |
| |
| /** |
| * Create sql text, makes bind names unique, and adds all binds to the bind map |
| * <p> |
| * Convenience for {@link #createSqlPart(AGGREGATION_NONE, String, OPERATOR_NONE, List, List, boolean, Map)} |
| */ |
| public String createSqlPart(String sql, List<String> bindNames, List<Object> bindValues, final boolean plainBind, Map<String, String> parentAliasMap) throws ProcessingException { |
| return createSqlPart(AGGREGATION_NONE, sql, OPERATOR_NONE, bindNames, bindValues, plainBind, parentAliasMap); |
| } |
| |
| /** |
| * Create sql text, makes bind names unique, and adds all binds to the bind map |
| * <p> |
| * To use no operator use {@link DataModelConstants#OPERATOR_NONE} and null for binds and values, stm will be |
| * decorated and is the result itself |
| * <p> |
| * To use no aggregation use {@link DataModelConstants#AGGREGATION_NONE} |
| */ |
| public String createSqlPart(final Integer aggregationType, String sql, final int operation, List<String> bindNames, List<Object> bindValues, final boolean plainBind, Map<String, String> parentAliasMap) throws ProcessingException { |
| if (sql == null) { |
| sql = ""; |
| } |
| if (bindNames == null) { |
| bindNames = new ArrayList<String>(0); |
| } |
| if (bindValues == null) { |
| bindValues = new ArrayList<Object>(0); |
| } |
| // the attribute was of the form: NAME or |
| // <attribute>NAME</attribute> |
| // make sure there is an attribute tag in the string, if none enclose all |
| // by default |
| if (sql.indexOf("<attribute>") < 0) { |
| sql = "<attribute>" + sql + "</attribute>"; |
| } |
| //convenience: automatically add missing alias on plain attributes, but only if the parent entity has at most 1 alias mapping |
| Matcher m = PLAIN_ATTRIBUTE_PATTERN.matcher(sql); |
| if (m.find()) { |
| if (parentAliasMap.size() == 0) { |
| //nop |
| } |
| else if (parentAliasMap.size() == 1) { |
| sql = m.replaceAll("$1@parent." + parentAliasMap.keySet().iterator().next() + "@.$2$3"); |
| } |
| else { |
| throw new ProcessingException("root attribute with " + sql + " uses no @...@ alias prefix, but parent has more than 1 alias: " + parentAliasMap); |
| } |
| } |
| //resolve aliases |
| sql = m_aliasMapper.replaceMarkersByAliases(sql, parentAliasMap, parentAliasMap); |
| // generate unique bind names |
| final ArrayList<String> newBindNames = new ArrayList<String>(2); |
| for (int i = 0; i < bindNames.size(); i++) { |
| String o = bindNames.get(i); |
| String n = localizeBindName(o, "__"); |
| newBindNames.add(n); |
| sql = localizeStatement(sql, o, n); |
| } |
| // part decoration |
| final List<Object> valuesFinal = bindValues; |
| ITagProcessor processor = new ITagProcessor() { |
| @Override |
| public String processTag(String tagName, String a) { |
| return createSqlOpValuePart(aggregationType, a, operation, newBindNames, valuesFinal, plainBind); |
| } |
| }; |
| return StringUtility.replaceTags(sql, "attribute", processor); |
| } |
| |
| public String createSqlOpValuePart(Integer aggregationType, String sql, int operation, List<String> bindNames, List<Object> bindValues, boolean plainBind) { |
| String[] names = (bindNames != null ? bindNames.toArray(new String[bindNames.size()]) : new String[0]); |
| Object[] values = (bindValues != null ? bindValues.toArray(new Object[bindValues.size()]) : new Object[0]); |
| if (plainBind && operation != OPERATOR_NONE) { |
| //rewrite bindNames by plain values |
| for (int i = 0; i < names.length; i++) { |
| names[i] = ISqlStyle.PLAIN_BIND_MARKER_PREFIX + m_sqlStyle.toPlainText(values[i]); |
| } |
| } |
| // |
| if (aggregationType != null && aggregationType != AGGREGATION_NONE) { |
| switch (aggregationType) { |
| case AGGREGATION_COUNT: { |
| sql = m_sqlStyle.toAggregationCount(sql); |
| break; |
| } |
| case AGGREGATION_MIN: { |
| sql = m_sqlStyle.toAggregationMin(sql); |
| break; |
| } |
| case AGGREGATION_MAX: { |
| sql = m_sqlStyle.toAggregationMax(sql); |
| break; |
| } |
| case AGGREGATION_SUM: { |
| sql = m_sqlStyle.toAggregationSum(sql); |
| break; |
| } |
| case AGGREGATION_AVG: { |
| sql = m_sqlStyle.toAggregationAvg(sql); |
| break; |
| } |
| case AGGREGATION_MEDIAN: { |
| sql = m_sqlStyle.toAggregationMedian(sql); |
| break; |
| } |
| } |
| } |
| else if (isZeroTraversingAttribute(operation, values)) { |
| sql = m_sqlStyle.getNvlToken() + "(" + sql + ",0)"; |
| } |
| // |
| switch (operation) { |
| case OPERATOR_NONE: { |
| if (plainBind) { |
| if (names != null) { |
| HashMap<String, String> tokenValue = new HashMap<String, String>(); |
| for (int i = 0; i < names.length; i++) { |
| tokenValue.put(names[i], m_sqlStyle.toPlainText(values[i])); |
| } |
| BindModel m = new BindParser(sql).parse(); |
| IToken[] tokens = m.getIOTokens(); |
| if (tokens != null) { |
| for (IToken iToken : tokens) { |
| if (iToken instanceof ValueInputToken) { |
| ValueInputToken t = (ValueInputToken) iToken; |
| t.setPlainValue(true); |
| t.setReplaceToken(tokenValue.get(t.getName())); |
| } |
| } |
| } |
| sql = m.getFilteredStatement(); |
| } |
| } |
| else { |
| addBinds(names, values); |
| } |
| return sql; |
| } |
| case OPERATOR_BETWEEN: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| if (values[0] == null) { |
| return m_sqlStyle.createLE(sql, names[1]); |
| } |
| else if (values[1] == null) { |
| return m_sqlStyle.createGE(sql, names[0]); |
| } |
| else { |
| return m_sqlStyle.createBetween(sql, names[0], names[1]); |
| } |
| } |
| case OPERATOR_DATE_BETWEEN: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| if (values[0] == null) { |
| return m_sqlStyle.createDateLE(sql, names[1]); |
| } |
| else if (values[1] == null) { |
| return m_sqlStyle.createDateGE(sql, names[0]); |
| } |
| else { |
| return m_sqlStyle.createDateBetween(sql, names[0], names[1]); |
| } |
| } |
| case OPERATOR_DATE_TIME_BETWEEN: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| if (values[0] == null) { |
| return m_sqlStyle.createDateTimeLE(sql, names[1]); |
| } |
| else if (values[1] == null) { |
| return m_sqlStyle.createDateTimeGE(sql, names[0]); |
| } |
| else { |
| return m_sqlStyle.createDateTimeBetween(sql, names[0], names[1]); |
| } |
| } |
| case OPERATOR_EQ: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createEQ(sql, names[0]); |
| } |
| case OPERATOR_DATE_EQ: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateEQ(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_EQ: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeEQ(sql, names[0]); |
| } |
| case OPERATOR_GE: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createGE(sql, names[0]); |
| } |
| case OPERATOR_DATE_GE: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateGE(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_GE: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeGE(sql, names[0]); |
| } |
| case OPERATOR_GT: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createGT(sql, names[0]); |
| } |
| case OPERATOR_DATE_GT: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateGT(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_GT: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeGT(sql, names[0]); |
| } |
| case OPERATOR_LE: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createLE(sql, names[0]); |
| } |
| case OPERATOR_DATE_LE: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateLE(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_LE: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeLE(sql, names[0]); |
| } |
| case OPERATOR_LT: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createLT(sql, names[0]); |
| } |
| case OPERATOR_DATE_LT: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateLT(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_LT: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeLT(sql, names[0]); |
| } |
| case OPERATOR_NEQ: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createNEQ(sql, names[0]); |
| } |
| case OPERATOR_DATE_NEQ: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateNEQ(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_NEQ: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeNEQ(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_DAYS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInDays(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_GE_DAYS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInGEDays(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_GE_MONTHS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInGEMonths(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_LE_DAYS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInLEDays(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_LE_MONTHS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInLEMonths(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_LAST_DAYS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInLastDays(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_LAST_MONTHS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInLastMonths(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_MONTHS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInMonths(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_NEXT_DAYS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInNextDays(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_IN_NEXT_MONTHS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateIsInNextMonths(sql, names[0]); |
| } |
| case OPERATOR_DATE_IS_NOT_TODAY: { |
| return m_sqlStyle.createDateIsNotToday(sql); |
| } |
| case OPERATOR_DATE_IS_TODAY: { |
| return m_sqlStyle.createDateIsToday(sql); |
| } |
| case OPERATOR_DATE_TIME_IS_IN_GE_HOURS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeIsInGEHours(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_IS_IN_GE_MINUTES: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeIsInGEMinutes(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_IS_IN_LE_HOURS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeIsInLEHours(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_IS_IN_LE_MINUTES: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createDateTimeIsInLEMinutes(sql, names[0]); |
| } |
| case OPERATOR_DATE_TIME_IS_NOT_NOW: { |
| return m_sqlStyle.createDateTimeIsNotNow(sql); |
| } |
| case OPERATOR_DATE_TIME_IS_NOW: { |
| return m_sqlStyle.createDateTimeIsNow(sql); |
| } |
| case OPERATOR_ENDS_WITH: { |
| if (!plainBind) { |
| addBind(names[0], m_sqlStyle.toLikePattern(values[0])); |
| } |
| return m_sqlStyle.createEndsWith(sql, names[0]); |
| } |
| case OPERATOR_NOT_ENDS_WITH: { |
| if (!plainBind) { |
| addBind(names[0], m_sqlStyle.toLikePattern(values[0])); |
| } |
| return m_sqlStyle.createNotEndsWith(sql, names[0]); |
| } |
| case OPERATOR_IN: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| //no support for plain bind in here. otherwise, ArrayInput gets confused. |
| return m_sqlStyle.createInList(sql, true, values[0]); |
| } |
| case OPERATOR_CONTAINS: { |
| if (!plainBind) { |
| addBind(names[0], m_sqlStyle.toLikePattern(values[0])); |
| } |
| return m_sqlStyle.createContains(sql, names[0]); |
| } |
| case OPERATOR_LIKE: { |
| if (!plainBind) { |
| addBind(names[0], m_sqlStyle.toLikePattern(values[0])); |
| } |
| return m_sqlStyle.createLike(sql, names[0]); |
| } |
| case OPERATOR_NOT_IN: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| //no support for plain bind in here. otherwise, ArrayInput gets confused. |
| return m_sqlStyle.createNotInList(sql, true, values[0]); |
| } |
| case OPERATOR_NOT_CONTAINS: { |
| if (!plainBind) { |
| addBind(names[0], m_sqlStyle.toLikePattern(values[0])); |
| } |
| return m_sqlStyle.createNotContains(sql, names[0]); |
| } |
| case OPERATOR_NOT_NULL: { |
| return m_sqlStyle.createNotNull(sql); |
| } |
| case OPERATOR_NUMBER_NOT_NULL: { |
| return m_sqlStyle.createNumberNotNull(sql); |
| } |
| case OPERATOR_NULL: { |
| return m_sqlStyle.createNull(sql); |
| } |
| case OPERATOR_NUMBER_NULL: { |
| return m_sqlStyle.createNumberNull(sql); |
| } |
| case OPERATOR_STARTS_WITH: { |
| if (!plainBind) { |
| addBind(names[0], m_sqlStyle.toLikePattern(values[0])); |
| } |
| return m_sqlStyle.createStartsWith(sql, names[0]); |
| } |
| case OPERATOR_NOT_STARTS_WITH: { |
| if (!plainBind) { |
| addBind(names[0], m_sqlStyle.toLikePattern(values[0])); |
| } |
| return m_sqlStyle.createNotStartsWith(sql, names[0]); |
| } |
| case OPERATOR_TIME_IS_IN_GE_HOURS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createTimeIsInGEHours(sql, names[0]); |
| } |
| case OPERATOR_TIME_IS_IN_GE_MINUTES: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createTimeIsInGEMinutes(sql, names[0]); |
| } |
| case OPERATOR_TIME_IS_IN_HOURS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createTimeIsInHours(sql, names[0]); |
| } |
| case OPERATOR_TIME_IS_IN_LE_HOURS: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createTimeIsInLEHours(sql, names[0]); |
| } |
| case OPERATOR_TIME_IS_IN_LE_MINUTES: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createTimeIsInLEMinutes(sql, names[0]); |
| } |
| case OPERATOR_TIME_IS_IN_MINUTES: { |
| if (!plainBind) { |
| addBinds(names, values); |
| } |
| return m_sqlStyle.createTimeIsInMinutes(sql, names[0]); |
| } |
| case OPERATOR_TIME_IS_NOW: { |
| return m_sqlStyle.createTimeIsNow(sql); |
| } |
| case OPERATOR_TIME_IS_NOT_NOW: { |
| return m_sqlStyle.createTimeIsNotNow(sql); |
| } |
| default: { |
| throw new IllegalArgumentException("invalid operator: " + operation); |
| } |
| } |
| } |
| } |