1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.command.dml; |
7 | |
8 | import java.util.ArrayList; |
9 | |
10 | import org.h2.api.ErrorCode; |
11 | import org.h2.api.Trigger; |
12 | import org.h2.command.Command; |
13 | import org.h2.command.CommandInterface; |
14 | import org.h2.command.Prepared; |
15 | import org.h2.engine.Right; |
16 | import org.h2.engine.Session; |
17 | import org.h2.engine.UndoLogRecord; |
18 | import org.h2.expression.Expression; |
19 | import org.h2.expression.Parameter; |
20 | import org.h2.index.Index; |
21 | import org.h2.message.DbException; |
22 | import org.h2.result.ResultInterface; |
23 | import org.h2.result.Row; |
24 | import org.h2.table.Column; |
25 | import org.h2.table.Table; |
26 | import org.h2.util.New; |
27 | import org.h2.util.StatementBuilder; |
28 | import org.h2.value.Value; |
29 | |
30 | /** |
31 | * This class represents the statement |
32 | * MERGE |
33 | */ |
34 | public class Merge extends Prepared { |
35 | |
36 | private Table table; |
37 | private Column[] columns; |
38 | private Column[] keys; |
39 | private final ArrayList<Expression[]> list = New.arrayList(); |
40 | private Query query; |
41 | private Prepared update; |
42 | |
43 | public Merge(Session session) { |
44 | super(session); |
45 | } |
46 | |
47 | @Override |
48 | public void setCommand(Command command) { |
49 | super.setCommand(command); |
50 | if (query != null) { |
51 | query.setCommand(command); |
52 | } |
53 | } |
54 | |
55 | public void setTable(Table table) { |
56 | this.table = table; |
57 | } |
58 | |
59 | public void setColumns(Column[] columns) { |
60 | this.columns = columns; |
61 | } |
62 | |
63 | public void setKeys(Column[] keys) { |
64 | this.keys = keys; |
65 | } |
66 | |
67 | public void setQuery(Query query) { |
68 | this.query = query; |
69 | } |
70 | |
71 | /** |
72 | * Add a row to this merge statement. |
73 | * |
74 | * @param expr the list of values |
75 | */ |
76 | public void addRow(Expression[] expr) { |
77 | list.add(expr); |
78 | } |
79 | |
80 | @Override |
81 | public int update() { |
82 | int count; |
83 | session.getUser().checkRight(table, Right.INSERT); |
84 | session.getUser().checkRight(table, Right.UPDATE); |
85 | setCurrentRowNumber(0); |
86 | if (list.size() > 0) { |
87 | count = 0; |
88 | for (int x = 0, size = list.size(); x < size; x++) { |
89 | setCurrentRowNumber(x + 1); |
90 | Expression[] expr = list.get(x); |
91 | Row newRow = table.getTemplateRow(); |
92 | for (int i = 0, len = columns.length; i < len; i++) { |
93 | Column c = columns[i]; |
94 | int index = c.getColumnId(); |
95 | Expression e = expr[i]; |
96 | if (e != null) { |
97 | // e can be null (DEFAULT) |
98 | try { |
99 | Value v = c.convert(e.getValue(session)); |
100 | newRow.setValue(index, v); |
101 | } catch (DbException ex) { |
102 | throw setRow(ex, count, getSQL(expr)); |
103 | } |
104 | } |
105 | } |
106 | merge(newRow); |
107 | count++; |
108 | } |
109 | } else { |
110 | ResultInterface rows = query.query(0); |
111 | count = 0; |
112 | table.fire(session, Trigger.UPDATE | Trigger.INSERT, true); |
113 | table.lock(session, true, false); |
114 | while (rows.next()) { |
115 | count++; |
116 | Value[] r = rows.currentRow(); |
117 | Row newRow = table.getTemplateRow(); |
118 | setCurrentRowNumber(count); |
119 | for (int j = 0; j < columns.length; j++) { |
120 | Column c = columns[j]; |
121 | int index = c.getColumnId(); |
122 | try { |
123 | Value v = c.convert(r[j]); |
124 | newRow.setValue(index, v); |
125 | } catch (DbException ex) { |
126 | throw setRow(ex, count, getSQL(r)); |
127 | } |
128 | } |
129 | merge(newRow); |
130 | } |
131 | rows.close(); |
132 | table.fire(session, Trigger.UPDATE | Trigger.INSERT, false); |
133 | } |
134 | return count; |
135 | } |
136 | |
137 | private void merge(Row row) { |
138 | ArrayList<Parameter> k = update.getParameters(); |
139 | for (int i = 0; i < columns.length; i++) { |
140 | Column col = columns[i]; |
141 | Value v = row.getValue(col.getColumnId()); |
142 | Parameter p = k.get(i); |
143 | p.setValue(v); |
144 | } |
145 | for (int i = 0; i < keys.length; i++) { |
146 | Column col = keys[i]; |
147 | Value v = row.getValue(col.getColumnId()); |
148 | if (v == null) { |
149 | throw DbException.get(ErrorCode.COLUMN_CONTAINS_NULL_VALUES_1, col.getSQL()); |
150 | } |
151 | Parameter p = k.get(columns.length + i); |
152 | p.setValue(v); |
153 | } |
154 | int count = update.update(); |
155 | if (count == 0) { |
156 | try { |
157 | table.validateConvertUpdateSequence(session, row); |
158 | boolean done = table.fireBeforeRow(session, null, row); |
159 | if (!done) { |
160 | table.lock(session, true, false); |
161 | table.addRow(session, row); |
162 | session.log(table, UndoLogRecord.INSERT, row); |
163 | table.fireAfterRow(session, null, row, false); |
164 | } |
165 | } catch (DbException e) { |
166 | if (e.getErrorCode() == ErrorCode.DUPLICATE_KEY_1) { |
167 | // possibly a concurrent merge or insert |
168 | Index index = (Index) e.getSource(); |
169 | if (index != null) { |
170 | // verify the index columns match the key |
171 | Column[] indexColumns = index.getColumns(); |
172 | boolean indexMatchesKeys = false; |
173 | if (indexColumns.length <= keys.length) { |
174 | for (int i = 0; i < indexColumns.length; i++) { |
175 | if (indexColumns[i] != keys[i]) { |
176 | indexMatchesKeys = false; |
177 | break; |
178 | } |
179 | } |
180 | } |
181 | if (indexMatchesKeys) { |
182 | throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName()); |
183 | } |
184 | } |
185 | } |
186 | throw e; |
187 | } |
188 | } else if (count != 1) { |
189 | throw DbException.get(ErrorCode.DUPLICATE_KEY_1, table.getSQL()); |
190 | } |
191 | } |
192 | |
193 | @Override |
194 | public String getPlanSQL() { |
195 | StatementBuilder buff = new StatementBuilder("MERGE INTO "); |
196 | buff.append(table.getSQL()).append('('); |
197 | for (Column c : columns) { |
198 | buff.appendExceptFirst(", "); |
199 | buff.append(c.getSQL()); |
200 | } |
201 | buff.append(')'); |
202 | if (keys != null) { |
203 | buff.append(" KEY("); |
204 | buff.resetCount(); |
205 | for (Column c : keys) { |
206 | buff.appendExceptFirst(", "); |
207 | buff.append(c.getSQL()); |
208 | } |
209 | buff.append(')'); |
210 | } |
211 | buff.append('\n'); |
212 | if (list.size() > 0) { |
213 | buff.append("VALUES "); |
214 | int row = 0; |
215 | for (Expression[] expr : list) { |
216 | if (row++ > 0) { |
217 | buff.append(", "); |
218 | } |
219 | buff.append('('); |
220 | buff.resetCount(); |
221 | for (Expression e : expr) { |
222 | buff.appendExceptFirst(", "); |
223 | if (e == null) { |
224 | buff.append("DEFAULT"); |
225 | } else { |
226 | buff.append(e.getSQL()); |
227 | } |
228 | } |
229 | buff.append(')'); |
230 | } |
231 | } else { |
232 | buff.append(query.getPlanSQL()); |
233 | } |
234 | return buff.toString(); |
235 | } |
236 | |
237 | @Override |
238 | public void prepare() { |
239 | if (columns == null) { |
240 | if (list.size() > 0 && list.get(0).length == 0) { |
241 | // special case where table is used as a sequence |
242 | columns = new Column[0]; |
243 | } else { |
244 | columns = table.getColumns(); |
245 | } |
246 | } |
247 | if (list.size() > 0) { |
248 | for (Expression[] expr : list) { |
249 | if (expr.length != columns.length) { |
250 | throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); |
251 | } |
252 | for (int i = 0; i < expr.length; i++) { |
253 | Expression e = expr[i]; |
254 | if (e != null) { |
255 | expr[i] = e.optimize(session); |
256 | } |
257 | } |
258 | } |
259 | } else { |
260 | query.prepare(); |
261 | if (query.getColumnCount() != columns.length) { |
262 | throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); |
263 | } |
264 | } |
265 | if (keys == null) { |
266 | Index idx = table.getPrimaryKey(); |
267 | if (idx == null) { |
268 | throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, "PRIMARY KEY"); |
269 | } |
270 | keys = idx.getColumns(); |
271 | } |
272 | StatementBuilder buff = new StatementBuilder("UPDATE "); |
273 | buff.append(table.getSQL()).append(" SET "); |
274 | for (Column c : columns) { |
275 | buff.appendExceptFirst(", "); |
276 | buff.append(c.getSQL()).append("=?"); |
277 | } |
278 | buff.append(" WHERE "); |
279 | buff.resetCount(); |
280 | for (Column c : keys) { |
281 | buff.appendExceptFirst(" AND "); |
282 | buff.append(c.getSQL()).append("=?"); |
283 | } |
284 | String sql = buff.toString(); |
285 | update = session.prepare(sql); |
286 | } |
287 | |
288 | @Override |
289 | public boolean isTransactional() { |
290 | return true; |
291 | } |
292 | |
293 | @Override |
294 | public ResultInterface queryMeta() { |
295 | return null; |
296 | } |
297 | |
298 | @Override |
299 | public int getType() { |
300 | return CommandInterface.MERGE; |
301 | } |
302 | |
303 | @Override |
304 | public boolean isCacheable() { |
305 | return true; |
306 | } |
307 | |
308 | } |