-- plxruby regression tests CREATE EXTENSION IF NOT EXISTS plx; SET client_min_messages = warning; -- scalar return, arithmetic CREATE FUNCTION rb_add(a int, b int) RETURNS int LANGUAGE plxruby AS $$ return a + b * 2 $$; SELECT rb_add(3, 4); -- if / elsif / else with a type annotation CREATE FUNCTION rb_grade(score int) RETURNS text LANGUAGE plxruby AS $$ grade #:: text if score >= 90 grade = "A" elsif score >= 80 grade = "B" else grade = "F" end return grade $$; SELECT rb_grade(95), rb_grade(85), rb_grade(70); -- unless modifier CREATE FUNCTION rb_zcheck(n int) RETURNS text LANGUAGE plxruby AS $$ return "empty" unless n != 0 return "nonzero" $$; SELECT rb_zcheck(0), rb_zcheck(5); -- while / until CREATE FUNCTION rb_countdown(n int) RETURNS int LANGUAGE plxruby AS $$ c = 0 #:: integer while n > 0 c = c + n n = n - 1 end return c $$; SELECT rb_countdown(5); -- for loop with next / break CREATE FUNCTION rb_sum_skip(n int) RETURNS int LANGUAGE plxruby AS $$ total = 0 for i in 1..n next if i == 3 break if i > 7 total = total + i end return total $$; SELECT rb_sum_skip(10); -- exclusive range via .each CREATE FUNCTION rb_exsum(n int) RETURNS int LANGUAGE plxruby AS $$ s = 0 (0...n).each do |j| s = s + j end return s $$; SELECT rb_exsum(5); -- ternary CREATE FUNCTION rb_sign(x int) RETURNS text LANGUAGE plxruby AS $$ return x >= 0 ? "nonneg" : "neg" $$; SELECT rb_sign(-4), rb_sign(4); -- inference of multiple types + interpolation CREATE FUNCTION rb_fmt() RETURNS text LANGUAGE plxruby AS $$ label = "count" n = 42 active = true return "#{label}=#{n} active=#{active}" $$; SELECT rb_fmt(); -- numeric annotation + interpolation CREATE FUNCTION rb_interest(principal numeric) RETURNS text LANGUAGE plxruby AS $$ rate = 0.05 #:: numeric amount #:: numeric amount = principal * rate return "interest: #{amount}" $$; SELECT rb_interest(1000); -- SETOF via emit CREATE FUNCTION rb_rows(n int) RETURNS TABLE(idx int, label text) LANGUAGE plxruby AS $$ for i in 1..n idx = i label = "row#{i}" emit end $$; SELECT * FROM rb_rows(3); -- query iteration and fetch_one against a table CREATE TABLE rb_orders(grp int, amount bigint); INSERT INTO rb_orders VALUES (1,10),(1,20),(1,30),(2,5); CREATE FUNCTION rb_grp_total(g int) RETURNS bigint LANGUAGE plxruby AS $$ total = 0 #:: bigint query("SELECT amount FROM rb_orders WHERE grp = #{g}").each do |row| total = total + row.amount end return total $$; SELECT rb_grp_total(1), rb_grp_total(2); CREATE TABLE rb_users(id int, name text); INSERT INTO rb_users VALUES (1,'Alice'),(2,'Bob'); CREATE FUNCTION rb_name(uid int) RETURNS text LANGUAGE plxruby AS $$ u = fetch_one("SELECT id, name FROM rb_users WHERE id = #{uid}") return u.name $$; SELECT rb_name(2); -- return_query CREATE TABLE rb_vip(id int); INSERT INTO rb_vip VALUES (3),(1),(2); CREATE FUNCTION rb_vips() RETURNS SETOF int LANGUAGE plxruby AS $$ return_query("SELECT id FROM rb_vip ORDER BY id") $$; SELECT * FROM rb_vips(); -- begin / rescue CREATE FUNCTION rb_safediv(d int) RETURNS int LANGUAGE plxruby AS $$ begin return 100 / d rescue => e raise notice: "caught: #{e.message}" return -1 end $$; SELECT rb_safediv(4), rb_safediv(0); -- raise with errcode CREATE FUNCTION rb_checkpos(v int) RETURNS int LANGUAGE plxruby AS $$ if v < 0 raise exception: "negative: #{v}", errcode: "22023" end return v $$; SELECT rb_checkpos(7); -- DO block (body is Ruby) DO LANGUAGE plxruby $$ raise warning: "plxruby do-block ran: #{6 * 7}" $$; -- case/when (simple and searched forms) CREATE FUNCTION rb_case_simple(n int) RETURNS text LANGUAGE plxruby AS $$ case n when 1 return "one" when 2, 3 return "few" else return "many" end $$; SELECT rb_case_simple(1), rb_case_simple(3), rb_case_simple(9); CREATE FUNCTION rb_case_searched(n int) RETURNS text LANGUAGE plxruby AS $$ case when n < 0 return "neg" when n == 0 return "zero" else return "pos" end $$; SELECT rb_case_searched(-5), rb_case_searched(0), rb_case_searched(5); -- assert CREATE FUNCTION rb_assert(n int) RETURNS int LANGUAGE plxruby AS $$ assert(n > 0, "must be positive") return n $$; SELECT rb_assert(5); -- row_count via GET DIAGNOSTICS, and found? CREATE TABLE rb_t(a int); CREATE FUNCTION rb_ins(n int) RETURNS bigint LANGUAGE plxruby AS $$ rc = 0 #:: bigint execute("INSERT INTO rb_t SELECT g FROM generate_series(1, #{n}) g") rc = row_count() return rc $$; SELECT rb_ins(4); CREATE FUNCTION rb_found() RETURNS boolean LANGUAGE plxruby AS $$ perform("SELECT 1 WHERE false") return found? $$; SELECT rb_found(); -- FOREACH over an array, and %ROWTYPE, and CONSTANT CREATE FUNCTION rb_sumarr(a int[]) RETURNS int LANGUAGE plxruby AS $$ total = 0 #:: int v #:: int a.each do |v| total = total + v end return total $$; SELECT rb_sumarr(ARRAY[10,20,30]); CREATE TABLE rb_emp(id int, name text); INSERT INTO rb_emp VALUES (1,'Alice'),(2,'Bob'); CREATE FUNCTION rb_rowtype(eid int) RETURNS text LANGUAGE plxruby AS $$ e #:: rb_emp%ROWTYPE e = fetch_one("SELECT * FROM rb_emp WHERE id = #{eid}") return e.name $$; SELECT rb_rowtype(2); CREATE FUNCTION rb_const(r numeric) RETURNS numeric LANGUAGE plxruby AS $$ pi = 3.14159 #:: numeric const return pi * r * r $$; SELECT rb_const(2); -- cursors: open_cursor / fetch_from / found? / close_cursor CREATE TABLE rb_nums(v int); INSERT INTO rb_nums SELECT generate_series(1,5); CREATE FUNCTION rb_cursor_sum() RETURNS int LANGUAGE plxruby AS $$ total = 0 #:: int c = open_cursor("SELECT v FROM rb_nums ORDER BY v") row = fetch_from(c) while found? total = total + row.v row = fetch_from(c) end close_cursor(c) return total $$; SELECT rb_cursor_sum(); -- labeled loop (break outer) and stacked diagnostics CREATE FUNCTION rb_labeled(n int) RETURNS int LANGUAGE plxruby AS $$ count = 0 #:: int outer: for i in 1..n for j in 1..n count = count + 1 break outer if i + j >= 4 end end return count $$; SELECT rb_labeled(5); CREATE TABLE rb_uq(id int PRIMARY KEY); INSERT INTO rb_uq VALUES (1); CREATE FUNCTION rb_diag(k int) RETURNS text LANGUAGE plxruby AS $$ begin execute("INSERT INTO rb_uq(id) VALUES ($1)", k) return "inserted" rescue PG::UniqueViolation => e return "constraint=#{e.constraint} detail=#{e.detail}" end $$; SELECT rb_diag(1); -- single-quoted strings are raw: backslash escapes stay literal ('\t' -> \ t) CREATE FUNCTION rb_raw_squote() RETURNS text LANGUAGE plxruby AS $$ return 'x\ty' $$; -- double-quoted strings still process escapes ("\n" -> newline) CREATE FUNCTION rb_dquote_esc() RETURNS text LANGUAGE plxruby AS $$ return "a\nb" $$; SELECT rb_raw_squote() AS raw_val, length(rb_raw_squote()) AS raw_len, rb_raw_squote() ~ E'\\\\' AS raw_backslash_kept, length(rb_dquote_esc()) AS dquote_len; -- non-decimal integer literals convert to decimal CREATE FUNCTION rb_hex() RETURNS int LANGUAGE plxruby AS $$ return 0xff $$; SELECT rb_hex();