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

COVERAGE SUMMARY FOR SOURCE FILE [TriggerObject.java]

nameclass, %method, %block, %line, %
TriggerObject.java100% (1/1)90%  (28/31)87%  (643/742)90%  (167.9/186)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class TriggerObject100% (1/1)90%  (28/31)87%  (643/742)90%  (167.9/186)
getDropSQL (): String 0%   (0/1)0%   (0/2)0%   (0/1)
getTriggerSource (): String 0%   (0/1)0%   (0/3)0%   (0/1)
setTriggerSource (String, boolean): void 0%   (0/1)0%   (0/6)0%   (0/2)
fire (Session, int, boolean): void 100% (1/1)55%  (49/89)79%  (11.8/15)
loadFromSource (): Trigger 100% (1/1)74%  (48/65)69%  (9/13)
isSelectTrigger (): boolean 100% (1/1)78%  (7/9)77%  (0.8/1)
removeChildrenAndResources (Session): void 100% (1/1)89%  (32/36)85%  (11/13)
getCreateSQLForCopy (Table, String): String 100% (1/1)91%  (78/86)94%  (17/18)
fireRow (Session, Row, Row, boolean, boolean): boolean 100% (1/1)92%  (165/179)94%  (40.3/43)
load (): void 100% (1/1)96%  (73/76)93%  (13/14)
TriggerObject (Schema, int, String, Table): void 100% (1/1)100% (19/19)100% (6/6)
checkRename (): void 100% (1/1)100% (1/1)100% (1/1)
close (): void 100% (1/1)100% (7/7)100% (3/3)
convertToObjectList (Row): Object [] 100% (1/1)100% (26/26)100% (7/7)
getCreateSQL (): String 100% (1/1)100% (7/7)100% (1/1)
getQueueSize (): int 100% (1/1)100% (3/3)100% (1/1)
getTable (): Table 100% (1/1)100% (3/3)100% (1/1)
getTriggerClassName (): String 100% (1/1)100% (3/3)100% (1/1)
getType (): int 100% (1/1)100% (2/2)100% (1/1)
getTypeNameList (): String 100% (1/1)100% (65/65)100% (17/17)
isBefore (): boolean 100% (1/1)100% (3/3)100% (1/1)
isNoWait (): boolean 100% (1/1)100% (3/3)100% (1/1)
setBefore (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setInsteadOf (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setNoWait (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setOnRollback (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setQueueSize (int): void 100% (1/1)100% (4/4)100% (2/2)
setRowBased (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setTriggerAction (String, String, boolean): void 100% (1/1)100% (15/15)100% (8/8)
setTriggerClassName (String, boolean): void 100% (1/1)100% (6/6)100% (2/2)
setTypeMask (int): void 100% (1/1)100% (4/4)100% (2/2)

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.schema;
7 
8import java.lang.reflect.Method;
9import java.sql.Connection;
10import java.sql.SQLException;
11 
12import org.h2.api.ErrorCode;
13import org.h2.api.Trigger;
14import org.h2.command.Parser;
15import org.h2.engine.Constants;
16import org.h2.engine.DbObject;
17import org.h2.engine.Session;
18import org.h2.message.DbException;
19import org.h2.message.Trace;
20import org.h2.result.Row;
21import org.h2.table.Table;
22import org.h2.util.JdbcUtils;
23import org.h2.util.SourceCompiler;
24import org.h2.util.StatementBuilder;
25import org.h2.util.StringUtils;
26import org.h2.value.DataType;
27import org.h2.value.Value;
28 
29/**
30 *A trigger is created using the statement
31 * CREATE TRIGGER
32 */
33public class TriggerObject extends SchemaObjectBase {
34 
35    /**
36     * The default queue size.
37     */
38    public static final int DEFAULT_QUEUE_SIZE = 1024;
39 
40    private boolean insteadOf;
41    private boolean before;
42    private int typeMask;
43    private boolean rowBased;
44    private boolean onRollback;
45    // TODO trigger: support queue and noWait = false as well
46    private int queueSize = DEFAULT_QUEUE_SIZE;
47    private boolean noWait;
48    private Table table;
49    private String triggerClassName;
50    private String triggerSource;
51    private Trigger triggerCallback;
52 
53    public TriggerObject(Schema schema, int id, String name, Table table) {
54        initSchemaObjectBase(schema, id, name, Trace.TRIGGER);
55        this.table = table;
56        setTemporary(table.isTemporary());
57    }
58 
59    public void setBefore(boolean before) {
60        this.before = before;
61    }
62 
63    public void setInsteadOf(boolean insteadOf) {
64        this.insteadOf = insteadOf;
65    }
66 
67    private synchronized void load() {
68        if (triggerCallback != null) {
69            return;
70        }
71        try {
72            Session sysSession = database.getSystemSession();
73            Connection c2 = sysSession.createConnection(false);
74            Object obj;
75            if (triggerClassName != null) {
76                obj = JdbcUtils.loadUserClass(triggerClassName).newInstance();
77            } else {
78                obj = loadFromSource();
79            }
80            triggerCallback = (Trigger) obj;
81            triggerCallback.init(c2, getSchema().getName(), getName(),
82                    table.getName(), before, typeMask);
83        } catch (Throwable e) {
84            // try again later
85            triggerCallback = null;
86            throw DbException.get(ErrorCode.ERROR_CREATING_TRIGGER_OBJECT_3, e, getName(),
87                triggerClassName != null ? triggerClassName : "..source..", e.toString());
88        }
89    }
90 
91    private Trigger loadFromSource() {
92        SourceCompiler compiler = database.getCompiler();
93        synchronized (compiler) {
94            String fullClassName = Constants.USER_PACKAGE + ".trigger." + getName();
95            compiler.setSource(fullClassName, triggerSource);
96            try {
97                Method m = compiler.getMethod(fullClassName);
98                if (m.getParameterTypes().length > 0) {
99                    throw new IllegalStateException("No parameters are allowed for a trigger");
100                }
101                return (Trigger) m.invoke(null);
102            } catch (DbException e) {
103                throw e;
104            } catch (Exception e) {
105                throw DbException.get(ErrorCode.SYNTAX_ERROR_1, e, triggerSource);
106            }
107        }
108    }
109 
110    /**
111     * Set the trigger class name and load the class if possible.
112     *
113     * @param triggerClassName the name of the trigger class
114     * @param force whether exceptions (due to missing class or access rights)
115     *            should be ignored
116     */
117    public void setTriggerClassName(String triggerClassName, boolean force) {
118        this.setTriggerAction(triggerClassName, null, force);
119    }
120 
121    /**
122     * Set the trigger source code and compile it if possible.
123     *
124     * @param source the source code of a method returning a {@link Trigger}
125     * @param force whether exceptions (due to syntax error)
126     *            should be ignored
127     */
128    public void setTriggerSource(String source, boolean force) {
129        this.setTriggerAction(null, source, force);
130    }
131 
132    private void setTriggerAction(String triggerClassName, String source, boolean force) {
133        this.triggerClassName = triggerClassName;
134        this.triggerSource = source;
135        try {
136            load();
137        } catch (DbException e) {
138            if (!force) {
139                throw e;
140            }
141        }
142    }
143 
144    /**
145     * Call the trigger class if required. This method does nothing if the
146     * trigger is not defined for the given action. This method is called before
147     * or after any rows have been processed, once for each statement.
148     *
149     * @param session the session
150     * @param type the trigger type
151     * @param beforeAction if this method is called before applying the changes
152     */
153    public void fire(Session session, int type, boolean beforeAction) {
154        if (rowBased || before != beforeAction || (typeMask & type) == 0) {
155            return;
156        }
157        load();
158        Connection c2 = session.createConnection(false);
159        boolean old = false;
160        if (type != Trigger.SELECT) {
161            old = session.setCommitOrRollbackDisabled(true);
162        }
163        Value identity = session.getLastScopeIdentity();
164        try {
165            triggerCallback.fire(c2, null, null);
166        } catch (Throwable e) {
167            throw DbException.get(ErrorCode.ERROR_EXECUTING_TRIGGER_3, e, getName(),
168                    triggerClassName != null ? triggerClassName : "..source..", e.toString());
169        } finally {
170            session.setLastScopeIdentity(identity);
171            if (type != Trigger.SELECT) {
172                session.setCommitOrRollbackDisabled(old);
173            }
174        }
175    }
176 
177    private static Object[] convertToObjectList(Row row) {
178        if (row == null) {
179            return null;
180        }
181        int len = row.getColumnCount();
182        Object[] list = new Object[len];
183        for (int i = 0; i < len; i++) {
184            list[i] = row.getValue(i).getObject();
185        }
186        return list;
187    }
188 
189    /**
190     * Call the fire method of the user-defined trigger class if required. This
191     * method does nothing if the trigger is not defined for the given action.
192     * This method is called before or after a row is processed, possibly many
193     * times for each statement.
194     *
195     * @param session the session
196     * @param oldRow the old row
197     * @param newRow the new row
198     * @param beforeAction true if this method is called before the operation is
199     *            applied
200     * @param rollback when the operation occurred within a rollback
201     * @return true if no further action is required (for 'instead of' triggers)
202     */
203    public boolean fireRow(Session session, Row oldRow, Row newRow,
204            boolean beforeAction, boolean rollback) {
205        if (!rowBased || before != beforeAction) {
206            return false;
207        }
208        if (rollback && !onRollback) {
209            return false;
210        }
211        load();
212        Object[] oldList;
213        Object[] newList;
214        boolean fire = false;
215        if ((typeMask & Trigger.INSERT) != 0) {
216            if (oldRow == null && newRow != null) {
217                fire = true;
218            }
219        }
220        if ((typeMask & Trigger.UPDATE) != 0) {
221            if (oldRow != null && newRow != null) {
222                fire = true;
223            }
224        }
225        if ((typeMask & Trigger.DELETE) != 0) {
226            if (oldRow != null && newRow == null) {
227                fire = true;
228            }
229        }
230        if (!fire) {
231            return false;
232        }
233        oldList = convertToObjectList(oldRow);
234        newList = convertToObjectList(newRow);
235        Object[] newListBackup;
236        if (before && newList != null) {
237            newListBackup = new Object[newList.length];
238            System.arraycopy(newList, 0, newListBackup, 0, newList.length);
239        } else {
240            newListBackup = null;
241        }
242        Connection c2 = session.createConnection(false);
243        boolean old = session.getAutoCommit();
244        boolean oldDisabled = session.setCommitOrRollbackDisabled(true);
245        Value identity = session.getLastScopeIdentity();
246        try {
247            session.setAutoCommit(false);
248            triggerCallback.fire(c2, oldList, newList);
249            if (newListBackup != null) {
250                for (int i = 0; i < newList.length; i++) {
251                    Object o = newList[i];
252                    if (o != newListBackup[i]) {
253                        Value v = DataType.convertToValue(session, o, Value.UNKNOWN);
254                        newRow.setValue(i, v);
255                    }
256                }
257            }
258        } catch (Exception e) {
259            if (onRollback) {
260                // ignore
261            } else {
262                throw DbException.convert(e);
263            }
264        } finally {
265            session.setLastScopeIdentity(identity);
266            session.setCommitOrRollbackDisabled(oldDisabled);
267            session.setAutoCommit(old);
268        }
269        return insteadOf;
270    }
271 
272    /**
273     * Set the trigger type.
274     *
275     * @param typeMask the type
276     */
277    public void setTypeMask(int typeMask) {
278        this.typeMask = typeMask;
279    }
280 
281    public void setRowBased(boolean rowBased) {
282        this.rowBased = rowBased;
283    }
284 
285    public void setQueueSize(int size) {
286        this.queueSize = size;
287    }
288 
289    public int getQueueSize() {
290        return queueSize;
291    }
292 
293    public void setNoWait(boolean noWait) {
294        this.noWait = noWait;
295    }
296 
297    public boolean isNoWait() {
298        return noWait;
299    }
300 
301    public void setOnRollback(boolean onRollback) {
302        this.onRollback = onRollback;
303    }
304 
305    @Override
306    public String getDropSQL() {
307        return null;
308    }
309 
310    @Override
311    public String getCreateSQLForCopy(Table targetTable, String quotedName) {
312        StringBuilder buff = new StringBuilder("CREATE FORCE TRIGGER ");
313        buff.append(quotedName);
314        if (insteadOf) {
315            buff.append(" INSTEAD OF ");
316        } else if (before) {
317            buff.append(" BEFORE ");
318        } else {
319            buff.append(" AFTER ");
320        }
321        buff.append(getTypeNameList());
322        buff.append(" ON ").append(targetTable.getSQL());
323        if (rowBased) {
324            buff.append(" FOR EACH ROW");
325        }
326        if (noWait) {
327            buff.append(" NOWAIT");
328        } else {
329            buff.append(" QUEUE ").append(queueSize);
330        }
331        if (triggerClassName != null) {
332            buff.append(" CALL ").append(Parser.quoteIdentifier(triggerClassName));
333        } else {
334            buff.append(" AS ").append(StringUtils.quoteStringSQL(triggerSource));
335        }
336        return buff.toString();
337    }
338 
339    public String getTypeNameList() {
340        StatementBuilder buff = new StatementBuilder();
341        if ((typeMask & Trigger.INSERT) != 0) {
342            buff.appendExceptFirst(", ");
343            buff.append("INSERT");
344        }
345        if ((typeMask & Trigger.UPDATE) != 0) {
346            buff.appendExceptFirst(", ");
347            buff.append("UPDATE");
348        }
349        if ((typeMask & Trigger.DELETE) != 0) {
350            buff.appendExceptFirst(", ");
351            buff.append("DELETE");
352        }
353        if ((typeMask & Trigger.SELECT) != 0) {
354            buff.appendExceptFirst(", ");
355            buff.append("SELECT");
356        }
357        if (onRollback) {
358            buff.appendExceptFirst(", ");
359            buff.append("ROLLBACK");
360        }
361        return buff.toString();
362    }
363 
364    @Override
365    public String getCreateSQL() {
366        return getCreateSQLForCopy(table, getSQL());
367    }
368 
369    @Override
370    public int getType() {
371        return DbObject.TRIGGER;
372    }
373 
374    @Override
375    public void removeChildrenAndResources(Session session) {
376        table.removeTrigger(this);
377        database.removeMeta(session, getId());
378        if (triggerCallback != null) {
379            try {
380                triggerCallback.remove();
381            } catch (SQLException e) {
382                throw DbException.convert(e);
383            }
384        }
385        table = null;
386        triggerClassName = null;
387        triggerSource = null;
388        triggerCallback = null;
389        invalidate();
390    }
391 
392    @Override
393    public void checkRename() {
394        // nothing to do
395    }
396 
397    /**
398     * Get the table of this trigger.
399     *
400     * @return the table
401     */
402    public Table getTable() {
403        return table;
404    }
405 
406    /**
407     * Check if this is a before trigger.
408     *
409     * @return true if it is
410     */
411    public boolean isBefore() {
412        return before;
413    }
414 
415    /**
416     * Get the trigger class name.
417     *
418     * @return the class name
419     */
420    public String getTriggerClassName() {
421        return triggerClassName;
422    }
423 
424    public String getTriggerSource() {
425        return triggerSource;
426    }
427 
428    /**
429     * Close the trigger.
430     */
431    public void close() throws SQLException {
432        if (triggerCallback != null) {
433            triggerCallback.close();
434        }
435    }
436 
437    /**
438     * Check whether this is a select trigger.
439     *
440     * @return true if it is
441     */
442    public boolean isSelectTrigger() {
443        return (typeMask & Trigger.SELECT) != 0;
444    }
445 
446}

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