##@@JCL.pm,dbixlib
##$$Job Control Library for Data Management Tasks
##author:Brad Adkins
##format:codehtml
##outfile:JCL.html
##title:Job Control Library
##toc:yes
##header:
DBIx-JCL
=head1 NAME
DBIx::JCL - Job Control Library for database load tasks.
=head1 SYNOPSIS
# file: test_job.pl
use strict;
use warnings;
use DBIx::JCL qw( :all );
my $jobname = 'name_of_job';
sys_init( $jobname );
# perform database tasks calling DBIx-JCL functions
# ...
sys_end();
exit sys_get_errorlevel();
=head1 DESCRIPTION
This documentation describes the perl module DBIx-JCL.pm and the use of
standardized perl scripts which together provide a common job execution
environment to support database backend load and maintenance tasks.
=head1 RATIONALE
Provide a suite of standard functions that can be shared across all batch
job scripts used to support database back end tasks. Provide a standardized
approach for the development of all back end database job scripts.
Centralize the administration and access to configuration data. Enforce
coding standards and documentation. Abstract the sql used to support back
end processes from the task processing logic, by placing all sqlinto an sql
library. This will make maintenance of back end sql a trivial task. Provide
generalized logging, notification, and system information functions.
If you want to write a robust database extract and load job with complete
support for logging and error notification, and do it in 25 lines of code,
read on.
=head1 OPTIONS
Database maintenance and load jobs written using DBIx-JCL support the following
options out-of-the-box, with no additional work required on your part.
Job Options:
| -r | Run job
| -rb | Run job in the background
| -rs | Run job at requested start time
| -rr | Restart job after failure
| -rde | Run using specified DE number
| -x | Pass extra parameters to job script
| -c | Specify database connections
| -v | Verbose
| -vv | Very Verbose
| -ng | No greeting
| -tc | Test database connections
Logging Options:
| -lf | Log filename
| -lg | Log generations
| -ll | Log log levels
| -lp | Log file prefix
| -lr | Log archive file radix
| -cl | Log console levels
Notificaiton Options:
| -ne | Notify email on completion
| -np | Notify pager on completion
| -et | Email notification to list
| -el | Email notification levels
| -pt | Pager notification to list
| -pl | Pager notification levels
Information Options:
| -dp | Display job parameters
| -dq | Display job querys
| -dd | Display job documentation
| -dl | Display last log file
| -da | Display archived log files
| -dj | Display a list of job scripts
| -dja | Diaplay jobs active in the system
Utility Options:
| -se | Send email message
| -sp | Send pager message
| -um | Util no move files
| -h | Help
| -ha | Help on option arguments
Please see L below.
=head1 CAPABILITIES
The DBIx-JCL modules provides many capabilities commonly needed in support of
database maintenance jobs designed to run in a production environment. Below
is a summary list of features and the types of functions provided to support
those features.
=head2 Features
The following features have been designed in to the DBIx-JCL module:
=over 4
=item * Logging support with log file rotation
=item * Notification support
=item * Simplified DBI interface
=item * Configuration data stored externally
=item * High level functions not available in the DBI
=item * SQL stored in "SQL books"
=item * Job documentation enforced
=item * Job control functions
=item * Plugin support
=back
=head2 Implementation
The features listed above have been implemented by providing [many] functions
for use by your database mantenance jobs:
=over 4
=item * Functions for command line interaction
=item * Functions for initialization, monitoring, and control
=item * Functions for database interaction
=item * Functions for log file access and maintenance
=item * Functions for file manipulation
=back
Please see L below.
=head1 EXAMPLE JOB
Shown below is the standard approach to writing job scripts.
##@@name_of_script.pl,bin
##$$Description of this job
use strict;
use warnings;
use DBIx::JCL qw( :all );
# initialize
# -------------------------------------------------------------------------
my $jobname = 'name_of_script';
sys_init( $jobname );
my $dbenv1 = 'mydb1';
my $mysql1 = sys_get_sql( 'query_number_1' );
# main
# -------------------------------------------------------------------------
log_info( sys_get_dbdescr( $dbenv1 ) );
db_connect( $dbenv1 );
# do more db stuff here
# end
# -------------------------------------------------------------------------
=begin wiki
!1 NAME
Name of script
----
!1 DESCRIPTION
Describe the job script here.
----
!1 RECOVERY NOTES
Document recovery notes here.
----
!1 DEPENDENCIES
Document dependencies here.
=cut
__END__
Please see L below.
=head1 ADDITIONAL INFORMATION
Please see the documentation embedded in this source file for [LOTS!] of
additional details on how to use JCL.pm. You can view this documentation using
WikiText.pm module to format the WikiText content in this file. Hint: download
and install WikiText.pm.
Thank you!
=head1 COPYRIGHT
Copyright 2008 Brad Adkins .
Permission is granted to copy, distribute and/or modify this document under the
terms of the GNU Free Documentation License, published by the Free Software
Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no
Back-Cover Texts.
=head1 AUTHOR
Brad Adkins, dbijcl@gmail.com
=cut
=begin wiki
!1 Name
DBIx-JCL - Job Control Library for database load tasks.
----
!1 Description
This documentation describes the perl module DBIx::JCL.pm and the use of \
standardized perl scripts which together provide a common job execution \
environment to support database backend maintenance.
----
!1 Synopsis
% language=Perl
% # file: test_job.pl
% use strict;
% use warnings;
% use DBIx::JCL qw( :all );
%
% my $jobname = 'name_of_job';
% sys_init( $jobname );
%
% # perform database tasks
%
% sys_end();
% exit sys_get_errorlevel();
%%
For a file named %test_job.pl% the %$jobname% would normally be simply \
%test_job%.
----
!1 Options
Job Options:
| -r | Run job|
| -rb | Run job in the background|
| -rs | Run job at requested start time|
| -rr | Restart job after failure|
| -rde | Run using specified DE number|
| -x | Pass extra parameters to job script|
| -c | Specify database connections|
| -v | Verbose|
| -vv | Very Verbose|
| -ng | No greeting|
| -tc | Test database connections|
Logging Options:
| -lf | Log filename|
| -lg | Log generations|
| -ll | Log log levels|
| -lp | Log file prefix|
| -lr | Log archive file radix|
| -cl | Log console levels|
Notificaiton Options:
| -ne | Notify email on completion|
| -np | Notify pager on completion|
| -et | Email notification to list|
| -el | Email notification levels|
| -pt | Pager notification to list|
| -pl | Pager notification levels|
Information Options:
| -dp | Display job parameters|
| -dq | Display job querys|
| -dd | Display job documentation|
| -dl | Display last log file|
| -da | Display archived log files|
| -dj | Display a list of job scripts|
| -dja | Diaplay jobs active in the system|
Utility Options:
| -se | Send email message|
| -sp | Send pager message|
| -um | Util no move files|
| -h | Help|
| -ha | Help on option arguments|
----
!1 Arguments
Job Params:
| -r | (on/off)|
| -rb | (on/off)|
| -rs | starttime Example: 17:30|
| -rr | jobstep Example: 3|
| -rde | denumber Example: 64753|
| -x | extra params Example: -x="a=1 b=2 c=3"|
| -c | connectdef Example: mydb:myinst|
| -v | (on/off)|
| -vv | (on/off)|
| -ng | (on/off)|
| -tc | connectdef Example: mydb:myinst|
Logging Params:
| -lf | filename Example: mylog.log|
| -lg | numgdg Example: 10|
| -ll | loglevels Example: FATAL,ERROR,WARN or WARN|
| -lp | logprefix Example: pre_|
| -lr | logradix Example: 3|
| -cl | loglevels Example: FATAL,ERROR,WARN,INFO,DEBUG or DEBUG|
Notificaiton Params:
| -ne | (on/off)|
| -np | (on/off)|
| -et | addrlist Example: me@myhost.com,you@myhost.com|
| -el | levels Example: FATAL,ERROR,WARN|
| -pt | addrlist Example: me@myhost.com,you@myhost.com|
| -pl | levels Example: FATAL,ERROR,WARN|
Information Params:
| -dp | (on/off)|
| -dq | (on/off)|
| -dd | (on/off)|
| -dl | (on/off)|
| -da | (on/off)|
| -dj | (on/off)|
| -dja | (on/off)|
Utility Params:
| -se | addrlist:msg Example: "me@myhost.com~Message text"|
| -sp | addrlist:msg Example: "me@myhost.com~Message text"|
| -um | (on/off)|
| -h | (on/off)|
| -ha | (on/off)|
----
!1 Rationale
Provide a suite of standard functions that can be shared across all batch \
job scripts used to support database back end tasks. Provide a standardized \
approach for the development of all back end database job scripts. \
Centralize the administration and access to configuration data. Enforce \
coding standards and documentation. Abstract the sql used to support back \
end processes from the task processing logic, by placing all sqlinto an sql \
library. This will make maintenance of back end sql a trivial task. Provide \
generalized logging, notification, and system information functions.
If you want to write a robust database extract and load job with complete \
support for logging and error notification, and do it in 25 lines of code, \
read on.
----
!1 Capabilities
Some of the capabilities provided by DBIx-JCL are: System initialization, \
variables for system-wide use, configuration file interface support, \
command line processing support, command line help interface, sql library \
interface, system documentation in pod form, handy information display \
routines, source filtering for quality control, database connection and \
sql processing, log file access and managment, email and pager notification, \
general file access routines, and a generic plugin interface.
----
!1 Configuration And Environment
Configuration is provided using an enhanced version of ini style \
configuration files. The big difference between the conf files used and \
ini files is that the conf files support here document syntax. This makes \
storing sql querys a trivial task. Several configuration files are used, \
these are described individually below.
!2 Environments
DBIx-JCL can support multiple database environments over multiple file \
systems, with attachments to any number of remote databases. An environment \
is actually a combination of file system and database instance. Remote \
databases and local databases can also be specified on the command line. \
The example conf files define the database environments shown in the \
diagram below.
On each local server, the default combination of database/instance is \
identified by an environment variable (shown in square brackets). The name \
of the environment variable is stored in the C file.
% language=Ini_Files
% (-------------------------------------+------------------------------------)
% LOCAL | REMOTE
% (-------------------------------------+------------------------------------)
% |
% .------------. .------------. | .------------.
% | Server 1 |--.--| mydb2/dev1 |-->| .-->| mydb1/frz |
% '------------' | `------------' | | '------------'
% | [mydev1] | |
% | | |
% | .------------. | | .------------.
% |--| mydb2/dev2 |-->| +-->| mydb1/prd |
% | '------------' | | '------------'
% | [mydev2] | |
% | | |
% | .------------. | | .------------.
% +--| mydb2/int |-->| +-->| mydb3/dev |
% '------------' | | '------------'
% [myint] |---+
% | |
% .------------. .------------. | | .------------.
% | Server 2 |-----| mydb2/frz |-->| +-->| mydb3/int |
% '------------' '------------' | | '------------'
% [myfrz] | |
% | |
% .------------. .------------. | | .------------.
% | Server 3 |-----| mydb2/prd |-->| +-->| mydb3/sys |
% '------------' '------------' | | '------------'
% [myprd] | |
% | |
% | | +------------.
% | +-->| mydb3/prd |
% | '------------'
% Key |
% (-----------------------------) |
% dev - development region |
% dev1 - development region |
% dev2 - development region |
% int - integration test region |
% frz - system test region |
% sys - system test region |
% prd - production region |
% (-----------------------------) |
% |
% (-------------------------------------+------------------------------------)
%%
!2 System Configuration
The /system.conf/ stores information about your installation environment. \
The default database environment related to this file system, a list of \
database environments, and a list of valid job acronyms:
% language=Ini_Files
% [system]
%
% envvar = mydbenv1
% dat_envrs = mydbenv1,mydbenv2,mydbenv3,mydbenv4
% job_acros = load_,extr_,merg_,vend_,job_,util_,test_,temp_
%%
Following this section are the directory sections, There is one directory \
section for each type of directory used: bin, lib, log, load, extr, and \
plugin. Each directory section is named as using the form \
%[directory ]%. Directory specifications for the the bin \
directory are shown below. For each database environment, you would have \
a directory entry for that particular environment. So for the bin directory, \
the entry would be something like the following:
% language=Ini_Files
% [directory bin]
%
% mydbenv1 = /home/account/bin/
% mydbenv2 = /home/account/bin/
% mydbenv3 = /home/account/bin/
% mydbenv4 = /home/account/bin/
%%
The trailing slashes on the directory entries are required.
The last section in the C file is the restart section. This \
stores the last job step attempted. This is set immediately before a job \
is restarted. The example below shows a job restart step of 3.
% language=Ini_Files
% [restart]
%
% restart=3
%%
!2 Job Configuration
The /job.conf/ file stores information about specific jobs. The key entry \
is the logfile entry. This entry provides a name to use for this job's log \
file. The entry is placed in a section named after the jobname used in the \
script. If your script uses:
% language=Perl
% my $jobname = 'job_number_1';
% sys_init( $jobname );
%%
Then the job section for that script would be:
% language=Ini_Files
%
% [job_number_1]
% logfile=epdw_contractor.log
%%
There are also several optional entries that can be made for a given job. \
These will be permanent overrides for that particular job. All of these are \
also available as command line options.
% language=Ini_Files
% logging_levels=
% gdg=
% emailto=
% pagerto=
% email_levels=
% pager_levels=
%%
This gives you the ability to set up logging and notifications differently \
for every job if you want to (probably not a good idea).
!2 Data Configuration
The /data.conf/ file is possibly the most complex file. This file is used \
to map your databases and database instances, both local and remote, and \
provides a default instance for each database.
Here is a sample /data.conf/ file. In the example below, the C<[instances]> \
section maps the available database instances for each database. The default \
sections %[default ] or C \
functions, they would not write to the log file becuase those log levels \
are not in the list of logging levels to be output. If you want to see you \
log messages on the console while your job is running, use the Verbose \
command line option.
The log write functions are:
|%log_fatal()% |outputs FATAL level messages|
|%log_error()% |outputs ERROR level messages|
|%log_warn()% |outputs WARN level messages|
|%log_info()% |outputs INFO level messages|
|%log_debug()% |outputs DEBUG level messages|
!2 Using Oracle's DBMS_OUTPUT Package
The functions used here to implement stored procedure calls (DBD::Oracle only) \
will gather dbms output automatically. If any is found, these are sent to \
the current log file using an appropriate logging level. To make your log \
files more readable, you should consider using a a custom package for all \
dbms output generated from stored procedures and functions. I've also found \
that if you preceed your dbms output messages with some white space, they \
will look better when viewed in your log files.
----
!1 Notifications
Another real strength of DBIx-JCL is the built-in support for notifications. \
There are two types of notifications, email notifications and pager \
notifications. One of the nice features of email notifications is that the \
log file is included in the email message following the message text. Pager \
notifications are just short versions of email notifications, pager \
notifications never have the contents of the log file appended.
The pager notifications are really just an email message. Your pager device \
must be able to support messaging via email interface to make use of this \
feature. Most cell phone devices and text pagers have this capability.
The severity of the message is included in the message subject line so you \
can immediately see if you need to respond to the message or not.
The log writing functions are hooked into the notification functions. \
Whenever a log write function is called it checks to see if a notification \
should also be sent based on the email and pager severity levels. These work
the same as described above for logging levels, in fact, the same levels are \
used. Care should be exercised when setting the notifications levels, if you \
set them too low you script could generate a lot of email/pager messages. \
Caveat emptor.
----
!1 Database Interface
This module uses the Perl DBI for all database functionality. However you do \
not have to deal with the raw DBI functions. All DBI access thru this module \
is made via a virtual name that you assign to each database connection used \
by your running job script. The virtual name is resolved using entries in a \
configuration file. Furthermore, all calls to DBI functions just require that \
virtual name. Underneath, the module functions handle storage of database \
handles and statement handles automatically for you. This has two benefits, \
it makes writing database job scripts for the novice much simpler, and it \
makes for cleaner, more readable job scripts.
You probably can't fully appreciation the latter until you are reading a \
job script at 2am, trying to figure out what went wrong with a production \
job. Of course, one of the design goals of this module is to make it so you \
never have to read a script when one of your jobs fails. All the information \
you need to diagnose and fix the problem should be in the most recent log \
file, with previous log history right at your finger tips as well.
----
!1 Script Naming Convention
Scripts which use DBIx-JCL are required to use a script naming convention, \
however, the convention chosen is up to you. All scripts using DBIx-JCL \
should be prefixed with an acronym. For example, if you had a script that \
sent a warning message on some condition, you might name it "util_warn.pl" \
where "util_" is the script prefix acronym. You decide what script prefix \
acronyms you want to use, and configure those in the system.conf file. This \
module will check that all invoking scripts adhere to your naming convention. \
DBIx-JCL will complain at runtime if a script is inappropriately named.
Some examples of script acronyms are:
|Acro |Description|
|load_ |load data script|
|extr_ |extract data script|
|merg_ |merge/update data script|
|job_ |job which runs other scripts|
|util_ |utility script|
|test_ |test script|
|temp_ |temporary scipt|
You should examine the sampel system configuration files that some with \
DBIx-JCL.
----
!1 Installation
The DBIx-JCL module can be installed into a private directory or appended to \
your Perl installation using the normal install process. If you intall into a \
private directory, you'll need to set the environment variable PERL5LIB so \
your scripts can find the module.
/Environment Variables/
The module also uses several envirnoment variables besides PERL5LIB, sample \
export entries are shown below. The module needs to know where your home \
directory is, this should normally be set for you in most installations. The \
module will look for a configuration file named /system.conf/ to start the \
boot-strap process, this location is identified by the JCLCONF environment \
variable. A default database environment needs to be identified. You \
determine what this variable will be called, in the example below the \
variable is named MYDBENV. The name you choose is stored in the \
/system.conf/ file in section %[system]%, under the key %envvar%.
Sample export settings:
% language=IniFiles
% export PERL5LIB=/home/myaccount/lib
% export HOME=/home/myaccount
% export JCLCONF=/home/myaccount/conf
% export MYDBENV=dbenv1
%%
Under a Windows system you will want to set these in yous Control Panel \
under System and Advanced options.
----
!1 Example Script
Shown below is the standard approach to writing job scripts.
% language=Perl
% #!perl
% ##@@name_of_script.pl,bin
% ##$$Description of the Job
%
% use strict;
% use warnings;
% use DBIx::JCL qw( :all );
%
% # initialize
% # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%
% my $jobname = 'name_of_script';
% sys_init( $jobname );
%
% my $dbenv1 = 'mydb1';
% my $mysql1 = sys_get_sql( 'query_number_1' );
%
% # main
% # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%
% log_info( sys_get_dbdescr( $dbenv1 ) );
% db_connect( $dbenv1 );
%
% # do more db stuff here
%
% # end
% # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%
% =begin wiki
%
% !1 NAME
%
% Name of script
%
% ----
%
% !1 DESCRIPTION
%
% Describe the job script here.
%
% ----
%
% !1 RECOVERY NOTES
%
% Document recovery notes here.
%
% ----
%
% !1 DEPENDENCIES
%
% Document dependencies here.
%
% =cut
%
% __END__
%
%%
The second and third lines of the example are required for every job script. \
The second line identifies the script and the script installation directory. \
The third line provides a brief description of the job and is used by the \
command line option that displays all installed jobs.
----
!1 Functions
The following provides an explanation of each of the functions provided by \
DBIx-JCL.
=cut
# package
# ------------------------------------------------------------------------------
package DBIx::JCL;
use strict;
use warnings;
# package exports
# ------------------------------------------------------------------------------
require Exporter;
use base qw( Exporter );
our @EXPORT_OK = qw(
sys_init
sys_init_setuser
sys_end
sys_init_plugin
sys_get_sql
sys_get_item
sys_get_hash
sys_get_array
sys_get_common_sql
sys_get_run_control
sys_get_dbdescr
sys_get_dbinst
sys_set_restart
sys_load_library
sys_set_verbose
sys_die
sys_warn
sys_info
sys_ctime2str
sys_disp_active_jobs
sys_run_job
sys_run_job_background
sys_run_job_wait
sys_run_job_maxrc
sys_run_job_reset
sys_get_path_bin_dir
sys_get_path_lib_dir
sys_get_path_log_dir
sys_get_path_load_dir
sys_get_path_extr_dir
sys_get_path_scripts_dir
sys_get_path_plugin_dir
sys_get_path_prev_dir
sys_get_mail_server
sys_get_mail_from
sys_get_mail_emailto
sys_get_mail_pagerto
sys_get_mail_email_levels
sys_get_mail_pager_levels
sys_get_log_file
sys_get_log_filefull
sys_get_log_logging_levels
sys_get_log_console_levels
sys_get_log_gdg
sys_get_dataenvr
sys_get_errorlevel
sys_get_conf_dir
sys_get_email_levels
sys_get_pager_levels
sys_get_logging_levels
sys_get_console_levels
sys_get_commandline
sys_get_commandline_opt
sys_get_commandline_val
sys_get_script_file
sys_get_user
sys_get_util_move
sys_get_maxval
sys_set_errorlevel
sys_set_die
sys_set_warn
sys_set_conf_file
sys_set_email_levels
sys_set_pager_levels
sys_set_mail_emailto
sys_set_logging_levels
sys_set_console_levels
sys_set_script_file
sys_set_path_log_dir
sys_set_path_plugin_dir
sys_set_maxval
sys_check_dataenvr
sys_timer
sys_wait
sys_disp_doc
log_fatal
log_error
log_warn
log_info
log_debug
log_close
log_write_log
log_write_screen
db_init
db_connect
db_nil
db_finish
db_disconnect
db_prepare
db_execute
db_commit
db_get_sth
db_get_defenvr
db_pef
db_pef_list
db_fetchrow
db_bindcols
db_rollback
db_insert_from_file
db_query_to_file
db_dump_query
db_dump_table
db_grant
db_func
db_proc
db_proc_in
db_proc_out
db_proc_inout
db_rowcount_query
db_sanity_check
db_rowcount_table
db_truncate
db_dbms_output_enable
db_dbms_output_disable
db_dbms_output_get
db_drop_index
db_drop_table
db_drop_procedure
db_drop_function
db_drop_package
db_rename_index
db_rename_table
db_purge_table
db_purge_index
db_update_statistics
db_sqlloader
db_sqlloaderx
db_sqlloaderx_parse_logfile
db_sqlloaderx_read
db_sqlloaderx_skipped
db_sqlloaderx_rejected
db_sqlloaderx_discarded
db_sqlloaderx_elapsed_time
db_sqlloaderx_cpu_time
db_index_rebuild
db_exchange_partition
util_get_filename_load
util_get_filename_extr
util_get_filename_log
util_read_header
util_read_footer
util_read_file
util_write_header
util_write_footer
util_move
util_trim
util_zsdf
test_init
test_ok
test_results
test_harness_init
test_harness_run
test_harness_results
$VERSION
$SQLLDR_SUCC
$SQLLDR_WARN
$SQLLDR_FAIL
$SQLLDR_FTL
);
our %EXPORT_TAGS = (
all => [
@EXPORT_OK
],
sys => [ qw(
sys_init
sys_init_setuser
sys_end
sys_init_plugin
sys_get_sql
sys_get_item
sys_get_hash
sys_get_array
sys_get_common_sql
sys_get_run_control
sys_get_dbdescr
sys_get_dbinst
sys_set_restart
sys_load_library
sys_set_verbose
sys_die
sys_warn
sys_info
sys_ctime2str
sys_disp_active_jobs
sys_run_job
sys_run_job_background
sys_run_job_wait
sys_run_job_maxrc
sys_run_job_reset
sys_get_path_bin_dir
sys_get_path_lib_dir
sys_get_path_log_dir
sys_get_path_load_dir
sys_get_path_extr_dir
sys_get_path_prev_dir
sys_get_path_scripts_dir
sys_get_mail_server
sys_get_mail_from
sys_get_mail_emailto
sys_get_mail_pagerto
sys_get_mail_email_levels
sys_get_mail_pager_levels
sys_get_log_file
sys_get_log_filefull
sys_get_log_logging_levels
sys_get_log_console_levels
sys_get_log_gdg
sys_get_dataenvr
sys_get_errorlevel
sys_get_conf_dir
sys_get_email_levels
sys_get_pager_levels
sys_get_logging_levels
sys_get_console_levels
sys_get_commandline
sys_get_commandline_opt
sys_get_commandline_val
sys_get_script_file
sys_get_path_plugin_dir
sys_get_util_move
sys_get_user
sys_get_maxval
sys_set_errorlevel
sys_set_die
sys_set_warn
sys_set_email_levels
sys_set_pager_levels
sys_set_mail_emailto
sys_set_logging_levels
sys_set_console_levels
sys_set_script_file
sys_set_conf_file
sys_set_path_log_dir
sys_set_path_plugin_dir
sys_set_maxval
sys_check_dataenvr
sys_timer
sys_wait
sys_disp_doc
) ],
log => [ qw(
log_fatal
log_error
log_warn
log_info
log_debug
log_close
log_write_log
log_write_screen
) ],
db => [ qw(
db_init
db_connect
db_nil
db_finish
db_disconnect
db_prepare
db_execute
db_commit
db_get_sth
db_get_defenvr
db_pef
db_pef_list
db_fetchrow
db_bindcols
db_rollback
db_insert_from_file
db_query_to_file
db_dump_query
db_dump_table
db_grant
db_func
db_proc
db_proc_in
db_proc_out
db_proc_inout
db_rowcount_query
db_sanity_check
db_rowcount_table
db_truncate
db_dbms_output_enable
db_dbms_output_disable
db_dbms_output_get
db_drop_index
db_drop_table
db_drop_procedure
db_drop_function
db_drop_package
db_rename_index
db_rename_table
db_purge_table
db_purge_index
db_update_statistics
db_sqlloader
db_sqlloaderx
db_sqlloaderx_parse_logfile
db_sqlloaderx_read
db_sqlloaderx_skipped
db_sqlloaderx_rejected
db_sqlloaderx_discarded
db_sqlloaderx_elapsed_time
db_sqlloaderx_cpu_time
db_index_rebuild
db_exchange_partition
) ],
util => [ qw(
util_get_filename_load
util_get_filename_extr
util_get_filename_log
util_read_header
util_read_footer
util_read_file
util_write_header
util_write_footer
util_move
util_trim
util_zsdf
) ],
test => [ qw(
test_init
test_ok
test_results
test_harness_init
test_harness_run
test_harness_results
) ],
const => [ qw(
$SQLLDR_SUCC
$SQLLDR_WARN
$SQLLDR_FAIL
$SQLLDR_FTL
) ],
);
# package imports
# ------------------------------------------------------------------------------
use English qw( -no_match_vars );
use Getopt::Long;
use Config::IniFiles;
use Pod::WikiText;
use IO::File;
use IO::Handle;
use IO::LockedFile;
use Fcntl qw(:flock);
use File::Copy;
use File::Bidirectional;
use File::Basename;
use MIME::Lite;
use Date::Format;
use DBI;
#|++ ## flush print buffer on write
# version
# ------------------------------------------------------------------------------
our $VERSION = "0.12";
# const exports
# ------------------------------------------------------------------------------
our $SQLLDR_SUCC = 0;
our $SQLLDR_WARN = 2;
our $SQLLDR_FAIL = 1;
our $SQLLDR_FTL = 3;
# state variables
# ------------------------------------------------------------------------------
my $path_bin_dir = '';
my $path_lib_dir = '';
my $path_log_dir = '';
my $path_load_dir = '';
my $path_extr_dir = '';
my $path_prev_dir = '';
my $path_scripts_dir = '';
my $mail_server = '';
my $mail_from = '';
my $mail_emailto = '';
my $mail_pagerto = '';
my $mail_email_levels = '';
my $mail_pager_levels = '';
my $log_file = '';
my $log_filefull = '';
my $log_logging_levels = '';
my $log_console_levels = '';
my $dataenvr = '';
my $log_gdg = 0;
my $log_prefix = '';
my $log_radix = 2;
my $errorlevel = 0;
my $util_move = 1;
# command line variables
# ------------------------------------------------------------------------------
my $opt_run = 0;
my $opt_run_background = 0;
my $opt_run_scheduled = '';
my $opt_run_restart = '';
my $opt_connection = '';
my $opt_run_de = '';
my $opt_commandline_ext = '';
my $opt_verbose = 0;
my $opt_very_verbose = 0;
my $opt_no_greeting = 0;
my $opt_test_dbcon = '';
my $opt_log_file = '';
my $opt_logging_levels = '';
my $opt_console_levels = '';
my $opt_log_gdg = 0;
my $opt_log_prefix = '';
my $opt_log_radix = 0;
my $opt_notify_email_oncomp = 0;
my $opt_notify_pager_oncomp = 0;
my $opt_notify_email_tolist = '';
my $opt_notify_pager_tolist = '';
my $opt_notify_email_levels = '';
my $opt_notify_pager_levels = '';
my $opt_disp_params = 0;
my $opt_disp_sql = 0;
my $opt_disp_doc = 0;
my $opt_disp_sysdoc = 0;
my $opt_disp_logprev = 0;
my $opt_disp_logarch = 0;
my $opt_disp_jobs = 0;
my $opt_disp_active_jobs = 0;
my $opt_disp_exec = 0;
my $opt_send_email = '';
my $opt_send_pager = '';
my $opt_util_move = 0;
my $opt_help = 0;
my $opt_help_args = 0;
my $opt_commandline = join ' ', @ARGV;
# module variables
# ------------------------------------------------------------------------------
use constant QUOTE => q{"};
use constant SPACE => q{ };
my $RC_FATAL = 32;
my $RC_ERROR = 16;
my $RC_WARN = 8;
my %MONTHS = (
Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5,
Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec=> 11,
);
my $jobname = ''; # name used to identify job script
my $pid = 0; # os process id number
my %pidlib = (); # hash of info about background jobs
my $pidcnt = 0; # count of child pids
my $maxrc = 0; # max return code for foreground jobs
my $osuser = ''; # os username
my $commandline_ext = ''; # extended command line
my @plugins = (); # loaded plugin information
my %timers = (); # hash of timers
my %function_params = (); # hash of stored function params
my $wt_seconds = 0; # wait seconds
my $wt_start = time; # init wait start time
my %maxval = (); # hash of max values
my $t_num = 0; # test script
my $t_ok = 0; # test script
my $t_notok = 0; # test script
my $th_num = 0; # test harness
my $th_error = 0; # test harness
my $sys_dbms_output = 0; # has dbms_output been enabled
my $sys_log_open = 0; # is log file open
my $sys_stderr_redirected = 0; # STDERR has been redirected to /dev/null
my $sys_jobconf_override = 0; # using override job conf file
my $sys_jobconf_file = ''; # override jobconf filename
my $path_plugin_dir = ''; # path to plugin directory
my $path_conf_dir = ''; # path to conf file directory
my %sqlloader_results = (); # hash of SQL*Loader results
my %log_level_opts = (); # hash of logging options
my (%conf_data, %conf_log, %conf_mail, %conf_query, %conf_job, %conf_util);
my (%conf_system, %conf_de, %conf_rcontrols);
my (@databases, @dat_envrs, @job_acros);
my (%dbname, %dbdefenvr, %dbinst, %dbconn, %dbhandles);
my $script_file = $PROGRAM_NAME;
my $script_filefull = $script_file;
my $log_ext = '.log';
my $dbitrace_base = 'dbitrace';
my $dbitrace_file = $dbitrace_base . $log_ext;
my $dbitrace_filefull = '';
$script_file =~ s{^/.*/}{};
$path_conf_dir = $ENV{JCLCONF} || '';
if ( ! defined $path_conf_dir ) {
sys_die( 'Environment variable JCLCONF not set', 0 );
}
if ( $path_conf_dir =~ m/(.*)\/$/ ) { $path_conf_dir = $1; }
my %db_func_params = (
db_insert_from_file => {
TrimLead => 'no',
TrimFieldLead => 'no',
TrimFieldTrail => 'no',
CommentChar => '#',
SkipComments => 'no',
SkipLastField => 'no',
UseRegex => 'no',
},
db_insert_from_conf => {
TrimLead => 'no',
TrimFieldLead => 'no',
TrimFieldTrail => 'no',
CommentChar => '#',
SkipComments => 'no',
SkipLastField => 'no',
UseRegex => 'no',
},
db_sqlloader => {
DatFilePath => '',
DbEnvr => '',
NetService => '',
},
);
# public methods
# ------------------------------------------------------------------------------
=begin wiki
!2 System Functions
These functions provide general job information and job managment \
capabilities.
=cut
sub sys_init {
=begin wiki
!3 sys_init
( jobname )
This is the job script initialization function. All job scripts should call \
this function first before any other JCL functions. This will validate a job \
name and does all the other setup work necessary to run a job script. This \
function also provides a standard command line interface and supporting \
functions for the supplied command line options.
=cut
my ($jn, @cl) = @_;
$jobname = $jn;
foreach my $opt ( @cl ) {
push @ARGV, $opt; # add additional command line option
}
unless ( $jobname ) {
sys_die( 'Please specify jobname when initializing', 0 );
}
_sys_init_vars();
$log_file = $jobname . $log_ext;
$log_filefull = $path_log_dir.$log_file;
push @ARGV, '-r' if $jobname eq "JCL"; # for convenience
$sys_jobconf_file = _sys_check_de_override( $jobname );
$sys_jobconf_file .= ".conf";
_sys_read_conf( $sys_jobconf_file ); # tie %conf_job to job's conf file
_sys_read_job(); # read job specific settings from %conf_job
GetOptions( "r" => \$opt_run,
"rb" => \$opt_run_background,
"rs=s" => \$opt_run_scheduled,
"rr=s" => \$opt_run_restart,
"rde=s" => \$opt_run_de,
"x=s" => \$opt_commandline_ext,
"c=s" => \$opt_connection,
"v" => \$opt_verbose,
"vv" => \$opt_very_verbose,
"ng" => \$opt_no_greeting,
"tc=s" => \$opt_test_dbcon,
"lf=s" => \$opt_log_file,
"lg=i" => \$opt_log_gdg,
"lp=s" => \$opt_log_prefix,
"lr=i" => \$opt_log_radix,
"ll=s" => \$opt_logging_levels,
"cl=s" => \$opt_console_levels,
"ne" => \$opt_notify_email_oncomp,
"np" => \$opt_notify_pager_oncomp,
"et=s" => \$opt_notify_email_tolist,
"el=s" => \$opt_notify_email_levels,
"pt=s" => \$opt_notify_pager_tolist,
"pl=s" => \$opt_notify_pager_levels,
"dp" => \$opt_disp_params,
"dq" => \$opt_disp_sql,
"dd" => \$opt_disp_doc,
"dl" => \$opt_disp_logprev,
"da" => \$opt_disp_logarch,
"dj" => \$opt_disp_jobs,
"dja" => \$opt_disp_active_jobs,
"se=s" => \$opt_send_email,
"sp=s" => \$opt_send_pager,
"um" => \$opt_util_move,
"h" => \$opt_help,
"ha" => \$opt_help_args,
) || _sys_help(0);
if ( $opt_connection ) {
foreach my $connectdef ( split m/,/, $opt_connection ) {
my ($db, $inst) = split m/:/, $connectdef;
_check_array_val( $db, \@databases )
|| sys_die( "Invalid database: [$db]", 0 );
_check_array_val( $inst, [split m/,/, $dbinst{$db}] )
|| sys_die( "Invalid database instance: [$db.$inst]", 0 );
## update default connection data
$dbdefenvr{$db} = $inst;
}
}
# create dbitrace file if not found
if ( ! -e $dbitrace_filefull ) {
open my $fh, ">", $dbitrace_filefull
|| sys_die( 'Unable to open dbitrace file', 0 );
close $fh;
}
if ( $opt_help ) {
_sys_help( 1 ); }
if ( $opt_help_args ) {
_sys_help( 2 ); }
if ( $opt_run_background ) {
_sys_run_background(); }
if ( $opt_run_scheduled ) {
_sys_run_scheduled(); }
if ( $opt_run_de ) {
_sys_run_de( $opt_run_de ); }
if ( $opt_run_restart ) {
_sys_run_restart(); }
if ( $opt_test_dbcon ) {
_sys_test_dbcon( $opt_test_dbcon); }
if ( $opt_commandline_ext ) {
$commandline_ext = $opt_commandline_ext; }
if ( $opt_logging_levels ) {
$log_logging_levels = _sys_check_severity_levels( $opt_logging_levels ); }
if ( $opt_console_levels ) {
$log_console_levels = _sys_check_severity_levels( $opt_console_levels ); }
if ( $opt_log_gdg ) {
$log_gdg = _sys_check_log_gdg( $opt_log_gdg ); }
if ( $opt_log_prefix ) {
$log_prefix = $opt_log_prefix; }
if ( $opt_log_radix ) {
$log_radix = _sys_check_log_radix( $opt_log_radix ); }
if ( $opt_notify_email_tolist ) {
$mail_emailto = $opt_notify_email_tolist; }
if ( $opt_notify_pager_tolist ) {
$mail_pagerto = $opt_notify_pager_tolist; }
if ( $opt_notify_email_levels ) {
$mail_email_levels = _sys_check_severity_levels( $opt_notify_email_levels ); }
if ( $opt_notify_pager_levels ) {
$mail_pager_levels = _sys_check_severity_levels( $opt_notify_pager_levels ); }
if ( $opt_disp_logprev ) {
_sys_disp_logprev(); }
if ( $opt_disp_logarch ) {
_sys_disp_logarch(); }
if ( $opt_disp_exec ) {
_sys_disp_exec(); }
if ( $opt_disp_sql ) {
_sys_disp_sql(); }
if ( $opt_disp_params ) {
_sys_disp_params(); }
if ( $opt_disp_doc ) {
_sys_disp_doc(); }
if ( $opt_disp_jobs ) {
_sys_disp_jobs(); }
if ( $opt_disp_active_jobs ) {
_sys_disp_active_jobs( 0 ); }
if ( $opt_send_email ) {
_sys_send_email_message($opt_send_email); }
if ( $opt_send_pager ) {
_sys_send_pager_message($opt_send_pager); }
if ( $opt_util_move ) {
$util_move = 0; }
# must have a Run option to continue
if ( ! $opt_run ) {
_sys_help(1);
}
$log_file = $log_prefix . $jobname . $log_ext; # default
if ( $osuser ) { # custom
$log_file = $log_prefix . $jobname . '_' . $osuser . $log_ext;
}
$log_filefull = $path_log_dir . $log_file;
if ( $opt_log_file ) { # override
$log_file = $opt_log_file;
$log_filefull = $path_log_dir . $log_file;
}
_log_init_log_file(); # log rotation handler
# validate script name using configured acros
my ($base, $path, $type) = fileparse( $script_file );
if ( $base =~ m/^([a-z]+_)/x ) { ## acro + underscore
$base = $1;
}
_check_array_val($base, \@job_acros) || sys_die( "Not a valid job acro", 0 );
_sys_init_source_validation();
sys_timer( 'start', '__default_timer' );
log_info( "Start: $jobname" ) unless $opt_no_greeting;
if ( $opt_very_verbose ) { $opt_verbose = 1; }
if ( $opt_verbose ) {
log_info( 'Running in verbose mode' );
log_info( "Process: $pid" );
log_info( "Options: $opt_commandline" );
}
if ( $sys_jobconf_override ) {
log_info( "Jobconf override: $sys_jobconf_file" );
}
_sys_job_init();
return 0;
}
sub sys_init_setuser {
=begin wiki
!3 sys_init_setuser
( jn, cl )
Please write this documentation.
=cut
my ($jn, @cl) = @_;
$osuser = getlogin || 'unknown';
sys_init( $jn, @cl );
return 0;
}
sub sys_end {
=begin wiki
!3 sys_end
No Parameters
Please write this documentation.
=cut
_sys_job_end();
if ( $opt_no_greeting ) { return 0; }
sys_timer( 'stop', '__default_timer' );
log_info( "Errorlevel: $errorlevel" );
log_info( "Elapsed time: " . sys_timer( 'elapsed', '__default_timer' ) );
log_info( "End: $jobname" ) unless $opt_no_greeting;
return 0;
}
sub sys_load_library {
=begin wiki
!3 sys_load_library
( conf_filename )
Give the user an opportunity to load a different conf file replacing the \
contents of sys_common.conf with the requested conf file contents.
=cut
my $conf_filename = shift;
## load a conf file replacing the contents of sys_common.conf
tie %conf_query, 'Config::IniFiles', ( -file => $path_conf_dir.'/'.$conf_filename )
or sys_die( "Unable to load conf file $conf_filename", 0 );
return 0;
}
sub sys_init_plugin {
=begin wiki
!3 sys_init_plugin
( plugin_file, package_name )
Provide plugin support. This function accepts a plugin filename and attempts \
to load a plugin file by that name from the plugin directory. Plugins are \
standard Perl modules with nothing exported. The package name used by the \
module is also passed in to this function and is used to call an \
initialization function named start.
Plugins should always implement a start and an end function, these take no \
parameters. All plugins should also implement a main plugin function named \
odly enough, plugin_main. The start and end functions should not take any \
parameters. The main plugin function can be written to accept whatever \
parameters are needed.
This little bit of deep magic by merlyn gleened from the Perl Monastery was \
very educational (I almost had it before finding this):
% language=Perl
% my %codeRefs = map {
% "Package"->can($_) || sub { die "can't find $_" }
% } qw(subroutine1 subroutine2 subroutine3);
%%
Merlyn, aka, Tom Christensen???
=cut
my ($plugin_file, $package_name) = @_;
my $plugin_filefull = $path_plugin_dir.$plugin_file.'.pm';
unless ( -f $plugin_filefull ) { sys_die( "Plugin not found: $plugin_file", 0 ); }
require $plugin_filefull;
push @plugins, join '~', ($package_name, $plugin_file, $plugin_filefull);
$package_name->start($path_conf_dir, $path_plugin_dir, $dataenvr);
return $package_name->can('plugin_main'); ## deep magic
}
sub sys_ctime2str {
=begin wiki
!3 sys_time2str
( format )
This is an interface to the Data::Format::time2str function. This simply \
provides an easier way for the job script to make use of the time2str \
function for acquiring a formatted current date/time. You can pass as a \
format string any of the following meta characters.
|%% |PERCENT|
|%a |day of the week abbr|
|%A |day of the week|
|%b |month abbr|
|%B |month|
|%c |MM/DD/YY HH:MM:SS|
|%C |ctime format: Sat Nov 19 21:05:57 1994|
|%d |numeric day of the month, with leading zeros (eg 01..31)|
|%e |numeric day of the month, without leading zeros (eg 1..31)|
|%D |MM/DD/YY|
|%G |GPS week number (weeks since January 6, 1980)|
|%h |month abbr|
|%H |hour, 24 hour clock, leading 0's)|
|%I |hour, 12 hour clock, leading 0's)|
|%j |day of the year|
|%k |hour|
|%l |hour, 12 hour clock|
|%L |month number, starting with 1|
|%m |month number, starting with 01|
|%M |minute, leading 0's|
|%n |NEWLINE|
|%o |ornate day of month -- "1st", "2nd", "25th", etc.|
|%p |AM or PM|
|%P |am or pm (Yes %p and %P are backwards :)|
|%q |Quarter number, starting with 1|
|%r |time format: 09:05:57 PM|
|%R |time format: 21:05|
|%s |seconds since the Epoch, UCT|
|%S |seconds, leading 0's|
|%t |TAB|
|%T |time format: 21:05:57|
|%U |week number, Sunday as first day of week|
|%w |day of the week, numerically, Sunday == 0|
|%W |week number, Monday as first day of week|
|%x |date format: 11/19/94|
|%X |time format: 21:05:57|
|%y |year (2 digits)|
|%Y |year (4 digits)|
|%Z |timezone in ascii. eg: PST|
|%z |timezone in format -/+0000|
/end of table/
=cut
my $format = shift;
return time2str($format, time);
}
sub sys_die {
=begin wiki
!3 sys_die
Parameters: ( message, notify )
Print a message to STDOUT and then exit returning $errorlevel $RC_FATAL. The \
message is printed to STDOUT because STDERR is redirected while running.
=cut
my ($message, $notify) = @_;
$notify = 0 unless defined $notify;
$errorlevel = $RC_FATAL;
_log_write_to_screen( 'FATAL', $notify, $message );
if ( $sys_log_open ) {
_log_write_to_log( 'FATAL', $notify, $message );
}
## save a call if possible
if ( $notify ) { _log_send_notifications( 'FATAL', $notify, $message ); }
_sys_job_end();
exit $errorlevel;
}
sub sys_warn {
=begin wiki
!3 sys_warn
Parameters: ( message, notify )
Print a message to STDOUT and then return to caller setting $errorlevel \
$RC_WARN. The message is printed to STDOUT because STDERR is redirected \
while running.
=cut
my ($message, $notify) = @_;
$notify = 1 unless defined $notify;
$errorlevel = $RC_WARN;
## force write to screen
_log_write_to_screen( 'WARN', 1, $message );
## force write to log if log is open
if ( $sys_log_open ) {
_log_write_to_log( 'WARN', 1, $message );
}
## force notifications if notification requested
if ( $notify ) { _log_send_notifications( 'WARN', 1, $message ); }
return $errorlevel;
}
sub sys_info {
=begin wiki
!3 sys_info
Parameters: ( message, notify )
=cut
my ($message, $extmsg, $notify, $nolog) = @_;
$notify = 1 unless defined $notify;
$nolog = 0 unless defined $nolog;
## get destination email address from job conf
my $emailto = sys_get_item( 'sys_info_emailto' );
my $mail_emailto_save = $mail_emailto;
$mail_emailto = $emailto;
log_info( $message, $extmsg, $nolog );
_log_send_notifications( 'INFO', 1, $message ) if $notify;
$mail_emailto = $mail_emailto_save;
return 0;
}
sub sys_disp_active_jobs {
=begin wiki
!3 sys_disp_active_jobs
No Parameters
Please write this documentation.
=cut
_sys_disp_active_jobs( 1 );
return 0;
}
sub sys_run_job {
=begin wiki
!3 sys_run_job
Parameters: (jobname, job_maxrc, params )
|$job |name of script or application to execute|
|@params |list of parameters to pass to the executed process|
This function usese the built-in Perl system function to invoke a JCL script \
(or other application). As such, this function will wait until the child \
completes before returning to the caller.
A reasonable attempt is made to insure that the process execute is invoked \
via a shell. This is accomplished by passing the system function the \
paramaters as a quoted string, rather than as a list.
Returns: Process return code from the script/application executed.
=cut
my ($jobname, $job_maxrc, @params) = @_;
my @args = ($jobname, @params);
system(@args);
my $childrc = $CHILD_ERROR >> 8;
if ( $childrc > $job_maxrc ) {
sys_die( "Process failed with return code $childrc" );
}
if ( $job_maxrc > $maxrc ) { $maxrc = $job_maxrc; }
return $childrc;
}
sub sys_run_job_background {
=begin wiki
!3 sys_run_job_background
Parameters: ( jobname, maxrc, params )
Please write this documentation.
Returns:
=cut
my ($jobname, $maxrc, @params) = @_;
$maxrc = 0 unless $maxrc;
my $pid = _sys_forkexec( $jobname, @params );
$pidlib{$pid} = { jobname => $jobname,
maxrc => $maxrc,
retcd => 0
};
$pidcnt++;
return $pid;
}
sub sys_run_job_wait {
=begin wiki
!3 sys_run_job_wait
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return 0 if $pidcnt < 1;
while (1) {
my $pid = _sys_reap_child();
$pidcnt--;
my $childrc = $pidlib{$pid}{retcd};
my $msg = "Complete $pidlib{$pid}{jobname}. Return code: $childrc.";
if ( $childrc > $pidlib{$pid}{maxrc} ) {
## log_warn sets errorlevel
log_warn( "$msg Max allowed: $pidlib{$pid}{maxrc}." );
} else {
log_info( $msg );
}
last if $pidcnt < 1;
}
return $pidcnt;
}
sub sys_run_job_maxrc {
=begin wiki
!3 sys_run_job_maxrc
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
## return the max of either the current background max return code or the
## current foreground max return code
my $tmprc = 0;
foreach my $pid ( keys %pidlib ) {
if ( $pidlib{$pid}{retcd} > $tmprc ) { $tmprc = $pidlib{$pid}{retcd}; }
}
( $tmprc >= $maxrc ) ? return $tmprc : return $maxrc;
}
sub sys_run_job_reset {
=begin wiki
!3 sys_run_job_reset
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
$pidcnt = 0; ## reset background jobs count
%pidlib = (); ## reset background jobs info hash
$maxrc = 0; ## reset foreground jobs max return code
return 0;
}
sub sys_get_path_bin_dir {
=begin wiki
!3 sys_get_path_bin_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $path_bin_dir;
}
sub sys_get_path_lib_dir {
=begin wiki
!3 sys_get_path_lib_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $path_lib_dir;
}
sub sys_get_path_log_dir {
=begin wiki
!3 sys_get_path_log_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $path_log_dir;
}
sub sys_get_path_load_dir {
=begin wiki
!3 sys_get_path_load_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $path_load_dir;
}
sub sys_get_path_extr_dir {
=begin wiki
!3 sys_get_path_extr_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $path_extr_dir;
}
sub sys_get_path_prev_dir {
=begin wiki
!3 sys_get_path_prev_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $path_prev_dir;
}
sub sys_get_path_scripts_dir {
=begin wiki
!3 sys_get_path_scripts_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $path_scripts_dir;
}
sub sys_get_path_plugin_dir {
=begin wiki
!3 sys_get_path_plugin_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $path_plugin_dir;
}
sub sys_get_mail_server {
=begin wiki
!3 sys_get_mail_server
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $mail_server;
}
sub sys_get_mail_from {
=begin wiki
!3 sys_get_mail_from
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $mail_from;
}
sub sys_get_mail_emailto {
=begin wiki
!3 sys_get_mail_emailto
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $mail_emailto;
}
sub sys_get_mail_pagerto {
=begin wiki
!3 sys_get_mail_pagerto
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $mail_pagerto;
}
sub sys_get_mail_email_levels {
=begin wiki
!3 sys_get_mail_email_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $mail_email_levels;
}
sub sys_get_mail_pager_levels {
=begin wiki
!3 sys_get_mail_pager_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $mail_pager_levels;
}
sub sys_get_log_file {
=begin wiki
!3 sys_get_log_file
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $log_file;
}
sub sys_get_log_filefull {
=begin wiki
!3 sys_get_log_filefull
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $log_filefull;
}
sub sys_get_log_logging_levels {
=begin wiki
!3 sys_get_log_logging_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $log_logging_levels;
}
sub sys_get_log_console_levels {
=begin wiki
!3 sys_get_log_console_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $log_console_levels;
}
sub sys_get_log_gdg {
=begin wiki
!3 sys_get_log_gdg
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $log_gdg;
}
sub sys_get_dataenvr {
=begin wiki
!3 sys_get_dataenvr
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $dataenvr;
}
sub sys_get_errorlevel {
=begin wiki
!3 sys_get_errorlevel
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $errorlevel;
}
sub sys_get_dbdescr {
=begin wiki
!3 sys_get_dbdescr
Parameters: ( dbacro )
Accept a database acro and return a database description string which \
consists of database name, acro, and current instance.
Returns:
=cut
my $dbacro = shift;
my $dbdescr = 'Database: acronym not found';
foreach my $acro ( @databases ) {
if ( $acro eq $dbacro ) {
$dbdescr = 'Database Connection: ' . $dbname{$dbacro} . ' (' .
$dbacro . '/' . $dbdefenvr{$dbacro} . ')';
}
}
return $dbdescr;
}
sub sys_get_dbinst {
=begin wiki
!3 sys_get_dbinst
Parameters: ( dbacro )
Please write this documentation.
Returns:
=cut
my $dbacro = shift;
my $dbdescr = 'Database: instance not found';
foreach my $acro ( @databases ) {
if ( $acro eq $dbacro ) {
$dbdescr = $dbacro . '/' . $dbdefenvr{$dbacro};
}
}
return uc($dbdescr);
}
sub sys_get_conf_dir {
=begin wiki
!3 sys_get_conf_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $path_conf_dir . '/';
}
sub sys_get_sql {
=begin wiki
!3 sys_get_sql
Parameters: ( sqlname, alternate_job_name )
Return the sql query from the query.conf file using the sqlname provided. \
If the requested sql name is not found, the name gets 'sql:' prepended and \
then another attempt is made. This allows entries of the form 'name' or \
alternately 'sql:name' to be used in the query.conf file.
The user may also pass in an optionl section name which will override the \
default section name. (Default section name is the current $jobname.)
Returns:
=cut
my ($sqlname, $altsection) = @_;
my $section = $altsection || 'sql';
if ( ! $conf_job{$section}{$sqlname} ) {
$sqlname = 'sql:'.$sqlname;
if ( ! $conf_job{$section}{$sqlname} ) {
sys_die( "The job conf file does not contain a query named [$sqlname]", 0 );
}
}
return $conf_job{$section}{$sqlname};
}
sub sys_get_item {
=begin wiki
!3 sys_get_item
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($item, $altsection) = @_;
my $section = $altsection || 'job';
my $value = $conf_job{$section}{$item};
if ( ! defined $value ) {
sys_die( "Job conf missing entry [$item] in section [$section]", 0 );
}
if ( $value eq '0' ) {
return $conf_job{$section}{$item};
}
return $value;
}
sub sys_get_hash {
=begin wiki
!3 sys_get_hash
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($section, $entry, $delim) = @_;
$delim = ':' unless $delim;
my ($pseudo, %hash);
if ( $conf_job{$section}{$entry} ) {
$pseudo = $conf_job{$section}{$entry};
} else {
sys_die( "No job conf entry found for $entry in section $section" );
}
## construct a real hash from the pseudo hash
foreach my $item ( split "\n", $pseudo ) {
my ($key, $value) = split m/$delim/, $item;
$hash{$key} = $value;
}
return \%hash; ## ref to hash
}
sub sys_get_array {
=begin wiki
!3 sys_get_array
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($section, $entry, $delim) = @_;
$delim = ':' unless $delim;
my ($pseudo, @array);
if ( $conf_job{$section}{$entry} ) {
$pseudo = $conf_job{$section}{$entry};
} else {
sys_die( "No job conf entry found for $entry in section $section" );
}
## construct a real array from the pseudo array
foreach my $item ( split "\n", $pseudo ) {
push @array, $item;
}
return \@array; ## ref to an array
}
sub sys_get_common_sql {
=begin wiki
!3 sys_get_common_sql
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($sqlname, $altsection) = @_;
my $section = $altsection || 'sql';
if ( ! $conf_query{$section}{$sqlname} ) {
$sqlname = 'sql:'.$sqlname;
if ( ! $conf_query{$section}{$sqlname} ) {
sys_die( 'Common sql conf missing query by that name', 0 );
}
}
return $conf_query{$section}{$sqlname};
}
sub sys_get_run_control {
=begin wiki
!3 sys_get_run_control
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($jobname, $section, $default) = @_;
my $rcontrol = $default || 0;
if ( ! $conf_rcontrols{$section}{$jobname} ) {
return $rcontrol;
}
return $conf_rcontrols{$section}{$jobname};
}
sub sys_get_email_levels {
=begin wiki
!3 sys_get_email_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $mail_email_levels;
}
sub sys_get_pager_levels {
=begin wiki
!3 sys_get_pager_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $mail_pager_levels;
}
sub sys_get_logging_levels {
=begin wiki
!3 sys_get_logging_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $log_logging_levels;
}
sub sys_get_console_levels {
=begin wiki
!3 sys_get_console_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $log_console_levels;
}
sub sys_get_commandline {
=begin wiki
!3 sys_get_commandline
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return join ' ', @ARGV;
}
sub sys_get_commandline_opt {
=begin wiki
!3 sys_get_commandline_opt
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $target_opt = shift;
foreach my $option ( @ARGV ) {
my ($opt,$val) = split m/=/, $option;
$opt =~ s/^-\s*//x;
$opt =~ s/\s+$//x;
if ( $opt =~ m/^$target_opt$/ix ) {
return 1;
}
}
return 0;
}
sub sys_get_commandline_val {
=begin wiki
!3 sys_get_commandline_val
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($target_opt,$default_value) = @_;
## handle:
## >script.pl -r -- -batchsize=10
foreach my $option ( @ARGV ) {
$option =~ s/\s+=/=/x;
$option =~ s/=\s+/=/x;
my ($opt,$val) = split m/=/, $option;
$opt =~ s/^-\s*//x;
$opt =~ s/\s+$//x;
if ( $opt =~ m/^$target_opt$/ix ) {
#$val =~ s/^\s*//;
#$val =~ s/\s*$//;
return $val;
}
}
return $default_value;
}
sub sys_get_script_file {
=begin wiki
!3 sys_get_script_file
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $script_file;
}
sub sys_get_util_move {
=begin wiki
!3 sys_get_util_move
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return $util_move;
}
sub sys_get_user {
=begin wiki
!3 sys_get_user
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return getlogin || 'unknown';
}
sub sys_get_maxval {
=begin wiki
!3 sys_get_maxval
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $key = shift;
return $maxval{$key} || 0;
}
sub sys_set_restart {
=begin wiki
!3 sys_set_restart
Parameters: ( restart_option )
Write the requested restart_option to the the system.conf file. This value \
is the last step attempted by the calling script.
Returns:
=cut
my $restart_option = shift;
if ( $restart_option !~ m/^\d+/x ) {
sys_die( 'Restart option is not numeric', 0 );
return 1;
}
my $rtconf = $path_conf_dir.'/'.$jobname.'.running';
my $conf = new Config::IniFiles( -file => $rtconf );
unless ( defined $conf ) { sys_die( "Error opening runtime jobconf file", 0 ); }
$conf->setval( 'restart', 'restart', $restart_option );
$conf->RewriteConfig;
return 0;
}
sub sys_set_verbose {
=begin wiki
!3 sys_set_verbose
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
$opt_verbose = 1;
return 0;
}
sub sys_set_errorlevel {
=begin wiki
!3 sys_set_errorlevel
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $errlvl = shift;
if ( $errlvl !~ /^\d+$/ ) {
sys_die( "Invalid value passed to sys_set_errorlevel()" );
}
my $save_errlvl = $errorlevel;
$errorlevel = $errlvl;
return $save_errlvl;
}
sub sys_set_warn {
=begin wiki
!3 sys_set_warn
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
$errorlevel = $RC_WARN;
return $RC_WARN;
}
sub sys_set_die {
=begin wiki
!3 sys_set_die
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
$errorlevel = $RC_FATAL;
return $RC_FATAL;
}
sub sys_set_email_levels {
=begin wiki
!3 sys_set_email_levels
Parameters: ( email_levels )
Accept a comma delimited list of message levels to use as the source for \
determing which message levels will generate a notification, and which \
message levels will be ignored when email notification is invoked.
Valid values for the list are: FATAL,ERROR,WARN,INFO,DEBUG,NONE
Returns:
=cut
my $email_levels = shift || "FATAL";
$mail_email_levels = _sys_check_severity_levels( $email_levels );
return $mail_email_levels;
}
sub sys_set_pager_levels {
=begin wiki
!3 sys_set_pager_levels
Parameters: ( pager_levels )
Accept a comma delimited list of message levels to use as the source for \
determing which message levels will generate a notification, and which \
message levels will be ignored when pager notification is invoked.
Valid values for the list are: FATAL,ERROR,WARN,INFO,DEBUG,NONE
Returns:
=cut
my $pager_levels = shift || "FATAL";
$mail_pager_levels = _sys_check_severity_levels( $pager_levels );
return $mail_pager_levels;
}
sub sys_set_mail_emailto {
=begin wiki
!3 sys_set_mail_emailto
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $new_emailto = shift;
my $old_emailto = $mail_emailto;
$mail_emailto = $new_emailto;
return $old_emailto;
}
sub sys_set_logging_levels {
=begin wiki
!3 sys_set_logging_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $logging_levels = shift || "FATAL,ERROR,WARN,INFO";
$log_logging_levels = _sys_check_severity_levels( $logging_levels );
return $log_logging_levels;
}
sub sys_set_console_levels {
=begin wiki
!3 sys_set_console_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $console_levels = shift || "FATAL,ERROR,WARN,INFO";
$log_console_levels = _sys_check_severity_levels( $console_levels );
return $log_console_levels;
}
sub sys_set_script_file {
=begin wiki
!3 sys_set_script_file
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $file = shift || $script_file;
$script_file = $file;
return $script_file;
}
sub sys_set_conf_file {
=begin wiki
Parameters: ( jobconf )
Manage the job conf file.
Set the value of the job conf filename and read the corresponding file. If \
no job conf filename is given, set the job conf filename back to the default \
value and reread the default job conf file (perform a reset).
Returns:
=cut
my $jobconf = shift || '';
if ( $jobconf ) {
## change jobconf file and read
$sys_jobconf_file = $jobconf . '.conf';
_sys_read_conf( $sys_jobconf_file ); ## tie %conf_job to job conf file
_sys_read_job(); ## read job specific settings from %conf_job
} else {
## reset jobconf file to default and reread
$sys_jobconf_file = _sys_check_de_override( $jobname . '.conf' );
_sys_read_conf( $sys_jobconf_file ); ## tie %conf_job to job conf file
_sys_read_job(); ## read job specific settings from %conf_job
}
return 0;
}
sub sys_set_path_log_dir {
=begin wiki
!3 sys_set_path_log_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $path = shift || $path_log_dir;
$path_log_dir = $path;
return $path_log_dir;
}
sub sys_set_path_plugin_dir {
=begin wiki
!3 sys_set_path_plugin_dir
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $path = shift || $path_plugin_dir;
$path_plugin_dir = $path;
return $path_plugin_dir;
}
sub sys_set_maxval {
=begin wiki
!3 sys_set_maxval
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($key, $val) = @_;
if ( $maxval{$key} ) {
if ( $val > $maxval{$key} ) {
$maxval{$key} = $val;
}
return $val;
}
$maxval{$key} = $val;
return $val;
}
sub sys_check_dataenvr {
=begin wiki
!3 sys_check_dataenvr
Parameters:
/data_envrs/ = dataenvrs to check
Accept either a dataenvr or a ref to an array of dataenvrs. If \
/data_envrs/ contains the current dataenvr, return true, otherwise return \
false.
Returns:
=cut
my $data_envrs = shift;
my @check_envrs;
if ( ref $data_envrs eq 'ARRAY' ) {
@check_envrs = map { $_ } @{$data_envrs};
} else {
push @check_envrs, $data_envrs; ## single entry
}
## is current data environment in the list of acceptable environments
if ( grep { $_ eq $dataenvr } @check_envrs ) {
return 1;
}
return 0;
}
sub sys_disp_doc {
=begin wiki
!3 sys_disp_doc
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
return _sys_disp_doc();
}
sub sys_timer {
=begin wiki
!3 sys_timer
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($opt, $timer_name) = @_;
$timer_name = 't1' unless $timer_name;
if ( $opt =~ m/start/ix ) {
$timers{$timer_name.'_start'} = time;
return $timers{$timer_name.'_start'};
}
if ( $opt =~ m/stop/ix ) {
$timers{$timer_name.'_stop'} = time;
return $timers{$timer_name.'_stop'};
}
if ( $opt =~ m/elapsed/ix ) {
my $estart = $timers{$timer_name.'_start'};
my $estop = $timers{$timer_name.'_stop'};
my $eelapsed = $estop - $estart;
my $ehours = int $eelapsed / 3600;
my $emins = int $eelapsed / 60 % 60;
my $esecs = int $eelapsed % 60;
return sprintf "%02d:%02d:%02d", $ehours, $emins, $esecs;
}
if ( $opt =~ /elapsed_seconds/i ) {
my $sstart = $timers{$timer_name.'_start'};
my $sstop = $timers{$timer_name.'_stop'};
my $selapsed = $sstop - $sstart;
return $selapsed;
}
return 'TIMER ERROR';
}
sub sys_wait {
=begin wiki
!3 sys_wait
Parameters: ( $action, $minutes )
$action can be either:
* 'init' - initialize wait's start time and elapsed time
* 'wait' - wait until $minutes has elapsed since start time
Example:
% language=Perl
% sys_wait( 'init', 3 );
% ... do some work
% sys_wait( 'wait' );
%%
Returns:
=cut
my ($action, $minutes) = @_;
if ( $action =~ /^init$/i ) {
$wt_start = time;
$wt_seconds = 0;
return 0 unless $minutes =~ /^\d+$/;
$wt_seconds = $minutes * 60;
}
if ( $action =~ /^wait$/i ) {
while ( 1 ) {
my $currtime = time;
my $elapsedt = $currtime - $wt_start;
log_info( "Waiting $wt_seconds, Elapsed: $elapsedt" );
if ( ($currtime - $wt_start) < $wt_seconds ) {
sleep 10;
} else {
last;
}
}
}
return 0;
}
=begin wiki
!2 Logging Functions
These functions provide logging and notification capabilities.
=cut
sub log_fatal {
=begin wiki
!3 log_fatal
Parameters: ( message )
Call lower level logging functions using severity level FATAL.
Returns:
=cut
my ($message, $extmsg) = @_;
$errorlevel = $RC_FATAL;
_log_write_to_log( 'FATAL', 0, $message, $extmsg);
_log_write_to_screen( 'FATAL', 0, $message, $extmsg);
return $errorlevel;
}
sub log_error {
=begin wiki
!3 log_error
Parameters: ( message )
Call lower level logging functions using severity level ERROR.
Returns:
=cut
my ($message, $extmsg) = @_;
$errorlevel = $RC_ERROR;
_log_write_to_log( 'ERROR', 0, $message, $extmsg);
_log_write_to_screen( 'ERROR', 0, $message, $extmsg);
return $errorlevel;
}
sub log_warn {
=begin wiki
!3 log_warn
Parameters: ( message )
Call lower level logging functions using severity level WARN.
Returns:
=cut
my ($message, $extmsg) = @_;
$errorlevel = $RC_WARN;
_log_write_to_log( 'WARN', 0, $message, $extmsg);
_log_write_to_screen( 'WARN', 0, $message, $extmsg);
return $errorlevel;
}
sub log_info {
=begin wiki
!3 log_info
Parameters: ( message )
Call lower level logging functions using severity level INFO.
Returns:
=cut
my ($message, $extmsg, $nolog) = @_;
$nolog = 0 unless $nolog;
return 0 if $nolog;
_log_write_to_log( 'INFO', 0, $message, $extmsg);
_log_write_to_screen( 'INFO', 0, $message, $extmsg);
return 0;
}
sub log_debug {
=begin wiki
!3 log_debug
Parameters: ( message )
Call lower level logging functions using severity level DEBUG.
Returns:
=cut
my ($message, $extmsg) = @_;
_log_write_to_log( 'DEBUG', 0, $message, $extmsg);
_log_write_to_screen( 'DEBUG', 0, $message, $extmsg);
return 0;
}
sub log_close {
=begin wiki
!3 log_close
Parameters: ( message )
Close the currently open log file.
Returns: 0
=cut
my ($message, $extmsg) = @_;
_log_write_to_log( 'INFO', 0, $message, $extmsg);
_log_write_to_screen( 'INFO', 0, $message, $extmsg);
$sys_log_open = 0;
return 0;
}
sub log_write_screen {
=begin wiki
!3 log_write_screen
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $message = shift;
_log_write_to_screen( 'INFO', 1, $message);
return 0;
}
sub log_write_log {
=begin wiki
!3 log_write_log
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $message = shift;
_log_write_to_log( 'INFO', 1, $message);
return 0;
}
=begin wiki
!2 Database Functions
These functions provide the database interface and data manipulation \
capabilities.
=cut
sub db_init {
=begin wiki
!3 db_init
Parameters: ( )
User interface to settings used by the various db functions. Requested \
settings are validated against those held in the db_func_parmas hash.
Returns:
=cut
my ($id, %params) = @_;
if ( ! defined $db_func_params{$id} ) {
sys_die( "Param $id to db_init is invalid")
}
foreach my $key ( keys %params ) {
if ( ! defined $db_func_params{$id}{$key} ) {
sys_die( "Param $key to db_init is invalid" );
}
$db_func_params{$id}{$key} = $params{$key};
}
return 0;
}
sub db_connect {
=begin wiki
!3 db_connect
Parameters: ( vdn )
This function accepts a virtual database name and makes a connection to the \
database resource identified by that name. The desired database instance has \
already been determined and stored before this function is called.
This function sets the DBI tracing mode so that we have a dbitrace.log file \
with pertinent history in it. This file will get large, so it should be \
rotated frequently. Contrary to what I've read, this does not supress \
output to STDERR. It appears that this just forces DBI to write errors to \
both STDERR and the dbitrace file. To fix that, this function redirects \
STDERR to /dev/null. This is an ugly hack. So until I can figure out if I \
read the docs wrong, or if DBI is just broken in this regard, I need to \
leave this to prevent garbage output. It's garbage because I always check \
and log DBI errors anyway.
Returns:
=cut
my ($vdn, %connect_params) = @_;
my ($starttime, $dbh, $instance);
## time increment is secs, action is either 'run' or 'fail'
my $dependent_jobname = $connect_params{dependent_jobname} || '';
my $wait_duration = $connect_params{wait_duration} || 60;
my $wait_max_secs = $connect_params{wait_max_secs} || 60*60;
my $wait_action = $connect_params{wait_action} || 'fail';
my $retry_duration = $connect_params{retry_duration} || 0;
my $retry_max_secs = $connect_params{retry_max_secs} || 0;
if ( $vdn =~ m/:/x ) { ## vdn contains instance definiton
my ($db, $inst) = split m/:/, $vdn;
_check_array_val( $db, \@databases )
|| sys_die( "Invalid database: [$db]", 0 );
_check_array_val( $inst, [split m/,/, $dbinst{$db}] )
|| sys_die( "Invalid database instance: [$db.$inst]", 0 );
$dbdefenvr{$db} = $inst; ## update default connection data
$vdn = $db; ## vdn gets true vdn
}
## check for dependent job
_db_connect_check_dependent(
$dependent_jobname, $wait_duration, $wait_max_secs, $wait_action
);
## get database parameters
my ($db, $un, $pw) = _db_vdn('connect', $vdn);
DBI->trace( 1, $dbitrace_filefull );
open STDERR, '>', '/dev/null' unless $opt_very_verbose;
## connect with retry
$dbh = _db_connect_retry(
$db, $un, $pw, $retry_duration, $retry_max_secs
);
## connection established
$dbhandles{$vdn}{'dbh'} = $dbh; ## store handle for cleanup on exit
db_nil( $vdn );
return 0;
}
sub db_nil {
=begin wiki
!3 db_nil
Parameters: ( )
This is just a convenience function. When running in test mode, this will \
call the internal C<_db_vdn> to function for force closure of all database \
connections immediately.
Returns:
=cut
my $vdn = shift;
my ($dbh, $sth) = _db_vdn( 'nil', $vdn);
return 0;
}
sub db_disconnect {
=begin wiki
!3 db_disconnect
Parameters: ( vdn )
Accept a virtual database name and disconnect from the datatabase specified \
by the virtual database name.
Returns:
=cut
my $vdn = shift;
my ($dbh, $sth) = _db_vdn( 'disconnect', $vdn);
if ( $dbh ) {
$dbh->disconnect;
if ( DBI->errstr ) {
log_warn( DBI->errstr );
return 1;
}
}
$dbhandles{$vdn}{'dbh'} = 0;
return 0;
}
sub db_finish {
=begin wiki
!3 db_finish
Parameters: ( vdn )
Accept a virtual database name and close the current statement handle for \
the database specified by the virtual database name.
Returns:
=cut
my $vdn = shift;
my ($dbh, $sth) = _db_vdn( 'finish', $vdn);
if ( $sth ) {
$sth->finish;
if ( DBI->errstr ) {
log_warn( DBI->errstr );
return 1;
}
}
$dbhandles{$vdn}{'sth'} = 0;
return 0;
}
sub db_prepare {
=begin wiki
!3 db_prepare
Parameters: ( vdn, sql_query )
Accept a virtual database name and an sql query and prepares the query for \
database processing. This function stores the resulting statement handle for \
subsequent access under the via the virtual database name.
Returns:
=cut
my ($vdn, $sql, $longrlen) = @_;
$longrlen = 0 unless $longrlen;
my $sth_name = 'sth_default'; ## default statement handle name
if ( $vdn =~ m/\./x ) {
($vdn, $sth_name) = split m/\./x, $vdn;
if ( $sth_name eq 'sth_default' ) {
sys_die( 'Invalid statement handle name', 0 );
}
}
my ($dbh, $sth) = _db_vdn('prepare', $vdn);
if ( $longrlen > 0 ) { $dbh->{LongReadLen} = $longrlen; }
$sth = $dbh->prepare( $sql )
or sys_die( $dbh->errstr );
## store statement handle for this vdn
$dbhandles{$vdn}{$sth_name} = $sth;
return 0;
}
sub db_truncate {
=begin wiki
!3 db_truncate
Parameters: ( vdn, table_name )
Accept a virtual database name and a table name. Truncate the specified \
table. This function returns number of rows truncated.
Returns:
=cut
my ($vdn, $table_name) = @_;
my ($dbh, $sth) = _db_vdn('truncate', $vdn);
my $sql = "truncate table $table_name";
$dbh->do( $sql )
or sys_die( DBI->errstr );
return 0;
}
sub db_execute {
=begin wiki
!3 db_execute
Parameters: ( vdn, sql_substitution_paramaters )
Accept a virtual database name and sql substitution parameters. Execute \
the query against the stored statement handle associated with the supplied \
virtual database name. The statement handle needs to be prepard before this \
function is called.
Returns:
=cut
my ($vdn, @params) = @_;
my ($dbh, $sth) = _db_vdn('execute', $vdn);
$sth->execute( @params )
or sys_die( $sth->errstr );
return 0;
}
sub db_get_sth {
=begin wiki
!3 db_get_sth
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $vdn = shift;
my $sth_name = 'sth_default'; ## default statement handle name
if ( $vdn =~ m/\./x ) {
($vdn, $sth_name) = split m/\./x, $vdn;
}
return $dbhandles{$vdn}{$sth_name};
}
sub db_get_defenvr {
=begin wiki
!3 db_get_defenvr
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $vdn = shift;
if ( $dbdefenvr{$vdn} ) {
return $dbdefenvr{$vdn};
}
return '';
}
sub db_bindcols {
=begin wiki
!3 db_bindcols
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
#
# interface:
# interface to sth->bind_columns()
#
# accepts:
# 1st position
# a raw statement handle
# a vdn which is used to obtain a default statment handle (one per vdn)
# a vdn, named statement handle pair in the form vdn||nsth
# remaining
# any number of references to scalars
#
# returns:
# 0 = success
# errors handled internally
#
my ($vdn,@colrefs) = @_;
my $sth;
if ( ref $vdn ) {
$sth = $vdn; ## received a raw sth
} else {
my $sth_name = 'sth_default'; ## default statement handle name
if ( $vdn =~ m/\./x ) { ## dot notation vdn.sthn
($vdn, $sth_name) = split m/\./x, $vdn;
}
$sth = $dbhandles{$vdn}{$sth_name};
}
foreach my $colref ( @colrefs ) {
if ( ! ref $colref ) { sys_die( "Received bad ref in db_bindcols" ); }
}
$sth->bind_columns( @colrefs );
return 0;
}
sub db_pef {
=begin wiki
!3 db_pef
Parameters: ( )
Prepare, Execute, Fetch a scalar value
This function always returns the first element of the first row of the
result set.
Returns:
=cut
my ($vdn, $sqlname, @params) = @_;
my $sql = sys_get_sql( $sqlname );
db_prepare( $vdn, $sql );
db_execute( $vdn, @params );
my $row = db_fetchrow( $vdn );
return @{$row}[0];
}
sub db_pef_list {
=begin wiki
!3 db_pef_list
Parameters: ( )
Prepare, Execute, Fetch a result set as a list of scalars
This function returns a list of the first element from each row of the \
result set.
Returns:
=cut
my ($vdn, $sqlname, @params) = @_;
my @rsalist;
my $sql = sys_get_sql( $sqlname );
db_prepare( $vdn, $sql );
db_execute( $vdn, @params );
while ( my $row = db_fetchrow( $vdn ) ) {
push @rsalist, @{$row}[0];
}
return \@rsalist; ## return result set asa list
}
sub db_fetchrow {
=begin wiki
!3 db_fetchrow
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
#
# interface:
# interface to sth->fetchrow_arrayref()
#
# accepts:
# a raw statement handle
# a vdn which is used to obtain a default statment handle (one per vdn)
# a vdn, named statement handle pair in the form vdn||nsth
#
# note:
# If you are going to make lots of calls to db_fetchrow for the
# same execute cycle, you will get better performance using a raw
# statement handle over a statement handle name
#
# returns:
# reference to an array
#
my $vdn = shift;
my $sth;
if ( ref $vdn ) {
$sth = $vdn; ## received a raw sth
} else {
my $sth_name = 'sth_default'; ## default statement handle name
if ( $vdn =~ m/\./x ) {
($vdn, $sth_name) = split m/\./x, $vdn;
}
$sth = $dbhandles{$vdn}{$sth_name};
}
return $sth->fetchrow_arrayref();
}
sub db_commit {
=begin wiki
!3 db_commit
Parameters: ( virtual_database_name )
Accept a virtual database name and perform a commit against the specified \
database connection.
Returns:
=cut
my ($vdn) = shift;
my ($dbh, $sth) = _db_vdn('commit', $vdn);
$dbh->commit;
if ( DBI->errstr ) {
sys_die( DBI->errstr );
return 1; ## test harness returns from sys_die
}
return 0;
}
sub db_rollback {
=begin wiki
!3 db_rollback
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($vdn) = shift;
my ($dbh, $sth) = _db_vdn('rollback', $vdn);
$dbh->rollback;
if ( DBI->errstr ) {
sys_die( DBI->errstr );
return 1; ## test harness returns from sys_die
}
return 0;
}
sub db_rowcount_table {
=begin wiki
!3 db_rowcount_table
Parameters: ( vdn, table_name )
Accept a virtual database name and a tablename and using the table name, \
do a select count(*) query against that table to get the current rowcount.
Returns:
=cut
my ($vdn, $table_name) = @_;
my ($dbh, $sth) = _db_vdn('rowcount_table', $vdn);
my $sql = "select count(*) from $table_name";
my $count = $dbh->selectrow_array( $sql );
return $count;
}
sub db_rowcount_query {
=begin wiki
!3 db_rowcount_query
Parameters: ( vdn, sql )
Using a supplied query that does a select count(*), get a row count. This \
function will accept optional params for the query.
Returns:
=cut
my ($vdn, $sql, @params ) = @_;
my ($dbh, $sth) = _db_vdn('rowcount_query', $vdn);
if ( @params ) {
my $tmp_sth = $dbh->prepare( $sql )
or sys_die( $dbh->errstr );
$tmp_sth->execute( @params )
or sys_die( $sth->errstr );
my @row = $tmp_sth->fetchrow_array();
return $row[0];
} else {
my $count = $dbh->selectrow_array( $sql );
return $count;
}
}
sub db_sanity_check {
=begin wiki
!3 db_sanity_check
Parameters: ( vdn, query_name, notify )
/vdn/ - virtual database name
/query_name/ - name of query in job conf file
/notify/ - send notification on warning
Verify that table contents are within acceptable range limits for a given \
column value.
This function utilizes information stored in the current job conf file. The \
query executed to perform each range limit test is passed as a parameter in \
/query_name/. That query is executed for each test stored in the \
"checkpoints" array in conf section "thereshold" in the job conf file.
A checkpoints array should be defined for each database environment. This \
function will look for a checkpoints by database environment by combining \
the name of the current database environment with the liter string \
"checkpoints". If you have four database environments, you should have \
four checkpoint entries in your job conf file. The name of the current \
database environment is determined using the function sys_get_dataenvr().
Once the range limit query and all of the checkpoint values have been \
obtained, the parameter vdn is used to execute the range limit query.
Each checkpoint entry takes the form:
COLUMN_VALUE = count:percent_deviation
The range limit query will be executed for each COLUMN_VALUE entry. The \
actual count returned will be compared to the checkpoint count, if the \
count returned is within the percent range specified by the checkpoint \
percent_deviation, the test will pass, otherwise the test will fail and a \
log warning will be generated.
A percent_deviation of 0 (zero) represents a special case. If a \
percent_deviation of 0 is used, this instructs db_sanity_check to accept \
any positive value for count as a valid value. Typically, this behavior \
would be invoked by using a column value entry of "1:0".
An expected value of 0 (zero) represents a special case as well. When the \
expected value is 0, checking for that column value will be bypassed. In \
this way you can "turn off" sanity checking for an entire database \
environment by making all of the column value entries equal to "0:0".
If the /notify/ parameter is set, a notification will be sent in addition \
to a log warning.
Returns:
=cut
my ($vdn, $query_name, $notify) = @_;
$notify = 0 unless $notify;
my $warnings = 0;
my $lead = "Sanity check:";
my $okay = " Ok ";
my $outofbounds = " Out Of Bounds ";
my $disabled = " Disabled ";
## get checkpoints
my $checkpoints;
my $conf_entry = sys_get_dataenvr . '_checkpoints';
if ( $conf_job{threshold}{$conf_entry} ) {
$checkpoints = $conf_job{threshold}{$conf_entry};
} else {
log_warn( "No threshold checkpoints found in job conf for: $conf_entry" );
return 1;
}
## prepare range limit query
my $query = sys_get_sql( $query_name );
db_prepare( $vdn, $query );
log_info( "$lead Status [Test] Expected/Actual/Threshold(%)/Threshold(#)" );
## perform checkpoint tests
foreach my $chkpt ( split "\n", $checkpoints ) {
my ($param,$rest) = split m/=/, $chkpt;
my ($exp,$range) = split m/:/, $rest;
$param = _trim($param); ## col to check
$exp = _trim($exp); ## expected value
$range = _trim($range); ## range/tolerance
db_execute( $vdn, $param );
my $row = db_fetchrow( $vdn );
my $act = @{$row}[0]; ## actual value
my $dev = int $exp * ( $range / 100 ); ## deviation as a percent
my $status = "[$param] $exp/$act/$range/$dev ";
if ( $exp == 0 ) { ## checking has been disabled
log_info( $lead . $disabled . $status );
next;
}
if ( $range == 0 ) { ## any positive value for actual is acceptable
if ( $act > 0 ) {
log_info( $lead . $okay . $status );
next;
}
$warnings++;
log_info( $lead . $outofbounds . $status );
next;
}
if ( $act < $exp ) { ## actual is below threshold
if ( $act < $exp - $dev ) {
log_info( $lead . $outofbounds . $status );
$warnings++;
next;
}
}
if ( $act > $exp ) { ## actual is above threshold
if ( $act > $exp + $dev ) {
log_info( $lead . $outofbounds . $status );
$warnings++;
next;
}
}
log_info( $lead . $okay . $status );
}
## send out notifications if there are warnings
if ( $warnings && $notify ) {
_log_send_notifications( "WARN", 1, "Sanity check threshold exceeded" );
}
return 0;
}
sub db_drop_index {
=begin wiki
!3 db_drop_index
Parameters: ( vdn, index_name )
Accept a virtual database name and an index name. Drop the index identified \
by index name. If there was a database error, we check last error. If the \
last error indicates that the index we are trying to drop did not exist, \
then the error is ignored, otherwise the error is logged.
Returns:
=cut
my ($vdn, $index_name) = @_;
my ($dbh, $sth) = _db_vdn('drop_index', $vdn);
my $tmp_sth = $dbh->prepare("drop index $index_name")
or sys_die( DBI->errstr );
$tmp_sth->execute;
if ( DBI->err && DBI->err != 1418 ) { ## ORA-00942: specified index does not exist
sys_die( DBI->errstr );
}
return 0;
}
sub db_drop_table {
=begin wiki
!3 db_drop_table
Parameters: ( vdn, table_name )
Accept a virtual database name and a table name. Drop the table identified \
by table name. If there was a database error, we check last error. If the \
last error indicates that the table we are trying to drop did not exist, \
then the error is ignored, otherwise the error is logged.
Returns:
=cut
my ($vdn, $table_name) = @_;
my ($dbh, $sth) = _db_vdn('drop_table', $vdn);
my $tmp_sth = $dbh->prepare("drop table $table_name" )
or sys_die( DBI->errstr );
$tmp_sth->execute;
if ( DBI->err && DBI->err != 942 ) { ## ORA-00942: specified table does not exist
sys_die( DBI->errstr );
}
$tmp_sth->finish;
return 0;
}
sub db_drop_procedure {
=begin wiki
!3 db_drop_procedure
Parameters: ( vdn, procedure_name )
Accept a virtual database name and a procedure name. Drop the procedure \
identified by the given name. Check the last error, if it indicates the \
procedure did not exist, then the error is ignored, otherwise the error is \
logged.
Returns:
=cut
my ($vdn, $procedure_name) = @_;
my ($dbh, $sth) = _db_vdn('drop_procedure', $vdn);
my $tmp_sth = $dbh->prepare("drop procedure $procedure_name")
or sys_die( DBI->errstr );
$tmp_sth->execute;
if ( DBI->err && DBI->err != 4043 ) { ## ORA-04043: object does not exist
sys_die( DBI->errstr );
}
$tmp_sth->finish;
return 0;
}
sub db_drop_function {
=begin wiki
!3 db_drop_function
Parameters: ( $vdn, $function_name )
Accept a virtual database name and a function name. Drop the function \
identified by the given name. Check the last error, if it indicates the \
function did not exist, then the error is ignored, otherwise the error is \
logged.
Returns:
=cut
my ($vdn, $function_name) = @_;
my ($dbh, $sth) = _db_vdn('drop_function', $vdn);
my $tmp_sth = $dbh->prepare("drop function $function_name")
or sys_die( DBI->errstr );
$tmp_sth->execute;
if ( DBI->err && DBI->err != 4043 ) { ## ORA-04043: object does not exist
sys_die( DBI->errstr );
}
$tmp_sth->finish;
return 0;
}
sub db_drop_package {
=begin wiki
!3 db_drop_package
Parameters: ( vdn, package_name )
Accept a virtual database name and a package name. Drop the package \
identified by the given name. Check the last error, if it indicates \
that the the package we are trying to drop did not exist, then the error \
is ignored, otherwise the error is logged.
Returns:
=cut
my ($vdn, $package_name) = @_;
my ($dbh, $sth) = _db_vdn('drop_package', $vdn);
my $tmp_sth = $dbh->prepare("drop package $package_name")
or sys_die( DBI->errstr );
$tmp_sth->execute;
if ( DBI->err && DBI->err != 4043 ) { ## ORA-04043: object does not exist
sys_die( DBI->errstr );
}
$tmp_sth->finish;
return 0;
}
sub db_rename_index {
=begin wiki
!3 db_rename_index
Parameters: ( vdn, oldndxname, newndxname )
Please write the documentation.
Returns:
=cut
my ($vdn, $oldname, $newname) = @_;
my ($dbh, $sth) = _db_vdn('rename_index', $vdn);
my $tmp_sth = $dbh->prepare("alter index $oldname rename to $newname")
or sys_die( DBI->errstr );
$tmp_sth->execute;
if ( DBI->err ) {
sys_die( DBI->errstr );
}
return 0;
}
sub db_rename_table {
=begin wiki
!3 db_rename_table
Parameters: ( vdn, oldtabname, newtabname )
Please write this documentation.
Returns:
=cut
my ($vdn, $oldname, $newname) = @_;
my ($dbh, $sth) = _db_vdn('rename_table', $vdn);
my $tmp_sth = $dbh->prepare("alter table $oldname rename to $newname" )
or sys_die( DBI->errstr );
$tmp_sth->execute;
if ( DBI->err ) {
sys_die( DBI->errstr );
}
$tmp_sth->finish;
return 0;
}
sub db_purge_table {
=begin wiki
!3 db_purge_table
Parameters: ( vdn, table_name )
Please write this documentations.
Returns:
=cut
my ($vdn, $table_name) = @_;
my ($dbh, $sth) = _db_vdn('purge_table', $vdn);
my $tmp_sth = $dbh->prepare("purge table $table_name" )
or sys_die( DBI->errstr );
$tmp_sth->execute;
if ( DBI->err && DBI->err != 38307 ) { ## ORA-38307: object not in recycle bin
sys_die( DBI->errstr );
}
$tmp_sth->finish;
return 0;
}
sub db_purge_index {
=begin wiki
!3 db_purge_index
Parameters: ( vdn, index_name )
Please write this documentation.
Returns:
=cut
my ($vdn, $table_name) = @_;
my ($dbh, $sth) = _db_vdn('purge_index', $vdn);
my $tmp_sth = $dbh->prepare("purge index $table_name")
or sys_die( DBI->errstr );
$tmp_sth->execute;
if ( DBI->err && DBI->err != 38307 ) { ## ORA-38307: object not in recycle bin
sys_die( DBI->errstr );
}
return 0;
}
sub db_grant {
=begin wiki
!3 db_grant
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($vdn, $priv, $objname, $ag) = @_;
my ($dbh, $sth) = _db_vdn('grant', $vdn);
unless ( $priv =~ m/^r$|^u$/x ) {
log_warn( "Privilege to db_grant must be either 'r' or 'u'" );
return 1;
}
my $sql;
if ( $priv eq 'r' ) {
$sql = qq{begin execute immediate 'grant select on $objname to $ag'; end;};
}
if ( $priv eq 'u' ) {
$sql = qq{begin execute immediate 'grant update, insert, delete on $objname to $ag'; end;};
}
my $tmp_sth = $dbh->prepare( $sql )
or sys_die( DBI->errstr );
$tmp_sth->execute
or sys_die( DBI->errstr );
$tmp_sth->finish;
return 0;
}
sub db_update_statistics {
=begin wiki
!3 db_update_statistics
Parameters: ( vdn, table_name )
Please write this documentation.
Returns:
=cut
my ($vdn, $table_name) = @_;
my ($dbh, $sth) = _db_vdn('update_statistics', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in db_update_statistics', 0 );
}
my $sql = "BEGIN dbms_stats.gather_table_stats('','"
. "$table_name',NULL,NULL,FALSE,'FOR ALL COLUMNS SIZE 1'"
. ",NULL,'DEFAULT',TRUE); END;";
my $tmp_sth = $dbh->prepare( $sql );
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->execute;
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->finish;
return 0;
}
sub db_insert_from_file {
=begin wiki
!3 db_insert_from_file
Parameters: ( vdn, file_name, delim )
* /vdn/ - Virtual Database Name
* /file_name/ - File containing data to read
* /delim/ - Field delimiter (can be a regex)
Accept a virtual database name, file name, and field delimiter. Insert records \
from specified file into the database table using the statement handle tied \
to the virtual database name. The file name should include full path \
information.
It is desireable to call db_init before using this function. There are several \
advanced options implemented by this function that can be configured by call \
db_init first.
By default the field delimiter is not interpreted as a Regular Expression, \
however by calling db_init first, you can make this function treat your \
delimiter as a regex, in that case the delimiter can be more than one \
character in length.
SQL used by this function should be prepared before calling this function.
Returns:
=cut
my ($vdn, $file_name, $delim) = @_;
my ($dbh, $sth) = _db_vdn('insert_from_file', $vdn);
my $id = 'db_insert_from_file';
my $TrimLead = _is_yes($db_func_params{$id}{'TrimLead'});
my $TrimFieldLead = _is_yes($db_func_params{$id}{'TrimFieldLead'});
my $TrimFieldTrail = _is_yes($db_func_params{$id}{'TrimFieldTrail'});
my $SkipComments = _is_yes($db_func_params{$id}{'SkipComments'});
my $SkipLastField = _is_yes($db_func_params{$id}{'SkipLastField'});
my $UseRegex = _is_yes($db_func_params{$id}{'UseRegex'});
my $CommentChar = $db_func_params{$id}{'CommentChar'};
my ($count, @row);
open my $fh, "<", $file_name or sys_die( "Error opening $file_name" );
my $regex = "\Q$delim\E"; # escape regex meta chars
if ( $UseRegex ) {
$regex = $delim; # do escaping meta chars
}
while ( <$fh> ) {
my $line = $_;
chomp $line;
if ( $TrimLead ) {
$line = _trim_lead($line);
}
if ( $SkipComments ) {
if ( substr($line,0,1) eq $CommentChar ) { next; }
}
@row = split($regex,$line,-1); # -1 preserves trailing null fields
if ( $SkipLastField ){
pop @row;
}
if ( $TrimFieldLead ) {
for (my $i=0;$i<@row;$i++) {
$row[$i]=_trim_lead($row[$i]);
}
}
if ( $TrimFieldTrail ) {
for (my $i=0;$i<@row;$i++) {
$row[$i]=_trim_trail($row[$i]);
}
}
$sth->execute( @row );
if ( DBI->errstr ) {
print DBI->errstr;
log_warn( DBI->errstr );
my $errrec = 'RECORD: ' . join "~", @row;
log_warn( $errrec );
sys_die( 'Aborting' );
}
$count++;
}
db_commit( $vdn );
close $fh or sys_die( "Error closing $file_name" );
return $count;
}
sub db_insert_from_query {
=begin wiki
!3 db_insert_from_query
Parameters: ( source_vdn, target_vdn )
Accept a virtual database name for a source and target databases and insert \
rows into the target database from the source database.
Note: This needs to be rewritten to use fetchrow_arrayref() instead for \
better performance.
Returns:
=cut
my ($src_vdn, $des_vdn, $plugin) = @_;
$plugin = 0 unless $plugin;
## set up array of plugins
my @plugins;
if ( ref $plugin eq 'ARRAY' ) {
@plugins = map { $_ } @{$plugin}; ## copy plugin list to plugin array
} else {
push @plugins, $plugin; ## copy single plugin entry to plugin array
}
my ($src_dbh, $src_sth) = _db_vdn('insert_from_query', $src_vdn);
my ($des_dbh, $des_sth) = _db_vdn('insert_from_query', $des_vdn);
my $count = 0;
while ( my $row = $src_sth->fetchrow_arrayref() ) { ## fetch insert loop
my @tmprow = @{$row};
my $plugin_result = 0;
foreach my $plugin ( @plugins ) { ## call each plugin
my $result = $plugin->( \@tmprow ) if $plugin;
if ( $result > 1000 ) { $plugin_result = 1; } ## plugin bad return
}
next if $plugin_result; ## if any plugin complains, skip the record
$des_sth->execute( @tmprow );
if ( DBI->errstr ) {
log_warn( DBI->errstr );
my $errrec = 'RECORD: ' . join "~", @{$row};
log_warn( $errrec );
sys_die( 'Aborting' );
}
$count++;
}
return $count;
}
sub db_query_to_file {
=begin wiki
!3 db_query_to_file
Parameters: ( vdn, file_name, delim )
Accept a virtual database name and a file name and write the result set to \
the requested file. This function should be passed a file name that includes \
full path information. The specified delimiter is used as a field separator \
when writing the result set to the file.
Plugins
Plugins can be called for each row returned in the record set. Plugins can \
return a value, any value returned that is greater than 1000 will cause the \
current record to be skiped rather than written to the output file.
Returns:
=cut
my ($vdn, $file_name, $delim, $append, $plugin, $protect) = @_;
$delim = '~' unless $delim;
$append = 0 unless $append;
$plugin = 0 unless $plugin; ## unblessed ref to a plugin or ref to array
$protect = 0 unless $protect; ## ref to array of cols to protect
## set up array of plugins
my @plugins;
if ( ref $plugin eq 'ARRAY' ) {
@plugins = map { $_ } @{$plugin}; ## copy plugin list to plugin array
} else {
push @plugins, $plugin; ## copy single plugin entry to plugin array
}
my ($dbh, $sth) = _db_vdn('query_to_file', $vdn);
my $mode;
if ( $append ) {
$mode = '>>';
} else {
$mode = '>';
}
my $count = 0;
open my $fh, $mode, $file_name or sys_die( "Error opening $file_name" );
while ( my $row = $sth->fetchrow_arrayref() ) {
my @outrow = @{$row};
my $plugin_result = 0;
foreach my $plugin ( @plugins ) { ## call each plugin in turn
my $result = $plugin->( \@outrow ) if $plugin;
if ( $result > 1000 ) { $plugin_result = 1; } ## bypass this record
}
next if $plugin_result;
_db_query_to_file_protect( \@outrow, $protect ) if $protect;
print {$fh} join $delim, @outrow;
print {$fh} "\n";
$count++;
}
close $fh or sys_die( "Error closing $file_name" );
return $count;
}
sub db_dump_query {
=begin wiki
!3 db_dump_query
Parameters: ( vdn, columns )
Accept a virtual database name and a list of column names, dump the \
query showing column names and field values.
Returns:
=cut
my ($vdn, $cols) = @_;
my ($dbh, $sth) = _db_vdn('dump_query', $vdn);
while ( my @row = $sth->fetchrow_array() ) {
print "RECORD:\n";
for my $i ( 0 .. $#row ) {
print "\t", $cols->[$i], '=', _db_null( $row[$i] ), "\n";
}
}
return 0;
}
sub db_dump_table {
=begin wiki
!3 db_dump_table
Parameters: ( vdn, table_name, max_rows )
Accept a virtual database name and a table name, dump the contents of the \
requested table showing column names and field values. If optional paramater \
max rows is provided, query output will be limited to that many rows. There \
is an upper limit on the number of rows that this query will return, this \
is set rather high, so in most cases you should probably supply a max rows \
limit.
Returns:
=cut
my ($vdn, $table_name, $max_rows) = @_;
my ($dbh, $sth) = _db_vdn('dump_table', $vdn);
$max_rows = 999_999 unless defined $max_rows;
$table_name = uc $table_name;
my $col_sql = "select column_name " .
" from all_tab_columns " .
" where table_name = '$table_name'";
my ( $tmp_sth, @cols );
$tmp_sth = $dbh->prepare( $col_sql )
or sys_die( DBI->errstr );
$tmp_sth->execute
or sys_die( DBI->errstr );
while ( my @row = $tmp_sth->fetchrow_array() ) {
push @cols, $row[0];
}
$tmp_sth->finish;
my $columns = join ', ', @cols;
my $tab_sql = "select $columns " .
" from $table_name";
$tmp_sth = $dbh->prepare( $tab_sql )
or sys_die( DBI->errstr );
$tmp_sth->execute
or sys_die( DBI->errstr );
my $row_count = 0;
while ( my @row = $tmp_sth->fetchrow_array() ) {
print "RECORD:\n";
for my $i ( 0 .. $#row ) {
print "\t", $cols[$i], "=", _db_null( $row[$i] ), "\n";
}
last if ++$row_count >= $max_rows;
}
$tmp_sth->finish;
return 0;
}
sub db_sqlloader {
=begin wiki
!3 db_sqlloader
Parameters: ( vdn, datfile, ctlname, maxerrors )
* /vdn/ - Virtual Database Name
* /datfile/ - SQL*Loader data file
* /ctlname/ - Job conf key for control file input
* /maxerrors/ - Maximum number of errors allowed
This is a convenience function which provides a simplified method for calling \
the various db_sqlloader functions. This will invoke SQL*Loader and handle \
the various execution and output parsing that whould otherwise have to be \
handled by calling the db_sqlloader functions directly (which certainly you \
can if you prefer).
Execute SQL*Loader using the supplied paramaters. The Virtual Database \
Name is used to obtain login credentials. This will launch SQL*Loader \
and wait for it to finish, returning the SQL*Loader return code to the \
caller.
Data file name must be fully qualified. Path provided by data file name \
will be used for out, bad, and dis files.
Return: One of
* SQLLDR_SUCC
* SQLLDR_WARN
* SQLLDR_FAIL
=cut
my ($vdn, $datfile, $ctlname, $maxerrors) = @_;
my $id = 'db_sqlloader';
my $datfilepath = $db_func_params{$id}{DatFilePath};
my $dbenvr = $db_func_params{$id}{DbEnvr};
my $netservice = $db_func_params{$id}{NetService};
my $datfilefull = $datfilepath . $datfile;
my ($sqlldr_retcd, $sqlldr_result);
log_info( "Executing SQLLoader" );
if ( $dbenvr =~ /$netservice/ ) {
log_info( "Using netservice db connection symantics" );
$sqlldr_retcd = db_sqlloaderx( "$vdn:$dbenvr", $datfilefull, $ctlname, $maxerrors );
} else {
log_info( "Using local db connection symantics" );
$sqlldr_retcd = db_sqlloaderx( $vdn, $datfilefull, $ctlname, $maxerrors );
}
$sqlldr_result = db_sqlloaderx_parse_logfile( $datfilefull );
log_info( "SQLLoader Output:", $sqlldr_result );
if ( $sqlldr_retcd == $SQLLDR_SUCC ) {
log_info( "Load data file $datfile completed successfully" );
}
if ( $sqlldr_retcd == $SQLLDR_WARN ) {
log_warn( "Load data file $datfile completed with warnings" );
}
if ( $sqlldr_retcd == $SQLLDR_FTL || $sqlldr_retcd == $SQLLDR_FAIL ) {
$sqlldr_retcd = $SQLLDR_FAIL;
log_warn( "Load data file $datfile failed" );
}
my $rej_count = db_sqlloaderx_rejected();
if ( $rej_count > 0 ) {
log_warn( "SQLLoader rejected $rej_count records loading $datfile to " . sys_get_dbinst( $vdn ) );
}
if ( $rej_count > $maxerrors ) {
log_warn( "SQLLoader failed loading $datfile to " . sys_get_dbinst( $vdn ) . " due to max rejected records" );
}
return $sqlldr_retcd;
}
sub db_sqlloaderx {
=begin wiki
!3 db_sqlloaderx
See: db_sqlloader for Parameters and Return Values.
=cut
my ($vdn, $datfile, $ctlname, $maxerrors) = @_;
my $defenvr = $dbdefenvr{$vdn};
my $netservice = _db_netservice( $vdn );
my ($db, $un, $pw) = _db_vdn('connect', $vdn);
$maxerrors = $maxerrors || 50;
## validate the data file exists
if ( ! -e $datfile ) { sys_die( "Data file $datfile not found" ); }
## get control file input from job conf
my $key = $ctlname;
my $section = 'sqlloader';
if ( ! $conf_job{$section}{$key} ) {
$key = 'control_file:' . $key;
if ( ! $conf_job{$section}{$key} ) {
sys_die( "No loader definition found in [$section] for key [$ctlname]", 0 );
}
}
my $control = $conf_job{$section}{$key};
my ($base,$path,$type) = fileparse($datfile,qr{\.dat|\.txt});
my $ctlfile = $path.$base.'.ctl';
my $parfile = $path.$base.'.par';
my $badfile = $path.$base.'.bad';
my $disfile = $path.$base.'.dis';
my $outfile = $path.$base.'.out';
## build control file
open my $fh, ">", $ctlfile || sys_die( 'Unable to create SQLLoader ctlfile', 0 );
print $fh $control;
close $fh;
## build params file
open $fh, ">", $parfile || sys_die( 'Unable to create SQLLoader parfile', 0 );
print $fh "userid=$un/$pw$netservice\n";
print $fh "control=$ctlfile\n";
print $fh "silent=(all)\n";
print $fh "data=$datfile\n";
print $fh "log=$outfile\n";
print $fh "bad=$badfile\n";
print $fh "discard=$disfile\n";
close $fh;
my @args = ("sqlldr", "PARFILE=$parfile errors=$maxerrors");
system @args;
my $sqlldr_retcd = $CHILD_ERROR >> 8;
## Normalize os dependent return codes. Why Oracle returns an os dependent
## return code from a cross-platform product is a mystery to me...
if ( $OSNAME eq 'MSWin32' ) {
if ( $sqlldr_retcd == 3 ) { $sqlldr_retcd = 1; }
if ( $sqlldr_retcd == 4 ) { $sqlldr_retcd = 3; }
}
unlink $parfile;
unlink $ctlfile;
return $sqlldr_retcd;
}
sub db_sqlloaderx_parse_logfile {
=begin wiki
!3 db_sqlloaderx_parse_logfile
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $datfile = shift;
my ($base,$path,$type) = fileparse($datfile,qr{\.dat|\.txt});
my $outfile = $path.$base.'.out';
return _db_sqlloaderx_parse_logfile( $outfile );
}
sub db_sqlloaderx_skipped {
=begin wiki
!3 db_sqlloaderx_skipped
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( defined $sqlloader_results{'skipped'} ) {
return $sqlloader_results{'skipped'}
} else {
return -1;
}
}
sub db_sqlloaderx_read {
=begin wiki
!3 db_sqlloaderx_read
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( defined $sqlloader_results{'read'} ) {
return $sqlloader_results{'read'}
} else {
return -1;
}
}
sub db_sqlloaderx_rejected {
=begin wiki
!3 db_sqlloaderx_rejected
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( defined $sqlloader_results{'rejected'} ) {
return $sqlloader_results{'rejected'}
} else {
return -1;
}
}
sub db_sqlloaderx_discarded {
=begin wiki
!3 db_sqlloaderx_discarded
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( defined $sqlloader_results{'discarded'} ) {
return $sqlloader_results{'discarded'}
} else {
return -1;
}
}
sub db_sqlloaderx_elapsed_time {
=begin wiki
!3 db_sqlloaderx_elapsed_time
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( defined $sqlloader_results{'elapsed_time'} ) {
return $sqlloader_results{'elapsed_time'}
} else {
return 'error';
}
}
sub db_sqlloaderx_cpu_time {
=begin wiki
!3 db_sqlloaderx_cpu_time
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( defined $sqlloader_results{'cpu_time'} ) {
return $sqlloader_results{'cpu_time'}
} else {
return 'error';
}
}
sub db_func {
=begin wiki
!3 db_func
Parameters: ( )
This function executes an Oracle stored procedure that takes no input \
parameters and returns a result via RETURN. This interface is Oracle \
specific, so a check is performed to make sure that the supplied vdn is \
pointing to an Oracle database. If a database error is raised it is \
trapped and reported. The existing vdn statement handle is preserved.
Returns:
=cut
my ($vdn, $package, $proc_name) = @_;
my ($dbh, $sth) = _db_vdn('funcx', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in db_funcx' );
}
if ( $package ) { $proc_name = $package. '.' .$proc_name; }
my $sql = 'BEGIN :result := ' . $proc_name . '; END;';
my $result;
my $tmp_sth = $dbh->prepare( $sql );
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->bind_param_inout( ':result', \$result, 100 );
$tmp_sth->execute;
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->finish;
return $result;
}
sub db_proc {
=begin wiki
!3 db_proc
Parameters: ( vdn, package, proc_name )
This function executes an Oracle stored procedure that takes no input \
parameters and returns no output. This interface is Oracle specific, so a \
check is performed to make sure that the supplied vdn is pointing to an \
Oracle database. If a database error is raised it is trapped and reported. \
The existing vdn statement handle is preserved.
Returns:
=cut
my ($vdn, $package, $proc_name) = @_;
my ($dbh, $sth) = _db_vdn('procx', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in db_procx' );
}
if ( $package ) { $proc_name = $package . '.' . $proc_name; }
my $sql = 'BEGIN ' . $proc_name . '; END;';
my $tmp_sth = $dbh->prepare( $sql );
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->execute;
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->finish;
return 0;
}
sub db_proc_in {
=begin wiki
!3 db_proc_in
Parameters: ( vdn, package, proc_name, parameters )
This function executes an Oracle stored procedure that takes any number of \
IN parameters and returns no output. This interface is Oracle specific, so a \
check is performed to make sure that the supplied vdn is pointing to an \
Oracle database. If a database error is raised it is trapped and reported. \
The existing vdn statement handle is preserved.
Returns:
=cut
my ($vdn, $package, $proc_name, $params) = @_;
unless ( ref $params eq 'ARRAY' ) {
sys_die( 'Invalid type in call to db_procx_in' );
}
my ($dbh, $sth) = _db_vdn('procx_in', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in db_procx_in' );
}
my $sql = _db_proc_build_sql( $package, $proc_name, $params );
my $tmp_sth = $dbh->prepare( $sql );
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth = _db_proc_bind_inparams( $tmp_sth, $params );
$tmp_sth->execute;
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->finish;
return 0;
}
sub db_proc_out {
=begin wiki
!3 db_proc_out
Parameters: ( vdn, package, proc_name, parameters )
This function executes an Oracle stored procedure that takes no input and \
returns any number of OUT parameters. This interface is Oracle specific, so \
a check is performed to make sure that the supplied vdn is pointing to an \
Oracle database. If a database error is raised it is trapped and reported. \
The existing vdn statement handle is preserved.
Returns:
=cut
my ($vdn, $package, $proc_name, $params) = @_;
unless ( ref $params eq 'ARRAY' ) {
sys_die( 'Invalid type in call to db_procx_out' );
}
my ($dbh, $sth) = _db_vdn('procx_out', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in db_procx_out' );
}
my $sql = _db_proc_build_sql( $package, $proc_name, $params );
my $tmp_sth = $dbh->prepare( $sql );
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth = _db_proc_bind_outparams( $tmp_sth, $params);
$tmp_sth->execute;
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->finish;
return 0;
}
sub db_proc_inout {
=begin wiki
!3 db_proc_inout
Parameters: ( vdn, package, proc_name, parameters )
This function executes an Oracle stored procedure that takes any combination \
of IN, IN OUT, or OUT parameters. This interface is Oracle specific, so a \
check is performed to make sure that the supplied vdn is pointing to an \
Oracle database. If a database error is raised it is trapped and reported. \
The existing vdn statement handle is preserved.
Returns:
=cut
my ($vdn, $package, $proc_name, $params) = @_;
unless ( ref $params eq 'ARRAY' ) {
sys_die( 'Invalid type in call to db_procx_inout' );
}
my ($dbh, $sth) = _db_vdn('procx_inout', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in db_procx_inout' );
}
my $sql = _db_proc_build_sql( $package, $proc_name, $params );
my $tmp_sth = $dbh->prepare( $sql );
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth = _db_proc_bind_inoutparams( $tmp_sth, $params);
$tmp_sth->execute;
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->finish;
return 0;
}
sub db_dbms_output_enable {
=begin wiki
!3 db_dbms_output_enable
Parameters: ( vdn, output_buffer_size)
This function enables dbms_output in the database. You may send this \
function an output buffer size if desired. If no buffersize is provided, \
a default buffer size of 1000000 is used. This interface is Oracle specific, \
so a check is performed to make sure that the supplied vdn is pointing to \
an Oracle database.
Returns:
=cut
my ($vdn, $bufsize) = shift;
my ($dbh, $sth) = _db_vdn('enable_dbms_output', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in db_dbms_output_get' );
}
$sys_dbms_output = 1;
$bufsize = 1_000_000 unless $bufsize;
$dbh->func($bufsize, 'dbms_output_enable');
if ( DBI->errstr ) { log_warn( DBI->errstr ); return 1; }
return 0;
}
sub db_dbms_output_disable {
=begin wiki
!3 db_dbms_output_disable
Parameters: ( vdn )
This function disables dbms_output retrieval. It does this by setting a \
module flag value. This interface is Oracle specific, so a check is \
performed to make sure that the supplied vdn is pointing to an Oracle \
database.
Returns:
=cut
my $vdn = shift;
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in db_dbms_output_get' );
}
$sys_dbms_output = 0;
return 0;
}
sub db_dbms_output_get {
=begin wiki
!3 db_dbms_output_get
Parameters: ( vdn )
This function retrieves the current dbms_output buffer and returns it to \
the caller as a reference to an array. This interface is Oracle specific, \
so a check is performed to make sure that the supplied vdn is pointing to \
an Oracle database. You need to call db_dbms_output_enable first.
Returns:
=cut
my $vdn = shift;
my ($dbh, $sth) = _db_vdn('get_dbms_output', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in db_dbms_output_get' );
}
my @arr;
unless ( $sys_dbms_output ) {
log_warn( 'Output option has not been enabled' );
return \@arr;
}
@arr = $dbh->func('dbms_output_get');
if ( DBI->errstr ) { log_warn( DBI->errstr ); }
return \@arr;
}
sub db_index_rebuild {
=begin wiki
!3 db_index_rebuild
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($vdn, $index_name) = @_;
my ($dbh, $sth) = _db_vdn('ora_index_rebuild', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in function index_rebuild', 0 );
}
my $sql = "ALTER INDEX $index_name REBUILD";
my $tmp_sth = $dbh->prepare( $sql );
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->execute;
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->finish;
return 0;
}
sub db_exchange_partition {
=begin wiki
!3 db_exchange_partition
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($vdn, $to_table, $from_table, $partition) = @_;
my ($dbh, $sth) = _db_vdn('ora_swap_partition', $vdn);
unless ( _db_is_oracle($vdn) ) {
sys_die( 'Not an Oracle database connection in function swap_partition', 0 );
}
## REPAIR REQUIRED need to figure out why this is required...
db_commit( $vdn );
sleep 3;
my $sql = "ALTER TABLE $to_table "
. "EXCHANGE PARTITION $partition "
. "WITH TABLE $from_table "
. "INCLUDING INDEXES "
. "WITH VALIDATION";
my $tmp_sth = $dbh->prepare( $sql );
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->execute;
if ( DBI->errstr ) { sys_die( DBI->errstr ); }
$tmp_sth->finish;
return 0;
}
=begin wiki
!2 Utility Functions
These functions provide the general purpose file access capabilities.
=cut
sub util_get_filename_load {
=begin wiki
!3 util_get_filename_load
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($base, $ext) = @_;
my $filename = $base . '.' . $ext;
if ( $osuser ) {
$filename = $base . '_' . $osuser . '.' . $ext;
}
return $path_load_dir . $filename;
}
sub util_get_filename_extr {
=begin wiki
!3 util_get_filename_extr
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($base, $ext) = @_;
my $filename = $base . '.' . $ext;
if ( $osuser ) {
$filename = $base . '_' . $osuser . '.' . $ext;
}
return $path_extr_dir . $filename;
}
sub util_get_filename_log {
=begin wiki
!3 util_get_filename_log
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $base = shift;
return $path_log_dir . $base . $log_ext;
}
sub util_read_header {
=begin wiki
!3 util_read_header
Parameters: ( filename, format )
Please write this documentation.
Returns:
=cut
my ($filename, $format) = @_;
my $fh = File::Bidirectional->new($filename, {origin => 1} )
or sys_die( "Unable to open file $filename" );
my $head = $fh->readline();
$fh->close;
return $head;
}
sub util_read_footer {
=begin wiki
!3 util_read_footer
Parameters: ( filename, format_string )
Please write this documentation.
Returns:
=cut
my ($filename, $format) = @_;
my $fh = File::Bidirectional->new($filename, {origin => -1} )
or sys_die( "Unable to open file $filename" );
my $foot = $fh->readline();
$fh->close;
return $foot;
}
sub util_read_file {
=begin wiki
Parameters: ( )
Slurp a file in one go and return a return a reference to the text contained \
in the file.
Returns:
=cut
my $file = shift;
open( my $fh, $file ) or return 0;
my $text = do { local( $/ ) ; <$fh> } ;
return \$text;
}
sub util_write_header {
=begin wiki
!3 util_write_header
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($filename, $header, $append) = @_;
$header = 'HEADER' unless $header;
my $mode = ">>";
$mode = ">" unless $append;
open my $fh, $mode, $filename or sys_die( "Error writing header to $filename" );
print {$fh} "$header\n";
close $fh or sys_die( "Error closing $filename" );
return 0;
}
sub util_write_footer {
=begin wiki
!3 util_write_footer
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($filename, $footer) = @_;
$footer = 'FOOTER' unless $footer;
open my $fh, ">>", $filename or sys_die( "Error writing footer to $filename" );
print {$fh} "$footer\n";
close $fh or sys_die( "Error closing $filename" );
return 0;
}
sub util_move {
=begin wiki
Parameters: ( )
The move function also takes two parameters: the current name and the \
intended name of the file to be moved. If the destination already exists \
and is a directory, and the source is not a directory, then the source \
file will be renamed into the directory specified by the destination.
If possible, move() will simply rename the file. Otherwise, it copies the \
file to the new location and deletes the original. If an error occurs \
during this copy-and-delete process, you may be left with a (possibly \
partial) copy of the file under the destination name.
Returns:
=cut
my ($from, $to) = @_;
return 0 unless $util_move;
my $result = move($from, $to);
return $result;
}
sub util_trim {
my $str = shift;
$str =~ s/^\s+//;
$str =~ s/\s+$//;
return $str;
}
sub util_zsdf {
=begin wiki
Parameters: ( )
This regex was taken from the book "Regular Expression Recipes", by Nathan \
A. Good. The idea for util_zsdf (Zero Supress Decimal Format) came from my \
first mentor, Ed Bowlen.
Returns:
=cut
my ($number, $width) = @_;
$number =~ s/(?<=\d)(?=(\d{3})+(?!\d))/,/g;
return sprintf '%*s', $width, $number;
}
=begin wiki
!2 Testing Functions
These functions some basic test capabilities. These can be used to write simple
database test scripts.
=cut
sub test_init {
=begin wiki
!3 test_init
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
$t_ok = 0;
$t_notok = 0;
return 0;
}
sub test_ok {
=begin wiki
!3 test_ok
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($actual,$expected,$description) = @_;
$t_num++;
if ($actual eq $expected) {
$t_ok++;
log_info("ok $t_num");
} else {
$t_notok++;
sys_set_errorlevel(sys_get_errorlevel()+1);
log_info("not ok $t_num - $description");
}
return 0;
}
sub test_results {
=begin wiki
!3 test_results
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
log_info("Test script: passed $t_ok, failed $t_notok");
if ( $t_notok == 0 ) {
log_info("Test script: PASS");
} else {
log_info("Test script: FAIL");
}
return 0;
}
sub test_harness_init {
=begin wiki
!3 test_harness_init
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
$th_num = 0;
return 0;
}
sub test_harness_run {
=begin wiki
!3 test_harness_run
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $test_scripts = shift;
foreach my $ts ( @{$test_scripts} ) {
$th_num++;
log_info("Test script: $ts");
my $retcd = sys_run_job($ts, 8, '-r', '-v');
if ( $retcd > 0 ) {
sys_set_errorlevel( sys_get_errorlevel() + $retcd );
}
}
return 0;
}
sub test_harness_results {
=begin wiki
!3 test_harness_results
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $test_scripts = shift;
my ($ts_passed, $ts_failed);
my $th_result = 'PASS';
my $th_passed = 0;
my $th_failed = 0;
foreach my $ts ( @{$test_scripts} ) {
$ts =~ s/\.pl$//;
my $tsfull = util_get_filename_log( $ts );
my $log = util_read_file( $tsfull );
if ( ! $log ) {
log_info( "Error reading log for test script: $ts" );
next;
}
$ts_passed = 0;
$ts_failed = 0;
$th_num++;
$$log =~ m#.{19,19} Test script: (PASS|FAIL|DUBIOUS)#;
my $ts_result = $1;
$$log =~ m#.{19,19} Test script: passed (\d+), failed (\d+)#;
$ts_passed = $1;
$ts_failed = $2;
if ( $ts_result eq 'PASS' ) {
$th_passed++;
}
if ( $ts_result eq 'FAIL' ) {
$th_failed++;
$th_result = 'FAIL';
}
log_info( "Test harness: script $ts, passed $ts_passed, failed $ts_failed, $ts_result" );
}
log_info( "Test harness: passed $th_passed, failed $th_failed" );
log_info( "Test harness: $th_result" );
return 0;
}
sub test_harness_summary {
=begin wiki
!3 test_harness_summary
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $test_harnesses = shift;
foreach my $th ( @{$test_harnesses} ) {
$th =~ s/\.pl$//;
my $thfull = util_get_filename_log( $th );
my $log = util_read_file( $thfull );
if ( ! $log ) {
log_info( "Error reading log for test harness: $th" );
next;
}
log_info( "Test harness summary: $th" );
}
return 0;
}
# private methods
# -----------------------------------------------------------------------------
=begin wiki
!2 Private Functions
These functions provide internal module support.
=cut
sub _sys_init_vars {
=begin wiki
!3 _sys_init_vars
Parameters: ( )
This function provides variable initialization for a particular jobname. \
Once sys_init has been called with a jobname, this function is called to \
initialize or reinitialize system variables. It is possible, although not \
recommended, to stack jobs in a single perl script. my callling sys_init with \
different jobnames each time. This feature has not been thoroughly tested.
Returns:
=cut
$pid = $PROCESS_ID;
$errorlevel = 0;
@plugins = ();
$sys_dbms_output = 1;
$sys_log_open = 0;
$sys_jobconf_override = 0;
$sys_jobconf_file = '';
%log_level_opts = (
FATAL => 'FATAL',
ERROR => 'FATAL,ERROR',
WARN => 'FATAL,ERROR,WARN',
INFO => 'FATAL,ERROR,WARN,INFO',
DEBUG => 'FATAL,ERROR,WARN,INFO,DEBUG',
NONE => 'NONE',
);
_sys_read_conf( 'sys_data.conf' );
_sys_read_conf( 'sys_log.conf' );
_sys_read_conf( 'sys_mail.conf' );
_sys_read_conf( 'sys_common.conf' );
_sys_read_conf( 'sys_util.conf' );
_sys_read_conf( 'sys_environment.conf' );
_sys_read_conf( 'sys_de.conf');
_sys_read_conf( 'sys_run_controls.conf');
my $envvar = uc $conf_system{'system'}{'envvar'};
$dataenvr = lc $ENV{$envvar};
if ( ! defined $dataenvr ) {
sys_die( "Environment variable $dataenvr not set", 0 );
}
$path_bin_dir = $conf_system{"$OSNAME directory bin"}{$dataenvr};
$path_lib_dir = $conf_system{"$OSNAME directory lib"}{$dataenvr};
$path_log_dir = $conf_system{"$OSNAME directory log"}{$dataenvr};
$path_load_dir = $conf_system{"$OSNAME directory load"}{$dataenvr};
$path_extr_dir = $conf_system{"$OSNAME directory extr"}{$dataenvr};
$path_prev_dir = $conf_system{"$OSNAME directory prev"}{$dataenvr};
$path_scripts_dir = $conf_system{"$OSNAME directory scripts"}{$dataenvr};
$mail_server = $conf_mail{'mail'}{'server'};
$mail_from = $conf_mail{'mail'}{'from'};
$mail_emailto = $conf_mail{'mail'}{'emailto'};
$mail_pagerto = $conf_mail{'mail'}{'pagerto'};
$mail_email_levels = $conf_mail{'mail'}{'email_levels'} || "FATAL";
$mail_pager_levels = $conf_mail{'mail'}{'pager_levels'} || "FATAL";
$log_file = $conf_log{'log'}{'default_logfile'};
$log_filefull = $path_log_dir . $log_file;
$log_logging_levels = $conf_log{'log'}{'logging_levels'} || "FATAL,ERROR,WARN,INFO";
$log_console_levels = $conf_log{'log'}{'console_levels'} || "FATAL,ERROR,WARN,INFO";
$log_gdg = $conf_log{'log'}{'gdg'} || 5;
$path_plugin_dir = $conf_system{"$OSNAME directory plugin"}{$dataenvr};
if ( $osuser ) {
$dbitrace_file = $dbitrace_base . '_' . $osuser . $log_ext;
}
$dbitrace_filefull = $path_log_dir.$dbitrace_file;
## load data structures
@databases = split m/,/, $conf_data{'databases'}{'databases'};
@dat_envrs = split m/,/, $conf_system{'system'}{'dat_envrs'};
@job_acros = split m/,/, $conf_system{'system'}{'job_acros'};
foreach my $db ( @databases ) {
$dbname{$db} = $conf_data{'names'}{$db};
}
foreach my $db ( @databases ) {
$dbdefenvr{$db} = $conf_data{'default '.$dataenvr}{$db};
}
foreach my $db ( @databases ) {
$dbhandles{$db}{'dbh'} = 0;
$dbhandles{$db}{'sth'} = 0;
}
foreach my $db ( @databases ) {
$dbinst{$db} = $conf_data{'instances'}{$db};
}
foreach my $db ( @databases ) {
foreach my $inst ( split m/,/, $conf_data{'instances'}{$db} ) {
$dbconn{$db}{$inst}{'netservice'} = $conf_data{"$db $inst"}{'netservice'};
$dbconn{$db}{$inst}{'database' } = $conf_data{"$db $inst"}{'database'};
$dbconn{$db}{$inst}{'username' } = $conf_data{"$db $inst"}{'username'};
$dbconn{$db}{$inst}{'password' } = $conf_data{"$db $inst"}{'password'};
}
}
return 0;
}
sub _sys_job_init {
=begin wiki
!3 _sys_job_init
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $rtconf = $path_conf_dir.'/'.$jobname.'.'.$pid.'.running';
## create runtime conf file
open my $cfile, '>', $rtconf or sys_die( "Error creating runtime jobconf file" );
close $cfile;
my $conf = new Config::IniFiles( -file => $rtconf );
unless ( defined $conf ) { sys_die( "Error opening runtime jobconf file" ); }
my $starttime = time;
$conf->newval( 'pid', 'pid', $pid );
$conf->newval( 'starttime', 'starttime', $starttime );
$conf->newval( 'restart', 'restart', 0 );
$conf->RewriteConfig;
return 0;
}
sub _sys_job_end {
=begin wiki
!3 _sys_job_end
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $rtconf = $path_conf_dir.'/'.$jobname.'.'.$pid.'.running';
if ( -e $rtconf ) {
unlink $rtconf;
}
return 0;
}
sub _sys_job_dependent {
=begin wiki
!3 _sys_job_dependent
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $dependent_jobname = shift;
return 0 unless $dependent_jobname;
my $conf = new Config::IniFiles( -file => $path_conf_dir.'/sys_environment.conf' );
unless ( defined $conf ) { sys_die( "Error opening sys_environment.conf (4)" ); }
my $params = join '~', $conf->Parameters( 'jobs' );
if ( $params =~ m/$dependent_jobname/x ) { ## case sensitive
## one or more instances of dependent job is currently running
log_info( "Job name $dependent_jobname is active in the system, waiting" );
return 1;
}
return 0;
}
sub _sys_read_conf {
=begin wiki
!3 _sys_read_conf
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $conf = shift;
my $conf_filefull = $path_conf_dir . '/' . $conf;
my $msg1 = "Probably syntax error, unable to load";
if ( $conf =~ m/^sys_data/x ) {
tie %conf_data, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 data conf: $conf", 0 );
}
if ( $conf =~ m/^sys_log/x ) {
tie %conf_log, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 log conf: $conf", 0 );
}
if ( $conf =~ m/^sys_mail/x ) {
tie %conf_mail, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 mail conf: $conf", 0 );
}
if ( $conf =~ m/^sys_common/x ) {
tie %conf_query, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 query conf: $conf", 0 );
}
if ( $conf =~ m/^sys_util/x ) {
tie %conf_util, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 util conf: $conf", 0 );
}
if ( $conf =~ m/^sys_environment/x ) {
tie %conf_system, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 environment conf: $conf", 0 );
}
if ( $conf =~ m/^sys_test/x ) {
tie %conf_job, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 test conf: $conf", 0 );
}
if ( $conf =~ m/^sys_de/x ) {
tie %conf_de, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 de conf: $conf", 0 );
}
if ( $conf =~ m/^sys_run_controls/x ) {
tie %conf_rcontrols, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 run controls conf: $conf", 0 );
}
## job specific conf file
if ( $conf !~ m/^sys_/x ) {
tie %conf_job, 'Config::IniFiles', ( -file => $conf_filefull )
or sys_die( "$msg1 job conf: $conf", 0 );
}
return 0;
}
sub _sys_read_job {
=begin wiki
!3 _sys_read_job
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( $conf_job{job}{'logfile'} ) {
$log_file = $conf_job{job}{'logfile'};
}
if ( $conf_job{job}{'logging_levels'} ) {
$log_logging_levels = $conf_job{job}{'logging_levels'};
}
if ( $conf_job{job}{'console_levels'} ) {
$log_console_levels = $conf_job{job}{'console_levels'};
}
if ( $conf_job{job}{'log_gdg'} ) {
$log_gdg = $conf_job{job}{'log_gdg'};
}
if ( $conf_job{job}{'log_prefix'} ) {
$log_prefix = $conf_job{job}{'log_prefix'};
}
if ( $conf_job{job}{'emailto'} ) {
$mail_emailto = $conf_job{job}{'emailto'};
}
if ( $conf_job{job}{'pagerto'} ) {
$mail_pagerto = $conf_job{job}{'pagerto'};
}
if ( $conf_job{job}{'email_levels'} ) {
$mail_email_levels = $conf_job{job}{'email_levels'};
}
if ( $conf_job{job}{'pager_levels'} ) {
$mail_pager_levels = $conf_job{job}{'pager_levels'};
}
return 0;
}
sub _sys_init_source_validation {
=begin wiki
!3 _sys_init_source_validation
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
open my $fh, "<", $script_filefull
|| sys_die( "Unable to open $script_file for validatation", 0 );
my @r = <$fh>;
close $fh;
my $source = join '', @r;
my $errm1 = "$script_file failed source validation, id tag ";
my $errm2 = "$script_file failed source validation, pod section ";
my $errm3 = " is missing or invalid";
my $checkfor;
$checkfor = "FILENAME";
$source =~ m/^\#\#@@.*/m
or sys_die( $errm1.$checkfor.$errm3, 0 );
$checkfor = "SOURCETITLE";
$source =~ m/^\#\#\$\$.*/m
or sys_die( $errm1.$checkfor.$errm3, 0 );
$checkfor = "NAME";
$source =~ m/^!1 $checkfor\n\n[A-Za-z]/m
or sys_die( $errm2.$checkfor.$errm3, 1 );
$checkfor = "DESCRIPTION";
$source =~ m/^!1 $checkfor\n\n[A-Za-z]/m
or sys_die( $errm2.$checkfor.$errm3, 1 );
$checkfor = "RECOVERY NOTES";
$source =~ m/^!1 $checkfor\n\n[A-Za-z]/m
or sys_die( $errm2.$checkfor.$errm3, 1 );
$checkfor = "ENVIRONMENT NOTES";
$source =~ m/^!1 $checkfor\n\n[A-Za-z]/m
or sys_die( $errm2.$checkfor.$errm3, 1 );
$checkfor = "DEPENDENCIES";
$source =~ m/^!1 $checkfor\n\n[A-Za-z]/m
or sys_die( $errm2.$checkfor.$errm3, 1 );
$checkfor = "HISTORY";
$source =~ m/^!1 $checkfor\n\n[A-Za-z0-9\*]/m
or sys_die( $errm2.$checkfor.$errm3, 1 );
return 0;
}
sub _sys_run_background {
=begin wiki
!3 _sys_run_background
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( $OSNAME eq 'MSWin32' ) {
sys_die( 'Background run mode not available on Windows', 0 );
}
$opt_commandline =~ s{-rb }{-r };
$opt_commandline =~ s{-rb$}{-r};
print "$script_filefull $opt_commandline".' &';
exit 0;
}
sub _sys_run_scheduled {
=begin wiki
!3 _sys_run_scheduled
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
## this die is temporary should use sys_die
die "Not yet implemented\n\n";
}
sub _sys_run_de {
=begin wiki
!3 _sys_run_de
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $de = shift;
my $conf_file = $jobname . '.' . $de . '.conf';
_sys_read_conf( $conf_file ); ## tie %conf_job to job specific conf file
_sys_read_job(); ## read job specific settings from %conf_job
return 0;
}
sub _sys_run_restart {
=begin wiki
!3 _sys_run_restart
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
## this die is temporary should use sys_die
die "Not yet implemented\n\n";
}
sub _sys_forkexec {
=begin wiki
!3 _sys_forkexec
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($jobname, @params) = @_;
my $pid;
if ( $pid = fork ) {
return $pid;
## this is the parent, so return the pid, everything below here is
## either the child or a major system failure
}
elsif ( defined $pid ) {
exec $jobname, @params;
## shouldn't reach this unless exec fails, we exit here (not return)
## becuase we are in the child
exit 0;
} else {
log_warn( "Could not fork $!" );
return 0;
}
}
sub _sys_reap_child {
=begin wiki
!3 _sys_reap_child
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $pid = 0;
if ( ($pid = waitpid(-1, 0)) > 0 ) {
$pidlib{$pid}{retcd} = $? >> 8;
}
return $pid;
}
sub _sys_test_dbcon {
=begin wiki
!3 _sys_test_dbcon
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $connections = shift;
## open dbi trace file
DBI->trace(1, $dbitrace_filefull );
foreach my $connectdef ( split m/,/, $connections ) {
my ($db, $inst) = split m/:/, $connectdef;
_check_array_val( $db, \@databases )
|| sys_die( "Invalid database: [$db]", 0 );
_check_array_val( $inst, [split m/,/, $dbinst{$db}] )
|| sys_die( "Invalid database instance: [$db.$inst]", 0 );
my $database = $dbconn{$db}{$inst}{'database'};
my $username = $dbconn{$db}{$inst}{'username'};
my $password = $dbconn{$db}{$inst}{'password'};
print "Connecting to: $db/$inst\n";
my $dbh = DBI->connect( $database, $username, $password, { RaiseError => 0, AutoCommit => 0 } )
or sys_die( DBI->errstr, 0 );
## push resulting handle onto handle stack for cleanup on exit
$dbhandles{$db}{'dbh'} = $dbh;
print "Success\n\n";
}
exit 0;
}
sub _sys_check_severity_levels {
=begin wiki
!3 _sys_check_severity_levels
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $lvls_str = shift;
## levls_str can be either a single value or a comma delimited list
if ( $lvls_str =~ /,/ ) {
## received a list of severity levels
my @loglvls = split m/,/, $lvls_str;
foreach my $level ( @loglvls ) {
if ( $level !~ /FATAL|ERROR|WARN|INFO|DEBUG|NONE/ ) {
sys_die( 'Invalid logging/notification severity list', 0 );
}
}
return $lvls_str;
} else {
## received a single severity level to be translated to a list
if ( $lvls_str =~ /^FATAL$/i ) {
$lvls_str = 'FATAL';
return $lvls_str;
}
if ( $lvls_str =~ /^ERROR$/i ) {
$lvls_str = 'FATAL,ERROR';
return $lvls_str;
}
if ( $lvls_str =~ /^WARN$/i ) {
$lvls_str = 'FATAL,ERROR,WARN';
return $lvls_str;
}
if ( $lvls_str =~ /^INFO$/i ) {
$lvls_str = 'FATAL,ERROR,WARN,INFO';
return $lvls_str;
}
if ( $lvls_str =~ /^DEBUG$/i ) {
$lvls_str = 'FATAL,ERROR,WARN,INFO,DEBUG';
return $lvls_str;
}
if ( $lvls_str =~ /^NONE$/i ) {
$lvls_str = '';
return $lvls_str;
}
sys_die( 'Invalid logging/notification severity level', 0 );
}
return 0;
}
sub _sys_check_log_gdg {
=begin wiki
!3 _sys_check_log_gdg
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( $opt_log_gdg =~ /[0-9]{1,3}/ ) {
sys_die( 'Invalid log gdg specified', 0 );
}
return $opt_log_gdg;
}
sub _sys_check_log_radix {
=begin wiki
!3 _sys_check_log_radix
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( $opt_log_radix < 1 || $opt_log_radix > 4 ) {
sys_die( 'Invalid log radix specified', 0 );
}
return $opt_log_radix;
}
sub _sys_check_de_override {
=begin wiki
!3 _sys_check_de_override
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $tmp_jobname = shift;
my $tmp_jobconf_file = $tmp_jobname;
my $delist = $conf_de{jobname}{$tmp_jobname};
if ( $delist ) { ## possible override of job conf
my $de = '0000';
if ( $delist =~ /(\d\d\d\d\d)\s?$/ ) {
$de = $1;
}
my $overenvs = $conf_de{$de}{'env'};
if ( $overenvs =~ /$dataenvr/i ) {
## as a side-effect, sys_jobconf_override gets set here...
$sys_jobconf_override = 1; ## so we know override is effective
$tmp_jobconf_file .= ".$de";
}
}
return $tmp_jobconf_file;
}
sub _sys_disp_logprev {
=begin wiki
!3 _sys_disp_logprev
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( $opt_log_file ) { $log_file = $opt_log_file; }
$log_filefull = $path_log_dir . $log_file;
if ( -e $log_filefull ) {
print "Log: $log_filefull\n";
system "cat $log_filefull";
print "\n";
exit 0;
}
print "No previous log file found\n\n";
return 0;
}
sub _sys_disp_logarch {
=begin wiki
!3 _sys_disp_logarch
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( $opt_log_file ) { $log_file = $opt_log_file; }
$log_filefull = $path_log_dir . $log_file;
my @logs = glob $log_filefull . '.*';
if ( @logs ) {
foreach my $log ( sort @logs ) {
print "Log: $log\n";
system "cat $log";
}
print "\n";
exit 0;
}
print "No archived log files found\n\n";
return 0;
}
sub _sys_disp_jobs {
=begin wiki
!3 _sys_disp_jobs
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my @jobs = glob $path_bin_dir.'*.pl';
if ( @jobs ) {
foreach my $job ( sort @jobs ) {
my $description = 'No description found';
open my $fh, "<", $job or sys_die( "Unable to open $job", 0 );
while ( <$fh> ) {
chomp;
if ( /^\#\#\$\$/ ) {
$description = substr $_, 4;
}
}
close $fh;
$job =~ s{^\/.*\/}{};
print "Job: $job\n";
print " $description\n";
}
print "\n";
exit 0;
}
print "No archived job files found\n\n";
return 0;
}
sub _sys_disp_active_jobs {
=begin wiki
!3 _sys_disp_active_jobs
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $logging = shift; ## needs implementing
my @actjobs = glob $path_conf_dir.'/*.running';
print 'Jobs currently active: ' . scalar @actjobs . "\n";
if ( @actjobs ) {
foreach my $job ( sort @actjobs ) {
my $conf = new Config::IniFiles( -file => $job );
unless ( defined $conf ) { sys_die( "Error opening $job" ); }
my $pid = $conf->val( 'pid', 'pid' );
## NOTE: use Unix::PID to determine if pid is still runninng...
## If pid is no longer running, replace "Job:" with "???:".
my $starttime = $conf->val( 'starttime', 'starttime' );
my $fmtdtime = time2str( '%Y/%m/%d %T', $starttime );
$job =~ s{^\/.*\/}{};
$job =~ s{\.\d+\.running$}{};
print "Job: $job\n";
print " pid=$pid\n";
print " starttime=$fmtdtime\n";
$conf = undef;
}
}
print "\n";
exit 0;
}
sub _sys_disp_doc {
=begin wiki
!3 _sys_disp_doc
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
if ( -e $script_filefull ) {
my %podparams = (
infile => $script_filefull,
outfile => "STDOUT",
);
wikipod2text( %podparams );
} else {
print "File not found $script_filefull\n\n";
}
exit 0;
}
sub _sys_disp_sql {
=begin wiki
!3 _sys_disp_sql
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my @query_names = keys %{$conf_query{$jobname}};
if ( @query_names ) {
foreach my $query_name ( sort @query_names ) {
my $query = $conf_query{$jobname}{$query_name};
print "Query: $query_name\n";
print $query;
print "\n\n";
}
} else {
print "No querys found\n\n";
}
exit 0;
}
sub _sys_disp_params {
=begin wiki
!3 _sys_disp_params
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $dblen = 0;
foreach my $db ( @databases ) {
if ( length $dbname{$db} > $dblen ) { $dblen = length $dbname{$db}; }
}
print "\n" . uc($dataenvr) . " Database Connections:\n";
foreach my $db ( @databases ) {
my $dbstr = sprintf "%-${dblen}s", $dbname{$db};
$dbstr .= ' = ' . $db . '/' . $dbdefenvr{$db};
print " $dbstr\n",;
}
print "\n" . uc($dataenvr) . " Job Settings:\n";
print " Job Name = ", $jobname, "\n";
print " Log File = ", $log_file, "\n";
print " Log Logging Levels = ", $log_logging_levels, "\n";
print " Log Console Levels = ", $log_console_levels, "\n";
print " Log Gdg = ", $log_gdg, "\n";
print " Path Bin Dir = ", $path_bin_dir, "\n";
print " Path Log Dir = ", $path_log_dir, "\n";
print " Path Lib Dir = ", $path_lib_dir, "\n";
print " Path Conf Dir = ", $path_conf_dir, "\n";
print " Path Plugin Dir = ", $path_plugin_dir, "\n";
print " Path Load Dir = ", $path_load_dir, "\n";
print " path Extract Dir = ", $path_extr_dir, "\n";
print " path Prev Dir = ", $path_prev_dir, "\n";
print " path Scripts Dir = ", $path_scripts_dir, "\n";
print " Mail Server = ", $mail_server, "\n";
print " Mail Email From = ", $mail_from, "\n";
print " Mail Email To = ", $mail_emailto, "\n";
print " Mail Pager To = ", $mail_pagerto, "\n";
print " Mail Email Levels = ", $mail_email_levels, "\n";
print " Mail Pager Levels = ", $mail_pager_levels, "\n";
print "\n";
exit 0;
}
sub _sys_send_email_message {
=begin wiki
!3 _sys_send_email_message
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $params = shift;
my ($addrlist, $message) = split m/~/, $params;
$mail_emailto = $addrlist;
_log_send_mail($message, 'MESSAGE');
exit 0;
}
sub _sys_send_pager_message {
=begin wiki
!3 _sys_send_pager_message
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $params = shift;
my ($addrlist, $message) = split m/~/, $params;
$mail_pagerto = $addrlist;
_log_send_page($message, 'MESSAGE');
exit 0;
}
sub _sys_help {
=begin wiki
!3 _sys_help
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $verbose = shift;
$verbose = 0 unless $verbose;
my $section;
if ( $verbose == 0 ) {
print "\nUSAGE\n $script_file [options]\n\n";
print "Use option -h for help with options\n";
print "Use option -hp for help with option parameters\n";
print "Use option -man for system documentation\n";
exit 1;
}
if ( $verbose == 1 ) { $section = 'OPTIONS'; };
if ( $verbose == 2 ) { $section = 'ARGUMENTS'; };
print "\n";
my %podparams = (
infile => $path_lib_dir."DBIx/JCL.pm",
outfile => "STDOUT",
section => $section,
);
wikipod2text( %podparams );
exit 1;
}
sub _log_init_log_file {
=begin wiki
!3 _log_init_log_file
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
## log file rotation if generations > 0
if ( -e $log_filefull && $log_gdg > 0 ) {
_log_rotate();
}
## create new locked log file
## if the file is already locked, will wait until the file is unlocked
my $fh = new IO::LockedFile(">$log_filefull")
or sys_die( 'Failed opening log file', 0 );
## close and unlock the file
$fh->close();
$sys_log_open = 1;
return 0;
}
sub _log_write_to_log {
=begin wiki
!3 _log_write_to_log
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($level, $force, $msg, $exmsg) = @_;
my ($message,$exmessage);
if ( ref $exmsg eq 'ARRAY' ) {
my $lead = ' ' x 18;
$lead .= '+ ';
my @output = map { $lead . $_ . "\n" } @{$exmsg};
my $exmessage = join '', @output;
$exmessage =~ s/\n$//ms;
$message = $msg . "\n" . $exmessage;
} else {
$message = $msg;
$message =~ s/\n/ /g;
}
if ( $log_logging_levels =~ /$level/ || $force ) {
_log_print_log( $level, $message );
}
_log_send_notifications( $level, $force, $msg );
return 0;
}
sub _log_write_to_screen {
=begin wiki
!3 _log_write_to_screen
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($level, $force, $msg, $exmsg) = @_;
my ($message,$exmessage);
if ( ref $exmsg eq 'ARRAY' ) {
my $lead = ' ' x 18;
$lead .= '+ ';
my @output = map { $lead . $_ . "\n" } @{$exmsg};
my $exmessage = join '', @output;
$message = $msg . "\n" . $exmessage;
} else {
$message = $msg;
$message =~ s/\n/ /g;
}
$message = _log_trim_msg( $message );
if ( $opt_verbose ) {
print "$message\n";
} else {
if ( $log_console_levels =~ /$level/ || $force ) {
print "$message\n";
}
}
return 0;
}
sub _log_print_log {
=begin wiki
!3 _log_print_log
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($level, $message) = @_;
my $preamble = time2str( '%Y/%m/%d %T', time );
if ( $level eq 'FATAL' ) { $preamble .= ' FATAL'; }
if ( $level eq 'ERROR' ) { $preamble .= ' ERROR'; }
if ( $level eq 'WARN' ) { $preamble .= ' WARNING'; }
## open locked log file for appending
## if the file is already locked, will wait until the file is unlocked
my $fh = new IO::LockedFile(">>$log_filefull")
or sys_die( 'Failed opening log file', 0 );
print {$fh} "$preamble $message\n";
## close and unlock the file
$fh->close();
return 0;
}
sub _log_trim_msg {
=begin wiki
!3 _log_trim_msg
Parameters: ( message )
Format log file text so that it looks good when printed to STDOUT. This \
function is only called from the logging functions. This takes message \
text that was previously retrieved by dbms_output_get and stringified by \
a logging function and removes the leading whitespace from each line of \
text, if there is any. This is made necessary due to the fact that this \
text started life as an array of lines retrieved from dbms_output_get(), \
and each of these lines had leading whitespace to make them more readable \
in the log file.
Returns:
=cut
my $msg = shift;
my $trimmed = '';
if ( $msg =~ /\n/ms ) { ## trim leading spaces from multi-line messages
foreach my $m ( split m/\n/, $msg ) {
$m =~ s/^\s+//;
$trimmed .= $m."\n";
}
$trimmed =~ s/\n$//ms;
} else {
$trimmed = $msg;
}
return $trimmed;
}
sub _log_send_notifications {
=begin wiki
!3 _log_send_notifications
Parameters: ( message, severity_level )
Send email and pager notifications based on supplied severity. If the \
severity levels for email and or pager notifications are at or below the \
severity level supplied to this function, a notification will be sent.
Note: if running under test harness (different than test mode), all \
messages are logged, but no notifications of any severity will be generated. \
Generation of actual email and pager notices is not testable using the test \
harness.
Returns:
=cut
my ($level, $force, $message) = @_;
# if ( $tst_harness ) {
# return 0;
# }
if ( $mail_email_levels =~ /$level/ || $force ) {
_log_send_mail( $message, $level );
}
if ( $mail_pager_levels =~ /$level/ || $force ) {
_log_send_page( $message, $level );
}
return 0;
}
sub _log_send_mail {
=begin wiki
!3 _log_send_mail
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($message, $severity) = @_;
return 0 unless $mail_emailto;
return 0 if $mail_emailto =~ /NONE/i;
my ($subject, $job);
if ( $severity eq 'MESSAGE' ) {
$subject = 'Message from ' . uc $dataenvr;
} else {
$subject = uc($dataenvr). ' Batch Notice';
$message = time2str("%Y/%m/%d %H:%M:%S : ", time) . uc($severity) . " : $script_file : $message";
}
## get the log file contents and append to message
if ( ! $severity eq 'MESSAGE' ) {
if ( -e $log_filefull ) {
$message .= "\nLog Entries:\n";
open my $fh, "<", $log_filefull;
while ( <$fh> ) {
$message .= $_;
}
close $fh;
}
}
MIME::Lite->send('smtp', $mail_server, Timeout => 60);
my $msg = MIME::Lite->new(
From => $mail_from,
To => $mail_emailto,
Subject => $subject,
Data => $message
);
$msg->send;
return 0;
}
sub _log_send_page {
=begin wiki
!3 _log_send_page
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($message, $severity) = @_;
return 0 unless $mail_pagerto;
return 0 if $mail_pagerto =~ /NONE/i;
my ($subject, $job);
if ( $severity eq 'MESSAGE' ) {
$subject = 'Message from ' . uc $dataenvr;
} else {
my $subject = uc($dataenvr). ' Batch Notice';
$message = time2str("%Y/%m/%d %H:%M:%S : ", time) . uc($severity) . " : $script_file : $message";
}
MIME::Lite->send('smtp', $mail_server, Timeout => 60);
my $msg = MIME::Lite->new(
From => $mail_from,
To => $mail_pagerto,
Subject => $subject,
Data => $message
);
$msg->send;
return 0;
}
sub _log_rotate {
=begin wiki
!3 _log_rotate
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($prev,$next,$i,$j);
my $curr = $log_filefull;
my $currn = $curr;
for ($i = $log_gdg; $i > 1; $i--) {
$j = $i - 1;
my $nextgen = sprintf("%0${log_radix}d", $i);
my $prevgen = sprintf("%0${log_radix}d", $j);
$next = "${currn}." . $nextgen; ##. $ext;
$prev = "${currn}." . $prevgen; ##. $ext;
if ( -r $prev && -f $prev ) {
move($prev,$next) or sys_die( "Log move failed: ($prev,$next)" );
}
}
## copy current to next incremental
my $nextgen = sprintf("%0${log_radix}d", 1);
$next = "${currn}." . $nextgen;
copy($curr, $next);
## preserve permissions and status
my @stat = stat $curr;
chmod( $stat[2], $next ) or sys_warn( "log chmod failed: ($next)" );
utime( $stat[8], $stat[9], $next ) or sys_warn( "log utime failed: ($next)" );
chown( $stat[4], $stat[5], $next ) or sys_warn( "log chown failed: ($next)" );
## now truncate the file
truncate $curr, 0 or sys_die( "Could not truncate $curr" );
return 0;
}
sub _db_connect_check_dependent {
=begin wiki
!3 _db_connect_check_dependent
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($dependent_jobname,$wait_duration,$wait_max_secs,$wait_action) = @_;
my $starttime = time;
while ( 1 ) {
if ( _sys_job_dependent($dependent_jobname) ) {
sleep $wait_duration;
my $curtime = time;
if ( $curtime - $starttime > $wait_max_secs ) {
if ( $wait_action =~ m/^run$/ix ) {
log_info( "Maximum dependent job wait time exceeded, starting" );
last;
} else {
sys_die( "Maximum dependent job wait time exceeded, aborting" );
return 1; ## reachable if $sys_test_harness
}
}
} else {
last;
}
}
return 0;
}
sub _db_connect_retry {
=begin wiki
!3 _db_connect_retry
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($db,$un,$pw,$retry_duration,$retry_max_secs) = @_;
my $dbh = 0;
my $starttime = time;
while ( 1 ) {
$dbh = DBI->connect( $db, $un, $pw, { RaiseError => 0, AutoCommit => 0 } );
if ( DBI->errstr ) {
if ( $retry_max_secs < 1 ) {
sys_die( DBI->errstr );
return 1; ## reachable if $sys_test_harness
}
if ( DBI->err == 1017 ) { ## ora invalid account or password
sys_die( DBI->errstr );
return 1; ## reachable if $sys_test_harness
}
log_info( DBI->errstr );
log_info( "Connection retry requested, waiting" );
sleep $retry_duration;
my $curtime = time;
if ( $curtime - $starttime > $retry_max_secs ) {
sys_die( "Maximum connection retry time exceeded, aborting" );
return 1; ## reachable if $sys_test_harness
}
} else {
last;
}
}
return $dbh;
}
sub _db_vdn {
=begin wiki
!3 _db_vdn
Parameters: ( caller_id_string, vdn )
This function accepts a caller id string and a virtual database name. A \
virtual database name is a text string which identifies a database \
connection. If we are running in test mode and the caller is not the \
db_connect function, this function will gracefully shut-down. Otherwise \
it returns either raw database connection information or it returns the \
appropriate database handle and statement handle for the named database.
Returns:
=cut
my ($caller, $vdn) = @_;
my $sth_name = 'sth_default'; ## default statement handle name
## does vdn contains explicit statement handle?
if ( $vdn =~ /\./ ) {
($vdn, $sth_name) = split /\./, $vdn;
}
my ($this_db, $this_inst);
if ( $vdn =~ m/:/x ) { ## does vdn contain explicit instance?
($this_db, $this_inst) = split m/:/, $vdn;
} else {
$this_db = $vdn;
$this_inst = $dbdefenvr{$vdn};
}
if ( ! $dbname{$this_db} ) {
sys_die( "Virtual database name [$vdn] is invalid" );
}
## special return values if caller is 'connect'
if ( $caller eq 'connect' ) {
my $database = $dbconn{$this_db}{$this_inst}{'database'};
my $username = $dbconn{$this_db}{$this_inst}{'username'};
my $password = $dbconn{$this_db}{$this_inst}{'password'};
return ($database, $username, $password);
}
# ## shutdown gracefully if running under the 'test connections' flag
# if ( $opt_test ) {
# log_close( "End connection test: $jobname" );
# sys_end();
# exit 0;
# }
## return database and statement handles for this vdn
my $dbh = $dbhandles{$this_db}{'dbh'};
my $sth = $dbhandles{$vdn}{$sth_name};
return ($dbh, $sth);
}
sub _db_netservice {
=begin wiki
!3 _db_netservice
Parameters: ( vdn )
This function accepts a virtual database name that contains an explicit \
instance. A virtual database name is a text string which identifies a \
database connection. The "network service", i.e., remote database \
connection string is returned from sys_data.conf for the provided instance.
Returns:
=cut
my ($vdni) = shift;
my $netservice = '';
if ( $vdni =~ m/:/x ) { ## vdn contains instance definiton
my ($db, $inst) = split m/:/, $vdni;
_check_array_val( $db, \@databases )
|| sys_die( "Invalid database: [$db]", 0 );
_check_array_val( $inst, [split m/,/, $dbinst{$db}] )
|| sys_die( "Invalid database instance: [$db.$inst]", 0 );
$netservice = $dbconn{$db}{$inst}{netservice};
}
return $netservice;
}
sub _db_proc_build_sql {
=begin wiki
!3 _db_proc_build_sql
Parameters: ( package_name, procedure_name, parameters)
* /parameters/ - parameters is a reference to an array
This function builds a sql statement to execute an Oracle Stored Procedure. \
The sql statement uses generated variable names, e.g., :p1, :p2, :p3, etc. \
This works because functions that use this sql statement all pass parameters \
to the requested stored procedure positionally. The function accepts a \
reference to an array of param in parameters. This is used only to get a \
count of the number of parameters in the procedure's signature.
Returns:
=cut
my ($package, $proc_name, $params) = @_;
my $numparams = scalar @{$params};
if ( $package ) { $proc_name = $package . '.' . $proc_name; }
my $sql = 'BEGIN ' . $proc_name . '(';
for my $i ( 0 .. $numparams - 1 ) {
$sql .= ':p'.$i;
if ( $i < $numparams - 1 ) { $sql .= ','; }
}
$sql .= '); END;';
return $sql;
}
sub _db_sqlloaderx_parse_logfile {
=begin wiki
!3 _db_sqlloaderx_parse_logfile
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $logfile = shift;
%sqlloader_results = (); ## hash of SQL*Loader results
## default values
$sqlloader_results{'skipped'} = "Problem obtaining value";
$sqlloader_results{'read'} = $sqlloader_results{'skipped'};
$sqlloader_results{'rejected'} = $sqlloader_results{'skipped'};
$sqlloader_results{'discarded'} = $sqlloader_results{'skipped'};
$sqlloader_results{'elapsed_time'} = $sqlloader_results{'skipped'};
$sqlloader_results{'cpu_time'} = $sqlloader_results{'skipped'};
my $log = new IO::File "<$logfile";
if (! defined $log) {
sys_warn( "Failed to open SQL*Loader log file $logfile" );
return 1;
}
## skip the first line, check the second for the SQL*Loader declaration
my $line = <$log>;
$line = <$log>;
unless ($line =~ /^SQL\*Loader/) {
sys_warn( 'File does not appear to be a valid SQL*Loader log file' );
return 1;
}
while (<$log>) {
chomp;
if ( m/^Total logical records skipped:\s+(\d+)/ ) {
$sqlloader_results{'skipped'} = $1;
next;
}
if ( m/^Total logical records read:\s+(\d+)/ ) {
$sqlloader_results{'read'} = $1;
next;
}
if ( m/^Total logical records rejected:\s+(\d+)/ ) {
$sqlloader_results{'rejected'} = $1;
next;
}
if ( m/^Total logical records discarded:\s+(\d+)/ ) {
$sqlloader_results{'discarded'} = $1;
next;
}
if( m/^Elapsed time was:\s+(.+)/ ) {
$sqlloader_results{'elapsed_time'} = $1;
next;
}
if( m/^CPU time was:\s+(.+)/ ) {
$sqlloader_results{'cpu_time'} = $1;
next;
}
}
$log->close;
my @results;
push @results, "Skipped: " . $sqlloader_results{'skipped'};
push @results, "Read: " . $sqlloader_results{'read'};
push @results, "Rejected: " . $sqlloader_results{'rejected'};
push @results, "Discarded: " . $sqlloader_results{'discarded'};
push @results, "Elapsed Time: " . $sqlloader_results{'elapsed_time'};
push @results, "CPU Time: " . $sqlloader_results{'cpu_time'};
## return ref to array of results
return \@results;
}
sub _db_proc_bind_inparams {
=begin wiki
!3 _db_proc_bind_inparams
Parameters: ( statement_handle, parameters )
This function binds parameters to a prepared statement. The parameters are \
passed as a ref to an array. This uses the same parameter names as those \
defined by the build_sql function. All parameters are bound as type IN \
parameters.
Returns:
=cut
my ($sth, $params) = @_;
my $numparams = scalar @{$params};
for my $i ( 0 .. $numparams - 1 ) {
my $var = ':p'.$i;
$sth->bind_param( $var, ${$params}[$i] );
}
return $sth;
}
sub _db_proc_bind_outparams {
=begin wiki
!3 _db_proc_bind_outparams
Parameters ( )
This function binds parameters to a prepared statement. The parameters are \
passed as a ref to an array. This uses the same parameter names as those \
defined by the build_sql function. All parameters are bound as type IN \
OUT/OUT parameters.
Returns:
=cut
my ($sth, $params) = @_;
my $numparams = scalar @{$params};
for my $i ( 0 .. $numparams - 1 ) {
my $var = ':p'.$i;
$sth->bind_param_inout( $var, @{$params}[$i], 100 );
}
return $sth;
}
sub _db_proc_bind_inoutparams {
=begin wiki
!3 _db_proc_bind_inoutparams
Parameters: ( )
This function binds parameters to a prepared statement. The parameters are \
passed as a ref to an array. This uses the same parameter names as those \
defined by the build_sql function. All parameters are bound as type IN or \
as type IN OUT/OUT. If the user passes a ref as an array member, that element \
will be bound as IN OUT/OUT. If the users passes a scalar as an array member, \
that element will be bound as a type IN parameter.
Returns:
=cut
my ($sth, $params) = @_;
my $numparams = scalar @{$params};
for my $i ( 0 .. $numparams - 1 ) {
my $var = ':p'.$i;
if ( ref @{$params}[$i] eq 'SCALAR' ) {
$sth->bind_param_inout( $var, @{$params}[$i], 100 );
} else {
$sth->bind_param( $var, ${$params}[$i] );
}
}
return $sth;
}
sub _db_is_oracle {
=begin wiki
!3 _db_is_oracle
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $vdn = shift;
my $inst = $dbdefenvr{$vdn};
my $database = $dbconn{$vdn}{$inst}{'database'}; ## e.g., dbi:Oracle:myinst
if ( $database=~ /^dbi:Oracle:/ ) {
return 1;
}
return 0;
}
sub _db_null {
=begin wiki
!3 _db_null
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my $val = shift;
return '' unless defined $val;
return $val;
}
sub _db_query_to_file_protect {
=begin wiki
!3 _db_query_to_file_protect
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($row, $protect) = @_;
return 0 if scalar @{$protect} < 1;
foreach my $i ( @{$protect} ) {
my $len = length @{$row}[$i];
my $fil = '*'x$len;
@{$row}[$i] = $fil;
}
return 0;
}
sub _check_array_val {
=begin wiki
!3 _check_array_val
Parameters: ( p1, p2, p3 )
Please write this documentation.
Returns:
=cut
my ($val, $arr) = @_;
if ( grep { $_ eq $val } @{$arr} ) {
return 1;
}
return 0;
}
sub _trim {
=begin wiki
!3 _trim
Parameters: ( str )
Trim leading and trailing spaces from a string. Return the trimmed string.
Returns:
=cut
my $str = shift;
$str =~ s/^\s+//;
$str =~ s/\s+$//;
return $str;
}
sub _trim_lead {
=begin wiki
!3 _trim_lead
Parameters: ( str )
Trim leading spaces from a string. Return the trimmed string.
=cut
my $str = shift;
$str =~ s/^\s+//;
return $str;
}
sub _trim_trail {
=begin wiki
!3 _trim_trail
Parameters: ( str )
Trim trailing spaces from a string. Return the trimmed string.
Results:
=cut
my $str = shift;
$str =~ s/\s+$//;
return $str;
}
sub _is_yes {
=begin wiki
!3 _is_yes
Parameters: ( str )
Examing a string and determine if the string indicates 'YES'. The string is \
examined as case insensitive and must be either a 'y' or 'yes'. If so, the \
function returns true (1), otherwise it returns false (0).
You can use this as a conversion function to make tests simpler using a \
technique like this:
% language=Perl
% my $truth = 'Y';
% $truth = _is_yes( $truth );
% # later
% if ( $truth ) {
% # do something
% }
%%
=cut
my $str = shift;
if ( $str =~ /^y$|^yes$/i ) { return 1; }
return 0;
}
sub _is_no {
=begin wiki
!3 _is_no
Parameters: ( str )
Examing a string and determine if the string indicates 'NO'. The string is \
examined as case insensitive and must be either a 'n' or 'no' exactly. If so, \
the function returns true (1), otherwise it returns false (0).
Returns:
=cut
my $str = shift;
if ( $str =~ /^n$|^no$/i ) { return 1; }
return 0;
}
sub END {
=begin wiki
!3 END
Parameters: None
Close all open statement handles and database handles. Statement handles and \
Database handles are stored for us by the database connection function. The \
end function in each loaded plugin is also called here. They are called in \
reverse load order. Send exit notifications if any have been requested.
Returns:
=cut
## remove job information from sys_environment.conf
_sys_job_end();
## disconnect any open database handles
foreach my $vdn ( keys %dbhandles ) {
my $dbh = $dbhandles{$vdn}{'dbh'};
my $sth = $dbhandles{$vdn}{'sth'};
if ( defined $sth && $sth ) { $sth->finish; }
if ( defined $dbh && $dbh ) { $dbh->disconnect; }
}
## call plugin end functions
while ( my $pluginf = pop @plugins ) {
my ($pp, $pf, $pff) = split m/~/, $pluginf;
$pp->end();
}
## send completion notifications
unless ( defined $jobname ) { $jobname = '?'; }
my $msg = "Job $jobname ($script_file) has completed ($errorlevel).";
if ( $opt_notify_email_oncomp ) {
_log_send_mail($msg, 'MESSAGE' );
}
if ( $opt_notify_pager_oncomp ) {
_log_send_page($msg, 'MESSAGE' );
}
}
1;
=begin wiki
----
!1 Dependencies
The following modules are all used by DBIx-JCL.
* English
* Getopt::Long
* Config::IniFiles
* IO::File
* IO::Handle
* IO::LockedFile
* Fcntl
* File::Copy
* File::Bidirectional
* File::Basename
* MIME::Lite
* Date::Format
* Pod::WikiText
* DBI
----
!1 Incompatibilities
None currently documented. Please feel free to notify the author if you have \
concern that you would like to see addressed.
----
!1 Test Support
There are a number of test functions built-in to DBIx-JCL. Please see the \
function reference section for descriptions of all the testing functions.
----
!1 Tips
Here are some tips for using job scripts. (A job script is any perl script \
that uses the DBIx-JCL Module.
!2 Verbose and Very Verbose
If you are running jobs from the console and you want tactile feedback, use \
the Verbose C<-v> option. If your job is failing and your not sure why, turn \
on the Very Verbose C<-vv> option. Very Verbose gives you everything that \
Verbose gives you, plus more.
!2 Required Options
A "Run job" option is always required. This is to avoid accidentally invoking \
a job script.
!2 Built-in Display Features
There are several built-in display features that you will find useful. When \
you use the Help option, C<-h> and C<-ha>, these will be listed under the \
heading of "Information Options". The most useful is possibly the C<-dl> \
option, which will display the last log file generated by the script that you \
are currently running.
!2 Use the Test Options
Use the /-t/ option to invoke the job script and run it to the point of \
database connection and then exit after database connections have been made.
Use the /-tc/ option to test any database connection interactively without \
invoking the current job script. Very handy for diagnostic purposes.
!2 Multiple Database Connections
You can set up jobs that make multiple connections to the same database. To \
do that, you simply add another set of connection parameters in your data.conf \
file. So if for example you have a database named 'xyz1' in your list of \
databases in %data.conf%, add another database named 'xyz2' and duplicate all \
other connection parameters from 'xyz1' under the new key 'xyz2'.
!2 Global Variables
There are a number of global variables that are automatically imported into \
your script's namespace. These are listed below with a brief explanation of \
each.
* %$path_bin_dir # path to bin directory%
* %$path_lib_dir # path to lib directory%
* %$path_log_dir # path to log directory%
* %$path_load_dir # path to load directory%
* %$path_extr_dir # path to extract directo%ry
* %$path_prev_dir # path to store previous vrsion files%
* %$path_scripts_dir # path to scripts directory%
* %$mail_server # mail server address%
* %$mail_from # from email address%
* %$mail_emailto # email to address list%
* %$mail_pagerto # pager to address list%
* %$mail_email_levels # log levels which initiate email notifications%
* %$mail_pager_levels # log levels which initiate pager notifications%
* %$log_file # log file filename%
* %$log_filefull # full path to log filename%
* %$log_logging_levels # log levels which initiate log mesages%
* %$log_console_levels # log levels which initiate console messages%
* %$log_gdg # number of log archive files to maintain%
Default values for all of these are defined in system conf files. The value \
of many of these can be set at runtime using command line options.
A special global variable defines the current database environment. This is \
the $dataenvr variable.
----
!1 Source Code Validation
In order to help maintain consistency across an entire library of job \
scripts. Several aspects of script files are check for compliance before \
the job will be executed. The following rules are checked before a job \
will be run by DBIx-JCL
/Header Checks/
There must be valid %##@@% and %##$$% statements. These statements can be \
used to help manage script libraries. The %##$$% statement is also used by \
the display jobs option to provide a brief description of each job.
/Documentation Checks/
There needs to be valid Pod containing at least a DESCRIPTION section, a \
RECOVERY NOTES section, and a DEPENDENCIES section in each job script.
----
!1 File And Directory Permissions
This information is here to document one approach to file and directory \
permissions. You should not adopt these for your use without careful \
consideration and testing.
All files owned by the account which processes batch jobs should be set to \
permission level 750, which will give owner rwx, group r-x, and all others no \
access.
% language=Ini_Files
% >chmod 750 filename
%
% 7 - owner permissions (rwx) i.e., read & write & execute
% 5 - group permissions (r-x) i.e., read & execute
% 0 - world permissions (---) i.e., none
%%
All directories owned by the account which processes batch jobs should \
normally be set to permission level 750.
Permission reference table:
|0 |--- |no access|
|1 |--x |execute|
|2 |-w- |write|
|3 |-wx |write and execute|
|4 |r-- |read|
|5 |r-x |read and execute|
|6 |rw- |read and write|
|7 |rwx |read write execute (full access)|
----
!1 Plugins
DBIx-JCL supports plugin modules using a simple plugin architecture. This \
will allow you to write your own modules and have them loaded at runtime to \
provide additional functionality for your job scripts. For example, you might \
want to write a module that uses http to turn off your web site before some \
processing in your batch job occurs.
Plugin modules are simple Perl modules with no exported functions or \
variables. Here is a trivial example of a plugin module:
% language=Perl
% package TestPlugin1;
%
% use strict;
% use warnings;
%
% my $tp_num = 0;
%
% sub start {
% my ($path_conf_dir, $path_plugin_dir, $dataenvr) = @_;
% $tp_num = 100;
% print "TestPlugin1 start function\n";
% }
%
% sub plugin_main {
% my $n = shift;
% $tp_num += $n;
% return $tp_num;
% }
%
% sub tp_add {
% my $n = shift;
% $tp_num += $n;
% return $tp_num;
% }
%
% sub end {
% print "TestPlugin1 end function\n";
% }
%
% 1;
%%
There are three functions that plugin modules are required to implement, a \
C, a C, and an C. The start and end functions \
are called automatically for you on load and script termination. The address \
to the C function is returned to you when your plugin is \
loaded. All of your plugin code can be implemented in C, or in \
additional functions that you supply. The decision will vary depending on \
your plugin's needs. All functions in your plugin module are callable, but \
the symantics vary.
!2 Loading your plugin
Your plugin is loaded using the C function. This function \
takes two parameters, The file name used by your plugin (without the .pm \
extension) and the package name used by your plugin. All plugins need to be \
installed in a plugins directory which has been specified in the system.conf \
file. For example, if you created the plugin shown above and placed it in a \
file named TestPlugin1.pm, you would load the plugin like this:
sys_init_plugin( 'TestPlugin1', 'TestPlugin1' );
or
my $plugin1 = 'TestPlugin1';
sys_init_plugin( $plugin1, $plugin1 );
!2 Calling functions in plugin modules
There are three ways (probably more) to call functions in your plugin.
B>
Use the fully qualified package name and function name.
sys_init_plugin( 'TestPlugin1', 'TestPlugin1' );
later
TestPlugin1::tp_add(1);
B>
If you are going to call your plugin from serveral places in your script, \
you might prefer to take this approach.
sys_init_plugin( 'TestPlugin1', 'TestPlugin1' );
my $plug_1 = \&TestPlugin1::tp_add;
later
$plug_1->(1);
B>
Probably the simplest approach it to implement as much of your plugin's code \
as possible within the C function. Then use the supplied \
coderef to execute your plugin.
my $plug1 = sys_init_plugin( 'TestPlugin1', 'TestPlugin1' );
later
$plug1->(1);
----
!1 Exported Variables
The following variables are available for use in job scripts and are \
exported by default.
|!Variable |Mod?|Description|
|%$path_bin_dir% |No |path to bin directory|
|%$path_lib_dir% |No |path to lib directory|
|%$path_log_dir% |No |path to log directory|
|%$path_load_dir% |No |path to load data directory|
|%$path_extr_dir% |No |path to extract data directory|
|%$path_prev_dir% |No |path to previous version files|
|%$path_scripts_dir% |No |path to scripts directory|
|%$mail_server% |. |mail server|
|%$mail_from% |. |mail from address|
|%$mail_emailto% |. |email to address list|
|%$mail_pagerto% |. |pager to address list|
|%$mail_email_levels% |. |email severity/notification levels|
|%$mail_pager_levels% |. |pager severity/notification levels|
|%$log_file% |No |name of log file|
|%$log_filefull% |No |full name including path of log file|
|%$log_logging_levels% |. |severity levels for log file logging|
|%$log_console_levels% |. |severity levels for console logging|
|%$log_gdg% |. |number of generations for log archiving|
|%$dataenvr% |No |environment variable which holds default datbase/instance |
|%$commandline_ext% |No |extra command variables passed to job script|
|%$errorlevel% |No |.|
Variables with "No" should not be modified.
----
!1 Bugs And Limitations
Please report all bugs to the author. Every attempt will be made to \
incorporate bug fixes into future releases of this package.
----
!1 Author
Brad Adkins brad.j.adkins@gmail.com.
You may contact the author regarding this module at dbijcl@gmail.com.
----
!1 License And Copyright
Copyright (c) 2008, Brad Adkins. All rights reserved.
This software may be freely distributed under the same terms as Perl itself.
----
=cut