Search in sources :

Example 11 with CallableUserAggregationFunction

use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.

the class ProcedureCompiler method compileAggregationFunction.

List<CallableUserAggregationFunction> compileAggregationFunction(Class<?> fcnDefinition) throws KernelException {
    try {
        List<Method> methods = Arrays.stream(fcnDefinition.getDeclaredMethods()).filter(m -> m.isAnnotationPresent(UserAggregationFunction.class)).collect(Collectors.toList());
        if (methods.isEmpty()) {
            return emptyList();
        }
        assertValidConstructor(fcnDefinition);
        List<CallableUserAggregationFunction> out = new ArrayList<>(methods.size());
        for (Method method : methods) {
            String valueName = method.getAnnotation(UserAggregationFunction.class).value();
            String definedName = method.getAnnotation(UserAggregationFunction.class).name();
            QualifiedName funcName = extractName(fcnDefinition, method, valueName, definedName);
            if (config.isWhitelisted(funcName.toString())) {
                out.add(compileAggregationFunction(fcnDefinition, method, funcName));
            } else {
                log.warn(String.format("The function '%s' is not on the allowlist and won't be loaded.", funcName.toString()));
            }
        }
        out.sort(Comparator.comparing(a -> a.signature().name().toString()));
        return out;
    } catch (KernelException e) {
        throw e;
    } catch (Exception e) {
        throw new ProcedureException(Status.Procedure.ProcedureRegistrationFailed, e, "Failed to compile function defined in `%s`: %s", fcnDefinition.getSimpleName(), e.getMessage());
    }
}
Also used : Mode(org.neo4j.procedure.Mode) Arrays(java.util.Arrays) Log(org.neo4j.logging.Log) UserAggregationResult(org.neo4j.procedure.UserAggregationResult) CallableUserAggregationFunction(org.neo4j.kernel.api.procedure.CallableUserAggregationFunction) Status(org.neo4j.kernel.api.exceptions.Status) UserAggregationUpdate(org.neo4j.procedure.UserAggregationUpdate) ProcedureException(org.neo4j.internal.kernel.api.exceptions.ProcedureException) Constructor(java.lang.reflect.Constructor) Supplier(java.util.function.Supplier) ArrayList(java.util.ArrayList) SystemProcedure(org.neo4j.kernel.api.procedure.SystemProcedure) UserFunctionSignature(org.neo4j.internal.kernel.api.procs.UserFunctionSignature) Procedure(org.neo4j.procedure.Procedure) Method(java.lang.reflect.Method) GraphDatabaseSettings.procedure_unrestricted(org.neo4j.configuration.GraphDatabaseSettings.procedure_unrestricted) ComponentInjectionException(org.neo4j.kernel.api.exceptions.ComponentInjectionException) Collections.emptyList(java.util.Collections.emptyList) CallableUserFunction(org.neo4j.kernel.api.procedure.CallableUserFunction) FailedLoadFunction(org.neo4j.kernel.api.procedure.FailedLoadFunction) Description(org.neo4j.procedure.Description) Collectors(java.util.stream.Collectors) FieldSignature(org.neo4j.internal.kernel.api.procs.FieldSignature) Internal(org.neo4j.procedure.Internal) QualifiedName(org.neo4j.internal.kernel.api.procs.QualifiedName) FailedLoadProcedure(org.neo4j.kernel.api.procedure.FailedLoadProcedure) UserFunction(org.neo4j.procedure.UserFunction) List(java.util.List) CallableProcedure(org.neo4j.kernel.api.procedure.CallableProcedure) KernelException(org.neo4j.exceptions.KernelException) Modifier.isPublic(java.lang.reflect.Modifier.isPublic) UserAggregationFunction(org.neo4j.procedure.UserAggregationFunction) FailedLoadAggregatedFunction(org.neo4j.kernel.api.procedure.FailedLoadAggregatedFunction) ProcedureSignature(org.neo4j.internal.kernel.api.procs.ProcedureSignature) Comparator(java.util.Comparator) Admin(org.neo4j.procedure.Admin) CallableUserAggregationFunction(org.neo4j.kernel.api.procedure.CallableUserAggregationFunction) UserAggregationFunction(org.neo4j.procedure.UserAggregationFunction) CallableUserAggregationFunction(org.neo4j.kernel.api.procedure.CallableUserAggregationFunction) QualifiedName(org.neo4j.internal.kernel.api.procs.QualifiedName) ArrayList(java.util.ArrayList) Method(java.lang.reflect.Method) ProcedureException(org.neo4j.internal.kernel.api.exceptions.ProcedureException) KernelException(org.neo4j.exceptions.KernelException) ProcedureException(org.neo4j.internal.kernel.api.exceptions.ProcedureException) ComponentInjectionException(org.neo4j.kernel.api.exceptions.ComponentInjectionException) KernelException(org.neo4j.exceptions.KernelException)

