DROP FUNCTION without knowing the number/type of parameters?

后端 未结 7 810
面向向阳花
面向向阳花 2020-12-01 02:20

I keep all my functions in a text file with \'CREATE OR REPLACE FUNCTION somefunction\'. So if I add or change some function I just feed the file to psql.

相关标签:
7条回答
  • 2020-12-01 02:23

    Basic query

    This query creates all necessary DDL statements. Later simplified with a cast to regprocedure, which displays as function with argument types, schema-qualified and/or double-quoted where necessary. Exactly what we need:

    SELECT 'DROP FUNCTION ' || oid::regprocedure
    FROM   pg_proc
    WHERE  proname = 'my_function_name'  -- name without schema-qualification
    AND    pg_function_is_visible(oid);  -- restrict to current search_path ..
                                         -- .. you may or may not want this
    

    Output:

    DROP FUNCTION my_function_name(string text, form text, maxlen integer);
    DROP FUNCTION my_function_name(string text, form text);
    DROP FUNCTION my_function_name(string text);
    

    Execute the commands (after a plausibility check).

    The function name is case-sensitive and with no added double-quotes when passed as text parameter to match against pg_proc.proname.

    The cast to the object identifier type regprocedure (oid::regprocedure) makes all identifiers safe against SQL injection (by way of maliciously malformed identifiers). When converting to text, the function name is double-quoted and schema-qualified according to the current search_path automatically where needed.

    pg_function_is_visible(oid) restricts the selection to functions in the current search_path. You may or may not want that. With the condition pg_function_is_visible(oid) in place, the function is guaranteed to be visible.

    If you have multiple functions of the same name in multiple schemas, or overloaded functions with various function arguments, all of those will be listed separately. You may want to restrict to specific schema(s) or specific function parameter(s) after all.

    Related:

    • When / how are default value expression functions bound with regard to search_path?

    Function

    You can build a plpgsql function around this to execute the statements immediately with EXECUTE. For Postgres 9.1 or later: Careful! It drops your functions!

    CREATE OR REPLACE FUNCTION f_delfunc(_name text, OUT func_dropped int) AS
    $func$
    DECLARE
       _sql text;
    BEGIN
       SELECT count(*)::int
            , 'DROP FUNCTION ' || string_agg(oid::regprocedure::text, '; DROP FUNCTION ')
       FROM   pg_proc
       WHERE  proname = _name
       AND    pg_function_is_visible(oid)
       INTO   func_dropped, _sql;  -- only returned if trailing DROPs succeed
    
       IF func_dropped > 0 THEN    -- only if function(s) found
         EXECUTE _sql;
       END IF;
    END
    $func$ LANGUAGE plpgsql;
    

    Call:

    SELECT * FROM f_delfunc('my_function_name');
    

    Or just:

    SELECT f_delfunc('my_function_name');
    

    This way you don't get the column name func_dropped for the result column. May not matter.

    The function returns the number of functions found and dropped (no exception raised) - 0 if none were found.

    It assumes a (default) search_path where pg_catalog has not been moved around. See:

    • How does the search_path influence identifier resolution and the "current schema"
    • Truncating all tables in a Postgres database
    • PostgreSQL parameterized Order By / Limit in table function

    For Postgres versions older than 9.1 or older variants of the function using regproc and pg_get_function_identity_arguments(oid) check the edit history of this answer.

    0 讨论(0)
  • 2020-12-01 02:26

    Improving original answer in order to take schema into account, ie. schema.my_function_name,

    select
        format('DROP FUNCTION %s(%s);',
          p.oid::regproc, pg_get_function_identity_arguments(p.oid))
    FROM pg_catalog.pg_proc p
    LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
    WHERE
        p.oid::regproc::text = 'schema.my_function_name';
    
    0 讨论(0)
  • 2020-12-01 02:27

    pgsql generates an error if there exists more than one procedure with the same name but different arguments when the procedure is deleted according to its name. Thus if you want to delete a single procedure without affecting others then simply use the following query.

    SELECT 'DROP FUNCTION ' || oid::regprocedure
    FROM   pg_proc
    WHERE  oid = {$proc_oid}
    
    0 讨论(0)
  • 2020-12-01 02:29

    Slightly enhanced version of Erwin's answer. Additionally supports following

    • 'like' instead of exact function name match
    • can run in 'dry-mode' and 'trace' the SQL for removing of the functions

    Code for copy/paste:

    /**
     * Removes all functions matching given function name mask
     *
     * @param p_name_mask   Mask in SQL 'like' syntax
     * @param p_opts        Combination of comma|space separated options:
     *                        trace - output SQL to be executed as 'NOTICE'
     *                        dryrun - do not execute generated SQL
     * @returns             Generated SQL 'drop functions' string
     */
    CREATE OR REPLACE FUNCTION mypg_drop_functions(IN p_name_mask text,
                                                   IN p_opts text = '')
        RETURNS text LANGUAGE plpgsql AS $$
    DECLARE
        v_trace boolean;
        v_dryrun boolean;
        v_opts text[];
        v_sql text;
    BEGIN
        if p_opts is null then
            v_trace = false;
            v_dryrun = false;
        else
            v_opts = regexp_split_to_array(p_opts, E'(\\s*,\\s*)|(\\s+)');
            v_trace = ('trace' = any(v_opts)); 
            v_dryrun = ('dry' = any(v_opts)) or ('dryrun' = any(v_opts)); 
        end if;
    
        select string_agg(format('DROP FUNCTION %s(%s);', 
            oid::regproc, pg_get_function_identity_arguments(oid)), E'\n')
        from pg_proc
        where proname like p_name_mask
        into v_sql;
    
        if v_sql is not null then
            if v_trace then
                raise notice E'\n%', v_sql;
            end if;
    
            if not v_dryrun then
                execute v_sql;
            end if;
        end if;
    
        return v_sql;
    END $$;
    
    select mypg_drop_functions('fn_dosomething_%', 'trace dryrun');
    
    0 讨论(0)
  • 2020-12-01 02:31

    You would need to write a function that took the function name, and looked up each overload with its parameter types from information_schema, then built and executed a DROP for each one.

    EDIT: This turned out to be a lot harder than I thought. It looks like information_schema doesn't keep the necessary parameter information in its routines catalog. So you need to use PostgreSQL's supplementary tables pg_proc and pg_type:

    CREATE OR REPLACE FUNCTION udf_dropfunction(functionname text)
      RETURNS text AS
    $BODY$
    DECLARE
        funcrow RECORD;
        numfunctions smallint := 0;
        numparameters int;
        i int;
        paramtext text;
    BEGIN
    FOR funcrow IN SELECT proargtypes FROM pg_proc WHERE proname = functionname LOOP
    
        --for some reason array_upper is off by one for the oidvector type, hence the +1
        numparameters = array_upper(funcrow.proargtypes, 1) + 1;
    
        i = 0;
        paramtext = '';
    
        LOOP
            IF i < numparameters THEN
                IF i > 0 THEN
                    paramtext = paramtext || ', ';
                END IF;
                paramtext = paramtext || (SELECT typname FROM pg_type WHERE oid = funcrow.proargtypes[i]);
                i = i + 1;
            ELSE
                EXIT;
            END IF;
        END LOOP;
    
        EXECUTE 'DROP FUNCTION ' || functionname || '(' || paramtext || ');';
        numfunctions = numfunctions + 1;
    
    END LOOP;
    
    RETURN 'Dropped ' || numfunctions || ' functions';
    END;
    $BODY$
      LANGUAGE plpgsql VOLATILE
      COST 100;
    

    I successfully tested this on an overloaded function. It was thrown together pretty fast, but works fine as a utility function. I would recommend testing more before using it in practice, in case I overlooked something.

    0 讨论(0)
  • 2020-12-01 02:36

    Here is the query I built on top of @Сухой27 solution that generates sql statements for dropping all the stored functions in a schema:

    WITH f AS (SELECT specific_schema || '.' || ROUTINE_NAME AS func_name 
            FROM information_schema.routines
            WHERE routine_type='FUNCTION' AND specific_schema='a3i')
    SELECT
        format('DROP FUNCTION %s(%s);',
          p.oid::regproc, pg_get_function_identity_arguments(p.oid))
    FROM pg_catalog.pg_proc p
    LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
    WHERE
        p.oid::regproc::text IN (SELECT func_name FROM f);
    
    0 讨论(0)
提交回复
热议问题