EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.command.ddl]

COVERAGE SUMMARY FOR SOURCE FILE [AlterTableAddConstraint.java]

nameclass, %method, %block, %line, %
AlterTableAddConstraint.java100% (1/1)100% (25/25)96%  (943/987)95%  (206.8/217)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class AlterTableAddConstraint100% (1/1)100% (25/25)96%  (943/987)95%  (206.8/217)
tryUpdate (): int 100% (1/1)93%  (550/590)93%  (113.8/122)
canUseIndex (Index, Table, IndexColumn [], boolean): boolean 100% (1/1)95%  (81/85)88%  (15/17)
AlterTableAddConstraint (Session, Schema, boolean): void 100% (1/1)100% (11/11)100% (4/4)
canUseUniqueIndex (Index, Table, IndexColumn []): boolean 100% (1/1)100% (67/67)100% (12/12)
createIndex (Table, IndexColumn [], boolean): Index 100% (1/1)100% (72/72)100% (11/11)
generateConstraintName (Table): String 100% (1/1)100% (14/14)100% (3/3)
getIndex (Table, IndexColumn [], boolean): Index 100% (1/1)100% (22/22)100% (5/5)
getIndexColumns (): IndexColumn [] 100% (1/1)100% (3/3)100% (1/1)
getType (): int 100% (1/1)100% (3/3)100% (1/1)
getUniqueIndex (Table, IndexColumn []): Index 100% (1/1)100% (21/21)100% (5/5)
setCheckExisting (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCheckExpression (Expression): void 100% (1/1)100% (4/4)100% (2/2)
setComment (String): void 100% (1/1)100% (4/4)100% (2/2)
setConstraintName (String): void 100% (1/1)100% (4/4)100% (2/2)
setDeleteAction (int): void 100% (1/1)100% (4/4)100% (2/2)
setIndex (Index): void 100% (1/1)100% (4/4)100% (2/2)
setIndexColumns (IndexColumn []): void 100% (1/1)100% (4/4)100% (2/2)
setPrimaryKeyHash (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setRefIndex (Index): void 100% (1/1)100% (4/4)100% (2/2)
setRefIndexColumns (IndexColumn []): void 100% (1/1)100% (4/4)100% (2/2)
setRefTableName (Schema, String): void 100% (1/1)100% (7/7)100% (3/3)
setTableName (String): void 100% (1/1)100% (4/4)100% (2/2)
setType (int): void 100% (1/1)100% (4/4)100% (2/2)
setUpdateAction (int): void 100% (1/1)100% (4/4)100% (2/2)
update (): int 100% (1/1)100% (40/40)100% (7/7)

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 */
6package org.h2.command.ddl;
7 
8import java.util.ArrayList;
9import java.util.HashSet;
10 
11import org.h2.api.ErrorCode;
12import org.h2.command.CommandInterface;
13import org.h2.constraint.Constraint;
14import org.h2.constraint.ConstraintCheck;
15import org.h2.constraint.ConstraintReferential;
16import org.h2.constraint.ConstraintUnique;
17import org.h2.engine.Constants;
18import org.h2.engine.Database;
19import org.h2.engine.Right;
20import org.h2.engine.Session;
21import org.h2.expression.Expression;
22import org.h2.index.Index;
23import org.h2.index.IndexType;
24import org.h2.message.DbException;
25import org.h2.schema.Schema;
26import org.h2.table.Column;
27import org.h2.table.IndexColumn;
28import org.h2.table.Table;
29import org.h2.table.TableFilter;
30import org.h2.util.New;
31 
32/**
33 * This class represents the statement
34 * ALTER TABLE ADD CONSTRAINT
35 */
36public class AlterTableAddConstraint extends SchemaCommand {
37 
38    private int type;
39    private String constraintName;
40    private String tableName;
41    private IndexColumn[] indexColumns;
42    private int deleteAction;
43    private int updateAction;
44    private Schema refSchema;
45    private String refTableName;
46    private IndexColumn[] refIndexColumns;
47    private Expression checkExpression;
48    private Index index, refIndex;
49    private String comment;
50    private boolean checkExisting;
51    private boolean primaryKeyHash;
52    private final boolean ifNotExists;
53    private ArrayList<Index> createdIndexes = New.arrayList();
54 
55    public AlterTableAddConstraint(Session session, Schema schema,
56            boolean ifNotExists) {
57        super(session, schema);
58        this.ifNotExists = ifNotExists;
59    }
60 
61    private String generateConstraintName(Table table) {
62        if (constraintName == null) {
63            constraintName = getSchema().getUniqueConstraintName(
64                    session, table);
65        }
66        return constraintName;
67    }
68 
69    @Override
70    public int update() {
71        try {
72            return tryUpdate();
73        } catch (DbException e) {
74            for (Index index : createdIndexes) {
75                session.getDatabase().removeSchemaObject(session, index);
76            }
77            throw e;
78        } finally {
79            getSchema().freeUniqueName(constraintName);
80        }
81    }
82 
83    /**
84     * Try to execute the statement.
85     *
86     * @return the update count
87     */
88    private int tryUpdate() {
89        if (!transactional) {
90            session.commit(true);
91        }
92        Database db = session.getDatabase();
93        Table table = getSchema().getTableOrView(session, tableName);
94        if (getSchema().findConstraint(session, constraintName) != null) {
95            if (ifNotExists) {
96                return 0;
97            }
98            throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1,
99                    constraintName);
100        }
101        session.getUser().checkRight(table, Right.ALL);
102        db.lockMeta(session);
103        table.lock(session, true, true);
104        Constraint constraint;
105        switch (type) {
106        case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY: {
107            IndexColumn.mapColumns(indexColumns, table);
108            index = table.findPrimaryKey();
109            ArrayList<Constraint> constraints = table.getConstraints();
110            for (int i = 0; constraints != null && i < constraints.size(); i++) {
111                Constraint c = constraints.get(i);
112                if (Constraint.PRIMARY_KEY.equals(c.getConstraintType())) {
113                    throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY);
114                }
115            }
116            if (index != null) {
117                // if there is an index, it must match with the one declared
118                // we don't test ascending / descending
119                IndexColumn[] pkCols = index.getIndexColumns();
120                if (pkCols.length != indexColumns.length) {
121                    throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY);
122                }
123                for (int i = 0; i < pkCols.length; i++) {
124                    if (pkCols[i].column != indexColumns[i].column) {
125                        throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY);
126                    }
127                }
128            }
129            if (index == null) {
130                IndexType indexType = IndexType.createPrimaryKey(
131                        table.isPersistIndexes(), primaryKeyHash);
132                String indexName = table.getSchema().getUniqueIndexName(
133                        session, table, Constants.PREFIX_PRIMARY_KEY);
134                int id = getObjectId();
135                try {
136                    index = table.addIndex(session, indexName, id,
137                            indexColumns, indexType, true, null);
138                } finally {
139                    getSchema().freeUniqueName(indexName);
140                }
141            }
142            index.getIndexType().setBelongsToConstraint(true);
143            int constraintId = getObjectId();
144            String name = generateConstraintName(table);
145            ConstraintUnique pk = new ConstraintUnique(getSchema(),
146                    constraintId, name, table, true);
147            pk.setColumns(indexColumns);
148            pk.setIndex(index, true);
149            constraint = pk;
150            break;
151        }
152        case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_UNIQUE: {
153            IndexColumn.mapColumns(indexColumns, table);
154            boolean isOwner = false;
155            if (index != null && canUseUniqueIndex(index, table, indexColumns)) {
156                isOwner = true;
157                index.getIndexType().setBelongsToConstraint(true);
158            } else {
159                index = getUniqueIndex(table, indexColumns);
160                if (index == null) {
161                    index = createIndex(table, indexColumns, true);
162                    isOwner = true;
163                }
164            }
165            int id = getObjectId();
166            String name = generateConstraintName(table);
167            ConstraintUnique unique = new ConstraintUnique(getSchema(), id,
168                    name, table, false);
169            unique.setColumns(indexColumns);
170            unique.setIndex(index, isOwner);
171            constraint = unique;
172            break;
173        }
174        case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_CHECK: {
175            int id = getObjectId();
176            String name = generateConstraintName(table);
177            ConstraintCheck check = new ConstraintCheck(getSchema(), id, name, table);
178            TableFilter filter = new TableFilter(session, table, null, false, null);
179            checkExpression.mapColumns(filter, 0);
180            checkExpression = checkExpression.optimize(session);
181            check.setExpression(checkExpression);
182            check.setTableFilter(filter);
183            constraint = check;
184            if (checkExisting) {
185                check.checkExistingData(session);
186            }
187            break;
188        }
189        case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_REFERENTIAL: {
190            Table refTable = refSchema.getTableOrView(session, refTableName);
191            session.getUser().checkRight(refTable, Right.ALL);
192            if (!refTable.canReference()) {
193                throw DbException.getUnsupportedException("Reference " +
194                        refTable.getSQL());
195            }
196            boolean isOwner = false;
197            IndexColumn.mapColumns(indexColumns, table);
198            if (index != null && canUseIndex(index, table, indexColumns, false)) {
199                isOwner = true;
200                index.getIndexType().setBelongsToConstraint(true);
201            } else {
202                if (db.isStarting()) {
203                    // before version 1.3.176, an existing index was used:
204                    // must do the same to avoid
205                    // Unique index or primary key violation:
206                    // "PRIMARY KEY ON """".PAGE_INDEX"
207                    index = getIndex(table, indexColumns, true);
208                } else {
209                    index = getIndex(table, indexColumns, false);
210                }
211                if (index == null) {
212                    index = createIndex(table, indexColumns, false);
213                    isOwner = true;
214                }
215            }
216            if (refIndexColumns == null) {
217                Index refIdx = refTable.getPrimaryKey();
218                refIndexColumns = refIdx.getIndexColumns();
219            } else {
220                IndexColumn.mapColumns(refIndexColumns, refTable);
221            }
222            if (refIndexColumns.length != indexColumns.length) {
223                throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
224            }
225            boolean isRefOwner = false;
226            if (refIndex != null && refIndex.getTable() == refTable &&
227                    canUseIndex(refIndex, refTable, refIndexColumns, false)) {
228                isRefOwner = true;
229                refIndex.getIndexType().setBelongsToConstraint(true);
230            } else {
231                refIndex = null;
232            }
233            if (refIndex == null) {
234                refIndex = getIndex(refTable, refIndexColumns, false);
235                if (refIndex == null) {
236                    refIndex = createIndex(refTable, refIndexColumns, true);
237                    isRefOwner = true;
238                }
239            }
240            int id = getObjectId();
241            String name = generateConstraintName(table);
242            ConstraintReferential ref = new ConstraintReferential(getSchema(),
243                    id, name, table);
244            ref.setColumns(indexColumns);
245            ref.setIndex(index, isOwner);
246            ref.setRefTable(refTable);
247            ref.setRefColumns(refIndexColumns);
248            ref.setRefIndex(refIndex, isRefOwner);
249            if (checkExisting) {
250                ref.checkExistingData(session);
251            }
252            constraint = ref;
253            refTable.addConstraint(constraint);
254            ref.setDeleteAction(deleteAction);
255            ref.setUpdateAction(updateAction);
256            break;
257        }
258        default:
259            throw DbException.throwInternalError("type=" + type);
260        }
261        // parent relationship is already set with addConstraint
262        constraint.setComment(comment);
263        if (table.isTemporary() && !table.isGlobalTemporary()) {
264            session.addLocalTempTableConstraint(constraint);
265        } else {
266            db.addSchemaObject(session, constraint);
267        }
268        table.addConstraint(constraint);
269        return 0;
270    }
271 
272    private Index createIndex(Table t, IndexColumn[] cols, boolean unique) {
273        int indexId = getObjectId();
274        IndexType indexType;
275        if (unique) {
276            // for unique constraints
277            indexType = IndexType.createUnique(t.isPersistIndexes(), false);
278        } else {
279            // constraints
280            indexType = IndexType.createNonUnique(t.isPersistIndexes());
281        }
282        indexType.setBelongsToConstraint(true);
283        String prefix = constraintName == null ? "CONSTRAINT" : constraintName;
284        String indexName = t.getSchema().getUniqueIndexName(session, t,
285                prefix + "_INDEX_");
286        try {
287            Index index = t.addIndex(session, indexName, indexId, cols,
288                    indexType, true, null);
289            createdIndexes.add(index);
290            return index;
291        } finally {
292            getSchema().freeUniqueName(indexName);
293        }
294    }
295 
296    public void setDeleteAction(int action) {
297        this.deleteAction = action;
298    }
299 
300    public void setUpdateAction(int action) {
301        this.updateAction = action;
302    }
303 
304    private static Index getUniqueIndex(Table t, IndexColumn[] cols) {
305        for (Index idx : t.getIndexes()) {
306            if (canUseUniqueIndex(idx, t, cols)) {
307                return idx;
308            }
309        }
310        return null;
311    }
312 
313    private static Index getIndex(Table t, IndexColumn[] cols, boolean moreColumnOk) {
314        for (Index idx : t.getIndexes()) {
315            if (canUseIndex(idx, t, cols, moreColumnOk)) {
316                return idx;
317            }
318        }
319        return null;
320    }
321 
322    private static boolean canUseUniqueIndex(Index idx, Table table,
323            IndexColumn[] cols) {
324        if (idx.getTable() != table || !idx.getIndexType().isUnique()) {
325            return false;
326        }
327        Column[] indexCols = idx.getColumns();
328        if (indexCols.length > cols.length) {
329            return false;
330        }
331        HashSet<Column> set = New.hashSet();
332        for (IndexColumn c : cols) {
333            set.add(c.column);
334        }
335        for (Column c : indexCols) {
336            // all columns of the index must be part of the list,
337            // but not all columns of the list need to be part of the index
338            if (!set.contains(c)) {
339                return false;
340            }
341        }
342        return true;
343    }
344 
345    private static boolean canUseIndex(Index existingIndex, Table table,
346            IndexColumn[] cols, boolean moreColumnsOk) {
347        if (existingIndex.getTable() != table || existingIndex.getCreateSQL() == null) {
348            // can't use the scan index or index of another table
349            return false;
350        }
351        Column[] indexCols = existingIndex.getColumns();
352 
353        if (moreColumnsOk) {
354            if (indexCols.length < cols.length) {
355                return false;
356            }
357            for (IndexColumn col : cols) {
358                // all columns of the list must be part of the index,
359                // but not all columns of the index need to be part of the list
360                // holes are not allowed (index=a,b,c & list=a,b is ok;
361                // but list=a,c is not)
362                int idx = existingIndex.getColumnIndex(col.column);
363                if (idx < 0 || idx >= cols.length) {
364                    return false;
365                }
366            }
367        } else {
368            if (indexCols.length != cols.length) {
369                return false;
370            }
371            for (IndexColumn col : cols) {
372                // all columns of the list must be part of the index
373                int idx = existingIndex.getColumnIndex(col.column);
374                if (idx < 0) {
375                    return false;
376                }
377            }
378        }
379        return true;
380    }
381 
382    public void setConstraintName(String constraintName) {
383        this.constraintName = constraintName;
384    }
385 
386    public void setType(int type) {
387        this.type = type;
388    }
389 
390    @Override
391    public int getType() {
392        return type;
393    }
394 
395    public void setCheckExpression(Expression expression) {
396        this.checkExpression = expression;
397    }
398 
399    public void setTableName(String tableName) {
400        this.tableName = tableName;
401    }
402 
403    public void setIndexColumns(IndexColumn[] indexColumns) {
404        this.indexColumns = indexColumns;
405    }
406 
407    public IndexColumn[] getIndexColumns() {
408        return indexColumns;
409    }
410 
411    /**
412     * Set the referenced table.
413     *
414     * @param refSchema the schema
415     * @param ref the table name
416     */
417    public void setRefTableName(Schema refSchema, String ref) {
418        this.refSchema = refSchema;
419        this.refTableName = ref;
420    }
421 
422    public void setRefIndexColumns(IndexColumn[] indexColumns) {
423        this.refIndexColumns = indexColumns;
424    }
425 
426    public void setIndex(Index index) {
427        this.index = index;
428    }
429 
430    public void setRefIndex(Index refIndex) {
431        this.refIndex = refIndex;
432    }
433 
434    public void setComment(String comment) {
435        this.comment = comment;
436    }
437 
438    public void setCheckExisting(boolean b) {
439        this.checkExisting = b;
440    }
441 
442    public void setPrimaryKeyHash(boolean b) {
443        this.primaryKeyHash = b;
444    }
445 
446}

[all classes][org.h2.command.ddl]
EMMA 2.0.5312 (C) Vladimir Roubtsov