Example 12 with CallableUserAggregationFunction

use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.

the class ProcedureRegistry method register.

/**
 * Register a new function.
 *
 * @param function the function.
 */
public void register(CallableUserAggregationFunction function, boolean overrideCurrentImplementation, boolean builtIn) throws ProcedureException {
    UserFunctionSignature signature = function.signature();
    QualifiedName name = signature.name();
    if (functions.get(name) != null) {
        throw new ProcedureException(Status.Procedure.ProcedureRegistrationFailed, "Unable to register aggregation function, because the name `%s` is already in use as a function.", name);
    }
    CallableUserAggregationFunction oldImplementation = aggregationFunctions.get(name);
    if (oldImplementation == null) {
        aggregationFunctions.put(name, function, signature.caseInsensitive());
    } else {
        if (overrideCurrentImplementation) {
            aggregationFunctions.put(name, function, signature.caseInsensitive());
        } else {
            throw new ProcedureException(Status.Procedure.ProcedureRegistrationFailed, "Unable to register aggregation function, because the name `%s` is already in use.", name);
        }
    }
    if (builtIn) {
        builtInAggregatingFunctionIds.add(aggregationFunctions.idOf(name));
    }
}
Also used : CallableUserAggregationFunction(org.neo4j.kernel.api.procedure.CallableUserAggregationFunction) QualifiedName(org.neo4j.internal.kernel.api.procs.QualifiedName) ProcedureException(org.neo4j.internal.kernel.api.exceptions.ProcedureException) UserFunctionSignature(org.neo4j.internal.kernel.api.procs.UserFunctionSignature)

Example 13 with CallableUserAggregationFunction

use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.

the class UserAggregationFunctionTest method shouldNotLoadNoneWhiteListedFunction.

@Test
void shouldNotLoadNoneWhiteListedFunction() throws Throwable {
    // Given
    Log log = spy(Log.class);
    procedureCompiler = new ProcedureCompiler(new TypeCheckers(), components, new ComponentRegistry(), log, new ProcedureConfig(Config.defaults(GraphDatabaseSettings.procedure_allowlist, List.of("WrongName"))));
    List<CallableUserAggregationFunction> method = compile(SingleAggregationFunction.class);
    verify(log).warn("The function 'org.neo4j.procedure.impl.collectCool' is not on the allowlist and won't be loaded.");
    assertThat(method.size()).isEqualTo(0);
}
Also used : CallableUserAggregationFunction(org.neo4j.kernel.api.procedure.CallableUserAggregationFunction) Log(org.neo4j.logging.Log) NullLog(org.neo4j.logging.NullLog) Test(org.junit.jupiter.api.Test)

Example 14 with CallableUserAggregationFunction

use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.

the class ProcedureCompilation method compileAggregation.

/**
 * Generates code for a user-defined aggregation function.
 * <p>
 * Given a user-defined aggregation function defined by
 *
 * <pre>
 *     class MyClass {
 *       {@literal @}Context
 *        public Log log;
 *
 *       {@literal @}UserAggregationFunction
 *        public Adder create() {
 *            return new Adder;
 *        }
 *     }
 *
 *     class Adder {
 *       private long sum;
 *       {@literal @}UserAggregationUpdate
 *       public void update(long in) {
 *           sum += in;
 *       }
 *       {@literal @}UserAggregationResult
 *       public long result() {
 *         return sum;
 *       }
 *    }
 *     }
 * </pre>
 * <p>
 * we will generate two classe looking something like
 *
 * <pre>
 *     class Generated implements CallableUserAggregationFunction {
 *         public static UserFunctionSignature SIGNATURE;
 *         public static FieldSetter SETTER_0;
 *
 *         public UserAggregator create(Context ctx) {
 *              try {
 *                  MyClass userClass = new MyClass();
 *                  userClass.log = (Log) SETTER_0.get(ctx);
 *                  return new GeneratedAdder(userClass.create());
 *              } catch (Throwable T) {
 *                  throw new ProcedureException([appropriate error msg], T);
 *              }
 *         }
 *
 *         public UserFunctionSignature signature() {return SIGNATURE;}
 *     }
 *     class GeneratedAdder implements UserAggregator {
 *       private Adder adder;
 *
 *       GeneratedAdder(Adder adder) {
 *         this.adder = adder;
 *       }
 *
 *       void update(AnyValue[] in) {
 *          adder.update(((NumberValue) in).longValue());
 *       }
 *
 *       AnyValue result() {
 *          return adder.result(Values.longValue(adder.result());
 *       }
 *     }
 * </pre>
 * <p>
 * where the static fields are set once during loading via reflection.
 *
 * @param signature the signature of the user-defined function
 * @param fieldSetters the fields to set before each call.
 * @param create the method that creates the aggregator
 * @param update the update method of the aggregator
 * @param result the result method of the aggregator
 * @return a CallableUserFunction delegating to the underlying user-defined function.
 * @throws ProcedureException if something went wrong when compiling the user-defined function.
 */
