Jul 022016
 

In my two previous posts I talked about creating a new port and copying a port from head to a branch.

The goal of this post is the creation of a new function: CreatePortOnBranch($category_name, $port_name, $CommitBranch)

The failed start

I started out with this stored procedure:

CREATE OR REPLACE FUNCTION CreatePort(text, text, text) returns int4 AS $$
DECLARE
  a_category_name ALIAS for $1;
  a_port_name     ALIAS for $2;
  a_branch_name   ALIAS for $3;

  l_element_id_of_new_port int4;
  l_port_id                int4;

  BEGIN

WITH RECURSIVE all_descendents AS (
  SELECT id, name, parent_id, directory_file_flag, status
    FROM element
    WHERE id = (select pathname_id('/ports/head/sysutils/bacula-client-static/'))
  UNION
  SELECT E.id, E.name, E.parent_id, E.directory_file_flag, E.status
    FROM element E
    JOIN all_descendents AD
      ON (E.parent_id = AD.id)
)
   SELECT element_add(replace(element_pathname(id), '/ports/head/', '/ports/branches/2016Q2/'), directory_file_flag), 
                      replace(element_pathname(id), '/ports/head/', '/ports/branches/2016Q2/')
      FROM all_descendents
     WHERE status = 'A';

  SELECT createport(l_element_id_of_new_port, getcategory(a_category_name))
    INTO l_port_id;
 
  RETURN l_port_id;

  END;
$$  LANGUAGE 'plpgsql';

Running it gave this message:

# select CreatePort('sysutils', 'bacula-server', '2016Q2');
ERROR:  query has no destination for result data
HINT:  If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT:  PL/pgSQL function createport(text,text,text) line 12 at SQL statement

I tried PERFORM, but failed to find a suitable syntax. I found out why. I asked on IRC:

I am trying to use PERFORM in order to discard the result set in a plpgsql function.. I think I want PERFORM, but I’m failing. https://gist.github.com/dlangille/3f464ac2537028f0602551f1bf4db2ba

The replies I got (NOTE: the entire channel log is not shown here):

[Jul 02 12:33]  <RhodiumToad>	perform and with have ... problems
[Jul 02 12:33]  <RhodiumToad>	PERFORM works by replacing the word PERFORM with SELECT and then parsing the query
[Jul 02 12:34]  <RhodiumToad>	so PERFORM WITH ...  never works
[Jul 02 12:36]  <dvl>	RhodiumToad: Sounds like I should create a function with just that WITH RECURSION in it, and call that instead.
[Jul 02 12:36]  <RhodiumToad>	that would probably be simpler
[Jul 02 12:36]  <Myon>	enricostn: that's not a locales problem, that's a version mismatch problem, 9.3 vs 9.5
[Jul 02 12:36]  <johto>	you could also do  PERFORM 1 FROM (<query>) ss;  because there are no wCTEs
[Jul 02 12:37]  <RhodiumToad>	yeah, but that's really ugly
[Jul 02 12:37]  <enricostn>	Myon: oh... would you please so kind to post there and shade some light? Would be really appreciated.
[Jul 02 12:38]  <dvl>	I also think dividing this up into distinct functions will have code benefits later.

The two new functions and a new datatype

Based on the above discussion, I created these functions:

--
-- this function copies a port from /ports/head/CATEGORY/PORT to /ports/branches/BRANCH
-- It returns the id of the new port.
--
CREATE OR REPLACE FUNCTION CopyPortFromHeadToBranch(text, text, text) returns int4 AS $$
DECLARE
  a_category_name ALIAS for $1;
  a_port_name     ALIAS for $2;
  a_branch_name   ALIAS for $3;

  l_r             element_id_pathname%rowtype;
  l_port_id       int4;

  BEGIN

  FOR l_r IN
  WITH RECURSIVE all_descendents AS ( 
  SELECT id, name, parent_id, directory_file_flag, status
    FROM element
    WHERE id = (select pathname_id('/ports/head/' || a_category_name || '/' || a_port_name || '/'))
  UNION
  SELECT E.id, E.name, E.parent_id, E.directory_file_flag, E.status
    FROM element E
    JOIN all_descendents AD
      ON (E.parent_id = AD.id)
)
    SELECT element_add(replace(element_pathname(id), '/ports/head/', '/ports/branches/' || a_branch_name || '/'), directory_file_flag),
                       replace(element_pathname(id), '/ports/head/', '/ports/branches/2016Q2/')
      FROM all_descendents
     WHERE status = 'A'
  LOOP
  END LOOP;

  IF FOUND THEN
    SELECT pathname_id('/ports/branches/' || a_branch_name || '/' || a_category_name || '/' || a_port_name || '/')
      INTO l_port_id;
  END IF;

  return l_port_id;
  END;
$$  LANGUAGE 'plpgsql';

With this helper function created, the original function becomes:

--
-- this function create a port on a branch using head as a starting point.
-- i.e. from /ports/head/CATEGORY/PORT to /ports/branches/BRANCH/CATEGORY/PORT
-- is created.
-- It returns the id of the new port.
--
CREATE OR REPLACE FUNCTION CreatePort(text, text, text) returns int4 AS $$
DECLARE
  a_category_name ALIAS for $1;
  a_port_name     ALIAS for $2;
  a_branch_name   ALIAS for $3;

  l_element_id_of_new_port int4;
  l_port_id                int4;

  BEGIN

  SELECT CopyPortFromHeadToBranch(a_category_name, a_port_name, a_branch_name)
    INTO l_element_id_of_new_port;

  IF FOUND THEN
    SELECT createport(l_element_id_of_new_port, getcategory(a_category_name))
      INTO l_port_id;
  END IF;
 
  RETURN l_port_id;

  END;
$$  LANGUAGE 'plpgsql';

Here is the first test of that function:

select CreatePort('sysutils', 'bacula-docs', '2016Q2');

 createport 
------------
      41762
(1 row)

Let’s see the full pathname of that port:

# select element_pathname(element_id) from ports where id = 41762;
              element_pathname               
---------------------------------------------
 /ports/branches/2016Q2/sysutils/bacula-docs
(1 row)

Success.

Website Pin Facebook Twitter Myspace Friendfeed Technorati del.icio.us Digg Google StumbleUpon Premium Responsive

  One Response to “A single function for creating a new port”

  1. What’s missing is ports_categories, so I added this:

      -- we need to set up the port_categories table first...
    
      RAISE NOTICE 'about to call GetPort and I only want stuff from head, so branch agnostic is OK.';
    
      INSERT INTO ports_categories(port_id, category_id)
      SELECT l_port_id, PC.category_id
        FROM ports_categories PC
       WHERE PC.port_id = GetPort(a_category_name, a_port_name);