Thursday, October 29, 2009

Named function parameters - a feature of PostgreSQL 8.5

Hello

New PostgreSQL 8.5 alfa 2 allows to use named parameters. You may to know this feature from Oracle or MS SQL server. This feature is possible in PostgreSQL now too. Following function add n working days to specified date. What is new? You can specify a free days (default is based on europen traditions).

CREATE TYPE dayname AS ENUM ('sun','mon','tue','wed','thu','fri','sat');

CREATE OR REPLACE FUNCTION dow(dayname) 
RETURNS int AS $$ 
SELECT CASE $1 
           WHEN 'sun' THEN 0 
           WHEN 'mon' THEN 1 
           WHEN 'tue' THEN 2 
           WHEN 'wed' THEN 3 
           WHEN 'thu' THEN 4 
           WHEN 'fri' THEN 5 
           WHEN 'sat' THEN 6 END; 
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION dows(dayname[]) 
RETURNS int[] AS $$ 
SELECT ARRAY(SELECT dow(d) FROM UNNEST($1) d(d))
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION addwdays(date, int, freedays text[] = ARRAY['sat','sun']) 
RETURNS date AS $$
SELECT ad 
   FROM (SELECT $1 + i as ad, 
                extract (dow from $1 + i), 
                sum(CASE WHEN EXTRACT(dow FROM $1 + i) = ANY(dows($3::dayname[])) THEN 0 ELSE 1 END) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS x 
            FROM generate_series(1,1000) g(i)) s 
  WHERE x = $2 AND date_part <> ALL(dows($3::dayname[]));
$$ LANGUAGE sql;

postgres=# select current_date, addwdays(current_date,1);
    date    │  addwdays  
────────────┼────────────
 2009-10-29 │ 2009-10-30
(1 row)

Time: 1,768 ms
postgres=# select current_date, addwdays(current_date,1, ARRAY['fri'] AS freedays);
    date    │  addwdays  
────────────┼────────────
 2009-10-29 │ 2009-10-31
(1 row)

Please, test it.
see http://www.postgresql.org/about/news.1152

Thursday, October 1, 2009

Dynamic access to record fields in PL/pgSQL

PL/pgSQL isn't dynamic language. Usually it isn't problem. This language is best as glue of SQL statements. One typical feature is strongly static access to record or row fields. But sometime we need dynamic access. This is very hard task in older version. Some better situation is in 8.4, and maybe in 8.5, this problem is definitely out. New an enhancing of hstore module allows conversion from ROW type to hstore type (this is similar to hash table in Perl). We could to use this functionality:

postgres=# SELECT * FROM foo;
 a  | b  |   c    
----+----+--------
 10 | 20 | Pavel
 30 | 40 | Zdenek
(2 rows)

postgres=# select (each(hstore(foo))).* from foo;
 key | value  
-----+--------
 a   | 10
 b   | 20
 c   | Pavel
 a   | 30
 b   | 40
 c   | Zdenek
(6 rows)

CREATE OR REPLACE FUNCTION trgfce() 
RETURNS trigger AS $$
DECLARE r record; 
BEGIN 
  FOR r IN SELECT (each(hstore(NEW))).* 
  LOOP 
    RAISE NOTICE 'key:%, value: %', r.key, r.value; 
  END LOOP; 
  RETURN new; 
END
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg BEFORE INSERT ON foo FOR EACH ROW EXECUTE PROCEDURE trgfce();

postgres=# INSERT INTO foo VALUES(80,90,'Zbynek');
NOTICE:  key:a, value: 80
NOTICE:  key:b, value: 90
NOTICE:  key:c, value: Zbynek
INSERT 0 1

Saturday, August 22, 2009

MySQL functions for PostgreSQL

Hello

I found article about MySQL function field. PostgreSQL doesn't support similar function (it is nice together with ORDER BY expression clause), but with new variadic parameters is easy to develop it in pg:

CREATE OR REPLACE FUNCTION field(text, variadic text[])
RETURNS int AS $$
  SELECT i
     FROM generate_subscripts($2,1) g(i)
    WHERE $1 = $2[i]
    UNION ALL
    SELECT 0
    LIMIT 1
$$ LANGUAGE sql STRICT;
An using looks like:
select * from pet order by field(species, 'cat', 'dog', 'bird');

