Index: doc/src/sgml/plperl.sgml =================================================================== RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/plperl.sgml,v retrieving revision 2.22 diff -2 -c -r2.22 plperl.sgml *** doc/src/sgml/plperl.sgml 14 Dec 2003 00:10:32 -0000 2.22 --- doc/src/sgml/plperl.sgml 27 Apr 2004 21:10:23 -0000 *************** *** 47,53 **** To create a function in the PL/Perl language, use the standard syntax: ! CREATE FUNCTION funcname (argument-types) RETURNS return-type AS ' # PL/Perl function body ! ' LANGUAGE plperl; The body of the function is ordinary Perl code. --- 47,54 ---- To create a function in the PL/Perl language, use the standard syntax: ! CREATE FUNCTION funcname ! (argument-types) RETURNS return-type AS $dollar_quote$ # PL/Perl function body ! $dollar_quote$ LANGUAGE plperl; The body of the function is ordinary Perl code. *************** *** 66,73 **** ! CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS ' if ($_[0] > $_[1]) { return $_[0]; } return $_[1]; ! ' LANGUAGE plperl; --- 67,74 ---- ! CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $perl_max$ if ($_[0] > $_[1]) { return $_[0]; } return $_[1]; ! $perl_max$ LANGUAGE plperl; *************** *** 89,93 **** ! CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS ' my ($a,$b) = @_; if (! defined $a) { --- 90,94 ---- ! CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $perl_max$ my ($a,$b) = @_; if (! defined $a) { *************** *** 98,102 **** if ($a > $b) { return $a; } return $b; ! ' LANGUAGE plperl; --- 99,103 ---- if ($a > $b) { return $a; } return $b; ! $perl_max$ LANGUAGE plperl; *************** *** 120,127 **** ); ! CREATE FUNCTION empcomp(employee) RETURNS integer AS ' my ($emp) = @_; ! return $emp->{''basesalary''} + $emp->{''bonus''}; ! ' LANGUAGE plperl; SELECT name, empcomp(employee) FROM employee; --- 121,128 ---- ); ! CREATE FUNCTION empcomp(employee) RETURNS integer AS $empcomp$ my ($emp) = @_; ! return $emp->{'basesalary'} + $emp->{'bonus'}; ! $empcomp$ LANGUAGE plperl; SELECT name, empcomp(employee) FROM employee; *************** *** 137,146 **** Because the function body is passed as an SQL string literal to ! CREATE FUNCTION, you have to escape single ! quotes and backslashes within your Perl source, typically by ! doubling them as shown in the above example. Another possible ! approach is to avoid writing single quotes by using Perl's ! extended quoting operators (q[], ! qq[], qw[]). --- 138,147 ---- Because the function body is passed as an SQL string literal to ! CREATE FUNCTION, you have to use dollar quoting ! or escape single quotes and backslashes within your Perl source, ! typically by doubling them. Another possible approach is to avoid ! writing single quotes by using Perl's extended quoting operators ! (q[], qq[], ! qw[]). *************** *** 227,235 **** system operations are not allowed for security reasons: ! CREATE FUNCTION badfunc() RETURNS integer AS ' open(TEMP, ">/tmp/badfile"); print TEMP "Gotcha!\n"; return 1; ! ' LANGUAGE plperl; The creation of the function will succeed, but executing it will not. --- 228,236 ---- system operations are not allowed for security reasons: ! CREATE FUNCTION badfunc() RETURNS integer AS $badfunc$ open(TEMP, ">/tmp/badfile"); print TEMP "Gotcha!\n"; return 1; ! $badfunc$ LANGUAGE plperl; The creation of the function will succeed, but executing it will not. Index: doc/src/sgml/plpgsql.sgml =================================================================== RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/plpgsql.sgml,v retrieving revision 1.37 diff -2 -c -r1.37 plpgsql.sgml *** doc/src/sgml/plpgsql.sgml 26 Mar 2004 03:18:28 -0000 1.37 --- doc/src/sgml/plpgsql.sgml 27 Apr 2004 21:10:24 -0000 *************** *** 262,272 **** Since the code of a PL/pgSQL function is specified in CREATE FUNCTION as a string literal, single ! quotes inside the function body must be escaped by doubling them. ! This can lead to ! rather complicated code at times, especially if you are writing a ! function that generates other functions, as in the example in . This chart may be useful ! as a summary of the needed numbers of quotation marks in ! various situations. --- 262,276 ---- Since the code of a PL/pgSQL function is specified in CREATE FUNCTION as a string literal, single ! quotes inside the function body must be escaped by doubling them ! unless the string literal comprising the function body is dollar ! quoted. ! ! ! ! Doubling can lead to incomprehensible code at times, especially if ! you are writing a function that generates other functions, as in the ! example in . This ! chart may be useful when translating pre-dollar quoting code into ! something that is comprehensible. *************** *** 419,427 **** block is entered, not only once per function call. For example: ! CREATE FUNCTION somefunc() RETURNS integer AS ' DECLARE quantity integer := 30; BEGIN ! RAISE NOTICE ''Quantity here is %'', quantity; -- Quantity here is 30 quantity := 50; -- --- 423,431 ---- block is entered, not only once per function call. For example: ! CREATE FUNCTION somefunc() RETURNS integer AS $func$ DECLARE quantity integer := 30; BEGIN ! RAISE NOTICE 'Quantity here is %', quantity; -- Quantity here is 30 quantity := 50; -- *************** *** 431,442 **** quantity integer := 80; BEGIN ! RAISE NOTICE ''Quantity here is %'', quantity; -- Quantity here is 80 END; ! RAISE NOTICE ''Quantity here is %'', quantity; -- Quantity here is 50 RETURN quantity; END; ! ' LANGUAGE plpgsql; --- 435,446 ---- quantity integer := 80; BEGIN ! RAISE NOTICE 'Quantity here is %', quantity; -- Quantity here is 80 END; ! RAISE NOTICE 'Quantity here is %', quantity; -- Quantity here is 50 RETURN quantity; END; ! $func$ LANGUAGE plpgsql; *************** *** 449,453 **** are always executed within a transaction established by an outer query --- they cannot start or commit transactions, since ! PostgreSQL does not have nested transactions. --- 453,457 ---- are always executed within a transaction established by an outer query --- they cannot start or commit transactions, since ! PostgreSQL does not yet have nested transactions. *************** *** 511,515 **** quantity integer DEFAULT 32; ! url varchar := ''http://mysite.com''; user_id CONSTANT integer := 10; --- 515,519 ---- quantity integer DEFAULT 32; ! url varchar := 'http://mysite.com'; user_id CONSTANT integer := 10; *************** *** 789,800 **** ! CREATE FUNCTION logfunc1(text) RETURNS timestamp AS ' DECLARE logtxt ALIAS FOR $1; BEGIN ! INSERT INTO logtable VALUES (logtxt, ''now''); ! RETURN ''now''; END; ! ' LANGUAGE plpgsql; --- 793,804 ---- ! CREATE FUNCTION logfunc1(text) RETURNS timestamp AS $f$ DECLARE logtxt ALIAS FOR $1; BEGIN ! INSERT INTO logtable VALUES (logtxt, 'now'); ! RETURN 'now'; END; ! $f$ LANGUAGE plpgsql; *************** *** 802,815 **** ! CREATE FUNCTION logfunc2(text) RETURNS timestamp AS ' DECLARE logtxt ALIAS FOR $1; curtime timestamp; BEGIN ! curtime := ''now''; INSERT INTO logtable VALUES (logtxt, curtime); RETURN curtime; END; ! ' LANGUAGE plpgsql; --- 806,819 ---- ! CREATE FUNCTION logfunc2(text) RETURNS timestamp AS $lf2$ DECLARE logtxt ALIAS FOR $1; curtime timestamp; BEGIN ! curtime := 'now'; INSERT INTO logtable VALUES (logtxt, curtime); RETURN curtime; END; ! $lf2$ LANGUAGE plpgsql; *************** *** 871,875 **** listed here. ! Assignment --- 875,879 ---- listed here. ! Assignment *************** *** 969,977 **** INTO statement to determine whether the assignment was successful (that is, at least one row was was returned by the query). For example: ! SELECT INTO myrec * FROM emp WHERE empname = myname; IF NOT FOUND THEN ! RAISE EXCEPTION ''employee % not found'', myname; END IF; --- 973,981 ---- INTO statement to determine whether the assignment was successful (that is, at least one row was was returned by the query). For example: ! SELECT INTO myrec * FROM emp WHERE empname = myname; IF NOT FOUND THEN ! RAISE EXCEPTION 'employee % not found', myname; END IF; *************** *** 979,982 **** --- 983,991 ---- + You were using dollar quoting to make the + function body, weren't you? + + + To test for whether a record/row result is null, you can use the IS NULL conditional. There is, however, no *************** *** 992,996 **** IF users_rec.homepage IS NULL THEN -- user entered no homepage, return "http://" ! RETURN ''http://''; END IF; END; --- 1001,1005 ---- IF users_rec.homepage IS NULL THEN -- user entered no homepage, return "http://" ! RETURN 'http://'; END IF; END; *************** *** 1033,1044 **** An example: ! PERFORM create_mv(''cs_session_page_requests_mv'', my_query); ! Executing Dynamic Commands ! Oftentimes you will want to generate dynamic commands inside your --- 1042,1053 ---- An example: ! PERFORM create_mv('cs_session_page_requests_mv', my_query); ! Executing Dynamic Commands ! Oftentimes you will want to generate dynamic commands inside your *************** *** 1067,1076 **** ! When working with dynamic commands you will have to face ! escaping of single quotes in PL/pgSQL. Please refer to the ! overview in , ! which can save you some effort. ! Unlike all other commands in PL/pgSQL, a command --- 1076,1087 ---- ! When working with dynamic commands you will have to face escaping ! of single quotes in PL/pgSQL. The recommended method ! is dollar quoting. If you have legacy code which does ! not use dollar quoting, please refer to the ! overview in , which can save you ! some effort when translating said code to a more reasonable scheme. ! Unlike all other commands in PL/pgSQL, a command *************** *** 1081,1085 **** actions on variable tables and columns. ! The results from SELECT commands are discarded --- 1092,1096 ---- actions on variable tables and columns. ! The results from SELECT commands are discarded *************** *** 1094,1104 **** ! An example: ! EXECUTE ''UPDATE tbl SET '' || quote_ident(colname) ! || '' = '' || quote_literal(newvalue) ! || '' WHERE ...''; --- 1105,1117 ---- ! An example (except where noted, all examples herein assume that ! you have dollar quoting and are using it): ! ! EXECUTE 'UPDATE tbl SET ' || quote_ident(colname) ! || ' = ' || quote_literal(newvalue) ! || ' WHERE ...'; *************** *** 1145,1155 **** || referrer_keys.referrer_type || ''''''; END IF;''; END LOOP; ! a_output := a_output || '' RETURN NULL; END; '''' LANGUAGE plpgsql;''; ! EXECUTE a_output; END; ' LANGUAGE plpgsql; --- 1158,1201 ---- || referrer_keys.referrer_type || ''''''; END IF;''; END LOOP; ! a_output := a_output || '' RETURN NULL; END; '''' LANGUAGE plpgsql;''; ! EXECUTE a_output; END; ' LANGUAGE plpgsql; + + And here is an equivalent using dollar quoting. At least it is more + legible than the above, although both versions show that the design, + rather than merely the formatting, needs to be re-thought entire. + + + CREATE or replace FUNCTION cs_update_referrer_type_proc2() RETURNS integer AS $func$ + DECLARE + referrer_keys RECORD; -- declare a generic record to be used in a FOR + a_output varchar(4000); + BEGIN + a_output := 'CREATE FUNCTION cs_find_referrer_type(varchar, varchar, varchar) + RETURNS varchar AS $innerfunc$ + DECLARE + v_host ALIAS FOR $1; + v_domain ALIAS FOR $2; + v_url ALIAS FOR $3; + BEGIN '; + + -- Notice how we scan through the results of a query in a FOR loop + -- using the FOR <record> construct. + + FOR referrer_keys IN SELECT * FROM cs_referrer_keys ORDER BY try_order LOOP + a_output := a_output || ' IF v_' || referrer_keys.kind || ' LIKE $$' + || referrer_keys.key_string || '$$ THEN RETURN $$' + || referrer_keys.referrer_type || '$$; END IF;'; + END LOOP; + + a_output := a_output || ' RETURN NULL; END; $innerfunc$ LANGUAGE plpgsql;'; + EXECUTE a_output; + END; + $func$ LANGUAGE plpgsql; + *************** *** 1253,1257 **** flexible and powerful way. ! Returning From a Function --- 1299,1303 ---- flexible and powerful way. ! Returning From a Function *************** *** 1363,1367 **** ! Conditionals --- 1409,1413 ---- ! Conditionals *************** *** 1435,1443 **** Examples: ! IF parentid IS NULL OR parentid = '''' THEN RETURN fullname; ELSE ! RETURN hp_true_filename(parentid) || ''/'' || fullname; END IF; --- 1481,1489 ---- Examples: ! IF parentid IS NULL OR parentid = '' THEN RETURN fullname; ELSE ! RETURN hp_true_filename(parentid) || '/' || fullname; END IF; *************** *** 1446,1452 **** IF v_count > 0 THEN INSERT INTO users_count (count) VALUES (v_count); ! RETURN ''t''; ELSE ! RETURN ''f''; END IF; --- 1492,1498 ---- IF v_count > 0 THEN INSERT INTO users_count (count) VALUES (v_count); ! RETURN 't'; ELSE ! RETURN 'f'; END IF; *************** *** 1462,1470 **** ! IF demo_row.sex = ''m'' THEN ! pretty_sex := ''man''; ELSE ! IF demo_row.sex = ''f'' THEN ! pretty_sex := ''woman''; END IF; END IF; --- 1508,1516 ---- ! IF demo_row.sex = 'm' THEN ! pretty_sex := 'man'; ELSE ! IF demo_row.sex = 'f' THEN ! pretty_sex := 'woman'; END IF; END IF; *************** *** 1515,1526 **** IF number = 0 THEN ! result := ''zero''; ELSIF number > 0 THEN ! result := ''positive''; ELSIF number < 0 THEN ! result := ''negative''; ELSE -- hmm, the only other possibility is that number is null ! result := ''NULL''; END IF; --- 1561,1572 ---- IF number = 0 THEN ! result := 'zero'; ELSIF number > 0 THEN ! result := 'positive'; ELSIF number < 0 THEN ! result := 'negative'; ELSE -- hmm, the only other possibility is that number is null ! result := 'NULL'; END IF; *************** *** 1667,1671 **** FOR i IN 1..10 LOOP -- some computations here ! RAISE NOTICE ''i is %'', i; END LOOP; --- 1713,1717 ---- FOR i IN 1..10 LOOP -- some computations here ! RAISE NOTICE 'i is %', i; END LOOP; *************** *** 1705,1709 **** mviews RECORD; BEGIN ! PERFORM cs_log(''Refreshing materialized views...''); FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP --- 1751,1755 ---- mviews RECORD; BEGIN ! PERFORM cs_log('Refreshing materialized views...'); FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP *************** *** 1711,1720 **** -- Now "mviews" has one record from cs_materialized_views ! PERFORM cs_log(''Refreshing materialized view '' || quote_ident(mviews.mv_name) || ''...''); ! EXECUTE ''TRUNCATE TABLE '' || quote_ident(mviews.mv_name); ! EXECUTE ''INSERT INTO '' || quote_ident(mviews.mv_name) || '' '' || mviews.mv_query; END LOOP; ! PERFORM cs_log(''Done refreshing materialized views.''); RETURN 1; END; --- 1757,1766 ---- -- Now "mviews" has one record from cs_materialized_views ! PERFORM cs_log('Refreshing materialized view ' || quote_ident(mviews.mv_name) || '...'); ! EXECUTE 'TRUNCATE TABLE ' || quote_ident(mviews.mv_name); ! EXECUTE 'INSERT INTO ' || quote_ident(mviews.mv_name) || ' ' || mviews.mv_query; END LOOP; ! PERFORM cs_log('Done refreshing materialized views.'); RETURN 1; END; *************** *** 1779,1783 **** large row sets from functions. ! Declaring Cursor Variables --- 1825,1829 ---- large row sets from functions. ! Declaring Cursor Variables *************** *** 1878,1882 **** An example: ! OPEN curs1 FOR EXECUTE ''SELECT * FROM '' || quote_ident($1); --- 1924,1928 ---- An example: ! OPEN curs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident($1); *************** *** 1979,1983 **** ! Returning Cursors --- 2025,2029 ---- ! Returning Cursors *************** *** 2041,2045 **** The following example uses automatic cursor name generation: ! CREATE FUNCTION reffunc2() RETURNS refcursor AS ' --- 2087,2091 ---- The following example uses automatic cursor name generation: ! CREATE FUNCTION reffunc2() RETURNS refcursor AS ' *************** *** 2054,2058 **** BEGIN; SELECT reffunc2(); ! reffunc2 -------------------- --- 2100,2104 ---- BEGIN; SELECT reffunc2(); ! reffunc2 -------------------- *************** *** 2104,2108 **** --- 2150,2154 ---- *************** *** 2112,2116 **** % in the string: ! RAISE NOTICE ''Calling cs_create_job(%)'', v_job_id; --- 2158,2162 ---- % in the string: ! RAISE NOTICE 'Calling cs_create_job(%)', v_job_id; *************** *** 2119,2123 **** This example will abort the transaction with the given error message: ! RAISE EXCEPTION ''Inexistent ID --> %'', user_id; --- 2165,2169 ---- This example will abort the transaction with the given error message: ! RAISE EXCEPTION 'Inexistent ID --> %', user_id; *************** *** 2172,2176 **** trigger, several special variables are created automatically in the top-level block. They are: ! --- 2218,2222 ---- trigger, several special variables are created automatically in the top-level block. They are: ! *************** *** 2335,2359 **** ); ! CREATE FUNCTION emp_stamp() RETURNS trigger AS ' BEGIN -- Check that empname and salary are given IF NEW.empname IS NULL THEN ! RAISE EXCEPTION ''empname cannot be null''; END IF; IF NEW.salary IS NULL THEN ! RAISE EXCEPTION ''% cannot have null salary'', NEW.empname; END IF; -- Who works for us when she must pay for it? IF NEW.salary < 0 THEN ! RAISE EXCEPTION ''% cannot have a negative salary'', NEW.empname; END IF; -- Remember who changed the payroll when ! NEW.last_date := ''now''; NEW.last_user := current_user; RETURN NEW; END; ! ' LANGUAGE plpgsql; CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp --- 2381,2405 ---- ); ! CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$ BEGIN -- Check that empname and salary are given IF NEW.empname IS NULL THEN ! RAISE EXCEPTION 'empname cannot be null'; END IF; IF NEW.salary IS NULL THEN ! RAISE EXCEPTION '% cannot have null salary', NEW.empname; END IF; -- Who works for us when she must pay for it? IF NEW.salary < 0 THEN ! RAISE EXCEPTION '% cannot have a negative salary', NEW.empname; END IF; -- Remember who changed the payroll when ! NEW.last_date := 'now'; NEW.last_user := current_user; RETURN NEW; END; ! $emp_stamp$ LANGUAGE plpgsql; CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp *************** *** 2452,2456 **** RETURN v_name; END IF; ! RETURN v_name || '/' || v_version; END; / --- 2498,2502 ---- RETURN v_name; END IF; ! RETURN v_name || ''/'' || v_version; END; / *************** *** 2515,2519 **** CREATE OR REPLACE FUNCTION cs_fmt_browser_version(varchar, varchar) ! RETURNS varchar AS ' DECLARE v_name ALIAS FOR $1; --- 2561,2565 ---- CREATE OR REPLACE FUNCTION cs_fmt_browser_version(varchar, varchar) ! RETURNS varchar AS $cs_fmt_browser_version$ DECLARE v_name ALIAS FOR $1; *************** *** 2523,2529 **** return v_name; END IF; ! RETURN v_name || ''/'' || v_version; END; ! ' LANGUAGE plpgsql; --- 2569,2575 ---- return v_name; END IF; ! RETURN v_name || '/' || v_version; END; ! $cs_fmt_browser_version$ LANGUAGE plpgsql; *************** *** 2535,2539 **** ensuing quoting problems. ! Porting a Function that Creates Another Function from <application>PL/SQL</> to <application>PL/pgSQL</> --- 2581,2585 ---- ensuing quoting problems. ! Porting a Function that Creates Another Function from <application>PL/SQL</> to <application>PL/pgSQL</> *************** *** 2578,2615 **** ! CREATE FUNCTION cs_update_referrer_type_proc() RETURNS integer AS ' DECLARE ! referrer_keys RECORD; -- Declare a generic record to be used in a FOR ! a_output varchar(4000); BEGIN ! a_output := ''CREATE FUNCTION cs_find_referrer_type(varchar, varchar, varchar) ! RETURNS varchar AS '''' DECLARE v_host ALIAS FOR $1; v_domain ALIAS FOR $2; v_url ALIAS FOR $3; ! BEGIN ''; ! -- Notice how we scan through the results of a query in a FOR loop ! -- using the FOR <record> construct. ! FOR referrer_keys IN SELECT * FROM cs_referrer_keys ORDER BY try_order LOOP ! a_output := a_output || '' IF v_'' || referrer_keys.kind || '' LIKE '''''''''' ! || referrer_keys.key_string || '''''''''' THEN RETURN '''''' ! || referrer_keys.referrer_type || ''''''; END IF;''; ! END LOOP; ! ! a_output := a_output || '' RETURN NULL; END; '''' LANGUAGE plpgsql;''; ! ! -- EXECUTE will work because we are not substituting any variables. ! -- Otherwise it would fail. Look at PERFORM for another way to run functions. ! ! EXECUTE a_output; END; ! ' LANGUAGE plpgsql; ! shows how to port a function --- 2624,2659 ---- ! CREATE or replace FUNCTION cs_update_referrer_type_proc2() RETURNS ! text AS $func$ DECLARE ! referrer_keys RECORD; -- declare a generic record to be used in a FOR ! a_output TEXT; BEGIN ! a_output := 'CREATE FUNCTION cs_find_referrer_type(varchar, varchar, varchar) ! RETURNS varchar AS $innerfunc$ DECLARE v_host ALIAS FOR $1; v_domain ALIAS FOR $2; v_url ALIAS FOR $3; ! BEGIN '; ! -- Notice how we scan through the results of a query in a FOR loop ! -- using the FOR <record> construct. ! FOR referrer_keys IN SELECT * FROM cs_referrer_keys ORDER BY try_order LOOP ! a_output := a_output || ' IF v_' || referrer_keys.kind || ' LIKE $$' ! || referrer_keys.key_string || '$$ THEN RETURN $$' ! || referrer_keys.referrer_type || '$$; END IF;'; ! END LOOP; ! ! a_output := a_output || ' RETURN NULL; END; $innerfunc$ LANGUAGE plpgsql;'; ! ! return a_output; END; ! $func$ LANGUAGE plpgsql; ! shows how to port a function *************** *** 2654,2671 **** v_path := NULL; v_query := NULL; ! a_pos1 := instr(v_url, '//'); IF a_pos1 = 0 THEN RETURN; END IF; ! a_pos2 := instr(v_url, '/', a_pos1 + 2); IF a_pos2 = 0 THEN v_host := substr(v_url, a_pos1 + 2); ! v_path := '/'; RETURN; END IF; v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2); ! a_pos1 := instr(v_url, '?', a_pos2 + 1); IF a_pos1 = 0 THEN --- 2698,2715 ---- v_path := NULL; v_query := NULL; ! a_pos1 := instr(v_url, ''//''); IF a_pos1 = 0 THEN RETURN; END IF; ! a_pos2 := instr(v_url, ''/'', a_pos1 + 2); IF a_pos2 = 0 THEN v_host := substr(v_url, a_pos1 + 2); ! v_path := ''/''; RETURN; END IF; v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2); ! a_pos1 := instr(v_url, ''?'', a_pos2 + 1); IF a_pos1 = 0 THEN *************** *** 2687,2691 **** ! CREATE OR REPLACE FUNCTION cs_parse_url_host(varchar) RETURNS varchar AS ' DECLARE v_url ALIAS FOR $1; --- 2731,2735 ---- ! CREATE OR REPLACE FUNCTION cs_parse_url_host(varchar) RETURNS varchar AS $func$ DECLARE v_url ALIAS FOR $1; *************** *** 2697,2710 **** BEGIN v_host := NULL; ! a_pos1 := instr(v_url, ''//''); IF a_pos1 = 0 THEN ! RETURN ''''; -- Return a blank END IF; ! a_pos2 := instr(v_url,''/'',a_pos1 + 2); IF a_pos2 = 0 THEN v_host := substr(v_url, a_pos1 + 2); ! v_path := ''/''; RETURN v_host; END IF; --- 2741,2754 ---- BEGIN v_host := NULL; ! a_pos1 := instr(v_url, '//'); IF a_pos1 = 0 THEN ! RETURN ''; -- Return a blank END IF; ! a_pos2 := instr(v_url,'/',a_pos1 + 2); IF a_pos2 = 0 THEN v_host := substr(v_url, a_pos1 + 2); ! v_path := '/'; RETURN v_host; END IF; *************** *** 2713,2717 **** RETURN v_host; END; ! ' LANGUAGE plpgsql; --- 2757,2761 ---- RETURN v_host; END; ! $func$ LANGUAGE plpgsql; *************** *** 2798,2802 **** ! CREATE OR REPLACE FUNCTION cs_create_job(integer) RETURNS integer AS ' DECLARE v_job_id ALIAS FOR $1; --- 2842,2846 ---- ! CREATE OR REPLACE FUNCTION cs_create_job(integer) RETURNS integer AS $func$ DECLARE v_job_id ALIAS FOR $1; *************** *** 2809,2813 **** IF a_running_job_count > 0 THEN ! RAISE EXCEPTION ''Unable to create a new job: a job is currently running.''; END IF; --- 2853,2857 ---- IF a_running_job_count > 0 THEN ! RAISE EXCEPTION 'Unable to create a new job: a job is currently running.'; END IF; *************** *** 2821,2830 **** RETURN 1; ELSE ! RAISE NOTICE ''Job already running.''; END IF; RETURN 0; END; ! ' LANGUAGE plpgsql; --- 2865,2874 ---- RETURN 1; ELSE ! RAISE NOTICE 'Job already running.'; END IF; RETURN 0; END; ! $func$ LANGUAGE plpgsql; *************** *** 2859,2863 **** quote_string(text) as described in . Constructs of the ! type EXECUTE ''SELECT * FROM $1''; will not work unless you use these functions. --- 2903,2907 ---- quote_string(text) as described in . Constructs of the ! type EXECUTE 'SELECT * FROM $1'; will not work unless you use these functions. *************** *** 2882,2888 **** ! CREATE FUNCTION foo(...) RETURNS integer AS ' ... ! ' LANGUAGE plpgsql STRICT IMMUTABLE; --- 2926,2932 ---- ! CREATE FUNCTION foo(...) RETURNS integer AS $f$ ... ! $f$ LANGUAGE plpgsql STRICT IMMUTABLE; *************** *** 2909,2913 **** -- ! CREATE FUNCTION instr(varchar, varchar) RETURNS integer AS ' DECLARE pos integer; --- 2953,2957 ---- -- ! CREATE FUNCTION instr(varchar, varchar) RETURNS integer AS $f$ DECLARE pos integer; *************** *** 2916,2923 **** RETURN pos; END; ! ' LANGUAGE plpgsql; ! CREATE FUNCTION instr(varchar, varchar, varchar) RETURNS integer AS ' DECLARE string ALIAS FOR $1; --- 2960,2967 ---- RETURN pos; END; ! $f$ LANGUAGE plpgsql; ! CREATE FUNCTION instr(varchar, varchar, varchar) RETURNS integer AS $f$ DECLARE string ALIAS FOR $1; *************** *** 2958,2965 **** END IF; END; ! ' LANGUAGE plpgsql; ! CREATE FUNCTION instr(varchar, varchar, integer, integer) RETURNS integer AS ' DECLARE string ALIAS FOR $1; --- 3002,3009 ---- END IF; END; ! $f$ LANGUAGE plpgsql; ! CREATE FUNCTION instr(varchar, varchar, integer, integer) RETURNS integer AS $f$ DECLARE string ALIAS FOR $1; *************** *** 3019,3026 **** END IF; END; ! ' LANGUAGE plpgsql; ! --- 3063,3070 ---- END IF; END; ! $f$ LANGUAGE plpgsql; ! Index: doc/src/sgml/pltcl.sgml =================================================================== RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/pltcl.sgml,v retrieving revision 2.29 diff -2 -c -r2.29 pltcl.sgml *** doc/src/sgml/pltcl.sgml 24 Jan 2004 23:06:29 -0000 2.29 --- doc/src/sgml/pltcl.sgml 27 Apr 2004 21:10:24 -0000 *************** *** 78,84 **** ! CREATE FUNCTION funcname (argument-types) RETURNS return-type AS ' # PL/Tcl function body ! ' LANGUAGE pltcl; --- 78,85 ---- ! CREATE FUNCTION funcname ! (argument-types) RETURNS return-type AS $tcl$ # PL/Tcl function body ! $tcl$ LANGUAGE pltcl; *************** *** 101,108 **** ! CREATE FUNCTION tcl_max(integer, integer) RETURNS integer AS ' if {$1 > $2} {return $1} return $2 ! ' LANGUAGE pltcl STRICT; --- 102,109 ---- ! CREATE FUNCTION tcl_max(integer, integer) RETURNS integer AS $tcl$ if {$1 > $2} {return $1} return $2 ! $tcl$ LANGUAGE pltcl STRICT; *************** *** 123,127 **** ! CREATE FUNCTION tcl_max(integer, integer) RETURNS integer AS ' if {[argisnull 1]} { if {[argisnull 2]} { return_null } --- 124,128 ---- ! CREATE FUNCTION tcl_max(integer, integer) RETURNS integer AS $tcl$ if {[argisnull 1]} { if {[argisnull 2]} { return_null } *************** *** 131,135 **** if {$1 > $2} {return $1} return $2 ! ' LANGUAGE pltcl; --- 132,136 ---- if {$1 > $2} {return $1} return $2 ! $tcl$ LANGUAGE pltcl; *************** *** 155,159 **** ); ! CREATE FUNCTION overpaid(employee) RETURNS boolean AS ' if {200000.0 < $1(salary)} { return "t" --- 156,160 ---- ); ! CREATE FUNCTION overpaid(employee) RETURNS boolean AS $overpaid$ if {200000.0 < $1(salary)} { return "t" *************** *** 163,167 **** } return "f" ! ' LANGUAGE pltcl; --- 164,168 ---- } return "f" ! $overpaid$ LANGUAGE pltcl; *************** *** 360,382 **** ! CREATE FUNCTION t1_count(integer, integer) RETURNS integer AS ' if {![ info exists GD(plan) ]} { # prepare the saved plan on the first call ! set GD(plan) [ spi_prepare \\ ! "SELECT count(*) AS cnt FROM t1 WHERE num >= \\$1 AND num <= \\$2" \\ [ list int4 int4 ] ] } spi_execp -count 1 $GD(plan) [ list $1 $2 ] return $cnt ! ' LANGUAGE pltcl; ! Note that each backslash that Tcl should see must be doubled when ! we type in the function, since the main parser processes ! backslashes, too, in CREATE FUNCTION. We need backslashes inside ! the query string given to spi_prepare to ensure that ! the $n markers will be passed through to ! spi_prepare as-is, and not ! replaced by Tcl variable substitution. --- 361,382 ---- ! CREATE FUNCTION t1_count(integer, integer) RETURNS integer AS $tcl$ if {![ info exists GD(plan) ]} { # prepare the saved plan on the first call ! set GD(plan) [ spi_prepare \ ! "SELECT count(*) AS cnt FROM t1 WHERE num >= \$1 AND num <= \$2" \ [ list int4 int4 ] ] } spi_execp -count 1 $GD(plan) [ list $1 $2 ] return $cnt ! $tcl$ LANGUAGE pltcl; ! We need backslashes inside the query string given to ! spi_prepare to ensure that the ! $n markers will be passed ! through to spi_prepare as-is, and not replaced by Tcl ! variable substitution. ! *************** *** 426,430 **** ! SELECT 'doesn''t' AS ret --- 426,430 ---- ! SELECT $q$doesn't$q$ AS ret *************** *** 612,616 **** ! CREATE FUNCTION trigfunc_modcount() RETURNS trigger AS ' switch $TG_op { INSERT { --- 612,616 ---- ! CREATE FUNCTION trigfunc_modcount() RETURNS trigger AS $t$ switch $TG_op { INSERT { *************** *** 626,630 **** } return [array get NEW] ! ' LANGUAGE pltcl; CREATE TABLE mytab (num integer, description text, modcnt integer); --- 626,630 ---- } return [array get NEW] ! $t$ LANGUAGE pltcl; CREATE TABLE mytab (num integer, description text, modcnt integer); Index: doc/src/sgml/syntax.sgml =================================================================== RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/syntax.sgml,v retrieving revision 1.90 diff -2 -c -r1.90 syntax.sgml *** doc/src/sgml/syntax.sgml 12 Mar 2004 00:25:40 -0000 1.90 --- doc/src/sgml/syntax.sgml 27 Apr 2004 21:10:24 -0000 *************** *** 241,252 **** escaping ! A string constant in SQL is an arbitrary sequence of characters ! bounded by single quotes ('), e.g., 'This ! is a string'. SQL allows single quotes to be embedded ! in strings by typing two adjacent single quotes, e.g., ! 'Dianne''s horse'. In ! PostgreSQL single quotes may ! alternatively be escaped with a backslash (\), ! e.g., 'Dianne\'s horse'. --- 241,284 ---- escaping ! A string constant in SQL is an arbitrary sequence of characters ! bounded by single quotes ('), e.g., 'This ! is a string', or dollar quotes, e.g. $q$This is a ! string$q$. SQL allows single quotes to be embedded in ! strings by typing two adjacent single quotes, e.g., ! 'Dianne''s horse'. In ! PostgreSQL single quotes may alternatively ! be escaped with a backslash (\), e.g., ! 'Dianne\'s horse'. With dollar quotes, this ! could be written as $$Dianne's horse$$ or ! $long_dollar_quote_string$Dianne's ! horse$long_dollar_quote_string$. Here is an example of ! how dollar quotes can nest: ! ! ! ! CREATE OR REPLACE FUNCTION has_bad_chars(TEXT) RETURNS BOOLEAN AS ! $function$ ! BEGIN ! RETURN ($1 ~ $q$[\t\r\n\v|\\]$q$); ! END; ! $function$ LANGUAGE plpgsql; ! ! ! ! Note that nesting requires a different identifier, as above, and ! only works when the quoted string will be re-parsed. ! ! ! Dollar quoting is not part of the SQL standard, but it is most ! useful in places, like function bodies, where the SQL standard does ! not apply. Please not that everything inside dollar quotes is ! passed literally. For example, inside dollar quotes, backslash is ! just another character with no magic attached. ! ! ! ! Dollar quotes are case sensitive, so ! $quote$This$quote$ is valid, but ! $QUOTE$This$quote$ is not.