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}