/* * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, * and the EPL 1.0 (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.command.dml; import java.util.ArrayList; import java.util.HashMap; import org.h2.api.ErrorCode; import org.h2.api.Trigger; import org.h2.command.CommandInterface; import org.h2.command.Prepared; import org.h2.engine.Right; import org.h2.engine.Session; import org.h2.expression.Expression; import org.h2.expression.ExpressionVisitor; import org.h2.expression.Parameter; import org.h2.expression.ValueExpression; import org.h2.message.DbException; import org.h2.result.ResultInterface; import org.h2.result.Row; import org.h2.result.RowList; import org.h2.table.Column; import org.h2.table.PlanItem; import org.h2.table.Table; import org.h2.table.TableFilter; import org.h2.util.New; import org.h2.util.StatementBuilder; import org.h2.util.StringUtils; import org.h2.value.Value; import org.h2.value.ValueNull; /** * This class represents the statement * UPDATE */ public class Update extends Prepared { private Expression condition; private TableFilter tableFilter; /** The limit expression as specified in the LIMIT clause. */ private Expression limitExpr; private final ArrayList<Column> columns = New.arrayList(); private final HashMap<Column, Expression> expressionMap = New.hashMap(); public Update(Session session) { super(session); } public void setTableFilter(TableFilter tableFilter) { this.tableFilter = tableFilter; } public void setCondition(Expression condition) { this.condition = condition; } /** * Add an assignment of the form column = expression. * * @param column the column * @param expression the expression */ public void setAssignment(Column column, Expression expression) { if (expressionMap.containsKey(column)) { throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, column .getName()); } columns.add(column); expressionMap.put(column, expression); if (expression instanceof Parameter) { Parameter p = (Parameter) expression; p.setColumn(column); } } @Override public int update() { tableFilter.startQuery(session); tableFilter.reset(); RowList rows = new RowList(session); try { Table table = tableFilter.getTable(); session.getUser().checkRight(table, Right.UPDATE); //调用针对整个update动作的触发器 table.fire(session, Trigger.UPDATE, true); //直到事务commit或rollback时才解琐,见org.h2.engine.Session.unlockAll() table.lock(session, true, false); int columnCount = table.getColumns().length; // get the old rows, compute the new rows setCurrentRowNumber(0); int count = 0; Column[] columns = table.getColumns(); int limitRows = -1; if (limitExpr != null) { Value v = limitExpr.getValue(session); if (v != ValueNull.INSTANCE) { limitRows = v.getInt(); } } //第一步先按where条件(如果有的话)取出所有满足条件的所有记录,如果没有where条件就是取全部记录,这些记录不超过limitRows while (tableFilter.next()) { setCurrentRowNumber(count+1); if (limitRows >= 0 && count >= limitRows) { break; } if (condition == null || Boolean.TRUE.equals(condition.getBooleanValue(session))) { Row oldRow = tableFilter.get(); Row newRow = table.getTemplateRow(); //以原表中的所有字段来遍历,而不是以update中的字段 for (int i = 0; i < columnCount; i++) { Expression newExpr = expressionMap.get(columns[i]); Value newValue; if (newExpr == null) { //说明不是更新字段,直接用原来的值 newValue = oldRow.getValue(i); } else if (newExpr == ValueExpression.getDefault()) { //是更新字段,但是取默认值 Column column = table.getColumn(i); newValue = table.getDefaultValue(session, column); } else { //是更新字段,并且取更新值 Column column = table.getColumn(i); newValue = column.convert(newExpr.getValue(session)); } newRow.setValue(i, newValue); } //验证新记录(包括字段约束检查) table.validateConvertUpdateSequence(session, newRow); boolean done = false; //调用针对行级别的触发器 if (table.fireRow()) { done = table.fireBeforeRow(session, oldRow, newRow); } if (!done) { rows.add(oldRow); rows.add(newRow); } count++; } } // TODO self referencing referential integrity constraints // don't work if update is multi-row and 'inversed' the condition! // probably need multi-row triggers with 'deleted' and 'inserted' // at the same time. anyway good for sql compatibility // TODO update in-place (but if the key changes, // we need to update all indexes) before row triggers // the cached row is already updated - we need the old values //第二步更新记录(先删除记录,再增加记录) table.updateRows(this, session, rows); if (table.fireRow()) { rows.invalidateCache(); for (rows.reset(); rows.hasNext();) { Row o = rows.next(); Row n = rows.next(); table.fireAfterRow(session, o, n, false); } } table.fire(session, Trigger.UPDATE, false); return count; } finally { rows.close(); } } @Override public String getPlanSQL() { StatementBuilder buff = new StatementBuilder("UPDATE "); buff.append(tableFilter.getPlanSQL(false)).append("\nSET\n "); for (int i = 0, size = columns.size(); i < size; i++) { Column c = columns.get(i); Expression e = expressionMap.get(c); buff.appendExceptFirst(",\n "); buff.append(c.getName()).append(" = ").append(e.getSQL()); } if (condition != null) { buff.append("\nWHERE ").append(StringUtils.unEnclose(condition.getSQL())); } if (limitExpr != null) { buff.append("\nLIMIT ").append( StringUtils.unEnclose(limitExpr.getSQL())); } return buff.toString(); } @Override public void prepare() { //跟org.h2.command.dml.Delete.prepare()一样,只是多了中间的for if (condition != null) { condition.mapColumns(tableFilter, 0); condition = condition.optimize(session); //根据where条件建立相关的索引条件,这样可以由where条件中的字段选择合适的索引 //如为字段name建立了索引,如果是where name>'124',那么此时就用name的索引。 condition.createIndexConditions(session, tableFilter); } for (int i = 0, size = columns.size(); i < size; i++) { Column c = columns.get(i); Expression e = expressionMap.get(c); e.mapColumns(tableFilter, 0); expressionMap.put(c, e.optimize(session)); } TableFilter[] filters = new TableFilter[] { tableFilter }; PlanItem item = tableFilter.getBestPlanItem(session, filters, 0, ExpressionVisitor.allColumnsForTableFilters(filters)); tableFilter.setPlanItem(item); tableFilter.prepare(); } @Override public boolean isTransactional() { return true; } @Override public ResultInterface queryMeta() { return null; } @Override public int getType() { return CommandInterface.UPDATE; } public void setLimit(Expression limit) { this.limitExpr = limit; } @Override public boolean isCacheable() { return true; } }