+----------+--------+---------+------+------------+------------+
| name     | owner  | species | sex  | birthday   | death      |
+----------+--------+---------+------+------------+------------+
| Fluffy   | Harold | cat     | f    | 1993-02-04 | NULL       |
| Claws    | Gwen   | cat     | m    | 1994-03-17 | NULL       |
| Buffy    | Harold | dog     | f    | 1989-05-13 | NULL       |
| Fang     | Benny  | dog     | m    | 1990-08-27 | NULL       |
| Bowser   | Diane  | dog     | m    | 1989-08-31 | 1995-07-29 |
| Chirpy   | Gwen   | bird    | f    | 1998-09-11 | NULL       |
| Whistler | Gwen   | bird    | NULL | 1997-12-09 | NULL       |
+----------+--------+---------+------+------------+------------+
When I played with it, I ported mostly used MySQL functions to PostgreSQL. I hope, so this file can help somebody with porting applications to PostgreSQL or with better orientation in PostgreSQL. Lot of thing with strings or date PostgreSQL is done little bit different than MySQL - these functions can help.

String Functions

CREATE OR REPLACE FUNCTION concat(variadic str text[])
RETURNS text AS $$
SELECT array_to_string($1, '');
$$ LANGUAGE sql

CREATE OR REPLACE FUNCTION concat_ws(separator text, variadic str text[])
RETURNS text as $$
SELECT array_to_string($2, $1);
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION elt(int, VARIADIC text[])
RETURNS text AS $$
SELECT $2[$1];
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION field(varchar, VARIADIC text[])
RETURNS int AS $$
SELECT i
FROM generate_subscripts($2,1) g(i)
WHERE $1 = $2[i]
UNION ALL
SELECT 0
LIMIT 1
$$ LANGUAGE sql STRICT;

CREATE OR REPLACE FUNCTION find_in_set(str text, strlist text)
RETURNS int AS $$
SELECT i
   FROM generate_subscripts(string_to_array($2,','),1) g(i)
  WHERE (string_to_array($2, ','))[i] = $1
  UNION ALL
  SELECT 0
  LIMIT 1
$$ LANGUAGE sql STRICT;

CREATE OR REPLACE FUNCTION hex(int)
RETURNS text AS $$
SELECT upper(to_hex($1));
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION hex(bigint)
RETURNS text AS $$
SELECT upper(to_hex($1));
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION hex(text)
RETURNS text AS $$
SELECT upper(encode($1::bytea, 'hex'))
$$ LANGUAGE sql;

/*
* char is keyword, double quotes are necessary.
*
*  postgres=# select "char"(77,121,83,81,'76');
*  char
* -------
*  MySQL
*/
CREATE OR REPLACE FUNCTION "char"(VARIADIC int[])
RETURNS text AS $$
SELECT array_to_string(ARRAY(SELECT chr(unnest($1))),'')
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION lcase(str text)
RETURNS text AS $$
SELECT lower($1)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION left(str text, len int)
RETURNS text AS $$
SELECT substring($1 FROM 1 FOR $2)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION locate(substr text, str text)
RETURNS int AS $$
SELECT position($1 in $2)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION reverse(str text)
RETURNS text AS $$
SELECT array_to_string(ARRAY(SELECT substring($1 FROM i FOR 1)
                                FROM generate_series(length($1),1,-1) g(i)),
                       '')
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION right(str text, len int)
RETURNS text AS $$
SELECT substring($1 FROM length($1) - $2 FOR $2)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION space(n int)
RETURNS text AS $$
SELECT repeat(' ', $1)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION strcmp(text, text)
RETURNS int AS $$
SELECT CASE WHEN $1 < $2 THEN -1
WHEN $1 > $2 THEN 1
ELSE 0 END;
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION substring_index(str text, delim text, count int)
RETURNS text AS $$
SELECT CASE WHEN $3 > 0 
THEN array_to_string((string_to_array($1, $2))[1:$3], $2)
ELSE array_to_string(ARRAY(SELECT unnest(string_to_array($1,$2))
                             OFFSET array_upper(string_to_array($1,$2),1) + $3),
                     $2)
