Understanding cast from bytea to oid - sql

I'm using PostgreSQL 9.2.
This blog entry by Grace Batumbya provides a cast from bytea to oid.
create or replace function blob_write(lbytea bytea)
returns oid
volatile
language plpgsql as
$f$
declare
loid oid;
lfd integer;
lsize integer;
begin
if(lbytea is null) then
return null;
end if;
loid := lo_create(0);
lfd := lo_open(loid,131072);
lsize := lowrite(lfd,lbytea);
perform lo_close(lfd);
return loid;
end;
$f$;
CREATE CAST (bytea AS oid) WITH FUNCTION blob_write(bytea) AS ASSIGNMENT;
CREATE TABLE bytea_to_lo (
largeObj lo
);
I didn't understand why should we create bytea_to_lo table? How is it going to be used by PostgreSQL?

The cast is not a true cast. It's just (ab)using the convenient syntax. A large object (LO) is created in the background which is stored separately and the OID referencing it is returned.
Per documentation:
All large objects are stored in a single system table named
pg_largeobject. Each large object also has an entry in the system
table pg_largeobject_metadata. Large objects can be created, modified,
and deleted using a read/write API that is similar to standard
operations on files.
The returned OID is basically a FK to the PK of the system table pg_largeobject.
CREATE TABLE is completely independent from the function and pseudo-cast.
CREATE TABLE bytea_to_lo (
largeObj lo
);
It's just a typical use case for the assignment cast created above, which becomes apparent from the following line that you forgot to quote:
INSERT INTO bytea_to_lo VALUES (DECODE('00AB','hex'));
What happens here?
The data type lo is a domain over the base type oid, created by the additional module lo (incorrectly referenced as "lo_manage package" in the blog enty of Grace Batumbya). Per documentation:
The module also provides a data type lo, which is really just a domain
of the oid type. This is useful for differentiating database columns
that hold large object references from those that are OIDs of other things.
The function decode() returns bytea. The INSERT statement assigns the bytea value to the column largeObj, which triggers an assignment cast to its type lo, and that's where the above cast comes in.
Warning / Corrective / Update
The blog entry is sloppy and outdated by now.
Does not bother to mention that (per documentation):
To be able to create a cast, you must own the source or the target
data type and have USAGE privilege on the other type.
Effectively, you must be superuser.
Typo in CREATE TABLE: column name and type reversed.
The function definition is verbose and inefficient. This would be better (for Postgres 9.3 or older):
CREATE OR REPLACE FUNCTION blob_write(bytea)
RETURNS oid AS
$func$
DECLARE
loid oid := lo_create(0);
lfd int := lo_open(loid,131072); -- = 2^17 = x2000
-- symbolic constant defined in the header file libpq/libpq-fs.h
-- #define INV_WRITE 0x00020000
BEGIN
PERFORM lowrite(lfd, $1);
PERFORM lo_close(lfd);
RETURN loid;
END
$func$ LANGUAGE plpgsql VOLATILE STRICT;
SQL Fiddle.
There is a built-in function for this in Postgres 9.4. Use that instead:
lo_from_bytea(loid oid, string bytea)
From the release notes:
Add SQL functions to allow large object reads/writes at arbitrary offsets (Pavel Stehule)
For CREATE CAST (per documentation):
The first argument type must be identical to or binary-coercible from the cast's source type.
I suggest an overloaded variant with only a bytea parameter:
CREATE OR REPLACE FUNCTION lo_from_bytea(bytea)
RETURNS oid LANGUAGE sql AS
'SELECT lo_from_bytea(0, $1)';
CREATE CAST (bytea AS oid) WITH FUNCTION lo_from_bytea(bytea) AS ASSIGNMENT;
Since the pseudo-cast has quite a big side effect, I am not convinced to make that an ASSIGNMENT cast. I'd probably start with explicit-only:
Generate series of dates - using date type as input

Related

How to create a Postgresql generated column of type JSONB from columns of types JSONB, VARCHAR and DATE

