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 | import java.util.HashMap; |
10 | |
11 | import org.h2.api.ErrorCode; |
12 | import org.h2.api.Trigger; |
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.expression.Expression; |
18 | import org.h2.expression.Parameter; |
19 | import org.h2.expression.ValueExpression; |
20 | import org.h2.message.DbException; |
21 | import org.h2.result.ResultInterface; |
22 | import org.h2.result.Row; |
23 | import org.h2.result.RowList; |
24 | import org.h2.table.Column; |
25 | import org.h2.table.PlanItem; |
26 | import org.h2.table.Table; |
27 | import org.h2.table.TableFilter; |
28 | import org.h2.util.New; |
29 | import org.h2.util.StatementBuilder; |
30 | import org.h2.util.StringUtils; |
31 | import org.h2.value.Value; |
32 | import org.h2.value.ValueNull; |
33 | |
34 | /** |
35 | * This class represents the statement |
36 | * UPDATE |
37 | */ |
38 | public class Update extends Prepared { |
39 | |
40 | private Expression condition; |
41 | private TableFilter tableFilter; |
42 | |
43 | /** The limit expression as specified in the LIMIT clause. */ |
44 | private Expression limitExpr; |
45 | |
46 | private final ArrayList<Column> columns = New.arrayList(); |
47 | private final HashMap<Column, Expression> expressionMap = New.hashMap(); |
48 | |
49 | public Update(Session session) { |
50 | super(session); |
51 | } |
52 | |
53 | public void setTableFilter(TableFilter tableFilter) { |
54 | this.tableFilter = tableFilter; |
55 | } |
56 | |
57 | public void setCondition(Expression condition) { |
58 | this.condition = condition; |
59 | } |
60 | |
61 | /** |
62 | * Add an assignment of the form column = expression. |
63 | * |
64 | * @param column the column |
65 | * @param expression the expression |
66 | */ |
67 | public void setAssignment(Column column, Expression expression) { |
68 | if (expressionMap.containsKey(column)) { |
69 | throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, column |
70 | .getName()); |
71 | } |
72 | columns.add(column); |
73 | expressionMap.put(column, expression); |
74 | if (expression instanceof Parameter) { |
75 | Parameter p = (Parameter) expression; |
76 | p.setColumn(column); |
77 | } |
78 | } |
79 | |
80 | @Override |
81 | public int update() { |
82 | tableFilter.startQuery(session); |
83 | tableFilter.reset(); |
84 | RowList rows = new RowList(session); |
85 | try { |
86 | Table table = tableFilter.getTable(); |
87 | session.getUser().checkRight(table, Right.UPDATE); |
88 | table.fire(session, Trigger.UPDATE, true); |
89 | table.lock(session, true, false); |
90 | int columnCount = table.getColumns().length; |
91 | // get the old rows, compute the new rows |
92 | setCurrentRowNumber(0); |
93 | int count = 0; |
94 | Column[] columns = table.getColumns(); |
95 | int limitRows = -1; |
96 | if (limitExpr != null) { |
97 | Value v = limitExpr.getValue(session); |
98 | if (v != ValueNull.INSTANCE) { |
99 | limitRows = v.getInt(); |
100 | } |
101 | } |
102 | while (tableFilter.next()) { |
103 | setCurrentRowNumber(count+1); |
104 | if (limitRows >= 0 && count >= limitRows) { |
105 | break; |
106 | } |
107 | if (condition == null || |
108 | Boolean.TRUE.equals(condition.getBooleanValue(session))) { |
109 | Row oldRow = tableFilter.get(); |
110 | Row newRow = table.getTemplateRow(); |
111 | for (int i = 0; i < columnCount; i++) { |
112 | Expression newExpr = expressionMap.get(columns[i]); |
113 | Value newValue; |
114 | if (newExpr == null) { |
115 | newValue = oldRow.getValue(i); |
116 | } else if (newExpr == ValueExpression.getDefault()) { |
117 | Column column = table.getColumn(i); |
118 | newValue = table.getDefaultValue(session, column); |
119 | } else { |
120 | Column column = table.getColumn(i); |
121 | newValue = column.convert(newExpr.getValue(session)); |
122 | } |
123 | newRow.setValue(i, newValue); |
124 | } |
125 | table.validateConvertUpdateSequence(session, newRow); |
126 | boolean done = false; |
127 | if (table.fireRow()) { |
128 | done = table.fireBeforeRow(session, oldRow, newRow); |
129 | } |
130 | if (!done) { |
131 | rows.add(oldRow); |
132 | rows.add(newRow); |
133 | } |
134 | count++; |
135 | } |
136 | } |
137 | // TODO self referencing referential integrity constraints |
138 | // don't work if update is multi-row and 'inversed' the condition! |
139 | // probably need multi-row triggers with 'deleted' and 'inserted' |
140 | // at the same time. anyway good for sql compatibility |
141 | // TODO update in-place (but if the key changes, |
142 | // we need to update all indexes) before row triggers |
143 | |
144 | // the cached row is already updated - we need the old values |
145 | table.updateRows(this, session, rows); |
146 | if (table.fireRow()) { |
147 | rows.invalidateCache(); |
148 | for (rows.reset(); rows.hasNext();) { |
149 | Row o = rows.next(); |
150 | Row n = rows.next(); |
151 | table.fireAfterRow(session, o, n, false); |
152 | } |
153 | } |
154 | table.fire(session, Trigger.UPDATE, false); |
155 | return count; |
156 | } finally { |
157 | rows.close(); |
158 | } |
159 | } |
160 | |
161 | @Override |
162 | public String getPlanSQL() { |
163 | StatementBuilder buff = new StatementBuilder("UPDATE "); |
164 | buff.append(tableFilter.getPlanSQL(false)).append("\nSET\n "); |
165 | for (int i = 0, size = columns.size(); i < size; i++) { |
166 | Column c = columns.get(i); |
167 | Expression e = expressionMap.get(c); |
168 | buff.appendExceptFirst(",\n "); |
169 | buff.append(c.getName()).append(" = ").append(e.getSQL()); |
170 | } |
171 | if (condition != null) { |
172 | buff.append("\nWHERE ").append(StringUtils.unEnclose(condition.getSQL())); |
173 | } |
174 | return buff.toString(); |
175 | } |
176 | |
177 | @Override |
178 | public void prepare() { |
179 | if (condition != null) { |
180 | condition.mapColumns(tableFilter, 0); |
181 | condition = condition.optimize(session); |
182 | condition.createIndexConditions(session, tableFilter); |
183 | } |
184 | for (int i = 0, size = columns.size(); i < size; i++) { |
185 | Column c = columns.get(i); |
186 | Expression e = expressionMap.get(c); |
187 | e.mapColumns(tableFilter, 0); |
188 | expressionMap.put(c, e.optimize(session)); |
189 | } |
190 | PlanItem item = tableFilter.getBestPlanItem(session, 1); |
191 | tableFilter.setPlanItem(item); |
192 | tableFilter.prepare(); |
193 | } |
194 | |
195 | @Override |
196 | public boolean isTransactional() { |
197 | return true; |
198 | } |
199 | |
200 | @Override |
201 | public ResultInterface queryMeta() { |
202 | return null; |
203 | } |
204 | |
205 | @Override |
206 | public int getType() { |
207 | return CommandInterface.UPDATE; |
208 | } |
209 | |
210 | public void setLimit(Expression limit) { |
211 | this.limitExpr = limit; |
212 | } |
213 | |
214 | @Override |
215 | public boolean isCacheable() { |
216 | return true; |
217 | } |
218 | |
219 | } |