END
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION ucase(str text)
RETURNS text AS $$
SELECT upper($1)
$$ LANGUAGE sql;

CREATE CAST (bytea AS text) WITHOUT FUNCTION AS ASSIGNMENT;

/*
* SELECT hex('žlutý kůň'), unhex(hex('žlutý kůň'))
*/
CREATE OR REPLACE FUNCTION unhex(text)
RETURNS text AS $$
SELECT decode($1, 'hex')::text;
$$ LANGUAGE sql;

Date, time Functions

/*
* postgres=# select adddate('2008-01-02','31 day');
*   adddate
* -----------
*  2008-02-02
*/
CREATE OR REPLACE FUNCTION adddate(date, interval)
RETURNS date AS $$
SELECT ($1 + $2)::date; $$
LANGUAGE sql;

CREATE OR REPLACE FUNCTION curdate()
RETURNS date AS $$
SELECT CURRENT_DATE
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION convert_tz(dt timestamp, from_tz text, to_tz text)
RETURNS timestamp AS $$
SELECT ($1 AT TIME ZONE $2) AT TIME ZONE $3;
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION date(anyelement)
RETURNS date AS $$
SELECT $1::date;
$$ LANGUAGE sql;

SELECT OR REPLACE FUNCTION datediff(date, date)
RETURNS int AS $$
SELECT $1 - $2
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION date_add(date, interval)
RETURNS date AS $$
SELECT adddate($1, $2)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION date_format(date, text)
RETURNS text AS $$
SELECT to_char($1, _mysqlf_pgsql($2))
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION date_format(timestamp, text)
RETURNS text AS $$
SELECT to_char($1, _mysqlf_pgsql($2))
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION date_sub(date, interval)
RETURNS date AS $$
SELECT ($1 - $2)::date;
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION dayofmonth(date)
RETURNS int AS $$
SELECT EXTRACT(day from $1)::int
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION day(date)
RETURNS int AS $$
SELECT dayofmonth($1)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION dayname(date)
RETURNS text AS $$
SELECT to_char($1, 'TMDay')
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION dayofweek(date)
RETURNS int AS $$
SELECT EXTRACT(dow FROM $1)::int
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION dayofyear(date)
RETURNS int AS $$
SELECT EXTRACT(doy FROM $1)::int
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION from_days(int)
RETURNS date AS $$
SELECT date '0001-01-01bc' + $1
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION from_unixtime(double precision)
RETURNS timestamp AS $$
SELECT to_timestamp($1)::timestamp
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION _mysqlf_pgsql(text)
RETURNS text AS $$
SELECT array_to_string(ARRAY(SELECT s
FROM (SELECT CASE WHEN substring($1 FROM i FOR 1) <> '%'
AND substring($1 FROM i-1 FOR 1) <> '%'
THEN substring($1 FROM i for 1)
ELSE CASE substring($1 FROM i FOR 2)
WHEN '%H' THEN 'HH24'
WHEN '%p' THEN 'am'
WHEN '%Y' THEN 'YYYY'
WHEN '%m' THEN 'MM'
WHEN '%d' THEN 'DD'
WHEN '%i' THEN 'MI'
WHEN '%s' THEN 'SS'
WHEN '%a' THEN 'Dy'
WHEN '%b' THEN 'Mon'
WHEN '%W' THEN 'Day'
WHEN '%M' THEN 'Month'
END
END s
FROM generate_series(1,length($1)) g(i)) g
WHERE s IS NOT NULL),
'')
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION get_format(text, text)
RETURNS text AS $$
SELECT CASE lower($1)
WHEN 'date' THEN
CASE lower($2)
WHEN 'usa' THEN '%m.%d.%Y'
WHEN 'jis' THEN '%Y-%m-%d'
WHEN 'iso' THEN '%Y-%m-%d'
WHEN 'eur' THEN '%d.%m.%Y'
WHEN 'internal' THEN '%Y%m%d'
END
WHEN 'datetime' THEN
CASE lower($2)
WHEN 'usa' THEN '%Y-%m-%d %H-.%i.%s'
WHEN 'jis' THEN '%Y-%m-%d %H:%i:%s'
WHEN 'iso' THEN '%Y-%m-%d %H:%i:%s'
WHEN 'eur' THEN '%Y-%m-%d %H.%i.%s'
WHEN 'internal' THEN '%Y%m%d%H%i%s'
END
WHEN 'time' THEN
CASE lower($2)
WHEN 'usa' THEN '%h:%i:%s %p'
WHEN 'jis' THEN '%H:%i:%s'
WHEN 'iso' THEN '%H:%i:%s'
WHEN 'eur' THEN '%H.%i.%s'
WHEN 'internal' THEN '%H%i%s'
END
END;
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION hour(time)
RETURNS int AS $$
SELECT EXTRACT(hour FROM $1)::int;
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION hour(timestamp)
RETURNS int AS $$
SELECT EXTRACT(hour FROM $1)::int;
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION last_day(date)
RETURNS date AS $$
SELECT (date_trunc('month',$1 + interval '1 month'))::date - 1
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION makedate(year int, dayofyear int)
RETURNS date AS $$
SELECT (date '0001-01-01' + ($1 - 1) * interval '1 year' + ($2 - 1) * interval '1 day'):: date
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION maketime(int, int, double precision)
RETURNS time AS $$
SELECT time '00:00:00' + $1 * interval '1 hour' + $2 * interval '1 min'
+ $3 * interval '1 sec'
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION minute(timestamp)
RETURNS int AS $$
SELECT EXTRACT(minute FROM $1)::int
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION month(date)
RETURNS int AS $$
SELECT EXTRACT(month FROM $1)::int
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION monthname(date)
RETURNS text AS $$
SELECT to_char($1, 'TMMonth')
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION str_to_date(text, text)
RETURNS date AS $$
SELECT to_date($1, _mysqlf_pgsql($2))
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION time(timestamp)
RETURNS time AS $$
SELECT $1::time
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION to_days(date)
RETURNS int AS $$
SELECT $1 - '0001-01-01bc'
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION unix_timestamp()
RETURNS double precision AS $$
SELECT EXTRACT(epoch FROM current_timestamp)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION unix_timestamp(timestamp)
RETURNS double precision AS $$
SELECT EXTRACT(epoch FROM $1)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION year(date)
RETURNS int AS $$
SELECT EXTRACT(year FROM $1)
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION week(date)
RETURNS int AS $$
SELECT EXTRACT(week FROM $1)::int;
$$ LANGUAGE sql;

GROUP_CONCAT

PostgreSQL doesn't support aggregate function group_concat. But you can use aggregate array_agg:
postgres=# select * from omega;
+---+
| x |
+---+
| 1 |
| 3 |
| 6 |
+---+
(3 rows)

postgres=# select array_to_string(array_agg(x),',') from omega;
+-----------------+
| array_to_string |
+-----------------+
| 1,3,6           |
+-----------------+
(1 row)
If you search more complete MySQL API, look on mysqlcompat project. Regards Pavel

Thursday, August 13, 2009

epsql available

Hello

Today I uploaded patch enhancing psql on pgfoundry. You can download it from url http://pgfoundry.org/frs/download.php/2335/epsql.diff. This patch should be applied on postgresql 8.4 source:
cd postgresql-8.4.0/src/bin/psql
patch -p1 < epsql.diff
make clean
make all
su
make install
Enhanced psql has new metacommand \lf and new formating option linestyle (+2 new border styles).

\lf

\lf print function's source code (with and without row numbers):
postgres=# \lf foo(int)
**** CREATE OR REPLACE FUNCTION public.foo(_a integer)
**** RETURNS integer
**** LANGUAGE plpgsql
**** AS $function$
1 BEGIN
2 RETURN _a + 1;
3 END;
**** $function$

postgres=# \lf- foo(int)
CREATE OR REPLACE FUNCTION public.foo(_a integer)
RETURNS integer
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN _a + 1;
END;
$function$

smart wrap mode

original wrap mode:
postgres=# \pset format wrapped
Output format is wrapped.
postgres=# select * from test;
+--------------------------------------------------------------------------------------+
| a |
+--------------------------------------------------------------------------------------+
| Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor inci |
| didunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exerc |
| itation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dol |
| or in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. |
| Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt m |
| ollit anim id est laborum. |
+--------------------------------------------------------------------------------------+
(1 row)
modified wrap mode:
postgres=# select * from test;
+--------------------------------------------------------------------------------------+
| a |
+--------------------------------------------------------------------------------------+
| Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor |
| incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud |
| exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute |
| irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla |
| pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia |
| deserunt mollit anim id est laborum. |
+--------------------------------------------------------------------------------------+
(1 row)

