package-procs.tcl
Does not contain a contract.
- Location:
- /packages/acs-subsite/tcl/package-procs.tcl
Related Files
- packages/acs-subsite/tcl/package-procs.xql
- packages/acs-subsite/tcl/package-procs.tcl
- packages/acs-subsite/tcl/package-procs-postgresql.xql
- packages/acs-subsite/tcl/package-procs-oracle.xql
[ hide source ] | [ make this the default ]
File Contents
ad_library { Procs to help build PL/SQL packages @author mbryzek@arsdigita.com @creation-date Wed Dec 27 16:02:44 2000 @cvs-id $Id: package-procs.tcl,v 1.40.2.11 2024/07/25 13:08:52 mischa Exp $ } d_proc -public package_type_dynamic_p { object_type } { Returns 1 if the object type is dynamic. 0 otherwise @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 12/30/2000 } { return [db_0or1row object_type_dynamic_p { select 1 from acs_object_types where dynamic_p = 't' and object_type = :object_type }] } d_proc -private package_create_attribute_list { { -supertype "" } { -object_name "" } { -limit_to "" } { -table "" } { -column "" } { -column_value "" } object_type } { Generates the list of attributes for this object type. Each element in the list is (table_name, column_name, default_value, column_value) where <code>default_value</code> and <code>column_value</code> are optional. Note that if either of table_name, id_column is unspecified, we retrieve the values for both from the acs_object_types table @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 12/2000 @param supertype The supertype of the object we are creating. If specified, along with object_name, we lookup the parameters to supertype.object_name and include any missing parameters in our argument list. @param object_name The name of the function / procedure we are creating. See supertype for explanation. @param limit_to If empty, this argument is ignored. Otherwise, it is a list of all the columns to be included in the attribute list. Any attribute whose column_name is not in this list is then ignored. @param table The <code>table_name</code> for this object_type (from the <code>acs_object_types</code> tables) @param column The <code>id_column</code> for this object_type (from the <code>acs_object_types</code> tables) @param column_value The value for this column in the present calling function. Useful when you are calling supertype function and need to refer to the supertype argument by a different name locally. @param object_type The object type for which we are generating attributes } { if { $table eq "" || $column eq "" } { # pull out the table and column names based on the object type acs_object_type::get -object_type $object_type -array acs_type set table $acs_type(table_name) set column $acs_type(id_column) } # set toupper for case-insensitive searching set limit_to [string toupper $limit_to] # For the actual package spec and body, we build up a list of # the arguments and use a helper proc to generate the actual # pl/sql code. Note that the helper procs also return nicely # formatted pl/sql code set attr_list [list] # Start with the primary key for this object type. Continuing with # convention that id_column can be null (will default to new # object_id) lappend attr_list [list $table "$column" NULL $column_value] # the all_attributes array is used to ensure we do not have # duplicate column names set all_attributes([string toupper $column]) 1 if { $column_value ne "" } { # column value is the same physical column as $column - just # named differently in the attribute list. We still don't want # duplicates set all_attributes([string toupper $column_value]) 1 } # Now, loop through and gather all the attributes for this object # type and all its supertypes in order starting with this object # type up the type hierarchy db_foreach select_all_attributes {} { # First make sure the attribute is okay if { $limit_to ne "" } { # We have a limited list of arguments to use. Make sure # this attribute is one of them if {$attr_column_name ni $limit_to} { # This column is not in the list of allowed # columns... ignore continue } } set default [package_attribute_default \ -min_n_values $min_n_values \ -attr_default $default_value \ $object_type $attr_table_name $attr_column_name] lappend attr_list [list $attr_table_name $attr_column_name $default] set all_attributes($attr_column_name) 1 } if { $supertype ne "" && $object_name ne "" } { foreach row [util_memoize [list package_table_columns_for_type $supertype]] { lassign $row table_name column_name # Note that limit_to doesn't apply here as we always need # to include these arguments else the call will fail if { [info exists all_attributes($column_name)] } { continue } set all_attributes($column_name) 1 set default [package_attribute_default $object_type $table_name $column_name] lappend attr_list [list $table_name $column_name $default] } } return $attr_list } d_proc -private package_attribute_default { { -min_n_values "0" } { -attr_default "" } object_type table column } { Returns a sql value to be used as the default in a pl/sql function or procedure parameter list. This is a special case, hardcoded function that specifies defaults for standard acs_object attributes. @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 12/28/2000 @param object_type The object type that owns the attribute we are using. Used only to set a default for <code>acs_object.object_type</code> stored (either table_name from the attribute or for the object_type) @param table The table in which the value of this attribute is stored (either table_name from the attribute or for the object_type) @param column The column in which the value of this attribute is stored (either column_name or attribute_name from the attributes table) @param min_n_values Used to determine if an argument is required (e.g. required = min_n_values != 0) @param attr_default The default values for this attribute as specified in the attributes table. } { # We handle defaults grossly here, but I don't currently have # a better idea how to do this if { $attr_default ne "" } { return [::ns_dbquotevalue $attr_default] } # Special cases for acs_object and acs_rels # attributes. Default case sets default to null unless the # attribute is required (min_n_values > 0) if {$table eq "ACS_OBJECTS"} { switch -- $column { "OBJECT_TYPE" { return [::ns_dbquotevalue $object_type] } "CREATION_DATE" { return [db_map creation_date] } "CREATION_IP" { return "NULL" } "CREATION_USER" { return "NULL" } "LAST_MODIFIED" { return [db_map last_modified] } "MODIFYING_IP" { return "NULL" } } } elseif {$table eq "ACS_RELS"} { switch -- $column { "REL_TYPE" { return [::ns_dbquotevalue $object_type] } } } # return to null unless this attribute is required # (min_n_values > 0) return [expr {$min_n_values > 0 ? "" : "NULL"}] } d_proc -public package_recreate_hierarchy { object_type } { Recreates all the packages for the hierarchy starting with the specified object type down to a leaf. Resets the package_object_view cache. Note: Only updates packages for dynamic objects (those with dynamic_p set to t) @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 12/28/2000 @param object_type The object type for which to recreate packages, including all children types. } { set object_type_list [db_list select_object_types {}] # Something changed... flush the data dictionary cache for the # type hierarchy starting with this object's type. Note that we # flush the cache in advance to reuse it when generating future packages # for object_types in the same level of the hierarchy. Note also that # maintaining this cache only gives us a few hits in the cache in # the degenerate case (one subtype), but the query we're caching # is dreadfully slow because of data dictionary tables. So # ensuring we only run the query once significantly improves # performance. -mbryzek foreach object_type $object_type_list { if { [util_memoize_cached_p [list package_table_columns_for_type $object_type]] } { util_memoize_flush [list package_table_columns_for_type $object_type] } } foreach type $object_type_list { package_create $type } } d_proc -private package_create { { -debug_p "f" } object_type } { Creates a packages with a new function and delete procedure for the specified object type. This function uses metadata exclusively to create the package. Resets the package_object_view cache Throws an error if the specified object type does not exist or is not dynamic @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 12/27/2000 @param object_type The object type for which to create a package @param debug_p If "t" then we return a text block containing the sql to create the package. Setting debug_p to t will not create the package. } { if {[catch { acs_object_type::get -object_type $object_type -array acs_type } errmsg]} { error "The specified object, $object_type does not exist." } if { ![string is true -strict $acs_type(dynamic_p)] } { error "The specified object, $object_type is not dynamic. Therefore, a package cannot be created for it" } # build up a list of the pl/sql to execute as it will make it # easier to return a string for debugging purposes. set package_name $acs_type(package_name) lappend plsql \ [list "package" "create_package" [package_generate_spec $object_type]] \ [list "package body" "create_package_body" [package_generate_body $object_type]] if { $debug_p == "t" } { foreach pair $plsql { # append text "[plsql_utility::parse_sql [lindex $pair 1]]\n\n" append text [lindex $pair 2] } return $text } foreach pair $plsql { lassign $pair type stmt_name code db_exec_plsql $stmt_name $code # Let's check to make sure the package is valid # # This seems to be a speciality in Oracle: The status of a # program unit (PL/SQL package, procedure, or function) is set # to INVALID if a database object on which it depends is # changed. That program unit must then be recompiled (which # Oracle Database will often do automatically the next time # you try to use that program unit). # if { ![db_string package_valid_p {}] } { error "$object_type \"$package_name\" is not valid after compiling:\n\n$code\n\n" } } # Now reset the object type view in case we've cached some attribute queries package_object_view_reset $object_type # Return the object type - what else to return? return $object_type } d_proc -private package_generate_spec { object_type } { Generates pl/sql to create a package specification. Does not execute the pl/sql - simply returns it. @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 10/2000 @param object_type The object for which to create a package spec } { # First pull out some basic information about this object type acs_object_type::get -object_type $object_type -array acs_type set table_name $acs_type(table_name) set id_column $acs_type(id_column) set package_name [string tolower $acs_type(package_name)] set supertype $acs_type(supertype) return [db_map spec] } d_proc -private package_generate_body { object_type } { Generates plsql to create the package body @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 10/2000 @param object_type The name of the object type for which we are creating the package } { # Pull out information about this object type acs_object_type::get -object_type $object_type -array acs_type set table_name $acs_type(table_name) set id_column $acs_type(id_column) set package_name [string tolower $acs_type(package_name)] set supertype $acs_type(supertype) # Pull out information about the supertype acs_object_type::get -object_type $supertype -array acs_type set supertype_table_name $acs_type(table_name) set supertype_id_column $acs_type(id_column) set supertype_package_name [string tolower $acs_type(package_name)] set attribute_list [package_create_attribute_list \ -supertype $supertype \ -object_name "NEW" \ -table $table_name \ -column $id_column \ $object_type] # Prune down the list of attributes in supertype_attr_list to # those specific to the function call in the supertype's package set supertype_params [db_list select_supertype_function_params {}] set supertype_attr_list [package_create_attribute_list \ -supertype $supertype \ -object_name "NEW" \ -limit_to $supertype_params \ -table $supertype_table_name \ -column $supertype_id_column \ -column_value $id_column \ $supertype] return [db_map body] } d_proc -public package_object_view_reset { object_type } { Resets the cached views for all chains (e.g. all variations of start_with in package_object_view) for the specified object type. @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 12/2000 } { # First flush the cache for all pairs of object_type, ancestor_type (start_with) db_foreach select_ancestor_types {} { if { [util_memoize_cached_p [list package_object_view_helper -start_with $ancestor_type $object_type]] } { util_memoize_flush [list package_object_view_helper -start_with $ancestor_type $object_type] } } # flush the cache for all pairs of sub_type, object_type(start_with) db_foreach select_sub_types {} { if { [util_memoize_cached_p [list package_object_view_helper -start_with $object_type $sub_type]] } { util_memoize_flush [list package_object_view_helper -start_with $object_type $sub_type] } } } d_proc -public package_object_view { { -refresh_p "f" } { -start_with "acs_object" } object_type } { Returns a select statement to be used as an inner view for selecting out all the attributes for the object_type. util_memoizes the result @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 10/2000 @param refresh_p If t, force a reload of the cache @param start_with The highest parent object type for which to include attributes @param object_type The object for which to create a package spec } { if {$refresh_p == "t"} { package_object_view_reset $object_type } return [util_memoize [list package_object_view_helper -start_with $start_with $object_type]] } d_proc -private package_object_view_helper { { -start_with "acs_object" } object_type } { Returns a select statement to be used as an inner view for selecting out all the attributes for the object_type. @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 10/2000 @param start_with The highest parent object type for which to include attributes @param object_type The object for which to create a package spec } { # Let's add the primary key for our lowest object type. We do this # separately in case there are no other attributes for this object type # Note that we also alias this primary key to object_id so # that the calling code can generically use it. acs_object_type::get -object_type $object_type -array acs_type set table_name $acs_type(table_name) set id_column $acs_type(id_column) set columns [list "${table_name}.${id_column}"] if { [string tolower $id_column] ne "object_id" } { # Add in an alias for object_id lappend columns "${table_name}.${id_column} as object_id" } set tables [list "${table_name}"] set primary_keys [list "${table_name}.${id_column}"] foreach row [package_object_attribute_list -start_with $start_with $object_type] { set table [lindex $row 1] set column [lindex $row 2] set object_column [lindex $row 8] if {[string tolower $column] eq "object_id"} { # We already have object_id... skip this column continue } # Do the column check first to include only the tables we need if {"$table.$column" in $columns} { # We already have a column with the same name. Keep the # first one as it's lower in the type hierarchy. continue } # first time we're seeing this column lappend columns "${table}.${column}" if {$table ni $tables} { # First time we're seeing this table lappend tables $table lappend primary_keys "${table}.${object_column}" } } set pk_formatted [list] for { set i 0 } { $i < [llength $primary_keys] - 1 } { incr i } { lappend pk_formatted "[lindex $primary_keys $i] = [lindex $primary_keys $i+1]" } set where_clause "" if {[llength $pk_formatted] > 0} { set where_clause [join [string tolower $pk_formatted] "\n AND "] set where_clause " WHERE $where_clause" } return "SELECT [string tolower [join $columns ",\n "]] FROM [string tolower [join $tables ", "]] $where_clause" } ad_proc -private package_insert_default_comment { } { Returns a string to be used verbatim as the default comment we insert into meta-generated packages and package bodies. If we have a connection, we grab the user's name from ad_conn user_id. @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 12/29/2000 } { set author [expr {[ns_conn isconnected] ? [acs_user::get_element -element name] : "Unknown"}] set creation_date [db_string current_timestamp { select current_timestamp from dual}] return " --/** THIS IS AN AUTO GENERATED PACKAGE. $author was the -- user who created it -- -- @creation-date $creation_date --*/ " } d_proc package_object_attribute_list { { -start_with "acs_object" } { -include_storage_types {type_specific} } object_type } { Returns a list of lists all the attributes (column name or attribute_name) to be used for this object type. Each list elements contains: <code>(attribute_id, table_name, attribute_name, pretty_name, datatype, required_p, default_value)</code> @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 12/29/2000 @param start_with The highest parent object type for which to include attributes @param object_type The object type for which to include attributes } { set storage_clause "" if {$include_storage_types ne ""} { set storage_clause " and a.storage in ([ns_dbquotelist $include_storage_types])" } return [db_list_of_lists attributes_select {}] } d_proc -private package_plsql_args { { -object_name "NEW" } package_name } { Return a list of parameters expected to a plsql function defined within a given package and cache these per thread. Changes in the interface will require a server restart. @author Ben Adida (ben@openforce.net) @creation-date 11/2001 @param package_name The package which owns the function @param object_name The function name which we're looking up @return list of parameters } { return [acs::per_thread_cache eval -key acs-subsite.package_plsql_args($object_name-$package_name) { db_list select_package_func_param_list {} }] } d_proc -private package_function_p { -object_name:required package_name } { @return true if the package's object is a function. } { return [acs::per_thread_cache eval -key acs-subsite.package_function_p($object_name-$package_name) { db_0or1row function_p "" }] } d_proc -private package_table_columns_for_type { object_type } { Generates the list of tables and columns that are parameters of the object named <code>NEW</code> for PL/SQL package associated with this object type. <p> Note we limit the argument list to only object_type to make it possible to use <code>util_memoize_flush<code> to clear any cached values for this procedure. @author Michael Bryzek (mbryzek@arsdigita.com) @creation-date 12/2000 @param object_type The object type for which we are generating the list @return a list of lists where each list element is a pair of table name, column name } { set object_name "NEW" acs_object_type::get -object_type $object_type -array acs_type set package_name $acs_type(package_name) # We need to hit the data dictionary to find the table and column names # for all the arguments to the object_types function/procedure # named "object_name." Note that we join against # acs_object_types to select out the tables and columns for the # object_type up the type tree starting from this object_type. # # NOTE: This query is tuned already, yet still slow (~1 # second on my box right now). Be careful modifying # it... It's slow because of the underlying data dictionary query # against user_arguments return [db_list_of_lists select_object_type_param_list {}] } d_proc -public package_instantiate_object { { -creation_user "" } { -creation_ip "" } { -package_name "" } { -var_list "" } { -extra_vars "" } { -start_with "" } { -form_id "" } { -variable_prefix "" } object_type } { Creates a new object of the specified type by calling the associated PL/SQL package new function. @author Michael Bryzek (mbryzek@arsdigita.com) @author Ben Adida (ben@openforce.net) @creation-date 02/01/2001 @param creation_user The current user. Defaults to <code>[ad_conn user_id]</code> if not specified and there is a connection @param creation_ip The current user's IP address. Defaults to <code>[ad_conn peeraddr]</code> if not specified and there is a connection @param package_name The PL/SQL package associated with this object type. Defaults to <code>acs_object_types.package_name</code> @param var_list A list of pairs of additional attributes and their values to pass to the constructor. Each pair is a list of two elements: key => value @param extra_vars an ns_set of extra vars @param start_with The object type to start with when gathering attributes for this object type. Defaults to the object type. @param form_id The form id from templating form system if we're using the forms API to specify attributes @param object_type The object type of the object we are instantiating @return The object id of the newly created object <p><b>Example:</b> <pre> template::form create add_group template::element create add_group group_name -value "Publisher" set var_list [list \ [list context_id $context_id] \ [list group_id $group_id]] return [package_instantiate_object \ -start_with "group" \ -var_list $var_list \ -form_id "add_group" \ "group"] </pre> } { if {$variable_prefix ne ""} { append variable_prefix "." } if {[catch { acs_object_type::get -object_type $object_type -array acs_type set package_name $acs_type(package_name) } errmsg]} { error "Object type \"$object_type\" does not exist" } # Select out the package name if it wasn't passed in if { $package_name eq "" } { set package_name $acs_type(package_name) } if { [ns_conn isconnected] } { if { $creation_user eq "" } { set creation_user [ad_conn user_id] } if { $creation_ip eq "" } { set creation_ip [ad_conn peeraddr] } } if {$creation_user == 0} { set creation_user "" } lappend var_list [list creation_user $creation_user] lappend var_list [list creation_ip $creation_ip] lappend var_list [list object_type $object_type] # The first thing we need to do is select out the list of all # the parameters that can be passed to this object type's new function. # This will prevent us from passing in any parameters that are # not defined foreach arg [package_plsql_args $package_name] { set real_params([string toupper $arg]) 1 } # Use pieces to generate the parameter list to the new # function. Pieces is just a list of lists where each list contains only # one item - the name of the parameter. We keep track of # parameters we've already added in the array param_array (all keys are # in uppercase) set pieces [list] foreach pair $var_list { lassign $pair __key __value if { ![info exists real_params([string toupper $__key])] } { # The parameter is not accepted as a parameter to the # pl/sql function. Ignore it. continue; } lappend pieces [list $__key] set param_array([string toupper $__key]) 1 # Set the value for binding set $__key $__value } # Go through the extra_vars (ben - OpenACS) if {$extra_vars ne "" } { foreach {__key __value} [ns_set array $extra_vars] { if { ![info exists real_params([string toupper $__key])] } { # The parameter is not accepted as a parameter to the # pl/sql function. Ignore it. continue; } lappend pieces [list $__key] set param_array([string toupper $__key]) 1 # Set the value for binding set $__key $__value } } if { $form_id ne ""} { set __id_column $acs_type(id_column) if { [info exists real_params([string toupper $__id_column])] && ![info exists param_array([string toupper $__id_column])] } { set param_array([string toupper $__id_column]) 1 set $__id_column [template::element::get_value $form_id "$variable_prefix$__id_column"] lappend pieces [list $__id_column] } if {$start_with eq ""} { set start_with $object_type } # Append the values from the template form for each attribute foreach row [package_object_attribute_list -start_with $start_with $object_type] { set __attribute [lindex $row 2] if { [info exists real_params([string toupper $__attribute])] && ![info exists param_array([string toupper $__attribute])] } { set param_array([string toupper $__attribute]) 1 set $__attribute [template::element::get_value $form_id "$variable_prefix$__attribute"] lappend pieces [list $__attribute] } } } ns_log notice package_instantiate_object.create_object-Q=[subst { select ${package_name}__new([plpgsql_utility::generate_attribute_parameter_call \ -prepend ":" \ ${package_name}__new \ $pieces])}] set object_id [db_exec_plsql create_object {}] if { [ns_conn isconnected] } { subsite::callback -object_type $object_type "insert" $object_id } # BUG FIX (ben - OpenACS) return $object_id } d_proc -public package_exec_plsql { { -var_list "" } package_name object_name } { Calls a pl/[pg]sql proc/func defined within the object type's package. Use of this Tcl API proc avoids the need for the developer to write separate SQL for each RDBMS we support. @author Don Baccus (dhogaza@pacifier.com) @creation-date 12/31/2003 @param package_name The PL/[pg]SQL package @param object_name The PL/[pg]SQL function within the package @param var_list A list of pairs of additional attributes and their values to pass to the constructor. Each pair is a list of two elements: key => value @return empty string for procs, function return value for funcs <p><b>Example:</b> <pre> set var_list [list \ [list group_id $group_id]] package_exec_plsql -var_list $var_list group delete </pre> } { # Ugly hack for the case where a proc has params named "package_name" or "object_name". set __package_name $package_name set __object_name $object_name foreach arg [package_plsql_args -object_name $__object_name $__package_name] { set real_params([string toupper $arg]) 1 } # Use pieces to generate the parameter list to the new # function. Pieces is just a list of lists where each list contains only # one item - the name of the parameter. We keep track of # parameters we've already added in the array param_array (all keys are # in uppercase) set pieces [list] foreach pair $var_list { lassign $pair __key __value if { ![info exists real_params([string toupper $__key])] } { # The parameter is not accepted as a parameter to the # pl/sql function. Ignore it. ns_log Warning "package_exec_plsql: skipping $__key not found in params for $__package_name $__object_name" continue } lappend pieces [list $__key] set param_array([string toupper $__key]) 1 # Set the value for binding set $__key $__value } if { [package_function_p -object_name $__object_name $__package_name] } { return [db_exec_plsql exec_func_plsql {}] } else { db_exec_plsql exec_proc_plsql {} } } # # Local variables: # mode: tcl # tcl-indent-level: 4 # indent-tabs-mode: nil # End: