Forum OpenACS Development: Attempt #2
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?)
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';