Border styles

postgres=# \pset border 0
Border style is 0.
postgres=# \d pg_database
Table "pg_catalog.pg_database"
Column Type Modifiers
------------- --------- ---------
datname name not null
datdba oid not null
encoding integer not null
datcollate name not null
datctype name not null
datistemplate boolean not null
datallowconn boolean not null
datconnlimit integer not null
datlastsysoid oid not null
datfrozenxid xid not null
dattablespace oid not null
datconfig text[]
datacl aclitem[]

postgres=# \pset border 1
Border style is 1.
postgres=# \d pg_database
Table "pg_catalog.pg_database"
Column | Type | Modifiers
---------------+-----------+-----------
datname | name | not null
datdba | oid | not null
encoding | integer | not null
datcollate | name | not null
datctype | name | not null
datistemplate | boolean | not null
datallowconn | boolean | not null
datconnlimit | integer | not null
datlastsysoid | oid | not null
datfrozenxid | xid | not null
dattablespace | oid | not null
datconfig | text[] |
datacl | aclitem[] |

postgres=# \pset border 2
Border style is 2.
postgres=# \d pg_database
Table "pg_catalog.pg_database"
+---------------+-----------+-----------+
| Column | Type | Modifiers |
+---------------+-----------+-----------+
| datname | name | not null |
| datdba | oid | not null |
| encoding | integer | not null |
| datcollate | name | not null |
| datctype | name | not null |
| datistemplate | boolean | not null |
| datallowconn | boolean | not null |
| datconnlimit | integer | not null |
| datlastsysoid | oid | not null |
| datfrozenxid | xid | not null |
| dattablespace | oid | not null |
| datconfig | text[] | |
| datacl | aclitem[] | |
+---------------+-----------+-----------+

postgres=# \pset border 3
Border style is 3.
postgres=# \d pg_database
Table "pg_catalog.pg_database"
+---------------+-----------+-----------+
| Column | Type | Modifiers |
+---------------+-----------+-----------+
| datname | name | not null |
+---------------+-----------+-----------+
| datdba | oid | not null |
+---------------+-----------+-----------+
| encoding | integer | not null |
+---------------+-----------+-----------+
| datcollate | name | not null |
+---------------+-----------+-----------+
| datctype | name | not null |
+---------------+-----------+-----------+
| datistemplate | boolean | not null |
+---------------+-----------+-----------+
| datallowconn | boolean | not null |
+---------------+-----------+-----------+
| datconnlimit | integer | not null |
+---------------+-----------+-----------+
| datlastsysoid | oid | not null |
+---------------+-----------+-----------+
| datfrozenxid | xid | not null |
+---------------+-----------+-----------+
| dattablespace | oid | not null |
+---------------+-----------+-----------+
| datconfig | text[] | |
+---------------+-----------+-----------+
| datacl | aclitem[] | |
+---------------+-----------+-----------+

postgres=# \pset border 4
Border style is 4.
postgres=# \d pg_database
Table "pg_catalog.pg_database"
Column | Type | Modifiers
---------------+-----------+----------
datname | name | not null
---------------+-----------+----------
datdba | oid | not null
---------------+-----------+----------
encoding | integer | not null
---------------+-----------+----------
datcollate | name | not null
---------------+-----------+----------
datctype | name | not null
---------------+-----------+----------
datistemplate | boolean | not null
---------------+-----------+----------
datallowconn | boolean | not null
---------------+-----------+----------
datconnlimit | integer | not null
---------------+-----------+----------
datlastsysoid | oid | not null
---------------+-----------+----------
datfrozenxid | xid | not null
---------------+-----------+----------
dattablespace | oid | not null
---------------+-----------+----------
datconfig | text[] |
---------------+-----------+----------
datacl | aclitem[] |

Linestyles

