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

COVERAGE SUMMARY FOR SOURCE FILE [FunctionAlias.java]

nameclass, %method, %block, %line, %
FunctionAlias.java100% (2/2)94%  (31/33)90%  (936/1035)90%  (196/217)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class FunctionAlias100% (1/1)92%  (22/24)88%  (487/552)88%  (104.8/119)
checkRename (): void 0%   (0/1)0%   (0/3)0%   (0/1)
getCreateSQLForCopy (Table, String): String 0%   (0/1)0%   (0/2)0%   (0/1)
getSQL (): String 100% (1/1)44%  (8/18)48%  (1.4/3)
loadFromSource (): void 100% (1/1)70%  (46/66)67%  (9.4/14)
isVarArgs (Method): boolean 100% (1/1)81%  (21/26)57%  (4/7)
loadClass (): void 100% (1/1)85%  (98/115)95%  (19/20)
newInstance (Schema, int, String, String, boolean, boolean): FunctionAlias 100% (1/1)92%  (44/48)90%  (9/10)
getCreateSQL (): String 100% (1/1)93%  (53/57)90%  (9/10)
FunctionAlias (Schema, int, String): void 100% (1/1)100% (12/12)100% (4/4)
findJavaMethod (Expression []): FunctionAlias$JavaMethod 100% (1/1)100% (59/59)100% (7/7)
getDropSQL (): String 100% (1/1)100% (10/10)100% (1/1)
getJavaClassName (): String 100% (1/1)100% (3/3)100% (1/1)
getJavaMethodName (): String 100% (1/1)100% (3/3)100% (1/1)
getJavaMethods (): FunctionAlias$JavaMethod [] 100% (1/1)100% (5/5)100% (2/2)
getMethodSignature (Method): String 100% (1/1)100% (52/52)100% (8/8)
getSource (): String 100% (1/1)100% (3/3)100% (1/1)
getType (): int 100% (1/1)100% (2/2)100% (1/1)
init (boolean): void 100% (1/1)100% (9/9)100% (6/6)
isBufferResultSetToLocalTemp (): boolean 100% (1/1)100% (3/3)100% (1/1)
isDeterministic (): boolean 100% (1/1)100% (3/3)100% (1/1)
load (): void 100% (1/1)100% (13/13)100% (6/6)
newInstanceFromSource (Schema, int, String, String, boolean, boolean): Functi... 100% (1/1)100% (18/18)100% (5/5)
removeChildrenAndResources (Session): void 100% (1/1)100% (18/18)100% (6/6)
setDeterministic (boolean): void 100% (1/1)100% (4/4)100% (2/2)
     
