Understanding difference between int literal vs int parameter in PL/pgSQL function

妖精的绣舞 提交于 2020-01-24 20:42:23

问题


I have a function to left pad bit stings in PostgreSQL 9.5:

CREATE OR REPLACE FUNCTION lpad_bits(val bit varying) 
RETURNS bit varying as
$BODY$
  BEGIN return val::bit(32) >> (32-length(val));
  END;
$BODY$
LANGUAGE plpgsql IMMUTABLE;

which works fine:

 # select lpad_bits(b'1001100111000');
        lpad_bits
 ----------------------------------
 00000000000000000001001100111000
 (1 row)

My problem is when I try to add a parameter to change the amount of padding:

CREATE OR REPLACE FUNCTION lpad_bits(val bit varying, sz integer default 1024) 
  RETURNS bit varying as
$BODY$
  BEGIN return val::bit(sz) >> (sz-length(val));
  END;
$BODY$
LANGUAGE plpgsql IMMUTABLE;

The function is now broken:

# select lpad_bits(b'1001100111000', 32);                                      
ERROR:  invalid input syntax for integer: "sz"
LINE 1: SELECT val::bit(sz) >> (sz-length(val))
                ^
QUERY:  SELECT val::bit(sz) >> (sz-length(val))
CONTEXT:  PL/pgSQL function lpad_bits(bit varying,integer) line 2 at RETURN

I have stared at the bitstring documentation and PL/pgSQL function documentation, am simply not seeing what is fundamentally different between these two implementations.


回答1:


Why?

PL/pgSQL executes SQL queries like prepared statements. The manual about parameter substituion:

Prepared statements can take parameters: values that are substituted into the statement when it is executed.

Note the term values. Only actual values can be parameterized, but not key words, identifiers or type names. 32 in bit(32) looks like a value, but the modifier of a data type is only a "value" internally and can't be parameterized. SQL demands to know data types at planning stage, it cannot wait for the execution stage.

You could achieve your goal with dynamic SQL and EXECUTE. As proof of concept:

CREATE OR REPLACE FUNCTION lpad_bits(val varbit, sz int = 32, OUT outval varbit) AS
$func$
BEGIN
   EXECUTE format('SELECT $1::bit(%s) >> $2', sz)  -- literal
   USING val, sz - length(val)                     -- values
   INTO outval;
END
$func$  LANGUAGE plpgsql IMMUTABLE;

Call:

SELECT lpad_bits(b'1001100111000', 32);  

Note the distinction between sz being used as literal to build the statement and its second occurrence where it's used as value, that can be passed as parameter.

Faster alternatives

A superior solution for this particular task is to just use lpad() like @Abelisto suggested:

CREATE OR REPLACE FUNCTION lpad_bits2(val varbit, sz int = 32)
  RETURNS varbit AS
$func$
SELECT lpad(val::text, sz, '0')::varbit;
$func$  LANGUAGE sql IMMUTABLE;

(Simpler as plain SQL function, which also allows function inlining in the context of outer queries.)

Several times faster than the above function. A minor flaw: we have to cast to text and back to varbit. Unfortunately, lpad() is not currently implemented for varbit. The manual:

The following SQL-standard functions work on bit strings as well as character strings: length, bit_length, octet_length, position, substring, overlay.

overlay() is available, we can have a cheaper function:

CREATE OR REPLACE FUNCTION lpad_bits3(val varbit, base varbit = '00000000000000000000000000000000')
  RETURNS varbit AS
$func$
SELECT overlay(base PLACING val FROM bit_length(base) - bit_length(val))
$func$  LANGUAGE sql IMMUTABLE;

Faster if you can work with varbit values to begin with. (The advantage is (partly) voided, if you have to cast text to varbit anyway.)

Call:

SELECT lpad_bits3(b'1001100111000', '00000000000000000000000000000000');
SELECT lpad_bits3(b'1001100111000',  repeat('0', 32)::varbit);

We might overlaod the function with a variant taking an integer to generate base itself:

CREATE OR REPLACE FUNCTION lpad_bits3(val varbit, sz int = 32)
  RETURNS varbit AS
$func$
SELECT overlay(repeat('0', sz)::varbit PLACING val FROM sz - bit_length(val))
$func$  LANGUAGE sql IMMUTABLE;

Call:

SELECT lpad_bits3(b'1001100111000', 32;

Related:

  • Postgresql Convert bit varying to integer
  • Convert hex in text representation to decimal number



回答2:


The parser does not allow a variable at that place. The alternative is to use a constant and trim it:

select right((val::bit(128) >> (128 -length(val)))::text, sz)::bit(sz)
from (values (b'1001100111000', 32)) s(val,sz)
;
              right               
----------------------------------
 00000000000000000001001100111000

Or the lpad function as suggested in the comments.



来源:https://stackoverflow.com/questions/40290807/understanding-difference-between-int-literal-vs-int-parameter-in-pl-pgsql-functi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!