postgres=# \pset linestyle 0
Border line style is 0.
postgres=# \d pg_user_mapping
Table "pg_catalog.pg_user_mapping"
+-----------+--------+-----------+
| Column | Type | Modifiers |
+-----------+--------+-----------+
| umuser | oid | not null |
+-----------+--------+-----------+
| umserver | oid | not null |
+-----------+--------+-----------+
| umoptions | text[] | |
+-----------+--------+-----------+

postgres=# \pset linestyle 1
Border line style is 1.
postgres=# \d pg_user_mapping
Table "pg_catalog.pg_user_mapping"
┌───────────┬────────┬───────────┐
│ Column │ Type │ Modifiers │
├───────────┼────────┼───────────┤
│ umuser │ oid │ not null │
├───────────┼────────┼───────────┤
│ umserver │ oid │ not null │
├───────────┼────────┼───────────┤
│ umoptions │ text[] │ │
└───────────┴────────┴───────────┘

postgres=# \pset linestyle 2
Border line style is 2.
postgres=# \d pg_user_mapping
Table "pg_catalog.pg_user_mapping"
┌───────────┬────────┬───────────┐
│ Column │ Type │ Modifiers │
├───────────┴────────┴───────────┤
│ umuser │ oid │ not null │
├───────────┼────────┼───────────┤
│ umserver │ oid │ not null │
├───────────┼────────┼───────────┤
│ umoptions │ text[] │ │
└────────────────────────────────┘

postgres=# \pset linestyle 3
Border line style is 3.
postgres=# \d pg_user_mapping
Table "pg_catalog.pg_user_mapping"
╔═══════════╤════════╤═══════════╗
║ Column │ Type │ Modifiers ║
╟───────────┼────────┼───────────╢
║ umuser │ oid │ not null ║
╟───────────┼────────┼───────────╢
║ umserver │ oid │ not null ║
╟───────────┼────────┼───────────╢
║ umoptions │ text[] │ ║
╚═══════════╧════════╧═══════════╝

postgres=# \pset linestyle 4
Border line style is 4.
postgres=# \d pg_user_mapping
Table "pg_catalog.pg_user_mapping"
╔═══════════╤════════╤═══════════╗
║ Column │ Type │ Modifiers ║
╠═══════════╪════════╪═══════════╣
║ umuser │ oid │ not null ║
╟───────────┼────────┼───────────╢
║ umserver │ oid │ not null ║
╟───────────┼────────┼───────────╢
║ umoptions │ text[] │ ║
╚═══════════╧════════╧═══════════╝

postgres=# \pset linestyle 5
Border line style is 5.
postgres=# \d pg_user_mapping
Table "pg_catalog.pg_user_mapping"
╔═══════════╦════════╦═══════════╗
║ Column ║ Type ║ Modifiers ║
╠═══════════╩════════╩═══════════╣
║ umuser │ oid │ not null ║
╟───────────┼────────┼───────────╢
║ umserver │ oid │ not null ║
╟───────────┼────────┼───────────╢
║ umoptions │ text[] │ ║
╚════════════════════════════════╝

postgres=# \pset linestyle 6
Border line style is 6.
postgres=# \d pg_user_mapping
Table "pg_catalog.pg_user_mapping"
╔═══════════╦════════╦═══════════╗
║ Column ║ Type ║ Modifiers ║
╠═══════════╬════════╬═══════════╣
║ umuser ║ oid ║ not null ║
╟───────────╫────────╫───────────╢
║ umserver ║ oid ║ not null ║
╟───────────╫────────╫───────────╢
║ umoptions ║ text[] ║ ║
╚═══════════╩════════╩═══════════╝
Please, test it, use it.
Regards
Pavel Stehule

Tuesday, March 10, 2009

Experimental psql

Hello

I spent some on psql's face lifting. Some new features are really experimental. I don't expect early adopting it for core. It's more game for now. epsql will be available with release 8.4. What epsql can do?

UTF8 boxes

  • one new border style, five new border lines styles

Stored function's listing

  • should help with debuging

    postgres=# select test(10);
    ERROR: division by zero
    CONTEXT: PL/pgSQL function "test" line 3 at assignment

    postgres=# \lf test
    *** CREATE OR REPLACE FUNCTION public.test(a integer)
    *** RETURNS integer
    *** LANGUAGE plpgsql
    *** AS $function$
    1 declare b int;
    2 begin
    3 b := a/0;
    4 return a;
    5 end;
    *** $function$

