@Override
    public void alterFunction(QualifiedObjectName functionName, Optional<List<TypeSignature>> parameterTypes, AlterRoutineCharacteristics alterRoutineCharacteristics)
    {
        checkCatalog(functionName);
        jdbi.useTransaction(handle -> {
            FunctionNamespaceDao transactionDao = handle.attach(functionNamespaceDaoClass);
            List<SqlInvokedFunction> functions = getSqlFunctions(transactionDao, functionName, parameterTypes);

            checkUnique(functions, functionName);
            checkExists(functions, functionName, parameterTypes);

            SqlInvokedFunction latest = functions.get(0);
            RoutineCharacteristics.Builder routineCharacteristics = RoutineCharacteristics.builder(latest.getRoutineCharacteristics());
            alterRoutineCharacteristics.getNullCallClause().ifPresent(routineCharacteristics::setNullCallClause);
            SqlInvokedFunction altered = new SqlInvokedFunction(
                    latest.getFunctionId().getFunctionName(),
                    latest.getParameters(),
                    latest.getSignature().getReturnType(),
                    latest.getDescription(),
                    routineCharacteristics.build(),
                    latest.getBody(),
                    notVersioned());
            if (!altered.hasSameDefinitionAs(latest)) {
                checkState(latest.hasVersion(), "Function version missing: %s", latest.getFunctionId());
                insertSqlInvokedFunction(transactionDao, altered, getLongVersion(latest) + 1);
            }
        });
        refreshFunctionsCache(functionName);
    }
@Test
    public void testAlterFunction()
    {
        createFunction(FUNCTION_POWER_TOWER_DOUBLE, false);
        createFunction(FUNCTION_POWER_TOWER_INT, false);
        createFunction(FUNCTION_TANGENT, false);

        // Alter a specific function by name and parameter types
        alterFunction(POWER_TOWER, Optional.of(ImmutableList.of(parseTypeSignature(DOUBLE))), new AlterRoutineCharacteristics(Optional.of(RETURNS_NULL_ON_NULL_INPUT)));
        assertGetFunctions(
                POWER_TOWER,
                FUNCTION_POWER_TOWER_INT.withVersion("1"),
                new SqlInvokedFunction(
                        POWER_TOWER,
                        ImmutableList.of(new Parameter("x", parseTypeSignature(DOUBLE))),
                        parseTypeSignature(DOUBLE),
                        "power tower",
                        RoutineCharacteristics.builder().setDeterminism(DETERMINISTIC).setNullCallClause(RETURNS_NULL_ON_NULL_INPUT).build(),
                        "RETURN pow(x, x)",
                        withVersion("2")));

        // Drop function and alter function by name
        dropFunction(POWER_TOWER, Optional.of(ImmutableList.of(parseTypeSignature(DOUBLE))), false);
        alterFunction(POWER_TOWER, Optional.empty(), new AlterRoutineCharacteristics(Optional.of(CALLED_ON_NULL_INPUT)));

        // Alter function by name
        alterFunction(TANGENT, Optional.empty(), new AlterRoutineCharacteristics(Optional.of(CALLED_ON_NULL_INPUT)));
        SqlInvokedFunction tangentV2 = new SqlInvokedFunction(
                TANGENT,
                FUNCTION_TANGENT.getParameters(),
                FUNCTION_TANGENT.getSignature().getReturnType(),
                FUNCTION_TANGENT.getDescription(),
                RoutineCharacteristics.builder().setDeterminism(DETERMINISTIC).build(),
                FUNCTION_TANGENT.getBody(),
                withVersion("2"));
        assertGetFunctions(TANGENT, tangentV2);

        // Alter function with no change
        alterFunction(TANGENT, Optional.empty(), new AlterRoutineCharacteristics(Optional.of(CALLED_ON_NULL_INPUT)));
        assertGetFunctions(TANGENT, tangentV2);
    }