I have a table test_data with 4 columns as shown below. I want to create a JSONB generated column with data from these columns. Below is what I have tried but keep on getting this error:
ERROR: generation expression is not immutable
SQL state: 42P17
name VARCHAR(50)
mixed_stuff JSONB
other_stuff JSONB
created TIMESTAMP
combined JSONB
CREATE TABLE test_data(name VARCHAR(50), mixed_stuff JSONB, other_stuff JSONB, created TIMESTAMP WITHOUT TIMEZONE default(now() at time zone 'utc'));
ALTER TABLE test_data
add combined JSONB GENERATED ALWAYS AS (jsonb_build_object('name',name) || "mixed_stuff" || "other_stuff" || jsonb_build_object('created',created)) STORED;
Generated columns can only use immutable functions (docs) while jsonb_build_object is only marked as stable (run \df+ jsonb_build_object in psql). As a workaround one can define a wrapper marked as immutable:
CREATE FUNCTION jsonb_build_object_imm(VARIADIC input anyarray) RETURNS JSONB AS $$
SELECT jsonb_build_object(VARIADIC input)
$$
LANGUAGE SQL IMMUTABLE LEAKPROOF PARALLEL SAFE;
Generated columns can only use immutable functions (docs) while jsonb_build_object() is only marked as stable (run \df+ jsonb_build_object in psql to check).
As a workaround one can define a wrapper function marked as immutable (but beware of consequences depending on your usage scenario):
CREATE FUNCTION jsonb_build_object_imm(VARIADIC input anyarray) RETURNS JSONB AS $$
SELECT jsonb_build_object(VARIADIC input)
$$
LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
The reason why jsonb_build_object() is only stable because to_json() is only stable as many other type conversions -- compare with json_object() which takes only strings and is marked as immutable.

Using a variable in a Postgres function [duplicate]