Cursor's support

  • allows using data from database for statement parameters
    • \fetch
      postgres=# BEGIN;
      BEGIN
      postgres=# DECLARE c CURSOR FOR SELECT * FROM pg_database;
      DECLARE CURSOR
      postgres=# \fetch c \echo :datname :datcollate
      template1 cs_CZ.UTF-8
      postgres=# \fetch c \echo :datname :datcollate
      template0 cs_CZ.UTF-8
      postgres=# \fetch c \echo :datname :datcollate
      postgres cs_CZ.UTF-8
      postgres=# \fetch c \echo :datname :datcollate
      ERROR: (not available)
      postgres=# COMMIT;
      COMMIT
    • \fetchall
      postgres=# CREATE TABLE test1(a int); CREATE TABLE test2(a int);
      CREATE TABLE
      CREATE TABLE
      postgres=# BEGIN;
      BEGIN
      postgres=# DECLARE c CURSOR FOR SELECT tablename FROM pg_tables WHERE tablename LIKE 'test%';
      DECLARE CURSOR
      postgres=# \fetchall c DROP TABLE :tablename;
      DROP TABLE
      DROP TABLE
      postgres=# COMMIT;
      COMMIT
      postgres=#
I invite any ideas, comments.

Pavel

Labels:

Friday, February 20, 2009

8.3 migration helper

8.3 dropped some implicit casts. I didn't watch discus about it, but I am thinking so there are two reasons. First - it shows some programmer's mistakes, second - it warns against wrong written predicates. I can speak, so all it does well. When we migrated older application, we found lot of nonsense: typically numeric data are stored in varchar column or deprecated style (every constant is string literal) like:

any_numeric_column = 'any_number' ... -- deprecated
any_numeric_column = any_number ... -- good style

Peter Eisentraut wrote module for 8.3, that enable all disabled implicit casts. This helps with migration, but doesn't help with question, where disabled casts are used in application. I wrote function, that adds warning to theses casts:

