001: /*
002: * $Id: UpsertCommand.java,v 1.35 2006/01/10 21:02:37 ahimanikya Exp $
003: * =======================================================================
004: * Copyright (c) 2002-2004 Axion Development Team. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above
011: * copyright notice, this list of conditions and the following
012: * disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The names "Tigris", "Axion", nor the names of its contributors may
020: * not be used to endorse or promote products derived from this
021: * software without specific prior written permission.
022: *
023: * 4. Products derived from this software may not be called "Axion", nor
024: * may "Tigris" or "Axion" appear in their names without specific prior
025: * written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
030: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
032: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
033: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
034: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
035: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
036: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
037: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
038: * =======================================================================
039: */
040:
041: package org.axiondb.engine.commands;
042:
043: import java.util.ArrayList;
044: import java.util.HashMap;
045: import java.util.HashSet;
046: import java.util.Iterator;
047: import java.util.List;
048: import java.util.Map;
049: import java.util.Set;
050:
051: import org.axiondb.AxionException;
052: import org.axiondb.ColumnIdentifier;
053: import org.axiondb.DataType;
054: import org.axiondb.Database;
055: import org.axiondb.Function;
056: import org.axiondb.Row;
057: import org.axiondb.RowDecorator;
058: import org.axiondb.RowIterator;
059: import org.axiondb.Selectable;
060: import org.axiondb.Table;
061: import org.axiondb.TableIdentifier;
062: import org.axiondb.engine.SnapshotIsolationTransaction;
063: import org.axiondb.engine.rowcollection.IntSet;
064: import org.axiondb.engine.rows.JoinedRow;
065: import org.axiondb.engine.rows.SimpleRow;
066: import org.axiondb.engine.tables.ExternalDatabaseTable;
067: import org.axiondb.engine.visitors.FindBindVariableVisitor;
068: import org.axiondb.engine.visitors.TableColumnsUsedInFunctionVisitor;
069: import org.axiondb.jdbc.AxionResultSet;
070: import org.axiondb.util.ValuePool;
071:
072: /**
073: * An <tt>UPSERT or MERGE</tt> command.
074: *
075: * @version $Revision: 1.35 $ $Date: 2006/01/10 21:02:37 $
076: * @author Ahimanikya Satapathy
077: */
078: public class UpsertCommand extends ChildTableUpdater {
079:
080: //------------------------------------------------------------ Constructors
081:
082: public UpsertCommand() {
083: }
084:
085: //---------------------------------------------------------- Public Methods
086:
087: public void addUpdateColumn(ColumnIdentifier col) {
088: _columnsForUpdate.add(col);
089: }
090:
091: public void addUpdateValue(Selectable val) {
092: _valuesForUpdate.add(val);
093: }
094:
095: public boolean execute(Database database) throws AxionException {
096: executeUpdate(database);
097: return false;
098: }
099:
100: /**
101: * Unsupported, use {@link #executeUpdate}instead.
102: *
103: * @throws UnsupportedOperationException
104: */
105: public AxionResultSet executeQuery(Database database)
106: throws AxionException {
107: throw new UnsupportedOperationException("Use executeUpdate.");
108: }
109:
110: public int executeUpdate(org.axiondb.Database db)
111: throws AxionException {
112: assertNotReadOnly(db);
113: IntSet rowcount = new IntSet();
114: int exTblCnt = 0;
115: int rowId = -1;
116:
117: RowIterator joinedRowIter;
118: JoinedRow joinRow;
119:
120: Set sourceColsUsedInCondition = new HashSet();
121: Set targetColsUsedInCondition = new HashSet();
122: Set uniqueSourceRowSet = new HashSet();
123:
124: try {
125: preProcess(db);
126: resolve(db);
127:
128: // 1. execute the 'select * from srcTable left outer join targetTable...'
129: joinedRowIter = _selectCommand.makeRowIterator(db, true);
130:
131: // 2. build decorator now, building this before executing the above
132: RowDecorator dec = buildDecorator(_sourceTable,
133: _targetTable);
134:
135: // 3. check if any target columns that participated in
136: // merge condition used in update
137: TableColumnsUsedInFunctionVisitor tVisitor = new TableColumnsUsedInFunctionVisitor();
138: tVisitor.visit((Function) _condition, _targetTable);
139: targetColsUsedInCondition = tVisitor
140: .getColumnsUsedInFunction();
141:
142: if (isTargetColumnUsedInUpdate(targetColsUsedInCondition)) {
143: throw new AxionException(
144: "Updates Not allowed for cols used in Merge/Upsert Condition");
145: }
146:
147: // 4. Find source columns used in Merge condition.
148: TableColumnsUsedInFunctionVisitor sVisitor = new TableColumnsUsedInFunctionVisitor();
149: sVisitor.visit((Function) _condition, _sourceTable);
150: sourceColsUsedInCondition = sVisitor
151: .getColumnsUsedInFunction();
152: int ttColCount = _targetTable.getColumnCount();
153:
154: setDeferAllConstraintIfRequired(_targetTable);
155:
156: // 5. Loop thru and merge(insert or update as appropriate)
157: while (joinedRowIter.hasNext()) {
158: Iterator colids;
159: Iterator values;
160: Row newrow;
161:
162: // Since we createrd a LOJ assume the the joined row has
163: // left table's row should be at index(0)
164: // and right table's row at index(1)
165: joinRow = (JoinedRow) joinedRowIter.next();
166:
167: Row sourceRow = joinRow.getRow(0); // get source table row
168: Row targetRow = joinRow.getRow(1); // get target table row
169:
170: dec.setRow(joinedRowIter.currentIndex(), joinRow);
171:
172: // check for unstable/duplicate row set in source table
173: if (hasDuplicateRow(dec, sourceColsUsedInCondition,
174: uniqueSourceRowSet)) {
175: throw new AxionException(
176: "Unable to get a stable set of rows in the source tables...");
177: }
178:
179: // if current row match exception when condition process else
180: if (_exceptionWhenClause != null
181: && _exceptionWhenClause.insertMatchingRow(db,
182: dec, sourceRow)) {
183: continue; // pick next row
184: }
185:
186: // check for null in columns that are participating in merge condition
187: // if true then it's an insert
188: // else we have matching row in the target table , so it's an update
189: if (isNullRow(targetRow)) {
190: // INSERT: add sourceRow to targetTable
191: newrow = new SimpleRow(ttColCount);
192: colids = this .getInsertColumnIterator();
193: values = this .getInsertValueIterator();
194: prepareRow(newrow, colids, values, dec,
195: _targetTable, db);
196:
197: RowDecorator trgtDec = makeTargetRowDecorator();
198: trgtDec.setRow(newrow);
199: populateDefaultValues(db, _targetTable,
200: _targetTableId, trgtDec);
201: if (_populateSequence) {
202: _populateSequence = populateSequenceColumns(db,
203: _targetTable, newrow);
204: }
205: _targetTable.addRow(newrow);
206: rowId = newrow.getIdentifier();
207: if (rowId == -1) {
208: exTblCnt++;
209: } else {
210: rowcount.add(rowId);
211: }
212: } else {
213: // UPDATE: replace old row with new row
214: newrow = new SimpleRow(targetRow);
215: colids = this .getUpdateColumnIterator();
216: values = this .getUpdateValueIterator();
217: prepareRow(newrow, colids, values, dec,
218: _targetTable, db);
219: if (_isExternalDBTable) {
220: ((ExternalDatabaseTable) _targetTable)
221: .updateRow(targetRow, newrow,
222: _columnsForUpdate);
223: } else {
224: updateGeneratedValues(db, _targetTable,
225: _targetTableId, newrow);
226: if (!targetRow.equals(newrow)) {
227: _targetTable.updateRow(targetRow, newrow);
228: updateOrSetNullChildRows(db, _targetTable,
229: targetRow, newrow);
230:
231: rowId = newrow.getIdentifier();
232: if (rowId == -1) {
233: exTblCnt++;
234: } else {
235: rowcount.add(rowId);
236: }
237: }
238: }
239: }
240: commitIfRequired(db);
241: }
242: } finally {
243: //cleanup if a view exist.
244: if (_usingSubselect != null && _sourceTable != null) {
245: if (db.hasTable(_sourceTable.getName())) {
246: db.dropTable(_sourceTable.getName());
247: }
248: }
249: }
250:
251: setEffectedRowCount(rowcount.size());
252: return rowcount.size();
253: }
254:
255: public void setColumnsForInsert(List columnForInsert) {
256: _columnsForInsert = columnForInsert;
257: }
258:
259: public void setCondition(Selectable condition) {
260: _condition = condition;
261: }
262:
263: public Selectable getCondition() {
264: return _condition;
265: }
266:
267: public void setExceptionWhenClause(DMLWhenClause w,
268: TableIdentifier t, List cols, List vals) {
269: _exceptionWhenClause = new ExceptionWhenClause(w, t, cols, vals);
270: }
271:
272: public ExceptionWhenClause getExceptionWhenClause() {
273: return _exceptionWhenClause;
274: }
275:
276: public void setSelectCommand(SubSelectCommand command) {
277: _selectCommand = command;
278: }
279:
280: public void setSourceTable(TableIdentifier table) {
281: _sourceTableId = table;
282: }
283:
284: public void setTargetTable(TableIdentifier table) {
285: _targetTableId = table;
286: }
287:
288: public void setUsingSubSelectAlias(String alias) {
289: _usingSubSelectAlias = alias;
290: }
291:
292: public void setUsingSubSelectCommand(SubSelectCommand command) {
293: _usingSubselect = command;
294: }
295:
296: public SubSelectCommand getUsingSubSelectCommand() {
297: return _usingSubselect;
298: }
299:
300: public void setValuesForInsert(List valuesForInsert) {
301: _valuesForInsert = valuesForInsert;
302: }
303:
304: protected void buildBindVariables() {
305: setBindVariableVisitor(new FindBindVariableVisitor());
306: getBindVariableVisitor().visit(this );
307: }
308:
309: // hashCode method must consistently return the same integer,
310: // provided no information used in equals comparisons on the object is modified.
311: // So this has to be built after we execute our internal LOJ select Query
312: // Otherwise hashCode for columnIdentifier will not match.
313: private RowDecorator buildDecorator(Table sourceTable,
314: Table targetTable) throws AxionException {
315: if (_dec == null) {
316: // build the indexMap (ColumnIdentifiers --> Integer index in Row)
317: Map indexMap = new HashMap();
318: int index = 0;
319:
320: Iterator iter = getColIdentifierList(sourceTable,
321: getSourceTable()).iterator();
322: while (iter.hasNext()) {
323: indexMap.put(iter.next(), ValuePool.getInt(index++));
324: }
325:
326: iter = getColIdentifierList(targetTable, getTargetTable())
327: .iterator();
328: while (iter.hasNext()) {
329: indexMap.put(iter.next(), ValuePool.getInt(index++));
330: }
331: _dec = new RowDecorator(indexMap);
332: }
333: return _dec;
334: }
335:
336: private void commitIfRequired(Database db) throws AxionException {
337: if (getCommitSize(db) == 0) {
338: return;
339: }
340:
341: if (db instanceof SnapshotIsolationTransaction
342: && (++_count % getCommitSize(db)) == 0) {
343: _targetTable = ((SnapshotIsolationTransaction) db)
344: .commit(_targetTableId);
345: }
346: }
347:
348: private TableIdentifier[] getAllTables() {
349: return new TableIdentifier[] { getSourceTable(),
350: getTargetTable() };
351: }
352:
353: private Iterator getInsertColumnIterator() {
354: return _columnsForInsert.iterator();
355: }
356:
357: public Iterator getInsertValueIterator() {
358: return _valuesForInsert.iterator();
359: }
360:
361: private TableIdentifier getSourceTable() {
362: return _sourceTableId;
363: }
364:
365: private TableIdentifier getTargetTable() {
366: return _targetTableId;
367: }
368:
369: private Iterator getUpdateColumnIterator() {
370: return _columnsForUpdate.iterator();
371: }
372:
373: public Iterator getUpdateValueIterator() {
374: return _valuesForUpdate.iterator();
375: }
376:
377: private String getUsingSubSelectAlias() {
378: return _usingSubSelectAlias;
379: }
380:
381: private boolean hasDuplicateRow(RowDecorator dec,
382: Set sourceColsUsedInCondition, Set uniqueSourceRowSet)
383: throws AxionException {
384: Row row = new SimpleRow(dec.getRowIndex(),
385: sourceColsUsedInCondition.size());
386: int i = 0;
387: boolean found = false;
388:
389: for (Iterator colids = sourceColsUsedInCondition.iterator(); colids
390: .hasNext();) {
391: Selectable colid = (Selectable) (colids.next());
392: Object val = colid.evaluate(dec);
393: row.set(i++, val);
394: }
395:
396: if (uniqueSourceRowSet.contains(row)) {
397: found = true;
398: } else {
399: uniqueSourceRowSet.add(row);
400: }
401:
402: return found;
403: }
404:
405: private boolean isNullRow(Row row) {
406: return (row.getIdentifier() == -1);
407: }
408:
409: private boolean isTargetColumnUsedInUpdate(Set cols) {
410: for (Iterator colItr = getUpdateColumnIterator(); colItr
411: .hasNext();) {
412: if (cols.contains(colItr.next())) {
413: return true;
414: }
415: }
416: return false;
417: }
418:
419: private void prepareRow(Row newrow, Iterator colids,
420: Iterator values, RowDecorator dec, Table targetTable,
421: Database db) throws AxionException {
422:
423: while (colids.hasNext()) {
424: ColumnIdentifier colid = (ColumnIdentifier) (colids.next());
425: Selectable sel = (Selectable) (values.next());
426: Object val = sel.evaluate(dec);
427: DataType type = db.getTable(colid.getTableName())
428: .getColumn(colid.getName()).getDataType();
429: val = attemptToConvertValue(val, type, colid);
430: newrow
431: .set(targetTable.getColumnIndex(colid.getName()),
432: val);
433: }
434: }
435:
436: private void preProcess(Database db) throws AxionException {
437: _count = 0;
438: // process sub-query/view
439: if (null != _usingSubselect) {
440: _sourceTable = _usingSubselect.getTableView(db, null, true);
441:
442: TableIdentifier tid = new TableIdentifier(_sourceTable
443: .getName(), getUsingSubSelectAlias());
444: setSourceTable(tid);
445: _selectCommand.getQueryContext().getFrom().setLeft(tid);
446:
447: // get from the database to enable transaction
448: _sourceTable = db.getTable(getSourceTable());
449: }
450:
451: // grab the table
452: if (null == _usingSubselect && null != getSourceTable()) {
453: _sourceTable = db.getTable(getSourceTable());
454: }
455:
456: if (null == _sourceTable) {
457: throw new AxionException("Table " + getSourceTable()
458: + " not found.");
459: }
460:
461: _targetTable = db.getTable(getTargetTable());
462: if (null == _targetTable) {
463: throw new AxionException("Table " + getTargetTable()
464: + " not found.");
465: }
466:
467: if (_exceptionWhenClause != null) {
468: _exceptionWhenClause.preProcess(db);
469: }
470: }
471:
472: protected void resolve(Database db) throws AxionException {
473: if (!_resolved) {
474: preProcess(db);
475:
476: resolveSelectableList(_columnsForInsert, db,
477: getTargetTable());
478: resolveSelectableList(_valuesForInsert, db, getAllTables());
479: resolveSelectableList(_columnsForUpdate, db,
480: getTargetTable());
481: resolveSelectableList(_valuesForUpdate, db, getAllTables());
482:
483: resolveGeneratedColumns(_targetTable, _targetTableId,
484: _columnsForInsert);
485: resolveGeneratedColumns(_targetTable, _targetTableId,
486: _columnsForUpdate);
487:
488: _condition = resolveSelectable(_condition, db,
489: getAllTables());
490:
491: // check and resolve Exception When clause
492: if (_exceptionWhenClause != null) {
493: _exceptionWhenClause.resolve(db);
494: }
495:
496: _isExternalDBTable = _targetTable instanceof ExternalDatabaseTable;
497: _resolved = true;
498: }
499: }
500:
501: private class ExceptionWhenClause extends InsertIntoClause {
502:
503: private boolean _isTargetPartOfSubQuery = false;
504:
505: public ExceptionWhenClause(DMLWhenClause when,
506: TableIdentifier tid, List cols, List vals) {
507: super (when, tid, cols, vals);
508: }
509:
510: protected boolean isTargetTablePartOfSubQuery()
511: throws AxionException {
512: return _isTargetPartOfSubQuery;
513: }
514:
515: protected void resolve(Database db) throws AxionException {
516: super .resolve(db);
517:
518: //resolve when condition
519: getWhenClause().resolve(db,
520: new TableIdentifier[] { getSourceTable() });
521: if (_usingSubselect != null) {
522: _isTargetPartOfSubQuery = _usingSubselect
523: .getQueryContext().isTablePartOfSelect(
524: getTargetTableId());
525: }
526:
527: resolveSelectableList(getValues(), db, getSourceTable());
528: assertRules(_sourceTable);
529: }
530: }
531:
532: protected final RowDecorator makeTargetRowDecorator() {
533: if (_trgtDec == null) {
534: _trgtDec = _targetTable.makeRowDecorator();
535: }
536: return _trgtDec;
537: }
538:
539: //-------------------------------------------------------------- Attributes
540:
541: private List _columnsForInsert;
542: private List _columnsForUpdate = new ArrayList();
543: private Selectable _condition;
544:
545: private int _count;
546: private ExceptionWhenClause _exceptionWhenClause;
547: private boolean _resolved = false;
548: private boolean _populateSequence = true;
549: private boolean _isExternalDBTable = false;
550:
551: private SubSelectCommand _selectCommand;
552: private Table _sourceTable;
553: private TableIdentifier _sourceTableId;
554: private Table _targetTable;
555: private RowDecorator _dec;
556: private RowDecorator _trgtDec;
557:
558: private TableIdentifier _targetTableId;
559: private SubSelectCommand _usingSubselect;
560: private String _usingSubSelectAlias;
561: private List _valuesForInsert;
562: private List _valuesForUpdate = new ArrayList();
563: }
|