/*
 * Minimal reproducer for inconsistent NULL handling of an aggregate UDF
 * over an empty input set in MariaDB 10.11.19.
 *
 * This aggregate multiplies all non-NULL integer arguments and returns NULL
 * when it receives no non-NULL arguments.
 */

#include <mysql.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int64_t product;
    int has_value;
} nullable_product_state;

my_bool nullable_product_init(
    UDF_INIT *initid,
    UDF_ARGS *args,
    char *message
) {
    nullable_product_state *state;

    if (args->arg_count != 1) {
        strcpy(message, "nullable_product requires one argument");
        return 1;
    }

    args->arg_type[0] = INT_RESULT;

    state = (nullable_product_state *)calloc(1, sizeof(*state));
    if (state == NULL) {
        strcpy(message, "nullable_product could not allocate state");
        return 1;
    }

    initid->ptr = (char *)state;
    initid->maybe_null = 1;

    return 0;
}

void nullable_product_clear(
    UDF_INIT *initid,
    char *is_null,
    char *error
) {
    nullable_product_state *state =
        (nullable_product_state *)initid->ptr;

    state->product = 1;
    state->has_value = 0;

    /* The initial aggregate state represents SQL NULL. */
    *is_null = 1;
    *error = 0;
}

void nullable_product_add(
    UDF_INIT *initid,
    UDF_ARGS *args,
    char *is_null,
    char *error
) {
    nullable_product_state *state =
        (nullable_product_state *)initid->ptr;

    *error = 0;

    if (args->args[0] == NULL)
        return;

    state->product *= *(const int64_t *)args->args[0];
    state->has_value = 1;
    *is_null = 0;
}

long long nullable_product(
    UDF_INIT *initid,
    UDF_ARGS *args,
    char *is_null,
    char *error
) {
    nullable_product_state *state =
        (nullable_product_state *)initid->ptr;

    (void)args;
    *error = 0;

    if (!state->has_value) {
        *is_null = 1;
        return 0;
    }

    *is_null = 0;
    return state->product;
}

void nullable_product_deinit(UDF_INIT *initid)
{
    free(initid->ptr);
}