CREATE OR REPLACE FUNCTION generator_81_casts()
RETURNS void AS $$
DECLARE
src varchar[] := '{integer,smallint,oid,date,double precision,real,time with time zone, time without time zone, timestamp with time zone, interval,bigint,numeric,timestamp without time zone}';
fn varchar[] := '{int4out,int2out,oidout,date_out,float8out,float4out,timetz_out,time_out,timestamptz_out,interval_out,int8out,numeric_out,timestamp_out}';
fn_name varchar;
fn_msg varchar; fn_body varchar;
BEGIN
FOR i IN array_lower(src,1)..array_upper(src,1) LOOP
fn_name := 'aux_cast_func_' || replace(src[i],' ','_') ||'_to_text';
fn_msg := '''using obsolete implicit casting from ' || src[i] || ' to text''';
fn_body := 'CREATE OR REPLACE FUNCTION '|| fn_name || '(' || src[i] ||') RETURNS text AS $_$ BEGIN RAISE WARNING '
|| fn_msg || ';RETURN textin(' || fn[i] || '($1)); END; $_$ LANGUAGE plpgsql IMMUTABLE';
EXECUTE fn_body;
-- for 8.1
--EXECUTE 'UPDATE pg_cast SET castfunc = ''' || fn_name || '''::regproc WHERE castsource = ''' || src[i] || '''::regtype AND casttarget = ''text''::regtype';
DROP CAST 'CREATE CAST (' || src[i] || ' AS text)';
EXECUTE 'CREATE CAST (' || src[i] || ' AS text) WITH FUNCTION ' || fn_name || '(' || src[i] || ') AS IMPLICIT';
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
SELECT generator_81_casts();

Saturday, December 27, 2008

DB2 SQL Procedures on Postgres? Yes, why not?

I ported PL/pgPSM for PostgreSQL 8.4 this week. PL/pgPSM is implementation of SQL/PSM language to PostgreSQL space. SQL/PSM is procedural language from ANSI SQL standard. DB2 or MySQL use it. PL/pgPSM has some features from IBM implementation, but it isn't fully compatible. It isn't possible - PostgreSQL is little bit different than DB2. Relation between PL/pgPSM and SQL PL is like relation between PL/pgSQL and PL/SQL.

Installation

  1. download source code,
  2. extract it in PL directory in PostgreSQL 8.4 source code tree
  3. compile and install it
    [pavel@localhost Desktop]$ mv plpgpsm.tgz ../src/pgsql/src/pl/
    [pavel@localhost Desktop]$ cd ../src/pgsql/src/pl/
    [pavel@localhost pl]$ tar xvfz plpgpsm.tgz
    plpgpsm/Makefile
    plpgpsm/src/
    plpgpsm/src/pl_gram.c
    plpgpsm/src/pl_exec.c
    plpgpsm/src/gram.y
    plpgpsm/src/pl_funcs.c
    plpgpsm/src/plpgpsm.h
    plpgpsm/src/y.tab.h
    plpgpsm/src/sql/
    plpgpsm/src/sql/plpgpsm.sql
    plpgpsm/src/INSTALL.plpgpsm
    plpgpsm/src/Makefile
    plpgpsm/src/pl_handler.c
    plpgpsm/src/plerrcodes.h
    plpgpsm/src/scan.l
    plpgpsm/src/y.tab.c
    plpgpsm/src/pl_comp.c
    plpgpsm/src/expected/
    plpgpsm/src/expected/plpgpsm.out
    plpgpsm/src/pl_gram.h
    plpgpsm/src/pl_scan.c
    [pavel@localhost pl]$ cd plpgpsm/src/
    [pavel@localhost src]$ make all
    ...
    [pavel@localhost src]$ su
    Heslo:
    [root@localhost src]# make install
    /bin/sh ../../../../config/mkinstalldirs '/usr/local/pgsql/lib'
    /bin/sh ../../../../config/install-sh -c -m 755 plpgpsm.so '/usr/local/pgsql/lib/plpgpsm.so'

  4. register pl handler:
    [pavel@localhost src]$ psql template1
    psql (8.4devel)
    Type "help" for help.

    template1=# insert into pg_pltemplate
    select 'plpgpsm','t'::boolean, 't'::boolean, 'plpgpsm_call_handler','plpgpsm_validator','$libdir/plpgpsm',NULL
    from (
    select 'plpgpsm'
    except all
    select tmplname
    from pg_pltemplate
    ) a;
    INSERT 0 0

  5. Check it:

    [pavel@localhost src]$ pwd
    /home/pavel/src/pgsql/src/pl/plpgpsm/src
    [pavel@localhost src]$ make installcheck
    make -C ../../../../src/test/regress pg_regress
    make[1]: Entering directory `/home/pavel/src/pgsql/src/test/regress'
    make[1]: `pg_regress' is up to date.
    make[1]: Leaving directory `/home/pavel/src/pgsql/src/test/regress'
    ../../../../src/test/regress/pg_regress --inputdir=. --psqldir= --dbname=pl_regression --load-language=plpgpsm plpgpsm
    (using postmaster on Unix socket, default port)
    ============== dropping database "pl_regression" ==============
    DROP DATABASE
    ============== creating database "pl_regression" ==============
    CREATE DATABASE
    ALTER DATABASE
    ============== installing plpgpsm ==============
    CREATE LANGUAGE
    ============== running regression test queries ==============
    test plpgpsm ... ok

    =====================
    All 1 tests passed.
    =====================

Usage


  1. Read some documentation http://www.sqlpl-guide.com/ and http://www.pgsql.cz/index.php/SQL/PSM_Manual, http://www.pgsql.cz/index.php/MySQL5:_Gentle_introduction_to_stored_procedures_of_MySQL5
  2. Play with it and use it :)
    postgres=# CREATE OR REPLACE FUNCTION foo2(a integer)
    postgres-# RETURNS void AS
    postgres-# $$
    postgres$# BEGIN
    postgres$# DECLARE i integer DEFAULT 1;
    postgres$# WHILE i <= a
    postgres$# DO
    postgres$# PRINT i;
    postgres$# SET i = i + 1;
    postgres$# END WHILE;
    postgres$# END
    postgres$# $$ LANGUAGE plpgpsm;
    CREATE FUNCTION
    postgres=# select foo2(3);
    NOTICE: 1
    NOTICE: 2
    NOTICE: 3
    foo2 ------

    (1 row)