It must be simple, but I'm making my first steps into Postgres functions and I can't find anything that works...
I'd like to create a function that will modify a table and / or column and I can't find the right way of specifying my tables and columns as arguments in my function.
Something like:
CREATE OR REPLACE FUNCTION foo(t table)
RETURNS void AS $$
BEGIN
alter table t add column c1 varchar(20);
alter table t add column c2 varchar(20);
alter table t add column c3 varchar(20);
alter table t add column c4 varchar(20);
END;
$$ LANGUAGE PLPGSQL;
select foo(some_table)
In another case, I'd like to have a function that alters a certain column from a certain table:
CREATE OR REPLACE FUNCTION foo(t table, c column)
RETURNS void AS $$
BEGIN
UPDATE t SET c = "This is a test";
END;
$$ LANGUAGE PLPGSQL;
Is it possible to do that?
You must defend against SQL injection whenever you turn user input into code. That includes table and column names coming from system catalogs or from direct user input alike. This way you also prevent trivial exceptions with non-standard identifiers. There are basically three built-in methods:
1. format()
1st query, sanitized:
CREATE OR REPLACE FUNCTION foo(_t text)
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format('
ALTER TABLE %I ADD COLUMN c1 varchar(20)
, ADD COLUMN c2 varchar(20)', _t);
END
$func$;
format() requires Postgres 9.1 or later. Use it with the %I format specifier.
The table name alone may be ambiguous. You may have to provide the schema name to avoid changing the wrong table by accident. Related:
INSERT with dynamic table name in trigger function
How does the search_path influence identifier resolution and the "current schema"
Aside: adding multiple columns with a single ALTER TABLE command is cheaper.
2. regclass
You can also use a cast to a registered class (regclass) for the special case of existing table names. Optionally schema-qualified. This fails immediately and gracefully for table names that are not be valid and visible to the calling user. The 1st query sanitized with a cast to regclass:
CREATE OR REPLACE FUNCTION foo(_t regclass)
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE 'ALTER TABLE ' || _t || ' ADD COLUMN c1 varchar(20)
, ADD COLUMN c2 varchar(20)';
END
$func$;
Call:
SELECT foo('table_name');
Or:
SELECT foo('my_schema.table_name'::regclass);
Aside: consider using just text instead of varchar(20).
3. quote_ident()
The 2nd query sanitized:
CREATE OR REPLACE FUNCTION foo(_t regclass, _c text)
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE 'UPDATE ' || _t -- sanitized with regclass
|| ' SET ' || quote_ident(_c) || ' = ''This is a test''';
END
$func$;
For multiple concatenations / interpolations, format() is cleaner ...
Related answers:
Table name as a PostgreSQL function parameter
Postgres functions vs prepared queries
Case sensitive!
Be aware that unquoted identifiers are not cast to lower case here. When used as identifier in SQL [Postgres casts to lower case automatically][7]. But here we pass strings for dynamic SQL. When escaped as demonstrated, CaMel-case identifiers (like UserS) will be preserved by doublequoting ("UserS"), just like other non-standard names like "name with space" "SELECT"etc. Hence, names are case sensitive in this context.
My standing advice is to use legal lower case identifiers exclusively and never worry about that.
Aside: single quotes are for values, double quotes are for identifiers. See:
Are PostgreSQL column names case-sensitive?

Algebraic Data Types in Postgres

Is it possible to create an Algebraic Data Type in Postgres and then use it as a column type?
For example:
CREATE TYPE hoofed AS ENUM('horse', 'goat');
CREATE TYPE monkey AS ENUM('chimp','macaque');
CREATE TYPE ANIMAL AS ENUM(hoofed, monkey);
This fails with:
syntax error at or near "hoofed"
LINE 1: CREATE TYPE ANIMAL AS ENUM(hoofed, monkey);
Is it possible to do something like this?
Ultimately what I would then like to be able to do is something like so:
CREATE TABLE zoo (
a ANIMAL,
name text
);
INSERT INTO zoo(a, name) VALUES('horse', 'bob');
INSERT INTO zoo(a, name) VALUES('macaque', 'jimmy');
And for both of the records to be independently valid.
EDIT: #Abihabi87's response below does allow me to create, in effect, a product type, but it still does not allow me to create a union type as desired.
You cant create type enum from others enum type:
you can create ANIMAL that like:
CREATE TYPE ANIMAL AS (h hoofed,m monkey);
Example in use:
CREATE TABLE your_table
(
a ANIMAL
);
INSERT INTO your_table(a) select (select ('horse','macaque')::ANIMAL);
With ENUM types, you cannot achieve dynamic type composition/union. However, with DOMAIN types, you could achieve something similar:
create function valid_any_domain(anyelement, variadic regtype[])
returns boolean
language plpgsql
immutable
as $func$
declare
t regtype;
begin
foreach t in array $2 loop
begin
execute format('select $1::%s', t) using $1;
exception
when not_null_violation or check_violation then
continue;
end;
return true;
end loop;
return false;
end;
$func$;
create domain hoofed as text
check (value in ('horse', 'goat'));
create domain monkey as text
check (value in ('chimp','macaque'));
create domain animal as text
check (valid_any_domain(value, 'hoofed', 'monkey'));
Changing the base types will dynamically change the composite/union type too, but still requires a manual constraint validation (especially, when some value(s) are removed from the valid spectrum):
alter domain hoofed drop constraint hoofed_check;
alter domain hoofed add check (value in ('horse', 'goat', 'zebra'));
alter domain animal validate constraint animal_check;
http://rextester.com/MBVC62095
Note: however, with DOMAIN types, you will lose an ENUM property: the custom ordering. DOMAINs will always use the underlying type's ordering.
Use the function:
create or replace function create_enum(name, variadic regtype[])
returns void language plpgsql as $$
begin
execute format(
'create type %I as enum(%s)',
$1,
string_agg(quote_literal(enumlabel), ',' order by enumtypid, enumsortorder))
from pg_enum
where enumtypid = any($2);
end $$;
Pass the name of a new type and a list of enum types as arguments:
select create_enum('animal', 'hoofed', 'monkey');
select enum_range(null::animal) as animal;
animal
----------------------------
{horse,goat,chimp,macaque}
(1 row)
Effectively you are trying to merge two enum types.
There are some open questions:
Can there be duplicate strings?
Is the design supposed to be static (changes to enum type hoofed do not change type animal later) or dynamic (the opposite).
Merge exactly two enum types or more?
Since the order of elements is significant, what is the order of elements in animal supposed to be?
Is this a one-time operation or intended for repeated use?
Assuming no duplicates, static design, two enum types, existing order of elements as appended and one-time operation.
You can use the built-in enum support function enum_range(anyenum) to get an array of all elements for a given enum type.
DO
$$
BEGIN
EXECUTE (
SELECT 'CREATE TYPE animal AS ENUM ('
|| array_to_string(enum_range(null::hoofed)::text[]
|| enum_range(null::monkey)::text[], ''',''')
|| ''')'
);
END
$$;

Malformed array literal when selecting into custom type within a postgresql function

I have a postgresql custom type, containing arrays
CREATE TYPE route_part (
nodea bigint[],
edgea bigint[],
geom geometry
);
And a function, returning this type
CREATE OR REPLACE FUNCTION net.get_route_part_dist(int8, int8, int4)
RETURNS route_part
AS
$BODY$
DECLARE routerec route_part;
BEGIN
SELECT INTO routerec
...
;
RETURN routerec;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
This function works as expected and returns route_part composite type.
I'm trying to use it inside another "wrapper" function, that looks like this:
CREATE OR REPLACE FUNCTION net.get_route(beg_ int8, end_ int8, mida int8[], dist int4)
RETURNS route_part
AS
$BODY$
DECLARE routerec route_part;
BEGIN
SELECT INTO routerec net.get_route_part_dist(beg_, end_, dist);
RETURN routerec;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
I get an error on the select query.
ERROR: malformed array literal: "(
{303513543,2289605239,...,306687989}","
{2585314,264212,...,1088633}",
0102000020110F000029000000AE47E11A81754F41C3F5280C07F25C)"
DETAIL: Array value must start with "{" or dimension information.
I don't cast types to strings or other types, so I can't figure out why the returned value considered to have a malformed array.
Any clues?
The solution is to assign decomposed values:
CREATE OR REPLACE FUNCTION net.get_route(beg_ int8, end_ int8, mida int8[], dist int4)
RETURNS route_part AS
$func$
DECLARE
routerec route_part;
BEGIN
SELECT INTO routerec * FROM net.get_route_part_dist(beg_, end_, dist);
RETURN routerec;
END
$func$ LANGUAGE plpgsql;
Since routerec is a row type (composite type), The columns of the SELECT list must match the columns of the row type. The form you had would attempt to fit the value (as a whole) returned by net.get_route_part_dist() into the first column of routerec.
Quoting the manual:
If a row or a variable list is used as target, the query's result
columns must exactly match the structure of the target as to number
and data types
Postgres tries to fit your composite type (or rather its text representation) into bigint[], the first column of the composite type routerec. The error message you quoted is the consequence.
Explanation in the manual:
If the expression's result data type doesn't match the variable's data
type, the value will be coerced as though by an assignment cast (see
Section 10.4). If no assignment cast is known for the pair of data
types involved, the PL/pgSQL interpreter will attempt to convert the
result value textually, that is by applying the result type's output
function followed by the variable type's input function. Note that
this could result in run-time errors generated by the input function,
if the string form of the result value is not acceptable to the input function.
This can be confusing and fooled the first time as well. The distinction seems necessary since INTO allows assigning a list of target variables at once.
The bottom line is this: Decompose row types with SELECT * FROM ... when assigning to a row / record / composite type with INTO. Else it will be assigned to the first target column as a whole.
Avoid these inefficient forms:
Like you commented:
SELECT INTO routerec (net.get_route_part_dist(beg_, end_, dist)).nodea
, (net.get_route_part_dist(beg_, end_, dist)).edgea
, (net.get_route_part_dist(beg_, end_, dist)).geom;
Or, less verbose, but equally inefficient:
SELECT INTO routerec (net.get_route_part_dist(beg_, end_, dist)).*;
Each would evaluate the function multiple times - as opposed to:
SELECT INTO routerec * FROM net.get_route_part_dist(beg_, end_, dist)
Related:
Use of custom return types in a FOR loop in plpgsql
Passing array of a composite type to stored procedure
Simple alternative
The simple alternative for your simple case: direct assignment (without INTO):
routerec := net.get_route_part_dist(beg_, end_, dist);
RETURN routerec;
Simple assignment only allows a single target to begin with.
Or return the result directly:
RETURN net.get_route_part_dist(beg_, end_, dist);

Postgresql trigger function with parameters

I want to create a trigger on a table called takes in postgresql to update a value in another table called student
I'm trying to do it in the following way. But I'm getting an error that there is syntax error near "OLD". I don't understand whats wrong with this. This is my code:
CREATE OR REPLACE FUNCTION upd8_cred_func
(id1 VARCHAR, gr1 VARCHAR,id2 VARCHAR, gr2 VARCHAR)
RETURNS void AS $$
BEGIN
IF (id1=id2 and gr1 is null and gr2 is not null) THEN
update student set tot_cred = tot_cred + 6 where id = id1;
END IF;
RETURN;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER upd8_cred
AFTER UPDATE ON takes
FOR EACH ROW
EXECUTE PROCEDURE upd8_cred_func(OLD.id,OLD.grade,NEW.id,NEW.grade);
You do not need to pass the NEW and OLD as parameters to the trigger function. They are automagically available there:
http://www.postgresql.org/docs/9.1/interactive/trigger-definition.html :
The trigger function must be declared as a function taking no arguments and returning type trigger. (The trigger function receives its input through a specially-passed TriggerData structure, not in the form of ordinary function arguments.)
About the records passed to the trigger procedure, please see http://www.postgresql.org/docs/9.1/interactive/plpgsql-trigger.html :
When a PL/pgSQL function is called as a trigger, several special variables are created automatically in the top-level block. They are: [...] NEW, [...] OLD [...]
As SeldomNeedy pointed in the comment below, you can still pass and use parameters to the trigger function. You declare the function as taking no parameters, but when defining the trigger (by CREATE TRIGGER), you may add some.
They will be available for the trigger as TG_NARG (the number of such parameters), and TG_ARGV[] (an array of text values).
As Greg stated, trigger functions can take arguments, but the functions themselves cannot have declared parameters. Here's a simple example in plpgsql:
CREATE TABLE my_table ( ID SERIAL PRIMARY KEY ); -- onelined for compactness
CREATE OR REPLACE FUNCTION raise_a_notice() RETURNS TRIGGER AS
$$
DECLARE
arg TEXT;
BEGIN
FOREACH arg IN ARRAY TG_ARGV LOOP
RAISE NOTICE 'Why would you pass in ''%''?',arg;
END LOOP;
RETURN NEW; -- in plpgsql you must return OLD, NEW, or another record of table's type
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER no_inserts_without_notices BEFORE INSERT ON my_table
FOR EACH ROW EXECUTE PROCEDURE raise_a_notice('spoiled fish','stunned parrots');
INSERT INTO my_table DEFAULT VALUES;
-- the above kicks out the following:
--
-- NOTICE: Why would you pass in 'spoiled fish'?
-- NOTICE: Why would you pass in 'stunned parrots'?
--
There are a few other goodies such as TG_NARGS (to know how many args you got without looping through them) discussed in the docs. There's also information there about how to get the name of the triggering table in case you have mostly-but-not-quite-shared logic for one trigger-function that spans a number of tables.
The trigger function can have parameters, but, you can't have those parameters passed like a normal function (e.g. arguments in the function definition). You can get the same result... In python you get access to the OLD and NEW data as the answer above describes. For example, I can use TD['new']['column_name'] in python to reference the new data for column_name. You also have access to the special variable TD['args']. So, if you like:
create function te() returns trigger language plpython2u as $function$
plpy.log("argument passed 1:%s 2:%s" %(TD['args'][0], TD['args'][1], ))
$function$
create constraint trigger ta after update of ttable
for each for execute procedure te('myarg1','myarg2');
Granted, these arguments are static, but, they are useful when calling a common trigger function from multiple trigger declarations. I am pretty sure that the same variables are available for other stored procedure languages. (sorry if the code doesn't work verbatim, but, I do practice this technique, so I know you can pass arguments!).