Beruflich Dokumente
Kultur Dokumente
Training Manual
RTK-BDS-00
07/10/00
Retek 9.0
Copyright Notice
Copyright
Trademarks
Retek 9.0 is a trademark of Retek Inc.
2000 Retek Inc. and its subsidiaries. All rights reserved. Retek and Active Retail Intelligence are registered
trademarks of Retek Inc. Windows is a registered trademark of Microsoft Corporation in the U.S. and/or other
countries. Microstrategy is a trademark of Microstrategy, Inc. All other trademarks and registered trademarks are
the property of their respective holders.
All other product names mentioned are trademarks or registered trademarks of their respective owners and should be
treated as such.
Contents
Contents
Module 1: Course Overview ....................................................................1
Lesson 1: Course Overview........................................................................................................ 2
Contents
ii
Contents
iii
Contents
iv
07/10/00
Course Objectives
After completing this course, you will be able to:
Effectively use Reteks batch programming languages and tools.
Identify how and why Retek uses standards in batch coding.
Identify how Retek uses Pro*C and take advantage of Pro*C functions
that enhance performance and overall efficiency.
Manage error handling.
Improve performance, using array processing.
Understand how to dynamically size arrays.
Use interface download programs to write data to a file that can be
used by other programs.
Use interface upload programs to transfer data from a file to the Retek
database.
Understand how both table based and file based restart recovery
functions are able to restart programs from fatal processing errors.
Understand how multithreading improves the actual processing of
data.
Understand the layout programs in the batch schedule.
Prerequisites
Participants in this course are expected to have two prerequisites:
1. Before you begin this training course, you should have a basic
knowledge of Pro*C and C programming. In addition, you should
understand basic SQL, PL/SQL, and database concepts.
2. Experience using a command line operating system interface (e.g.
UNIX, DOS, VMS).
Lesson Structure
The following is a list of the sections that make up a lesson and a
description of each sections purpose.
Module Overview
The Overview section provides a brief description of the topic and its
context within the class.
Objectives
The Objectives section lists lesson goals and expectations.
Lessons
The subject matter is broken down into discreet lessons to assist in
understanding and comprehension.
Exercises
The Exercises section provides coding exercises relevant to each lesson.
Key Information
Key Information lists key fields or provide tables of information about the
topic.
Self-Evaluation
The Self-Evaluation chart may be used to assess your understanding and a
to note areas you may wish to review or research.
Summary
This section provides a synopsis of the lesson.
Whats Next
This section briefly describes the next lesson or module.
Summary
In this module, you learned overall training objectives of this course and
reviewed the components included in this manual.
Whats Next?
In the next module, you will be introduced to the basic vocabulary and
concepts of batch programs and how they contribute to Reteks product
offerings.
07/10/00
Module Overview
Batch programs are only one component of Reteks client server
application architecture. This module describes the batch programming
context by defining some of the basic vocabulary used by batch
programmers and thorough descriptions of actual programs.
Objectives
After completing this module, you will be able to:
= Describe the purpose of batch programs in the context of Reteks
online modules and external systems.
= Identify four general types of Retek batch programs.
10
11
12
Phases
A brief description of the phases:
= Phase 0: A maintenance pass.
= Phase 1: Primes tables for interfacing with external systems.
= Phase 2: Processes external interfaces.
= Phase 3: Processes replenishment, ordering, and stock ledger.
= Phase 4: Outputs external interface files and rebuilds changed system
info.
= Date Set: Increments system date.
= Ad Hoc: Runs as required. May not have phase restrictions.
13
Daily Purge
The Daily Record Deletion (dlyprg) module is executed nightly to delete
all of the records in the system that have been marked for deletion by the
online system during the day. Before deletion, all relations are checked to
ensure that the record can be deleted. For example, if a staple SKU has
been marked for deletion, this module will check that the SKU has not
been placed on order later in the day. If relations are found to exist, they
are noted on a report that will itemize any problems found when running
the module, and the record will not be deleted that night.
Sales Posting
The Sales Posting batch component is executed in order to process sales
data brought in from an external system while also maintaining sales
history within the Retek system. The POS Upload (posupld, phase 2)
module is run on a daily basis at the beginning of the batch processing.
The purpose of this batch module is to process sales and return details
from an external point of sale system. Sales history, using Sales History
Rollup by Department, Class, and Subclass (hstbld, phase 3) module, is
then collected for each SKU in the system. The collected history
information is stored at the subclass, class, and department level. For each
SKU, the data to be saved includes sales quantity, value, gross profit, and
sales rate. The Sales History Rollup by Department, Class and Subclass
(hstbld) module should be run with the parameter Weekly. After sales
history is built, the Pre-Post module is run in order to maintain the rebuild
tables.
Clearance
The Clearance function is designed to provide an orderly and efficient
framework for the authorization and control of clearance markdowns. In
order to do this, RMS uses the concept of events, similar to the events used
in Pricing. Clearance events allow multiple markdowns of the same
item/zone. Also, the Clearance price for the item is not permanentthe
retail price will be reset to the regular retail after the reset date specified
for the event has passed. Therefore, the batch processes that run in the
Clearance function of the RMS have two distinct parts processing
markdown prices and processing reset prices.
14
Exercises
Having read the example descriptions of batch functionality above, how
would you answer the following questions?
1. What are the relationships among online, batch and external
systems with respect to the functionality described?
4. Can you explain the relative position in the batch cycle of any one
program?
15
16
Key Information
Definitions: batch, batch programming, batch cycle, batch window
Four general types of batch programs:
= Upload
= Download
= Functional maintenance
= System maintenance
Whats Next
Before you can begin creating batch programs, you must be familiar with
Reteks batch programming tools and environment. The next module will
introduce you to the UNIX operating system and discuss its suitability as
an environment for C programming.
07/10/00
Module Overview
To date, Reteks core products have been run exclusively on UNIX
servers. The development environment in Minneapolis is designed with
this in mind, and batch programming is done using a UNIX server, file
system, and toolset. This module introduces many of the concepts and
tools needed to work effectively in this environment.
Objectives
After completing this module, you will be able to:
= Connect to the development server.
= Navigate the file system from a command line interface.
= Create text files using vi or xemacs.
= Know where to find more information about the UNIX environment.
18
19
Choose a password that is easy to remember and type, but not easy for
someone else to guess. It is recommended that your password contain a
combination of alpha and numeric characters. After you have successfully
changed your password, end this telnet session.
20
Exceed
There are a variety of client applications available to provide access to the
Retek UNIX servers. Some are easier or faster to use than others. The
client most commonly used at Retek and the one that is supported by the
IS department is Exceed. Exceed should already be loaded and configured
on your machine. You should find an entry in the programs submenu of
your start menu.
Exceed is actually a suite of applications that perform a variety of
communications and networking tasks. Notice, for example, there is a
telnet client available. You can try connecting to mspdev01 just as you
did with the Windows telnet client. You may want to explore the various
applications and configurations offered by Exceed on your own time.
For now, launch the application Exceed found within the Exceed menu.
You will be prompted to log in. Use your username, and the new
password you just created.
Logging in to the Exceed X Windows server takes a few minutes in its
initial configuration.
Exceed emulates Solaris Open Windows, desktop environment with X
Windows applications running on it. In its original configuration, Exceed
will appear as a full screen desktop. The file manager window, help
window and toolbar are analogous in many ways to the Windows
Explorer, help and start menu on your Windows desktop.
If you prefer, Exceed can be reconfigured to eliminate the desktop look
and feel and simply present windows floating on your Windows desktop.
To do this select the tools/configuration menu found by right clicking on
the Exceed button on Windows taskbar. Double click on the Window
Mode option and select the multiple radio button. Hint: you may also
want to explore Exceeds passive mode.
21
22
Vi Cheat Sheet
Two secrets to success
1. Know what mode you are in.
2. Dont forget caps lock!
Three modes
1. Insert
2. Edit
3. Command line
Changing modes:
Esc
I,i,a,A
23
Edit mode:
h,j,k,l
ctrl-f, ctrl-b
x
dw, dd, D
cw
yy
p
u
/
n,N
Command line:
w
q!
x
<a line number>
Xemacs
Emacs and xemacs are not found as widely in the UNIX universe. Still
they are in wide enough distribution that knowing this family of text
editors is a highly transferable skill. For most people, learning xemacs is a
much more pleasant experience than learning vi because it supports many
familiar mouse-driven operations (cut & paste, menus, buttons, etc.).
Dont be fooled by how easy it is to begin using, however. Xemacs is a
very powerful and programmable environment. If xemacs GUI interface
is relied upon entirely, then it is certainly less efficient to use than vi. But
as one learns keystroke commands and programming for xemacs,
productivity increases dramatically.
Key features of xemacs for working at Retek include integrated help text,
C mode, auto-tabbing, syntax highlighting, multiple frames, panes and
buffers, and parenthesis matching.
24
Two frequently used terms in emacs documentation are the control-, and
meta-. Emacs documentation refers to these keystrokes as C- and M-.
C- denotes that the control key and some other key be depressed
simultaneously (often followed by another keystroke). For example, C-x
C-s (control-x, control-s) saves changes in the current file to disk.
M- requires first hitting the metakey (mapped to the esc key on your
keyboard), then some other sequence of keys. For example, M-x % (meta,
x, % one at a time) begins a search and replace operation.
As advertised
Move cursor next line,
previous line, back, forward,
beginning of current line,
end of current line
Move cursor forward,
backward one word, up,
down paragraph
Move cursor to beginning or
end of buffer
Go to line <number>
M-w
C-y
Paste
C-d
Delete
25
Keyboard macros:
C-x ( , C-x )
C-e
C-<number> <command>
26
27
User interface
ksh
Standard
Libraries
fopen( )
printf( )
kernel
Process
Control
File
System
Hardware
Shell
So, if the operating system is all these layers and the user interface
includes programs and libraries, what does a user interact when typing
commands in a command line interface? The short answer is a shell. Put
simply, a shell is a program that interprets commands and passes them on
to the kernel for execution.
There are a variety of shells to choose from. Each has slightly different
features and its own zealous followers. The Retek programming
environment is set up to use the Korn shellalso known as the k-shell, or
ksh. If you have no experience with another shell, then learn the k-shell.
If you are accustomed to using some other shell, feel free to use it but
realize that Reteks scripts are written for ksh.
28
Shell Variables
Like all good programs a shell has variables. Use echo to view the current
value of a shell variable. The dollar sign, $, is used to signify a variable
name. Try:
echo $ORACLE_HOME
A useful feature of a shell is the user can create new variables and assign
them values. This often serves the purpose of holding a path to a directory
to be repeatedly revisited during a session.
For example, one day you are writing a C program that uses
communication protocols. This is not a familiar job for you so you need
to refer often to the library header files in the directory
/usr/include/protocols. You could type
cd/usr/include/protocols every time you want to visit the directory,
but this would quickly grow tiresome. Knowing about shell variables you
decide to create a one letter variable name, n, to hold this directory. For
the remainder of the session you can access the protocols directory by
simply typing cd $n.
Command History
Another convenience provided by your shell is that it remembers the
commands you type. When you are performing a task, or sequence of
tasks over and over, you can use this command history to avoid retyping
the command(s). Your system should be configured to use the esc and the
vi editor commands to allow you to scroll through and edit your command
history.
Filename Completion
Another keystroke-saving feature is filename completion. This should be
mapped to esc-\ on your system. Use it whenever your are typing
filenames and paths.
29
Pattern Matching
Regular expressions are expressions using metacharacters to specify
patterns in text. In the context of the shell they are used to either specify
directory or filenames when the exact name is unknown, or to specify
multiple files or directories with similar names.
For example, the * character is used to replace any set of zero or more
characters. If a directory contains hundreds of files and you want to view
information about only those that begin rs and end .pc you could
either list all the files then scroll up and down to find those you are
interested in, or you could issue the command
ls rs*.pc
Meaning
Zero or more instances of any
character
Matches any single character
Matches any one character listed.
Ranges may be listed as [a-c1-3]
Subshells
Interestingly, when a command is issued at the command line it is not run
in the current shell. Rather, a new shell program is started, the new shell
runs the command, and then the new shell is discarded leaving the user
back at the original shell when control returns. The new shell is a child
process of the original shell and inherits many of its parents properties.
Most of the time this behavior is transparent to the user and can be
ignored. However, occasionally it is necessary to run a program in the
current shell. This is accomplished by preceding a command with a
period-space.
. /home/daviesr/.profile
This example shows the .profile executable being run in the current shell.
Running a command in the current shell may be desirable if it establishes
values for shell variables. If such a file were run in a subshell, the
variables would only have the lifespan of the subshell.
In order for a subshell to inherit the value of its parent shells variables the
export keyword must be used in the variables declaration. Consider these
four examples.
30
Setup:
UNIX> echo echo $hi > echotest
UNIX> chmod 755 echotest
UNIX> hi=hello
1. UNIX> echotest
UNIX>
2. UNIX> . echotest
hello
UNIX>
3. UNIX> export hi
UNIX> echotest
hello
UNIX>
31
What Is a File?
In UNIX there are two answers:
= A stream of bytes
= Everything
One of the elegant design decisions made by the creators of UNIX is that
files have no internal structure. Files of all types are merely stored, read
and written as a sequence of bytes.
Furthermore, everything is considered to be a file. It is up to the programs
that use a file to interpret its bytes in an appropriate way. For example,
the kernel does not know the difference between a programs source code
text file and its executable binary file.
Below is a list of some files. Do you normally think of these objects as
files?
= A text file
= An program executable
= A keyboard
= A terminal window
= A disk drive
= A communication session
Experiment: use od c to view the contents of a text file, a directory and
an executable.
32
Standard Files
Every process has three files defined for it, standard in, standard out and
standard error. These are commonly written stdin, stdout, and stderr.
File
Shorthand
Default
stdin
0
Keyboard
stdout
1
Terminal
stderr
2
Terminal
In light of the small sharp tools philosophy of UNIX, perhaps its most
powerful feature redirection. Redirection allows stdin, stdout and stderr to
be specified, rather than defaulted. The greater-than >, less-than < and
pipe | characters are used for this purpose. Try these examples:
1. UNIX> ps ef
UNIX> ps ef > processes.txt
2. UNIX> cat
UNIX> cat < processes.txt
3. UNIX> cat < processes.txt > junk.txt
4. UNIX> ps -ef | more
Another example shows a useful way to save compiling errors for viewing
with a text editor. This can be useful when a program is generating many
pages of compiler errors and warnings.
UNIX> hcomp8 my_program 1>comp.out 2>&1
Now that the concept of the UNIX kernel treating everything as a file is
familiar, terms like file, directory, keyboard, terminal, etc. can revert back
to their more pedestrian meanings as long as you can, when necessary,
recall how UNIX handles them.
33
File Hierarchy
All directories and files are organized in a tree-like hierarchy with a single
starting point (root). Any directory can have many subdirectories but only
one parent directory. For example, a users directory structure might look
like this:
/
/home
/daviesr
/bin
/letters
/scripts /binary
/projects
/sirs
Note the root directory (by definition the only directory without a parent)
is signified by a single forward slash, /. Notice too that each directory is
shown with a / before it. This character serves to separate directory
names when listed together. For example, this users letters directory can
be written
/home/daviesr/letters
Symbol
/
.
..
~
Meaning
Root, or separator
Current directory
Parent directory
Home
As a result of the UNIX file hierarchy structure any file or directory can be
stated unambiguously by naming its entire path from root. This is known
as an absolute path. For example, in the above set of directories, the
absolute path to the rtk70 project directory is:
/home/daviesr/projects/rtk70
34
Furthermore, using the three special symbols { / . ..} any file or directory
can be specified relative to any other. This is known as a relative path.
For example, in the directory structure shown above, the relative path
from binary to sirs is:
../../sirs
Search Path
If everything in UNIX is a file, what do you suppose the commands you
have been typing in are? Files, of course! There is no special object type
command in UNIX that has special properties and magically performs
some behavior when typed. Commands are simply programs (usually
written in C or as a shell script). A program is executed when its name is
typed at the command line.
But how does the shell know where to find the program? There are
thousands of directories in a typical file system. Is it reasonable to search
all of them until a file with the name of the command is encountered?
Probably not, but for a moment, assume that it is. What if there are more
than one file with the same name? How does the shell know which is
desired by the user?
The answer to all these questions is in your $PATH . Try
UNIX> echo $PATH
35
Control Files
There are a number of files in your home directory that control aspects of
your environment. Here is a listing of some you may see in your home
directory:
-rw-r--r-1 you dev
.DISPLAY
-rw------1 you dev
.Xauthority
-rw-r--r-1 you dev
.Xdefaults
-rw-r--r-1 you dev
.ab_library
-rwxr-xr-x
1 you dev
-rw-r--r-1 you dev
.default.wst
drwxr-xr-x 12 you dev
-rwxr-xr-x
1 you dev
.dtprofile
-rwxr-xr-x
1 you dev
-rw-r--r-1 you dev
.eserve-options
-rwxr-xr-x
1 you dev
.profile
-rw------1 you dev
.rms_user
-rw------1 you dev
.sh_history
-rw-r--r-1 you dev
.workshop-options
-rw-r--r-1 you dev
.workshoprc
-rw-r--r-1 you dev
.xemacs-options
15 May
7 11:41
735 May
4 17:48
300 Aug
1998
186 Jan 16
1998
6 19:03
1452 May
7 12:21
1998
36
$h
oracle/
log/
Output directory
for batch error files
sqlplus/
$s
lst/
Library object
files
proc/
lib/
$c
$l
src/
Pro*C
library
source and
header files
bin/
Executable
Pro*C
programs
etc/
Miscellaneous
schedule
documentation
lst/
Pro*C
object files
src/
Pro*C
source
files
*** dev, tst, and prd all have the identical structure
For ease of navigation several environment variables are set up for you by
.profile. Of them, $h, $s, $l, and $c are shown.
SQL*PLUS
With the assistance of some shell variables, sql*plus sessions can be
started from with the UNIX environment with the command, sp. If you
fail to connect directly with this command or if you connect to the wrong
schema, you may want to change the values of the appropriate shell
variables.
The variables used to create your connect string are:
$MMUSER/$PASSWORD@$ORACLE_SID.
37
38
Awk
Shell Programming
It is sometimes convenient to create a new command out of a sequence of
existing commands, or even one long command. If you find yourself
typing the same command(s) repeatedly, you may want to consider
creating a new command.
Shell programs are text files containing one or more commands to be
interpreted and processed by the shell just as if they had been entered at a
command line. Control structures like for, while, and if are available.
Many shell programs are written by individual programmers for their own
use. Others (usually more complex) are written for a project team.
Examples of the latter might include change environment or compilation
scripts.
39
Exercises
Basic commands:
1. Print to stdout your current position in the file hierarchy.
2. Change directories to the top of the hierarchy one level at a time.
3. Go home.
4. Go back to root with one command.
5. Locate a poem about winters in Wyoming somewhere in the
instructors files.
6. Create a directory to hold poetry.
7. Copy the second poem in the same directory as the one found in #5
into your new directory.
Text Editors:
Choose a text editor (or practice both) and play with your copy the poetry
file (#7 above). Search for text, move paragraphs, insert text, append text,
replace all occurrences of existing text, etc
Advanced commands
1. Make sure your .rms_user has the correct permissions. If not,
change them.
2. Print to stdout all environment variables that contain ORACLE.
3. List all the processes running with your user as the process owner.
Can you explain each?
4. Find the version of refresh you are configured to use. Create your
own bin directory. Copy refresh into your bin directory. (Hint:
`<command>` can be used to run one command inside another).
Change your environment so your local version of refresh is used.
Whats Next
You now are familiar with the operating environment and at least one text
editor used in Retek batch programming. Next, you will explore some
important Pro*C programming extensions to the C Programming
language.
40
07/10/00
42
Module Overview
In this module, you will learn about Pro*C, Oracles precompiler that
allows programmers to embed SQL statements within C code. You will
learn how to embed the SQL statements, how C variables interact with
Oracle statements, and how Pro*C handles constructs that exist in Oracle
but not in C (such as NULL values). Finally, error codes returned by SQL
will be discussed.
Objectives
After completing this module, you will be able to:
= Write a C program with SQL statements embedded in it.
= Define the differences between Oracle and C variables and know the
correct method to overcome these differences.
= Handle Oracle NULL values in a C program three different ways.
= Embed a PL/SQL block in a C program.
= Interpret signals that SQL calls return correctly.
43
44
If you try compiling and you just get a message saying that dependencies
are up to date instead of getting the actual executable, it is because there is
a more recent version of the program that you are trying to compile in the
$c directory. (This will not normally happen--usually you compile a
program locally after you've just made changes to it). To get around this
problem, use the UNIX command "touch" to update the file date, like this:
>touch programname.pc
45
This line tells the Pro*C precompiler that the program has SQL statements
that will need to be interpreted in it.
long SQLCODE;
46
Oracle Type
NUMBER
VARCHAR2
DATE
ROWID
C Type
float, double, int, short,
long, char*
char*
char*
char*
Note that because DATE and ROWID types do not have any direct
counterparts in C, C strings are used to hold the data. Appropriate
conversion functions should be used when fetching or inserting values of
these types. Note that no explicit conversions are needed between
NUMBER and char* and vice versa, Oracle does these conversions
implicitly.
Character strings in C (char*) are not exactly like those in Oracle
(VARCHAR2). The difference is in how the length of a character string is
determined: C places a null character (\0) at the end of the string, while
Oracle keeps track of a separate field that contains the length. If a
VARCHAR2 is fetched directly into a C string, the C string wont have a
null terminator. Likewise, if a C string is inserted directly into a
VARCHAR2, the VARCHAR2s length field wont have a value. To
solve this problem, the program must explicitly tell the Pro*C precompiler
to create the code to correctly convert between these two types. The way
this is done is with the following statement:
47
For example:
char ls_my_string[18];
char ls_store_string[NULL_STORE];
EXEC SQL VAR ls_my_string IS STRING(18);
EXEC SQL VAR ls_store_string IS STRING(NULL_STORE);
Note: The IS STRING statement uses parentheses around the length, not
brackets.
The IS STRING statement has been used throughout Retek batch code.
But familiarity breeds contempt, however, and it was soon discovered
there was a way to do without the IS STRING by using a switch during
the pre-compile process. This switch is invisible to the coder, but its
effects are not. It simply tells Oracle to treat ALL character arrays as
strings. This means you do not need to add the IS STRING statements -with one very important exception: if the string has not been explicitly
given a length and you are fetching into the variable.
Discussion: When does this occur?
48
49
For example:
EXEC SQL DECLARE c_get_vdate CURSOR FOR
SELECT TO_CHAR(vdate,YYYYMMDD)
FROM period;
50
Pro*C cursors are opened, fetched, and closed, just like in PL/SQL:
/* This function gets the date ii_days_ahead after the
vdate */
int get_future_date(char *os_future_date, int
ii_days_ahead)
{
EXEC SQL VAR os_future_date IS STRING(NULL_DATE);
/* notice that C variables can be used in select */
/* statements, too. */
EXEC SQL DECLARE c_get_vdate CURSOR FOR
SELECT TO_CHAR(vdate + :ii_days_ahead,YYYYMMDD)
FROM period;
EXEC SQL OPEN c_get_vdate;
EXEC SQL FETCH c_get_vdate INTO :os_future_date;
EXEC SQL CLOSE c_get_vdate;
return (0);
}
There are a few important things to note about cursors in Retek batch
programming. First, implicit cursors are never used. Second, it is
standard to close cursors only in the case where the cursor is fetched from
only once as is the case most often in the init() routine. When the program
ends, the cursors will be closed automatically and more efficiently, so the
practice has been to not explicitly close them except in init() or where they
are fetched from once. Lastly, C variables used in the where clause of
the cursor are known forever more as bind variables.
The PRO-C precompiler and the Oracle database can be very helpful in
handling bind variables. As is done most often in Retek batch programs,
many number fields in the Oracle database are handled as strings. This
increases the robustness of the software, as it is less vulnerable to errors
of precision. Oracle will convert these strings when used as a number in
a where clause for you. An example:
EXEC SQL DECLARE c_item_info CURSOR FOR
SELECT dept,
system_ind
FROM desc_look
WHERE sku = :is_item;
Pro*C and Oracle will make the conversion between numbers and strings
automatically. Dates, on the other hand, should always be explicitly
converted to a date variable using the TO_DATE function.
51
Indicator Variables
Indicator variables are simply C variables of type short that indicate
whether the variable theyre attached to is NULL. They are attached to a
variable by being listed directly after said variable in a SQL statement.
For example:
{
char ls_default_wh[NULL_WH];
short li_wh_null_ind;
EXEC SQL VAR ls_default_wh IS STRING(NULL_WH);
EXEC SQL DECLARE c_store_info CURSOR FOR
SELECT default_wh,
store_name
FROM store
WHERE store = :is_store;
...
/* default_wh is a nullable column on store, and so
ls_default_wh */
/* needs an indicator variable, li_wh_null_ind */
FETCH c_store_info INTO :ls_default_wh:li_wh_null_ind,
:ls_store_name;
...
}
52
The same method can be used when inserting NULL values into the
database. The program simply must manually set the indicator variable to
1 if the value is NULL or 0 if it isnt before inserting or updating. For
example:
{
...
/* in this example, were assuming that if
ls_default_wh is */
/* empty, it means we want to put NULL on the database
*/
if (strcmp(ls_default_wh,) == 0)
li_wh_null_ind := -1;
else
li_wh_null_ind := 0;
EXEC SQL UPDATE store
SET default_wh =
ls_default_wh:li_wh_null_ind
WHERE store = :is_store;
...
}
NVL
Another option for dealing with variables that may be NULL is to use
Oracles NVL function. Selecting NVL(database column, new value) will
return the database column value if it is not NULL and the new value if
the database column is NULL. The new value can be either a number or a
character string. For example:
EXEC SQL DECLARE c_info CURSOR FOR
SELECT NVL(dept,1),
NVL(TO_CHAR(orig_approval_date,YYYYMMDD),:ps_vdate)
FROM ordhead;
...
EXEC SQL FETCH c_info INTO :ls_dept,
:ls_approve_date;
Here, ls_dept will be 1 if dept is NULL (and have the value of dept
otherwise), and ls_approve_date will be the same as ps_vdate if the
orig_approval_date was NULL.
53
DECODE
The Oracle DECODE function can also be used to change the value of
variables.
The usual use is
DECODE(<value to decode>,
<match value>, <return value if decode value
matches>,
<return value if decode value doesnt match>)
For example:
DECODE(alloc_detail.wh,
-1, alloc_detail.store,
alloc_detail.wh)
54
For example:
if (strcmp(ps_vdate,) == 0)
{
EXEC SQL EXECUTE
DECLARE
L_plsql_variable DATE;
BEGIN
L_plsql_variable := GET_VDATE;
:ps_vdate :=
TO_CHAR(L_plsql_variable,YYYYMMDD);
END;
END-EXEC;
if (strcmp(ps_vdate,19971225) == 0)
...
PL/SQL blocks are used in Retek Pro*C programs for only one
reason: to call stored PL/SQL functions or procedures. A large
amount of time overhead is involved in the context switch between C and
PL/SQL, which greatly reduces the efficiency of a program, so try to avoid
PL/SQL blocks unless absolutely necessary, such as when calling a
PL/SQL stored procedure.
55
The problems with C and Oracles NULL value also occur in PL/SQL
blocks. To solve the problem, rather than using indicator variables (which
tend to be unwieldy), it is simpler to create a PL/SQL variable to receive
the potentially NULL value, and then check it and fill in the C variable
appropriately. For example:
long ll_dept_no;
char ls_order_no[NULL_ORDER_NO];
EXEC SQL VAR ls_order_no IS STRING(NULL_ORDER_NO);
...
/* GET_ORDER_DEPT is a function that will return */
/* the department associated with the inputted order, */
/* if one exists. If not, it will return NULL. */
EXEC SQL EXECUTE
DECLARE
L_dept ordhead.dept%TYPE;
BEGIN
/* We have to use a PL/SQL variable to hold the
department */
/* because a bind variable would fail if
GET_ORDER_DEPT */
/* returned NULL. */
L_dept := GET_ORDER_DEPT(:ls_order_no);
if L_dept is not NULL then
/* This statement would fail if L_dept was NULL
*/
:ll_dept_no := L_dept;
else
:ll_dept_no := -1;
end if;
END;
END-EXEC;
56
Message Text
Successful completion.
Unique key constraint
violation.
Invalid cursor.
Cannot insert NULL into
NOT NULL column.
Inserted value too large
for column.
No data found.
NULL fetched into a
bind variable with no
indicator variable.
Several of these signals are common enough that Retek has set up macros
to check for certain conditions. Here are the definitions from the header
std_err.h:
#define NO_DATA_FOUND
#define SQL_ERROR_FOUND
1403)
#define DUP_VAL_FOUND
(SQLCODE == 1403)
(SQLCODE != 0 && SQLCODE !=
(SQLCODE == -1)
57
For example:
> oerr ora 1
00001, 00000, "unique constraint (%s.%s) violated"
// *Cause: An update or insert statement attempted to insert
a duplicate key
// *Action: Either remove the unique restriction or do not
insert the key
NUM_RECORDS_PROCESSED
When a SQL statement is called, another variable that receives a value is
sqlca.sqlerrd, which is an array of integers defined by the Pro*C
precompiler. Only one element of the array is of particular interest:
sqlca.sqlerrd[2] contains the cumulative number of records processed by
the SQL statement so far. This value is very important when dealing with
array processing, and will be discussed further in Module 9. For now, it is
sufficient to know that the value exists and that a Retek macro has been
defined for it:
#define NUM_RECORDS_PROCESSED sqlca.sqlerrd[2]
Exercise 1
Use the tmp_00.pc in your home directory as a starting point.
Use your initials to name each program (xxx_01.pc, where xxx = your
initials).
Run the scripts to create the trn_win_store table provided by your
instructor
The program for exercise 1 should do the following:
= Declare a cursor and select vdate from the table trn_period
= Declare a cursor and select the sku/store columns from the table
trn_win_store
= Print the date and the columns to the screen
For further practice:
= Select all columns from the table trn_win_store
58
Evaluation Criteria
Comfortable
You are able to understand key processes and concepts relating to the
listed topic.
Not Comfortable
You are unable to understand key processes and concepts relating to the
listed topic.
59
60
Comfortable
Not
Comfortable
Suggestions for
More Work
Summary
In this module, you learned how to embed SQL statements and PL/SQL
blocks into C code with Oracles Pro*C precompiler. You learned how to
use C variables in these statements correctly, making sure that all Oracle
values are interpreted correctly, including the NULL value, which can be
handled by indicator variables, NVL, or DECODE. Finally, you learned
how to interpret the signals that Oracle sends back to the C program after a
SQL call has been completed.
Whats Next
In the next module, you will focus on Retek error handling and simple
debugging techniques.
07/10/00
62
Module Overview
Now that the C programming language and its extension Pro*C have been
introduced, you need to take a look at how they are used at Retek. This
module introduces the structure, syntax and data handling conventions
used in Retek batch programs.
Objectives
After completing this module, you will be able to:
= Declare variables for use in batch programs using data typing and
naming standards.
= Write the skeleton structure of a Retek batch program.
= Identify and use Retek C/Pro*C style standards.
63
init( )
process( )
final( )
?
In general, from one program to the next there is very little variety in the
contents of the four base functions. Init( ) does one-time initialization,
final( ) cleans up any loose ends, and process( ) fetches data to be
processed, and main( ) controls the calling of the others. What varies
between programs is the processing done by functions called from
process( ), represented in the diagram as the question mark module(s).
main()
The main() function is the starting point of any C program. In Retek batch
programs, the only things that happen in the main() function are:
= A connection is made to the database.
= Init( ), process( ), and final( ) are called to perform the work of the
program.
= Messages are written to the daily log file in order to indicate the
beginning and the end of the program's run.
64
init()
The init( ) function is where one-time tasks, which must happen before
actual data processing, are performed:
= Restart/recovery is initialized and any outstanding bookmarks are
retrieved.
= System-level variables and options are fetched from the database.
= Input and output files are opened for reading and writing.
Driving Cursor
The driving cursor is a SQL cursor that defines the data to be processed by
a given batch program. The tables queried from in the driving cursor and
the conditions used to gather data from them determine much of the
behavior of a program and influence decisions regarding the use of the
restart/recovery libraries and multithreading views. The new 9.0 batch
standard is to put the driving cursor inside the process function unless the
cursor is referenced in serveral places within the batch program.
process()
The process( ) function is where the bulk of the work of a batch program
is controlled:
= The driving cursor is opened and fetched from.
= Supporting functions are called to perform program-specific functions.
= Restart/recovery is maintained by writing bookmarks to the database.
Ideally, significant processing does not occur in the process function itself,
rather it occurs in functions called by process( ). Perhaps a better name
for this function would be process_control( ).
final()
The final( ) function is where loose ends in the program are tied up:
= Restart/recovery is closed down.
= Input and output files are closed.
65
if (final() < 0)
{
sprintf(ls_logmessage,"Aborted in final...");
LOG_MESSAGE(ls_logmessage);
exit(-1);
}
else
{
sprintf(ls_logmessage,"Program terminated OK");
LOG_MESSAGE(ls_logmessage);
exit(0);
}
/* end of main */
int init()
{
char *function = "init";
return(0);
} /* end of init */
int process()
{
/* declare driving cursor */
char *function = "process";
66
67
68
Return Values
Most functions in Retek batch programs return an integer. Return values
are interpreted as a code indicating whether an error occurred during
execution of the function according to the following rules:
= 0 The function completed without error, and processing should
continue normally.
= 1 A fatal error occurred, and the calling function should also return
1, as should its calling function, and so on up to main( ), where the
final error messages are logged and the program is halted.
= 1 A non-fatal error occurred (such as validation of an input record
failed), and the calling function should either pass this error up another
level or handle the exception.
69
70
Lesson 2: Variables
Naming
Descriptive identifiers
Variable names should not only indicate what information is stored by the
variable, but also suggest its purpose:
/* These variable names give no indication of their
usage. */
char x[5];
char y[5];
char a[9];
double d = 0;
/* These variable names give some indication of their
usage, */
/* but still rely on context for interpretation.
*/
char ls_wh[NULL_LOC];
char ls_store[NULL_LOC];
char ls_sku[NULL_SKU];
double ld_qty = 0;
/* These variable names give a clear indication that
*/
/* they are being used within a transfer routine.
*/
char ls_source_wh[NULL_LOC];
char ls_destination_store[NULL_LOC];
char ls_transfer_sku[NULL_SKU];
double ld_transfer_qty = 0;
Prefixes
Variable names in Retek batch programs are given prefixes that allow the
programmer or reader to identify their type and use without having to
reference their declarations.
71
C Variable Prefixes
C language variables are prefixed with characters to indicate scope and
type in accordance with the rules below. For example,
ll record count /* a long integer local to a function
*/
gs_username
/* a globally declared string
*/
if_reject_file
/* an function input argument of
type file pointer
*/
Scope
g Global variables declared externally to the program, usually in a
library or header file.
p Variables global to the program.
l Variables local to a function.
i Parameters to a function passed with information to be used by the
function.
o Parameters to a function passed with the purpose of being modified
and passed back.
io Parameters to a function passed with information that will be used by
the function, changed within the function, and passed back to the parent
function.
Type
i Integer or short.
l Long integer.
d Double (the C float type should never be used).
c Single character.
s Character string.
a Array or structure of arrays.
f File pointer.
Cursor Prefixes
Embedded SQL cursors are always prefixed with a lowercase c. No
scope prefix is needed:
72
Capitalization
Variables
C variables are lowercase:
FILE *pf_final_file;
strcpy(ls_sku,20002116);
Macro Substitutions
Macro substitutions should always be in uppercase:
#define TRAN_DATA_RETURN_CODE 4
char ls_location[NULL_LOC];
73
74
Data Types
Constants and Macros
Batch programs should not contain any hard-coded numbers. If a program
must use a constant value, that constant should be defined by a macro.
The macro should be defined at the top of the program (use #define). If
the value of the constant changes, it will be much easier to modify the
macro definition rather than in many instances where the constant is being
used. Named constants are also beneficial in writing self-documenting
code.
LEN_* vs. NULL_*
Two macros are used to define the width of each string based on a
database column or a field in a file. One is has the value of the maximum
width of the field, and starts with the prefix 'LEN_'. The other adds room
for the null-terminating character and starts with the prefix 'NULL_':
#define LEN_LOC
4
#define NULL_LOC
5
#define LEN_LOC_TYPE
1
#define NULL_LOC_TYPE
2
...
int get_loc(char *os_location)
{
char ls_location[NULL_LOC];
char ls_loc_type[NULL_LOC_TYPE];
...
zero_pad(LEN_LOC,ls_location);
...
}
Strings
IS STRING
Prior to version 9.0, in order for Oracle to map C strings to its internal
string types, all strings that will be used as bind variables in a SQL
statement or PL/SQL block need a two-part declaration. The first part is
the normal C declaration. The second is a statement identifying the string
as an Oracle STRING type. The IS STRING statement must declare the
string width, but it uses parentheses rather than square brackets:
char
EXEC
char
EXEC
ls_sku[NULL_SKU];
SQL VAR ls_sku IS STRING(NULL_SKU);
ls_location[NULL_LOC];
SQL VAR ls_location IS STRING(NULL_LOC);
75
If you are compiling using make or hcomp81 or later (ask if youre not
sure) you will not need the IS STRING declaration to let Oracle know
that your character array is a string. However, for the vast majority of the
batch code in existence IS STRING declarations exist in the code and are
still required when compiling earlier Retek versions. One difference when
using the new compiler is when strings are passed into functions. The
string argument in the function declaration should be declared as a
character array instead of a character pointer. This lets Oracle know how
long the string is.
strcmp() vs. MATCH()
strcmp( ) is an ANSI C function used to lexigraphically compare two
strings. MATCH( ) is a macro defined by Retek. The strcmp( ) function
returns a zero (logical FALSE) if the two given strings are equivalent and
returns a non-zero value (logical TRUE) if they are different, based on the
difference between the first non-matching characters in the strings.
MATCH( ) works in almost the opposite manner. It returns 1 (TRUE) if
the strings match and 0 (FALSE) if they don't. In fact, MATCH( ) is
defined as !strcmp( ). Compare the readability of the following two
statements:
if( ! strcmp(is_ord_sku, ls_invc_sku) )
the '!' */
{
...
/* Note
Use either strcmp or MATCH in your programs. But, for the sake of
readability, use one or the other.
Numbers
Integer
C has three types of integer types: short, int, and long. Shorts are the
smallest form of integer, and are used mostly for NULL-indicator
variables in SQL statements and for yes/no (1/0) flags.
Longs offer the most precision of any integer type and are used as
counters.
Ints are not normally used. Values from the database should not be
fetched into integer types, since most values in tables are either floatingpoint numbers or identifiers.
76
Floating-Point
C has two types of floating-point numbers: float and double. Doubles are
used to hold true numeric values when arithmetic must be performed on
them. Floats offer only half the precision of doubles and should never be
used.
Numbers as Strings
Because C and UNIX place limitations on the maximum precision of a
number, it is possible for a numeric value in Oracle to be too long to fit
into a long or double. In order to minimize this possibility, numbers
should be held in C strings as much as possible. As much arithmetic as
possible should be kept in SQL cursors. If a number must be manipulated
in C, it should be stored as a double in order to provide the maximum
precision.
77
Dates
Because C has no date type, Oracle dates are converted into strings for use
in batch programs. Date strings should be in the Oracle format
'YYYYMMDD'. If a timestamp is part of the date, the format should be
'YYYYMMDDHH24MISS'. The ordering of fields (year, month, then
day) allows date comparisons to be done in C by using a simple strcmp( ):
EXEC SQL DECLARE c_ord_dates CURSOR FOR
SELECT TO_CHAR(not_before_date,'YYYYMMDD'),
TO_CHAR(not_after_date,'YYYYMMDD'),
FROM ordhead;
WHERE order_no = :is_order_no
AND not_after_date >
TO_DATE(:is_yesterday,'YYYYMMDD');
...
EXEC SQL FETCH c_ord_dates INTO :ls_not_before_date,
:ls_not_after_date;
...
/*
* If the not-before-date is "less than" (earlier than) the
* not-after-date, strcmp() will return a number less than
zero:
*
ls_not_before_date == "19990722" (July 22, 1999)
*
ls_not_after_date == "19990903" (September 3, 1999)
* ('7' is lexigraphically less than '9', so strcmp() will
return 2.)
*
* However, if the not-before-date is later than the
* not-after-date, strcmp() will return a number greater
than zero:
*
ls_not_before_date == "19990722" (July 22, 1999)
*
ls_not_after_date == "19980903" (September 3, 1998)
* ('9' is lexigraphically greater than '8', so strcmp()
will return 1.)
*/
if(strcmp(ls_not_before_date,ls_not_after_date) > 0)
{
sprintf(err_data,"not-after-date is earlier than notbefore-date");
...
78
Scope
Global
The only global variables in Retek batch programs should be system-level
options, dates, and other static entities. These are set primarily in the init(
) function. If you are tempted to declare a global variable consider
whether the reason is because you need static storage. If this is the case,
consider declaring a static local variable that can be passed to other
functions when necessary.
Local
Nearly all variables in Retek batch programs should be declared locally.
Where multiple functions need to share information, that information
should be passed between them as parameters. Groups of variables that
are related to each other and used together should be gathered together
into a single struct. Structs should be defined as a type at the top of the
program so they can be declared locally and passed as parameters. All
cursors should be defined locally within the function that opens them.
Variables that are changed by a PL/SQL function (output variables) must
be declared locally (within the PL/SQL block) and then copied into C
variables declared outside the block.
Parameters
Input
Input variables are passed from the parent function to the child to use in its
processing. These parameters should be passed by value whenever
possible; however, this will not always be possible. Character strings are
an obvious example, as they are always passed by reference. It is
important to note that parameters passed by reference will not necessarily
be changed by the child function. Input parameters will have the scope
prefix i.
79
Output
Output variables are passed into a function so that it can populate them
with a value that will later to be used in some way by the parent. Output
parameters must be passed by reference so that the child function can
change their value. These parameters have the scope prefix 'o'.
Input/Output
Some variables are passed by a parent into a function with a value that is
used by the child. The child then performs actions that change the value
of the variable. This change affects the later behavior of the parent
function. These variables are referred to as input/output variables (similar
to the IN OUT parameter type in PL/SQL). These parameters must be
passed by reference and have the scope prefix 'io'.
80
*/
*/
*/
*/
*/
*/
81
Indentation
Indentation helps you keep track of the depth of a statement within
functions and multiple levels of conditional or looping clauses. When
nesting lines of code, indent all new lines three spaces. DO NOT use tabs
to indent code:
/* Inconsistent indentation makes code very difficult to
read, */
/* and can mislead the reader trying to determine the
depth
*/
/* of a statement.
*/
int get_promotion(int pi_multi_prom_ind,
char *is_store,
char *os_promotion)
{
char *function = "get_promotion");
char ls_promotion[NULL_PROM];
EXEC SQL DECLARE c_promotion CURSOR FOR
SELECT promotion
FROM promstore
WHERE store = :is_store;
if(pi_multi_prom_ind)
{
EXEC SQL OPEN c_promotion;
if(SQL_ERROR_FOUND)
{
sprintf(err_data,Open c_promotion);
strcpy(table,promstore);
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
EXEC SQL FETCH c_promotion INTO :ls_promotion;
if(SQL_ERROR_FOUND)
{
sprintf(err_data,Fetch c_promotion);
strcpy(table,promstore);
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
else if(NO_DATA_FOUND)
{
sprintf(err_data,Can't find promotion);
strcpy(table,promstore);
if(WRITE_ERROR(SQLCODE,function,table,err_data))
return(-1);
return(1);
}
else
{
strcpy(os_promotion,ls_promotion);
}
}
else
{
strcpy(os_promotion,);
}
return(0);
} /* end get_promotion */
/* Proper indentation is much easier to read. */
int get_promotion(int
pi_multi_prom_ind,
char *is_store,
82
char *os_promotion)
{
char *function = "get_promotion);
char ls_promotion[NULL_PROM];
EXEC SQL DECLARE c_promotion CURSOR FOR
SELECT promotion
FROM promstore
WHERE store = :is_store;
if(pi_multi_prom_ind)
{
EXEC SQL OPEN c_promotion;
if(SQL_ERROR_FOUND)
{
sprintf(err_data,Open c_promotion);
strcpy(table,promstore);
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
EXEC SQL FETCH c_promotion INTO :ls_promotion;
if(SQL_ERROR_FOUND)
{
sprintf(err_data,Fetch c_promotion);
strcpy(table,promstore);
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
else if(NO_DATA_FOUND)
{
sprintf(err_data,Can't find promotion);
strcpy(table,promstore);
if(WRITE_ERROR(SQLCODE,function,
table,err_data))
return(-1);
return(1);
}
else
{
strcpy(os_promotion,ls_promotion);
}
}
else
{
strcpy(os_promotion,);
}
return(0);
} /* end get_promotion */
Brackets
Brackets begin on the line following the statement triggering their use.
Indent them to the same depth as that statement. A line containing a
bracket should not contain any other statements, although comments are
acceptable:
/* Improper bracketing. */
int get_promotion() {
...
if(pi_multi_prom_ind)
{
...
}
83
...
if(SQL_ERROR_FOUND) { sprintf(err_data,"SQL error");
strcpy(table,"promstore");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1); }
...
return(0);
} /* end get_promotion */
/* Proper bracketing. */
int get_promotion()
{
...
if(pi_multi_prom_ind)
{
...
}
...
if(SQL_ERROR_FOUND)
{
sprintf(err_data,"SQL error");
strcpy(table,"promstore");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
...
return(0);
} /* end get_promotion */
84
Comments
Source code is meant to explain a process in the clearest and most explicit
manner possible, so that it can be reliably translated into machine code for
the computer to execute. However, a machine's concept of clarity often
differs from that of a human reader. Furthermore, situations often arise in
which the actions being performed by a program may be so complex or
subtle (from both a technical and business standpoint) that they may
require additional explanation. Comments in a program exist to help the
reader of that program to understand sections of code when the function or
logic may not be obvious.
Format
Because comments are meant to help a reader gain a better understanding
of the program, it is important for them to be formatted for clarity.
Most comments should be placed on the line before the code they
describe:
/* Calculate the share of the retail amount that is VAT.
*/
ld_vat_amt = id_amt id_amt / (1 + id_vat_rate);
Very short comments can be inserted at the end of the line of code they
describe:
ld_vat_excl_amt = id_amt / (1 + id_vat_rate); /* Strip
out VAT. */
85
86
Content
Comments are meant to make the code more readable and clarify complex
passages. In order for comments to be effective, they must be written
clearly.
Do not include comments to reiterate the obvious:
/* Add the fetched quantity to the total quantity.
*/
ld_total_qty += ld_fetched_qty;
88
Module Overview
In this module, you will learn how to add error messaging to your Pro*C
programs, and will be introduced to some common debugging tools used
at Retek.
Most programs will write to a daily log file to record program information
and to a program error file to store errors experienced by the programs.
This module will discuss when these messages should be written and
where the messaging routines should be placed in a program.
Finally, a brief discussion of three types of debugging tools used by Retek
is included.
Objectives
After completing this module, you will be able to:
Find the daily log file and write messages to it with LOG_MESSAGE.
Find a programs error file and write messages to it with
WRITE_ERROR.
Insert proper error handling routines after SQL statements.
Insert proper error handling routines after PL/SQL blocks that call
batch-enabled or non-batch-enabled PL/SQL functions.
Define the differences between fatal and non-fatal errors and the
differences in dealing with them.
Identify three different types of debugging tools and describe their
uses.
89
A message written to the log file has a date stamp, the name of the
program, and a message stating either that the program has started, or that
it has finished (successfully or not):
Mon Jan 25 18:17:26 Program: posupld: Started by
rmsdev80user
Mon Jan 25 18:17:47 Program: posupld: Thread [1] Terminated OK.
LOG_MESSAGE( )
Messages are written to the log file by the LOG_MESSAGE( ) function.
This function takes a single string as a parameter and automatically adds
the timestamp and program name. If a variable needs to be written to the
log file, it should be printed into a formatted string, and the formatted
string should be sent into the LOG_MESSAGE( ) function:
sprintf(logmessage, "Thread [%d] - Terminated OK.",
pl_commit_max_ctr);
LOG_MESSAGE(logmessage);
exit(0);
90
A message written to the program error file has the program name and
thread number, a time stamp, the function where the error occurred, any
related database tables, an error code (usually the Oracle server error
number), the Oracle error message, and a program error message:
posupld_1~19981222101405~validate_promotion~promhead~S~14
03~ORA-1403: No Data Found~Record 0000000433: fetch
c_promotion where promotion: 5197
Messages are written to the error file using the WRITE_ERROR function.
91
WRITE_ERROR( )
WRITE_ERROR(SQLCODE,function,table,err_data);
Parameters
Error code: A numeric value identifying the error. It should never
contain a hard-coded literal value. Instead, SQLCODE or RET_*_ERR
should be passed in here.
Value: SQLCODE (sqlca.sqlcode)
Use: The most commonly used error code. Declared as a long at the
top of the program, preferably just after the inclusion of SQLCA.H.
SQLCODE is populated by Oracle after every SQL statement and
holds the Oracle server error number.
Table: A list of the tables on which the error occurred. Table is a global
string variable. It is set whenever WRITE_ERROR() is called after a SQL
statement. It should be populated with the name of the tables accessed by
the SQL statement:
strcpy(table,"ordsku, ordloc");
92
If no tables are associated with the SQL statement use a null string, .
Error data: A formatted string describing the error condition. This
should provide reasonable detail to users to help them figure out where the
error occurred. This should not repeat any information in the program,
function, or table parameters. Use the err_data global variable.
This string should describe the action taken, the cursor involved (if
applicable), and any associated bind variables. The error message is the
only evidence of a problem and the only guide to finding that problem, so
it should be as helpful as possible:
if (SQL_ERROR_FOUND)
{
sprintf(err_data,"Fetch c_ordloc for order_no %s, sku %s",
ls_order_no, ls_sku);
93
94
NO_DATA_FOUND
Depending on the situation, a cursor that returns no rows (or an UPDATE
or DELETE that affects no rows) may be an error, or it may have some
other significance. For this reason, a NO_DATA_FOUND
(SQLCODE==1403) condition is excluded from the
SQL_ERROR_FOUND condition and must be trapped in a different way:
...
EXEC SQL FETCH c_item_info INTO :ls_item_dept,
:ls_system_ind;
/* If there is a problem with the FETCH statement itself,
*/
/* log an error message and send the error up to the
caller. */
if(SQL_ERROR_FOUND)
{
sprintf(err_data,"Fetch c_item_info for item
%s",is_item);
strcpy(table,"desc_look");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
/* If the item does not exist, simply exit the function
without error. */
else if(NO_DATA_FOUND)
{
return(0);
}
95
}
/* See if there was an error inside the package. */
if (li_plsql_flag)
{
sprintf(err_data, "Error in PACKAGE.FUNCTION -%s",
ls_plsql_msg;
WRITE_ERROR(RET_PROC_ERROR,function,"",err_data);
return(-1);
}
96
Notice the PL/SQL block does not have an exception section. Although it
would not be wrong to add an exception section here, it is considered
unnecessary in Retek batch programming because the only thing you use
PL/SQL block for is to call stored functions. The only possible problems
encountered calling functions are caught by the function returning false, or
by SQL_ERROR_FOUND.
As an example and a warning, here is a sample of awful error-handling
from an existing batch program (now corrected!). Note that this code
CAN get through the cracks, i.e. people in charge of reviewing code will
miss things. Can you find whats wrong with this?
if( strncmp(is_rev_no, "-1", NULL_SA_REV_NO) == 0)
{
EXEC SQL OPEN c_sa_tran_tender;
if (SQL_ERROR_FOUND)
{
sprintf( err_data,
"OPEN c_sa_tran_tender ||
c_sa_tran_tender_rev");
strcpy( table, "sa_tran_tender ||
sa_tran_tender_rev");
WRITE_ERROR(SQLCODE,function,table,err_data);
return( FATAL);
}
}
else
{
EXEC SQL OPEN c_sa_tran_tender_rev;
if (SQL_ERROR_FOUND)
{
sprintf( err_data,
"OPEN c_sa_tran_tender || c_sa_tran_tender_rev");
strcpy( table,
"sa_tran_tender || sa_tran_tender_rev");
WRITE_ERROR( SQLCODE,function,table,err_data);
return( FATAL);
}
}
*oa_sa_tran_tender = NULL;
*ol_num_sa_tran_tender = 0;
for (;;)
{
Batch-Enabled
Many packages are batch-enabled. This allows error-trapping logic
outside of the embedded PL/SQL block to provide users with more
informative and descriptive error messages. For batch-enabled packages,
an additional function call should be made to the
97
98
99
100
Output Flags
The simplest method for debugging is simply putting output statements
into your code in order to mark when the program has performed a certain
task, such as executed a certain statement, entered a function, or taken a
specific logic branch. Output statements are also used to display the
runtime values of variables.
Overall, output flags can be replaced by even the simplest of debuggers,
which routinely have methods of showing the flow of the program and
displaying the values of variables in much more convenient ways. The
only advantage that output flags have is that they can be used in any
environment with any programming language, as long as the program has
access to some sort of output stream.
So, in the absence of any actual debuggers, using output flags is a useful
way to trace the flow of your program and the data moving through it.
However, if any sort of debugger is available, theres no real reason to use
output flags.
101
There are two C debuggers available for use: dbx and Workshop. Both
debuggers allow the user to do all of the tasks mentioned above.
However, Workshop has a GUI interface while dbx does not. This means
that Workshop is more intuitive, but must be run through Exceed. If using
a telnet client, you must use dbx to debug. dbx is provided in most UNIX
environments, while Workshop is specific to Exceed. Also, dbx tends to
be a little more stable than Workshop.
To begin workshop type
> workshop
If you have problems with your display, make sure your PCs IP address is
in the file ~/.DISPLAY.
If you are using xemacs, workshop can be started within xemacs.
To start dbx type
> dbx <executable filename>
Both dbx and Workshop take an executable and a C file as input (usually,
only the executable has to be explicitly named, unless the C file is in
another directory). Neither of the debuggers understand Pro*C.
lint
The C compiler catches syntactic errors in a programit makes sure the
program has the correct number of opening and closing parentheses, warns
if functions dont have return values, finds typos, and so on. However,
this leaves a wide range of errors that dont get caught.
Lint is a program that catches less obvious errors in the codeit finds
variables that arent initialized before theyre used, looks for functions that
are called incorrectly (or never called at all), and warns if you may be
using = when you mean ==.
To run lint, you must have a C file (lint just wont understand a Pro*C
file). You must also tell lint the location of the library files that your
program uses (for most programs, thats $MMHOME/oracle/lib/src, also
known as $l). So, a run of lint would look like this:
> lint I$l my_prog.c
102
Lint returns many more warnings besides those that actually apply to your
programfor example, it will point out all the functions in retek.h that
arent used in your program (and theres a lot of them). So, there is a
certain amount of filtering that must be done to lints output, but the
results that do pertain to your code almost always prove useful.
Exercise 2
Make a copy of exercise 1 and rename it for exercise 2 (cp xxx_01.pc
xxx_02.pc, where xxx = your initials)
This program should do the following:
= Calculate the gross product.
= Instead of printing information to the screen, insert the information
into the table trn_win_store_hist.
= Update the program to include error handling and proper naming
conventions.
= See handout for additional information.
103
104
Evaluation Criteria
Comfortable
You are able to understand key processes and concepts relating to the
listed topic.
Not Comfortable
You are unable to understand key processes and concepts relating to the
listed topic.
Comfortable
Not
Comfortable
105
Summary
In this module, you learned how to add error messaging to your program
and were introduced to some common debugging tools that Retek uses.
You learned to write to the daily log file with LOG_MESSAGE and to the
program error file with WRITE_ERROR. You then learned where your
program these two functions should be called and what sort of signals
should trigger the routines that call them. Finally, a brief discussion of
three types of debugging tools used by Retek was included.
Whats Next
In the next module, you will explore ways to use a powerful feature of the
C programming language to improve the performance of your batch
programs.
106
108
Module Overview
In this module, you will learn how to use arrays in SQL statements, such
as FETCH, INSERT, UPDATE, and DELETE, to make their contact with
the database more efficient. Adding array processing to a batch program
somewhat alters the flow of the program, and the proper modifications
will be discussed in detail.
Finally, this module will discuss dynamically sizing arrays, which is
Reteks standard practice.
Objectives
After completing this module, you will be able to:
Describe why arrayed SQL statements are often better than nonarrayed statements.
Write a FETCH statement that will fetch records into an array.
Perform arrayed updates, inserts, and deletes.
Understand the restrictions on arrayed SQL statements.
Allocate memory to arrays dynamically, according to the Retek
standard.
109
110
becomes:
double lad_total_cost[10000];
char las_sku[10000][NULL_SKU];
EXEC SQL VAR las_sku IS STRING(NULL_SKU);
Notice that you can apply IS STRING to an array of strings exactly the
same way you apply it to a single string.
After declaring the arrays, the FETCH statement needs to be fixed. SQL
commands need to be told when arrays are being used, and how big those
arrays are (after all, a FETCH statement has to know how many records
its bringing back). This is accomplished with the FOR clause:
ll_array_size = 10000;
EXEC SQL FOR :ll_array_size
FETCH c_my_cur INTO :lad_total_cost,
:las_sku;
Notice that the addition of the FOR clause telling the SQL statement to
bring back 10,000 records is the only adjustment necessary.
111
To add array processing to this code, you perform the steps discussed
above:
{
double lad_total_cost[10000];
char las_sku[10000][NULL_SKU];
EXEC SQL VAR las_sku IS STRING(NULL_SKU);
long ll_array_size = 10000;
...
while(1)
{
EXEC SQL FOR :ll_array_size
FETCH c_my_cur INTO :lad_total_cost,
:las_sku;
if (NO_DATA_FOUND)
break;
/* Process lad_total_cost and las_sku */
} /* end of while loop */
}
112
Obviously, you have to add an inner loop to process all the records
brought back, because you probably cant process then en masse:
{
double lad_total_cost[10000];
char las_sku[10000][NULL_SKU];
EXEC SQL VAR las_sku IS STRING(NULL_SKU);
long ll_array_size = 10000;
long ll_ctr;
...
while(1)
{
EXEC SQL FOR :ll_array_size
FETCH c_my_cur INTO :lad_total_cost,
:las_sku;
if (NO_DATA_FOUND)
break;
for (ll_ctr=0; ll_ctr < ll_array_size; ll_ctr++)
{
/* Process lad_total_cost[ll_ctr] and
las_sku[ll_ctr] */
}
} /* end of while loop */
}
This looks fine, but consider this scenario: c_my_curs cursor brings back
a total of 20,120 records. Heres what happens:
= The first time through the while loop, 10,000 records are fetched and
processed.
= The second time through, 10,000 records are fetched and processed,
bringing your total to 20,000 records processed.
= The third time through, only 120 records are fetched. Because it ran
out of records before it was finished fetching, SQL returns the
NO_DATA_FOUND signal. You break out of the loop, never having
processed the last 120 records.
113
114
To figure out how many records were brought back by the last call to
FETCH, you need only subtract the previous total number of records
retrieved from NUM_RECORDS_PROCESSED. So, you have to create a
variable to keep track of the previous total:
{
double lad_total_cost[10000];
char las_sku[10000][NULL_SKU];
EXEC SQL VAR las_sku IS STRING(NULL_SKU);
short li_ndf= 0;
long ll_array_size = 10000;
long ll_ctr;
long ll_num_records_fetched = 0;
long ll_prev_total = 0;
...
while(!li_ndf)
{
EXEC SQL FOR :ll_array_size
FETCH c_my_cur INTO :lad_total_cost,
:las_sku;
if (NO_DATA_FOUND)
li_ndf= 1;
ll_num_records_fetched = NUM_RECORDS_PROCESSED
ll_prev_total;
ll_prev_total = NUM_RECORDS_PROCESSED;
for (ll_ctr=0;
ll_ctr < ll_num_records_fetched;
ll_ctr++)
{
/* Process lad_total_cost[ll_ctr] and
las_sku[ll_ctr] */
}
} /* end of while loop */
}
115
116
Indicator Arrays
Just as bind variables can have associated indicator variables, bind arrays
can have associated indicator arrays. Indicator arrays are simply arrays of
shorts and act exactly the same way as indicator variables: to find out if
my_arr[i] is NULL, simply check the value in my_arr_ind[i] for 1. The
syntax of an indicator array is exactly the same as a regular indicator
variable:
{
double lad_total_cost[10000];
short lai_total_cost_ind[1000];
char las_sku[10000][NULL_SKU];
EXEC SQL VAR las_sku IS STRING(NULL_SKU);
...
while(!li_ndf)
{
EXEC SQL FOR :ll_array_size
FETCH c_my_cur INTO
:lad_total_cost:lai_total_cost_ind,
:las_sku;
...
Of course, an indicator array must be the same size as its associated bind
array.
117
but the first statement does it all in one call to the database, incurring a lot
less overhead.
Here is an example of an arrayed delete, which is almost exactly the same
as an arrayed update:
EXEC SQL FOR :ll_num_deletes
DELETE FROM desc_look dl
WHERE dl.sku=:las_sku;
118
Finally, an arrayed insert. Note that you can mix constants and bind
arrays, but you still may not mix in bind variables.
EXEC SQL FOR :ll_num_inserts
INSERT INTO desc_look (sku,
dept,
desc_up,
system_ind,
image_name,
image_type)
VALUES (:l_insert_arrays.as_sku,
:l_insert_arrays.as_dept,
:l_insert_arrays.as_desc_up,
S,
NULL,
NULL);
Notice that in this example, the bind arrays are members of a struct.
Oracle allows members of structs to be used as bind variables and bind
arrays with no problem whatsoever. A common Retek practice is to group
of related arrays into a struct to keep them organized.
It is important to note that the restriction of no bind arrays in a SELECT
statement still holds, even when used in an INSERT-SELECT statement.
PL/SQL blocks cannot be arrayed.
119
Note that for strings, parentheses need to be around the pointer and the
variable name for Oracle to correctly understand the arrays of strings.
To make things a little simpler down the line, were going to group these
arrays in a struct to make passing them through parameters much simpler:
typedef struct
{
double *ad_total_cost;
short *ai_total_cost_ind;
char (*as_sku)[NULL_SKU];
} array_holder;
...
{
array_holder l_fetch_arrays;
/* Note that you have to use IS STRING when you declare
the variable, not when you define the type. */
EXEC SQL VAR l_fetch_arrays.as_sku IS STRING(NULL_SKU);
120
So, the actual function allocating memory for the variables declared above
would look like this:
/* Remember that we have to pass the struct by reference
here */
int size_fetch_arrays(array_holder *o_fetch_arrays)
{
char *function = size_fetch_arrays;
o_fetch_arrays->ad_total_cost =
(double*)calloc(pl_commit_max_ctr,sizeof(double));
o_fetch_arrays->ai_total_cost_ind =
(short*)calloc(pl_commit_max_ctr,sizeof(short));
o_fetch_arrays->as_sku =
(char(*)[NULL_SKU])calloc(pl_commit_max_ctr,NULL_SKU);
return 0;
} /* end of size_fetch_arrays */
121
Of course, this function has absolutely no error handling and it needs it. If
the calloc call cannot find enough memory, then it will return NULL. So,
you should trap for that error and return an error message if it occurs:
int size_fetch_arrays(array_holder *o_fetch_arrays)
{
char *function = size_fetch_arrays;
short li_no_mem = 0;
if ((o_fetch_arrays->ad_total_cost =
(double*)calloc(pl_commit_max_ctr,sizeof(double)))
== NULL)
li_no_mem = 1;
if ((o_fetch_arrays->ai_total_cost_ind =
(short*)calloc(pl_commit_max_ctr,sizeof(short))) ==
NULL)
li_no_mem = 1;
if ((o_fetch_arrays->as_sku =
(char(*)[NULL_SKU])calloc(pl_commit_max_ctr,NULL_SKU)) ==
NULL)
li_no_mem = 1;
if (li_no_mem)
{
sprintf(err_data,"Unable to allocate memory");
WRITE_ERROR(RET_FUNCTION_ERR,function,"",err_data);
return(-1);
}
return 0;
} /* end of size_fetch_arrays */
122
And finally, for completeness, you present the rest of the code from the
example, now using a struct and dynamically allocating memory for the
arrays:
{
array_holder l_fetch_arrays;
EXEC SQL VAR l_fetch_arrays.as_sku IS
STRING(NULL_SKU);
short
long
long
long
li_ndf= 0;
ll_ctr;
ll_num_records_fetched = 0;
ll_prev_total = 0;
...
/* Make a call to the sizing function, passing by
reference */
/* Make sure to call it before referencing the arrays
*/
if (size_fetch_arrays(&l_fetch_arrays) < 0)
return(-1);
while(!li_ndf)
{
EXEC SQL FOR : commit_max_ctr
FETCH c_my_cur
INTO :l fetch arrays.ad total cost
:l_fetch_arrays.ai_total_cost_ind,
:l_fetch_arrays.as_sku;
if (NO_DATA_FOUND)
li_ndf= 1;
ll_num_records_fetched =
NUM_RECORDS_PROCESSED ll_prev_total;
ll_prev_total = NUM_RECORDS_PROCESSED;
for (ll_ctr=0;
ll_ctr < ll_num_records_fetched;
ll_ctr++)
{
/* Process l_fetch_arrays.ad_total_cost[ll_ctr]
and*/
/* l_fetch_arrays.as_sku[ll_ctr]
*/
}
} /* end of while loop */
}
Exercise 3
Make a copy of exercise 2 and rename it for exercise 3 (cp xxx_02.pc
xxx_03.pc, where xxx = your initials)
Modify the program as follows:
= Declare a struct for the arrays.
= Add array processing to the fetch and insert statements.
= Fetch 1000 rows at a time, using arrays.
= See handout for additional information.
Part 2
Make a copy of exercise 3 and rename it for the optional exercise (cp
xxx_03.pc xxx_03dym.pc, where xxx = your initials)
Modify Exercise 3 to implement dynamic array processing.
= Declare pointers for each array.
= Create a new function size_arrays() to calloc memory.
= Pass in the size of the arrays as a parameter at the command line.
= See handout for additional information.
123
124
Evaluation Criteria
Comfortable
You are able to understand key processes and concepts relating to the
listed topic.
Not Comfortable
You are unable to understand key processes and concepts relating to the
listed topic.
Comfortable
Not
Comfortable
125
Summary
In this module, you learned how to use arrays in SQL statements using the
FOR clause. As you saw, adding array processing to a batch program
alters the flow of the program, due to the addition of a for loop which
loops through the arrays.
There are several restrictions on arrayed SQL statements, most notably
that if bind arrays are used in a statement, all bind variables must be bind
arrays, and these bind arrays must be of the same size.
Finally, you learned how to dynamically size the arrays, according to
Reteks standards.
Whats Next
In the next module, you will focus on Reteks Restart/Recovery
procedures.
126
128
Module Overview
Retek batch programs must balance a number of conflicting requirements.
They must be robust enough to recover from all types of failure while
remaining efficient. They must process large quantities of data within the
confines of reasonably sized rollback segments. The Retek
Restart/Recovery and Multithreading API provides solutions to these
challenges.
The two facets of this API, restart/recovery and multithreading, are
conceptually distinct, yet inextricably bound in Reteks implementation.
A program may implement multithreading and not restart/recovery, for
example. Yet, as you will see, an understanding of both is necessary to
implement either in a Retek batch program.
This module and the next introduce these two distinct topics; first at a
conceptual level, then more practically.
Objectives
After completing this module, you will be able to:
Explain the purpose of restart/recovery in batch programs.
Understand the variables and functions provided in the API.
Describe how a programs design affects commit logic.
129
130
Restart String
Ordering data by the LUW variables provides a means, when restarting, to
get back to the exact record where the program left off, but it is not the
whole solution. The position within the cursors records must somehow
be stored persistently whenever data is successfully processed and
committed.
To accomplish this, functions have been created that store a string
containing the LUW elements current value on a restart table. This value
is historically known as the restart string, but is stored on
RESTART_BOOKMARK.BOOKMARK_STRING. Suppose that in a
program with a store-sku LUW, the current record is for store 1012 and
SKU 10004356. Then the bookmark string would contain:
;1012;10004356
Now each time that work is committed to the database the restart string
can be saved as well. When a restart is required, retrieving bookmark
131
strings saved values and using them in the where clause of the driving
cursor will limit the programs fetched data to only unprocessed records.
Application Image
Similar to the restart string, the application image is a set of strings
necessary for the program to return to its pre-abort state when restarting.
Unlike the restart string, an application image may or may not be
necessary for a particular program.
The specific details of the application image are entirely specific to each
program. Consider it a general purpose variable to hold any information
required for the program to pick up where it left off.
A common use of the application image is to hold line number counters in
programs that write output to files. Without an application image it
would, of course, still be possible to restart a download program, open a
cursor to select the correct data and open the partially completed output
file ready to append the next line. But, the next line number and other
specific data that may be necessary to begin creating the next output line
will have been lost when the program failed. These data are stored in the
application image.
Commit Frequency
In an ideal world, a commit would take place after each LUW so that no
repetition of work is required in the event of a restart. However,
committing after every LUW would compromise the efficiency of a
program. A balance must be struck between committing often enough to
avoid redoing too much work and committing occasionally so as not to
unreasonably slow down processing.
Yet, where the balance is found will be specific to many runtimeenvironment factors unknown to programmer during development. It
must be left to the system administrator at each installation to determine
the frequency of commits for each batch program.
To allow for this flexibility, a value known as the commit_max_ctr is
stored on a database table for each batch program implementing restart
and recovery. The value can be changed before each run of a program as a
performance tuning tool. The commit max counter is fetched into a public
variable during the initialization of each run, and used in the main
processing loop to determine the frequency of actual commits.
132
133
134
Initialization
All batch programs implementing restart/recovery will call retek_init().
This function is called from init() and takes the place of the older version,
restart_init(). Its purpose is to initialize all the restart variables, including
the variables which allow the program to pick up where it left off in the
case of a failure. Retek_init is designed to handle a variable number of
arguments since each program will have a unique set of restart variables.
This is communicated through the first two arguments to the function, the
first of which is a pre-compiler macro telling the function how many
arguments to expect, e.g. #define NUM_INIT_PARAMETERS 7. The
second argument is named parameter and is declared globally. It tells
what type of variables retek_init is expected to populate. It is defined to
be of type init_parameter which was created for the new restart-recovery
API. An example of how to declare the parameter variable follows:
init_parameter parameter[NUM_INIT_PARAMETERS] =
{
/* NAME ---------- TYPE ------ SUB_TYPE */
"commit_max_ctr",
"num_threads",
"thread_val",
"sku",
"supplier",
"origin_country",
"start_date",
};
"long",
"int",
"int",
"string",
"string",
"string",
"string",
"",
"",
"",
"S",
"S",
"S",
"S"
135
Each row of the parameter array describes a variable that will be passed
into retek_init(). The first 3 are standard and should be used exactly as
they are here if the program is multi-threaded and has restart/recovery
functionality. The next 4 values are for the start string which are used in
the where clause of the driving cursor. The name should describe what
variable is being returned. The type in this example are all strings
with a subtype of S which indicates a Start string variable (start string
is synonymous to restart string). With parameter defined as such, the call
to retek_init would look like this:
li_init_return = retek_init(NUM_INIT_PARAMETERS,
parameter,
&pl_commit_max_ctr,
&pi_num_threads,
&pi_thread_val,
ps_restart_sku,
ps_restart_supplier,
ps_restart_origin_country,
ps_restart_start_date);
Note all the variables are defined to be of program-scope and are named as
such. This last example covers the case of a table-based program. For a
file-based program, additional information is required to restart the
program. This information, known as the application image, contains
anything else besides the start string to restart the program. Also, the file
itself is handled differently. A new type of struct has been created for this
purpose called rtk_file. The structure includes the file pointer, the
filename, and a flag used internally to restart-recovery. For restartrecovery functions, only the address of this struct is required. An example
illustrating these concepts follows.
Declarations
#define
NUM_INIT_PARAMETERS 7
init_parameter parameter[NUM_INIT_PARAMETERS] =
{
"commit_max_ctr", "long",
"",
"restart_lc",
"string",
"S",
"line_seq",
"string",
"I",
"transaction_seq", "string",
"I",
"detail_seq",
"string",
"I",
"total_seq",
"string",
"I",
"out_lc_download", "rtk_file", "O"
};
long
long
char
EXEC
char
char
char
char
pl_commit_max_ctr;
pl_current_ctr = 0;
ps_restart_lc[NULL_LC_REF_ID];
SQL VAR ps_restart_lc IS STRING(NULL_LC_REF_ID);
ps_line_seq[NULL_REC_NO];
ps_transaction_seq[NULL_REC_NO];
ps_detail_seq[NULL_REC_NO];
ps_total_seq[NULL_REC_NO];
/* 1 output file */
rtk_file pf_out;
136
inside init()
li_init_return = retek_init(NUM_INIT_PARAMETERS,
parameter,
&pl_commit_max_ctr,
ps_restart_lc,
ps_line_seq,
ps_transaction_seq,
ps_detail_seq,
ps_total_seq,
&pf_out);
if (is_new_start())
{
strcpy(ps_line_seq, 1);
}
This example shows both the declarations of the global variables, and how
they are initialized. In addition to the new file type, there are two new
subtypes introduced here: I and O. The first designates a string
variable as an Image-string variable. However, it can also be used with
the rtk_file type to indicate a file as being Input. A subtype of O
designates the file as being Output.
In the case of a restart, all the variables will be populated. The start-string
variables and the rtk_file structure will contain the values where the
program left off. If the program is not restarting, only the
commit_max_ctr variable is filled. This condition can be trapped by a
new function, is_new_start(). This is used to initialize variables that need
a value even for a new start, such as the line sequence variable in the
previous example.
137
Driving Cursor
As alluded to in lesson 2, two changes are made to the driving cursor for
implementing restart/recovery:
1. An order by clause must be added. It must include the columns
corresponding to the LUW elements. (mandatory)
2. The where clause must be amended to select only those values
greater than the restart start string values. (mandatory)
Using the store-SKU example program again, the driving cursor might
look like:
EXEC SQL DECLARE c_sales_data CURSOR FOR
SELECT
sku,
store,
NVL(unit_cost,0),
NVL(sales_type,R),
NVL(sales_units,0),
NVL(sales_value,0),
FROM
win_store
WHERE
(store > NVL(:ps_restart_store, -999) OR
(store = :ps_restart_store AND sku >
:ps_restart_sku))
ORDER BY store, sku;
138
Committing
Now that the what to commit has been decided, the how and when
will be discussed. Nowhere in a batch program should there be ANY line
with EXEC SQL COMMIT or ROLLBACK. This job is handled by 2
new functions: retek_commit() and retek_force_commit(). Which to
choose and how to use them depends mainly on the type of LUW chosen
for the program. Retek_commit() should be used with a non-unique LUW.
For that case, retek_commit() will be called after each record is processed.
It will maintain a counter and ensure that the number of records processed
is > or = to RESTART_CONTROL.COMMIT_MAX_CTR and that a
new LUW has been reached. On the other hand, if the LUW is unique,
retek_force_commit() should be used. It commits, as the name implies,
everytime it is called and should be called only after an array of size =
COMMIT_MAX_CTR has been processed.
Both of these functions have the same variable parameter list. In much the
same way as retek_init(), the first parameter for these two functions is a
pre-compiler macro,
e.g. #define NUM_COMMIT_PARAMETERS 4. Next, pass in all the
strings that make up the start-string and the image-string, in the same
order as was passed into retek_init().
if (retek_commit(NUM_COMMIT_PARAMETERS,
pa_fetch.sku[ll_cur_rec],
pa_fetch.supplier[ll_cur_rec],
pa_fetch.origin_country_id[ll_cur_rec],
pa_fetch.start_date[ll_cur_rec]) < 0)
return(-1);
}
139
Closing
The function retek_close() is used to close file streams, commit work, and
update restart-recovery tables. It has no parameters.
140
Function Declarations
In retek_2.h:
int retek_init
(int num_args,
init_parameter parameter[],);
int commit_point_reached
(int num_args,);
int retek_force_commit
(int num_args, ...);
int retek_close(void);
Cleans up restart/recovery
tables, commits/rolls back
last changes to the database,
and closes files.
void increment_current_count(void);
Increments pl_current_count.
int is_new_start(void);
int parse_name_for_thread_val
(char* name);
141
1. retek_init
= Pass in num_args as the number of elements in the init_parameter array, then the
init_parameter array, then variables a batch program needs to initialize in the order and
types defined in the init_parameter array.|
= NOTE: all variables need to be passed by reference (incl. int and long);
= Get all global and module level values from databases;
= Initialize records for RESTART_PROGRAM_STATUS and RESTART_BOOKMARK;
= Parse out user-specified initialization variables (variable arg list);
= Return NO_THREAD_AVAILABLE if no qualified record in RESTART_CONTROL or
RESTART_PROGRAM_STATUS;
= Commit work.
2. retek_commit
= Pass in num_args, then variables for start_string first, and those for image string (if
needed) second. The num_args is the total number of these two groups. All are string
variables and are passed in the same order as in retek_init();
=
Check if commit point reached (counter check and, if table-based, start string
comparison);
= If reached, concatenated image_string from passed in variables (if needed) and call
internal_commit() to get out_file_string and update RESTART_BOOKMARK;
=
3. commit_point_reached
= Pass in num_args, then all string variables for start_string in the same order as in
retek_init(). The num_args is the number of variables for start_string. If no start_string
(as in file-based), pass in NULL;
= For table-based, if pl_curren_count reaches pl_max_counter and if newly concatenated
bookmark string is different from ps_cur_string, return 1; otherwise return 0;
= For file-based, if pl_curren_count reaches pl_max_counter return 1; otherwise return 0.
142
NOTE: The difference between this function and the check in retek_commit() is that here
the pl_current_count and ps_cur_string are not updated. This checking function is
designed to be used with retek_force_commit(), and the logic to ensure integrity of LUW
exists in user batch program.
4. retek_force_commit
= Pass in num_args, then variables for start_string first, and those for image string (if
needed) second. The num_args is the total number of these two groups. All are string
variables and are passed in the same order as in retek_init();
= Concatenate start_string either from passed in variables (table-based) or from ftell of
input file pointers (file-based);
= Concatenated image_string from passed in variables (if needed) and call
internal_commit() to get out_file_string and update RESTART_BOOKMARK;
= If table-based, increment pl_current_count and update ps_cur_string.
5. retek_close
= If gi_error_flag or NO_COMMIT command line option is TRUE, rollback all database
changes;
= Update RESTART_PROGRAM_STATUS according to gi_error_flag;
= If no gi_error_flag, insert record into RESTART_PROGRAM_HISTORY with
information fetched from RESTART_CONTROL,
RESTART_PROGRAM_BOOKMARK and RESTART_PROGRAM_STATUS tables;
= If no gi_error_flag, delete RESTART_BOOKMARK record;
= Commit work;
= Close all opened file streams
143
Array Processing
Since a majority of Retek batch programs implement array processing, it is
important to note how array processing affects restart/recovery
implementation. A key determinant of the exact details is the LUW. Is it
unique or non-unique?
When the LUW is unique, given that the arrays are sized using
commit_max_ctr and given that the restart_cur_string and the
restart_new_string will differ for every record, a commit may be forced
after each set of arrays is processed (i.e. after the for loop, inside the while
loop).
With a non-unique LUW, as discussed above, there is the possibility that
the commit_max_ctr will be reached in the middle of a LUW. When
processing arrays sized equal to the commit_max_ctr, this means a LUW
may span two array fetches, thus you cannot force a commit after each set
of arrays as you did with a unique LUW. As a result, the commit logic
must take place inside the for loop (i.e. a commit can happen in the middle
of an array).
The following examples progress through four levels of complexity of a
process( ) functions while loop:
= No array processing, no restart/recovery
= Array processing, no restart/recovery
= Array processing & restart/recovery with unique LUW
= Array processing & restart/recovery with non-unique LUW
1. No array processing, no restart/recovery
while(1)
{
EXEC SQL FETCH c_win_store INTO :ls_sku,
:ls_store;
if NO_DATA_FOUND
break;
if (calculate_price(ls_sku, ls_store, ls_price) < 0)
return (-1);
EXEC SQL UPDATE win_store
SET price = :ls_price
WHERE sku
= :ls_sku
AND store = :ls_store;
}
144
Note: There are two sets of arrays, one for fetching and one for updating, and they share
the same index counter, li_current_rec.
li_data_to_process = 1;
char *ls_sku, *ls_store;
while(li_data_to_process)
{
EXEC SQL FETCH c_win_store INTO :pa_fetch_array.s_sku,
:pa_fetch_array.s_store;
li_recs_returned = NUM_RECORDS_PROCESSED li_recs_completed;
li_recs_completed = NUM_RECORDS_PROCESSED;
if NO_DATA_FOUND
li_data_to_process = 0;
for(li_current_rec = 0;
li_current_rec < li_recs_returned;
li_current_rec++)
{
ls_sku = pa_fetch_array.s_sku[li_current_rec];
ls_store = pa_fetch_array.s_store[li_current_rec];
if (calculate_price(ls_sku, ls_store, ls_price)< 0)
return(-1);
strcpy(pa_update_array.s_price[li_current_rec],
ls_price);
}
EXEC SQL FOR :li_recs_returned
UPDATE win_store
SET price = :pa_update_array.s_price
WHERE sku
= :pa_fetch_array.s_sku
AND store = :pa_fetch_array.s_store;
}
145
strcpy(la_update.s_price[la_fetch.l_cur_rec],
ls_price);
}
EXEC SQL FOR :la_fetch.l_num_recs
UPDATE win_store
SET price = :la_update.s_price
WHERE sku
= :la_fetch.s_sku
AND store = :la_fetch.s_store;
/* force a commit */
if(retek_force_commit(2,
la_fetch.s_sku[la_fetch.l_num_recs - 1],
la_fetch.s_store[la_fetch.l_num_recs 1 ])<0)
return(-1);
}
while(!cursor_empty)
{
EXEC SQL FETCH c_win_store INTO :la_fetch_array.s_sku,
:la_fetch_array.s_store;
if(NO_DATA_FOUND)
cursor_empty = 1;
la_fetch.l_num_recs = NUM_RECORDS_PROCESSED
total_recs;
total_recs = NUM_RECORDS_PROCESSED;
for(la_fetch.l_cur_rec = 0;
la_fetch.l_cur_rec < la_fetch.l_num_recs;
la_fetch.l_cur_rec++)
{
/* if time to commit, then do update */
if(commit_point_reached(2,
la_fetch.s_sku[la_fetch.l_cur_rec],
la_fetch.s_store[la_fetch.l_cur_rec]))
{
EXEC SQL FOR :la_update.l_num_recs
UPDATE win_store
SET price = :la_update.s_price
WHERE sku
= :la_fetch.s_sku
AND store = :la_fetch.s_store;
la_update.l_num_recs = 0;
} /* end if time to commit, then UPDATE */
if(retek_commit(2,
la_fetch.s_sku[la_fetch.l_cur_rec,
146
la_fetch.s_store[la_fetch.l_cur_rec])<0)
return(-1);
ls_sku = la_fech.s_sku[la_fetch.l_cur_rec];
ls_store = la_fetch.s_store[la_fetch.l_cur_rec];
if(calculate_price(ls_sku, ls_store, ls_price)< 0)
return(-1);
strcpy(la_update.s_price[la_update.l_num_recs],
ls_price);
strcpy(la_update.s_sku[la_update.l_num_recs],ls_sku);
strcpy(la_update.s_store[la_update.l_num_recs],
ls_sku);
la_update.l_num_recs++;
}
} /* end for */
/* end while */
if(la_update.l_num_recs > 0 )
{
/* last time through for update/commit may not have
* happened Therefore, a final update call and forced
* commit are required here
*/
}
147
148
Tables:
RESTART_BOOKMARK:
Additional column:
Name
------------------------OUT_FILE_STRING
NON_FATAL_ERR_FLAG
NUM_COMMITS
AVG_TIME_BTWN_COMMITS
Null?
-------NULL
NULL
NULL
NULL
Type
---VARCHAR2(255)
VARCHAR2(1)
NUMBER(12)
NUMBER(12)
Null?
-------NULL
NULL
Type
---NUMBER(15)
NUMBER(15)
Null?
-------NULL
NULL
NULL
NULL
NULL
Type
---NUMBER(15)
VARCHAR2(1)
VARCHAR2(1)
NUMBER(12)
NUMBER(12)
RESTART_PROGRAM_STATUS:
Additional column:
Name
------------------------CURRENT_ORACLE_SID
CURRENT_SHADOW_PID
RESTART_PROGRAM_HISTORY:
Additional column:
Name
------------------------SHADOW_PID
SUCCESS_FLAG
NON_FATAL_ERR_FLAG
NUM_COMMITS
AVG_TIME_BTWN_COMMITS
149
Example Program
Compare the programs code_restartrecovery.pc, which is a simple
implementation of the old API, with example.pc, which implements the
same functional logic using the new r/r API.
See also example1.pc, example2, example3.pc and example4.pc available
from your instructor for more complex examples of the new API usage.
Exercise 4
Make a copy of exercise 2 and rename it for exercise 4 (cp xxx_02.pc
xxx_04.pc, where xxx = your initials)
= Add restart recovery functions to the program.
= Add restart recovery to the driving cursor.
= Implement restart recovery logic
= See handout for additional information.
150
151
Whats Next
Restart/recovery and mulithreading are conceptually different attributes of
a batch program. In fact, one can be implemented without the other.
However, the Retek Restart/Recovery and Multithreading API, for better
or worse, combine their implementation. The next module discusses
multithreading concepts and Reteks batch programming implementation.
Once completed, you will be able to implement either or both in a batch
program.
152
154
Module Overview
As was explained in module 10, the implementations of multithreading
and restart/recovery in Retek batch programs are inextricably intertwined.
Yet, they are very separate concepts and can be implemented individually
or together in any single program.
This module discusses the benefits of multithreading, its implementation
in Retek batch programs, and finally, the database tables used for
persistent storage of restart/recovery and multithreading data.
Objectives
After completing this lesson you will be able to:
Explain the purpose of multithreading of batch programs.
Understand the variables and functions provided in the API.
Understand the restart/recovery and multithreading data model.
Implement restart/recovery or multithreading or both in a batch
program.
155
156
(EXAMPLE)
157
Which Thread Am I?
Each instance of a batch program with program multithreading must know
its thread value. This is determined during the call to retek_init( ).
There are actually two values required to answer this question completely.
Answering, Which thread am I? actually requires knowing, which
thread am I, and out of how many? It is not enough to know that a
program is thread number two, it must know that it is thread two of five in
order to properly divide the data.
During retek_init( ) two variables are populated to hold these values.
pi_num_threads is an integer pointer argument passed as an argument. It
points to the total number of threads scheduled. pi_thread_val stores the
integer value of the current thread, and should be copied into a local
variable.
158
These views tell the threads how to divide up the records. To better
understand views, look at the v_restart_dept view. On this table, there is a
set of records for every number of threads currently on restart_control for
this particular driver, num_threads. In each set, there is one record for
every existing department, driver_value. Finally, the value in thread_val
determines which thread will work on this department. For example,
assume 4 departments and 3 programs that thread by department, one with
only 1 thread, one with 2 threads, and one with 4 threads. v_restart_dept
would look like this.
DRIVER_NAME
NUM_THREADS DRIVER_VALUE THREAD_VAL
-------------------- ----------- ------------ ---------DEPT
1
1001
1
DEPT
1
1002
1
DEPT
1
1003
1
DEPT
1
1004
1
DEPT
2
1001
2
DEPT
2
1002
1
DEPT
2
1003
2
DEPT
2
1004
1
DEPT
4
1001
2
DEPT
4
1002
3
DEPT
4
1003
4
DEPT
4
1004
1
So, given that any particular instance of a batch program knows its thread
value and the total number of threads, it only requires one more bit of
information to be able to select all its DRIVER_VALUES from the
appropriate restart view; namely the driver name. This, too, is a variable
populated by retek_init( ).
Driving Cursor
Given all these values, the driving cursor can now be modified to select
only the data assigned to the current thread. This is accomplished by
joining the existing cursor to the V_RESTART table.
Continuing with the previous example, the cursor might be modified as:
SELECT ...
FROM promstore, v_restart_store, ...
WHERE v_restart_store.driver_name =
:ls_restart_driver_name
AND v_restart_store.driver_value = promstore.store
AND v_restart_store.num_threads =
:ls_restart_num_threads
AND v_restart_store.thread_val
=
:ls_restart_thread_val
AND ...
159
restart_control
The RESTART_CONTROL table is the master table in the
restart/recovery table set. One record will exist on this table for each batch
program that is run with restart/recovery logic in place.
The restart/recovery initialization process uses this table to determine:
= The total number of threads used for each batch program,
= The maximum records that will be processed before a commit event
takes place,
= The driver for the threading (multi-processing) logic.
160
restart_program_status
The RESTART_PROGRAM_STATUS table holds record keeping
information about current program processes. The number of rows for a
program on the status table will be equal to its NUM_THREADS value on
the RESTART_CONTROL table.
The table is modified during restart/recovery initialization and close logic.
The restart/recovery initialization logic will assign the next available
thread to a program based on the program status and restart flag. Once a
thread has been assigned the program_status is updated to prevent the
assignment of that thread to another process. Information will be logged
on the current status of a given thread, as well as record keeping
information such as operator and process timing information.
restart_program_history
TheRESTART_PROGRAM_HISTORY table will contain one record for
every successfully completed program thread with restart/recovery logic.
The restart_close( ) function inserts this record. Table purgings will be at
user discretion.
restart_bookmark
When a restart/recovery program thread is currently active, its state is
started or aborted. a record for it will exist on the restart_bookmark table.
Restart/recovery initialization logic inserts the record into the table for a
program thread. The restart/recovery commit process updates the record
with the following restart information:
= A concatenated string of key values for table processing,
= A file pointer value for file processing,
= Application context information such as counters and accumulators.
The restart/recovery closing process will delete the program thread record
if the program finishes successfully. In the event of a restart, the program
thread information on this table will allow the process to begin from the
last commit point.
161
v_restart_x
As discussed in lesson 3 in this module, restart views will be used for
query-based programs that require multi-threading. Separate views will be
created for each threading driver, such as department or store. A join will
be made to a view based on threading driver to force the separation of
discrete data into particular threads.
1. Restart_Control:
Column
Program_name
Program_desc
driver_name
num_threads
update_allowed
process_flag
commit_max_ctr
Initial Value
Should always be the same as restart_name with some rare exceptions
(defined in the program and on restart_program_status table).
Description of the program - will be used in the online maintenance
screen.
Threading mechanism to be used: check out the views for actual
names. Examples - DEPT, STORE, STORE_WH, PRICE_CHG,
Use NONE when implementing r/r but not multi-threading.
Total number of threads to be used by the program.
Recommendation for testing is to set this to 2.
Clients will be able to modify this number for better performance.
Always set to 1 in file based processing.
File based processing (and potentially others) set this to N else Y.
F for file-based processing, T for table-based.
The number of records to be processed before a commit event occurs
162
2. restart_program_status:
Column
restart_name
thread_val
start_time
program_name
program_status
restart_flag
Restart_time
Finish_time
current_pid
current_operator_id
err_message
Initial Value
Must be the same as in the program
There should exist as many records on this table as defined in
num_threads on RESTART_CONTROL, thread_val is the integer
counter.
If num_threads = 3, there should be 3 records on this table, one with
thread_val =1, one with thread_val = 2, and one with thread_val = 3.
Null (set by retek_init function)
Same as program_name on restart_control (probably same as
restart_name)
Initially enter as ready for start
Should be null - should only have value (Y) when program is
restarting. On normal start, this should always be null.
Null (set by retek_init function)
Null (set by retek_close function)
Null (possibly set by retek_init function)
Null (possibly set by retek_init function)
Null (set by retek_close function)
163
164
if SQL_ERROR_FOUND
{
printf("cannot update restart_program_status\n");
exit(-1);
}
if NO_DATA_FOUND
{
printf("cannot update...no restart_name = %s on
table\n",
ora_prog_name.arr);
exit(-1);
}
EXEC SQL COMMIT;
printf("refresh done\n");
return(0);
}
Exercise 5
Make a copy of exercise 4 and rename it for exercise 5 (cp xxx_04.pc
xxx_05.pc, where xxx = your initials)
= Modify Exercise 4 to include multithreading
= See handout for additional information.
165
Whats Next
File interfaces to and from Retek products.
166
168
Module Overview
An interface batch program is simply a program that either takes data from
or prints data to a file.
An input (or upload) program reads data from a file that is then processed
and entered into the RMS database (an example is posupld, which reads
daily point of sales data from a file). An output or download program
prints the data to a file. That file can be sent to other places (such as
vendors), or used to interface with other systems the client uses (i.e.
accounts payable systems).
Objectives
After completing this lesson, you will be able to:
= Understand how Retek products interface with external systems
= Use Reteks file interface libraries
= Implement restart/recovery in a file based batch program
169
File Layout
The Retek interface library supports two standard file layouts. These are
master/detail files and detail only files. Master/detail files contain sets of
transactions with multiple detail lines within each transaction. Detail only
files consist of one transaction per detail line.
All records or lines within an input or output file, regardless of file type,
are identified by a 5-character file type record descriptor. This identifies
what type of record the line is. Each line of the file must begin with the
file type record descriptor, followed by a 10-character file line identifier.
This is the unique line number for each record.
Master/Detail Files
File layouts will have a standard file header record, a set of records for
each transaction to be processed, and a file trailer record. The transaction
set will consist of a transaction header record; one or more transaction
detail records which specify details such as SKUs or locations; and a
transaction trailer record. Valid record types include FHEAD, THEAD,
TDETL, TTAIL, and FTAIL. Other record types may be used in order to
better define the line type or in when there is more than one type of detail
line (i.e.: TSHIP, TITEM).
The FHEAD line has a 10-character long line ID plus customized
information; the THEAD lines have a 10-character ID plus a 14 character
long transaction ID plus customized information, as do the TDETL lines.
TTAIL lines have a 10-character ID followed by a 6-character long string
giving the number of lines in the transaction (not counting THEAD and
TTAIL lines). FTAIL lines have a line ID followed by a 10-character long
170
string giving the total number of transaction lines in the file (all THEAD,
TDETL, and TTAIL lines).
Line IDs are unique; transaction IDs are unique to a transaction but should
remain the same for all records within the transaction.
Example:
FHEAD0000000001RTV 19960908172000
THEAD000000000200000000000001199609091202000000000003R
TDETL000000000300000000000001000001SKU10000012
TTAIL0000000004000001
THEAD000000000500000000000002199609091202001215720131R
TDETL000000000600000000000002000001UPC400100002667
TDETL000000000700000000000002000001UPC400100002643 0
TTAIL0000000008000002
FTAIL00000000090000000007
171
As shown above, the structure contains the file pointer, the filename, and a
pad flag for adding the thread value to the filename. The normal struct
operators could access any of the elements of the structure if needed. The
library of pre-defined restart/recovery functions, however, is defined to
just pass in the struct name. The restart API takes care of opening the file,
adding the thread value to the name if its required, writing to it, and
closing the file. The functions using the new Retek file struct and the new
restart/recovery are similar to the old interface functions except they
accept the new file struct instead of a file pointer. The new functions have
been named retek_ + the old function name, e.g. the old interface
function get_record() has been recreated with the new file struct as
retek_get_record().
Reject Files
Because input files come from other systems, they are not entirely
trustworthy, and may contain bad data. Upload programs will check this,
and transactions containing bad data will be written by the upload program
to a reject file. This reject file has exactly the same format as the input file,
but only contains rejected transactions (a reject file for a run with a good
input file will only contain FHEAD and FTAIL lines). The FTAIL line
will contain the total number of lines in the reject file (not in the input
file).
172
Business Validation
It is also important to do business validation of variables coming from an
input file. For example, once you have checked that a SKU is all numeric,
you must also make sure that it exists in the system.
173
Reads the next line from input file in_file into the structure (of size
struct_size) pointed at by struct_ptr. The five-letter record
identification code (FHEAD, TDETL, etc.) is returned in rec_type.
get_record() performs minimal validation on the line, making sure
that the identification code is FHEAD, FDETL, FTAIL, THEAD, TDETL,
or TTAIL and that this line type is legally positioned in the file (i.e., that a
TDETL only follows a THEAD or another TDETL).
If the validation is unsuccessful for any reason, the line is written to reject
file rej_file and get_record() returns -1.
174
retek_write_rej()
int retek_write_rej(rtk_file *rej_file, rtk_file
*in_file)
usage: retek_write_rej(fp_rejrec,fp_carton)
Writes the current transaction being read from input file in_file to the
reject file rej_file. If in_file is a detail only file, only the current FDETL
record is written to rej_file. If in_file is a master/detail file, everything
from the last THEAD record to the current record is written to rej_file.
Note that when part of a transaction has an error, the entire transaction
should be written to the reject file, so write_rej() should be called only
after the current transactions TTAIL record is read.
Conversion Functions
left_shift()
int left_shift(char *str)
null_pad()
int null_pad(char *str, int str_len)
usage:
nullpad(table,255)
zero_pad()
void zero_pad(int str_len, char *str)
usage:
zero_pad(LEN_QTY, os_qty)
Pads str to length str_len with leading zeros (i.e., zero padding "123"
to str_len = 6 would result in "000123").
175
Validation Functions
valid_all_numeric()
int valid_all_numeric(char *str, int desired_len)
usage:
if (valid_all_numeric(os_carton,LEN_CARTON)...
valid_all_numeric_signed()
int valid_all_numeric_signed(char *str, int desired_len)
all_blank()
int all_blank(char
*str)
valid_date()
int valid_date(char *str)
176
177
178
set_filename()
int set_filename(rtk_file *file_struct,
char *file_name,
int pad_flag)
if (set_filename(&pf_out, argv[2], PAD)<0)
exit (-1);
Takes in a file name from the command line (argv[2]), and copies it into
the new rtk_file structure. The thread value can be appended to the name
in the format <file name>.<thread value>, by use of the pad_flag
parameter. Two pre-compiler definitions have been defined in retek_2.h
to handle this, PAD and NO_PAD. If the thread value is required to be
part of the name, use PAD for the pad_flag. If no thread should be
appended to the name, then use NO_PAD. Since the file is actually
opened in retek_init(), this function is normally called just before the call
to retek_init.
Exercise 6
Make a copy of exercise 4 and rename it for exercise 6 (cp xxx_04.pc
xxx_06.pc, where xxx = your initials)
1. Write a download program to copy data from this table to an output
file with the following structure:
179
180
Record
Variable name
Field type
Default
Description
FHEAD
Char(5)
FHEAD
identifies file
record type
Line number
Number(10)
0000000001
identifies file
line number
Program descriptor
Char(4)
WIST
identifies the
program
Create date
Char(14)
FDETL
Char(5)
line number
Number(10)
FDETL
Detail line
sequential line
number
sku
Char(8)
SKU number
store
Char(4)
store number
unit_cost
Char(20)
sales_type
Char(1)
R regular, 'P'
promotion, 'C'
clearance
sales_units
Char(20)
number of units
(4 implied dec.)
sales_value
Char(20)
value (4 implied
decimals.
FTAIL
Char(5)
line number
Number(10)
TTAIL
File trailer
sequential line
number
(here=total
number of lines
in file)
total number of
transaction
lines in file
(not including
FHEAD and
FTAIL)
181
Exercise 7
Make a copy of exercise 4 and rename it for exercise 7 (cp xxx_04.pc
xxx_07.pc, where xxx = your initials)
1. Write an upload program that reads records from output file produced
by the program in exercise one, validates the data, and writes them to
the trn_win_store_hist table.
182
184
Lesson 2: Resources
DDL
PVCS
Man
Books K&R, PL/SQL, UNIX Programming Environment
Oracle Documentation CD, technet
$l grep
impact grep
Dinkum C library reference
Operations Guide
Getting Started Guide
Standards Guides
185
Whats Next
Your first assignment.
186
188
Appendix Overview
Reteks batch programs are written in C and Pro*C, an Oracle proprietary
extension to C that provides easy access to an Oracle database. This
appendix reviews the major aspects of C, a popular procedural language,
with particular emphasis on how it is used at Retek.
This appendix is not intended to teach C to those with no experience in a
procedural programming language. If you are not familiar with concepts
such as variable declaration, assignment, Boolean logic, functions,
program flow and operator precedence please read Reteks Amazing C
Primer by Andy Beger, or the second edition of Brian Kernighan and
Dennis Ritchies The C Programming Language (Prentice Hall, 1988).
Objectives
After completing this appendix, you will be able to:
Name the basic data types in C.
Use sequence, iteration, selection and functions to control program
flow.
Recognize the various ways strings are declared, allocated and
manipulated.
Understand how a C program is invoked by the operating system.
Declare, define, and call functions.
Write a C program using Retek standards for naming variables.
Know where to find answers to C language questions.
189
as in
int my_integer;
There are four basic data types available in C; int, char, float, and double.
A variable declared as an int can hold an integer. The min and max values
are platform dependent. An integer declaration can be modified with the
following key words: signed, unsigned, short and long. Short and long
integers are commonly used in Retek batch programming.
A char variable holds one character. The exact internal representation is
platform dependent. Integers and characters can be freely interpreted as
the other data type (implicitly cast). This practice is not often used in
Retek code, but the concept may be useful in understanding compiler
messages.
Variables of type float and double hold real numbers. Doubles allow for a
larger range of values. By convention real numbers are always declared as
type double in Retek batch programs.
Notice there are NO types for Boolean or string variables. Boolean
expressions are evaluated using integers. Any non-zero integer is
considered true. Zero is false. String variables are discussed below.
190
xp now holds the address of the first integer of the array, x, that you
declared earlier. The & operator is said to de-reference the variable x[0].
In other words, it returns the address of the variable.
The value 12064 that you assigned to the first integer in your array can
now be accessed in multiple ways, *xp and x[0] are two examples. In this
situation the * operator means, the value pointed to by; as in, the
integer value pointed to by xp.
A bit more complexity is added when you examine the array name without
the square brackets that identify a specific array member. Consider the
array name x declared above. It is in every way, except one, treated by
the C language as a pointer. Thus, you can add *x to your list of ways of
referring to the value 12064. Also, your earlier assignment statement
xp = &x[0];
can be rewritten as
xp = x;
191
The one difference between a pointer and an array name is that a pointer is
a variable, an array name is not. Thus, the statements
xp = x
xp = xp + 1 /* adds 1 to the address, not the value
pointed to */
A final bit of caution about arrays. Arrays really have no internal structure
in C, as they do in some other programming languages. You cannot ask
an array how long it is, as you can in Java, for instance. Arrays are simply
pointers to the first element in a sequence of reserved memory locations.
The key point is that arrays provide no bounds checking. Given your
declaration
int x[10];
192
Strings
Representation
Recall that there is no string data type in C. Rather, strings are represented
as arrays of characters. Even though there is no such data type, the term
string is used frequently in this course and generally among C
programmers. Always keep in mind that this term is informal, and strictly
speaking, is referring to a character array.
Based on the above discussion of arrays and pointers, you might recognize
a difficulty with this representation of strings. That is, if arrays have no
inherent length management and strings by definition are of variable
length, then managing the length of a string becomes the problem of the
programmer.
Cs solution is relatively elegant, but, to say the least, not very robust.
There is a character value refereed to as the null (or nil) character. The
null character is used as the terminating character for strings. Thus, a
string is actually a character array with an implicit agreement between the
programmer and the runtime system that the final character will be the null
character.
As a result, when an operation is to be performed on a string, the first
character in the string is referenced with a character pointer. The
operation continues on subsequent characters until the null character is
encountered. If, for some reason, a null character is never encountered
(and hence the implicit agreement is violated), bad things will happen.
Memory Allocation
A further difficulty is in ensuring that the character array in which a string
is to be stored is large enough. Imagine a program that needs a string
variable to hold customer names input from a user interface. How long
should a character array be declared to hold a customer name?
This is a very real problem for C programmers. Fortunately, Retek batch
programs deal with data fields the length of which are usually known
they are defined by corresponding columns on a database table.
Nevertheless, it is worth pointing out that there are essentially two
approaches to memory allocation. The first is static, as in
char last_name[120];
193
Structs
An array is a collection of objects of the same type that are stored in
contiguous memory locations. Structs are similar but allow for objects of
various types to be stored together.
Heres what a structure declaration looks like:
struct <structure name>
{
<field type> <field name>;
<field type> <field name>;
...
} <variable name>;
If you wanted to create a struct for a personnel program, it might look like
this:
struct personnel_record
{
char name[30];
int age;
char sex;
} Andy_record;
194
Control Structures
All logic possible using a procedural language can be accomplished
through three basic control structures: sequence, iteration, and selection.
Sequence
Sequence is the most basic control structure. It describes how control
flows from one statement to the next in the order they are entered in the
source code file. Consider the following two statements.
x = x + 1;
y = x * 2;
/* line 1 */
/* line 2 */
Iteration
Iteration is a generic name for the class of control structures that allow for
repeated execution of a set of statements. In C, there are two basic kinds
of iteration: the while loop and the for loop.
The while loop is a relatively straightforward:
while ( <expression> )
195
{
.
.
.
}
Basically, what this says is, Do everything in the following block of code
over and over as long as <expression> is true. Then go on. For
example, consider the following code:
int i = 10;
/*
while (i <= 12)
{
i = i + 1;
}
j = m + 58;
/*
Line 1 */
/* Line 2 */
/* Line 3 */
Line 4 */
i starts out as 10. Then, Line 2 checks: is i (10) less than or equal to 12?
Since it is, you go on to Line 3 and add one to i. Now, back to Line 2 to
check: is i (11) less than or equal to 12? Yes. So, add one to i. Since i
(12) is still less than or equal to 12, you add one more to i. Now, when
Line 2 checks, i (13) is not less than or equal to 12, so you do not do Line
3, and you go on to Line 4 and you continue from there.
The for loop is basically an advanced while loop:
for (<statement1>; <expression>; <statement2>)
{
.
.
.
}
What this says is: First, do <statement1>. Now, do the code in the
braces over and over while <expression> is true. Each time, after youve
done the code in the braces, but before youve checked <expression>, do
<statement2>. Confused? Well, maybe this will help: a for statement
can basically be looked at like this:
<statement1>;
while (<expression>)
{
.
.
.
<statement2>;
}
196
Selection
The if statement in C allows you to tell the program to only execute
certain commands if a certain condition is true. Remember, there is no
Boolean data type, so integers are used. Non-zero is true, and zero is
false. The Boolean comparison operator == returns 1 if true, and 0 if
false. The general structure of an if statement is
if ( <expression> )
{
<one or more statements>
}
else if ( <expression> )
{
<one or more statements>
}
else
{
<one or more statements>
}
197
But what if you want to do one thing when j is even, and another thing
when its odd? You could write two if statements, but thats pretty
redundant, especially since you have the else clause. The else clause says
if the expression in the if statement is not true, do the following
commands instead. For example,
x = 5;
/*Line 1*/
y = 9;
/*Line 2*/
if (j % 2 == 0)
/*Line 3*/
{
x++;
/*Line 4*/
}
else
{
y = x * 3;
/*Line 5*/
}
printf(x is %d.\ny is %d.\n, x, y);
/*Line 6*/
When j is even, Line 3 will evaluate to True, and so Line 4 will get
executed, and then you go on to line 6 and print out
x is 6.
y is 9.
198
Goto
Notice that gotos are not a necessary control structure according to the list
presented above. Any logic that can be accomplished with a goto can be
done better with sequence, integration, and selection. It is generally
accepted that using goto statements can easily result in unmanageable
code. Although goto statements are part of the C language, they are
NEVER used in Retek batch programming.
199
Lesson 2: Operators
The basic operators:
Mathematical
Operators
+ addition
== equal to
- subtraction
!= not equal to
* multiplication
/ division
% modulo
++
unary increment
--
unary decrement
Logical Operators
Associativity
Left to right
Right to left
Left to right
Left to right
Left to right
Left to right
Left to right
Left to right
Right to left
200
Lesson 3: Functions
Main()
The most basic C program looks like this
main()
{
<one or more commands>
}
Main is a function. Every C program has exactly one function called main
because this is the function invoked by the operating system when a
program begins executing. The general syntax of a function will be
discussed presently.
Function Basics
Although programs could be written as one block of code in main(), it
would be extremely wasteful and difficult to read. This approach would
be wasteful because most programs repeat certain tasks many times, which
in turn, would require repeating the same sequence of commands many
times. A function is a set of commands grouped together and named.
This allows the set of commands to be repeated executed by simply calling
the function. This should be a familiar concept. If not, refer to a text on
the C programming language.
The basic syntax of a C function is:
<return type> <function name> ( <set of parameter
declarations> )
{
<variable declarations>
.
.
.
<executable statements>
.
.
.
return(<return value>);
}
201
Variable Scope
Variables may be declared outside the scope of any function. In which
case they called global variables. Global variables can be seen and used
by any function in the program. Global variables keep their values for as
long as the program is running. There is occasionally good reason to use
global variables, but they are to be avoided.
One last thing: a local variable can have the same name as a global
variable; C isnt going to stop you. However, be warned: if youre in a
function that has a local variable that has the same name as a global
variable, C will assume that youre always referring to the local variable in
that function. Generally, its a good idea not to override variable names
like that.
202
Parameter Passing
The subtle implications of feature 7 listed above, Arguments are always
passed by valueincluding pointers, usually take experience to fully
appreciate, and are a key to truly being comfortable writing C. In general,
passing arguments by value means that, while a function can change the
value of an argument, the value of the variable in the calling function
remains unchanged. This is also called passing by copy.
int foo()
{
int x = 10;
bar(x);
printf(x = %d,x);
}
/* x = 10 */
int bar(int a)
{
a = 15;
return(0);
}
/* x = 15 */
203
An important bit of subtlety is that even pointers are passed by copy. This
means that if the address stored in a pointer is changed in a called
function, the address to the original variable is not lost.
int foo()
{
int x = 10;
int *xp;
xp = &x;
bar(xp);
printf(*xp = %d,*xp);
= 10 */
}
/* xp still points at x. x
204
Function Prototypes
Functions may be declared before they are defined, usually near the top of
the source code file. This practice is encouraged because it provides the
compiler with a signature for each function. This signature is used at
compile time to verify each function call has the correct number and type
of arguments.
Without function prototypes, a program may compile, link and run with
incorrectly formatted function calls. If, for example, a non-prototyped
function is written to use two arguments, but only one is passed in, the
runtime system will use what may seem like a random value for the
second argument and the results will usually be very confusing.
This type of bug is usually quite time consuming to find. Functional
prototyping is an easy way to ensure that you will never need to do so.
Here is an example with some function prototypes.
/* prototypes */
int foo(int x, char *y);
double bar(double d, double dd);
main()
{
int a = 2;
int b = 3;
int c;
double d = 5.23;
double e = 6.29;
double f;
c = foo(a);
compiler
foo prototype */
c = foo(a, b);
}
int foo(int x, char *y)
{
return (0);
}
double bar(double d, double dd)
{
return (0);
}
205
Function Scope
A function can be called from any other function declared in the same
program. The order in which they are defined does not matter.
C programs may span more than one source code file, making it possible
to call functions defined in multiple files. In Retek batch programming,
you make some use of this through library functions. However, ignoring
for a moment these library functions which most programmers will never
be asked to change, your batch programs will be contained in one source
file. As a result, this course will not discuss in any depth the syntax or
semantics of creating and using functions in multiple files.
Main() Revisited
Now that you have a fuller understanding of function declarations and
parameter passing, return to main( ), the function that is called by the
operating system to begin processing a C program.
This is the complete signature for main.
int main ( int argc, char *argv[ ] )
206
Value
3
tria
3.24
5.4444
Notice the numeric arguments are stored in strings. The program would
need to convert them to double before calculating the triangles area.
207
Description
Concatenates s2 on to s1
Searches s for the first
occurrence of c, returns
the address of c, else
NULL
Compares s1 and s2.
Returns 0 if they are the
same, else non-zero.
Copies s2 into s1
Copies s2 into s1, but
only up to at most n
characters
Prints formatted text
string to stdout
Prints formatted text
string to stream
Stores formatted text
string in string s
Reads text from stdin in
the using formatted string
as guide to format of
input, each of the
variables to be used for
storage must be pointers
Returns string s
converted into an integer.
Ex. atoi(12) returns the
integer 12
Ex. atoi(2.3) returns
the integer 2
Ex. atoi(hi) returns null
(zero)
208
Notice first that the character % is used to specify a that variables value
should be inserted into the string in the current position. The format string
is followed by a comma-separated list of variables holding the values to be
inserted.
As you might guess the letter(s) following the % specify the type of
variable. As shown in the example, s specifies a string, d specifies an
integer and lf specifies a double. Refer to a text for an exhaustive list of
type specifies.
More complex examples of format strings will be part of the file
interfacing appendix later in this course.
The \n at the end of the format string is the new line character.
209
The preprocessor would replace this and all other instances of the string
FALSE with the constant 0.
Most often, Retek #defines are used to implement constant values as
described in the next lesson.
210
<stdio.h>
<stdlib.h>
<string.h>
<ctype.h>
<memory.h>
<termio.h>
<math.h>
<floatingpoint.h>
<restart.h>
<std_err.h>
<std_len.h>
/* macros */
#define MATCH(a,b)
!strcmp(a,b)
Of the eleven header files listed, the first eight are standard C headers. To
view their contents visit the directory /usr/include. For more information
and examples, there are many web sites and texts with descriptions of the
standard C libraries.
The final three header files are Retek specific. They can be viewed in
your $l directory.
= Restart.h provides the functionality for restart and recovery which will
be discussed later in this course.
= Std_err.h provides several variables and macros used to simplify error
handling in Pro*C. Here is the text of std_err.h:
char PROGRAM[30];
char err_data[355];
char table[255];
/* macros */
#define NO_DATA_FOUND
(SQLCODE == 1403)
#define SQL_ERROR_FOUND
(SQLCODE != 0 && SQLCODE !=
1403)
#define NUM_RECORDS_PROCESSED sqlca.sqlerrd[2]
#define DUP_VAL_FOUND
(SQLCODE == -1)
/* define error codes to
function */
#define RET_FUNCTION_ERR
#define RET_FILE_ERR
#define RET_PROC_ERR
#define RET_EDI_ERR
211
String variables should never be defined with a hard coded integer value.
Rather, always use a #defined constant.
char store_id1[5];
/* works, but very bad
practice */
char store_id2[NULL_STORE]; /* much better */
Exercises
1. Write a C program to print the sum of two integer command line
arguments to stdout. Here is an example compile and run.
> cc -o add add.c
> add
usage: add <int1> <int2>
> add 12 38
12 + 38 = 50
> add 2.5 34
2 + 34 = 36
> add 3.33 4.2232
3 + 4 = 7
> add one two
0 + 0 = 0
212
213
grade
F
D
C
B
A
214
216
Appendix Overview
Creating quality in the first place saves money, time, and frustration in the
long run. As one batch trainer so succinctly phrased it, Coding is never a
race. It is also important to keep in mind that other people will be
reading (and maintaining) your code. . . creating quality code makes their
(and your) job easier.
Objectives
After completing this appendix, you will be able to:
Understand how implementation (a.k.a. coding) fits into the context of
a software development organization.
Approach a difficult programming task in a thoughtful and structured
manor.
Know where to begin looking for background information relative to
your programming tasks.
Understand the importance of modularity and have a vocabulary for
discussing modularity issues.
217
218
Functional Knowledge
Whenever a new project begins, many developers are eager to begin
coding and only do a brief and perfunctory read through of the functional
design before concentrating on the individual piece or pieces of
functionality that has been assigned to them. THIS IS A MISTAKE.
Take the time to read through all the functional and technical designs.
Ask questions of the lead designers about how your piece fits in with the
rest of the project. Many problems and issues that were not apparent at a
high level, can be found and fixed early if the developers have a real
functional knowledge of what is going on. Designers can often miss
issues that can become critical at later points. . . if you as a developer have
a sturdy understanding of the functional whole AND you are actually
getting down and dirty with the code, you will find things that others miss.
Guaranteed.
Resources for a greater functional understanding can be:
-
the designer
219
Unit Testing
While many developers groan at the prospect of organized unit testing, the
quality of code that results from researched, thoughtful unit test cases is so
superior to that code which goes through a partial, disorganized and
undocumented testing phase it boggles the mind. Test cases should be
thought through before a single line of code is typed out. . . and while this
may seem illogical, it ensures that the test cases are written to test what is
designed and not what is coded. Of course, when coding, additional
scenarios can always be added to test certain aspects of the code that
present themselves later on in the process.
One of the most useful, frustration-easing tools to help in the testing
process is the script. If your data can be re-setup simply by running a
script, hours of online work can be avoided. A script can, for example,
repeatedly re-insert records that you delete However, remember that all
data should always initially be set up online to ensure that all business
rules and dependencies are followed and that all necessary tables are
populated.
Stepwise Refinement
Stepwise refinement is a method for coding that focuses initially on the
big picture, the appendix as a whole, and only after all parts of the code
have been mapped out is complexity and functionality added. For
example, if the task given is to code a new program from scratch, a
developer using stepwise refinement as a mantra would initially map out
the totality of the program, perhaps drawing some flow or bubble charts
(informally) and picking the names and purposes of all of the sub
functions. Only after this program outline had been coded (and perhaps
even compile) would any of the guts of the functions be added. And
again, only after all the business functionality in is place and working
would features like restart/recovery and multithreading be added. One of
the benefits of this method is that it allows the developer to see where
functionality is repeated and hence will help in further modularizing the
code.
Lesson 3: Functions
Atomic code units (do one thing, do it well)
Modularity (cohesion, coupling)
Balance fan-in, fan-out
Parameters & arguments
Global variables (common coupling)
220
221
Lesson 4: Quality
Internal Quality
The internal quality of any given piece of code is determined by several
factors:
-
how well the functions are named (i.e. Can you tell exactly what a
given program does by a read-through of the function names? And if
some functions are too hard to name, perhaps they are not modularized
enough?)
External Quality
The external quality of code is determined by measuring very different
factors:
-
how robust it is
222
Exercise 1
/*********************************\
| 9.0 Batch Development Standards |
| xxx_01.pc
|
| Revised mm/dd/yy - Your Name
|
| Get vdate and get store
|
| and sku from trn_win_store
|
\*********************************/
#include "retek_2.h"
EXEC SQL INCLUDE SQLCA.H;
long SQLCODE;
/* Vdate assumed for most batch programs */
char ps_vdate[NULL_DATE];
int main(int argc, char *argv[])
{
int init_results;
if (argc < 2)
{
fprintf(stderr, "Usage: %s userid/passwd\n", argv[0]);
exit(-1);
}
if (LOGON(argc, argv) < 0)
exit(-1);
if (init() < 0)
{
fprintf(stderr,"Aborted in init...\n");
exit(-1);
}
if (process() < 0)
{
fprintf(stderr,"Aborted in process...\n");
exit(-1);
}
if (final() < 0)
{
fprintf(stderr,"Aborted in final...\n");
exit(-1);
}
else
exit(0);
} /* end main */
int init()
{
char *function = "init";
EXEC SQL VAR ps_vdate IS STRING(NULL_DATE);
EXEC SQL DECLARE c_get_vdate CURSOR FOR
SELECT TO_CHAR( vdate, 'YYYYMMDD' )
FROM trn_period;
EXEC SQL OPEN c_get_vdate;
EXEC SQL FETCH c_get_vdate INTO :ps_vdate;
EXEC SQL CLOSE c_get_vdate;
224
225
226
Exercise 2
/***********************************\
| 9.0 Batch Development Standards
|
| Exercise 2 - xxx_02.pc
|
| Revised mm/dd/yy - Your Name
|
| Gross product calculation with
|
| Get vdate function and get store, |
| sku, unit cost and sales info
|
| from trn_win_store
|
| error handling added. With gross |
| profit calculation
|
\***********************************/
#include "retek_2.h"
#define NULL_SATYPE
ls_sku[NULL_SKU];
ls_store[NULL_STORE];
ld_unit_cost;
ls_sales_type[NULL_SATYPE];
ld_sales_units;
ld_sales_value;
ld_gp;
/* Declare Cursor */
EXEC SQL DECLARE c_get_grossproduct
CURSOR FOR
select sku,
store,
NVL(unit_cost,0),
NVL(sales_type, 'R'),
NVL(sales_units,0),
NVL(sales_value,0)
from trn_win_store;
EXEC SQL OPEN c_get_grossproduct;
if (SQL_ERROR_FOUND)
{
sprintf(err_data, "Opening c_get_grossproduct");
strcpy(table, "trn_win_store");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
while(1)
{
/* fetch cursor */
EXEC SQL FETCH c_get_grossproduct
INTO :ls_sku,
:ls_store,
:ld_unit_cost,
:ls_sales_type,
:ld_sales_units,
:ld_sales_value;
if SQL_ERROR_FOUND
{
sprintf(err_data, "Fetch c_get_grossproduct");
strcpy(table, "trn_win_store");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
/* if no data found then break out of while loop */
227
228
if (NO_DATA_FOUND)
break;
ld_gp = ld_sales_value - (ld_sales_units * ld_unit_cost);
EXEC SQL INSERT INTO trn_win_store_hist
(sku,
store,
eow_date,
sales_type,
value,
gp)
VALUES
(:ls_sku,
:ls_store,
to_date(:ps_vdate, 'YYYYMMDD'),
:ls_sales_type,
:ld_sales_value,
:ld_gp);
if (SQL_ERROR_FOUND || NO_DATA_FOUND)
{
sprintf(err_data, "Inserting data into win_store_hist");
strcpy(table, "trn_win_store_hist");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
/*
printf ("SKU:%s
} /* end while */
printf("\nthe date is: %s\n", ps_vdate);
return(0);
} /* end process */
int final()
{
char *function = "final";
printf("Program completed successfully.\n");
return(0);
} /* end final */
/* Assumed future library function */
int get_vdate(char* os_vdate)
{
EXEC SQL VAR os_vdate IS STRING(NULL_DATE);
char* function = "get_vdate";
EXEC SQL DECLARE c_get_vdate CURSOR FOR
SELECT TO_CHAR( vdate, 'YYYYMMDD' )
FROM trn_period;
EXEC SQL OPEN c_get_vdate;
if (SQL_ERROR_FOUND)
{
sprintf(err_data,"Opening c_get_vdate");
WRITE_ERROR(SQLCODE,function,"trn_period",err_data);
return(-1);
}
EXEC SQL FETCH c_get_vdate INTO :os_vdate;
if (SQL_ERROR_FOUND || NO_DATA_FOUND)
{
sprintf(err_data,"Fetching from c_get_vdate");
return(0);
/* end of get_vdate() */
229
Exercise 3
/************************************\
| 9.0 Batch Development Standards
|
| Exercise 3 - xxx_03.pc
|
| Revised mm/dd/yy - Your Name
|
| Gross product calculation with
|
| array processing.
|
\************************************/
#include "retek_2.h"
#define NULL_TYPE 2
#define ARRAYSIZE 1000
struct fetch_array
{
char s_sku[ARRAYSIZE][NULL_SKU];
char s_store[ARRAYSIZE][NULL_STORE];
double d_unit_cost[ARRAYSIZE];
char s_sales_type[ARRAYSIZE][NULL_TYPE];
double d_sales_units[ARRAYSIZE];
short i_units_ind[ARRAYSIZE];
double d_sales_value[ARRAYSIZE];
short i_value_ind[ARRAYSIZE];
double d_gp[ARRAYSIZE];
short i_gp_ind[ARRAYSIZE];
short i_type_ind[ARRAYSIZE];
char s_vdate[ARRAYSIZE][NULL_DATE];
} pa_fetch_array;
EXEC SQL INCLUDE SQLCA.H;
long SQLCODE;
/* Vdate assumed for most batch programs */
char ps_vdate[NULL_DATE];
main(int argc, char *argv[])
{
char ls_logmessage[255];
if (argc < 2)
{
fprintf(stderr, "Usage: %s userid/passwd\n", argv[0]);
exit(-1);
}
if (LOGON(argc, argv) < 0) exit(-1);
if (init() < 0)
{
strcpy(ls_logmessage,"Aborted in init");
LOG_MESSAGE(ls_logmessage);
exit(-1);
}
if (process() < 0)
{
strcpy(ls_logmessage,"Aborted in process");
LOG_MESSAGE(ls_logmessage);
exit(-1);
}
if (final() < 0)
{
230
231
232
ws.sales_units,
ws.sales_value
FROM trn_win_store ws;
EXEC SQL OPEN c_get_win_store;
if (SQL_ERROR_FOUND)
{
sprintf(err_data,"Opening c_get_win_store");
WRITE_ERROR(SQLCODE,function,"trn_win_store",err_data);
return(-1);
}
/*start of while loop to insert into trn_win_store_hist*/
while (1)
{
EXEC SQL FOR :ARRAYSIZE
FETCH c_get_win_store INTO :pa_fetch_array.s_sku,
:pa_fetch_array.s_store,
:pa_fetch_array.d_unit_cost,
:pa_fetch_array.s_sales_type:pa_fetch_array.i_type_ind,
:pa_fetch_array.d_sales_units:pa_fetch_array.i_units_ind,
:pa_fetch_array.d_sales_value:pa_fetch_array.i_value_ind;
if (SQL_ERROR_FOUND)
{
strcpy(err_data,"Fetch c_get_win_store");
WRITE_ERROR(SQLCODE,function,"trn_win_store",err_data);
return(-1);
}
if (NO_DATA_FOUND) li_ndf=1;
li_recs_returned = NUM_RECORDS_PROCESSED - li_nrp;
li_nrp=NUM_RECORDS_PROCESSED;
for(li_current_record=0;
li_current_record < ARRAYSIZE;
li_current_record++)
{
/* calculate GP--mark as NULL if no units were sold */
if (pa_fetch_array.i_units_ind[li_current_record] != -1
&& pa_fetch_array.i_value_ind[li_current_record] != -1)
{
pa_fetch_array.d_gp[li_current_record] =
pa_fetch_array.d_sales_value[li_current_record] (pa_fetch_array.d_sales_units[li_current_record] *
pa_fetch_array.d_unit_cost[li_current_record]);
}
else
{
pa_fetch_array.i_gp_ind[li_current_record] = -1;
}
}
EXEC SQL FOR :li_recs_returned
INSERT INTO trn_win_store_hist(sku,
store,
eow_date,
sales_type,
value,
gp)
values(:pa_fetch_array.s_sku,
:pa_fetch_array.s_store,
TO_DATE(:pa_fetch_array.s_vdate,'YYYYMMDD'),
:pa_fetch_array.s_sales_type:pa_fetch_array.i_type_ind,
:pa_fetch_array.d_sales_value:pa_fetch_array.i_value_ind,
:pa_fetch_array.d_gp:pa_fetch_array.i_gp_ind);
if (SQL_ERROR_FOUND)
{
sprintf(err_data,"Inserting");
WRITE_ERROR(SQLCODE,function,"trn_win_store_hist",err_data);
return(-1);
}
if (li_ndf) break;
/*end of while loop*/
233
Exercise 3 Part 2
/***********************************\
| 9.0 Batch Development Standards
|
| Exercise 3 - Part 2 - xxx_03.pc
|
| Revised mm/dd/yy - Your Name
|
| Gross product calculation with
|
| dynamic array processing.
|
\************************************/
#include "retek_2.h"
#define NULL_TYPE 2
int gi_size;
struct fetch_array
{
char (* s_sku)[NULL_SKU];
char (* s_store)[NULL_STORE];
double *d_unit_cost;
char (* s_sales_type)[NULL_TYPE];
double *d_sales_units;
short *i_units_ind;
double *d_sales_value;
short *i_value_ind;
double *d_gp;
short *i_gp_ind;
short *i_type_ind;
char (* s_vdate)[NULL_DATE];
} pa_fetch_array;
EXEC SQL VAR pa_fetch_array.s_sku IS STRING(NULL_SKU);
EXEC SQL VAR pa_fetch_array.s_store IS STRING(NULL_STORE);
EXEC SQL VAR pa_fetch_array.s_sales_type IS STRING(NULL_TYPE);
EXEC SQL INCLUDE SQLCA.H;
long SQLCODE;
/* Vdate assumed for most batch programs */
char ps_vdate[NULL_DATE];
/* Function prototypes */
int size_array();
main(int argc, char *argv[])
{
char ls_logmessage[255];
if (argc < 3)
{
fprintf(stderr,"Usage: %s userid/passwd ARRAYSIZE\n",
argv[0]);
exit(-1);
}
if (LOGON(argc, argv) < 0) exit(-1);
gi_size = atoi(argv[2]);
if (init() < 0)
{
strcpy(ls_logmessage,"Aborted in init");
LOG_MESSAGE(ls_logmessage);
exit(-1);
}
234
235
236
while (1)
{
EXEC SQL FOR :gi_size
FETCH c_get_win_store INTO :pa_fetch_array.s_sku,
:pa_fetch_array.s_store,
:pa_fetch_array.d_unit_cost,
:pa_fetch_array.s_sales_type:pa_fetch_array.i_type_ind,
:pa_fetch_array.d_sales_units:pa_fetch_array.i_units_ind,
:pa_fetch_array.d_sales_value:pa_fetch_array.i_value_ind;
if (SQL_ERROR_FOUND)
{
strcpy(err_data,"Fetch c_get_win_store");
WRITE_ERROR(SQLCODE,function,"trn_win_store",err_data);
return(-1);
}
if (NO_DATA_FOUND) li_ndf=1;
li_recs_returned=NUM_RECORDS_PROCESSED-li_nrp;
li_nrp=NUM_RECORDS_PROCESSED;
for(li_current_record=0;li_current_record<li_recs_returned;li_curr
ent_record++)
{
/* calculate GP--mark as NULL if no units were sold */
if (pa_fetch_array.i_units_ind[li_current_record] != -1
&& pa_fetch_array.i_value_ind[li_current_record] != -1)
{
pa_fetch_array.d_gp[li_current_record] =
pa_fetch_array.d_sales_value[li_current_record] (pa_fetch_array.d_sales_units[li_current_record] *
pa_fetch_array.d_unit_cost[li_current_record]);
}
else
{
pa_fetch_array.i_gp_ind[li_current_record] = -1;
}
}
EXEC SQL FOR :li_recs_returned
INSERT INTO trn_win_store_hist(sku,
store,
eow_date,
sales_type,
value,
gp)
values(:pa_fetch_array.s_sku,
:pa_fetch_array.s_store,
TO_DATE(:pa_fetch_array.s_vdate,'YYYYMMDD'),
:pa_fetch_array.s_sales_type:pa_fetch_array.i_type_ind,
:pa_fetch_array.d_sales_value:pa_fetch_array.i_value_ind,
:pa_fetch_array.d_gp:pa_fetch_array.i_gp_ind);
if (SQL_ERROR_FOUND)
{
sprintf(err_data,"Inserting");
WRITE_ERROR(SQLCODE,function,"trn_win_store_hist",err_data);
return(-1);
}
if (li_ndf) break;
}
/*end of while loop*/
printf("\nthe date is: %s\n", ps_vdate);
return(0);
}/* end of process */
int final()
{
char *function = "final";
return(0);
/* end of get_vdate() */
int size_array()
{
char *function = "size_arrays";
int li_no_mem = 0;
if ((pa_fetch_array.s_sku =
(char(*)[NULL_SKU])calloc(gi_size,NULL_SKU)) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.s_store =
(char(*)[NULL_STORE])calloc(gi_size,NULL_STORE)) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.d_unit_cost =
(double*)calloc(gi_size,sizeof(double))) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.s_sales_type =
(char(*)[NULL_TYPE])calloc(gi_size,NULL_TYPE)) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.d_sales_units =
(double*)calloc(gi_size,sizeof(double))) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.i_units_ind =
(short*)calloc(gi_size,sizeof(short))) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.d_sales_value =
(double*)calloc(gi_size,sizeof(double))) ==
NULL) li_no_mem = 1;
237
238
if ((pa_fetch_array.i_value_ind =
(short*)calloc(gi_size,sizeof(short))) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.d_gp
=
(double*)calloc(gi_size,sizeof(double))) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.i_gp_ind
=
(short*)calloc(gi_size,sizeof(short))) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.i_type_ind =
(short*)calloc(gi_size,sizeof(short))) ==
NULL) li_no_mem = 1;
if ((pa_fetch_array.s_vdate =
(char(*)[NULL_DATE])calloc(gi_size,NULL_DATE)) ==
NULL) li_no_mem = 1;
if (li_no_mem)
{
sprintf(err_data,"Unable to allocate memory");
WRITE_ERROR(RET_FUNCTION_ERR,function,"",err_data);
return(-1);
}
return(0);
} /* end size_array */
Exercise 4
/*********************************\
| 9.0 Batch Development Standards |
| Exercise 4 - xxx_04.pc
|
| Revised mm/dd/yy - Your Name
|
| Gross product calculation with |
| table-based Retart/Recovery
|
| , sku, unit cost and sales info |
| from trn_win_store
|
\*********************************/
#include <retek_2.h>
#define NUM_INIT_PARAMETERS
array */
#define NULL_CODE
sales_type */
#define NULL_SATYPE 2
exit(0);
/* end of main() */
int init( )
{
char* function = "init";
int
li_init_return;
/********************************\
| retek_init:
|
| Initialize restart/recovery. |
\********************************/
239
return(0);
/* end of init() */
int process()
{
char* function = "process";
char
char
double
char
double
double
ls_sku[NULL_SKU];
ls_store[NULL_STORE];
ld_unit_cost;
ls_sales_type[NULL_SATYPE];
ld_sales_units;
ld_sales_value;
/* Driving Cursor */
EXEC SQL DECLARE c_trn_win_store CURSOR FOR
SELECT sku,
store,
NVL(unit_cost,'0'),
'R',
'0',
'0'
FROM trn_win_store
WHERE (store > NVL(:ps_restart_store, -999) OR
(store = :ps_restart_store AND
(sku > :ps_restart_sku)))
ORDER BY store, sku;
/* Open driving cursor */
EXEC SQL OPEN c_trn_win_store;
if (SQL_ERROR_FOUND)
{
sprintf(err_data, "Opening c_trn_win_store");
strcpy(table, "trn_win_store");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
while (1)
{
/* Fetch a record */
EXEC SQL FETCH c_trn_win_store INTO :ls_sku,
:ls_store,
:ld_unit_cost,
:ls_sales_type,
:ld_sales_units,
:ld_sales_value;
if (SQL_ERROR_FOUND)
{
sprintf(err_data, "Fetching from c_trn_win_store");
strcpy(table, "trn_win_store");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(-1);
}
else if (NO_DATA_FOUND)
break;
240
241
return(0);
/* end of process() */
int final()
{
char* function = "final";
int li_final_return;
/*************************************\
| retek_close:
|
| Clean up the restart tables;
|
| Perform final rollback or commit. |
\*************************************/
li_final_return = retek_close();
}
return(li_final_return);
/* end of final() */
242
return(0);
/* end of get_vdate() */
243