001package io.ebeaninternal.server.querydefn;
002
003import io.ebeaninternal.server.deploy.DeployParser;
004import io.ebeaninternal.server.persist.Binder;
005import io.ebeaninternal.server.type.DataBind;
006
007import java.sql.SQLException;
008import java.util.LinkedHashMap;
009import java.util.Map;
010import java.util.Set;
011import java.util.regex.Pattern;
012
013/**
014 * Set properties for a UpdateQuery.
015 */
016public class OrmUpdateProperties {
017
018  private static final NullValue NULL_VALUE = new NullValue();
019
020  private static final NoneValue NONE_VALUE = new NoneValue();
021
022  private static final Pattern TABLE_ALIAS_REPLACE = Pattern.compile("${}", Pattern.LITERAL);
023
024  /**
025   * Bind value used in the set clause for update query.
026   * It may/may not have bind values etc.
027   */
028  public static abstract class Value {
029
030    public void bind(Binder binder, DataBind dataBind) throws SQLException {
031      // default to no bind values
032    }
033
034    public String bindClause() {
035      return "";
036    }
037
038    public int getBindCount() {
039      return 0;
040    }
041  }
042
043  /**
044   * Set property to null.
045   */
046  private static class NullValue extends Value {
047    @Override
048    public String bindClause() {
049      return "=null";
050    }
051  }
052
053  /**
054   * Set property to a simple value.
055   */
056  private static class SimpleValue extends Value {
057
058    final Object value;
059
060    SimpleValue(Object value) {
061      this.value = value;
062    }
063
064    @Override
065    public int getBindCount() {
066      return 1;
067    }
068
069    @Override
070    public String bindClause() {
071      return "=?";
072    }
073
074    @Override
075    public void bind(Binder binder, DataBind dataBind) throws SQLException {
076      binder.bindObject(dataBind, value);
077      dataBind.append(value).append(",");
078    }
079  }
080
081  /**
082   * Set using an expression with no bind value.
083   */
084  private static class NoneValue extends Value {
085    @Override
086    public String bindClause() {
087      return "";
088    }
089  }
090
091  /**
092   * Set using an expression with many bind values.
093   */
094  private static class RawArrayValue extends Value {
095
096    final Object[] bindValues;
097
098    RawArrayValue(Object[] bindValues) {
099      this.bindValues = bindValues;
100    }
101
102    @Override
103    public int getBindCount() {
104      return bindValues.length;
105    }
106
107    @Override
108    public void bind(Binder binder, DataBind dataBind) throws SQLException {
109      for (Object val : bindValues) {
110        binder.bindObject(dataBind, val);
111        dataBind.append(val).append(",");
112      }
113    }
114  }
115
116  /**
117   * The set properties/expressions and their bind values.
118   */
119  private final LinkedHashMap<String, Value> values = new LinkedHashMap<>();
120
121  /**
122   * Normal set property.
123   */
124  public void set(String propertyName, Object value) {
125    if (value == null) {
126      values.put(propertyName, NULL_VALUE);
127
128    } else {
129      values.put(propertyName, new SimpleValue(value));
130    }
131  }
132
133  /**
134   * Set a raw expression with no bind values.
135   */
136  public void setRaw(String propertyName) {
137    values.put(propertyName, NONE_VALUE);
138  }
139
140  /**
141   * Set a raw expression with many bind values.
142   */
143  void setRaw(String propertyExpression, Object... vals) {
144    if (vals.length == 0) {
145      setRaw(propertyExpression);
146    } else {
147      values.put(propertyExpression, new RawArrayValue(vals));
148    }
149  }
150
151  /**
152   * Build the hash for the query plan caching.
153   */
154  void buildQueryPlanHash(StringBuilder builder) {
155    Set<Map.Entry<String, Value>> entries = values.entrySet();
156    for (Map.Entry<String, Value> entry : entries) {
157      builder.append("key:").append(entry.getKey());
158      builder.append(" ?:").append(entry.getValue().getBindCount());
159    }
160  }
161
162  /**
163   * Bind all the bind values for the update set clause.
164   */
165  public void bind(Binder binder, DataBind dataBind) throws SQLException {
166    for (Value bindValue : values.values()) {
167      bindValue.bind(binder, dataBind);
168    }
169  }
170
171  /**
172   * Build the actual set clause converting logical property names to db columns etc.
173   */
174  public String buildSetClause(DeployParser deployParser) {
175
176    int setCount = 0;
177    StringBuilder sb = new StringBuilder();
178
179    for (Map.Entry<String, Value> entry : values.entrySet()) {
180      String property = entry.getKey();
181      if (setCount++ > 0) {
182        sb.append(", ");
183      }
184
185      // translate to db columns and remove table alias placeholders
186      sb.append(TABLE_ALIAS_REPLACE.matcher(deployParser.parse(property)).replaceAll(""));
187      sb.append(entry.getValue().bindClause());
188    }
189    return sb.toString();
190  }
191
192}