class FunctionAlias$JavaMethod100% (1/1)100% (9/9)93%  (449/483)93%  (91.2/98)
compareTo (FunctionAlias$JavaMethod): int 100% (1/1)51%  (21/41)53%  (3.7/7)
getValue (Session, Expression [], boolean): Value 100% (1/1)96%  (342/356)95%  (63.5/67)
FunctionAlias$JavaMethod (Method, int): void 100% (1/1)100% (66/66)100% (18/18)
getColumnClasses (): Class [] 100% (1/1)100% (4/4)100% (1/1)
getDataType (): int 100% (1/1)100% (3/3)100% (1/1)
getParameterCount (): int 100% (1/1)100% (3/3)100% (1/1)
hasConnectionParam (): boolean 100% (1/1)100% (3/3)100% (1/1)
isVarArgs (): boolean 100% (1/1)100% (3/3)100% (1/1)
toString (): String 100% (1/1)100% (4/4)100% (1/1)

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.engine;
7 
8import java.lang.reflect.Array;
9import java.lang.reflect.InvocationTargetException;
10import java.lang.reflect.Method;
11import java.lang.reflect.Modifier;
12import java.sql.Connection;
13import java.util.ArrayList;
14import java.util.Arrays;
15 
16import org.h2.Driver;
17import org.h2.api.ErrorCode;
18import org.h2.command.Parser;
19import org.h2.expression.Expression;
20import org.h2.message.DbException;
21import org.h2.message.Trace;
22import org.h2.schema.Schema;
23import org.h2.schema.SchemaObjectBase;
24import org.h2.table.Table;
25import org.h2.util.JdbcUtils;
26import org.h2.util.New;
27import org.h2.util.SourceCompiler;
28import org.h2.util.StatementBuilder;
29import org.h2.util.StringUtils;
30import org.h2.value.DataType;
31import org.h2.value.Value;
32import org.h2.value.ValueArray;
33import org.h2.value.ValueNull;
34 
35/**
36 * Represents a user-defined function, or alias.
37 *
38 * @author Thomas Mueller
39 * @author Gary Tong
40 */
41public class FunctionAlias extends SchemaObjectBase {
42 
43    private String className;
44    private String methodName;
45    private String source;
46    private JavaMethod[] javaMethods;
47    private boolean deterministic;
48    private boolean bufferResultSetToLocalTemp = true;
49 
50    private FunctionAlias(Schema schema, int id, String name) {
51        initSchemaObjectBase(schema, id, name, Trace.FUNCTION);
52    }
53 
54    /**
55     * Create a new alias based on a method name.
56     *
57     * @param schema the schema
58     * @param id the id
59     * @param name the name
60     * @param javaClassMethod the class and method name
61     * @param force create the object even if the class or method does not exist
62     * @param bufferResultSetToLocalTemp whether the result should be buffered
63     * @return the database object
64     */
65    public static FunctionAlias newInstance(
66            Schema schema, int id, String name, String javaClassMethod,
67            boolean force, boolean bufferResultSetToLocalTemp) {
68        FunctionAlias alias = new FunctionAlias(schema, id, name);
69        int paren = javaClassMethod.indexOf('(');
70        int lastDot = javaClassMethod.lastIndexOf('.', paren < 0 ?
71                javaClassMethod.length() : paren);
72        if (lastDot < 0) {
73            throw DbException.get(ErrorCode.SYNTAX_ERROR_1, javaClassMethod);
74        }
75        alias.className = javaClassMethod.substring(0, lastDot);
76        alias.methodName = javaClassMethod.substring(lastDot + 1);
77        alias.bufferResultSetToLocalTemp = bufferResultSetToLocalTemp;
78        alias.init(force);
79        return alias;
80    }
81 
82    /**
83     * Create a new alias based on source code.
84     *
85     * @param schema the schema
86     * @param id the id
87     * @param name the name
88     * @param source the source code
89     * @param force create the object even if the class or method does not exist
90     * @param bufferResultSetToLocalTemp whether the result should be buffered
91     * @return the database object
92     */
93    public static FunctionAlias newInstanceFromSource(
94            Schema schema, int id, String name, String source, boolean force,
95            boolean bufferResultSetToLocalTemp) {
96        FunctionAlias alias = new FunctionAlias(schema, id, name);
97        alias.source = source;
98        alias.bufferResultSetToLocalTemp = bufferResultSetToLocalTemp;
99        alias.init(force);
100        return alias;
101    }
102 
103    private void init(boolean force) {
104        try {
105            // at least try to compile the class, otherwise the data type is not
106            // initialized if it could be
107            load();
108        } catch (DbException e) {
109            if (!force) {
110                throw e;
111            }
112        }
113    }
114 
115    private synchronized void load() {
116        if (javaMethods != null) {
117            return;
118        }
119        if (source != null) {
120            loadFromSource();
121        } else {
122            loadClass();
123        }
124    }
125 
126    private void loadFromSource() {
127        SourceCompiler compiler = database.getCompiler();
128        synchronized (compiler) {
129            String fullClassName = Constants.USER_PACKAGE + "." + getName();
130            compiler.setSource(fullClassName, source);
131            try {
132                Method m = compiler.getMethod(fullClassName);
133                JavaMethod method = new JavaMethod(m, 0);
134                javaMethods = new JavaMethod[] {
135                        method
136                };
137            } catch (DbException e) {
138                throw e;
139            } catch (Exception e) {
140                throw DbException.get(ErrorCode.SYNTAX_ERROR_1, e, source);
141            }
142        }
143    }
144 
145    private void loadClass() {
146        Class<?> javaClass = JdbcUtils.loadUserClass(className);
147        Method[] methods = javaClass.getMethods();
148        ArrayList<JavaMethod> list = New.arrayList();
149        for (int i = 0, len = methods.length; i < len; i++) {
150            Method m = methods[i];
151            if (!Modifier.isStatic(m.getModifiers())) {
152                continue;
153            }
154            if (m.getName().equals(methodName) ||
155                    getMethodSignature(m).equals(methodName)) {
156                JavaMethod javaMethod = new JavaMethod(m, i);
157                for (JavaMethod old : list) {
158                    if (old.getParameterCount() == javaMethod.getParameterCount()) {
159                        throw DbException.get(ErrorCode.
160                                METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2,
161                                old.toString(), javaMethod.toString());
162                    }
163                }
164                list.add(javaMethod);
165            }
166        }
167        if (list.size() == 0) {
168            throw DbException.get(
169                    ErrorCode.PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1,
170                    methodName + " (" + className + ")");
171        }
172        javaMethods = new JavaMethod[list.size()];
173        list.toArray(javaMethods);
174        // Sort elements. Methods with a variable number of arguments must be at
175        // the end. Reason: there could be one method without parameters and one
176        // with a variable number. The one without parameters needs to be used
177        // if no parameters are given.
178        Arrays.sort(javaMethods);
179    }
180 
181    private static String getMethodSignature(Method m) {
182        StatementBuilder buff = new StatementBuilder(m.getName());
183        buff.append('(');
184        for (Class<?> p : m.getParameterTypes()) {
185            // do not use a space here, because spaces are removed
186            // in CreateFunctionAlias.setJavaClassMethod()
187            buff.appendExceptFirst(",");
188            if (p.isArray()) {
189                buff.append(p.getComponentType().getName()).append("[]");
190            } else {
191                buff.append(p.getName());
192            }
193        }
194        return buff.append(')').toString();
195    }
196 
197    @Override
198    public String getCreateSQLForCopy(Table table, String quotedName) {
199        throw DbException.throwInternalError();
200    }
201 
202    @Override
203    public String getDropSQL() {
204        return "DROP ALIAS IF EXISTS " + getSQL();
205    }
206 
207    @Override
208    public String getSQL() {
209        // TODO can remove this method once FUNCTIONS_IN_SCHEMA is enabled
210        if (database.getSettings().functionsInSchema ||
211                !getSchema().getName().equals(Constants.SCHEMA_MAIN)) {
212            return super.getSQL();
213        }
214        return Parser.quoteIdentifier(getName());
215    }
216 
217    @Override
218    public String getCreateSQL() {
219        StringBuilder buff = new StringBuilder("CREATE FORCE ALIAS ");
220        buff.append(getSQL());
221        if (deterministic) {
222            buff.append(" DETERMINISTIC");
223        }
224        if (!bufferResultSetToLocalTemp) {
225            buff.append(" NOBUFFER");
226        }
227        if (source != null) {
228            buff.append(" AS ").append(StringUtils.quoteStringSQL(source));
229        } else {
230            buff.append(" FOR ").append(Parser.quoteIdentifier(
231                    className + "." + methodName));
232        }
233        return buff.toString();
234    }
235 
236    @Override
237    public int getType() {
238        return DbObject.FUNCTION_ALIAS;
239    }
240 
241    @Override
242    public synchronized void removeChildrenAndResources(Session session) {
243        database.removeMeta(session, getId());
244        className = null;
245        methodName = null;
246        javaMethods = null;
247        invalidate();
248    }
249 
250    @Override
251    public void checkRename() {
252        throw DbException.getUnsupportedException("RENAME");
253    }
254 
255    /**
256     * Find the Java method that matches the arguments.
257     *
258     * @param args the argument list
259     * @return the Java method
260     * @throws DbException if no matching method could be found
261     */
262    public JavaMethod findJavaMethod(Expression[] args) {
263        load();
264        int parameterCount = args.length;
265        for (JavaMethod m : javaMethods) {
266            int count = m.getParameterCount();
267            if (count == parameterCount || (m.isVarArgs() &&
268                    count <= parameterCount + 1)) {
269                return m;
270            }
271        }
272        throw DbException.get(ErrorCode.METHOD_NOT_FOUND_1, getName() + " (" +
273                className + ", parameter count: " + parameterCount + ")");
274    }
275 
276    public String getJavaClassName() {
277        return this.className;
278    }
279 
280    public String getJavaMethodName() {
281        return this.methodName;
282    }
283 
284    /**
285     * Get the Java methods mapped by this function.
286     *
287     * @return the Java methods.
288     */
289    public JavaMethod[] getJavaMethods() {
290        load();
291        return javaMethods;
292    }
293 
294    public void setDeterministic(boolean deterministic) {
295        this.deterministic = deterministic;
296    }
297 
298    public boolean isDeterministic() {
299        return deterministic;
300    }
301 
302    public String getSource() {
303        return source;
304    }
305 
306    /**
307     * Checks if the given method takes a variable number of arguments. For Java
308     * 1.4 and older, false is returned. Example:
309     * <pre>
310     * public static double mean(double... values)
311     * </pre>
312     *
313     * @param m the method to test
314     * @return true if the method takes a variable number of arguments.
315     */
316    static boolean isVarArgs(Method m) {
317        if ("1.5".compareTo(SysProperties.JAVA_SPECIFICATION_VERSION) > 0) {
318            return false;
319        }
320        try {
321            Method isVarArgs = m.getClass().getMethod("isVarArgs");
322            Boolean result = (Boolean) isVarArgs.invoke(m);
323            return result.booleanValue();
324        } catch (Exception e) {
325            return false;
326        }
327    }
328 
329    /**
330     * Should the return value ResultSet be buffered in a local temporary file?
331     *
332     * @return true if yes
333     */
334    public boolean isBufferResultSetToLocalTemp() {
335        return bufferResultSetToLocalTemp;
336    }
337 
338    /**
339     * There may be multiple Java methods that match a function name.
340     * Each method must have a different number of parameters however.
341     * This helper class represents one such method.
342     */
343    public static class JavaMethod implements Comparable<JavaMethod> {
344        private final int id;
345        private final Method method;
346        private final int dataType;
347        private boolean hasConnectionParam;
348        private boolean varArgs;
349        private Class<?> varArgClass;
350        private int paramCount;
351 
352        JavaMethod(Method method, int id) {
353            this.method = method;
354            this.id = id;
355            Class<?>[] paramClasses = method.getParameterTypes();
356            paramCount = paramClasses.length;
357            if (paramCount > 0) {
358                Class<?> paramClass = paramClasses[0];
359                if (Connection.class.isAssignableFrom(paramClass)) {
360                    hasConnectionParam = true;
361                    paramCount--;
362                }
363            }
364            if (paramCount > 0) {
365                Class<?> lastArg = paramClasses[paramClasses.length - 1];
366                if (lastArg.isArray() && FunctionAlias.isVarArgs(method)) {
367                    varArgs = true;
368                    varArgClass = lastArg.getComponentType();
369                }
370            }
371            Class<?> returnClass = method.getReturnType();
372            dataType = DataType.getTypeFromClass(returnClass);
373        }
374 
375        @Override
376        public String toString() {
377            return method.toString();
378        }
379 
380        /**
381         * Check if this function requires a database connection.
382         *
383         * @return if the function requires a connection
384         */
385        public boolean hasConnectionParam() {
386            return this.hasConnectionParam;
387        }
388 
389        /**
390         * Call the user-defined function and return the value.
391         *
392         * @param session the session
393         * @param args the argument list
394         * @param columnList true if the function should only return the column
395         *            list
396         * @return the value
397         */
398        public Value getValue(Session session, Expression[] args,
399                boolean columnList) {
400            Class<?>[] paramClasses = method.getParameterTypes();
401            Object[] params = new Object[paramClasses.length];
402            int p = 0;
403            if (hasConnectionParam && params.length > 0) {
404                params[p++] = session.createConnection(columnList);
405            }
406 
407            // allocate array for varArgs parameters
408            Object varArg = null;
409            if (varArgs) {
410                int len = args.length - params.length + 1 +
411                        (hasConnectionParam ? 1 : 0);
412                varArg = Array.newInstance(varArgClass, len);
413                params[params.length - 1] = varArg;
414            }
415 
416            for (int a = 0, len = args.length; a < len; a++, p++) {
417                boolean currentIsVarArg = varArgs &&
418                        p >= paramClasses.length - 1;
419                Class<?> paramClass;
420                if (currentIsVarArg) {
421                    paramClass = varArgClass;
422                } else {
423                    paramClass = paramClasses[p];
424                }
425                int type = DataType.getTypeFromClass(paramClass);
426                Value v = args[a].getValue(session);
427                Object o;
428                if (Value.class.isAssignableFrom(paramClass)) {
429                    o = v;
430                } else if (v.getType() == Value.ARRAY &&
431                        paramClass.isArray() &&
432                        paramClass.getComponentType() != Object.class) {
433                    Value[] array = ((ValueArray) v).getList();
434                    Object[] objArray = (Object[]) Array.newInstance(
435                            paramClass.getComponentType(), array.length);
436                    int componentType = DataType.getTypeFromClass(
437                            paramClass.getComponentType());
438                    for (int i = 0; i < objArray.length; i++) {
439                        objArray[i] = array[i].convertTo(componentType).getObject();
440                    }
441                    o = objArray;
442                } else {
443                    v = v.convertTo(type);
444                    o = v.getObject();
445                }
446                if (o == null) {
447                    if (paramClass.isPrimitive()) {
448                        if (columnList) {
449                            // If the column list is requested, the parameters
450                            // may be null. Need to set to default value,
451                            // otherwise the function can't be called at all.
452                            o = DataType.getDefaultForPrimitiveType(paramClass);
453                        } else {
454                            // NULL for a java primitive: return NULL
455                            return ValueNull.INSTANCE;
456                        }
457                    }
458                } else {
459                    if (!paramClass.isAssignableFrom(o.getClass()) && !paramClass.isPrimitive()) {
460                        o = DataType.convertTo(session.createConnection(false), v, paramClass);
461                    }
462                }
463                if (currentIsVarArg) {
464                    Array.set(varArg, p - params.length + 1, o);
465                } else {
466                    params[p] = o;
467                }
468            }
469            boolean old = session.getAutoCommit();
470            Value identity = session.getLastScopeIdentity();
471            boolean defaultConnection = session.getDatabase().
472                    getSettings().defaultConnection;
473            try {
474                session.setAutoCommit(false);
475                Object returnValue;
476                try {
477                    if (defaultConnection) {
478                        Driver.setDefaultConnection(
479                                session.createConnection(columnList));
480                    }
481                    returnValue = method.invoke(null, params);
482                    if (returnValue == null) {
483                        return ValueNull.INSTANCE;
484                    }
485                } catch (InvocationTargetException e) {
486                    StatementBuilder buff = new StatementBuilder(method.getName());
487                    buff.append('(');
488                    for (Object o : params) {
489                        buff.appendExceptFirst(", ");
490                        buff.append(o == null ? "null" : o.toString());
491                    }
492                    buff.append(')');
493                    throw DbException.convertInvocation(e, buff.toString());
494                } catch (Exception e) {
495                    throw DbException.convert(e);
496                }
497                if (Value.class.isAssignableFrom(method.getReturnType())) {
498                    return (Value) returnValue;
499                }
500                Value ret = DataType.convertToValue(session, returnValue, dataType);
501                return ret.convertTo(dataType);
502            } finally {
503                session.setLastScopeIdentity(identity);
504                session.setAutoCommit(old);
505                if (defaultConnection) {
506                    Driver.setDefaultConnection(null);
507                }
508            }
509        }
510 
511        public Class<?>[] getColumnClasses() {
512            return method.getParameterTypes();
513        }
514 
515        public int getDataType() {
516            return dataType;
517        }
518 
519        public int getParameterCount() {
520            return paramCount;
521        }
522 
523        public boolean isVarArgs() {
524            return varArgs;
525        }
526 
527        @Override
528        public int compareTo(JavaMethod m) {
529            if (varArgs != m.varArgs) {
530                return varArgs ? 1 : -1;
531            }
532            if (paramCount != m.paramCount) {
533                return paramCount - m.paramCount;
534            }
535            if (hasConnectionParam != m.hasConnectionParam) {
536                return hasConnectionParam ? 1 : -1;
537            }
538            return id - m.id;
539        }
540 
541    }
542 
543}

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