Forum OpenACS Development: Attempt #2

Collapse
14: Attempt #2 (response to 1)
Posted by Sebastian Skracic on
Here's another proposal for porting ACS 4.0 PL/SQL packages to PG:

For each function/procedure contained within a PL/SQL package we write pltcl or plpgsql function named as <package_name>__<function_name>. Sometimes function_name can be abbreviated when it's obvious from package_name, e.g. acs_object_type__create instead of acs_object_type__create_type.

However, we must take care of the following:

  • not to pass NULL as one of the arguments, because function will simply return NULL
  • whenever dynamic sql is required, we are forced to use pltcl (is this correct?)
  • whenever we want to _return_ NULL, we must resort to plpgsql (correct?)
So, here are the simple ones:
create function acs_attribute__add_description(text, text, text, text) returns text as '
  declare
    v_object_type       alias for $1;
    v_attribute_name    alias for $2;
    v_description_key   alias for $3;
    v_description       alias for $4;
  begin
    insert into acs_attribute_descriptions
     (object_type, attribute_name, description_key, description)
    values
     (v_object_type, v_attribute_name,
      v_description_key, v_description);
  end;
  return v_description;
' language 'plpgsql';



create function acs_attribute__drop_description(text, text, text) returns text as '
  declare
    v_object_type       alias for $1;
    v_attribute_name    alias for $2;
    v_description_key   alias for $3;
  begin
    delete from acs_attribute_descriptions
      where object_type = v_object_type
      and attribute_name = v_attribute_name
      and description_key = v_description_key;
  end;
  return v_description_key;
' language 'plpgsql';

Now, here's the complicated one. Once again, I'll try to find a workaround to Oracle's default value handling. Needless to say, it's ugly, again. I've created a simple pltcl function:
create function arg_pack(text, text) returns text as '
  return "[list $1 $2] "
' language 'pltcl' ;

So that Oracle's PL/SQL is tranformed in following way:

  Oracle:

   attr_id := acs_attribute.create_attribute(
        object_type => 'acs_object',
     attribute_name => 'object_type',
           datatype => 'string',
        pretty_name => 'Object Type',
      pretty_plural => 'Object Types'
   );

  Postgres:

   select acs_attribute__create(
     arg_pack('object_type', 'acs_object') ||
     arg_pack('attribute_name', 'object_type') ||
     arg_pack('datatype', 'string') ||
     arg_pack('pretty_name', 'Object Type') ||
     arg_pack('pretty_plural', 'Object Types')
   );

arg_pack is used to construct key/value pairs into array-like lists which could be then parsed from within plpgsql function, using arg_unpack:
create function arg_unpack(text, text, text) returns text as '
  declare
    packed_args           alias for $1;
    argument_name         alias for $2;
    default_value         alias for $3;
  begin
    if ... NULL indicator is set ... then
      return NULL;
    else
      if ... the argument is supplied ...  then
        return argument_value;
      else
        return default_value;
      end if;
    end if;
  end;
' language 'plpgsql' ;
arg_unpack accepts 3 arguments: the first one is packed argument list ready to be fed into array set, the second is the argument name (key) and the third is default value which is returned if key does not appear in packed argument list. The function is written in plpgsql so we can return genuine NULL from it.

Yes, but how can we explicitly set one named argument to NULL? Since we cannot pass NULL value to arg_pack (because complete packed argument list would be NULL-ified), we denote NULL argument by setting its "NULL indicator", i.e. another argument named <argument_name>.null to arbitrary value:

   select acs_object_type__create(
     arg_pack('object_type', 'acs_object') ||
     arg_pack('supertype.null', 'yes') ||
     arg_pack('pretty_name' , 'Object') ||
     arg_pack('pretty_plural' , 'Objects') ||
     arg_pack('table_name' , 'acs_objects') ||
     arg_pack('id_column' , 'object_id') ||
     arg_pack('package_name' , 'acs_object') ||
     arg_pack('name_method' , 'acs_object__default_name')
   );
Let's see how acs_object_type__create handles this:
create function acs_object_type__create(text) returns text as '

declare
    packed_args         alias for $1;
    v_object_type       acs_object_types.object_type%TYPE;
    v_pretty_name       acs_object_types.pretty_name%TYPE;
    v_pretty_plural     acs_object_types.pretty_plural%TYPE;
    v_supertype         acs_object_types.supertype%TYPE;
    v_table_name        acs_object_types.table_name%TYPE;
    v_id_column         acs_object_types.id_column%TYPE;
    v_package_name      acs_object_types.package_name%TYPE;
    v_abstract_p        acs_object_types.abstract_p%TYPE;
    v_type_extension_table acs_object_types.type_extension_table%TYPE;
    v_name_method       acs_object_types.name_method%TYPE;
begin
  select
    arg_unpack(packed_args, ''object_type''),
    arg_unpack(packed_args, ''pretty_name''),
    arg_unpack(packed_args, ''pretty_plural''),
    arg_unpack(packed_args, ''supertype'', ''acs_object''),
    arg_unpack(packed_args, ''table_name''),
    arg_unpack(packed_args, ''id_column''),
    arg_unpack(packed_args, ''package_name''),
    arg_unpack(packed_args, ''abstract_p'', ''f''),
    arg_unpack(packed_args, ''type_extension_table''),
    arg_unpack(packed_args, ''name_method'')
  into
    v_object_type,
    v_pretty_name,
    v_pretty_plural,
    v_supertype,
    v_table_name,
    v_id_column,
    v_package_name,
    v_abstract_p,
    v_type_extension_table,
    v_name_method;

    if v_package_name is null then
      v_package_name := v_object_type;
    end if;

    insert into acs_object_types
      (object_type, pretty_name, pretty_plural, supertype, table_name,
       id_column, abstract_p, type_extension_table, package_name,
       name_method)
    values
      (v_object_type, v_pretty_name, v_pretty_plural, v_supertype, v_table_name,
       v_id_column, v_abstract_p, v_type_extension_table, v_package_name,
       v_name_method);

    return v_object_type;
end;
' language 'plpgsql';