static CallableUserAggregationFunction compileAggregation(UserFunctionSignature signature, List<FieldSetter> fieldSetters, Method create, Method update, Method result) throws ProcedureException {
    ClassHandle handle;
    try {
        CodeGenerator codeGenerator = codeGenerator();
        Class<?> aggregator = generateAggregator(codeGenerator, update, result, signature);
        try (ClassGenerator generator = codeGenerator.generateClass(PACKAGE, className(signature), CallableUserAggregationFunction.class)) {
            // static fields
            FieldReference signatureField = generator.publicStaticField(typeReference(UserFunctionSignature.class), SIGNATURE_NAME);
            List<FieldReference> fieldsToSet = createContextSetters(fieldSetters, generator);
            // CallableUserAggregationFunction::create
            try (CodeBlock method = generator.generate(AGGREGATION_CREATE)) {
                method.tryCatch(body -> createAggregationBody(body, fieldSetters, fieldsToSet, create, aggregator), onError -> onError(onError, format("function `%s`", signature.name())), param(Throwable.class, "T"));
            }
            // CallableUserFunction::signature
            try (CodeBlock method = generator.generateMethod(UserFunctionSignature.class, "signature")) {
                method.returns(getStatic(signatureField));
            }
            handle = generator.handle();
        }
        Class<?> clazz = handle.loadClass();
        // set all static fields
        setAllStaticFields(signature, fieldSetters, create, clazz);
        return (CallableUserAggregationFunction) clazz.getConstructor().newInstance();
    } catch (Throwable e) {
        throw new ProcedureException(Status.Procedure.ProcedureRegistrationFailed, e, "Failed to compile function defined in `%s`: %s", create.getDeclaringClass().getSimpleName(), e.getMessage());
    }
}
Also used : ClassGenerator(org.neo4j.codegen.ClassGenerator) FieldReference(org.neo4j.codegen.FieldReference) CallableUserAggregationFunction(org.neo4j.kernel.api.procedure.CallableUserAggregationFunction) CodeBlock(org.neo4j.codegen.CodeBlock) CodeGenerator(org.neo4j.codegen.CodeGenerator) ProcedureException(org.neo4j.internal.kernel.api.exceptions.ProcedureException) UserFunctionSignature(org.neo4j.internal.kernel.api.procs.UserFunctionSignature) ClassHandle(org.neo4j.codegen.ClassHandle)

Example 15 with CallableUserAggregationFunction

use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.

the class ResourceInjectionTest method shouldCompileAndRunUserAggregationFunctions.

@Test
void shouldCompileAndRunUserAggregationFunctions() throws Throwable {
    // Given
    CallableUserAggregationFunction proc = compiler.compileAggregationFunction(AggregationFunctionWithInjectedAPI.class).get(0);
    // When
    proc.create(prepareContext()).update(new AnyValue[] {});
    AnyValue out = proc.create(prepareContext()).result();
    // Then
    assertThat(out).isEqualTo(stringValue("[Bonnie, Clyde]"));
}
Also used : CallableUserAggregationFunction(org.neo4j.kernel.api.procedure.CallableUserAggregationFunction) AnyValue(org.neo4j.values.AnyValue) Test(org.junit.jupiter.api.Test)

Aggregations

CallableUserAggregationFunction (org.neo4j.kernel.api.procedure.CallableUserAggregationFunction)20 Test (org.junit.jupiter.api.Test)15 UserAggregator (org.neo4j.internal.kernel.api.procs.UserAggregator)7 UserFunctionSignature (org.neo4j.internal.kernel.api.procs.UserFunctionSignature)7 ProcedureException (org.neo4j.internal.kernel.api.exceptions.ProcedureException)6 Log (org.neo4j.logging.Log)5 NullLog (org.neo4j.logging.NullLog)4 Method (java.lang.reflect.Method)2 FieldSignature (org.neo4j.internal.kernel.api.procs.FieldSignature)2 QualifiedName (org.neo4j.internal.kernel.api.procs.QualifiedName)2 ComponentInjectionException (org.neo4j.kernel.api.exceptions.ComponentInjectionException)2 CallableProcedure (org.neo4j.kernel.api.procedure.CallableProcedure)2 CallableUserFunction (org.neo4j.kernel.api.procedure.CallableUserFunction)2 FailedLoadAggregatedFunction (org.neo4j.kernel.api.procedure.FailedLoadAggregatedFunction)2 UserAggregationFunction (org.neo4j.procedure.UserAggregationFunction)2 UserAggregationResult (org.neo4j.procedure.UserAggregationResult)2 UserAggregationUpdate (org.neo4j.procedure.UserAggregationUpdate)2 AnyValue (org.neo4j.values.AnyValue)2 Constructor (java.lang.reflect.Constructor)1 Modifier.isPublic (java.lang.reflect.Modifier.isPublic)1