Developing UniBasic Applications

Transcription

Developing UniBasic Applications
Rocket UniData
Developing UniBasic Applications
Version 8.1.0
February 2015
UDT-810-BASD-1
Notices
Edition
Publication date: February 2015
Book number: UDT-810-BASD-1
Product version: Rocket UniData 8.1.0
Copyright
© Rocket Software, Inc. or its affiliates 1985-2015. All Rights Reserved.
Trademarks
Rocket is a registered trademark of Rocket Software, Inc. For a list of Rocket registered trademarks
go to: www.rocketsoftware.com/about/legal. All other products or services mentioned in this
document may be covered by the trademarks, service marks, or product names of their
respective owners.
Examples
This information might contain examples of data and reports. The examples include the names of
individuals, companies, brands, and products. All of these names are fictitious and any similarity
to the names and addresses used by an actual business enterprise is entirely coincidental.
License agreement
This software and the associated documentation are proprietary and confidential to Rocket
Software, Inc. or its affiliates, are furnished under license, and may be used and copied only in
accordance with the terms of such license.
Note: This product may contain encryption technology. Many countries prohibit or restrict the
use, import, or export of encryption technologies, and current use, import, and export
regulations should be followed when exporting this product.
ii
Corporate information
Rocket Software, Inc. develops enterprise infrastructure products in four key areas: storage,
networks, and compliance; database servers and tools; business information and analytics; and
application development, integration, and modernization.
Website: www.rocketsoftware.com
Rocket Global Headquarters
77 4th Avenue, Suite 100
Waltham, MA 02451-1468
USA
To contact Rocket Software by telephone for any reason, including obtaining pre-sales
information and technical support, use one of the following telephone numbers.
Country
United States
Australia
Belgium
Canada
China
France
Germany
Italy
Japan
Netherlands
New Zealand
South Africa
United Kingdom
Toll-free telephone number
1-855-577-4323
1-800-823-405
0800-266-65
1-855-577-4323
800-720-1170
0800-180-0882
08-05-08-05-62
800-878-295
0800-170-5464
0-800-022-2961
0800-003210
0-800-980-818
0800-520-0439
Contacting Technical Support
The Rocket Customer Portal is the primary method of obtaining support. If you have current
support and maintenance agreements with Rocket Software, you can access the Rocket Customer
Portal and report a problem, download an update, or find answers in the U2 Knowledgebase. To
log into the Rocket Customer Portal or to request a Rocket Customer Portal account, go to
www.rocketsoftware.com/support.
In addition to using the Rocket Customer Portal to obtain support, you can send email to
[email protected] or use one of the following telephone numbers.
Country
North America
United Kingdom/France
Europe/Africa
Australia
New Zealand
Toll-free telephone number
+1 800 729 3553
+44(0) 800 773 771 or +44(0) 20 8867 3691
+44 (0) 20 88673692
+1 800 707 703 or +61 (0) 29412 5450
+0800 505 515
iii
Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta
Table of
Contents
Table of Contents
Chapter 1
Chapter 1: Introduction to UniBasic
In This Chapter . . . . . . . . . .
UniBasic Capabilities . . . . . . . .
UniBasic Statements . . . . . . . .
Types of Statements . . . . . . .
Building Blocks for Writing Statements
Statement Syntax and Layout . . . .
UniData File Types . . . . . . . . .
Data Representation in UniBasic Programs .
Delimiters and the Null Value . . .
Constants. . . . . . . . . . .
Variables . . . . . . . . . . .
Arrays . . . . . . . . . . . .
Getting System Information . . . . . .
@Variables . . . . . . . . . .
STATUS Function . . . . . . . .
Chapter 2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1-4
1-5
1-6
1-6
1-6
1-9
1-12
1-13
1-13
1-13
1-14
1-15
1-16
1-16
1-16
In This Chapter . . . . . . . . . . . . . . . .
Subroutines . . . . . . . . . . . . . . . . .
Internal Subroutines . . . . . . . . . . . . .
External Subroutines . . . . . . . . . . . . .
Looping . . . . . . . . . . . . . . . . . . .
Conditional Tests. . . . . . . . . . . . . . . .
IF/THEN/ELSE Statements . . . . . . . . . .
CASE Statements . . . . . . . . . . . . . .
Reversing Conditional Evaluations: The NOT Function .
Comparison Operators Used in Conditional Statements.
Branching . . . . . . . . . . . . . . . .
Summary of Program Control Commands . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2-4
2-5
2-5
2-7
2-9
2-11
2-11
2-13
2-14
2-15
2-19
2-20
Chapter 2: Program Control
C:\Users\awaite\Documents\U2Doc\UniData\8.1\Source\BASD\BASDTOC.fm (bookTOC.template)
February 2, 2015 1:46 pm
C:\Users\awaite\Documents\U2Doc\UniData\8.1\Source\BASD\BASDTOC.fm
(bookTOC.template)
Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta
Chapter 3
Chapter 3: Creating and Running a Program
In This Chapter . . . . . . . . . .
Creating a Program with AE . . . . .
Creating a Program Record . . . .
More AE Commands . . . . . .
Compiling a UniBasic Program . . . .
Compile Commands . . . . . .
Directing the Compiler . . . . . .
Compiler Messages . . . . . . .
Creating Cross-Reference Reports . .
Cataloging a UniBasic Program . . . .
Points to Remember about CATALOG
Direct Cataloging. . . . . . . .
Local Cataloging . . . . . . . .
Global Cataloging . . . . . . .
Using the ECL CATALOG Command.
Removing a Catalog Entry . . . .
CATALOG Examples . . . . . .
Running a Program from AE. . . .
Running a Program from ECL . . .
UniBasic Runtime Error Logging . . . .
Example . . . . . . . . . . .
Chapter 4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3-4
3-5
3-5
3-8
3-10
3-10
3-10
3-15
3-18
3-22
3-22
3-23
3-23
3-24
3-25
3-26
3-27
3-29
3-29
3-37
3-38
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4-4
4-5
4-6
4-6
4-7
4-8
4-9
4-9
4-13
4-16
4-17
4-18
4-18
4-19
4-22
4-25
Chapter 4: Maintaining Data in Files
In This Chapter . . . . . . . . . . .
UniData Locks . . . . . . . . . . .
Database Triggers . . . . . . . . . .
Trigger Rules . . . . . . . . . .
The Nature of Triggers . . . . . . .
ECL Commands and Triggers . . . .
UniBasic Commands Affected by Triggers
Writing an UPDATE Trigger Subroutine .
Writing a DELETE Trigger Subroutine .
UniBasic STATUS Function Return Values
Troubleshooting . . . . . . . . .
Maintaining Files . . . . . . . . . .
UniData Hashed Data Files . . . . .
Alternate Key Indexes . . . . . . .
Non-UniData Sequential Files . . . .
Opening Files . . . . . . . . . . .
5 Developing UniBasic Applications
C:\Users\awaite\Documents\U2Doc\UniData\8.1\Source\BASD\BASDTOC.fm
(bookTOC.template)
Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta
Example . . . . . . . . . . . . .
Selecting Records . . . . . . . . . . .
Creating a Select List of Record IDs . . .
Clearing a Select List . . . . . . . .
Reading, Writing, and Deleting Data from Files .
Getting Ready to Read . . . . . . . .
Reading Record IDs from a Select List . .
Reading Data from Files . . . . . . .
Example . . . . . . . . . . . . .
Writing Data to Files . . . . . . . .
Example . . . . . . . . . . . . .
Deleting Data from Files . . . . . . .
Closing Files . . . . . . . . . . . . .
Accessing Data in Unopened Files . . . . .
Chapter 5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4-25
4-26
4-26
4-28
4-29
4-29
4-29
4-29
4-32
4-32
4-34
4-34
4-36
4-37
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5-4
5-5
5-5
5-5
5-6
5-6
5-8
5-8
5-10
5-12
5-14
5-14
5-14
5-16
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6-4
6-5
6-5
6-9
6-9
6-11
6-11
6-13
Chapter 5: Using UniData Locks
In This Chapter . . . . . . . . . . .
Understanding the UniData Locking System .
How UniData Locks Work . . . . .
Types of UniData Locks . . . . . .
When UniBasic Finds a Lock . . . . .
Points to Remember about Locks . . .
Locking Commands . . . . . . . . .
Checking Lock Status . . . . . . .
What Commands Do with Locks . . . . .
When to Use Locking Commands . . . .
Programming Problems . . . . . . . .
Causes . . . . . . . . . . . .
Minimizing Problems . . . . . . .
Locking Example . . . . . . . . . .
Chapter 6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Chapter 6: Working with Data in Programs
In This Chapter . . . .
UniData Arrays . . . .
Dynamic Arrays . .
Example . . . . .
Dimensioned Arrays
Inquiring about Data
Type . . . . . .
Location . . . . .
6 Developing UniBasic Applications
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
C:\Users\awaite\Documents\U2Doc\UniData\8.1\Source\BASD\BASDTOC.fm
(bookTOC.template)
Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta
Extraction . . . . . . . . . . . . . .
Performing Numeric Operations . . . . . . . .
Arithmetic Operators . . . . . . . . . .
Mathematic Functions . . . . . . . . . .
Formatting and Converting Data . . . . . . . .
ICONV and OCONV: The All-Purpose Functions
Character Format Conversion . . . . . . .
Strings and Dynamic Arrays . . . . . . . .
Numbers . . . . . . . . . . . . . .
Dates and Times . . . . . . . . . . . .
UniBasic Multibyte Support . . . . . . . . .
Modified Functions and Commands . . . . .
Single-Byte Functions . . . . . . . . . .
Multibyte Functions. . . . . . . . . . .
Chapter 7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6-14
6-16
6-16
6-20
6-21
6-21
6-23
6-23
6-25
6-30
6-35
6-35
6-37
6-38
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7-4
7-5
7-5
7-9
7-10
7-10
7-10
7-11
7-12
7-13
7-13
7-13
7-14
7-15
7-15
7-17
7-17
7-22
7-23
7-25
. .
. .
8-4
8-5
Chapter 7: External Interaction
In This Chapter . . . . . . . . . . . .
Interacting with Other UniBasic Programs . .
Sharing Data . . . . . . . . . . .
Including Code at Compilation . . . . .
Interacting with UniData . . . . . . . .
Executing Virtual Attributes . . . . . .
Executing ECL Statements . . . . . .
Executing UniData SQL Statements . . .
Defining and Using Programs and Functions
Writing User Exits . . . . . . . . . . .
What Are User Exits? . . . . . . . .
Calling a User Exit from UniBasic . . . .
Calling a User Exit from a Virtual Attribute
Calling a User Exit from a Proc . . . . .
Parameters in User Exits . . . . . . .
Interacting with Hardware I/O Devices . . .
Display Terminals . . . . . . . . .
Printers . . . . . . . . . . . . .
Tape Drives. . . . . . . . . . . .
Interacting with the Operating System . . . .
Chapter 8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Chapter 8: Linking Programs with UniData
In This Chapter . . . . . . . . .
Linking C Programs (UNIX Only) . .
7 Developing UniBasic Applications
. . . .
. . . .
. . . .
. . . .
C:\Users\awaite\Documents\U2Doc\UniData\8.1\Source\BASD\BASDTOC.fm
(bookTOC.template)
Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta
Before You Begin . . . . . . . . . . . . . . . . .
Calling a C Function from UniBasic with CALLC . . . . .
Calling a UniBasic Subroutine from a C Program with CallBasic
2. Write the C Program . . . . . . . . . . . . . . .
3. Create a makefile . . . . . . . . . . . . . . . .
4. Compile and Link the C Program . . . . . . . . . .
5. Execute the C Program . . . . . . . . . . . . . .
Relinking C Functions to UniData . . . . . . . . . . . .
File Examples . . . . . . . . . . . . . . . . . .
More on make, makeudt, and makeudapi . . . . . . . . . .
makeudt and makeudapi . . . . . . . . . . . . . .
make . . . . . . . . . . . . . . . . . . . . .
Troubleshooting CALLC . . . . . . . . . . . . . . . .
Linking C Programs (Windows Platforms Only) . . . . . . .
Dynamic Link Libraries (DLLs) and UniData . . . . . . .
CALLC Features and Components. . . . . . . . . . .
Using CALLC . . . . . . . . . . . . . . . . . .
CallBasic Features and Components . . . . . . . . . .
Using CallBasic . . . . . . . . . . . . . . . . .
Chapter 9
Chapter 9: UniBasic Transaction Processing
In This Chapter . . . . . . . . . . . . .
Transaction Processing Commands . . . . . .
Executing TP and Non-TP Transactions . . .
Starting a Transaction . . . . . . . . .
Committing a Transaction. . . . . . . .
Aborting a Transaction . . . . . . . . .
Testing for an Active Transaction . . . . .
Transaction Processing Programming Example
Transaction Processing Programming Problems . .
Transaction Abort . . . . . . . . . .
Degraded Performance. . . . . . . . .
Chapter 10
8-5
8-7
8-21
8-23
8-28
8-29
8-29
8-31
8-33
8-39
8-39
8-40
8-42
8-44
8-44
8-45
8-48
8-51
8-55
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9-4
9-5
9-5
9-6
9-7
9-9
9-10
9-11
9-12
9-12
9-16
Representing Unknown Values . . . . . . . .
Turning Null Value Handling Off . . . . . .
Null Value Handling in UniBasic . . . . . . . .
Printing and Displaying . . . . . . . . .
Sorting and Indexing . . . . . . . . . .
Summary of Effects on Commands and Functions
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
10-4
10-4
10-5
10-5
10-5
10-6
Chapter 10: Null Value Handling
8 Developing UniBasic Applications
C:\Users\awaite\Documents\U2Doc\UniData\8.1\Source\BASD\BASDTOC.fm
(bookTOC.template)
Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta Beta
The Null Value in Numeric Functions .
The Null Value in Conditional Tests . .
The Null Value in Conversion Functions.
The Null Value in String Functions . .
Chapter 11
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 10-7
. 10-9
. 10-15
. 10-16
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 12-3
. 12-4
. 12-4
. 12-4
. 12-6
. 12-6
. 12-6
. 12-7
. 12-7
. 12-8
. 12-8
. 12-9
. 12-9
. 12-10
. 12-10
. 12-11
Chapter 11: Managing Named Pipes
Points to Remember. . . .
OSOPEN . . . . . . . . .
Opening Named Pipes . . .
OSBREAD. . . . . . . . .
Reading from a Named Pipe .
OSBWRITE . . . . . . . .
Writing to Named Pipes . .
OSCLOSE . . . . . . . . .
STATUS Function Return Values .
INMAT . . . . . . . . . .
Troubleshooting. . . . . . .
Chapter 12
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Chapter 12: Local functions and subroutines
Defining local subroutines and functions . . .
Local subroutine declaration and boundary . .
Variable scope . . . . . . . . . . .
Other behaviors . . . . . . . . . .
Examples . . . . . . . . . . . . . .
Simple local subroutine . . . . . . .
Invalid GOTO statement . . . . . . .
Local subroutine with a local variable . .
Multiple local subroutines and external calls
Calling another local subroutine . . . .
Local subroutines with COMMON . . .
Conditionally compiling a program . . .
Subroutine without END statement . . .
Calling a local subroutine through @VAR .
Illegal ENTER statement . . . . . . .
Using $DEFINE . . . . . . . . . .
Appendix A
11-4
11-5
11-6
11-9
11-9
11-13
11-13
11-16
11-17
11-18
11-19
Appendix A: Sample Program
UPDATE_ORDER . . .
DISPLAY_MESSAGE . .
9 Developing UniBasic Applications
. . . . .
. . . . .
. . . .
. . . .
. . . .
. . . .
. .
. .
A-2
A-8
Appendix B
Appendix B: UniBasic Transaction Processing Concepts
In This Appendix . . . . . . . . . . . . .
Transaction Processing Rules: The ACID Properties .
Atomicity . . . . . . . . . . . . . .
Consistency . . . . . . . . . . . . .
Isolation . . . . . . . . . . . . . .
Durability. . . . . . . . . . . . . .
Transaction Isolation . . . . . . . . . . .
Transaction Processing Errors . . . . . . .
What Are Isolation Levels? . . . . . . . .
Programming to Isolation Levels . . . . . .
Example: Programming to Isolation Level 2 . .
Example: Programming to Isolation Level 3 . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. B-2
. B-3
. B-3
. B-3
. B-3
. B-4
. B-5
. B-5
. B-8
. B-9
. B-10
. B-10
Table of Contents
10
Chapter
Chapter 1: Introduction to
UniBasic
In This Chapter . . . . . . . . . .
UniBasic Capabilities . . . . . . . .
UniBasic Statements . . . . . . . .
Types of Statements . . . . . . .
Building Blocks for Writing Statements
Statement Syntax and Layout . . .
UniData File Types . . . . . . . .
Data Representation in UniBasic Programs
Delimiters and the Null Value . . .
Constants . . . . . . . . . .
Variables. . . . . . . . . . .
Arrays . . . . . . . . . . .
Getting System Information . . . . .
@Variables . . . . . . . . . .
STATUS Function . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1-3
1-4
1-5
1-5
1-5
1-8
1-11
1-12
1-12
1-12
1-13
1-14
1-15
1-15
1-15
This manual is for programmers who write applications that use the Rocket
UniData relational database management system (RDBMS). It explains how
to use UniBasic commands and functions to write application programs.
1-3
In This Chapter
This chapter introduces the concepts and terms used throughout this manual
and in the UniBasic Commands Reference. It includes the following sections:

“UniBasic Capabilities”

“UniBasic Statements”

“UniData File Types”

“Data Representation in UniBasic Programs”

“Getting System Information”
1-4 Developing UniBasic Applications
UniBasic Capabilities
UniBasic is UniData’s structured programming language intended for a
variety of applications. UniBasic provides you with the following
capabilities:

The modular language style enables you to create general UniBasic
subroutines that other processes can call.

From within a UniBasic program, you can perform the following
tasks:

Access other UniData products and operating system tools.

Call external C functions.

Pass data to and from external C programs.

Manage display terminal input and output.

Dynamic arrays and a full set of array commands provide an easy
interface to UniData’s extended relational database structure.

An interface with the query languages UniData SQL and UniQuery
enable you to pass record IDs or entire records to the query language.
For the syntax of UniBasic commands and functions, see the UniBasic
Commands Reference
UniBasic Capabilities 1-5
UniBasic Statements
This section describes the types of statements you can use in UniBasic.
Statements are the building blocks you use to write UniBasic programs. This
section also describes how you can lay out your statements in a program.
Types of Statements
You can write four types of UniBasic statements:

Assignments – Load numeric or string values into variables. You also
can assign the results of a function to a variable: variable =
OCONV(1286, “MD/2”).

Control statements – Direct the order in which commands are
executed, such as in a subroutine call: GOSUB update_client.

Conditional statements – Control the execution of a set of operations
based on the value of a variable: IF...THEN or DO...UNTIL.

Compiler directives – Instruct the UniBasic compiler to evaluate and
conditionally include statements: $INCLUDE.
Building Blocks for Writing Statements
Statements consist of five types of building blocks, which are described in the
following subsections:

“Commands”

“Functions”

“Operators”

“Special Characters”

“Comments”
1-6 Developing UniBasic Applications
Commands
Commands are the primary building blocks of the UniBasic instruction set.
Generally, each statement begins with a command word. In structured
coding, only one command is included on each line of code. The UniBasic
Commands Reference explains the syntax and use of each command. In the
following UniBasic statement, SELECT is the command word, and ORDER
and 1 are arguments used by the command:
SELECT ORDER TO 1.
Functions
Functions reformat, extract, and perform mathematical calculations. The
result of a function can be stored in a variable or used in a command. In the
following statement, the function EXTRACT is an argument in the PRINT
command:
PRINT EXTRACT(A,2,2).
You can nest UniBasic functions. Therefore, a function can be an argument in
a function as in:
PRINT OCONV(EXTRACT(A,2,2),"MD2,$").
This statement extracts data from the array A and prints out the extracted
data with two decimal places, preceded by $.
The UniBasic Commands Reference explains the syntax and use of each
function.
Operators
UniBasic supports the following operator types:

Arithmetic – For computing and assigning values.

Conditional – For testing data and directing program execution. You
can use the following operator types:

Relational

Boolean
UniBasic Statements 1-7
Note: For information about arithmetic operators, see Chapter 6, “Chapter 6:
Working with Data in Programs.” For information about conditional operators,
including how they are used in UniBasic programs, see Chapter 2, “Chapter 2:
Program Control.”
Special Characters
You can use the following special characters in UniBasic statements:

Continuation character (|) – Placed at the end of a line. It indicates
that a command continues on the next line.

Semicolon (;) – Separates commands on the same line. Be aware,
though, that including more than one command on a line makes
your code more difficult to read and debug.

Quotation marks (“ ” or ‘ ’) – Must enclose string variables and literal
values. They can be used together to differentiate a literal that is
embedded in a string.

Backslash (\) – Can also be used to replace quotation marks when a
string variable is embedded in a literal.
In the following example, HISTORY4 is assigned to a string that includes the
HEADING command. Single quotation marks are embedded in double
quotation marks, and backslashes are used to enclose the entire string:
MON = @MONTH
DA = @DAY
HISTORY1 = "LIST ALMANAC WITH MONTH = ":MON:" AND DAY = ":DA
HISTORY2 = "BY TYPE BY YEAR"
HISTORY3 = "YEAR TYPE EVENT ID.SUP COL.SUP NI.SUP "
HISTORY4 = \HEADING "'C'Today is 'DLC' On This Day in History
'LL'" \
HISTORY = HISTORY1:HISTORY2:HISTORY3:HISTORY4
PERFORM HISTORY
Comments
Comments narrate the actions of a program for clarity, but the compiler
ignores them. You can enter a comment on a line by itself, or you can enter it
on the same line as a program statement. The comment commands are *, !,
and REM.
To enter a comment on a line by itself, begin the line with a comment
command, and then enter the comment text.
1-8 Developing UniBasic Applications
In the following example, two asterisks (**) are used for commented text:
**
**
**
**
**
**
**
**
**
**
Program
:
Programmer :
Created
:
Description:
:
:
:
:
:
:
UPDATE_ORDERS
Todd Roemmich
04/02/2005
Check and/or alter Order records
Display Screen and ask for Order #
Read record (if it exists) and display fields
Prompt for a command (Alter, Delete, or Quit)
A) Allow the user to change price or address
D) Delete the record
Q) Exit the program
To enter a comment on the same line as a program statement, precede the
comment command with a semicolon. In the following example, a comment
is entered on the same line as the GOSUB command:
GOSUB OPEN_FILES
; ** go open files based on user input
Statement Syntax and Layout
The syntax for each command and function is included in the UniBasic
Commands Reference. Multiple statements can be grouped on one line, single
statements can extend over several lines, and statements can be nested.
Stacking Multiple Statements on a Line
Structured programming conventions suggest that you place no more than
one statement on a single line. For backward compatibility, UniBasic enables
you to include multiple statements on a single line separated by a semicolon
(;).
Note: Programs are easier to follow if you place a single command on each line.
Extending Statements Across Multiple Lines
Individual statements can extend across several lines if you follow one of
these rules:

Break the statement as shown in the syntax of the command or
function in the UniBasic Commands Reference.
UniBasic Statements 1-9

Use a continuation character (|) at the end of the line. The following
example prints “This line is CONTINUED”:
001: PRINT "This line is ": OCONV(|
002: "continued","MCU")
While we recommend that you use a single-line construction, in two cases
you might need to use a multiline construction:

Statements with arguments – Statements or functions that have
arguments separated by commas, such as CALL, COMMON, and
DATA, often require several lines. A multiline COMMON statement
follows:
COMMON NAME,DOB,SS.NUM,ADDRESS,CITY,STATE,
ZIP,HOME.PHONE,BUS.PHONE,ACC.NUM

Long statements – Lengthy constructions, such as FOR/NEXT and
IF/THEN/ELSE, often require several lines.
The following example, which is taken from the sample program in
Appendix A, “Appendix A: Sample Program,” shows a lengthy
IF/THEN/ELSE construction:
IF PRODUCT_LINE = '' THEN
PRODUCT_LINE = PRODUCT_NUMBER "R#10"
COLOR_LINE = COLOR "R#10"
QUANTITY_LINE = QUANTITY "R#10"
PRICE_LINE = PRICE
END ELSE
PRODUCT_LINE := "
":PRODUCT_NUMBER "R#10"
COLOR_LINE := "
":COLOR "R#10"
QUANTITY_LINE := "
":QUANTITY "R#10"
PRICE_LINE := "
":PRICE
END
Nesting Statements
You can nest a statement within another statement. For example, you can test
for one condition, and if it is true, test for an additional condition. In the
following example, the second IF/THEN is nested:
IF expression THEN
statements
IF expression THEN
statements
END ELSE
statements
END
END
1-10 Developing UniBasic Applications
The following program segment, which is taken from the sample program in
Appendix A, “Appendix A: Sample Program,” is an example of an
IF/THEN/ELSE nested inside a conditional LOOP. The LOOP test is in the
UNTIL statement.
LOOP
DISPLAY @(15,6):
INPUT ORDER_NUMBER
ORDER_NUMBER = OCONV(ORDER_NUMBER,"MCU")
IF NUM(ORDER_NUMBER) OR ORDER_NUMBER[1,1] = "Q" THEN
VALID_ORDER_NUMBER = 1
END ELSE
VALID_ORDER_NUMBER = 0
MESSAGE = "Order # must be a Number, or the letter 'Q'"
CALL DISPLAY_MESSAGE(MESSAGE)
END
UNTIL VALID_ORDER_NUMBER
REPEAT
UniBasic Statements 1-11
UniData File Types
File types that make up the UniData database include those listed in the
following table. For more information about UniData files, see Using
UniData.
UniData File Type
Description
Static hashed file
Data file.
Dynamic hashed file
Directory file that contains a data and an overflow portion.
UniData treats these two portions as one file. UniData
automatically resizes the file to avoid level 2 overflow.
Dictionary (DICT) file Data file that contains attribute definitions for a UniData
file.
Alternate key index
(static file)
Data file located in the same directory at the same level as
the indexed data file. This index is based on an attribute
other than the primary key.
Alternate key index
(dynamic file)
Data file located in the dynamic file directory along with
the data file and the overflow file.
DIR file
An operating system directory directly accessible by
UniData.
MULTIFILE (multilevel file)
A directory that contains one or more UniData hashed
files. There is one dictionary for the MULTIFILE. All
hashed files in the MULTIFILE share the same dictionary.
MULTIDIR (multilevel DIR file)
A directory that contains one or more subdirectories.
There is one dictionary for the MULTIDIR. All
subdirectories in the MULTIDIR share the same
dictionary. If you copy records from a file into one of the
subdirectories, each record is stored as a text file.
UniData File Types
1-12 Developing UniBasic Applications
Data Representation in UniBasic Programs
Before you can work with data in a program, you must bring it into the
program and store it in a usable form. In UniBasic programs, data is
represented in the following ways:

Delimiters and the null value

Constants

Variables

Arrays

@variables
Delimiters and the Null Value
We recommend that you use @variables to represent UniData delimiters and
the null value in UniBasic programs instead of entering the ASCII code
directly. The ASCII code used for these values is determined by the language
(such as English, Chinese, or Spanish) being represented. The @values to use
are:

Null value – @NULL

Record mark – @RM

Attribute mark – @AM, @FM

Value mark – @VM

Subvalue mark – @SM

Text mark – @TM
Note: You turn on or off null value handling with the UniData configuration
parameter NULL_FLAG. For a full explanation, see Chapter 10, “Chapter 10: Null
Value Handling.”
Constants
A constant can be a numeric value, such as 25, or a string value, such as
“inventory.” You must use quotation marks to enclose string values. You can
use up to 32,765 constants at one time.
Data Representation in UniBasic Programs 1-13
Variables
A variable can be as simple as a character string or as complex as a
dimensioned array. UniBasic does not require explicit declaration of variable
data type (unless it is a dimensioned array). Type is inferred from the data
initially assigned to it.
Setting Variable Values
Variables receive their values from one of the following:

Data input through the keyboard.

An assignment (variable_name = 1234).

A computation statement.

Data read from a file.

Data stored in virtual memory or on hard disk, tape, or another
storage medium. For more information, see Chapter 3, “Chapter 3:
Creating and Running a Program.”

Arguments in a program call. For more information, see Chapter 3,
“Chapter 3: Creating and Running a Program.”
Variable Names
Variable names must follow the conventions described below:

Must be unique.

Must begin with a letter.

Can contain any combination of letters, numbers, underscores,
periods, and dollar signs.

Are limited to a length of 64 characters.

Cannot be a reserved word. For a list of reserved words, see the
UniBasic Commands Reference.
1-14 Developing UniBasic Applications
Arrays
An array is simply a form of variable, which is a place reserved in memory
for storing data in a UniBasic program. Each element of an array can contain
a constant, a simple variable, or a dynamic array. UniBasic allows for two
types of arrays:

Dynamic arrays – Separate each attribute, value, and subvalue of a
record by delimiters.

Dimensioned arrays – Store data in designated cells of a fixed-sized
matrix. Access to data in a dimensioned array is rapid, but less
flexible.
Tip: Use dynamic arrays when the number of elements is small, unknown, varied, or
when the data to examine is near the beginning of the array. Use dimensioned arrays
when the number of elements is large or the program will process the elements
nonsequentially.
For more information about using arrays to process data in a UniBasic
program, see Chapter 6, “Chapter 6: Working with Data in Programs.”
Data Representation in UniBasic Programs 1-15
Getting System Information
A wealth of information is available to your UniBasic program through
system variables (called @variables) and through the UniBasic STATUS
function.
@Variables
UniData @variables store and make available information about the system,
the user process, and the UniBasic program being executed. Some @variables
are available through any UniData or 4-GL tool. Others are available through
UniBasic only.

UniData – Available through any tool. For a complete list of UniData
@variables, see Using UniData. For example:
@SYSTEM.RETURN.CODE returns a value indicating a condition or
error after the execution of an ECL command. It also returns
additional information. For example, after SELECT, it reports the
number of records selected.

UniBasic – Available through UniBasic only. For a complete list of
UniBasic @variables, see the UniBasic Commands Reference. For
example:
@SENTENCE stores the statement that invoked the current UniBasic
program.
@TRANSACTION returns 1 if a transaction is active, and returns 0 if
no transaction is active.
Note: Use @variables to represent UniData delimiters and the null value. Do not use
the UniBasic CHAR function because the ASCII code varies with language group.
STATUS Function
The UniBasic STATUS function provides information about the last UniBasic
statement executed. Generally, a return value of 0 indicates successful
execution, while 1 or any other number indicates an error condition. For cases
in which return values are set by a command, those values are documented
with the command in the UniBasic Commands Reference.
1-16 Developing UniBasic Applications
UniData triggers also set STATUS values. For more information about
triggers, the return values set for the STATUS function, and an additional
return value set by triggers, see Chapter 4, “Chapter 4: Maintaining Data in
Files.”
Getting System Information 1-17
Chapter
Chapter 2: Program Control
In This Chapter . . . . . . . . . . . . . . . .
Subroutines . . . . . . . . . . . . . . . . .
Internal Subroutines. . . . . . . . . . . . .
External Subroutines . . . . . . . . . . . .
Looping . . . . . . . . . . . . . . . . . .
Conditional Tests . . . . . . . . . . . . . . .
IF/THEN/ELSE Statements . . . . . . . . . .
CASE Statements. . . . . . . . . . . . . .
Reversing Conditional Evaluations: The NOT Function
Comparison Operators Used in Conditional Statements
Branching . . . . . . . . . . . . . . . .
Summary of Program Control Commands . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2-3
2-4
2-4
2-6
2-8
2-10
2-10
2-12
2-13
2-14
2-18
2-19
A structured program consists of:

A main routine that controls the order of program execution, and

Subroutines that perform functions such as screen, printer, and data
manipulation.
This chapter introduces the UniBasic program control mechanisms that
enable you to write structured code. If you are new to programming or new
to UniBasic, read Chapter 1, “Chapter 1: Introduction to UniBasic” first.
For more information about the syntax of UniBasic commands and functions,
see UniBasic Commands Reference.
2-3
In This Chapter
The UniBasic commands control the order of execution of statements in the
program. These commands create subroutines, loops, and conditional tests,
and otherwise direct program flow. These structures and the commands that
build them are introduced in the following sections:

“Subroutines”

“Looping”

“Conditional Tests”

“Summary of Program Control Commands”
2-4 Developing UniBasic Applications
Subroutines
In UniBasic, you can code subroutines that are internal to the calling program
or separately compiled and cataloged. You can nest subroutines up to 1024
levels deep.
Note: A subroutine name cannot exceed 65 characters. If it does, the program will
fail to compile.
Internal Subroutines
Internal subroutines are self-contained units within a program. They begin
with a label and end with a RETURN statement.
Commands That Call Internal Subroutines
Within the constraints of structured programming, GOSUB and ON GOSUB
are the only acceptable statements to use for calling an internal subroutine:

GOSUB label1
RETURN [TO]

ON expr GOSUB label1[:][,label2[:]...]
RETURN [TO]
Tip: GOTO and ON GOTO are provided in UniBasic for backward compatibility.
These commands do not return control to the calling statement and are generally not
used in structured programs because they make the code difficult to read and
maintain.
Naming Internal Subroutines
Each internal subroutine must begin with a label. Labels can be numbers or
short descriptive strings. The following labels are used in the sample
program in Appendix A, “Appendix A: Sample Program.”
INITIALIZE
ALTER_RECORD
WRITE_RECORD
GET_RECORD_COMMAND
Subroutines 2-5
You must observe the following conventions when creating statement labels:

If a label begins with a number, it must contain only numbers.

If the label begins with a letter, it can contain any combination of
letters, numbers, underscores, periods, and dollar signs.

Spaces are not allowed in subroutine names.

If the label is alpha or alphanumeric, it must end with a colon to
differentiate it from a variable.

The label cannot be a reserved keyword.
When you select names for subroutine labels, choose meaningful, descriptive
names. Avoid abbreviations. Add comments after the statement label, and
when you use numeric statement labels, arrange the subroutines in
ascending order. If you follow these guidelines, you will be able to locate
internal subroutines easily.
Example of an Internal Subroutine
The following example, taken from the sample program in Appendix A,
“Appendix A: Sample Program,” illustrates calling internal subroutines:
*-------------- Main Logic ----------------------------GOSUB INITIALIZE
LOOP
GOSUB DISPLAY_SCREEN
GOSUB GET_ORDER_NUMBER
UNTIL ORDER_NUMBER[1,1] = 'Q'
GOSUB DISPLAY_DATA
IF RECORD_FOUND THEN GOSUB GET_RECORD_COMMAND
RELEASE
REPEAT
GOSUB EXIT
In the previous example, the first line calls the subroutine INITIALIZE.
Processing proceeds from the “INITIALIZE:” label, as the following example
shows, until it reaches RETURN:
INITIALIZE:
DISPLAY @(-1)
PROMPT ''
RETURN
Processing picks up again at the next line that follows the subroutine call in
Main Logic (see the preceding program example). This line contains the
command LOOP.
2-6 Developing UniBasic Applications
External Subroutines
External subroutines are separately compiled programs that are called from
a UniBasic program using the CALL statement. You can pass variables and
constants to an external subroutine through arguments. The structural
requirements of external subroutines include the following:

The SUBROUTINE statement must be the first noncomment line in
the subroutine.

The SUBROUTINE statement must contain the same number of
arguments as the calling program.

The subroutine must finish with a RETURN command.

The compiled subroutine must be cataloged so that the main
program can locate it.

You can nest subroutines up to 1024 levels deep.
Calling an External Subroutine
Use the CALL command to initiate an external subroutine.
The syntax of the CALL command follows:
CALL sub.name [(argument1[,argument2]...)]
CHAIN and ENTER also execute subroutines, but they do not return control
to the calling program and are not recommended in structured
programming.
Naming an External Subroutine
The SUBROUTINE command names the external routine. This command
must be the first line of code in the called routine, and the last line must be
RETURN. The syntax follows:
SUBROUTINE sub.name [(argument1[,argument2]...) statement(s)
RETURN
Subroutines 2-7
Examples
The following sample program calls the external subroutine CALLED.PGM.
It passes on the operation code selected by the user.
PRINT "Enter operation to perform: (1)add, (2)delete, (3)update :
";INPUT operation
CALL CALLED.PGM(operation,ret.val)
PRINT "Operation completed: ":ret.val
END
The following subroutine accepts the code, passes in the parameter operation,
and returns a value in ret.value:
SUBROUTINE CALLED.PGM(operation,ret.val)
BEGIN CASE
CASE operation = 1
ret.val = "Record added."
CASE operation = 2
ret.val = "Record deleted."
CASE operation = 3
ret.val = "Record updated."
END CASE
RETURN
The following program is the globally-cataloged subroutine
DISPLAY_MESSAGE that is included in Appendix A, “Appendix A: Sample
Program.” It displays the message passed in the variable MESSAGE.
SUBROUTINE DISPLAY_MESSAGE(MESSAGE)
DISPLAY @(5,20):MESSAGE
DISPLAY @(5,21):"Press the (Return) key.":
INPUT PAUSE,1_
RETURN
The following program segment is an example of a call to the preceding
subroutine from UPDATE_ORDER:
MESSAGE ="**(Record Does Not Exist)**"
GO_BACK = "Y"
CALL DISPLAY_MESSAGE(MESSAGE)
2-8 Developing UniBasic Applications
Looping
UniBasic provides the following commands that support looping to
repeatedly execute a set of commands.
Command
Action
LOOP/REPEAT
Statements can precede and/or follow the evaluation. You can
include multiple UNTIL and/or WHILE clauses. The loop
continues until the UNTIL condition is satisfied or until WHILE
is false.
FOR/NEXT
Executes statements repeatedly while a counter increments or
decrements (default is +1). The loop continues until the counter
reaches a specified number or the UNTIL condition is satisfied
or until WHILE is false.
CONTINUE
Transfers control to the next iteration of a FOR/NEXT or
LOOP/REPEAT statement.
EXIT
Transfers control to the line after REPEAT or NEXT.
Commands for Looping
Note: To test for the null value, you must test the return value of the function ISNV
or ISNVS, as in LOOP UNTIL ISNV(X) = 1. You cannot test for the null value
directly, as in LOOP UNTIL X = @NULL, because the null value is not comparable
to any value. For information about how null values affect UniBasic, see Chapter 10,
“Chapter 10: Null Value Handling.”
Looping 2-9
Example
The following program segment is taken from the sample program in
Appendix A, “Appendix A: Sample Program.” This segment updates the
price if the user has entered a new one.
*------------ Subroutines ---------------------------ALTER_RECORD:
* Create a new screen, and allow PRICE and ADDRESS to be changed.
*
Initialize variables and draw the screen
NEED.TO.WRITE = 0
DISPLAY @(-1):@(15,5):"Alter ORDER":
DISPLAY @(10,8):"(Press RETURN to leave un-changed)"
DISPLAY @(8,9):"Old Price":@(42,9):"New Price (Enter 2 decimal
places)"
* Change the PRICE field (if desired)
FOR ENTRY = 1 TO NUM_ENTRIES
NEW.PRICE = ""
DISPLAY @(10,9+ENTRY):OCONV(ORDER.REC<7,ENTRY>,"MR2$,"):
INPUT @(45,9+ENTRY):NEW.PRICE
NEW.PRICE = OCONV(NEW.PRICE,"MCN")
IF NEW.PRICE # '' AND NUM(NEW.PRICE) THEN
ORDER.REC<7,ENTRY> = NEW.PRICE
NEED.TO.WRITE = 1
END
NEXT ENTRY
2-10 Developing UniBasic Applications
Conditional Tests
Two primary functions of a program are evaluating and processing data
appropriately based on that evaluation. To evaluate data, you need to build
conditional statements. UniBasic conditional statements include the
following:

IF/THEN/ELSE

CASE
Within these statements, you can nest additional conditional tests with the
following keywords:

WHILE

UNTIL
IF/THEN/ELSE Statements
An IF/THEN/ELSE statement evaluates a piece of data and then processes it
in a manner appropriate for that particular data.
Note: For information about how null values affect UniBasic, see Chapter 10,
“Chapter 10: Null Value Handling.”
You can use the syntax in any of the following IF/THEN/ELSE statements.
Type
Syntax
Example
1
IF ...THEN ...
IF confirmed = "Y" THEN ...
IF/THEN/ELSE Statement Syntax
Conditional Tests 2-11
Type
Syntax
Example
2
IF ... THEN ... ELSE
...
IF confirmed = "Y" THEN GOSUB DISPLAY
ELSE ...
3
IF ... THEN ...
IF confirmed = "Y" THEN
...
INFOMESSAGE = ...
END ELSE
...
GOSUB DISPLAY_INFO
END ELSE
GOSUB DISPLAY_ERR
END
4
IF expr THEN
IF confirmed = "Y" THEN
...
INFOMESSAGE = ...
END ELSE
GOSUB DISPLAY_INFO
...
END
END ELSE
INFOMESSAGE = ...
GOSUB DISPLAY_INFO
END
IF/THEN/ELSE Statement Syntax (continued)
You can test for a true condition in a variable with a statement similar to the
following:
IF var THEN statements1 ELSE statements2
When var is true, (specifically, when it is anything other than 0), statements1
are executed. When var is false (when it is 0), statements2 are executed.
Example
The following program segment is taken from the sample program in
Appendix A, “Appendix A: Sample Program.” This statement is an example
of Type 1, which is described in the previous table.
* Accept INPUT to change values of address
INPUT @(40,13):STREET1
IF STREET1 = '' THEN STREET1 = CLIENT.REC<4>
INPUT @(40,14):STREET2
IF STREET2 = '' THEN STREET2 = CLIENT.REC<5>
2-12 Developing UniBasic Applications
The following program segment is taken from the sample program in
Appendix A, “Appendix A: Sample Program.” This statement is an example
of Type 4, which is described in the preceding IF/THEN/ELSE Statement
Syntax table.
IF STATUS() = 1 THEN
DISPLAY "A Transaction had already been started, NESTED
Transactions"
DISPLAY "are NOT Allowed. (Contact System Administrator)"
INPUT PAUSE,1_
END ELSE
DISPLAY "The Recoverable File System is not enabled."
END
CASE Statements
The CASE statement accommodates multiple conditions. When testing for
more than two conditions, the CASE statement is more efficient than
IF/THEN/ELSE because when a true condition is encountered, UniBasic
skips all subsequent CASE evaluations.
Syntax:
BEGIN CASE
CASE expr1
statements
CASE expr2
statements
. . .
{CASE 1
statements}
END CASE
If a condition is true, UniBasic performs all statements associated with that
CASE statement and then transfers control to the statement that follows END
CASE. If a condition is false, UniBasic goes on to test the next CASE
condition. You can enter an unlimited number of CASE conditions.
The CASE 1 statement always evaluates to true. Placed as the last test, it
executes if all previous CASE conditions are false.
Note: The null value is always evaluated to false. For information about how the null
value affects UniBasic, see Chapter 10, “Chapter 10: Null Value Handling.”
Conditional Tests 2-13
Example
The following program segment is taken from UPDATE_ORDER in
Appendix A, “Appendix A: Sample Program.” In this subroutine, the user
selects an operation to perform (alter, delete, or quit), and then the program
calls the appropriate subroutine to perform that operation. Note that CASE 1
handles the user entering an invalid response.
GET_RECORD_COMMAND:
* Enter a valid command to act upon (the Desired record is already
shown).
DISPLAY @(7,22):"Enter A)lter, D)elete, or Q)uit: ":
INPUT COMMAND,1
COMMAND = OCONV(COMMAND[1,1],"MCU")
BEGIN CASE
CASE COMMAND = "A"
GOSUB ALTER_RECORD
CASE COMMAND = "D"
GOSUB DELETE_RECORD
CASE COMMAND = "Q"
FINISHED = 1
CASE 1
MESSAGE = "Valid options are A, D, or Q. Please try again"
CALL DISPLAY_MESSAGE(MESSAGE)
END CASE
RETURN
Reversing Conditional Evaluations: The NOT Function
The NOT function inverts the result of conditional statements, making a true
result false or a false result true. If the expression is true, the function returns
0 (false). If the expression is false, the function returns 1 (true). You must
enclose the expression being reversed in parentheses.
The STATUS function in the following program tests for a value other than 0,
which indicates that the record was not found. Note that the comparison of
the STATUS return value with 0 is enclosed in parentheses.
2-14 Developing UniBasic Applications
Note: You cannot use NOT to test for the absence of the null value because NOT
@NULL is evaluated to @NULL. For more information about how the null value is
handled in conditional statements, see Chapter 10, “Chapter 10: Null Value
Handling.”
OPEN "INVENTORY" TO open.file ELSE PRINT "Open error." ;STOP
PRINT "Enter product to display: ";INPUT search_product
search_product = OCONV(search_product, "MCT")
new_product = search_product
SETINDEX "PROD_NAME", search_product ON open.file
IF NOT(STATUS()=0) THEN PRINT "Record not found.";STOP
LOOP UNTIL new_product <> search_product
READFWD dyn.array.var FROM open.file ELSE PRINT "Readfwd
error."
new_product = EXTRACT(dyn.array.var,3)
IF new_product = search_product THEN PRINT "Record is:
":dyn.array.var
REPEAT
END
Comparison Operators Used in Conditional Statements
Comparison operators can be used to make compound conditional
statements. Comparison operators include Boolean and relational operators.
Boolean Operators
Boolean operators combine or compare sets of relational data. A common use
for Boolean operators is to check if a numeric variable falls within a range.
Boolean operators take one of the following forms.
Expression
Description
expr1 AND expr2
When both expressions are true, the result is true.
expr1 OR expr2
When either expression is true, the result is true.
Boolean Operators
Note: With null value handling turned on, the logical operators AND and OR follow
the ANSI SQL 3-way logic and null value evaluation comparison rules. For more
information, see Chapter 10, “Chapter 10: Null Value Handling.”
Conditional Tests 2-15
In the following example, either expression (NEW_CLIENT # OLD_CLIENT)
or NEED.TO.WRITE must be true for the CLIENT.REC values to be updated.
The values in NEW_CLIENT and OLD_CLIENT must not be equal, or
NEED.TO.WRITE must be 1.
IF (NEW_CLIENT # OLD_CLIENT) OR NEED.TO.WRITE THEN
* Re-assign values to CLIENT.REC
CLIENT.REC<4> = STREET1
CLIENT.REC<5> = STREET2
CLIENT.REC<6> = CITY
CLIENT.REC<7> = STATE
CLIENT.REC<8> = ZIP
GOSUB WRITE_RECORD
END
Relational Operators
UniBasic supports the following relational operators for making
comparisons.
Operator
Description
EQ =
Equal to.
NE # <> ><
Not equal to.
GT >
Greater than.
LT <
Less than.
GE >= =>
Greater than or equal to.
LE <= =<
Less than or equal to.
#>
Not greater than.
#<
Not less than.
MATCH
MATCHES
Value matches a pattern (such as 0N or 0A) or a literal.
Relational Operators
Note: Some symbols serve as both relational and arithmetic operators.
2-16 Developing UniBasic Applications
UDT.OPTIONS provide settings that modify the operation of UniData and
UniBasic. For value comparison, if you set UDT.OPTION 1 ON, UniData evaluates
zero and empty string equally. For more information, see the UDT.OPTIONS
Commands Reference.
Operator Precedence
Within nested operations, you can control the order of execution by using
parentheses with arithmetic or comparison operators. UniBasic first resolves
the innermost parenthetical expression and then works outward.
If multiple operators appear within a single command statement or within a
single parenthetical expression, and the operators have equal precedence,
UniBasic performs the operations from left to right.
In the absence of parentheses, UniBasic uses the following order of
precedence.
Order of Precedence
Operator
Description
First
^ **
Exponentiation
Second
*
Multiplication
/
Division
-
Unary minus
+
Addition
-
Subtraction
Third
Operator Precedences
Conditional Tests 2-17
Order of Precedence
Operator
Description
Fourth
: CAT
Concatenation
Fifth
> GT
Greater than
< r
Less than
= EQ
Equal to
>= => GE
Greater than or equal to
#>
Not greater than
<= =< LE
Less than or equal to
#<
Not less than
<> >< #
NE
Not equal to
Matching
MATCH
MATCHES
Sixth
& AND
! OR
And
Or
Operator Precedences (continued)
Example
You can use the following sample program to test the logical operator OR. To
test the logical operator AND, substitute AND for OR in the program.
PROMPT''
PRINT "First value: Enter true or false: ";INPUT answer
answer = UPCASE(answer)
GOSUB SET.VAL
val.one=value
PRINT "Second value: Enter true or false: ";INPUT answer
answer = UPCASE(answer)
GOSUB SET.VAL
val.two=value
If (val.one OR val.two ) = 1
PRINT "Condition True."
ELSE PRINT "Condition is false."
2-18 Developing UniBasic Applications
Branching
For backward compatibility, UniBasic supports branching, although this
practice makes code difficult to follow and maintain. The GOTO and ON
GOTO commands direct the program to jump to another place in the code.
For the syntax of these commands, see the UniBasic Commands Reference.
Conditional Tests 2-19
Summary of Program Control Commands
The following table summarizes the UniBasic commands that facilitate
structure and control in programs.
Command
Action
ABORT
Stops program execution.
CALL
Calls an external subroutine.
CALLC
Calls an external C subroutine.
CASE
Executes statements based on a condition.
CHAIN
Branches to a separate program.
CONTINUE
Transfers control to the next iteration of the FOR/NEXT or
LOOP/REPEAT statement.
END
Indicates the end of a conditional statement, program, or
subroutine.
EXECUTE
Executes an external UniData statement, such as a
UniQuery, UniData SQL, or operating system command.
(Identical to PERFORM.)
EXECUTESQL
Executes a UniData SQL statement from within a UniBasic
program.
EXIT
Ends a FOR/NEXT or LOOP/REPEAT statement and
transfers control to the first statement after the loop
statement.
FOR/NEXT
Repeats statements based on a condition.
GOSUB
Executes an internal subroutine, and then returns to the
next statement after GOSUB.
IF/THEN/ELSE
Executes statements one time based on a condition.
LOOP/REPEAT
Repeats a set of statements based on a condition.
ON expr GOSUB name Branches to a subroutine based on the value of an
expression.
Program Control Commands
2-20 Developing UniBasic Applications
Command
Action
PCPERFORM
Executes an operating system command from within a
UniBasic program.
PERFORM
Executes an external UniData statement, such as a
UniQuery, UniData SQL, or operating system command
from within a UniBasic program. (Identical to EXECUTE.)
RETURN
Returns control from an external subroutine back to the
original program, or returns control from an internal
subroutine to the next statement that follows GOSUB.
STOP
Stops program execution.
Program Control Commands (continued)
Summary of Program Control Commands 2-21
Chapter
Chapter 3: Creating and Running
a Program
In This Chapter . . . . . . . . . .
Creating a Program with AE . . . . .
Creating a Program Record . . . .
More AE Commands . . . . . .
Compiling a UniBasic Program . . . .
Compile Commands . . . . . .
Directing the Compiler . . . . . .
Compiler Messages . . . . . . .
Creating Cross-Reference Reports . .
Cataloging a UniBasic Program . . . .
Points to Remember about CATALOG
Direct Cataloging. . . . . . . .
Local Cataloging . . . . . . . .
Global Cataloging . . . . . . .
Using the ECL CATALOG Command.
Removing a Catalog Entry . . . .
CATALOG Examples . . . . . .
Running a Program from AE. . . .
Running a Program from ECL . . .
UniBasic Runtime Error Logging. . . .
Example . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3-3
3-4
3-4
3-7
3-9
3-9
3-9
3-14
3-17
3-21
3-21
3-22
3-22
3-23
3-24
3-25
3-26
3-28
3-28
3-36
3-37
3
This chapter describes how to create and run a UniBasic program. If you are
not familiar with the UniData RDBMS, the UniBasic statement types, and
how data is represented in UniBasic programs, we recommend that you
review Chapter 1, “Chapter 1: Introduction to UniBasic” before you begin
this chapter.
3-3
In This Chapter
This chapter introduces the tools that you need to create and edit UniBasic
programs. It includes compiling, cataloging, and running the program. These
topics are presented in the following sections:

“Creating a Program with AE”

“Compiling a UniBasic Program”

“Cataloging a UniBasic Program”

“Running a UniBasic Program”
3-4 Developing UniBasic Applications
Creating a Program with AE
You can use any ASCII text editor or word processor to create a UniBasic
program, as long as you save it in ASCII format as a record in a directory file.
UniData creates a basic program (BP) file for this purpose when you initiate
a new account. UniData provides the Alternate Editor (AE) line editor, or you
can use the operating system default editor (with the ECL command ED).
This section provides instructions for using AE.
Tip: AE is designed for writing UniBasic programs. It recognizes UniData hashed
files, it enables you to execute UniData commands from within a file you are editing,
and it comes with online help.
For information about the building blocks used to create UniBasic programs,
see Chapter 1, “Chapter 1: Introduction to UniBasic.”
Creating a Program Record
Perform the following steps to create a program record:
1. Start AE
To create a program using AE, enter the AE command at the ECL prompt.
You will use this same command later to edit the program.
Syntax:
AE directory.file prog.name
directory.file is the directory in which the source code is located (usually BP);
prog.name is the name you choose for the program.
Tip: UniData creates a BP directory file when you create your account.
Creating a Program with AE 3-5
If You Are Successful
After you start the AE editor, UniBasic opens the program at the top and
displays the command mode prompt:
:AE BP NEWFIL
Top of New "NEWFIL" in "BP".
*--:
When you first start AE, you are in command mode. This is where you enter
commands that affect the entire file.
If You Are Not Successful
If you fail to indicate a directory file where the program is to reside (such as
the BP file), AE does not recognize the file name, and it displays a message
like this:
:AE NEWFIL
Unable to find file "NEWFIL".
File >
At this point, your best option is to press ENTER and start over.
2. Change to Input Mode
For now, you want to key in the program, so you want to be in input mode.
To change to input mode, enter i. AE changes the prompt to reflect your
current line number.
*--: i
001=
Enter as many lines of code as you want. Press ENTER at the end of each line.
You remain in input mode as long as you enter text on a line before pressing
ENTER, as shown in the following example:
001= ! Program: NEWFIL
002= ! Created: 1/1/96
003= !
Tip: To display and enter the ASCII code for control characters (including UniData
delimiters and the null value) in AE, press SHIFT+6.
3-6 Developing UniBasic Applications
3. Return to Command Mode
When you want to get help, save the file, compile the program, or execute
some other command directive, press ENTER at the line number. You are
returned to command mode, as demonstrated by the return of the command
mode prompt. In the following example, the line number prompt “004=” is
replaced by the command mode prompt “*--”:
004=
*--:
<-- line number prompt
<-- is replaced by command mode prompt
4. Access AE Help
To get help on AE, enter HELP or H at the command-line prompt, as shown
in the following example:
*--: HELP
You can also enter a help topic on the same line. To get a list of all topics, enter
ALL or A.
*--: H ALL
5. File, Save, or Quit
You file, save, or quit in command mode. To save and quit the program, enter
FI. To quit the program without saving it, enter EX or Q. After you quit the
program, the ECL prompt reappears.
Use the FIBCFN or ECL NEWPCODE command to dynamically activate a
cataloged subroutine. You can use a UniBasic shell to modify, recompile,
recatalog, and retest a UniBasic program without returning to the ECL
prompt.
Creating a Program with AE 3-7
More AE Commands
You can enter any of the following basic AE editor commands when you are
in command mode.
Command
Description
C/old.string/new.string
Changes the current character string to a new character
string on the current line.
R/old.string/new.string
Replaces the current line with new text entered.
P
Displays one page of the record.
HELP
Displays online help for AE. You can also enter HELP
followed by a topic or AE command.
I
Changes to input mode so you can enter text.
EX or Q
Quits the record without saving changes made during
this editing session.
FI
Files the program record and saves changes.
FIB
Files the program record and compiles it.
FIBR
Files the record, compiles it, and runs it.
If the compile is unsuccessful, the program you execute
is an earlier version that does not include changes in the
most recent version.
FIR
Files the record and runs the compiled version.
Be aware that the version run can differ from the one you
are editing.
FIBCFN
The N option of the FI command equates to the ECL
NEWPCODE command. FIBCFN compiles a program
and catalogs it (locally) with NEWPCODE. You need to
use F (force) in conjunction with the N option. For more
information, see the online help for the AE editor.
Use FIBCFN or ECL NEWPCODE to dynamically
activate a cataloged subroutine. You can use a UniBasic
“shell” to modify, recompile, recatalog, and retest a
UniBasic program without exiting to ECL.
More AE Commands
3-8 Developing UniBasic Applications
Command
Description
LNn
Lists the number of lines specified, with no line numbers.
n
Goes to line number n.
T
Goes to the top of the record.
SPOOLHELP
Prints brief help.
SPOOLHELP -FULL
Prints extensive help.
ENTER
Returns to command mode.
More AE Commands (continued)
Tip: Do you need to cut and paste lines in a program without the line numbers? AE
offers the command LNn, which lists no line numbers. n is the number of lines to list.
Creating a Program with AE 3-9
Compiling a UniBasic Program
The UniBasic compiler processes UniBasic code into the interpretive code
(also called object code) that is used by the UniBasic interpreter at runtime.
Compile Commands
You can compile a program using the following commands:

Compiling within AE – You can compile a program you are currently
editing in AE by entering FIB at the command prompt. When you
enter FIB, UniData saves the program, compiles it, files the compiled
code, and returns you to the ECL prompt. For more information
about compiling from AE, see “Creating a Program with AE” on
page 3-5.

Compiling from ECL – The BASIC command compiles the program
from the ECL prompt. For the syntax of the BASIC command, see
“ECL BASIC Command Options” on page 3-10.
Directing the Compiler
You can direct the compiler to take some action during compilation with the
following:

ECL BASIC command options

ECL and UniBasic commands
ECL BASIC Command Options
One way to direct the compiler is with ECL BASIC command options.
Syntax:
BASIC filename [TO filename] progname1 [progname2...] [option]
3-10 Developing UniBasic Applications
The following table describes each parameter of the syntax.
Parameter
Description
filename
Specifies the file from which the source program code is compiled.
The compiled version is saved in _filename.
progname
Specifies the source code program to be compiled and used with
the UniData Basic interpreter. If you do not enter progname, and if
you do not have a select list active, UniData prompts for a program
name.
You can create a select list before executing BASIC; this compiles all
programs in the select list. For example, to select all UniBasic
source files in the BP directory, enter
SELECT BP WITH @ID UNLIKE "_..."
Then, enter
BASIC BP
UniData compiles all programs in BP.
option
Specifies additional function(s) to be performed. You must choose
and option and include the hyphen. For valid options, see the
BASIC Command Option table.
BASIC Command Parameters
The following table lists compile options available with the ECL BASIC
command.
Option
Description
-D
Creates a symbol table for use with the UniBasic debugger. For more
information about the debugger, see Using the UniBasic Debugger.
-G
Reports on execution time for the program, as described in “Reporting
Execution Time” on page 3-31.
-I
Compiles UniBasic reserved words regardless of the case in which they
are entered (uppercase, lowercase, or mixed case). Without this option,
the compiler recognizes uppercase reserved words. For a list of
UniBasic reserved words, see the UniBasic Commands Reference.
-L
-LIST
Generates a list of the program.
BASIC Command Options
Compiling a UniBasic Program 3-11
Option
Description
-X -L
-XREF -L
Generates a cross-reference table of statement labels and variable
names used in the program. For more information, see “Creating
Cross-Reference Reports” on page 3-18.
-Z1
-Z2
Creates a symbol table for use with the UniBasic debugger. For more
information about the debugger, see Using the UniBasic Debugger.
-I
If you compile a program with the -I option, all reserved words in
UniBasic are case insensitive.
BASIC Command Options (continued)
ECL and UniBasic Commands
Some additional ECL and UniBasic commands are provided to instruct the
compiler to take action of the following types:

Select a compiler type.

Define variables and include or exclude blocks of code.
Selecting a Compiler Type
You can tune your UniBasic program to be more compatible with other
software that does not follow UniData syntax. You can direct the compiler to
do this from the ECL prompt or from within a UniBasic program, as follows:

ECL BASICTYPE command, entered from the ECL prompt, sets the
current BASICTYPE to be used by the UniBasic compiler. You can
also use this command to learn which BASICTYPE is currently active
or which BASICTYPE was used to compile a program.

UniBasic $BASICTYPE command, entered as the first line in a
UniBasic program, determines the BASICTYPE to be used in the
compilation of the program that follows. $BASICTYPE in a UniBasic
program overrides the BASICTYPE established at the ECL prompt.
3-12 Developing UniBasic Applications
The following options are available for use with both BASICTYPE and
$BASICTYPE.
Parameter
Description
U
UniBasic
P
Pick® BASIC
R
Advanced Revelation® BASIC
M
McDonnell Douglas BASIC/Reality® BASIC
$BASICTYPE Parameters
For more information about the ECL BASICTYPE command, see the UniData
Commands Reference. For more information about the compiler directive
$BASICTYPE, see the UniBasic Commands Reference.
More Compiler Directives
Along with $BASICTYPE, UniData offers other compiler directives that you
can use within UniBasic programs to define variables and establish blocks of
code based on the variables. These directives are shown in the following
table.
Compiler Command
Description
$DEFINE var
$UNDEFINE var
Defines and clears a control variable var.
$IFDEF var statements1
[$ELSE statements2]
$ENDIF
If var is defined, include statements1 in compiled
code. Otherwise, include statements2.
$IFNDEF var statements1
[$ELSE statements2]
$ENDIF
If var is not defined, include statements1 in compiled
code. Otherwise, include statements2.
$INCLUDE
$INSERT
Inserts the indicated UniBasic program or program
segment into your program during compilation. A
common use is to insert frequently used blocks of
code or data into appropriate programs.
More Compiler Directives
Compiling a UniBasic Program 3-13
The constructions of these compiler directives are very similar to Boolean IF...
THEN... ELSE logic. However, these compiler directives instruct the compiler
to include or exclude blocks of code based on the variables provided.
Compiler Directives Example
When you compile the following program, UniBasic compiles either the first
section (line 4) to open files for factory 1, or it compiles the second section
(line 17) to open files for factory 2:
comp_dir
001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
015:
016:
017:
018:
019:
020:
021:
022:
023:
024:
025:
026:
027:
028:
029:
030:
031:
032:
033:
034:
035:
3-14 Developing UniBasic Applications
CANT.OPEN = ''
$DEFINE FACT1
$IFDEF FACT1
OPEN 'PARTS1' TO PARTS ELSE
CANT.OPEN<-1> = 'PARTS1'
END
OPEN 'PURCHASE.ORDERS1' TO PURCHASE.ORDERS ELSE
CANT.OPEN<-1> = 'PURCHASE.ORDERS1'
END
OPEN 'SALES.ORDERS1' TO SALES.ORDERS ELSE
CANT.OPEN<-1> = 'SALES.ORDERS1'
END
$ELSE
OPEN 'PARTS2' TO PARTS ELSE
CANT.OPEN<-1> = 'PARTS2'
END
OPEN 'PURCHASE.ORDERS2' TO PURCHASE.ORDERS ELSE
CANT.OPEN<-1> = 'PURCHASE.ORDERS2'
END
OPEN 'SALES.ORDERS2' TO SALES.ORDERS ELSE
CANT.OPEN<-1> = 'SALES.ORDERS2'
END
$ENDIF
IF CANT.OPEN THEN
PRINT 'Cannot open file(s): '
LOOP
REMOVE FILE FROM CANT.OPEN SETTING MORE.FILES
036:
037:
038:
039:
040:
041:
042:
PRINT SPACE(5):FILE
WHILE MORE.FILES REPEAT
PRINT 'Stopping...':
INPUT ANYTHING
STOP
END
END
Compiler Messages
The UniBasic compiler displays both warning and error messages. If the
compiler generates warning messages only, it compiles the program and
produces object code. However, if the compiler generates any error messages,
it does not produce object code. The last line of compiler messages indicates
whether the program compiled successfully.
Object code is stored in the same DIR-type file as the source code. The record
name for the object code is the same as that for the source code, but is prefixed
with an underscore (_). For example, the source code record TEST1 generates
object code record _TEST1.
Successful Compilation
After UniBasic successfully compiles a program, it displays a completion
message and returns you to the UniData colon (ECL) prompt. The following
example demonstrates the successful compilation of program TEST1:
...
Compiling Unibasic: BP/TEST1 in mode 'u'.
compilation finished
:
The compiler might return warning messages when compilation is
successful. These messages are preceded by “Warning: ”.
Note: If you run batch jobs to compile groups of programs, you need to code these
jobs to terminate only when error messages are returned by the compiler. Warning
messages do not terminate processing.
Compiling a UniBasic Program 3-15
The following program prints a variable that is never assigned a value:
PROMPT ''
PRINT @(-1)
PRINT var
PRINT "Enter record to update: "; INPUT answer
OPEN 'STUDENT' TO STU ELSE PRINT "Can't open STUDENT file."
READU RECORD FROM STU,answer
LOCKED PRINT "Record locked by another user, waiting..." THEN
PRINT "I am locking the record now and going to sleep."
FOR X = 1 TO 10
SLEEP 2
NEXT X
END
PRINT "Waking up now."
RELEASE
END
The UniBasic compiler returns a warning message when it compiles this
program. Notice that the program is compiled, and object code is produced.
Compiling Unibasic: BP/LOCKUP in mode 'u'.
Warning: Variable 'var' never assigned a value
compilation finished
:
Unsuccessful Compilation
If UniBasic fails to compile a program, it returns the cursor to the colon (ECL)
prompt.
The example that follows is a UniBasic program that contains the following
errors:

The variable answer is spelled two different ways. See lines 3 and 5.
3-16 Developing UniBasic Applications

The FOR/NEXT loop beginning on line 8 contains a misspelling: “T”
should be “TO.”
001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
PROMPT ''
PRINT @(-1)
PRINT "Enter record to update: "; INPUT answer
OPEN 'STUDENT' TO STU ELSE PRINT "Can't open STUDENT file."
READU RECORD FROM STU,answr
LOCKED PRINT "Record locked by another user, waiting..." THEN
PRINT "I am locking the record now and going to sleep."
FOR X = 1 T 10
SLEEP 2
NEXT X
END
PRINT "Waking up now."
RELEASE
END
The UniBasic compiler produces the following warning and error messages
when it attempts to compile this program:
Compiling Unibasic: BP/LOCKUP in mode 'u'.
main program: syntax error at or before
<line 8>
FOR X = 1 T 10
----------------^
Expecting:
array,string,number,function,variable,TO,OR,AND,!,>,>=,<,<=,=,<>,M
ATCH,CAT,:,+,main program: syntax error at or before
<line 10>
NEXT X
-----^
Expecting: end-of-line,END,;
Warning: Variable 'X' never assigned a value
Warning: Variable 'T' never assigned a value
Warning: Variable 'answr' never assigned a value
compilation failed
:
The missing “O” in “TO” causes the compiler to misinterpret line 8, the
NEXT statement on line10 is not matched, and two unassigned variable
warning messages result. Finally, the misspelling of the “answer” variable
produces an unassigned variable message.
If the only error in this program had been the misspelled variable, only
warning messages would have been displayed, and object code would have
been produced. However, a runtime error would occur if you tried to execute
the program.
Compiling a UniBasic Program 3-17
Note: Syntax errors are sometimes reported on a line following the actual error. This
happens when the statement is incomplete, and the compiler looks for the remainder
of the statement on a subsequent line. Also, combining multiple statements on the
same line, or including an inordinate number of comment lines could cause the line
counter to be incorrect.
Creating Cross-Reference Reports
With the UniBasic cross-reference capability, you can generate a table that
describes statement labels and variables. This capability is especially useful
when you use it in conjunction with the UniBasic debugger.
You can use the cross-referencing feature by compiling programs with the
BASIC command and the -L (or -LIST) and -X -L (or -XREF -L) options, using
the syntax introduced in “ECL BASIC Command Options” on page 3-10.
Listing Included Code
When you use the -L (or -LIST) option, UniBasic generates a source code
listing of the program, including all lines brought in with an $INSERT or
$INCLUDE statement. UniBasic saves the listing in the source directory with
an underscore (_) prefix, followed by the program name and a LIST suffix.
Listing Statements and Variables
When you use the -X -L (or -XREF -L) option, UniBasic produces a crossreference report listing statement label and variable names used in the
program. The report states where and how the program uses its labels and
variables by listing the name, type, reference, and function of each variable.
•
3-18 Developing UniBasic Applications
Name – Name of the label or variable used in the program, such
as 100, PARTNO, or X.

Type – Type of label or variable. UniData sorts the report by type
starting with label because its type is 0, and ending with equate
because it has the highest type number, which is 14. The crossreference types are described in the following table.
Type
Description
0
Label
1
Common area
2
Compiler @variable
3
Read-only @variable
4
@variable
5
Variable
6
Variable function
7
Common variable
8
One-dimensioned array
9
Two-dimensioned array
10
One-dimensioned array argument
11
Two-dimensioned array argument
12
One-dimensioned array in COMMON
13
Two-dimensioned array in COMMON
14
Equate
Cross-Reference Types

Reference – Line number on which the program refers to the label or
variable.
Compiling a UniBasic Program 3-19

Function – UniBasic uses the function symbol to reference the
variable. Symbols and their functions are listed in the following
table.
Symbol
Function
*
Definition of variable or label.
=
Assignment of variable.
!
Dimension of array.
@
Argument to called subroutine.
$
Variable or label is inside an INCLUDE or INSERT
statement.
no symbol
Simple reference such as IF X = 7 THEN.
Function Symbol References
Cross-Reference Report Examples
The following example shows the compile command with cross-referencing
options and the listing UniData generates using the -L option:
:BASIC BP -X -L
Please enter BASIC program file name: index.test
:SPOOL BP _index.test.LIST -T
BP:
_index.test.LIST
BP/index.test Source Listing
0001
OPEN "INVENTORY" TO open.file ELSE PRINT "Open error."
;STOP
0002
PRINT "Enter product to display: ";INPUT search_product
0003
search_product = OCONV(search_product, "MCT")
0004
new_product = search_product
0005
SETINDEX "PROD_NAME", search_product ON open.file
0006
IF NOT(STATUS()=0) THEN PRINT "Record not found.";STOP
0007
LOOP UNTIL new_product <> search_product
0008
READFWD dyn.array.var FROM open.file ELSE PRINT
"Readfwd error."
0009
new_product = EXTRACT(dyn.array.var,3)
0010
IF new_product = search_product THEN PRINT "Record is:
":dyn.array.var
0011
REPEAT
0012
END
3-20 Developing UniBasic Applications
The following example shows the output from the -X -L option:
:SPOOL BP _index.test.XREF -T
BP:
_index.test.XREF
BP/index.test Cross Reference Listing
Name....................... Type..
References...................................
dyn.array.var
5
0008=
0009
new_product
5
0004=
0007
open.file
5
0001=
0005
search_product
5
0002=
0003=
0007
0010
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Label
Common Area
Compiler @ Variable
Readonly @ Variable
@ Variable
Variable
Variable Argument
Common Variable
One Dimension Array
Two Dimension Array
One Dimension Array Argument
Two Dimension Array Argument
Common One Dimension Array
Common Two Dimension Array
Equate
*
=
!
@
$
Definition of Symbol
Assignment of Variable
Dimension of Array
Argument to CALL
Include or Insert statement
0010
0009=
0010
0008
0003
0004
0005
Compiling a UniBasic Program 3-21
Cataloging a UniBasic Program
Cataloging UniBasic programs simplifies program execution and can
improve efficiency of system resource use by letting multiple users access a
single copy of a compiled program from memory. Use the ECL CATALOG
command to catalog one or more UniBasic programs.
Note: For more information about the CATALOG command, see the UniData
Commands Reference. For more information about managing catalogs, see
Administering UniData on UNIX or Administering UniData on Windows
Platforms.
Compiled UniBasic programs can be cataloged directly, locally, or globally.
The process and options for cataloging a UniBasic program are introduced in
the following sections:

“Points to Remember about CATALOG”

“Direct Cataloging”

“Local Cataloging”

“Global Cataloging”

“Using the ECL CATALOG Command”

“Removing a Catalog Entry”

“CATALOG Examples”
Points to Remember about CATALOG
The ECL CATALOG command copies the compiled code of a UniBasic
program or list of UniBasic programs into the system catalog. Some points to
remember about cataloged programs are listed below:

When you catalog a program globally, more than one user can run
the program at the same time, even though only one copy resides in
system memory.

You can use the ECL VCATALOG command to find out if your
source code is the same version as the cataloged program.
3-22 Developing UniBasic Applications

A recataloged program is not available until the user who is
recataloging it returns to the ECL prompt. You can use the ECL
NEWPCODE command to ensure that you are running the latest
version.
Note: For more information about NEWPCODE and VCATALOG, see
Administering UniData on UNIX or Administering UniData on Windows
Platforms, and the UniData Commands Reference.
Direct Cataloging
Keep in mind the following points about direct cataloging:

Compiled code is located in the program file in the UniData account
where the program was compiled and cataloged.

The VOC file in the account contains a pointer to the compiled code
in the program file. Users in the same account can execute the
program by entering the program name at the ECL prompt.

Because users access the compiled code in the program file,
developers do not need to recatalog the code if they recompile.

When a user executes a directly cataloged program, UniData loads a
copy of the program into the address space of the user.

Because users execute the programs from their own address space,
one user can recompile a directly cataloged program while another
user is running the program.
Local Cataloging
Keep in mind the following points about local cataloging:

Compiled code is located in the CTLG directory in the UniData
account where the program was cataloged, as well as in the program
file. CTLG is a directory, and each record is a compiled UniBasic
program.

The account’s VOC file contains a pointer to the compiled program
in the CTLG. Users in the same account can execute the program by
entering the program name at the ECL prompt.
Cataloging a UniBasic Program 3-23

Developers must recatalog a program after recompiling, to place a
new copy of the compiled code into the CTLG.

When a user executes a locally cataloged program, UniData loads a
copy of the program into the user’s address space.

Because users execute the programs from their own address space,
one user can recompile a directly cataloged program while another
user is running the program.
Global Cataloging
Keep in mind the following points about global cataloging:

When a user tries to globally catalog a UniBasic program, UniData
checks to see if a copy of the program resides in memory.

If a copy exists in memory, a process is currently executing either
the program in question or a program that called the program in
question. The program in question cannot be recataloged until
all processes executing it, or all programs that called it, have
completed. UniData displays an error message, and the
CATALOG command fails.

If a copy does not exist in memory, no one is currently executing
it and no program that is still running has called it. UniData
copies the new compiled code into the global catalog.

Global cataloging is the default. If you execute the CATALOG
command without specifying local or direct cataloging, your
program is globally cataloged.

Compiled code is located in a system wide global catalog. The
default global catalog is /udthome/sys/CTLG (for UNIX) or
\udthome\sys\CTLG (for Windows Platforms).

Even though Windows platforms do not require file and path names
that are case sensitive, UniData uses case-sensitive strings for
comparing and searching for names in cataloged programs.

Developers must recatalog a program after recompiling, to place a
new copy of the compiled code into the global catalog.

To run a globally cataloged program if you have a program with the
same name cataloged locally or directly, precede the program name
with an asterisk, as in *prog.name.
3-24 Developing UniBasic Applications
Note: A UniData installation can have more than one global catalog space. udthome
determines which global catalog space a particular UniData session accesses. For
more information about multiple global catalog spaces, see Administering UniData
on UNIX or Administering UniData on Windows Platforms.

A system-wide global catalog space is a directory with 26
subdirectories named A through Z. Compiled code is stored in the
subdirectory corresponding to the first letter of the program name.
Cataloged programs that begin with nonalpha characters are stored
in the subdirectory named X. The program name can be the same as
the source and object, or you can specify a different name when you
execute the CATALOG command.
Tip: Consider your program naming conventions if you are using global cataloging.
Because the compiled code is placed in subdirectories according to name, you could
have an unbalanced situation if a large number of your program names begin with
the same letter (for instance, a general ledger application in which all the files begin
with “gl”).

A globally cataloged program is available to users in all UniData
accounts.

When a user executes a globally cataloged program, UniData checks
to see if a copy already exists in shared memory.

If so, UniData notifies the udt process where to locate the copy in
shared memory.

If not, UniData loads a copy into shared memory for the user to
execute.
Using the ECL CATALOG Command
To catalog a program, enter the following command at the ECL prompt:
Syntax:
CATALOG file.name cat.name prog.name [LOCAL | DIRECT]
[FORCE] [NEWVERSION | newversion]
By default, cataloged code is placed in the program directory with the source
code and in the system global catalog. This makes the program available to
all accounts. Users share one copy of the cataloged code.
Cataloging a UniBasic Program 3-25
The following table describes each parameter of the syntax.
Option
Features
file.name
Specifies the file containing the program to be cataloged. Usually
a basic program (BP) file.
cat.name
Specifies the name of the system catalog into which the object
code is to be copied.
prog.name
Specifies the UniBasic program containing object code to be
cataloged.
GLOBAL
This is the default option (do not specify it in the CATALOG
command); the compiled code is placed in the program file and
in the system global catalog. This makes the program available to
all accounts. Users share one copy of the compiled code.
LOCAL
Compiled code is located in the program file and in local catalog
space. The LOCAL option is available on the local account. To use
it from another account, catalog it in the other account also.
DIRECT
The compiled code is placed in the program file only and is
available only on the local account. To use it on another account,
catalog it in that account also; recataloging is not required when
you recompile the program.
FORCE
Replace a cataloged program without prompting.
Catalog Command Options
Note: You can execute a cataloged program from the ECL prompt or from any
UniBasic program.
Removing a Catalog Entry
Use the ECL DELETE.CATALOG command to remove a cataloged program.
For more information about this command, see the UniData Commands
Reference.
3-26 Developing UniBasic Applications
CATALOG Examples
The following example shows the FORCE option to globally catalog the
object code of the program CUST.XREF, whose source code is located in the
BP.UTIL directory file:
:CATALOG BP.UTIL CUST.XREF FORCE
The next example shows the CATALOG command without the FORCE
option. Because the program GPA was already cataloged, UniData requests
verification before proceeding to recatalog the item.
:CATALOG BP GPA
/usr/ud/sys/CTLG/GPA exists, do you want to overwrite?(Y/N)Y
The following example lists the contents of the CTLG file in the demo
database:
:LIST CTLG
LIST CTLG 17:39:37 Aug 04 2010 1
CTLG......
AddRecord
DelRecord
DUMMY
EXAMPLE
FndRecord
UpdRecord
6 records listed
:
Note: You must compile your programs successfully before you can catalog them.
In the next example, UniData catalogs the compiled object code of the
PSTLCODE_FMT program locally. Afterwards, notice the following:

The local CTLG directory shows an entry for PSTLCODE_FMT.
Cataloging a UniBasic Program 3-27

A VOC pointer exists that shows a path to a copy of the program (in
this case, \UniData\demo\CTLG) and shows where the program
source is kept (BP_SOURCE).
:CATALOG BP_SOURCE PSTLCODE_FMT LOCAL
:LIST CTLG
LIST CTLG 17:39:37 Aug 04 2010 1
CTLG......
AddRecord
DelRecord
DUMMY
EXAMPLE
FndRecord
PSTLCODE_F
MT
UpdRecord
7 records listed
:CT VOC PSTLCODE_FMT
VOC:
PSTLCODE_FMT:
C
D:\UNIDATA\DEMO\CTLG\PSTLCODE_FMT
BP_SOURCE PSTLCODE_FMT
:
In the next example, UniData directly catalogs the PSTLCODE_FMT
program. Compare the VOC record to the previous example, and notice how
the path to the program has changed. The DIRECT keyword causes UniData
to create a VOC pointer that points to the file in which the program resides.
UniData does not place a copy of the program in either CTLG directory.
:CATALOG BP_SOURCE PSTLCODE_FMT DIRECT
PSTLCODE_FMT has been cataloged, do you want to overwrite(Y/N)? Y
:CT VOC PSTLCODE_FMT
VOC:
PSTLCODE_FMT:
C
BP_SOURCE\_PSTLCODE_FMT
:
Tip: To remove a copy of a program from the local or system CTLG directory, use the
ECL DELETE command or DELETE.CATALOG. In ECLTYPE P, you also can use
the DECATALOG command.
3-28 Developing UniBasic Applications
Running a UniBasic Program
You can run a program from AE or from ECL.
Running a Program from AE
To compile and run a program you are currently editing, enter the AE
command FIBR at the AE command prompt. UniData compiles and files the
program, runs the compiled version, and displays the ECL prompt. If
compilation is unsuccessful, the last successfully compiled version runs.
Note: The FIR command files the source code you are editing and runs the compiled
version. The two versions will be different if you have modified the program since the
last compilation. Be sure to use the AE command FIBR to compile, file, and run the
program.
Running a Program from ECL
The RUN command tells UniData to run a compiled UniBasic program. If the
program is not globally cataloged, you must specify the directory file that
contains the program (for example, BP).
Syntax:
RUN directory.file program.name [-option]
If the program is globally cataloged, you must have a pointer to the directory
file that contains the program in your VOC file to execute the globally
cataloged version. To run the program, enter its cataloged name at the ECL
prompt.
Syntax:
catalog.name [-option]
In addition, you can run a program that is locally cataloged by entering the
its name at the ECL prompt. UniBasic programs are usually stored in the BP
directory.
Cataloging a UniBasic Program 3-29
Syntax:
program.name [-option]
RUN Options
The following table describes the options [-option] in the syntax for running
a program.
Note: Several of the following RUN options invoke the UniBasic Debugger. To make
full use of the debugger, you must prepare your program as explained in the Using
the UniBasic Debugger.
Option
Description
-D
Invokes the UniBasic debugger immediately before the program
executes.
-E
Invokes the UniBasic debugger when a warning or runtime error
occurs.
-F
Invokes the UniBasic debugger when a fatal error occurs.
-D -E
Invokes the UniBasic debugger immediately. After you execute the
program from the debugger, if UniData encounters a warning or
runtime error, UniData returns to the debugger.
-D -F
Invokes the UniBasic debugger immediately. After you execute the
program from the debugger, if UniData encounters a fatal error,
UniData returns to the debugger.
-G
Creates a cross-reference report (program profile). For further
information, see “Reporting Execution Time” on page 3-31 .
-N
Displays output without pausing at the bottom of the screen. Without
this option, scrolling stops at the bottom of each page, prompting the
user to press ENTER to continue.
Use UDT.OPTIONS 32 to direct UniData to retain or suppress the
HEADING statement when the “no page” option is used.
-P
Routes all program output to the system printer.
-S
Suppresses warning messages.
Program Run Options
3-30 Developing UniBasic Applications
Note: You can determine how UniBasic sets an uninitialized variable with
UDT.OPTIONS 15 on for empty string or off for zero. For more information about
UDT.OPTIONS, see the UDT.OPTIONS Commands Reference.
Reporting Execution Time
UniData generates a program profile report when you run or execute a
program with the -G option. If you previously compiled the program with
the -G option, the profile also reports on internal subroutines. UniData
creates two reports that it stores in the current directory. Both reports are of
the same format, and are named as follows:

"profile."@USERNO (for UNIX) or "profile."pid (for Windows
platforms) – Calculates CPU execution time. For example, the file
profile.3 would be a report of CPU time generated for the user
whose user number is 3.

"profile.elapse."@USERNO (for UNIX) or "profile.elapse."pid
(for Windows platforms) – Calculates real execution time (CPU and
I/O). For example, profile.elapse.3 would be the corresponding
report of real execution time for the same user.
Note: For Windows platforms, run the LISTUSER command to determine the
process ID (pid). It is listed as USRNBR.
For more information about program profiling, see Administering UniData on
UNIX or Administering UniData on Windows Platforms.
Layout of Profile Reports
Summary Section – Summary statistics regarding execution time are listed
for each program, subroutine, and called program; entries are sorted by
decreasing execution time.
Body Section – Each program or subroutine is assigned an identifying index
number, in descending order of execution time. Each index is analyzed in a
subsection delineated by dashed lines. Within the subdivided sections, the
first column lists the index number for the program or subroutine analyzed
in that section. Parent programs and subroutines are analyzed on the lines
preceding the index number; the indexed item is analyzed on the line
containing the index number; and child programs or subroutines are
analyzed on subsequent lines.
Cataloging a UniBasic Program 3-31
The following UNIX example is the profile report for the UPDATE_ORDER
program in Appendix A, “Appendix A: Sample Program”:
%time cumsecs seconds
calls name
%time cumsecs seconds
calls name
25.0
25.0
25.0
25.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.02
0.04
0.06
0.08
0.08
0.08
0.08
0.08
0.08
0.08
0.08
index
%time
[1]
100.0
0.02
0.02
0.02
0.02
0.00
0.00
0.00
0.00
0.00
0.00
0.00
1
3
2
1
1
1
3
1
1
1
1
BP/_UPDATE_ORDER
BP/_UPDATE_ORDER:DISPLAY_SCREEN
BP/_UPDATE_ORDER:DISPLAY_DATA
BP/_UPDATE_ORDER:ALTER_RECORD
BP/_UPDATE_ORDER:OPEN_FILES
BP/_UPDATE_ORDER:INITIALIZE
BP/_UPDATE_ORDER:GET_ORDER_NUMBER
/users/ud_61/sys/CTLG/d/DISPLAY_MESSAGE
BP/_UPDATE_ORDER:GET_RECORD_COMMAND
BP/_UPDATE_ORDER:WRITE_RECORD
BP/_UPDATE_ORDER:EXIT
self descendents
called/total
called+self
called/total
0.02
0.06
1
0.02
0.00
3/3
BP/_UPDATE_ORDER:DISPLAY_SCREEN [2]
0.02
0.00
2/2
BP/_UPDATE_ORDER:DISPLAY_DATA [3]
0.00
0.02
1/1
BP/_UPDATE_ORDER:GET_RECORD_COMMAND [5]
0.00
0.00
1/1
BP/_UPDATE_ORDER:OPEN_FILES [6]
0.00
0.00
1/1
BP/_UPDATE_ORDER:INITIALIZE [7]
0.00
0.00
3/3
BP/_UPDATE_ORDER:GET_ORDER_NUMBER [8]
0.00
0.00
1/1
[11]
---------------------------------------------0.02
0.00
3/3
[2]
25.0
0.02
0.00
3
BP/_UPDATE_ORDER:DISPLAY_SCREEN [2]
---------------------------------------------0.02
0.00
2/2
[3]
25.0
0.02
0.00
2
BP/_UPDATE_ORDER:DISPLAY_DATA [3]
0.00
0.00
1/1
/users/ud_61/sys/CTLG/d/DISPLAY_MESSAGE [9]
---------------------------------------------0.02
0.00
1/1
BP/_UPDATE_ORDER:GET_RECORD_COMMAND [5]
[4]
25.0
0.02
0.00
1
BP/_UPDATE_ORDER:ALTER_RECORD [4]
0.00
0.00
1/1
BP/_UPDATE_ORDER:WRITE_RECORD [10]
---------------------------------------------0.00
0.02
1/1
[5]
25.0
0.00
0.02
1
3-32 Developing UniBasic Applications
parents
index
children
<spontaneous>
BP/_UPDATE_ORDER [1]
name
BP/_UPDATE_ORDER:EXIT
BP/_UPDATE_ORDER [1]
BP/_UPDATE_ORDER [1]
BP/_UPDATE_ORDER [1]
BP/_UPDATE_ORDER:GET_RECORD_COMMAND [5]
0.02
0.00
1/1
BP/_UPDATE_ORDER:ALTER_RECORD [4]
---------------------------------------------0.00
0.00
1/1
[6]
0.0
0.00
0.00
1
BP/_UPDATE_ORDER:OPEN_FILES [6]
---------------------------------------------0.00
0.00
1/1
[7]
0.0
0.00
0.00
1
BP/_UPDATE_ORDER:INITIALIZE [7]
---------------------------------------------0.00
0.00
3/3
[8]
0.0
0.00
0.00
3
BP/_UPDATE_ORDER:GET_ORDER_NUMBER [8]
---------------------------------------------0.00
0.00
1/1
BP/_UPDATE_ORDER:DISPLAY_DATA [3]
[9]
0.0
0.00
0.00
1
/users/ud_61/sys/CTLG/d/DISPLAY_MESSAGE [9]
---------------------------------------------0.00
0.00
1/1
BP/_UPDATE_ORDER:ALTER_RECORD [4]
[10]
0.0
0.00
0.00
1
BP/_UPDATE_ORDER:WRITE_RECORD [10]
---------------------------------------------0.00
0.00
1/1
[11]
0.0
0.00
0.00
1
----------------------------------------------
BP/_UPDATE_ORDER [1]
BP/_UPDATE_ORDER [1]
BP/_UPDATE_ORDER [1]
BP/_UPDATE_ORDER [1]
BP/_UPDATE_ORDER:EXIT [11]
----------------------------------------------
The summary section of the report provides information in the following
columns.
Parameter
Description
%time
Percentage of the total runtime used by this program or
subroutine.
cumsecs
Execution time, in number of seconds, for this program or
subroutine and all called programs and subroutines.
seconds
Execution time, in number of seconds, for this program or
subroutine alone.
calls
Number of times this program or subroutine is called.
name
Name of the program or subroutine.
Program Profile – Summary
Cataloging a UniBasic Program 3-33
The body of the report provides information in the following columns.
Parameter
Description
index
An identifying number assigned to this program or subroutine.
Index numbers are assigned in descending order of execution
time.
%time
Percentage of the total program runtime used by this program or
subroutine and its descendents.
self
Execution time for this program or subroutine.
descendents
Execution time for descendents of this program or subroutine.
called
Line contents differ according to the line of the subsection you are
reading:
called/total – Lines preceding the index analyze parents; lists
number of times this index is called by the parent listed in the
name field.
called+self – Line containing the index; lists number of times the
routine is called and the number of times it calls itself.
called/total – Lines following the index number analyze children
and descendents; lists number of times this index calls the child
listed in the name field.
name
Name of the program or subroutine analyzed in this row of the
report subsection.
index
Index identifying the program or subroutine listed in the name
field.
Program Profile – Body
The tables that follow the next example explain the detail report lines for item
[3] in the report. The subject lines are repeated here.
index
%time
called/total
self descendents
parents
called+self
called/total
---------------------------------------------0.02
0.00
2/2
[3]
25.0
0.02
0.00
2
BP/_UPDATE_ORDER:DISPLAY_DATA [3]
0.00
0.00
1/1
/users/ud_61/sys/CTLG/d/DISPLAY_MESSAGE [9]
----------------------------------------------
3-34 Developing UniBasic Applications
name
index
children
<spontaneous>
BP/_UPDATE_ORDER [1]
The following table describes the first line of the preceding report segment.
Data
Column
Description
0.02
self
The execution time for parent (calling
program); in this case, BP/_UPDATE_ORDER.
0.00
descendents
The execution time for parent (calling
program); in this case, BP/_UPDATE_ORDER.
2/2
called/total
The number of times parent
BP/_UPDATE_ORDER called
BP/_UPDATE_ORDER:DISPLAY_DATA.
BP/_UPDATE_ORDER
parents
The parent (calling subroutine).
[1]
index
The index number for BP/_UPDATE_ORDER.
Program Profile Detail: First Line
The next table describes the second line of the preceding report segment.
Data
Column
Description
[3]
index
The index number for
BP/_UPDATE_ORDER:DISPLAY_DATA.
25.0
%time
The percentage of the total program
runtime used by
BP/_UPDATE_ORDER:DISPLAY_DATA.
0.00
self
The execution time for
BP/_UPDATE_ORDER:DISPLAY_DATA.
0.02
descendents
The execution time for
/users/ud_73/sys/CTLG/d/DISPLAY_
MESSAGE.
BP/_UPDATE_ORDER:
DISPLAY_DATA
name
The name of this subroutine.
[3]
index
number
The index number for
BP/_UPDATE_ORDER:
DISPLAY_DATA.
Program Profile Detail: Second Line
Cataloging a UniBasic Program 3-35
The final table describes the third line of the preceding report segment.
Data
Column
Description
0.00
self
The execution time for child (called
program); in this case,
/users/ud_73/sys/CTLG/d/
DISPLAY_MESSAGE.
0.00
descendents The execution time for child (called
program); in this case,
/users/ud_73/sys/CTLG/d/
DISPLAY_MESSAGE.
1/1
called/total
The number of times parent
BP/_UPDATE_ORDER:DISPLAY_DATA called
child /users/ud_73/sys/CTLG/d/
DISPLAY_MESSAGE.
/users/ud_60/sys
/CTLG/d/
DISPLAY_MESSAGE
child
The name of this child (called program).
[9]
index
The index number for
/users/ud_73/sys/CTLG/d/
DISPLAY_MESSAGE.
Program Profile Detail: Third Line
3-36 Developing UniBasic Applications
UniBasic Runtime Error Logging
To enable UniBasic runtime error logging, create the msglevelconfig file in
the include directory. On UNIX platforms, this directory is located in
/usr/udnn/include, where nn is the major version number for the UniData
release you are running. For Windows platforms, this directory is located in
%UDTHOM%\INCLUDE. This text file can contain multiple lines, with each
line defining a unique logging event.
You do not need to restart UniData for logging to begin or end. As soon as the
msglevelconfig file contains appropriate logging entries, the UniData
processes you specify will start writing UniBasic runtime errors to the
udt.errlog. If you remove the msglevelconfig entry, those processes will stop
writing errors to the udterrlog.
The format of each line the msglevelconfig files is:
['unidata' | pid] = Log Level (options) [,]
If you specify ‘unidata’ all udt and udapi_slave processes will begin logging.
If you want to monitor a single process, determine the process ID of that
UniData process (from the ‘listuser’ or other command), and enter that
process ID. Set the Log Level to 16 to enable logging.
If you create multiple lines in the msglevelconfig file, you must terminate
each line with a comma.
You can specify the following options to specify what information is included
in the message written to the udt.errlog:
Option
Description
/CWD
Include the 'current working directory' of the process you are
logging. This is the path to the UniData account where the process
is running.
/DT
Include the date and time the error occurred.
/PID
Include the process ID that encountered the error.
/SI
Include the UniData C runtime library and line number where the
error occurred. This is only useful to UniData developers.
msglevelconfig Options
UniBasic Runtime Error Logging 3-37
Note: After changing the msglevelconfig file, you may need to wait up to 60 seconds
for the change to be activated for running processes. This delay is determined by the
value of the -t option associated with the UniData shared memory manager (smm)
daemon.
Example
The following is an example of a msglevelconfig file on a UNIX server that
enables UniBasic runtime error logging for all UniData processes:
cat /usr/ud73/include/msglevelconfig
unidata = 16 /CWD /DT /PID /SI,
If runtime errors occurred when running a UniBasic program, information
similar to the following example are written to the udt.errlog:
cat $UDTBIN/udt.errlog
Starting: Tue May 3 07:21:05 MDT 2011
Tue May 3 07:21:33 pid=684072 file=errprint.c line=256 cwd=/disk1/ud73/demo In
BP/_UNASSIGNED at line 1 WARNING: UNINITIALIZED VARIABLE USED!
Zero Assumed!
Tue May 3 07:21:33 pid=684072 file=errprint.c line=256 cwd=/disk1/ud72/demo In
BP/_UNASSIGNED at line 3 Can not access unopened file. File variable not used in
file operation
Tue May 3 07:21:33 pid=684072 file=errprint.c line=256 cwd=/disk1/ud72/demo In
BP/_UNASSIGNED at line 3 Fatal error: READ error
#
3-38 Developing UniBasic Applications
Chapter
Chapter 4: Maintaining Data in
Files
In This Chapter . . . . . . . . . . . .
UniData Locks . . . . . . . . . . . .
Database Triggers . . . . . . . . . . .
Trigger Rules . . . . . . . . . . .
The Nature of Triggers . . . . . . . .
ECL Commands and Triggers . . . . .
UniBasic Commands Affected by Triggers .
Writing an UPDATE Trigger Subroutine . .
Writing a DELETE Trigger Subroutine . .
UniBasic STATUS Function Return Values .
Troubleshooting . . . . . . . . . .
Maintaining Files . . . . . . . . . . .
UniData Hashed Data Files . . . . . .
Alternate Key Indexes . . . . . . . .
Non-UniData Sequential Files . . . . .
Opening Files . . . . . . . . . . . .
Example . . . . . . . . . . . . .
Selecting Records . . . . . . . . . . .
Creating a Select List of Record IDs . . .
Clearing a Select List . . . . . . . .
Reading, Writing, and Deleting Data from Files.
Getting Ready to Read . . . . . . . .
Reading Record IDs from a Select List . .
Reading Data from Files . . . . . . .
Example . . . . . . . . . . . . .
Writing Data to Files . . . . . . . .
Example . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
4-4
4-5
4-6
4-6
4-7
4-8
4-9
4-9
4-13
4-16
4-17
4-18
4-18
4-19
4-22
4-25
4-25
4-26
4-26
4-28
4-29
4-29
4-29
4-29
4-32
4-32
4-34
Deleting Data from Files . . . .
Closing Files . . . . . . . . . .
Accessing Data in Unopened Files . .
4-2 Developing UniBasic Applications
. . . .
. . . .
. . . .
. . . . .
. . . . .
. . . . .
.
.
.
4-34
4-36
4-37
This chapter provides the information you need to select the correct
command or function for maintaining data in files through UniBasic
programs. For the syntax of these commands, see the UniBasic Commands
Reference.
4-3
In This Chapter
This chapter introduces the concepts and commands you use to maintain
files. It consists of the following sections:

“UniData Locks”

“Database Triggers”

“Maintaining Files”

“UniData Hashed Data Files”

“Alternate Key Indexes”

“Non-UniData Sequential Files”

“Opening Files”

“Selecting Records”

“Reading, Writing, and Deleting Data from Files”

“Closing Files”

“Accessing Data in Unopened Files”
4-4 Developing UniBasic Applications
UniData Locks
In a multiuser system, record and file locks, when used consistently, prevent
more than one user from accessing the same record at the same time.
UniBasic locks are advisory only. You must observe locking conventions
consistently in all of your applications to prevent one user from overwriting
another user’s updates.
You can use resource locks to reserve computer resources for your exclusive
use. To set, check, and release UniBasic locks, you must use the commands
specifically created for the purpose. For information about using UniBasic
locks, see Chapter 5, “Chapter 5: Using UniData Locks.”
Tip: Another way to ensure consistency among files is through UniData transaction
processing. For more information, see Chapter 9, “Chapter 9: UniBasic Transaction
Processing,” or Appendix B, “Appendix B: UniBasic Transaction Processing
Concepts.”
UniData Locks 4-5
Database Triggers
A database trigger specifies a UniBasic subroutine to call when a user
attempts to access a record. The UniBasic subroutine can then allow or
prevent access. Triggers are tied to record updates (modifying or inserting)
and deletions.
A UniData trigger can be used to validate an attempt to make a change to the
database against user-defined constraints, or “business rules,” and allow the
change only if the constraint is satisfied. Triggers are associated with the data
file, so the business rules are applied to any access to the file or record, not
just access through a particular application. For instance, a trigger subroutine
might allow only certain users to update records in a UniData file, or prevent
some records from being updated at all, or prevent entry of the null value.
Trigger Rules
UniData triggers are governed by the following rules:

A UniData trigger must be a globally cataloged UniBasic subroutine.

When running Network File Access (NFA), the trigger subroutine
and the trigger itself must reside on the server where the hashed file
resides, not on the host.

UniData triggers are activated before the update or delete (beforeevent) or after the update or delete (after-event). Note, after-event
triggers are only available in UniData 8.1 or later.

A trigger can invoke another trigger. UniData does not limit the
number of levels you can nest triggers. Be careful to use conditional
statements to avoid infinite loops that can be caused by nested
triggers.

You can call an external C routine from a trigger.
4-6 Developing UniBasic Applications


File types:

Hashed files – You can use triggers only with static or dynamic
hashed files. The files can be recoverable or nonrecoverable.

No DIR or multilevel directories – You cannot set a trigger on a
DIR file or a multilevel directory. You can associate triggers with
the static or dynamic subfiles within a multilevel file, but not
with the multilevel file itself.
Writing from a trigger subroutine:

Writes allowed to DIR files – You can write to a directory file
from within a trigger subroutine. You might want to do so to
create a log of trigger operations.

Writes allowed to hashed files – You can include write
commands within a trigger subroutine.
The Nature of Triggers
Triggers are composed of the trigger definition, which is stored in the file
header, and a globally cataloged UniBasic subroutine that is called when you
attempt to update or delete a record.
Use the ECL CREATE.TRIGGER command to define a trigger. If the file
header already contains a trigger by the same name, UniData does not
overwrite the existing trigger. Instead, you must delete the existing trigger
before you can create the new one.
Three ECL commands let you create, delete, and list triggers. Note that these
changes to the trigger in the file header do not affect the UniBasic subroutine.
Command syntax and details about how to code these commands follow this
section.
The UniBasic trigger subroutine can contain any operation on a file,
including writing to it. For example, the subroutine could stipulate that
certain kinds of record modifications are not allowed for a file, or it might
place restrictions on whether users can delete records from a particular file.
You can change the underlying trigger subroutine without having to delete
and re-create the trigger.
Note: To create or delete a trigger, you must be the owner of the file at the operating
system level, or you must have root privileges on UNIX or Administrator privileges
on Windows platforms.
Database Triggers 4-7
After-event triggers (AFTER UPDATE or AFTER DELETE) always execute, and
there is no way to prevent after-event triggers. This type of trigger is useful for
auditing or logging types of events. An after-event trigger fires after the triggering
change completes. If the change is not in a transaction and the After-event trigger
fails, then there is no way to rollback that change. That is why it is recommended to
wrap the update/delete event in a transaction to guarantee data integrity.
ECL Commands and Triggers
This section discusses ECL command that create or maintain triggers, and
lists ECL commands that are affected by triggers.
ECL Commands That Create or Maintain Triggers
The following ECL commands create or maintain triggers. For the syntax of
these commands, see the UniData Commands Reference.

CREATE.TRIGGER – Creates a trigger in a file header that calls a
UniBasic subroutine. If the BEFORE | AFTER option is not specified,
then BEFORE is assumed.

DELETE.TRIGGER – Deletes the trigger definition from a file header.
It does not affect the UniBasic trigger subroutine.

LIST.TRIGGER – Displays a list of triggers.
ECL Commands Affected by Triggers
The UniData triggers feature has an impact on how some ECL commands
behave. The following commands have changed behavior. For the syntax of
these commands, see the UniData Commands Reference.

CLEAR.FILE

COPY

DELETE

ED

MODIFY

UNIENTRY
4-8 Developing UniBasic Applications
UniBasic Commands Affected by Triggers
The following UniBasic commands invoke triggers:

DELETE

DELETEU

WRITE

WRITEU

WRITEV

WRITEVU
If a trigger returns a constraint violation, control is passed to the ON ERROR
clause of the command, if one exists. If no ON ERROR clause is included in
the command, the UniBasic program aborts.
Tip: The UniBasic STATUS function returns the status of the preceding command.
You can place it within the trigger subroutine to learn about the status of individual
commands executed within the trigger. If you place it immediately after the statement
that calls the trigger, it returns the status of the UniBasic command as determined
by the trigger. These values are listed in “UniBasic STATUS Function Return
Values” on page 4-16. The trigger subroutine also returns a value indicating its
status in the parameter execstat; the values returned in execstat are listed in the
Parameters sections for the UPDATE and DELETE trigger subroutines.
Writing an UPDATE Trigger Subroutine
A UniBasic subroutine or function serves as the BEFORE, AFTER, or
UPDATE trigger that is executed when a user attempts to update a record in
the subject file.
When after-event triggers are used, changes to the recordval and execstat values are
ignored.
Tip: You can call an external C routine from the UniBasic subroutine or function that
is called from a trigger.
Database Triggers 4-9
Subroutine
The following SUBROUTINE definition must be the first non-comment line
in the UniBasic trigger subroutine.
BEFORE UPDATE trigger syntax:
SUBROUTINE trigname(execstat, dictflag, filename, record.ID.expr,
recordval)
AFTER UPDATE trigger syntax:
SUBROUTINE trigname(execstat, dictflag, filename, record.ID.expr,
recordval, old_recordval)
The parameters in this statement are defined later in this section.
Note: A subroutine name cannot exceed 65 characters. If it does, the program will
fail to compile.
Function
The following FUNCTION definition must be the first line in the UniBasic
trigger function.
BEFORE UPDATE trigger syntax:
FUNCTION trigname(dictflag, filename, record.ID.expr, recordval)
AFTER UPDATE trigger syntax:
FUNCTION trigname(dictflag, filename, record.ID.expr, recordval,
old_recordval)
The function must include the following statement:
RETURN excstat
4-10 Developing UniBasic Applications
Parameters
The following table describes each parameter of the syntax.
Parameter
Description
trigname
The name of the globally cataloged subroutine.
execstat
The execution status returned by the trigger subroutine:
0 – No updates are allowed.
1 – Updates are allowed.
2 – Updates are allowed, using the return recordval.
Note: Changes to this parameter in after-event triggers are
ignored.
dictflag
“DICT” – Indicates that the trigger is operating on the
dictionary file.
“” – Indicates that the trigger is operating on the data file.
The quotation marks are required.
filename
The name of the file on which the trigger is operating.
record.ID.expr
The record to be updated.
recordval
The input record value submitted to the UPDATE trigger.
recordval is both an input and output parameter. The trigger can
change this value (for example, by performing a conversion).
Then, if the trigger sets execstat to 2, this value is passed back in
recordval and updates the data record. Only strings and
numbers are valid.
If the value returned in recordval is invalid, the record is not
updated, even if the trigger subroutine sets execstat to 2. In this
case, the UniBasic STATUS function returns 3 when executed
immediately after the command that calls the trigger. Only
strings and numbers are valid.
Note: Changes to this parameter in after-event triggers are
ignored.
old_recordval
The original record contents.
Trigger Parameters
Database Triggers 4-11
BEFORE UPDATE Trigger Example
The following example begins with a BEFORE UPDATE trigger subroutine
called TRIG1. Because the return status is always 5, no record in the file can
be updated.
SUBROUTINE TRIG1(exec.stat,dict.flag,filename,rec.id.expr,rec.val)
exec.stat=5
RETURN
Next, we create the trigger, associate it with the ORDERS file, and list the
triggers associated with the ORDERS file:
:LIST.TRIGGER ORDERS
BEFORE UPDATE TRIGGER: TRIG1
BEFORE DELETE TRIGGER: not defined
AFTER UPDATE TRIGGER: not defined
AFTER DELETE TRIGGER: not defined
Finally, we attempt to copy record 969 into record 970 in the ORDERS file,
and the trigger prevents the copy:
:COPY FROM ORDERS TO ORDERS 969,970
Cannot update 970, due to trigger constraint.
0 records copied
AFTER UPDATE Trigger Example
The following example begins with an AFTER UPDATE trigger subroutine
called AFTER_UPDTRIG. The exec.stat option is not set since this trigger is
always executed. Note, the use of the 'OPEN' in this example is just for illustrative purposes. It is best to open the file in COMMON.
SUBROUTINE
AFTER_UPDTRIG(exec.stat,dict.flag,file.name,rec.id.expr,rec.val,or
ig.rec.val)
OPEN "","LOGFILE" TO LOGFILE ELSE RETURN
MY.ID = @DATE:"*":@TIME:"*":@USERNO:"*U"
WRITE rec.id.expr ON LOGFILE,MY.ID ON ERROR RETURN
RETURN
Note: After creating and compiling the subroutine, you must catalog it globally.
4-12 Developing UniBasic Applications
Next, create the trigger, associate it with the ORDERS file, and list the triggers
associated with the ORDERS file:
:CREATE.TRIGGER ORDERS AFTER_UPDTRIG AFTER UPDATE
:LIST.TRIGGER ORDERS
BEFORE UPDATE TRIGGER: not defined
BEFORE DELETE TRIGGER: not defined
AFTER UPDATE TRIGGER: AFTER_UPDTRIG
AFTER DELETE TRIGGER: not defined
Finally, copy record 969 to 985 and list the log file:
:COPY FROM ORDERS TO ORDERS 969,985
1 records copied
:LIST LOGFILE
LIST LOGFILE 15:12:50 Jan 27 2015 1
LOGFILE.......................
17194*54729*15298*U
1 record listed
Writing a DELETE Trigger Subroutine
A UniBasic subroutine or function serves as the BEFORE DELETE or AFTER
DELETE trigger that is executed when a user attempts to delete a record in
the subject file.
Tip: You can call an external C routine from the UniBasic subroutine or function that
is called from a trigger.
Subroutine
The following SUBROUTINE definition must be the first line in the UniBasic
trigger subroutine.
BEFORE DELETE trigger syntax:
SUBROUTINE trigname(execstat, dictflag, filename, record.ID.expr)
AFTER DELETE trigger syntax:
SUBROUTINE trigname(execstat, dictflag, filename, record.ID.expr,
original.recordval)
The parameters in this statement are listed later in this section.
Database Triggers 4-13
Note: A subroutine name cannot exceed 65 characters. If it does, the program will
fail to compile.
Function
The following FUNCTION definition must be the first line in the UniBasic
trigger function.
BEFORE DELETE trigger syntax:
FUNCTION trigname(dictflag, filename, record.ID.expr)
AFTER DELETE trigger syntax:
FUNCTION trigname(dictflag, filename, record.ID.expr,
original.recordval)
The function must include the following statement:
RETURN execstat
Parameters
The following table describes each parameter of the syntax.
Parameter
Description
trigname
The name of the globally cataloged subroutine.
execstat
The execution status returned by the trigger subroutine. Valid
values for this include:
0 – Delete is not allowed
1 – Delete is allowed
dictflag
“DICT” – Indicates that the trigger is operating on the dictionary
file.
“” – Indicates that the trigger is operating on the data file.
The quotation marks are required.
Trigger Parameters
4-14 Developing UniBasic Applications
Parameter
Description
filename
The name of the file the trigger is operating on.
record.ID.expr
The record to be deleted.
original.recordval
The original record contents.
Trigger Parameters (continued)
BEFORE DELETE Trigger Example
The following example begins with a BEFORE DELETE trigger subroutine
called DEL_TRIG that always returns 1 and always allows records to be
deleted:
SUBROUTINE DEL_TRIG(exec.stat,dict.flag,filename,rec.id.expr)
exec.stat=1
RETURN
Note: After creating and compiling the subroutine, you must catalog it globally.
Next, we create the trigger and associate it with the ORDERS file:
:CREATE.TRIGGER ORDERS DEL_TRIG DELETE
Finally, we delete records in the ORDERS file. The trigger always allows the
deletion because the subroutine sets the execution status to 1.
:DELETE ORDERS 912
'912' deleted.
:
AFTER DELETE Trigger Example
The following example begins with an AFTER DELETE trigger subroutine
called AFTER_DELTRIG. The exec.stat option is not set since this trigger is
always executed. The use of the 'OPEN' in this example is just for illustrative
purposes. It is best to open the file in COMMON.
SUBROUTINE
AFTER_DELTRIG(exec.stat,dict.flag,file.name,rec.id.expr,orig.rec.v
al)
OPEN "","LOGFILE" TO LOGFILE ELSE RETURN
MY.ID = @DATE:"*":@TIME:"*":@USERNO:"*D"
WRITE rec.id.expr ON LOGFILE,MY.ID ON ERROR RETURN
RETURN
Database Triggers 4-15
Note: After creating and compiling the subroutine, you must catalog it globally.
Next, create the trigger, associate it with the ORDERS file, and list the triggers
associated with the ORDERS file:
:CREATE.TRIGGER ORDERS AFTER_DELTRIG AFTER DELETE
:LIST.TRIGGER ORDERS
BEFORE UPDATE TRIGGER: not defined
BEFORE DELETE TRIGGER: not defined
AFTER UPDATE TRIGGER: AFTER_UPDTRIG
AFTER DELETE TRIGGER: AFTER_DELTRIG
:Finally, delete the 985 record and list the log file:
:DELETE ORDERS 985
1 records copied
:LIST LOGFILE
LIST LOGFILE 17:04:45 Jan 27 2015 1
LOGFILE.......................
17194*61316*9961530*D
1 record listed
UniBasic STATUS Function Return Values
The UniBasic STATUS function returns values that give information about
trigger operations.
Return Value
Description
0
No error.
1
System error, such as a damaged file.
2
Constraint violation. In this case, the UniBasic trigger subroutine
returns a value of 0 in the parameter excstat, indicating that the
update or delete is not allowed.
3
Trigger execution error or unexpected return from trigger routine
(for example, trigger subroutine is not cataloged).
5
After Trigger program returns 0 indicating an error occurred.
After Trigger execution error or unexpected return from trigger
routine (for example, trigger subroutine is not cataloged).
STATUS Function Values
4-16 Developing UniBasic Applications
Note: Most UniBasic commands and some ECL commands, within a trigger or
outside a trigger, set the value of STATUS.
Warning: When you write UniBasic programs or subroutines, always include the
ON ERROR clause for statements that update hashed files. This is especially
important when using triggers, because any failure caused by a trigger aborts the
UniBasic program unless the ON ERROR clause is present.
Troubleshooting
If a trigger does not work correctly, check the following:

Debug and test trigger subroutines.

Check the return values. Are they what UniData expects?

Is the trigger subroutine cataloged?
Database Triggers 4-17
Maintaining Files
You can write UniBasic programs to maintain hashed data files, alternate key
indexes, and non-UniData sequential files. The following sections describe
how to maintain them:

“UniData Hashed Data Files”

“Alternate Key Indexes”

“Non-UniData Sequential Files”
UniData Hashed Data Files
The types of UniData files that you can maintain with UniBasic programs are
introduced in Chapter 1, “Chapter 1: Introduction to UniBasic.” This section
suggests the steps you might follow to maintain files.
Maintaining UniData Hashed Files
The following procedure is one that you might follow when updating a
UniData hashed file. (For a sample program, see Appendix A, “Appendix A:
Sample Program.”)
1.
Use the OPEN command to open the data file.
2.
Use one of the following methods to select records:
3.

Create a select list using SELECT, SELECTINDEX, UniQuery
SELECT, UniData SQL SELECT, or a paragraph select.

Set a pointer in an alternate index with SETINDEX.
Use one of the following commands to read data from selected
records:
READU (use this command to set an exclusive lock), READ,
READV, READVL, READVU, READNEXTTUPLE, READFWD,
READBCK, READFWDL, READBCKL, READFWDU,
READBCKU
4.
Manipulate data in arrays. See Chapter 6, “Chapter 6: Working with
Data in Programs.”
4-18 Developing UniBasic Applications
5.
Use one of the following commands to write data to selected records:
WRITE, WRITEU, WRITEV, WRITEVU, WRITET
6.
Use the CLOSE command to close the data file.
Alternate Key Indexes
Alternate key indexing speeds searching for records within a database.
Alternate keys allow access to records without the need to use the record ID
or read through the entire file. An alternate key index consists of values for
the alternate key and the associated @IDs of each record.
The Primary Key
UniData requires that each record in the database have a unique identifier,
which is called the record ID, @ID, or primary key. The UniData hashing
algorithm uses the @ID to locate the record in the database. When UniData
looks for a record, it uses the @ID as the “key” to finding the record quickly.
Tip: Create an index for @ID to speed access to data records.
The Alternate Key
UniData allows only one primary key for a record. If you are searching for a
record based on the primary key, searches are fast. Most of the time, however,
searches are based on other information contained in a record, such as a
name, city, phone number, or the result of a virtual attribute calculation.
These searches are slower than a search based on the primary key.
You can create alternate keys any time after you create the data file. For more
information, see Using UniData or the CREATE.INDEX command in the
UniData Commands Reference.
Maintaining Files 4-19
Duplicate Alternate Keys
Although primary keys must be unique, duplicate alternate keys can be
entered. To block the entry of duplicate keys in indexes of non-RFS files,
specify the NO.DUPS keyword when you create the index (with
CREATE.INDEX). However, if you do so, you will not be able to build the
index by using BUILD.INDEX if duplicate values are already present in the
key attribute in your data file. Also, any WRITE command that attempts to
create a duplicate alternate key will generate a fatal error if no ON ERROR
clause is coded.
Note: The Recoverable File System (RFS) does not support NO.DUPS.
You can detect duplicate keys by executing the ECL command DUP.STATUS
ON, then checking for a STATUS function return value of 10 immediately
after the following UniBasic commands:

WRITE, WRITEU, WRITEV, WRITEVU

READFWD, READFWDL, READFWDU

READBCK, READBCKL, READBCKL
Creating and Maintaining Alternate Indexes
You might perform the following steps to create and manage alternate
indexes. For the syntax of the ECL commands, see the UniData Commands
Reference.
1.
Use the ECL CREATE.INDEX command to create the index.
2.
Use the ECL BUILD.INDEX command to load alternate keys into the
index.
You cannot execute BUILD.INDEX while users are accessing the file.
3.
Use the ECL ENABLE.INDEX or DISABLE.INDEX command to
enable or disable (respectively) the automatic updating of the
alternate key index. You also can use the ECL UPDATE.INDEX
command to update the index manually.
4.
Use the SELECTINDEX command to build a select list based on the
alternate index file, or use the SETINDEX command to set a pointer
to an alternate key value in the index.
4-20 Developing UniBasic Applications
5.
Read records from the UniData file by using the alternate index and
any of the following UniBasic commands:
READFWD, READFWDL, REAFWDU, READBK, READBKL,
READBKU
Using Alternate Index Files
Use the following alternate index commands and functions to speed access
to UniData files. For syntax and usage, see the UniBasic Commands Reference.
Command/Function
Action
FILEINFO
Returns the name of the index to which the last SETINDEX
statement was applied. Also requests the key value of the
index for the record that was read by the last browsing
statement, such as READFWD or READBCK.
READBCK
Retrieves one record ID from an index, assigns the
contents of the record to a dynamic array, and assigns the
record ID to the @ID variable.
READBCKL
Same as READBCK, but places a shared lock on the record.
READBCKU
Same as READBCK, but places an exclusive lock on the
record.
READFWD
Retrieves one record ID from an index, assigns the
contents of the record to a dynamic array, and assigns the
record ID to the @ID variable.
READFWDL
Same as READFWD, but places a shared lock on the
record.
READFWDU
Same as READFWD, but places an exclusive lock on the
record.
SELECTINDEX
Creates a select list based on an alternate key. The list can
contain all records for this alternate index or a subset
based on a particular key value.
SETINDEX
Sets a pointer to the initial alternate key value in the
alternate index. Use READFWD or READBACK to
traverse the index.
Alternate Index Commands
Maintaining Files 4-21
The following program demonstrates use of an alternate index file for the
demonstration database file INVENTORY. The index is on attribute 3,
PROD_NAME. The program prompts the user for a product, and then it prints
all records for that product.
OPEN "INVENTORY" TO open.file ELSE PRINT "Open error." ;STOP
PRINT "Enter product to display: ";INPUT search_product
search_product = OCONV(search_product, "MCT")
new_product = search_product
SETINDEX "PROD_NAME", search_product ON open.file
IF STATUS()>0 THEN PRINT "Record not found.";STOP
LOOP UNTIL new_product <> search_product
READFWD dyn.array.var FROM open.file ELSE PRINT "Record not
found."
new_product = EXTRACT(dyn.array.var,3)
IF new_product = search_product THEN PRINT "Record is:
":dyn.array.var
REPEAT
END
The following example shows user input and output from the preceding
program:
Enter product to display:
?trackball
Record is: 01/17/2010p09:00AMpTrackballpDeluxe
ModelpGrayp2373p5799p30
Record is: 01/18/2010p10:00AMpTrackballpEconomy
ModelpWhitep2299p4499p30
Record is: 01/11/2010p12:00PMpTrackballpSuper Deluxe
ModelpGrayp494p9899p70
Non-UniData Sequential Files
You can use the following types of commands to maintain data in nonUniData sequential files:

Sequential File Commands – Specifically for manipulating nonUniData sequential files.

UniData Hashed File Commands – You can use these after you make
an entry for the sequential file in the UniData VOC file. For
instructions about creating a VOC entry, see Using UniData.
4-22 Developing UniBasic Applications
Sequential File Commands
UniBasic provides commands to manipulate non-UniData sequential files,
including files that contain data stored in binary format. These commands
access the file through the operating system file name or place name and
therefore do not use a VOC entry to locate the file. Access to different types
of sequential files lets you modify print jobs, log files, and source code using
a UniBasic program.
UniBasic provides several sets of commands to handle sequential file
operations. To open a sequential file, use either the OSOPEN or OPENSEQ
command. After opening a file, you can read and write the entire file, read
and write one record at a time, or read and write the file beginning at a
specified byte location. The following table lists the commands that pertain
to sequential read and write operations.
Read/Write Entire File
Read/Write One
Record
Read/Write Starting at
a Specified Byte
Location
OSREAD
READSEQ
OSBREAD
OSWRITE
WRITESEQ
WRITESEQF
OSBWRITE
n/a
WEOFSEQ
n/a
Commands for Reading/Writing Non-UniData Sequential Files
To close a sequential file, use either the OSCLOSE or CLOSESEQ command.
To delete a sequential file, use the OSDELETE command.
Note: Even though you can use any combination of sequential read and write
commands, you should be careful to avoid writing data in unintended areas of the file.
We recommend you become thoroughly familiar with them before you mix their use.
For the syntax and use of these commands, see the UniBasic Commands Reference.
In addition, if you have opened a named pipe, you cannot use the READSEQ
command to read a record from it, and you cannot use WRITESEQ or WRITESEQF
to write a record to it. You must use the OSBREAD and OSBWRITE commands.
Maintaining Files 4-23
Warning: The UniBasic OSDELETE command is intended for use on OS-type files
(not UniData hashed files). If you execute the command against a recoverable hashed
file, UniData could crash immediately or at the next RFS checkpoint. If the command
is executed against a nonrecoverable hashed file, UniData will not crash, but other
unpredictable problems could occur.
UniData Hashed File Commands
You can use hashed file commands (OPEN, READ, WRITE, CLOSE, and
DELETE) on non-UniData sequential files that have VOC entries. However,
we discourage this practice because UniData could, in some cases, insert
characters in the file, even upon opening it. Use the sequential file commands
listed earlier, or operating system commands.
4-24 Developing UniBasic Applications
Opening Files
UniBasic provides the following commands for opening files. You must open
a file in a UniBasic program before you can access the data in it.
Command
Action
OPEN
Open a file. Generally, you store a pointer to the open file in a
variable. However, you can open a default file by omitting the
variable. Then you can execute file-level commands against this
default file by again omitting the file variable.
OPENSEQ
Open a sequential file, starting at the beginning of the file.
OSOPEN
Open a sequential file that does not use CHAR(10) as the line
delimiter. The maximum length of the file name path cannot exceed
254 characters.
Commands That Open Files
For the syntax and use of these commands, see the UniBasic Commands
Reference.
Example
The following program segment is taken from the sample program in
Appendix A, “Appendix A: Sample Program.” It demonstrates opening a
UniData hashed file.
OPEN_FILES:
OPEN "CLIENTS" TO CLIENT_FILE ELSE
MESSAGE = "The CLIENT file could not be opened."
CALL DISPLAY_MESSAGE(MESSAGE)
STOP
END
Opening Files 4-25
Selecting Records
Some ways you might determine which record(s) to retrieve include:

Prompting the user or a data stack for record ID(s) or other selection
criteria.

Performing calculations.

Executing conditional tests.
Creating a Select List of Record IDs
The record IDs of selected records are placed in a select list. UniData provides
ten storage areas identified by their number: 0 through 9. If you do not
specify a list number when you select records IDs, UniData stores them in list
0. You can use multiple select lists to process data from several files
simultaneously.
UniBasic supports record selection by internal and external statements and
commands, or externally from ECL, a paragraph, or a paragraph that
performs an ECL (UniQuery or UniData SQL) SELECT statement.
After you create a select list, the prompt changes to a greater than symbol (>),
any subsequent select statement acts against that list. You must delete or
deactivate the select list before retrieving IDs based on completely new
criteria. For information about deleting and deactivating select lists, see the
ECL commands CLEARSELECT and SAVE.LIST in the UniData Commands
Reference or the UniBasic CLEARSELECT command in the UniBasic
Commands Reference.
Saving a Select List
Select lists are available only during the current work session. You can save a
select list to a file by using the UniBasic WRITELIST command. Then, when
you want to use the list again, execute the SELECT command followed by the
name of the file.
4-26 Developing UniBasic Applications
Internal Select
You can select records within a UniBasic program using the following
commands.
Command
Action
SELECT
Collect a list of all @IDs from a specified file.
SELECTINDEX
Create a select list based on an alternate key index. The list
can be made up of an entire index or can be limited to a
particular alternate key value or values.
You must use the SETINDEX command to set the pointer
before you can read records.
FORMLIST
Create a select list from a dynamic array.
EXECUTE a
UniQuery SELECT
EXECUTE a UniData
SQL SELECT
Execute a UniData SQL or UniQuery SELECT command
using the UniBasic EXECUTE or EXECUTESQL command
to create a list of record IDs.
SELECTINFO
Determine if a select list is active.
These SELECT statements offer the advantage of letting
you use selection criteria.
Selecting Records Within a UniBasic Program
For the syntax and use of UniBasic commands, see the UniBasic Commands
Reference. For the syntax and use of UniQuery commands, see Using
UniQuery. For the syntax and use of UniData SQL SELECT, see the UniData
SQL Commands Reference.
External Select
You can select records from the ECL prompt or from a UniData paragraph.
Then run the program or paragraph using the select list.
Selecting Records 4-27
Examples of External Select
In the following example, a UniQuery SELECT is executed from the ECL
prompt. The > prompt indicates that a select list is active. The UniBasic
program executed from this prompt uses the active select list.
:SELECT INVENTORY WITH QUANTITY > '999'
>RUN BP OVERSTOCK_REPORT
The following example UniData paragraph selects a stored select list, and
then it runs a UniBasic program using the selected records:
PA
SELECT INVENTORY3
RUN BP NEWITEMS_REPORT
Clearing a Select List
Once you create a select list, any subsequent select statement acts against that
list. You must delete or deactivate the active select list before retrieving IDs
based on completely new criteria.
CLEARSELECT empties a record ID list created by a SELECT statement. You
can clear one or all select lists.
The ECL commands CLEARSELECT and SAVE.LIST also clear or deactivate
select lists. For more information about these commands, see the UniData
Commands Reference.
4-28 Developing UniBasic Applications
Reading, Writing, and Deleting Data from Files
This section describes how to read, write, and delete data from files.
Getting Ready to Read
Before you start reading records, you might want to take one of the following
steps:

Make a record ID available to the program with the UniBasic
READNEXT or READNEXTTUPLE command.

Set a pointer to an initial alternate key value in an alternate index
with the SETINDEX command.
Note: After you open a file and before you read data, you need to select records for
processing. For information about selecting records and creating select lists of record
IDs, see the previous section.
Reading Record IDs from a Select List
After you have selected record IDs for processing, you can use the UniBasic
READNEXT command to assign the next record ID from an active select list
to a variable.
Reading Data from Files
Note: You must understand the UniBasic record locking system to select the
appropriate combination of commands to prevent data inconsistency, which can
result from multiple users updating files at the same time. For more information, see
Chapter 5, “Chapter 5: Using UniData Locks.”
Reading, Writing, and Deleting Data from Files 4-29
UniBasic supplies a number of commands for reading records so that the data
in them can be manipulated in a UniBasic program. You will want to use the
proper command for the type of file from which you are reading, the type of
variable you are reading into, and the type of lock (if any) you want to check
for and set. The following table introduces the UniBasic commands that read
records from files and data from records.
READ Command
Action
MATREAD
Assigns values in attributes of a record to corresponding
elements of a dimensioned array regardless of lock status.
Retains existing locks.
MATREADL
If the record is available (unlocked or shared lock), sets a
shared lock on the record. Assigns the contents of the record
to a dimensioned array.
MATREADU
If the record is available (no locks), sets an exclusive lock.
Assigns the attributes of the record to a dimensioned array.
READ
Reads a record regardless of lock status and assigns its
contents to a dynamic array. Retains existing locks.
READL
If the record is available (unlocked or shared lock), sets a
shared lock on the record. Assigns the record’s contents to a
dynamic array.
READU
If the record is available (no locks), set an exclusive lock.
Assigns the contents of the record to a dynamic array.
READV
Reads data from an attribute and assigns it to a variable
regardless of lock status. Retains existing locks.
READVL
If the record is available (unlocked or shared lock), sets a
shared lock. Reads data from an attribute and assigns it to a
variable.
READVU
If the record is available (no locks), sets an exclusive lock on
the record. Reads data from an attribute and assigns it to a
variable.
READSEQ
Reads the next record from a non-UniData sequential file
and assigns the data to a variable regardless of lock status.
OSREAD
Reads an entire sequential file and assigns the contents to a
variable.
READ Commands
4-30 Developing UniBasic Applications
READ Command
Action
OSBREAD
Reads data from a sequential file starting at a specified byte
location for a certain length of bytes, and assigns the data to
a variable.
READT
Regardless of lock status, reads the next available record
from a tape and assigns it to a variable.
READNEXTTUPLE
Using a list of record IDs, reads the next record to a variable
regardless of lock status. Retains existing locks.
READFWD
READBCK
Retrieves one record ID from an index, assigns the contents
of the record to a dynamic array, and assigns the record ID
to the @ID variable regardless of lock status. Retains
existing locks.
READFWDL
READBCKL
If the record is available (unlocked or shared lock), sets a
shared lock. Retrieves one record ID from an index, assigns
the contents of the record to a dynamic array, and assigns
the record ID to the @ID variable.
READFWDU
If the record is available (no locks), sets an exclusive lock.
Retrieves one record ID from an index, assigns the contents
of the record to a dynamic array, and assigns the record ID
to the @ID variable.
READBCKU
READ Commands (continued)
For syntax and instructions for coding read statements, see the UniBasic
Commands Reference.
Tip: Using UniBasic commands to modify files and records containing binary data
(for example, directory files such as BP) could have unpredictable results. Use
operating system commands instead, such as cp (for UNIX) or copy (for Windows
platforms).
Reading, Writing, and Deleting Data from Files 4-31
Example
The following program segment is taken from the sample program
UPDATE_ORDER in Appendix A, “Appendix A: Sample Program.” The
READU command reads a record in the ORDERS file (in the demo database),
converting attributes in preparation for displaying them on the screen.
DISPLAY_DATA:
* Display the current information in the desired record. This is
* determined by the number the user entered (ORDER_NUMBER).
READU ORDER.REC FROM ORDERS_FILE,ORDER_NUMBER THEN
* Read with a lock so that no one else can modify it at the same
time.
RECORD_FOUND = 1
ORDER_DATE = OCONV(ORDER.REC<1>,"D4/")
ORDER_TIME = OCONV(ORDER.REC<2>,"MT")
CLIENT_NUMBER = ORDER.REC<3>
ADDRESS = ''
Writing Data to Files
Note: Write statements execute regardless of lock status. For this reason, you must
precede them with the READU command to prevent data inconsistency that can
result from multiple users updating files at the same time. For more information, see
Chapter 5, “Chapter 5: Using UniData Locks.”
4-32 Developing UniBasic Applications
UniBasic supplies a number of commands for writing records into UniData
hashed files from UniBasic variables, including dynamic and dimensioned
arrays. You will want to use the proper command for the type of file you are
writing to and the type of variable you are writing from. Use the following
table to decide which UniBasic command to use to write to a file.
WRITE Command
Action
MATWRITE
Writes elements of a dimensioned array to corresponding
attributes of a record regardless of lock status.
MATWRITEL
If the record is available (unlocked or shared lock), sets a shared
lock on the record. Writes elements of a dimensioned array to
corresponding attributes of a record.
MATWRITEU
If the record is available (no locks), sets an exclusive lock. Writes
elements of a dimensioned array to corresponding attributes of
a record.
WRITE
Writes an expression to a record regardless of lock status.
Releases any record or file lock this process set.
WRITEU
Writes an expression to a record regardless of lock status, but
retains locks if present.
WRITEV
Writes an expression to an attribute of a data record regardless
of lock status.
WRITEVU
Writes an expression to an attribute of a data record.
OSWRITE
Writes the contents of a variable to a sequential file.
OSBWRITE
Writes a variable starting at a specified byte location to a
sequential file.
WRITESEQ
Writes a variable as a record on a sequential file from a current
record pointer position.
WRITESEQF
Writes a variable as a record on a sequential file from a current
record pointer position. Forces UniData to immediately write
the data to the disk.
WRITET
Writes the value of an expression onto a tape.
WRITE Commands
For syntax and instructions for coding write statements, see the UniBasic
Commands Reference.
Reading, Writing, and Deleting Data from Files 4-33
Tip: Using UniBasic commands to modify files and records containing binary data
(for example, directory files, such as BP) could have unpredictable results. Use
operating system commands instead, such as cp (for UNIX) or copy (for Windows
platforms).
Example
The following program segment is taken from the sample program,
UPDATE_ORDER in Appendix A, “Appendix A: Sample Program.” The
WRITEV command writes an attribute to the ORDERS file (in the demo
database) after an order number (one value in a multivalued attribute) has
been deleted from it.
DELETE_RECORD:
* (Assuming the order #'s are on line 12)
READVU ORDER_LINE FROM CLIENT_FILE,CLIENT_NUMBER,12 THEN
LOCATE ORDER_NUMBER IN ORDER_LINE<1> SETTING POSITION THEN
DEL ORDER_LINE<1,POSITION>
END
WRITEV ORDER_LINE ON CLIENT_FILE, CLIENT_NUMBER, 12
END
*
DELETE ORDERS_FILE, ORDER_NUMBER
RELEASE CLIENT_FILE,CLIENT_NUMBER
RETURN
Deleting Data from Files
DELETE executes regardless of lock status. The only way to prevent data
inconsistency that can result from multiple users updating files at the same
time is to precede DELETE with READU. For more information, see Chapter
5, “Chapter 5: Using UniData Locks.”
4-34 Developing UniBasic Applications
The following table provides a brief introduction to the UniBasic commands
that delete data from files and/or records.
Command
Action
DELETE
Deletes a record from a UniData file regardless of lock status.
Releases all locks set by this process.
DELETEU
Checks for locks. If the record is available, deletes it.
OSDELETE
Deletes a sequential file.
Commands That Close Files
Warning: The UniBasic OSDELETE command is intended for use on OS-type files
(not on UniData hashed files). If the command is executed against a recoverable
hashed file, UniData could crash immediately or at the next RFS checkpoint. If the
command is executed against a nonrecoverable hashed file, UniData will not crash,
but other unpredictable problems could occur.
For the syntax and use of these commands, see the UniBasic Commands
Reference.
DELETE_RECORD:
* (Assuming the order #'s are on line 12)
READVU ORDER_LINE FROM CLIENT_FILE,CLIENT_NUMBER,12 THEN
LOCATE ORDER_NUMBER IN ORDER_LINE<1> SETTING POSITION THEN
DEL ORDER_LINE<1,POSITION>
END
WRITEV ORDER_LINE ON CLIENT_FILE, CLIENT_NUMBER, 12
END
*
DELETE ORDERS_FILE, ORDER_NUMBER
RELEASE CLIENT_FILE,CLIENT_NUMBER
RETURN
Reading, Writing, and Deleting Data from Files 4-35
Closing Files
The following table introduces the commands that close files.
Command
Action
CLOSE
Releases locks and closes a data or dictionary file.
CLOSESEQ
Releases locks and closes a sequential file.
OSCLOSE
Closes a sequential file.
Commands That Close Files
For the syntax of these commands and instructions for coding close
statements, see the UniBasic Commands Reference.
4-36 Developing UniBasic Applications
Accessing Data in Unopened Files
With the ICONV T and OCONV T functions you can access singlevalued,
multivalued, and multi-subvalued attributes in a file that you have not
opened. You can also use these functions to determine if a particular record
or attribute exists or contains an empty string.
The first line of the following program segment reads the demonstration
database file ORDERS, accessing the record with @ID 912, and retrieving the
second attribute. OCONV T also converts the internal date to external
display format. The second line prints the value in that attribute.
record.ret = OCONV("912","TORDERS;V;;2")
PRINT "Record retrieved: ":record.ret
END
The following output is obtained by running the preceding program:
Record retrieved: 12:30PM
Accessing Data in Unopened Files 4-37
Chapter
Chapter 5: Using UniData Locks
In This Chapter . . . . . . . . . . .
Understanding the UniData Locking System
How UniData Locks Work . . . . .
Types of UniData Locks . . . . . .
When UniBasic Finds a Lock . . . . .
Points to Remember about Locks . . .
Locking Commands . . . . . . . . .
Checking Lock Status . . . . . . .
What Commands Do with Locks. . . . .
When to Use Locking Commands . . . .
Programming Problems . . . . . . . .
Causes . . . . . . . . . . . .
Minimizing Problems . . . . . . .
Locking Example . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5-3
5-4
5-4
5-4
5-5
5-5
5-7
5-7
5-9
5-11
5-13
5-13
5-13
5-15
5
In a multiuser environment, you must be able to prevent more that one user
from updating records simultaneously. By observing the UniBasic locking
conventions consistently in all of your applications, you can provide this
protection. You can also use locks to reserve computer resources.
This chapter introduces UniBasic locks and the commands that comprise the
UniBasic locking system. For more information about ensuring database
consistency, see Chapter 9, “Chapter 9: UniBasic Transaction Processing.”
5-3
In This Chapter
This chapter contains the following sections:

“Understanding the UniData Locking System”

“Locking Commands”

“What Commands Do with Locks”

“When to Use Locking Commands”

“Programming Problems”

“Locking Example”
5-4 Developing UniBasic Applications
Understanding the UniData Locking System
In a multiuser system, record and file locks prevent more than one user from
accessing the same record at the same time.
How UniData Locks Work
Unlike physical locks that prevent access, UniBasic locks are advisory only.
Imagine a physical lock as the deadbolt on a hotel room door and an advisory
lock as a “Do Not Disturb” sign. UniBasic locks merely inform others
attempting access that the “locked” entity is in use, while not explicitly
preventing access.
Types of UniData Locks
The UniData lock types are:

Exclusive lock – Checks for and sets locks before updating data.

Shared lock – Used for read-only operations. Prevents other users
(who also use locking commands) from writing to the record while it
is being read.
Exclusive Locks (U)
Records locked with exclusive (U) locks:

Cannot be accessed by any READ U or L command.

Are accessed by commands that do not check for locks and that are
issued from other processes.
The original lock, although ignored, is retained.

Are accessed and modified by any WRITE or DELETE command.

Are released by the commands WRITE and DELETE issued from the
same process.

Cannot be locked by RECORDLOCKU or RECORDLOCKL issued
from another process.
Understanding the UniData Locking System 5-5
Shared Locks (L)
Records locked with shared (L) locks:

Cannot be accessed or locked by READ U commands.

Are accessed by READ L commands.
The original lock is retained.

Are accessed by READ commands that do not check for locks.

Are accessed and changed by any WRITE or DELETE command.
Even though these commands update regardless of lock status, they retain
the original lock.

Are released by the commands WRITE or DELETE issued from the
same process.

Cannot be locked by RECORDLOCKU from another process.
Tip: WRITE and DELETE commands execute regardless of lock status. For this
reason, to prevent data inconsistency, these commands must be preceded by READU
or another command that issues locks.
When UniBasic Finds a Lock
When a locking command encounters a locked file or record, UniBasic does
one of the following (in order of preference):
1.
Executes the LOCKED clause of the UniBasic command, if present.
2.
Beeps periodically while waiting if the ECL command
DEFAULT.LOCKED.ACTION BELL [interval] is set.
3.
Waits for the record to be unlocked, but does not send a signal to the
terminal.
Points to Remember about Locks
Keep in mind the following points about locks:

Anyone can update or delete locked files and records with any
WRITE or DELETE command.
5-6 Developing UniBasic Applications

If you set a lock, only you, or someone signed on with root privileges
on UniData for UNIX or Administrator privileges on UniData for
Windows platforms, can release it.

TRANSACTION COMMIT and TRANSACTION ABORT releases
all locks your process has set within the transaction.
Note: For more information about using transactions to ensure database consistency,
see Chapter 9, “Chapter 9: UniBasic Transaction Processing.”
Understanding the UniData Locking System 5-7
Locking Commands
UniBasic locks are advisory only. To maintain data consistency, all programs
that update the database must check for locks.
Keep the following points in mind when writing UniBasic programs:

Locking Records – UniBasic provides distinct read, write, and delete
commands that check and set locks. These commands all have an
appended U or L in the command name, indicating the type of locks
they set. All other commands ignore locks, operating as if no lock is
set. A chart of locking commands is provided in “What Commands
Do with Locks” on page 5-10.

Locking Files – You can set an advisory lock on an entire file by
issuing a FILELOCK against it. Use the FILEUNLOCK command to
unlock the file.

Locking Resources – Use the LOCK command to set an advisory lock
that reserves a computer resource (such as a printer or tape drive) for
use by a particular program. Unlock the resource with the UNLOCK
command.

Unlocking – You can use the ECL command SUPERRELEASE to
release record and file locks. To unlock resources, use the
CLEAR.LOCKS and SUPERCLEAR.LOCKS commands.
For more information about ECL commands, see the UniData Commands
Reference.
Note: Setting UDT.OPTIONS 35 on prevents locking a record in a called subroutine
that was locked in the calling program. UDT.OPTIONS 78 addresses two situations
in which UniBasic locking is incompatible with Pick®-style locking. For more
information about UDT.OPTIONS, see the UDT.OPTIONS Commands Reference.
Checking Lock Status
To learn about a the lock status of a record, use the UniBasic
RECORDLOCKED function. After calling RECORDLOCKED, you can use
the STATUS function to obtain the user number of the user who set the lock.
5-8 Developing UniBasic Applications
The GETREADU function returns a dynamic array containing detailed
information about all records that have been locked by any UniBasic or ECL
command. The ECL command LIST.READU also provides this same
information (for more information, see GETREADU in the UniBasic
Commands Reference).
The GETQUEUE function returns a dynamic array that contains information
about all records currently locked and waiting to be released. The ECL
command LIST.QUEUE also provides this information (for more
information, see GETQUEUE in the UniBasic Commands Reference).
Locking Commands 5-9
What Commands Do with Locks
The following table identifies how the previously discussed commands react
to UniBasic locks. Note that some commands execute regardless of lock
status, but leave locks in place (Ignores column). Others, regardless of lock
status, release locks and execute (Releases column). Still others delay file
activity until locks are released (Sets Exclusive and Sets Shared columns).
Tip: Include the LOCKED clause in command statements to tell UniData what to do
when it encounters a lock.
Command
Ignores
READ
X
READU
READV
X
X
X
MATREADU
X
READBCKU
X
READFWDU
X
RECORDLOCKU
X
READL
X
READVL
X
MATREADL
X
READBCKL
X
READFWDL
X
RECORDLOCKL
X
WRITE
WRITEU
X
X
What Commands Do With Locks
5-10 Developing UniBasic Applications
Releases
X
READVU
MATREAD
Sets Exclusive Sets Shared
Command
Ignores
WRITEVU
X
MATWRITE
X
MATWRITEU
X
Sets Exclusive Sets Shared
DELETE
DELETEU
Releases
X
X
What Commands Do With Locks (continued)
What Commands Do with Locks 5-11
When to Use Locking Commands
Follow the guidelines in this table when selecting record- and file-locking
commands.
Files
Dynamic
Arrays
Attributes of
Dynamic
Arrays
Dimensioned
Arrays
When to Use
Action
Retrieving
information;
allowing
update while
reading.
Read
without
checking
locks.
READ
READV
MATREAD
Updating
records (must
be preceded
by READU to
ensure data
consistency).
Write;
release
locks.
WRITE
WRITEV
MATWRITE
Reading and
securing
information
for update
(other
programs
must check
for locks).
Read and
set
exclusive
lock.
READU
RECORDLOCKU
READVU
MATREADU
FILELOCK
Record Locking Guidelines
5-12 Developing UniBasic Applications
Files
Dynamic
Arrays
Attributes of
Dynamic
Arrays
Dimensioned
Arrays
When to Use
Action
Updating;
keeping
ownership of
lock (other
programs
must check
for locks).
Write; do
not
release
locks.
WRITEU
WRITEVU
MATWRITEU
Retrieving
information,
but keeping
others from
updating
(using
READU or
WRITEU)
while you are
reading
(other
programs
must check
for locks).
Read and
lock for
read-only.
READL
READVL
MATREADL
Release locks
you placed.
Release
locks.
RELEASE
RELEASE
FILEUNLOCK RELEASE
Record Locking Guidelines (continued)
When to Use Locking Commands 5-13
Programming Problems
This section discusses how UniBasic programming problems may arise, and
provides suggestions on how to minimize the problems.
Causes
A multiuser environment creates opportunities for two programs to access
the same record at the same time, resulting in the following types of
problems:


Inconsistent data caused by the following:

Lost updates – The updates from one program overlay those of
another.

Dirty reads – A program reads a record that is in the process of
being modified by another.

Unrepeatable reads – A program reads the same record twice,
obtaining two different values. This results when the first read
executes while the record is in the process of being updated, and
the second read executes after the record has been updated.
Bottlenecks – Result when one program retains a lock on a file or
record for an inordinate amount of time, causing other programs to
wait.

Deadlocks – Occur when two or more programs are waiting for
each other to release records with no chance of either being
released.
Minimizing Problems
Using record locking consistently, you can eliminate the programming
problems listed earlier while reducing bottlenecks and deadlocks. To do so,
use the following guidelines:

Always check for locks and always lock records before writing.

Minimize the amount of time records are locked.
5-14 Developing UniBasic Applications

Request user input before locking records, not while the records are
locked.

Impose a protocol on the order data items can be updated. For
example, the protocol might let programs lock record B only after
locking record A (to avoid deadlocks).
Note: Deadlocks cause transaction processing to abort one of the deadlocked
transactions. For more information about UniData transactions, see Chapter 9,
“Chapter 9: UniBasic Transaction Processing.”
Programming Problems 5-15
Locking Example
The following program segments are portions of the sample program in
Appendix A, “Appendix A: Sample Program.” The main routine (Main
Logic), drives the program by calling subroutines to perform the main tasks.
Notice that RELEASE is executed as the last step in the LOOP. In almost all
cases, records are released as soon as they are written to the file. However,
some error conditions could cause records to remain locked when control is
returned to the LOOP.
*-------------- Main Logic ----------------------------GOSUB INITIALIZE
LOOP
GOSUB DISPLAY_SCREEN
GOSUB GET_ORDER_NUMBER
UNTIL ORDER_NUMBER[1,1] = 'Q'
GOSUB DISPLAY_DATA
IF RECORD_FOUND THEN GOSUB GET_RECORD_COMMAND
RELEASE
REPEAT
GOSUB EXIT
The following program segment shows the READU command that sets an
exclusive lock on the record that the user has selected for update:
DISPLAY_DATA:
* Display the current information in the desired record. This is
* determined by the number the user entered (ORDER_NUMBER).
READU ORDER.REC FROM ORDERS_FILE,ORDER_NUMBER THEN
* Read with a lock so that no one else can modify it at the same
time.
ORDER_DATE = OCONV(ORDER.REC<1>,"D4/")
ORDER_TIME = OCONV(ORDER.REC<2>,"MT")
CLIENT_NUMBER = ORDER.REC<3> "R#5"
In the next program segment, the WRITE command updates the previous
record read and releases the exclusive lock (WRITE always releases locks that
were set by the same user process):
WRITE_RECORD:
.
.
.
WRITE CLIENT.REC ON CLIENT_FILE,CLIENT_NUMBER
WRITE ORDER.REC ON ORDERS_FILE,ORDER_NUMBER
5-16 Developing UniBasic Applications
Chapter
Chapter 6: Working with Data in
Programs
In This Chapter . . . . . . . . . . . . . .
UniData Arrays. . . . . . . . . . . . . .
Dynamic Arrays . . . . . . . . . . . .
Example . . . . . . . . . . . . . . .
Dimensioned Arrays . . . . . . . . . .
Inquiring about Data . . . . . . . . . .
Type . . . . . . . . . . . . . . . .
Location . . . . . . . . . . . . . . .
Extraction . . . . . . . . . . . . . .
Performing Numeric Operations . . . . . . . .
Arithmetic Operators . . . . . . . . . .
Mathematic Functions . . . . . . . . . .
Formatting and Converting Data. . . . . . . .
ICONV and OCONV: The All-Purpose Functions
Character Format Conversion . . . . . . .
Strings and Dynamic Arrays . . . . . . . .
Numbers . . . . . . . . . . . . . .
Dates and Times . . . . . . . . . . . .
UniBasic Multibyte Support . . . . . . . . .
Modified Functions and Commands . . . . .
Single-Byte Functions . . . . . . . . . .
Multibyte Functions. . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6-3
6-4
6-4
6-8
6-8
6-10
6-10
6-12
6-13
6-15
6-15
6-19
6-20
6-20
6-22
6-22
6-24
6-29
6-34
6-34
6-36
6-37
6
In most cases, you select from a number of similar commands to perform a
specific task. This chapter provides the information you need to select the
correct command or function to manipulate data in UniBasic programs.
6-3
In This Chapter
You have read data into the program from files or accepted input from the
user. You are ready to manipulate the data before writing it to a file or report.
This chapter introduces in the following sections the concepts and
commands you will use to do this manipulation:

“UniData Arrays”

“Inquiring about Data”

“Performing Numeric Operations”

“Formatting and Converting Data”

“UniBasic Multibyte Support”
6-4 Developing UniBasic Applications
UniData Arrays
You generally use an array to store data that you read into a UniBasic
program from UniData hashed files. Multivalued and multi-subvalued
attributes can be loaded into an array in the same way that you load an
individual value into a variable.
The following two types of UniBasic arrays are introduced in Chapter 1,
“Chapter 1: Introduction to UniBasic.”

Dynamic arrays – Separate each attribute, value, and subvalue of a
record by delimiters; they are flexible, allowing easy modification.

Dimensioned arrays – Store data in designated cells of a fixed-sized
matrix; access to data in a dimensioned array is rapid but less
flexible.
Dynamic Arrays
The UniData file structure uses reserved delimiters to partition data items
that are stored sequentially. The same delimiters are used in dynamic arrays
(see “Dynamic Array Delimiters” on page 6-6). As the name implies, the size
of a dynamic array is determined by the data contained in it.
Advantages of Using Dynamic Arrays
Dynamic arrays have the following advantages over dimensioned arrays:

Need not be defined before you use them.

Can be assigned quickly.

Require less memory than dimensioned arrays.

Can be three-dimensional.
6-5
Dynamic Array Delimiters
UniData uses the same delimiters to separate elements in a dynamic array as
those used in a UniData hashed file. UniBasic evaluates the delimiter
symbols and writes the correct ASCII values when you write a record. The
following table illustrates common symbols used to represent delimiters. The
character used to display the delimiter symbols depends on your terminal
emulation.
Delimiter Symbol
Meaning
@variable
(unprintable)
Record mark
@RM
~
Attribute mark
@AM, @FM
}
Value mark
@VM
|
Subvalue mark
@SM
{
Text mark
@TM
Dynamic Array Delimiters
Note: You must use the @variables to refer to delimiters within UniBasic programs,
not the delimiter symbols. The symbols that display for these delimiters could differ
on your system.
Maintaining Dynamic Arrays
The commands you use to load data into an array and write to a file from an
array are introduced in Chapter 4, “Chapter 4: Maintaining Data in Files.”
After data is loaded into the sorted array, you might use the following
procedure to add, replace, insert, and delete data in the array:
1.
Make sure that the data in the array is sorted. Performing a LOCATE
or FIND on unsorted data produces unpredictable results.
2.
Execute the LOCATE or FIND command to determine the location of
one of the following:

Where new data should be inserted in a multivalued attribute.

Of a string that you intend to update or delete within a
multivalued attribute.
6-6 Developing UniBasic Applications
3.
Use the EXTRACT command (or < >) to extract an attribute (value,
multivalue, or multi-subvalue) from the array.
4.
Use one of the following commands as appropriate to insert, delete,
update, or replace an array element:

INSERT, INS

DEL, DELETE (the function)

REPLACE (or < >)
Summary of Dynamic Array Commands
Use the following commands and functions to create and maintain data in a
dynamic array.
Command/Function
Action
FIND, LOCATE
Determines the location of an existing array element. Sets
a pointer to the location where an array element should
be inserted.
EXTRACT, < >
Copies an array element (attribute, value, multivalue, or
multi-subvalue) into a variable.
REPLACE, < >
Inserts or replaces an array element (attribute, value,
multivalue, or multi-subvalue) from a variable.
DEL, DELETE
Deletes an array element.
Dynamic Array Commands
6-7
FIND vs. LOCATE
UniBasic provides two commands that return the location of a string within
a dynamic array: FIND and LOCATE. The following table contrasts the two
commands.
FIND
LOCATE
Returns attribute position.
Returns attribute, value, and subvalue
position.
Expression searched for could be a
variable, matrix element, function, or a
literal string.
Expression searched for must be
numeric or string.
Can limit the search to a specific
attribute, value, or subvalue.
Searches the entire array.
Can begin the search at a specific
attribute, value, or subvalue. Assumes
the records are sorted and justified.
Can find the nth occurrence of a string.
Assumes the records are sorted and
justified.
SETTING returns the position after the
last value if the string is not found.
SETTING returns 0 if the string is not
located.
Must match the entire element in an
array at the level specified.
Locates an element or part of an element
in an array.
FIND vs. LOCATE
For the syntax and use of these commands, see the UniBasic Commands
Reference.
6-8 Developing UniBasic Applications
Example
The following example is taken from the sample program UPDATE_ORDER
in Appendix A, “Appendix A: Sample Program.” In this example, the user
has entered a record to be deleted. The desired attribute (containing the order
line) is read from the CLIENT file and placed in a dynamic array
(ORDER_LINE). Then, the LOCATE command is used to determine the
position of the order in the array. That order is deleted and the attribute is
written back to the file.
DELETE_RECORD:
...
(Assuming the order #'s are on line 12)
READVU ORDER_LINE FROM CLIENT_FILE,CLIENT_NUMBER,12 THEN
LOCATE ORDER_NUMBER IN ORDER_LINE<1> SETTING POSITION THEN
DEL ORDER_LINE<1,POSITION>
END
WRITEV ORDER_LINE ON CLIENT_FILE, CLIENT_NUMBER, 12
END
*
Dimensioned Arrays
A dimensioned array stores data in the elements of a fixed-size matrix. The
array must be declared in your UniBasic program before you can use it. When
you read data in from a UniData hashed file, the delimited data is placed in
the matrix elements in the order they appear in the file. If your array is not
large enough to contain all the data read in, all leftover data is placed in the
zero element, even if more than one attribute is left over.
Advantages of Using Dimensioned Arrays
Dimensioned arrays have the following advantages over dynamic arrays:

Access is faster.

Access time is not affected by the size of the array.

Can contain dynamic arrays.
Maintaining Dimensioned Arrays
Perform the following steps to create and use a dimensioned array:
6-9
1.
Use the DIM or DIMENSION command to create the array and
assign a static dimension.
2.
Manipulate data in the array:

Use the MATREAD, MATREADL, or MATREADU command to
place data in the array from a record.

Use the MATPARSE command to place data in the array from a
dynamic array or string.

Use the MAT command to assign a single value to all elements of
the array or to copy one dimensioned array to another.

Use the MATBUILD command to convert a dimensioned array
to a dynamic array.
3.
Use a statement (for example, X1 = var) to update elements in the
dimensioned array.
4.
Use the MATWRITE, MATWRITEL, or MATWRITEU command to
write array elements to a record.
Summary of Dimensioned Array Commands
Use the following commands and functions to create and maintain data in a
dimensioned array.
Command/Function
Action
DIM
DIMENSION
Creates a dimensioned array. Required before data can be
loaded into the array.
INMAT
Returns the number of elements or the dimension of the
array. Also returns the status of some commands.
MAT
Assigns a value to all elements of a dimensioned array, or
copies one array to another.
MATBUILD
Generates a dynamic array from a dimensioned array.
MATPARSE
Distributes elements in a delimited string or dynamic
array to consecutive elements of a dimensioned array.
dim.array(x,y,z)=var
Extracts a value from an element of a dimensioned array.
Dimensioned Array Commands
6-10 Developing UniBasic Applications
For the syntax and use of these commands and functions, see the UniBasic
Commands Reference.
Inquiring about Data
A number of UniBasic functions provide information about a string or array
without modifying the original data. They fall into three categories:

Type – Functions that qualify the data (such as alpha or numeric and
length).

Location – Functions that return the positions of strings or attributes
in the data.

Extraction – Functions that return values found within the string or
array.
Type
This section describes functions that qualify the data.
Testing for the Null Value
The null value is defined within UniData as an unknown value, as opposed
to an empty string. In UniBasic programs, this unknown value is represented
by @NULL, which is translated into a specific language-dependent (such as
English, German, or Chinese) ASCII code. The empty string, represented by
“” is no longer considered to be null in UniData. Two functions test for the
null value: ISNV and ISNVS.
6-11
Testing for Other Data Types
The following functions provide information about type of data.
Function
Action
ALPHA
Determines whether a string is made up exclusively of
alphabetic characters.
NUM, NUMS
Determines whether a value is numeric.
LEN
Determines the length of a string.
MAXIMUM
Determines the largest numeric element found in an array.
MINIMUM
Determines the smallest numeric element in an array.
COUNTS
Determines the number of times a substring appears within one
or more elements of an array.
COUNT
Determines the number of times a substring appears within a
string or all elements within an array.
DCOUNT
Determines the number of delimited substrings in a string.
ISNV/ISNVS
Tests for the null value (the unknown value, not empty string).
Functions That Inquire about Data Type
For the syntax and use of these functions, see the UniBasic Commands
Reference.
6-12 Developing UniBasic Applications
Location
The following functions return the location of substrings in a string or array.
Function
Action
COL1
Returns the column position before the string located by the FIELD
function.
For an introduction to FIELD, see “Extraction” on page 6-14 in this
chapter.
COL2
Determines the column position after the string located by the
FIELD function.
For an introduction to FIELD, see “Extraction” on page 6-14
following this table.
FIND
Determines the position of a string in a dynamic array.
LOCATE
FINDSTR
Determines the position of a string in a dynamic array element.
INDEX
Determines the starting position of a substring within a string; user
can specify occurrence.
Functions That Inquire about Location
For the syntax and use of these functions, see the UniBasic Commands
Reference.
6-13
Extraction
The following functions return values found in a string or array without
modifying the original data.
Function
Action
[]
Returns a specified number of characters beginning at a
specified location.
OCONV/S T
Returns a contiguous string of a specified length starting
at a specified location.
REMOVE
Successively copies each element of a dynamic array to a
variable. Maintains a pointer to the location in the array
of the last element copied.
EXTRACT
<>
Returns data from an attribute, value, or subvalue in a
dynamic array.
FIELD
Returns a substring or group of substrings. Treats a string
as an array with fields delimited by any specified ASCII
character.
OCONV/S CB
Handles data at the byte level substring when running in
multibyte character mode.
OCONV/S G
Returns one or more strings separated by a specified
delimiter.
OCONV/S L
Returns a string if it is of a specified length or falls within
the specified range of lengths.
OCONV/S R
Returns data values that fall within specified ranges.
OCONV/S P
Returns data values that match a specified pattern
OCONV/S MCA
Extracts all alphabetic characters.
OCONV/S MC/A
Extracts all nonalphabetic characters.
OCONV/S MCN
Extracts all numeric characters (0-9).
Functions That Extract Without Modifying
6-14 Developing UniBasic Applications
Function
Action
OCONV/S MC/N
Extracts nonnumeric characters.
OCONV/S MCB
Extracts all alphabetic and numeric values.
OCONV/S MC/B
Extracts characters that are neither alphabetic nor
numeric.
Functions That Extract Without Modifying (continued)
For the syntax and use of these functions, see the UniBasic Commands
Reference.
6-15
Performing Numeric Operations
You can perform numeric operations in UniBasic with the two tools, which
are described in the following sections:

“Arithmetic Operators”

“Mathematic Functions”
Arithmetic Operators
Arithmetic operators compute values. For example, the following statement
multiplies the value of COST by the value of QUANTITY and stores the
result in the variable PRICE:
PRICE = COST * QUANTITY
The following table lists valid arithmetic and concatenation operators.
Operator
Action
+
Unary plus (same as multiplying by +1).
-
Unary minus (use to change a value to negative: var = -var).
+
Addition.
-
Subtraction.
*
Multiplication.
/
Division.
** or ^
Exponentiation.
:
Concatenation.
Arithmetic Operators
6-16 Developing UniBasic Applications
You can combine arithmetic and concatenation operators to perform special
functions as the following table shows.
Operator
Action
+=
Increments the value of a variable.
LINES += 1 is more efficient than
LINES=LINES+1.
-=
Decrements the value of a variable.
LINES –= 1 is more efficient than
LINES=LINES-1.
*=
Multiplies the value to the left of the operator by the value to the right of
the operator, as in var *= var.
/=
Divides the value to the left of the operator by the value to the right of the
operator, as in var /= var.
:=
Concatenates the value to the left of the operator by the value to the right
of the operator, as in var := var.
Combines Arithmetic and Concatenation Operators
Points to Remember
Keep in mind the following points when you write UniBasic commands that
perform calculations:

Floating point – When you execute an arithmetic operation, UniData
invokes the appropriate host operating system command, which
performs the operation in floating point. When the results are
converted to string format for print or display, the rounding that is
automatically applied can produce unexpected results. Use the ECL
command FLOAT.PRECISION to control when and how rounding is
applied.

Significance – UniData supports up to 14 digits of significance
(although this significance can vary across hardware lines). If you
use more than 14 digits, accuracy is not guaranteed. Use the UniBasic
PRECISION command to set significance for a work session. The
default is 4.
6-17

Data type – Be aware of data type when you execute arithmetic
calculations. Results of the same calculations executed on string and
numeric data occasionally differ.

Null value – The result of calculations on data containing the null
value is the null value. Generally, null is converted to 0 for arithmetic
calculations. However, when UniData encounters the null value as a
divisor, 1 is used to avoid a system error.
Note: The null value is defined within UniData as an unknown value, as opposed to
an empty string.
Before you execute an arithmetic operation, you can set UDT.OPTIONS 10 on to
trim blank spaces around a numeric value. The trim prevents a runtime error. For
more information about UDT.OPTIONS, see the UDT.OPTIONS Commands
Reference.
Arithmetic Operations on Arrays
When you execute arithmetic operations on dynamic arrays, you can apply
them in one of the three following ways:

Apply the operation to just the first element in the array.

Apply the operation to all elements in the array.

Apply an operation to two arrays (for example, multiply one array
by another).
Applying the Operation to the First Element in the Array
Performing an arithmetic operation on an array applies the operation to just
the first element of the array. For example, the program segment:
X = 1:@VM:2:@VM:3
Y = X/100
PRINT Y
prints 0.01y2y3 (where y represents the value mark).
6-18 Developing UniBasic Applications
Applying the Operation to All Elements in the Array
To apply the operation to all elements, include the REUSE command, as in the
following sample program:
VIEWERS = 100:@VM:200:@VM:300
COST = 40
VCOST = VIEWERS*COST
PRINT "1. ":VCOST
VCOST = VIEWERS*REUSE(COST)
PRINT "2. ":VCOST
In the following execution of this program, notice that the first result reflects
application of the arithmetic operation (multiplication) is applied to only the
first element of the array. The REUSE command is used to produce the
second result, causing the arithmetic operation to be applied to each element
in the array (y represents the value mark in this display).
:RUN BP arith
1. 4000y0y0
2. 4000y8000y12000
Applying Operations to Two Dynamic Arrays
When an arithmetic operation is applied to dynamic arrays of different
length, the operation is applied to only the number of element in the shortest
array. The remaining elements are filled with 1 or 0, depending on the
operation performed: for division, elements are filled with 1; for addition,
subtraction, and multiplication, elements are filled with 0. In the following
example program, the first two elements are multiplied, and then the second
two elements are multiplied. The third element is filled with 1, which results
in VCOST = 4000:@VM:200:@VM.
VIEWERS = 100:@VM:200:@VM:300
COST = 40:@VM:1
VCOST = VIEWERS*COST
However, if you apply the REUSE function, the last element in the shorter
array is used to complete the operation on the remaining elements of the
longer array. The following version of the program produces a result of
4000:@VM:200:@VM:300:
VIEWERS = 100:@VM:200:@VM:3
COST = 40:@VM:1
VCOST = VIEWERS*REUSE(COST)
6-19
Mathematic Functions
Numeric functions process variables, constants, tables, or data in a variety of
ways. The following table lists UniBasic mathematical functions.
Function
Action
ABS
Returns the positive value of a number.
ACOS
Returns the arc cosine (inverse cosine) in degrees.
ASIN
Returns the arc sine (inverse sine) in degrees.
ATAN
Returns the arc tangent (inverse tangent) in degrees.
COS
Returns the cosine of a number.
EXP
Returns the base number raised to the power specified. EXP is the
inverse of LN.
INT
Returns the integer value of a number. INT truncates; it does not
round the value.
LN
Returns the natural base logarithm. LN is the inverse of EXP.
MOD
Returns the remainder of the division operation using specified
numbers.
PWR
Raises a number to the power specified.
RND
Returns a random integer from 0 to a specified number -1.
SIN
Returns the sine of a number.
SQRT
Return the square root of a positive numeric expression.
SUM
Totals the numeric values in an attribute according to dynamic
array delimiters. You can enter a range, starting position, and level
for which to calculate the total.
TAN
Returns the tangent of numeric expression.
Mathematic Functions
For the syntax and use of these functions, see the UniBasic Commands
Reference.
Note: Numeric functions on data containing the null value result in the null value.
6-20 Developing UniBasic Applications
Formatting and Converting Data
UniData provides functions to format, modify, and convert the following
types of data:

Characters – Convert between EBCDIC and ASCII for compatibility
with other computer systems. You can also convert from ASCII
character to ASCII code (decimal value).

Strings and dynamic arrays – Justify and format using special
characters.

Numbers – Justify and format using special characters for dollars
and other numeric display formats. Convert among ASCII character,
decimal, binary, octal, and hexadecimal. Modify, justify, scale, and
round numbers.

Dates and times – Convert between external display format and
UniData internal format. Format dates and time for display using
special characters; spell out month or day of the week.
Before getting into the types of data, you should understand the ICONV and
OCONV functions.
ICONV and OCONV: The All-Purpose Functions
The UniBasic functions ICONV and OCONV perform many conversions
between internal and display format, among numbering systems, and others.
ICONV primarily converts to internal format for date and number storage,
and OCONV primarily converts to external display format. Yet both perform
many other conversions, and sometimes both execute the same conversion.
You will see the different options for these commands listed throughout the
following sections.
OCONVS performs on an array the same function that OCONV performs on
a variable. When OCONVS is available, the functions are represented in the
tables that follow as OCONV/S.
Note: When invalid data is submitted to ICONV or OCONV, or an invalid
conversion code is used, these functions return the original value. In BASICTYPE P,
with UDT.OPTIONS 56 on, OCONV returns an empty string if the input value or
conversion code is invalid.
6-21
You can use the following program example to test the many conversion code
options available for ICONV and OCONV:
PROMPT ""
LOOP
PRINT "Input or output [I/O]?" :
INPUT i_or_o
IF i_or_o = "" THEN STOP
PRINT "Conversion code? " :
INPUT y_conv_code
PRINT "Argument? " :
INPUT z_argument
IF OCONV(i_or_o, "MCU")= "O" THEN
PRINT "Executing OCONV"
answer = OCONV(z_argument,y_conv_code)
END ELSE
answer = ICONV(z_argument,y_conv_code)
END
PRINT \"\ : answer : \"\
WHILE 1 DO REPEAT
The following screen example shows how you can test the masked extended
(MD) option for OCONV by running the previous program:
Input or output [I/O]?O
Conversion code? ME2;1
Argument? 123.123
Executing OCONV
"12.31"
Input or output [I/O]?
:
6-22 Developing UniBasic Applications
Character Format Conversion
Use the character conversion functions described in the following table to
convert data to and from formats for other computer systems.
Function
Action
ASCII
Converts the string expression str.expr from EBCDIC to the
corresponding ASCII values. This is the inverse of the EBCDIC
function.
EBCDIC
Converts the string expression str.expr from ASCII to the
corresponding EBCDIC values. This is the inverse of the ASCII
function.
CHAR
Converts from ASCII code (decimal value) to ASCII character.
SEQ
Converts from ASCII character to ASCII code (decimal).
Converting Character Formats
For the syntax and use of these functions, see the UniBasic Commands
Reference.
Strings and Dynamic Arrays
Several formatting functions are available for manipulating data in strings or
dynamic arrays.
Function
Action
FMTS
Formats a multivalued attribute for output, adding special
characters for dollars, dates, or other number formats.
[]
Replaces a string, starting at the position indicated.
:
Concatenates strings.
DOWNCASE
ICONV MCL
OCONV/S MCL
Converts characters to lowercase.
Formatting and Modifying Strings
6-23
Function
Action
UPCASE
ICONV MCU
OCONV/S MCU
Converts characters to uppercase.
OCONV/S MCT
ICONV MCT
Converts to initial caps style. The first character in each word
is uppercase, and the remaining characters are lowercase.
OCONV/S ML
ICONV ML
Left-justifies a string or dynamic array.
CONVERT
Replaces selected characters in a string.
OCONV/S MCC
ICONV MCC
Converts all occurrences of substring x to substring y.
OCONV/S MCP
ICONV MCP
Converts nonprinting characters to tildes (~).
TRIM
Removes all extraneous spaces from a string.
TRIMB
Removes all trailing spaces from a string.
TRIMF
Removes all leading spaces from a string.
OCONV/S S
Converts to SOUNDEX phonetic code.
Formatting and Modifying Strings (continued)
For the syntax and use of these functions, see the UniBasic Commands
Reference.
6-24 Developing UniBasic Applications
In the following example, OCONV MCU converts all characters in the
variable i_or_o to uppercase:
PROMPT ""
LOOP
PRINT "Input or output [I/O]?" :
INPUT i_or_o
IF i_or_o = "" THEN STOP
PRINT "Conversion code? " :
INPUT y_conv_code
PRINT "Argument? " :
INPUT z_argument
IF OCONV(i_or_o, "MCU")= "O" THEN
PRINT "Executing OCONV"
answer = OCONV(z_argument,y_conv_code)
END ELSE
answer = ICONV(z_argument,y_conv_code)
END
PRINT \"\ : answer : \"\
WHILE 1 DO REPEAT
DISPLAY @(10,9+ENTRY):OCONV(ORDER.REC<7,ENTRY>,"MR2$,"):
Numbers
You can round and scale numbers, and you can use special characters to
format numbers for display as dollars or other styles. You can also convert
numbers among decimal, binary, octal, hexadecimal, and ASCII. The
following two sections introduce the conversions available and the functions
for performing these conversions:

Formatting and modifying numbers.

Converting among numbering systems and ASCII.
6-25
Formatting and Modifying Numbers
You can use the functions described in the following table to format, convert,
and modify numbers.
Function
Action
FMT
Right or left justifies, suppresses zeros, and formats using
special characters.
OCONV/S ML
Left justifies, suppresses zeros, scales a specified number of
decimal places, rounds to specified number of decimal
places, and formats using special characters.
ICONV ML
Left justifies, scales a specified number of decimal places,
and rounds to specified number of decimal places.
ICONV MR
OCONV/S MR
Right justifies, scales a specified number of decimal places,
rounds to the number of decimal places indicated, and
suppresses or adds zeros to the left or right.
ICONV MRn
OCONV/S MRn
ICONV MD
OCONV/S MD
Formats for dollars using special characters. For OCONV
only, adds special characters for credit, debit, and other
monetary indicators.
FMT
Specifies length of converted string, fills string with a
specified character if data is shorter than the length
indicated, breaks up the string if it is longer than the length
indicated, centers text, scales a specified number of decimal
places, and adds other special characters.
ICONV MD
Converts decimal to integer, scales a specified number of
decimal places, and rounds to specified number of decimal
places.
OCONV/S MP
OCONV/S MP1
Converts integer to packed decimal.
ICONV MP
Converts packed decimal to integer.
Functions for Formatting and Modifying Numbers
For the syntax and use of these commands, see the UniBasic Commands
Reference.
6-26 Developing UniBasic Applications
The following program segment is taken from the sample program in
Appendix A, “Appendix A: Sample Program.” In this example, OCONV
MR2$ is used to display the price of an item.
ALTER_RECORD:
* Create a new screen, and allow PRICE and ADDRESS to be changed.
*
Initialize variables and draw the screen
NEED.TO.WRITE = 0
DISPLAY @(-1):@(15,5):"Alter ORDER":
DISPLAY @(10,8):"(Press RETURN to leave un-changed)"
DISPLAY @(8,9):"Old Price":@(42,9):"New Price (Enter 2 decimal
places)"
* Change the PRICE field (if desired)
FOR ENTRY = 1 TO NUM_ENTRIES
NEW.PRICE = ""
DISPLAY @(10,9+ENTRY):OCONV(ORDER.REC<7,ENTRY>,"MR2$,"):
INPUT @(45,9+ENTRY):NEW.PRICE
NEW.PRICE = OCONV(NEW.PRICE,"MCN")
IF NEW.PRICE # '' AND NUM(NEW.PRICE) THEN
ORDER.REC<7,ENTRY> = NEW.PRICE
NEED.TO.WRITE = 1
END
NEXT ENTRY
Alternate Numbering Systems and ASCII
You can convert among decimal, ASCII character, binary, octal, and
hexadecimal by using various options of the ICONV and OCONV functions.
Function
From
To
ICONV MB
Binary
Decimal
ICONV MB0C
Binary
ASCII characters
OCONV/S MB
Decimal
Binary
OCONV/S MO
Decimal
Octal
OCONV/S MX
OCONV/S MCD[x]
ICONV MCX[D]
Decimal
Hexadecimal
SEQ
Decimal
ASCII character
ICONV MO
Octal
Decimal
Converting Among Numbering Systems and ASCII
6-27
Function
From
To
ICONV MO0C
Octal
ASCII characters
OCONV/S MCX[D]
OCONV/S MCD
ICONV MX
ICONV MCD[X]
Hexadecimal
Decimal
ICONV MX0C
ICONV HEX
Hexadecimal
ASCII characters
OCONV/S MB0C
ASCII character
Binary
OCONV/S MO0C
ASCII character
Octal
Converting Among Numbering Systems and ASCII (continued)
OCONV conversions from ASCII characters produce multiple-digit numbers
in the target numbering system:

Hexadecimal – One ASCII character produces two hexadecimal
characters. For example, ASCII character 0 is equal to hexadecimal
30.

Octal – One ASCII character produces three octal characters. For
example, ASCII character 0 is equal to octal 060.

Binary–One ASCII character produces eight binary characters. For
example, ASCII character 0 is equal to octal 00110000.
For the syntax and use of these commands, see the UniBasic Commands
Reference.
6-28 Developing UniBasic Applications
Numbering System Examples
The following table provides examples using the ICONV and OCONV
functions to convert among numbering systems and ASCII characters.
Function
From
To
ICONV MB
01100100
100
ICONV MB0C
01100100
d
OCONV/S MB
100
01100100
OCONV/S MO
100
144
OCONV/S MX
100
64
SE
100
d
ICONV MO
144
100
ICONV MO0C
144
d
ICONV MX
64
100
ICONV MX0C
ICONV HEX
64
d
OCONV/S MB0C
d
01100100
OCONV MO0C
d
64
OCONV/S MX0C
OCONV/S HEX
d
64
CHAR
d
100
Examples of Numbering System Conversions
For the syntax and use of these functions, see the UniBasic Commands
Reference.
6-29
The following example demonstrates the use of ICONV to convert from
hexadecimal to ASCII character:
PRINT
PRINT
INPUT
o_num
PRINT
END
"Convert hex to ASCII character."
"Enter hex number to convert: " :
i_num
= ICONV(i_num,'MX0C')
"hex ":i_num:" = ASCII character ": o_num
Here is the output from the previous program:
Convert hex to ASCII character.
Enter hex number to convert: ?64
hex 64 = ASCII character d
Dates and Times
The OCONV and ICONV functions convert dates and times from the internal
format stored in UniData files to a more readable external format suitable for
display or inclusion in a report.
Date Formats
Valid formats for internal storage and external display of dates include the
following:

Internal format – The number of days before or since December 31,
1967.

External format – You can direct UniData to use spaces, forward or
backward slashes, periods, or commas as delimiters when
converting to or from the following output formats:

Standard U.S.: MM/DD/YY

International: DD/MM/YY

Month, Day, Year (month can be a number, three-digit
abbreviation, or can be spelled out; day of the week can be
added)
Note: Store dates in internal format so that you can then perform arithmetic
operations on them.
6-30 Developing UniBasic Applications
Functions for Converting Dates
Use the ICONV and OCONV functions to convert between internal and
external date formats.
Function
Action
ICONV D
Converts from standard U.S. external date format to system
date.
OCONV/S D
Converts from internal system date to a specified external date
format.
ICONV MT
Converts from external time format to system time.
OCONV/S MT
Converts from internal system time to external time format.
Functions for Converting Data
For the syntax and use of these functions, see the UniBasic Commands
Reference.
Points to Remember When Entering Dates
If you are not aware of the following defaults, conversions into internal date
format (using ICONV) might not produce the effects you expect:

If you do not enter a year, UniData defaults to the current year. For
example, if you enter 12/1 and the year is 1996, the system stores
10563 (12/1/96).

If you enter a number between 1 and 12, UniData defaults to the first
day of the month and the current year. For example, if this year is
1996 and you enter 12 only as the date, UniData stores 10563
(12/1/96).
Note: You can invalidate all date input of less than six digits by setting
UDT.OPTIONS 82 on.
Century Pivot Date
Prior to UniData 5.2, any 2-digit year entered from 01 through 29 defaulted
to the next century. For example, UniData interpreted 12/31/29 as December
31, 2029. 1930 was the century pivot date.
6-31
You can now set your own century pivot date. The century pivot date only
applies to the ICONV function when using the D2 format, not D3 or D4.
The udtconfig file, located in /usr/ud73/include on UniData for UNIX or
\udthome\include on UniData for Windows Platforms, now contains the
CENTURY_PIVOT parameter, with a default value of 1930. You can change
this value in one of the following ways:

Enter a 4-digit year. UniData interprets this first 2 digits as the
century, and the last 2 digits as the year. The last 2 digits of the year
you enter, through 99, are considered to be in the century you specify.
0, through the year you entered -1, are considered to be in the next
century. For example, if the century pivot date is 1950, years 50
through 99 are in the 1900’s, and years 0 through 49 are in the 2000’s.
If the century pivot date is 2000, 0 through 99 are in the 2000’s.

Enter a code in the form of nn, indicating that the next nn years are in
the next century. UniData calculates the century pivot date as:
current_year - (100 - nn)
For example, if the current year is 2000 and the century pivot code is
50, the century pivot date is 1950 (2000 - (100 - 50)).
The CENTURY.PIVOT ECL command overrides the system-wide century
pivot date defined in the udtconfig file.
Syntax:
CENTURY.PIVOT [4-digit year | nn]
If you enter CENTURY.PIVOT with no options, UniData returns the current
setting for the century pivot date.
You can also use the UniBasic CENTURY.PIVOT function to set the century
pivot date.
Syntax:
CENTURY.PIVOT(4-digit year | nn)
Time Formats
Valid formats for internal and external storage and display of time include
the following:
6-32 Developing UniBasic Applications

Internal format – The number of seconds since midnight.

External format – You can direct UniData to use spaces or special
characters such as slash, period, comma, or asterisk as delimiters
when converting to the following output format: HH MM [SS].
Date and Time Examples
The following program demonstrates conversion into internal date format.
First, the user is prompted for date, and that date is converted to internal
format. On the first print line, the date is displayed both as entered and in
internal format. The next print line displays the internal date converted back
to external format with a slash between the two-character month, day, and
year.
PRINT "Enter date to be converted to internal format:"
INPUT i_date
o_date = ICONV(i_date,"D")
PRINT "Date ":i_date:" was converted to ":o_date
PRINT o_date:" converts back to ":OCONV(o_date,"D2/")
END
The following results if the year is 1995 and you enter 3 in response to the
prompt:
Enter date to be converted to internal format:
?3
Date 3 was converted to 9922
9922 converts back to 03/01/95
The following table shows several date and time conversions.
Function
Input Value
Output Format
OCONV/S D2/
1
1/1/68
OCONV/S D4*
10300
03*13*1996
ICONV MT
12:00
43260
ICONV D
12/1/28
22251
OCONV/S
DDMY,A,Z4
22251
01 December 2028
OCONV/S MT
82800
23:00
Date and Time Conversion Examples
6-33
Note: Dates default to U.S. format. For European format, use the ECL command
DATE.FORMAT, which is described in the UniData Commands Reference.
6-34 Developing UniBasic Applications
UniBasic Multibyte Support
This section summarizes the support of languages that require multiple bytes
to represent a single character.

Modified Functions and Commands – Support multibyte languages.

Single-Byte Functions – Support only single-byte languages.

Multibyte Functions – Designed specifically for multibyte language
support.
Multibyte languages require that strings be recognized by character rather
than byte. These changes do not affect functionality for single-byte
languages, because, for these languages, one byte represents each character.
Modified Functions and Commands
The following table lists the UniBasic functions that have been modified to
analyze strings by character instead of byte.
Function
Comment
CHANGE
Replaces all occurrences of a substring with a string.
CHAR
Changes a numeric expression to its ASCII (American Standard
Code for Information Interchange) character string equivalent.
CHARS
Changes a numeric value in an array to its ASCII character
equivalent.
COL1
Returns the column position preceding a substring found by the
FIELD function.
COL2
Returns the column position following a substring found by the
FIELD function.
CONVERT
Converts a single- or multibyte character string to another
character string.
COUNT
Returns the number of times a substring appears within a string.
UniBasic Character Recognition Functions
6-35
Function
Comment
COUNTS
Returns the number of times a substring appears within each
element of an array.
DCOUNT
Counts delimited substrings.
FIELD
Locates and returns a substring or group of substrings; treats a
string as an array, with fields delimited by any ASCII character.
See also COL1 and COL2 in this table.
FINDSTR
Determines the position of a substring in a dynamic array.
GET
Receives unprompted input from an attached line.
ICONV
Converts string or numeric data to internal representation format
based on conversion codes.
INDEX
Returns the starting position of a specified occurrence of a
substring within a string.
INPUT
Requests data from an input queue or the terminal screen.
INPUT @
Places the cursor at a specific location on the terminal screen and
requests input from the user. See the note on mask parameters
later in this chapter.
LEN
Returns the length of an expression.
LENS
Returns the length of the values within each element of a
dynamic array.
MATCH
Determines if a variable matches a specific pattern of characters
or numbers. Only single-byte characters are considered to be
alphabetic or numeric.
MATCHFIELD Returns a substring that matches a pattern or literal. Only singlebyte characters are considered to be alphabetic or numeric.
OCONV
Converts string or numeric data from internal format to display
format based on conversion codes.
REMOVE
Searches a dynamic array for system delimiters, then assigns the
delimiter and following array element to a variable. Recognizes
col.pos as a character position.
SEQ
Converts a single character to its ASCII code value.
UniBasic Character Recognition Functions (continued)
6-36 Developing UniBasic Applications
Function
Comment
SEQS
Converts the first character in each element of a dynamic array to
its ASCII code value.
SUBSTRINGS
Extracts strings from elements within a dynamic array.
SWAP
Replaces all occurrences of one substring with a second
substring.
UniBasic Character Recognition Functions (continued)
Note: Mask – Multibyte characters could be masked for display purposes. However,
each double-byte character uses two display characters. So, four double-byte
characters displayed in the mask "###-###" prints as "XX -XX " (two characters,
a space, a hyphen, two characters, and a space).
Single-Byte Functions
The following table lists the functions that might not work properly when
used with a multibyte character set.
Function
Comment
ALPHA
Because UniBasic does not recognize multibyte characters as
alphabetic, returns 0 instead of converting them.
DOWNCASE
Converts only single-byte characters to lowercase.
FMT
Formats an expression for display. The length parameter defines
the number of display characters. The fill character must be
single-byte, and the justification option does not work for
multibyte languages. See the note on mask following this table.
NUM
Determines if an expression is numeric. Returns 0 for multibyte
characters.
NUMS
Determines, for each element of an array, if that element is
numeric. Returns 0 if the dynamic array element contains
multibyte characters.
Single-Byte Functions
6-37
Function
Comment
PROMPT
Sets the prompt displayed by the INPUT command to a specified
single-byte character. You cannot prompt with a multibyte
character.
SOUNDEX
Converts an expression into a phonetic code. Can return
unpredictable results with multibyte characters.
UPCASE
Converts only single-byte characters to uppercase.
Single-Byte Functions (continued)
Note: Mask – Multibyte characters can be masked for display purposes. However,
each double-byte character uses two display characters. So, four double-byte
characters displayed in the mask “###-###” prints as “XX -XX ” (two characters,
space, hyphen, two characters, and a space).
Multibyte Functions
The following UniBasic functions are designed for use with multibyte
character sets.
Function
Description
BYTELEN
Returns the number of bytes in a string.
CHARLEN
Returns the number of characters in a string.
DISPLAYWIDTH
Returns the number of bytes required to display a string
expression.
ISMB
Returns a 0 for single-byte language setting, 1 for
multibyte language setting.
LEN
Returns the number of bytes in a string.
MBLEN
Returns the number of bytes required to display a
character.
UniBasic Functions for Multibyte Languages
For examples of these commands in multibyte languages, see UniData
International.
6-38 Developing UniBasic Applications
Chapter
Chapter 7: External Interaction
In This Chapter . . . . . . . . . . . .
Interacting with Other UniBasic Programs . .
Sharing Data . . . . . . . . . . .
Including Code at Compilation . . . . .
Interacting with UniData . . . . . . . .
Executing Virtual Attributes . . . . . .
Executing ECL Statements . . . . . .
Executing UniData SQL Statements . . .
Defining and Using Programs and Functions
Writing User Exits . . . . . . . . . . .
What Are User Exits? . . . . . . . .
Calling a User Exit from UniBasic . . . .
Calling a User Exit from a Virtual Attribute
Calling a User Exit from a Proc . . . . .
Parameters in User Exits . . . . . . .
Interacting with Hardware I/O Devices . . .
Display Terminals . . . . . . . . .
Printers . . . . . . . . . . . . .
Tape Drives. . . . . . . . . . . .
Interacting with the Operating System . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
7-3
7-4
7-4
7-8
7-9
7-9
7-9
7-10
7-11
7-12
7-12
7-12
7-13
7-14
7-14
7-16
7-16
7-21
7-22
7-24
This chapter introduces the concepts and commands that enable you to write
a UniBasic program that communicates with external software and
hardware.
7-3
In This Chapter
UniBasic program interaction with software and hardware is presented in the
following sections:

“Interacting with Other UniBasic Programs”

“Interacting with UniData”

“Writing User Exits”

“Interacting with Hardware I/O Devices”

“Interacting with the Operating System”
For more information about running UniBasic programs from ECL, see the
UniData Commands Reference. For more information about using C programs
(for UNIX) or external programs (for Windows platforms) with UniData, see
Chapter 8, “Chapter 8: Linking Programs with UniData.” For more information about the interaction of UniBasic with paragraphs, menus, and
virtual attributes, see the Using UniData.
7-4 Developing UniBasic Applications
Interacting with Other UniBasic Programs
You can interact with other UniBasic programs by sharing data, by
incorporating code at compilation, and by calling programs. These
commands and concepts are presented in the following parts of this section:

Sharing Data

Including Code at Compilation
Sharing Data
When you design an application, you can divide processing into several
programs that use the same variables. You can use one of the following
vehicles for passing variables among UniBasic programs:

Program Arguments (using @SENTENCE)

Subroutine Call Arguments

COMMON

Data Stacks
Program Arguments
You can execute a UniBasic program from ECL, from a paragraph, or from
another UniBasic program. The statement that executes the program is stored
in @SENTENCE. You can retrieve arguments that were included in this
statement by parsing the contents of @SENTENCE.
Tip: Remember that if you include EXECUTE or CHAIN in the program, the value
of @SENTENCE will be updated.
Interacting with Other UniBasic Programs 7-5
Subroutine Call Arguments
When only few variables are being passed, or only two programs share them,
passing variables through arguments is the most expedient method of
sharing data. The variables must be defined the same in the calling and called
routines, and the called program or subroutine must provide for the
arguments in the SUBROUTINE statement. The CALL command transfers
processing to the called program and lists the arguments to be passed. The
SUBROUTINE command begins a cataloged subroutine and lists the
arguments to be received.
Regardless of the BASICTYPE you use to compile, if you pass the same
variable twice, UniBasic updates the variable values for both occurrences of
the variable whenever you change either one. For this reason, both xx and yy
always contain the same value in the following program:
test program:
tv1 = "howdy"
CALL test.sub(tv1,tv1)
test.sub program:
SUBROUTINE test.sub(xx,yy)
xx = xx : "!"
yy = yy : "?"
PRINT "xx = ":xx
PRINT "yy = ":yy
RETURN
In the preceding subroutine, both variables, xx and yy refer to the variable
tv1. So when xx is updated by being concatenated with !, yy is also updated,
and when yy, which now contains howdy!, is updated by being concatenated
with ?, xx is also updated. The output for this program follows:
xx = howdy!?
yy = howdy!?
7-6 Developing UniBasic Applications
COMMON
When you are sharing groups of variables among multiple programs, you
can use the single unnamed common or multiple named common areas to
pass a stack or stacks of variables. Each program that needs to access the
variables must define the named or unnamed common area(s), by executing
the COMMON command, before accessing them. You can most efficiently
accomplish this by defining common in a file and including that file in
programs at compile time (with $INCLUDE).
Tip: When possible, pass parameters in SUBROUTINE arguments rather than
through common areas. This makes it easier to tell which subroutine uses which
arguments.
Warning: Trying to access variables in common areas before defining them with the
COMMON command could result in a core dump.
Data Stacks
You can use a data stack to pass data to INPUT statements in called processes
or UniBasic programs. The stack is available until the user process ends,
returning the user to the ECL prompt.
Tip: The DATA statement, which loads the data stack, can also be placed in a
UniData paragraph.
The data stack is managed as follows:

The stack is stored in the @DATA system variable.

The stack is loaded by the DATA command.

Items in the stack are delimited by CR (carriage return).

The stack is built and depleted on a first in first out basis. After
UniData exhausts all the responses in the data stack, it reverts to the
terminal for user input.
Tip: To suppress output to the terminal, execute the UniBasic HUSH command
immediately before the INPUT command that reads the stack; otherwise, the INPUT
prompt and data stack response are displayed.
Interacting with Other UniBasic Programs 7-7
Examples of Passing Data Through a Data Stack
In this example, a program prompts for user input and displays information
requested by the user on a display terminal; another program prints the same
information in hardcopy. Both programs require input to be loaded into the
variables ACTIVITY.END.DATE and REGION. The first program CHAINs to
the second program, passing the values in a data stack rather than prompting
the user for the same data twice. The first program contains the following
code:
PRINT "Enter the last date for activity"
INPUT ACT.END.DATE
* check for valid dates and convert to internal form
PRINT "Enter Region"
INPUT REGION
* check for valid regions
* display data
PRINT "Do you want a hardcopy?"
INPUT ANS
IF ANS = "Y" THEN
DATA ACT.END.DATE
DATA REGION
CHAIN "REGION.PRINT"; * execute REGION.PRINT program
END
.
.
.
When the INPUT statements in REGION.PRINT are executed, UniBasic fills
them from the data stack.
The following program segment, which is taken from REGION.PRINT,
prompts for the passed data. The presence of the data stack does not inhibit
the prompt; it displays on the screen followed by the response from the data
stack.
PRINT "Enter the last date for activity"
INPUT ACT.END.DATE
* check for valid dates and convert to internal form
PRINT "Enter Region"
INPUT REGION
* check for valid regions
Note: You can clear the stack created by DATA statements using either the UniBasic
or ECL CLEARDATA commands.
7-8 Developing UniBasic Applications
Including Code at Compilation
You can insert a UniBasic program or program segment during compilation
with the UniBasic commands $INCLUDE and $INSERT. You might use these
commands to include stack routines that perform frequently used
operations, such as EQUATE and DEFFUN.
Executing UniBasic Programs and Subroutines
The following table lists commands that execute other UniBasic programs
and subroutines.
Command
Action
CALL
Transfers control to an external subroutine. When UniBasic
encounters a RETURN statement in the subroutine, it returns
control to the calling program.
CHAIN
Terminates the current UniBasic program and executes another
program. A common use of CHAIN is to execute a cataloged
program.
ENTER
Transfers control to a cataloged program.
EXECUTE/
PERFORM
Executes an ECL or UniData SQL command. (You can
EXECUTE “RUN program.)
Executing UniBasic Programs and Subroutines
Note: UDT.OPTIONS 6 and 40 determine where control is passed on RETURN;
UDT.OPTIONS 11 and 27 clear the data stack. For more information, see the
UDT.OPTIONS Commands Reference.
Interacting with Other UniBasic Programs 7-9
Interacting with UniData
Interaction with other UniBasic programs and UniData products is presented
in the following sections:

“Executing Virtual Attributes”

“Executing ECL Statements”

“Executing UniData SQL Statements”

“Defining and Using Programs and Functions”
Note: UniData stores and makes available information about the system, the user
process, and the UniBasic program being executed in @variables. Some are available
through any UniData or 4-GL tool; others are available only through UniBasic. For
a complete list of UniData @variables, see Using UniData. The UniBasic @variables
are listed in the UniBasic Commands Reference.
Executing Virtual Attributes
Virtual attributes are dictionary records that contain executable code. They
are explained in Using UniData.
You can execute a virtual attribute from within a UniBasic program with the
UniBasic CALCULATE or {} command, or the ITYPE function. You must first
open the dictionary containing the virtual attribute to @DICT and read a
record from the corresponding data file into @RECORD.
Executing ECL Statements
You can execute ECL commands within a UniBasic program. Any entry valid
at the ECL prompt can follow an execute command.
7-10 Developing UniBasic Applications
The following table lists the UniBasic commands for executing UniData
commands.
Command
Action
EXECUTE
PERFORM
Executes an ECL command. Returns output and error messages
in variables if specified.
Use the CAPTURING clause to save output of the executed
command in an dynamic array. Use the RETURNING clause to
save error messages.
MDPERFORM
Executes a UniData command, and transfers a select list if
specified. Also returns output and error messages in variables if
specified.
UDTEXECUTE
Executes an ECL command in BASICTYPE U regardless of the
BASICTYPE the program was compiled in. Also returns output
and error messages in variables if specified.
Execute Commands
The following UniBasic statement executes the ECL command LIST.READU:
EXECUTE "LIST.READU"
The next UniBasic statement executes a UniQuery statement:
EXECUTE "SSELECT CUSTOMER WITH STATE='CO' BY ZIP"
For more information about ECL, see Using UniData.
Executing UniData SQL Statements
You can use the EXECUTESQL, EXECUTE, and PERFORM commands to
execute UniData SQL statements. If you need to return selected items for
subsequent processing by the program, include a TO clause and a variable in
which UniData SQL can return selected items.
Interacting with UniData 7-11
In the following example, the variable SQL_INPUT is provided for the return
of NAME, ADDR, and CITY:
EXECUTESQL "SELECT NAME,ADDRESS,CITY FROM CLIENTS TO SQL_INPUT;"
DONE=0
LOOP
PRINT "DONE = ":DONE
READNEXTTUPLE CLIENT.REC FROM 'SQL_INPUT' ELSE DONE = 1
UNTIL DONE
CONVERT @AM TO " " IN CLIENT.REC
CONVERT @VM TO "," IN CLIENT.REC
PRINT "CLIENT.REC = ":CLIENT.REC
REPEAT
END
Note: You must use the UniBasic command CLEARSQL to clear active UniData
SQL variables created with EXECUTESQL...TO statements.
Defining and Using Programs and Functions
Within a UniBasic program, you can use the commands described in the
following table to write and call other programs and functions.
Command/Function
Action
PROGRAM
Provided for backward compatibility. Defines a program.
FUNCTION
Creates a user-written function that accepts passed
arguments and can return values.
DEFFUN
Declares a user-written function that was defined by the
UniBasic FUNCTION command.
Commands for Defining and Using Programs and Functions
7-12 Developing UniBasic Applications
Writing User Exits
An explanation of user exits and the procedures for writing and calling them
are explained in the following subsections:

“What Are User Exits?”

“Calling a User Exit from UniBasic”

“Calling a User Exit from a Virtual Attribute”

“Calling a User Exit from a Proc”

“Parameters in User Exits”
What Are User Exits?
Some RDBMS systems employ “user exits” to convert and format data.
UniBasic provides many of these conversions in the UniBasic ICONV and
OCONV functions. However, for backward compatibility, UniData supports
user exits, which consist of a calling statement and a UniData-provided or
user-defined UniBasic subroutine.
You can call user exits from UniBasic, UniQuery, and UniData paragraphs.
For backward compatibility, UniData supports user exits from Procs. This
section focuses on calling user exits from UniBasic programs and calling
UniBasic subroutines that serve as user exit functions.
Tip: You might find that the operations provided by user exits can be performed more
efficiently by a UniData @variable, a UniBasic command or function, or a UniData
paragraph.
Calling a User Exit from UniBasic
Perform the following steps to call a user exit from a UniBasic program. The
called user exit can be UniData-defined or user-defined.
Writing User Exits 7-13
1.
Select a UniData user exit that performs the conversion you want.
For a list of user exit conversion routines, see Using UniData.
or
Write a UniBasic subroutine to perform the conversion. The first
command in the subroutine must be the following:
Syntax:
SUBROUTINE Uuser.exit(ret.val, status, input.val, type)
2.
Write the user exit call in your UniBasic program. Regardless of the
type of user exit you are calling, use the following syntax:
Syntax:
ret.val = {ICONV | OCONV}(input.val,"Uuser.exit")
The parameters used in these commands are explained in “Parameters in
User Exits” on page 7-15.
Calling a User Exit from a Virtual Attribute
Perform the following steps to write a virtual attribute that calls a UniBasic
user exit or UniData-defined user exit:
1.
Select a UniData user exit that performs the conversion you want.
For a list of user exit conversion routines, see Using UniData.
or
Write a UniBasic subroutine to perform the conversion. The first
command in the subroutine must be the following:
SUBROUTINE Uuser.exit(ret.val, status, input.val, type)
2.
Write a virtual attribute that calls the user exit. The syntax that you
use depends on whether you are converting single or
multivalued/multi-subvalued attributes:

Singlevalued:
{ICONV | OCONV}(input.val,"Uuser.exit")

Multivalued/Multi-subvalued:
SUBR(’-{ICONVS | OCONVS}’,input.val,"Uuser.exit")
The parameters used in these commands are explained in “Parameters in
User Exits” on page 7-15.
7-14 Developing UniBasic Applications
Calling a User Exit from a Proc
Perform the following steps to write a Proc that calls a UniBasic- or UniDatadefined user exit:
1.
Select a UniData user exit that performs the conversion you want.
For a list of user exit conversion routines, see Using UniData.
or
Write a UniBasic subroutine to perform the conversion using the
following syntax:
Syntax:
SUBROUTINE Uuser.exit(proc,cib,pib,sib,ibp,cob,pob,sob)
2.
Write a statement in a Proc that calls the user exit.
Syntax:
Uuser.exit
param1 [,param2]...[,]...
Parameters in User Exits
The following table describes each parameter of the syntax.
Parameter
Description
Uuser.exit
The name of the UniData user exit or user-defined UniBasic
subroutine that performs the conversion.
ret.val
The string or value UniData returns to the calling program as a
result of the conversion function.
status
The status of the conversion; 0 indicates a successful conversion.
input.val
The input string or value to be converted.
type
The type of conversion: 0 for external format to internal format
and 1 for internal format to external format.
proc
The source code of the Proc itself. UniData reads the Proc user exit
statement, then passes it and all subsequent lines as proc.
cib
The current input buffer: 0 for primary and 1 for secondary.
Parameters in User Exits
Writing User Exits 7-15
Parameter
Description
pib
The primary input buffer.
sib
The secondary input buffer.
ibp
The input buffer pointer.
cob
The current output buffer: 0 for primary and 1 for secondary.
pob
The primary output buffer.
sob
The secondary output buffer.
Parameters in User Exits (continued)
7-16 Developing UniBasic Applications
Interacting with Hardware I/O Devices
When programming in UniBasic, you can direct input and output (I/O)
devices such as display terminals, printers, and tape drives. These
commands are introduced in the following subsections:

“Display Terminals”

“Printers”

“Tape Drives”
Display Terminals
Input Commands
Input commands request data from a user’s keyboard or a data stack and
store the result in a variable for use in a UniBasic program.
For more information about data stacks, see Using UniData.
UniBasic provides the following commands for accepting and acting on user
input from the terminal.
Command
Action
INPUT
Requests data from a user terminal or a data stack.
INPUT @
Requests data from a user terminal or a data stack, determines the
cursor position and display a mask on the terminal, and
determines the characteristics of the data received.
INPUTNULL
Allows a single character to be defined as the null character in an
INPUT @ statement.
INPUTERR
Prints a user-defined error message on the bottom line of the
display terminal and clears the error message when UniData
receives the next input.
Input Commands
Interacting with Hardware I/O Devices 7-17
Command
Action
INPUTIF
Assigns data from the type-ahead buffer to a variable.
CLEARINPUT
INPUTCLEAR
Clears the type-ahead buffer.
INPUTTRAP
Conditionally branches to a specific statement label when specific
characters are entered.
Input Commands (continued)
Tip: You can fill input statements from the data stack rather than from user input.
For example, the following program segment loads the string “FINISHED” in the
data stack and then loads it in the variable CODE.
DATA "FINISHED"
INPUT CODE
UniBasic allows up to 500 elements in the data stack. For more information
about the data stack, see “Sharing Data” on page 7-5, or see the DATA
command in the UniBasic Commands Reference.
The UDT.OPTIONS described in the following table affect the input
commands.
UDT.OPTION
Description When ON
12
If you use INPUT var, expr with a data stack and an element in the
data stack is shorter than expr, UniData retains unused characters
and they are available for subsequent input statements.
18
When UniData passes data to a UniBasic program to fill an input
statement, UniData suppresses the echo of the prompt character and
the data.
65
If you exceed the field length during an INPUT command, the
terminal beeps.
UDT.OPTIONs for Input
Note: For more information about UDT.OPTIONS, see the UDT.OPTIONS
Commands Reference.
7-18 Developing UniBasic Applications
More Display Terminal Commands and Functions
You can use the following UniBasic commands to manage display terminal
and keyboard I/O.
Terminal Control
Use the commands in the following table to control display on the terminal.
Command
Action
CRT,
DISPLAY
Directs output to the display terminal (CRT) regardless of the setting
of the PRINTER command.
HUSH
Enables or disables terminal output.
PAGE
Prints the current output page.
PRINT
Prints to the display terminal or printer.
PRINT @
Prints to the display terminal at a specific column and row position.
For more about the @ function, see “Enhanced Terminal Capabilities”
on page 7-20.
Terminal Control Commands
Note: Print lines can be up to 256 characters. For more information about LIMITS,
see the UniData Commands Reference.
Keyboard Control
Use the ECHO and BREAK commands to control keyboard input.
Command
Action
ECHO
Enables or disables display of keyboard input on the terminal.
BREAK
Enables or disables the break key. When enabled, the break key
interrupts program execution and places the user at the debugger
prompt.
Keyboard Control Commands
Interacting with Hardware I/O Devices 7-19
Prompt, Headings, and Footings
The commands in the following table control the display of the prompt
character and headings and footings.
Command
Action
PROMPT
Sets the input prompt to a user-supplied one-character string or
empty string.
HEADING
Displays a heading at the top of each page, which can be up to 536
characters.
FOOTING
Displays a footing at the bottom of each page, which can be up to
536 characters.
Prompt, Heading, and Footing Commands
Enhanced Terminal Capabilities
The UniBasic @ function has two forms, which enable you to:

Specify a column and row to position the cursor.

Perform one of 84 terminal actions.
Syntax:
@(col[,row])
@(-num.expr)
For a table of all @ options, see the @ function in the UniBasic Commands
Reference.
Some commonly used options are provided in the following table.
Option
Action
-1
Clears the screen and places the cursor at position 0,0.
-2
Places the cursor at position 0,0.
-3
Clears from cursor to end of screen.
-4
Clears from cursor to end of line.
@ Function Options for Terminal Control
7-20 Developing UniBasic Applications
Option
Action
-19
Sends audible signal. (Terminal beeps.)
-10
Moves cursor up one line.
-17
Move cursor down one line.
@ Function Options for Terminal Control (continued)
Note: Because @ is a function, it must be used within a statement that contains a
command, such as PRINT @ or DISPLAY @.
Examples
The PRINT @ statement prints data at a specific column and row. For
example, the following statement prints three asterisks on row 1 beginning in
column 38:
PRINT @(38,1):'***'
The following command clears the display terminal and positions the cursor
at row 0, column 0:
PRINT @(-1)
In the next example the terminal bell beeps after an error message is printed:
PRINT @(0,23):"Invalid choice. Please re-enter.":@(-19)
Note: UniBasic searches the /usr/lib/terminfo file (for UNIX) or the
udthome\include\udtermcap file (for Windows platforms) to obtain terminal
configuration and interpretation of the @ code. For UNIX, if terminfo does not exist,
UniBasic queries the /etc/termcap file. Obtain terminal emulation settings from your
hardware vendor. For Windows platforms, if udtermcap does not exist, UniBasic
returns a status message stating that it cannot find the file and will default to vt100
terminal emulation.
Interacting with Hardware I/O Devices 7-21
Printers
The following table lists the commands that direct the printer.
Command
Action
PRINT ON
Direct output to a specific printer.
PRINTER CLOSE
Direct output stored in a print file or a print buffer to the print
queue.
PRINTER OFF
Direct output to the display terminal.
PRINTER ON
Direct output to the printer.
Printer Commands
Note: Print lines can be up to 256 characters. For more information about LIMITS,
see the UniData Commands Reference.
The following commands manage both display terminal and printer output:
FOOTING, HEADING, PAGE, and PRINT.
UDT. OPTIONS and Printing
The UDT.OPTIONS in the following table affect the way in which data is
printed.
UDT.OPTION
Command Affected
Effect When On
4
OCONV
When printing dates, formats month in
uppercase.
5
in the absence of
HEADING
Pauses at the bottom of each screen page.
7
n/a
Page feeds or returns to the colon
prompt after the last line of data is
printed rather than printing blank lines
to end of page.
9
EXECUTE, PERFORM
Closes the print job after printing
completes.
UDT.OPTIONS for Printing
7-22 Developing UniBasic Applications
UDT.OPTION
Command Affected
Effect When On
25
UniQuery BREAK.ON
keyword
Prints the break line and suppresses the
preceding blank line, essentially
inhibiting double spacing.
29
OCONV
When printing dates, converts Sunday
to 7 (rather than 0).
32
HEADING, @ function
Retains the heading when the @ function
is used.
34
HEADING, FOOTING
Displays the system date in a specific
format. This option also is affected by
the ECL command DATE.FORMAT.
46
PRINT, CRT DISPLAY,
EXECUTE PERFORM,
INPUT, SLEEP, STOP
Waits for PRINT, INPUT, EXECUTE, or
FLUSH before flushing data to the
system buffer.
48
PRINT, DISPLAY, CRT
When printing right-justified data, does
not break as specified in format. (Will
overwrite another column of data.)
59
PRINT, DISPLAY, CRT
When generating a BSELECT list, does
not create a blank line for a key when the
selected attribute is an empty string.
64
FOOTING
Forces the footing to print on the final
page of a report.
UDT.OPTIONS for Printing (continued)
For detailed information about UDT.OPTIONS, see the UDT.OPTIONS
Commands Reference.
Tape Drives
Before you execute the UniBasic tape drive commands, you must:

Initialize the tape drive with the ECL SETTAPE command.

Attach the tape drive with the ECL T.ATT command.
Other ECL commands also manage the tape drive. For more information, see
Using UniData.
Interacting with Hardware I/O Devices 7-23
UniBasic manages tape drive I/O with the following commands.
Command
Action
READT
Reads the next available record from a tape.
RESIZET
Changes the block size used by the WRITET command when the
block size in one file is not the same as the block size in T.ATT.
REWIND
Rewinds the tape.
WEOF
Writes an EOF (end-of-file) mark to tape.
WRITET
Writes a record onto tape.
Tape Drive Commands
7-24 Developing UniBasic Applications
Interacting with the Operating System
Use the commands in the following table to interact with the operating
system within a UniBasic program.
Command/Function
Action
PCPERFORM
Executes an operating system command within a UniBasic
program.
SYSTEM
Retrieve system-level information set by previous
UniBasic or ECL commands such as SETPTR and TERM.
UniBasic Operating System Commands
For more information about these functions, see the UniBasic Commands
Reference.
The following UniBasic statement executes the UNIX command pwd (print
working directory) and stores the results in the variable MY.LOCATION:
PCPERFORM 'pwd' CAPTURING MY.LOCATION
Interacting with the Operating System 7-25
Chapter
Chapter 8: Linking Programs with
UniData
In This Chapter . . . . . . . . . . . . . . . . . . .
Linking C Programs (UNIX Only) . . . . . . . . . . . .
Before You Begin . . . . . . . . . . . . . . . . .
Calling a C Function from UniBasic with CALLC . . . . .
Calling a UniBasic Subroutine from a C Program with CallBasic
2. Write the C Program . . . . . . . . . . . . . . .
3. Create a makefile . . . . . . . . . . . . . . . .
4. Compile and Link the C Program . . . . . . . . . .
5. Execute the C Program . . . . . . . . . . . . . .
Relinking C Functions to UniData . . . . . . . . . . . .
File Examples . . . . . . . . . . . . . . . . . .
More on make, makeudt, and makeudapi . . . . . . . . .
makeudt and makeudapi . . . . . . . . . . . . . .
make . . . . . . . . . . . . . . . . . . . . .
Troubleshooting CALLC . . . . . . . . . . . . . . .
Linking C Programs (Windows Platforms Only) . . . . . . .
Dynamic Link Libraries (DLLs) and UniData . . . . . . .
CALLC Features and Components. . . . . . . . . . .
Using CALLC . . . . . . . . . . . . . . . . . .
CallBasic Features and Components . . . . . . . . . .
Using CallBasic . . . . . . . . . . . . . . . . .
8-3
8-4
8-4
8-5
8-19
8-21
8-26
8-27
8-27
8-29
8-31
8-37
8-37
8-38
8-40
8-42
8-42
8-43
8-46
8-49
8-53
8
This chapter introduces the concepts and procedures for writing a UniBasic
program that calls or is called by an external C program.
For more information about CALLC, see Administering UniData on UNIX or
the Administering UniData on Windows Platforms.
8-3
In This Chapter
This chapter consists of the following sections:

“Linking C Programs (UNIX Only)”

“Linking C Programs (Windows Platforms Only)”
8-4 Developing UniBasic Applications
Linking C Programs (UNIX Only)
The information in this section applies to the UNIX operating system only. If
you use a Windows platform, see “Linking C Programs (Windows Platforms
Only)” on page 8-44.
Before You Begin
Before you try to set up links between UniData and C programs, review the
following tips.

Before you link a C program for CallBasic
Determine whether someone has previously modified the executable
files in udthome/work in an effort to link C programs to your
UniData executable. If these files have been changed, you must
restore them to their original state. For more information, see
“Relinking C Functions to UniData” on page 8-31.

Before you link a C program for CALLC
When you upgrade from an earlier release, UniData asks whether
you want to overlay the cfuncdef file. If someone installing or
upgrading UniData chose not to overlay this file, the wrong version
could reside in udthome/work. The cfuncdef file printed in the
section “File Examples” on page 8-33 shows the correct version of
this file to use with UniData.

Set up a test environment
You can set up an alternate test environment with a separate udt to
use to test linked programs so you do not disrupt the work of others.
For instructions, see Administering UniData on UNIX.

Log on as root
UniData supplies required files and templates needed to link the C
program or function with UniData. These files and templates reside
in the udthome/work directory, to which only root has read or write
access. You can copy the files to another directory temporarily to
work on them. However, unless you set up a test environment, they
must reside in udthome/work before you run the make, makeudt, or
makeudapi command to set up a CallBasic or CALLC link with
UniData.
Linking C Programs (UNIX Only) 8-5

Provide absolute path
Any time you specify a path in a file, you must specify the absolute
path, such as /usr/ud73/work, rather than using an environment
variable, such as udthome/work.

Regarding triggers
You can call a C function from a UniBasic trigger subroutine. For
information about UniData triggers, see “Chapter 4: Maintaining
Data in Files.”

Define environment variables
The following environment variables must be defined: udthome,
udtlib, and udtbin. For information about setting up environment
variables, see Administering UniData on UNIX.

Run the CallBasic Program from a UniData Account
You must run the CallBasic program from a UniData account. If the
CallBasic program is not executed from a UniData account, a status
of -1 is returned.
You must have the following components to use the CallBasic API:

Development environment
Your system must have a full software development kit. (A base
compiler is not sufficient.) You also will need network libraries if you
are using NFA.
Tip: Consult your host operating system documentation and your
hardware vendor if you have questions about your C development
environment.

C program
You need to code and compile the C application that calls UniBasic.

Function definitions and makefiles
When you install UniData, the file callbas.mk is installed into the
directory udthome/work. You will use this makefile as a template to
build your application with UniData linked into it.
8-6 Developing UniBasic Applications
Calling a C Function from UniBasic with CALLC
Follow the procedure described in this section to link a C function with
UniData so that it can be called by a UniBasic program.
Tip: To save time and frustration, read “Before You Begin” on page 8-5.
Procedure Summary
Here is a summary of the steps you must follow to link a C program with
UniData so that it can be called by a UniBasic program:

Write the C program.

Compile the C program.

Tell UniData about the C program (in cfuncdef_user).

Rebuild the UniData executable (using makeudt or makeudapi).

Write and compile the UniBasic subroutine.

Execute the UniBasic subroutine.
1.
Write the C Program
The first step in creating a C function that you will call from a
UniBasic program is to write the C program with the standard
compiler for your platform and operating system.
Guidelines for Writing C Programs
You might find the following guidelines helpful when writing the C
program.

Naming Variables
Avoid naming variables or functions with the prefix U and an
underscore (U_), such as U_unit and U_errout. UniData uses U_ as
an identifier for variable names and functions.

Passing Arguments
You cannot pass more than 22 arguments. Each argument is limited
to 255 characters. Include the bstring header to pass binary data. For
more information, see the “Chapter 8: Linking Programs with UniData” table later in this chapter.
Linking C Programs (UNIX Only) 8-7

Returning Arguments
The maximum number of bytes allowed in a function return is 256. If
you increase the size of a variable of data type bstring, you must free
the original memory assignment and reallocate it and reassign the
length to avoid a memory leak. For more information, see the “Chapter 8: Linking Programs with UniData” table later in this chapter.

Passing Binary Data
Include the bstring header file to pass a binary string to or from a C
program, especially when that data could contain imbedded ASCII
character 0 (which C interprets as a line terminator). To do this, the C
function must include the header file callc_bstr.h, which contains the
definition of bstring, and set the returning string length (for example,
retbuf->len and out->len). A sample C program is provided in
“Passing bstring-Type Data” on page 8-17.

Displaying Error Messages
To display error messages, use the UniBasic C function U_errout.
U_errout has the same syntax as U_preprint, except the variable
U_unit is replaced by 0. U_erroutoutput goes to errout whereas
U_preprint output goes to stdout.
Syntax:
U_errout(0,"error message from the routine, value is %d",value);

Printing – To maintain screen integrity and I/O redirection, use the
UniBasic C function U_preprint instead of the C function printf. The
U_preprint function refreshes the screen, enabling the C subroutine
to properly manage screen I/O. This function follows syntax similar
to printf.
Syntax:
U_preprint pattern,arg1,arg2...
For example:
extern int U_unit;
.
.
.
U_preprint(U_unit,"message from the routine, value is
%d",value);
8-8 Developing UniBasic Applications

Ending the C Program
Do not use exit to end the C program. Instead, use U_done, which
performs various cleanup tasks, and then terminates the C program.

C Program Example
The following C function (c_example) emulates the UniBasic
GETENV function. Both retrieve the value of the specified UNIX
environment variable using the UNIX getenv system call.
char *c_example(envar)
char *envar;
{
char *getenv();
static char buf[100];
char *pathlist;
sprintf (buf,"%s",getenv(envar));
return(buf);
/* return string to UniData */
}
2.

Compile the C Program
Use the UNIX cc command with the -c option to produce the object
code. Verify the following before you proceed to the next step:

The C program does not contain the main() function.

The C program compiles cleanly.
For our example, you enter the following commands at the
UNIX shell prompt:
$ cc -c c_example.c

Copy the object code to the udthome/work directory:
$ cp c_example.o $UDTHOME/work
To place the program in udthome/work, you must have write
permission to that directory. For assistance, see your system
administrator.
3.
Tell UniData about the C Program
Linking C Programs (UNIX Only) 8-9

Check for required files in udthome/work.
To proceed, the C program must reside in the udthome/work
directory, along with the makefile (c_example.mk in this example)
and the function definition file (cfuncdef), and the C functions that
the makeudt or makeudapi utility uses (funchead.o interfunc.o
callcf.o, and efs_init.o).

Look at cfuncdef.
This file, placed in udthome/work at installation, must not refer to
any UniData or site-specific C programs. It could contain reference to
site-specific libraries.
Some earlier releases of UniData included some UniData functions
in cfuncdef. If the person installing UniData Release 7.3 or upgrading
from an earlier version chose not to overlay this file, you could have
the wrong version. Also, if someone has previously linked C
programs to UniData for CALLC, cfuncdef could contain reference
to these programs. For an example of an “empty” cfuncdef file, see
the template in “File Examples” on page 8-33.
8-10 Developing UniBasic Applications

Create cfuncdef_user.
After making sure that cfuncdef is free of references to other C
programs, make a copy of it, and call it cfuncdef_user. The file should
contain the following three lines:

$$FUN
(you put the C function definition here)

$$OBJ
(you put the object file here)

$$LIB
(you put the library path here if the C function is not stored in
udtlib)
cfuncdef_user File Example
The following example shows a version of the cfuncdef_user file that
has been modified to set up the function c_example on a Hewlett
Packard computer. The next few steps refer to this example.
/* this is a test for adding a C function to the*/
/* Runtime version of UniData. */
/* C function declaration format:
function-name:return-type:number-of-argument:arg1,arg2,...argn */
$$FUN /* beginning of C function */
c_example:string:1:string
$$OBJ /* object files or .o file comes here */
/usr/ud73/work/c_example.o
$$LIB /* library comes here */
Linking C Programs (UNIX Only) 8-11

Add the function definition to cfuncdef_user.
The function definition line follows $$FUN.
Syntax:
fun_name:rtn_data_type:num_arg:arg_data_type
A colon separates each element, and a comma separates multiple
arguments. You can enter more than one function definition, each on
a separate line.
The following table lists the function definition arguments.
Argument
Description
Value in Example
fun_name
The name of the C program as it will be
called from UniBasic programs.
c_example
rtn_data_type
The data type of the return value.
string
num_arg
The number of arguments.
1
arg_data_type
The data type of the arguments.
string
cfuncdef Function Definition
The following table lists valid data types for arguments and return
values. .
Data Type
Description
int
An integer. A 32-bit signed long word.
short
A 16-bit signed long word.
long
A 32-bit signed long word (same as int).
double
A 64-bit floating point value.
float
A 32-bit floating point value.
char
A signed byte.
Data Types for UNIX C Definition
8-12 Developing UniBasic Applications
Data Type
Description
string
A pointer to a character string terminated by NULL (0) in a 34-K
buffer. The buffer size cannot be changed.
Although memory reallocation is not supported, you can allocate
memory for the variable in a UniBasic program before passing it in
the parameter to the C program. For example:
...
var = SPACE(64000)
CALLC(var...)
The variable now has been allocated a size of 64 KB.
bstring
A pointer to a structure (struc) in a buffer that is a minimum of 34
KB in size. You can reallocate this buffer. Also, you can pass data
contain the NULL (0) character by using bstring. Defined as:
typedef struct (
char *str;
int len
} bstring;
To prevent a memory leak, you must free the original buffer
assignment before reallocating it. If the string is changed, the length
must also be reassigned.
pointer
A 32-bit signed long word.
Data Types for UNIX C Definition (continued)

Add the object code path and file name.
Enter the object code path and file name on the line that follows
$$OBJ. You can put it in any directory as long as you specify the
absolute path here.

Add the library path to cfuncdef_user.
If your function does not reside in udtlib, enter the library path on the
line that follows $$LIB, preceded by -L. Do not use the environment
variable (udtlib) in the path name, but instead list the absolute path.
The function in the example does not link any alternate libraries.
Linking C Programs (UNIX Only) 8-13
4.
Rebuild the UniData Executable
The system-level makeudt command builds a new UniData
executable (udt). The makeudapi command also builds a new
UniData executable (udapi_slave) with links to C programs so that
they are accessible through InterCall, UniObjects, or UniObjects for
Java. Use the command that is appropriate for your situation to build
the executable.
Warning: If you are upgrading from a version of UniData before Release
3.3, you will need to install the makefile for Release 7.3 because the format
for this file has changed more than once in subsequent releases. If you use a
pre-3.3 version of the file, makeudt or makeudapi will fail.
It is best to be logged on as root to execute makeudt or makeudapi
because you must have write access to udthome/work. Also, you
must be in the udthome/work directory, but execute the utility from
udtbin, as in the following examples: udtbin/makeudt or
udtbin/makeudapi.
These utilities use cfuncdef_user and base.mk to create the file
new.mk. For more information about these utilities, see “More on
make, makeudt, and makeudapi” on page 8-39. For information
about error messages and common problems you could encounter
during this process, see “Troubleshooting CALLC” on page 8-42.
8-14 Developing UniBasic Applications
Tip: If users are logged on to UniData, the makeudt or makeudapi utility might not
finish because it might not be able to overwrite the production udt or udapi_slave. If
an error message displays indicating an unsuccessful completion, you need to locate
the new udt or udapi_slave executable in the udthome/work directory. Later on, when
UniData is no longer busy, move the new executable to udtbin.
makeudt
Syntax:
makeudt [-n nfa]
The following table describes each parameter of the syntax.
Parameter
Description
-n nfa
Use this option only if you are not using UniData OFS/NFA. This
option uses “dummy” libraries rather than network libraries
required by NFA. Software development environments may or may
not include the network libraries. If your environment does not
include these, and you do not use the -n nfa option, makeudt fails.
makeudt Parameters
For more information about using the makeudt utility, see
Administering UniData on UNIX.
makeudapi
Syntax:
makeudapi
For more information about using the makeudapi utility, see
Administering UniData on UNIX.
5.

Write and Compile the UniBasic Program
Write the UniBasic program.
Next, you will write the UniBasic program that uses the UniBasic
CALLC function to call the C function.
Linking C Programs (UNIX Only) 8-15
UniBasic Program Example
In the following example, the UniBasic program CDEMO performs
the following tasks:

Prompts for the name of the environment variable for which to
display the value.

Calls the C program c_example you created.

Returns the value of the environment variable requested in the
prompt.
* CDEMO
*
PROMPT ''
PRINT 'Enter an environment variable (such as PATH or TERM)':
INPUT VARNAME
EVAL = CALLC c_example(VARNAME)
PRINT
PRINT 'Value of ":VARNAME:" is ':EVAL
PRINT
STOP
END

Compile the UniBasic program.
Enter the BASIC command at the ECL prompt, as in the following
example. For instructions about compiling and cataloging UniBasic
programs, see Chapter 3, “Chapter 3: Creating and Running a
Program.”
:BASIC BP CDEMO
6.
Execute the UniBasic Program
Execute the UniBasic program using the UniData ECL RUN
command.
The following example demonstrates running the UniBasic program
CDEMO:
:RUN BP CDEMO
Enter an environment variable name (such as PATH or TERM)
To continue the example, the user enters TERM. The program calls
the C function that determines the value of the environmental
variable and returns it to the UniBasic program, which displays the
value.
?TERM
Value of TERM is vt100
8-16 Developing UniBasic Applications
Passing bstring-Type Data
This example demonstrates passing bstring-type data. Notice that the
original bstring passed by the UniBasic program is shorter than the minimum
buffer size of 34 KB. Therefore, UniData assigns a 34-KB buffer to it. Within
the C program, however, the size of the variable passed by the UniBasic
program is changed. To avoid a memory leak resulting from assigning a
larger buffer, the C program checks the size of the string to be passed back,
frees up the original 34-KB allocation, and reassigns a buffer of adequate size.
The following UniBasic program prompts the user for the length of buffer to
assign (see length in the INPUT statement), then passes this length to the C
program (see length in the CALLC statement):
PROGRAM trycallc
*
* Input a length to indicate that the length of the 2nd IO string
* and bstring will be changed in C routine.
*
PRINT "Input length of new IO strings ":
INPUT length
<— INPUT
statement
instr = "THE FIRST INPUT STRING"
iostr1 = "THE FIRST IO STRING"
iostr2 = "THE SECOND IO STRING"
bstring = "THE BSTRING”
PRINT "Before calling C function str_arg()"
PRINT
CALLC str_arg(length, instr, iostr1, iostr2, bstring)
statement
PRINT
PRINT
PRINT
PRINT
PRINT
PRINT
PRINT
PRINT
PRINT
PRINT
<— CALLC
"After called C function str_arg()"
"The
"The
"The
"The
"The
"The
1st input string
1st IO string
length of the 2nd IO string
length of the bstring is
1st 20 char of the 2nd string
1st 20 char of the bstring is
>>
>>
>>
>>
>>
>>
":instr
":iostr1
":LEN(iostr2)
":LEN(bstring)
":iostr2[1,20]
":bstring[1,20]
Linking C Programs (UNIX Only) 8-17
The following C program is called by the CALLC statement in the preceding
UniBasic program. The buffer size assigned to the arguments IOstr2 and
bstring are tested. If either is greater than 34KB, the memory allocation for
that variable must be freed and reallocated. Because IOstr2 is 1KB in size, the
memory allocation is freed and reallocated before passing it to the UniBasic
program.
#include <stdio.h>
#include <string.h>
#include "/usr/ud73/include/callc_bstr.h"
#define ONEK 1024
extern int U_unit;
int
str_arg(len, InStr, IOstr1, IOstr2, Bstr)
int len; *length of the 2nd IO string and Bstring after updating
*/
char *InStr; /* an input string, read only */
char *IOstr1; /* an IO string, length of which is less
than 34K */
char *IOstr2; * an IO string whose length will be greater
* than 34K.
*/
bstring *Bstr; /* an IO bstring struct pointer */
{
charbuf1[30], buf2[30];
intmemsize;
/*
* What the input strings are
*/
U_preprint(U_unit, "The 1st input string
U_preprint(U_unit, "The 1st IO string
U_preprint(U_unit, "The 2nd IO string
U_preprint(U_unit, "The binary string
U_preprint(U_unit, "Length of the bstring
(%s)\n",
(%s)\n",
(%s)\n",
(%s)\n",
(%d)\n",
InStr);
IOstr1);
IOstr2);
Bstr->str);
Bstr->len);
/*
* For an IO string, the memory size is either 34 KB
* or the length of the original string in the basic
* program. Memory reallocation
* is not supported for string-type parameters.
* Allocate memory in the UniBasic program. (Assign
* a long string to the variable as
* a work around if the size is known.)
*/
strcpy(IOstr1, "The 1st IO string is changed");
len *= ONEK;
memsize = strlen(IOstr2);
8-18 Developing UniBasic Applications
memsize = (memsize > 34*ONEK ? memsize : 34*ONEK);
if ( len < memsize ) {
memset(IOstr2, 'a', len);
memcpy(IOstr2, "len < 34K ", 10);
IOstr2[len] = '\0';
}
else {
/* no memory reallocation is allowed here */
memset(IOstr2, 'b', 100);
IOstr2[30*ONEK] = '\0';
memcpy(IOstr2, "len > 34K ", 10);
}
/*
* For bstring data, the initial memory size is the
* same as for string data, and memory reallocation
* is supported. To avoid a memory leak, the buffer
* size for the bstring needs to be freed
* before allocating a larger buffer.
*/
memsize = Bstr->len;
memsize = (memsize > 34*ONEK ? memsize : 34*ONEK);
if ( len < memsize ) {
Bstr->len = len;
memset(Bstr->str, 'c', len);
memcpy(Bstr->str, "len < 34K ", 10);
}
else {
/* memory re-allocation is allowed here */
free(Bstr->str);
Bstr->str = malloc(len);
Bstr->len = len;
memset(Bstr->str, 'd', len);
memcpy(Bstr->str, "len > 34K ", 10);
}
/*
* print out changed strings
*/
memcpy(buf1, IOstr2, 20);
memcpy(buf2, Bstr->str, 20);
buf1[20] = buf2[20] = '\0';
U_preprint(U_unit, "\nAfter modification:\n");
U_preprint(U_unit, "The 1st IO string
(%s)\n",
IOstr1);
U_preprint(U_unit, "Length of the 2nd IO string
(%d)\n",strlen(IOstr2));
U_preprint(U_unit, "Length of the bstring
(%d)\n", Bstr>len);
U_preprint(U_unit, "The 1st 20 char of IO str2 (%s)\n", buf1);
Linking C Programs (UNIX Only) 8-19
U_preprint(U_unit, "The 1st 20 char of Bstring
(%s)\n", buf2);
return 0;
}
The following cfuncdef_user file contains the function and argument
definitions needed to link the preceding C program with UniData:
/* comment lines come here.
*/
/* C function declaration format:
* function-name:return-type:number-of-argument:arg1,arg2,...,argn
*/
$$FUN
/* beginning of C function */
str_arg:int:5:int,string,string,string,bstring
<— argument
definitions
$$OBJ
/* *.o come here */
str_arg.o
<— function definition
$$LIB
/* library comes here
The following shows the output produced by executing the UniBasic
program trycallc after linking the C program str_arg with UniData. Notice
that the user responded to the prompt of length for the output string with a
number less than 34; this will require that the memory allocation for IOstr2
be freed and reallocated to pass back a variable of 1 KB in size.
:run BP trycallc
Input length of new IO strings ?30
Before calling C function str_arg()
The 1st input string
The 1st IO string
The 2nd IO string
The binary string
Length of the bstring
(THE
(THE
(THE
(THE
(11)
After modification:
The 1st IO string
Length of the 2nd IO string
Length of the bstring
The 1st 20 char of IO str2
The 1st 20 char of Bstring
FIRST INPUT STRING)
FIRST IO STRING)
SECOND IO STRING)
BSTRING)
(The 1st IO string is changed)
(30720)
(30720)
(len < 34K aaaaaaaaaa)
(len < 34K cccccccccc)
After called C function str_arg()
The 1st input string
The 1st IO string
The length of the 2nd IO string
The length of the bstring is
The 1st 20 char of the 2nd string
The 1st 20 char of the bstring is
Enter <New line> to continue...
8-20 Developing UniBasic Applications
>>
>>
>>
>>
>>
>>
THE FIRST INPUT STRING
The 1st IO string is changed
30720
30720
len < 34K aaaaaaaaaa
len < 34K cccccccccc
Calling a UniBasic Subroutine from a C Program with
CallBasic
You can link a C program with UniData so that it executes a UniBasic
subroutine by using the UniData CallBasic application programming
interface (API). When you use CallBasic, your UniBasic routine is called from
a C program that is executed from the operating system prompt. Both
UniBasic and UniData are invisible to the user.
CallBasic lets you combine the power of C with the advantages offered by
UniBasic subroutines. For example, you can write an application in C to
access the UniData database, or you can retrofit existing C applications to
incorporate UniBasic subroutines to access the UniData database.
Tip: To save time and frustration, read “Before You Begin” on page 8-5.
Procedure Summary
Here is a summary of the steps you must follow to link a C program with
UniData so that it will call a UniBasic subroutine:
1.
Write, compile, and catalog the UniBasic subroutine.
2.
Write the C program.
3.
Create a makefile.
4.
Compile and link the C program.
5.
Execute the C program.
Requirements
You must have the following components to use the CallBasic API:

Development environment
Your system must have a full software development kit. (A base
compiler is not sufficient). You also will need network libraries (for
example, TCP/IP) if you are using NFA.
Tip: Consult your host operating system documentation and your hardware
vendor if you have questions about your C development environment.
Linking C Programs (UNIX Only) 8-21

C programs
You will need to code and compile the C application that calls
UniBasic.

Function definitions and makefiles
When you install UniData, the file callbas.mk is installed in the
udthome/work directory. You will use this makefile as a template to
build your application with UniData linked into it.
The Procedure
You are now ready to link a C program that will call a UniBasic subroutine.
1. Write, Compile, and Catalog the UniBasic Subroutine
Perform the following steps to create a UniBasic subroutine that will be called
from a C program:

Write the UniBasic subroutine.
Write a UniBasic subroutine that includes a RETURN statement. You
cannot use a UniBasic mainline program, because the subroutine
must return control to the C program.
For example, the following subroutine (EXAMPLE) returns a value
in RTNVAL. The remaining arguments, ARG1 and ARG2, pass data
from the C program to the EXAMPLE subroutine.
SUBROUTINE EXAMPLE(RTNVAL, ARG1, ARG2)
PRINT ARG1
PRINT ARG2
RTNVAL = "val1"
RETURN
END

Compile the UniBasic subroutine.
Use the ECL command BASIC to compile the UniBasic subroutine.
For more information about this command, see Chapter 3, “Chapter
3: Creating and Running a Program,” or the UniData Commands
Reference.
In this example, we compile the EXAMPLE subroutine:
:BASIC BP EXAMPLE
8-22 Developing UniBasic Applications

Catalog the UniBasic subroutine.
Use the ECL command CATALOG to catalog the UniBasic
subroutine.
Here we catalog the EXAMPLE subroutine:
:CATALOG BP EXAMPLE
2. Write the C Program
Write the C program that calls your UniBasic subroutine.
Note: A transaction started in a UniBasic subroutine invoked by a C program can
continue into another subroutine called by the same C program:
udtcallbasic_init( )
U_callbas(subroutineA) * this subroutine starts a
transaction
U_callbas(subroutineB) * this subroutine commits the
transaction
udtcallbasic_done( )
However, a transaction started in a UniBasic program executed from ECL
must complete before the process returns to ECL.
Linking C Programs (UNIX Only) 8-23
Sample Program
The following example (mypgm.c) illustrates a C program that calls a
UniBasic subroutine:
/* call a UniBasic subroutine EXAMPLE from mypgm.c */
#include <stdio.h>
#include <setjmp.h>
#include "/usr/ud73/include/share.h"
#endif
main()
{
/* declare variables */
char arg0[BUFSIZ];
char arg1[BUFSIZ];
char *args[2];
char *rtn;
int sts;
/* initialize UniData environment */
/* first, set up an error handler */
int jmpret, sat;
U_SET_JMP(jmpret, sat);
if (!jmpret) {
udtcallbasic_done(1);
exit(-1);
}
udtcallbasic_init(0,0);
/* assign arguments to be passed to the subroutine */
strcpy(arg0, "1ST ARGUMENT");
strcpy(arg1, "2ND ARGUMENT");
args[0] = arg0;
args[1] = arg1;
/* call the subroutine */
sts = U_callbas(&rtn, "EXAMPLE", 2, args);
if (sts = = 0){
printf("Return: %s\n", rtn);
free(rtn);
}
printf("Status: %d\n", sts);
/* shut down UniData environment */
udtcallbasic_done(sts);
}
8-24 Developing UniBasic Applications
Include Required Functions
Three functions must be included in the C program: udtcallbasic_init, which
initializes UniData, U_callbas, which calls the UniBasic subroutine, and
udtcallbasic_done, which closes files and the UniData session.
Required Functions: Start UniData
Your C program must execute this function once and only once. The C
function udtcallbasic_init initializes the UniData environment, starting a udt
process to execute the CallBasic subroutine call.
Syntax:
udtcallbasic_init(value1, value2)
int — value1;
int — value2;
You must initialize the UniData environment with udtcallbasic_init before
calling a UniBasic subroutine.
Warning: You can call udtcallbasic_init only once in your program. If you attempt
to call it more than once, your program could cause a core dump.
Linking C Programs (UNIX Only) 8-25
The following table describes each parameter of the syntax.
Parameter
Description
value1
Names the type of UniData process that will be started. The default
is 0. Values for each parameter are in the share.h file in the
/usr/ud73/include directory.
Valid parameters include the following:
01 – U_PHANTOM – This parameter behaves like the ECL
PHANTOM command.
0100 – U_REDIRECT – Use this parameter to override the terminal
attributes that UniData sets by default; it sends UniData output to
a file instead of the terminal screen.
0400 – U_UDT_SERVER – Sets a flag that, when a fatal error occurs,
returns control to the calling UniBasic program rather than
aborting (same as UDT.OPTIONS 41).
When UDT.OPTION 41 is used, errors are returned in EXECUTE
instead of longjmp.
value2
Enter one of the following values to clear or not clear the screen
when CALLBASIC is initialized:
0 – Do not clear the screen.
1 – Clear the screen.
udtcallbasic_init Arguments
Required Functions: Call the UniBasic Subroutine
This function calls a UniBasic subroutine, passes arguments, and sets a
pointer to the return value. You can execute this function numerous times in
your C application, each time you want to call a UniBasic subroutine.
Syntax:
int U_callbas (rtn, progname, argc, argv)
8-26 Developing UniBasic Applications
The following table describes each parameter of the syntax.
Parameter
Data Type
Description
rtn
char
The address of the pointer to the value returned
from the UniBasic subroutine; you cannot use this
argument to pass anything to the subroutine. The
return values include the following:
0 – The function executed successfully.
-1 – A UniBasic program error occurred in the RUN
process, you are out of memory, or the system is
performing a CHAIN.
-2 – A fatal error occurred or a STOP was
encountered in a UniBasic program.
progname
char
The pointer to the name of the subroutine.
argc
int
The number of arguments passed to the subroutine.
argv
char
The address of an array of pointers to arguments.
You cannot use these arguments to pass anything to
the subroutine.
U_callbas Function Arguments
Note: UniData allocates memory every time you execute U_callbas. In the C
program, you must free the storage space pointed to by the first argument or the
memory will be unavailable to the system. However, you must free memory only if
the function completes successfully.
Required Functions: Close Files and UniData
This function clears all UniData-related temporary files and other resources
and ends the interface between the C application and UniData. You must
execute this function once in your C application. You must also include this
function in any error exits in your application that could be taken after
udtcallbasic_init.
Syntax:
udtcallbasic_done(status)
int status;
status is returned, but it is currently not interpreted.
Linking C Programs (UNIX Only) 8-27
3. Create a makefile
Execute the following steps to create or modify a makefile to specify to
UniData the linked program name and location.

Copy callbas.mk.
Copy the callbas.mk file from udthome/work to your work directory.
You will need to make a backup copy of this file in case you need to
start this process over. You might want to give the copy a name
similar to that of your C function to make it easier to remember later
which makefile was used to link which C function.
A sample of this file is displayed in “File Examples” on page 8-33.

Enter the library path.
Change the libpath in your makefile file to your udthome/lib directory,
using the absolute path instead of udthome. Here is an example of a
libpath line:
libpath = -L/usr/ud73/lib
Also, if you find a line like the following in the file, remove it:
libpath = -L$$UDTLIB
This ensures that the make process will execute regardless of the
definition of the environment variable udtlib.

Find NEWOBJS.
Look for the statement that defines NEWOBJS. It looks like this:
NEWOBJS = callbas.o
Does your NEWOBJS line differ from this? If it refers to a previously
linked C function, you need to reinitialize the files UniData uses to
create the link. For more instructions, see “Relinking C Functions to
UniData” on page 8-31.

Change callbas.o.
NEWOBJS must refer to your object file. Because our sample C
program is c_example.c, the object name is c_example.o:
NEWOBJS = c_example.o
8-28 Developing UniBasic Applications

Enter object name again.
Modify the executable name again in a line that looks like this before
modification:
callbas:
$(NEWOBJS) $OBJS)
To link our sample C program, this line must be:
c_example:
$(NEWOBJS) $(OBJS)
4. Compile and Link the C Program
Note: Before you execute make, you need to know if anyone has executed the
UniData system-level command makeudt or makeudapi since UniData was installed
or upgraded. If they have, you MUST restore the files modified by this utility. For
more information, see “Relinking C Functions to UniData” on page 8-31.
From the UNIX prompt, enter the make command to compile the C program
and link it to UniData. The -f option tells UNIX to use the file specified in
makefile_name rather than the default, callbas.mk.
%make
Syntax:
make -f makefile_name
For example, the following command compiles the C program named
c_example.c into an executable named c_example and links the executable to
UniData:
%make -f c_example.mk
5. Execute the C Program
Run the C program from the operating system prompt.
Syntax:
[path]executable_name [arg1] [args2...]
If the UniBasic subroutine is globally cataloged, you can execute the C
program from any UniData account or directory.
Linking C Programs (UNIX Only) 8-29
If the UniBasic subroutine is locally or directly cataloged, you must execute
the C program in a UniData account that has an entry for the program in the
VOC file. This is the account from which the make utility was executed or one
in which you manually create a VOC record for the C program.
You must set udthome, udtbin, and your path. For information about setting
these environment variables, see Administering UniData on UNIX.
The following command invokes the program named c_example from the
/usr/ud73/work directory:
%/usr/ud73/work/c_example
The sample C program, c_example, which calls the UniBasic program
EXAMPLE, produces the following output:
1ST ARGUMENT
2ND ARGUMENT
Status: 0
Return: val1
8-30 Developing UniBasic Applications
Relinking C Functions to UniData
Perform the following steps to reinitialize the files in udthome/work if
someone has already linked C functions to UniData. You need to perform this
procedure before you try to execute the make, makeudt, or makeudapi utility.
1. Find cfuncdef_user
Look in the udthome/work directory for a file named cfuncdef_user. If you do
not find it, look for cfuncdef.
Here is why: When UniData is installed, the files required for the UNIX make
utility to link C programs with UniData are provided in udthome/work. The
instructions in the preceding section (for CallBasic) direct the user to copy
cfuncdef to cfuncdef_user, then modify this template with information
required by the UNIX make utility.
2. Take a Look Inside
Use the UNIX more command to look at the contents. Does the file look like
this template?
/* this is a test for adding C function to the RUN Machine
*/
/* comment lines come here.
*/
/* C function declaration format:
function-name:return-type:number-ofargument:arg1,arg2,...,argn
*/
$$FUN
/* beginning of C function */
$$OBJ
/* *.o come here */
$$LIB
/* library comes here */
Relinking C Functions to UniData 8-31
3. Look for Modifications
If you have linked one or more C functions to UniData for the purpose of
CALLC, cfuncdef or cfuncdef_user will contain references to C functions on
the line that follows $$FUN, and object names in the line following $$OBJ. It
could also list site-specific libraries under $$LIB. If the file has been modified,
you need to reinitialize the files the UNIX make utility uses to create the link
between C programs and UniData. (These are the other files placed in
udthome/work when UniData is installed: funchead.c, interfunc.c, and so
forth.)
4. Log On as root
Before you proceed to the next step, you must log on as root.
5. Reinitialize
Execute the UniData system-level commands genefs, gencdef, and genfunc
to update funchead, interfunc, and other C functions used by the makeudt or
makeudapi utility.
Enter the following commands, in the indicated order, from the
UNIX prompt:
# $UDTBIN/genefs
# $UDTBIN/gencdef
# $UDTBIN/genfunc
# ls -tl |pg
total 26
-r--r--r-1 root
sys
2735 Jun 10 16:08 interfunc.c
-r--r--r-1 root
sys
738 Jun 10 16:08 funchead.c
-r--r--r-1 root
sys
760 Jun 10 16:08 callcf.c
-rw-rw-rw1 root
sys
97 Jun 10 16:08 ndef
-rw-rw-rw1 root
sys
422 Jun 10 16:08 cdef
-r--r--r-1 root
sys
1006 Jun 10 16:08 efs_init.c
-r--r--r-1 root
sys
155 Jun 10 16:04 efsdef
-rw-rw-rw1 root
sys
985 Jun 10 16:04 callbas.mk
-r--r--r-1 root
sys
292 Jun 10 16:04 cfuncdef
-rw-rw-rw1 root
sys
1424 Jun 10 16:04 base.mk
You must be logged on as root to execute these commands, and you must
execute them in the order shown in the example.
After you reinitialize, return to the procedure for linking C programs with
UniData.
8-32 Developing UniBasic Applications
File Examples
The base.mk file and the cfuncdef file are platform-specific.
base.mk Example
Warning: Do not copy the sample makefile onto your system, and do not copy a
makefile from another platform. If you do, makeudt or makeudapi will probably fail.
Always start with the base.mk file released with UniData.
Relinking C Functions to UniData 8-33
The following example shows a base.mk file for UniData on an HP system:
# pg base.mk
#
#
The Porting Date
: Jun. 15, 2011
#
The System to Be Ported : HPUX11
#
CC
= cc
CFLAGS
= -Ae -q -z +ESsfc -w
LDFLAGS
=
OPTFLAGS
= -O +Ovolatile
DBFLAGS
=
libpath
= -L/liz1/ud73/lib
addlib
= -lm -lcurses -lsec
addlibpath =
odslib
= -lodsdummy
udlib
= -lndbm -lcl -lBSD
licnlib = -llicn
dclcnlib
=
nfalib
= -lnfaclnt
dfslib
=
ODBC_LIBS
= -lodbc -lstd -lstream -lCsup -lm -lcl -ldld Wl,-B deferred Wl,+b /.udlibs
OBJS = funchead.o interfunc.o callcf.o efs_init.o cpprt0_stub.o
cpprt0_stub.o:
$(CC) $(CFLAGS) $(OPTFLAGS) $(DBFLAGS) -c cpprt0_stub.s
libs_clt = -lshare -ludsql -ludmach -lbasic -lret1 -lperf -lides lpipe \
-lfunc -lndx $(dfslib) -lrep -lshm -lmglm -lglm -lulc lcmn -llicn \
-ludus -lunix -lbci -lunirpc -lssl -lcrypto \
$(ODBC_LIBS) $(nfalib) $(odslib)
libs_svr = -lnfasvr -lshare -ludsql -ludmach -lbasic -lret1 -lperf
-lides \
-lpipe -lfunc -lndx $(dfslib) -lrep -lshm -lmglm -lglm
-lulc -lcmn \
-llicn -ludus -lunix -lbci -lunirpc -lssl -lcrypto \
$(ODBC_LIBS) $(odslib)
libs_srv = -lushare -lusql -lumach -lbasic -lret1 -lperf -lides lpipe \
-lushare -lumach -lret1 -lperf -lpipe \
-lfunc -lndx -lrep -lshm -lmglm -lglm -lulc -lcmn llicn \
-ludus -lunix -lbci -lssl -lcrypto \
$(ODBC_LIBS) -lunirpc $(nfalib) $(odslib)
8-34 Developing UniBasic Applications
udt: $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) $(NEWOBJS) $(NEWLIBS) \
$(libpath) -lapidummy $(libs_clt) \
$(addlibpath) $(addlib) \
-o $@
udtsvr: $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) $(NEWOBJS) $(NEWLIBS) \
$(libpath) -lapidummy $(libs_svr) \
$(addlibpath) $(addlib) \
-o $@
uniapisvr: $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) $(NEWOBJS) $(NEWLIBS) \
$(libpath) -lapisvr $(libs_clt) -lmsg \
$(udlib) $(addlibpath) $(addlib) \
-o $@
udapi_slave: $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) $(NEWOBJS) $(NEWLIBS) \
$(libpath) -lapidummy -licapi $(libs_clt) -lunirpc \
$(addlibpath) $(addlib) \
-o $@
udsrvd: $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) $(NEWOBJS) $(NEWLIBS) \
$(libpath) -lapidummy $(libs_srv) \
$(addlibpath) $(addlib) \
-o $@
.c.o:
$(CC) $(CFLAGS) $(IDIR) $(OPTFLAGS) $(DBFLAGS) -c $<
#
Relinking C Functions to UniData 8-35
cfuncdef Example
Some earlier releases of UniData included some UniData functions in
cfuncdef. If someone upgraded UniData and chose not to overlay this file, the
wrong version could reside in udthome/work. The cfuncdef file must look
similar to the following example, which contains no reference to C functions:
# pg cfuncdef
/* this is a test for adding C function to the RUN Machine */
/* comment lines come here.
*/
/* C function declaration format:
function-name:return-type:number-of-argument:arg1,arg2,...,argn
*/
$$FUN
/* beginning of C function */
$$OBJ
/* *.o come here */
$$LIB
/* library comes here */
callbas.mk Example
The following is an example of what the callbas.mk template might look like.
8-36 Developing UniBasic Applications
Note: Do not copy this example. Use the template provided in your udthome/work
directory for the platform on which you are linking C programs with UniData. This
example shows link options that might not be valid on some platforms.
#
#
#
#
The Porting Date
: Dec. 11, 2011
The System to Be Ported : HPUX10.debug
CC
CFLAGS
LDFLAGS
OPTFLAGS
DBFLAGS
libpath
addlib
addlibpath
odslib
udlib
licnlib
dclcnlib
nfalib
dfslib
OBJS
=
=
=
=
=
=
=
=
=
=
=
=
=
=
cc
-q -z +ESsfc -DNULL_OK -DSQLTP -DUDMS -DNEW_INTER
-Wl,-a,archive
-g
-L/disk2/srcman/alpha/ud_pkqa4/bin/lib
-lm -Wl,-a,shared -lcurses
-lud
-lndbm -lcl -lBSD
-llicn
-lnfaclnt
= funchead.o interfunc.o callcf.o efs_init.o
NEWOBJS
= callbas.o
newlibpath =
newlibs
=
libpath
= -L$$UDTLIB
libs
-lpipe \
= -lshare -ludsql -ludmach -lbasic -lperf -lret1 -lides
-lfunc -lndx $(dfslib) -lshm -lcmn $(licnlib) -ludus
$(nfalib) \
$(odslib) $(udlib)
callbas:$(NEWOBJS) $(OBJS)
$(CC) $(LDFLAGS) $(NEWOBJS) $(OBJS) \
$(libpath) -lapidummy $(libs) $(newlibpath) $(newlibs) \
$(addlibpath) $(addlib) -o $@
.c.o:
$(CC) $(CFLAGS) $(IDIR) $(OPTFLAGS) $(DBFLAGS) -c $<
Relinking C Functions to UniData 8-37
bstring Definition File
callc_bstr.h, which is located in /usr/ud61/include, contains the definition
of bstring. Include this header in a C function when you need to pass binary
data, especially when that data contains /0 (called NULL in C), which is
interpreted by C as a line terminator.
typedef struct {
char
*str;
int
len;
} bstring;
8-38 Developing UniBasic Applications
More on make, makeudt, and makeudapi
The utilities make, makeudt, and makeudapi create new UniData
executables. However, the makeudt and makeudapi commands perform
more operations than the make command performs.
makeudt and makeudapi
Use the makeudt utility to create a new executable udt, or use makeudapi to
create a new executable udapi_slave, in udthome. Using one of these utilities
forces udt or udapi_slave to be created in udtbin.
1.
First, makeudt or makeudapi reads cfuncef and cfuncdef_user to
obtain the following information:
...$$FUN
/* beginning of C function */
str_arg:int:5:int,string,string,string,bstring
<— function
and argument
$$OBJ
/* *.o come here */
definitions
str_arg.o
<— C program
object file name
$$LIB
/* library comes here */
/disk2/ud61/our_lib
<— site-
specific library path,
if different from udtlib
2.
Using information gained from cfuncdef and cfuncdef_user,
makeudt or makeudapi executes the following three functions:

genefs – Needed for NFA (Network File Access) only.

gencdef – Creates a definition file (called cdef) for the following
UniData functions:

callcf.c

funchead.c

interfunc.c

genfunc – Uses the definitions in cdef to create the following
UniData functions:
callcf.c
funchead.c
interfunc.c
More on make, makeudt, and makeudapi 8-39
3.
Next, makeudt or makeudapi copies the base.mk file to new.mk and
writes the C program and library path, which it obtained from
cfuncdef_user:
NEWOBJS = str_arg.o
NEWLIBS =/disk2/ud73/our_lib
UniData overlays new.mk if it already exists.
Note: makeudt or makeudapi always writes the udt or udapi_slave
executable to udtbin. If UniData is executing when it tries to overwrite the
file, it notifies you. You must then copy the executable file, udt or
udapi_slave, into udtbin when all users are signed out of UniData.
4.
Finally, makeudt or makeudapi deletes the cdef file.
make
By default, the makeudt or makeudapi utility makes a new UniData
executable and replaces udtbin/udt (or, in the case of makeudapi,
udtbin/udapi_slave) with the new one.
The make utility performs functions similar to makeudt and makeudapi with
the following exceptions:

The make utility does not automatically execute the three functions
genefs, gencdef, and genfunc.

The make utility lets you place the udt or udapi_slave executable in
any directory. To do so, change the output path for the targets from o $@ to -o path. For more information, see the UNIX man page for
make.
8-40 Developing UniBasic Applications
The following example shows a sample base.mk with the output destinations
indicated:
# pg base.mk
...
udt:
$(OBJS)
<— udt target
$(CC) $(LDFLAGS) $(OBJS) $(NEWOBJS) $(NEWLIBS)
$(libpath) -lapidummy $(libs_clt)
$(addlibpath) $(addlib)
-o $@
<— change to -o path
udapi_slave:
$(OBJS) <— udapi_slave target
$(CC) $(LDFLAGS) $(OBJS) $(NEWOBJS) $(NEWLIBS)
$(libpath) -lapidummy -licapi $(libs_clt) -lunirpc
$(addlibpath) $(addlib)
-o $@
<—change to -o path
udtsvr: $(OBJS)
<— udtsvr target
$(CC) $(LDFLAGS) $(OBJS) $(NEWOBJS) $(NEWLIBS)
$(libpath) -lapidummy $(libs_svr)
$(addlibpath) $(addlib)
-o $@
<—change to -o path
udsrvd: unirpc_srv.o $(OBJS) <— udsrvd target
$(CC) unirpc_srv.o $(OBJS)
$(libpath) -lapidummy $(libs_srv)
$(addlibpath) $(addlib)
-o $@
<—change to -o path
uniapisvr:
$(OBJS) <— uniapi.svr target
$(CC) $(LDFLAGS) $(OBJS) $(NEWOBJS) $(NEWLIBS)
$(libpath) -lapisvr $(libs_clt) -lmsg
$(udlib) $(addlibpath) $(addlib) \
-o $@
<—change to -o path
More on make, makeudt, and makeudapi 8-41
Troubleshooting CALLC
The following table contains solutions for some common problems
encountered when you attempt to link C programs with UniData.
Problem
Solution
makeudt or makeudapi aborts with a
message indicating that UniData
cannot find a library. For example:
/usr/ccs/bin/ld: Can't find
library for -lapidummy
Add the full path to the C program in
the cfuncdef_user file that follows
$$OBJ.
*** Error exit code 1
Check the libpath line in your makefile
(the one you created by copying
base.mk). It should list the absolute
path to the UniData libraries. This is
stored in the environment variable
udtlib. To find out the path for udtlib, at
the UNIX prompt, enter env.
After completing, makeudt displays
the message: 'udt' is up to date
or makeudapi displays the message:
'udapi_slave' is up to date
This is not an error message. The new
udt or udapi_slave has been built
successfully.
makeudt or makeudapi aborts with an
error message such as: unsatisfied
symbol: str_arg
References to libraries are incorrect.
Check the cfuncdef_user file. Enter the
full path to the object file for the C
program after the line that contains
$$OBJ.
where str_arg is the object file for
your C program.
To find out where the makeudt or
makeudapi utility thinks the libraries
are, look in your makefile at the line
beginning with “libpath = -L”. If it is
correct, it lists the absolute path to
udtlib. To find out the path for udtlib, at
the UNIX prompt, enter env.
When you try to copy your C program
to udthome, UNIX returns an error
message like the following:
cp: cannot create
/disk1/ud73/str_arg.o:
Permission denied
Unless the permissions have been
changed for the directory udthome since
UniData was installed, you must be
logged in as root to copy files into this
directory.
Troubleshooting Tips
8-42 Developing UniBasic Applications
Problem
Solution
When you execute the UniBasic
program that calls the C program you
linked to UniData, a memory leak
results.
To prevent a memory leak, you must
free the original buffer assignment in
the C program before reallocating it. If
the string is changed, the length must
also be reassigned. For more
information, see “Passing bstring-Type
Data” on page 8-17.
makeudt or makeudapi aborts with an
error message similar to the following
(for makeudapi, replace “udt” with
“udapi_slave” in this example):
Users are currently executing UniData
sessions. (Execute the command
LISTUSER from the ECL prompt to
determine who those users are.)
mv: /disk1/ud73/bin/udt:
The new executable has been created,
cannot write: Text file busy but UniData was unable to overlay the
current executable. With all users
Execute "mv udt
/disk1/ud73/bin/udt" error, signed off, copy the udt or udapi_slave
executable from udthome/work to
errno = 0
udtbin.
The udt make failed.
When you log in as root, UniData
cannot find udthome or other files or
directories.
The setting for the UNIX environment
variable PATH is lost. Reset your path
as it is in your .login or .profile file (if
you have one), or see your system
administrator or the UNIX man pages
for instructions on setting PATH.
Reset the environment variables
udthome and udtbin. For instructions, see
Administering UniData on UNIX or the
UNIX man pages.
Troubleshooting Tips (continued)
Troubleshooting CALLC 8-43
Linking C Programs (Windows Platforms Only)
The information in this section applies to Windows platforms only.
For more information about CALLC, see Administering UniData on Windows
Platforms.
Dynamic Link Libraries (DLLs) and UniData
Both the CALLC implementation and the CallBasic implementation in
UniData for Windows Platforms use the Microsoft Windows Dynamic Link
Library (DLL) facility. This facility lets separate pieces of code call one
another without being permanently bound together. Linking between the
separate pieces is accomplished at runtime (rather than compile time)
through a Dynamic Link Library (DLL) interface. Both the CALLC interface
(for calling external functions from UniData) and the CallBasic interface
(calling UniData functions from external code) are implemented as DLLs.

For CALLC, developers create a DLL and then call that DLL from
UniData. Special VOC entries for each function that is called from a
DLL communicate interface information to UniData.

For CallBasic, developers link their code with UniData.LIB (located
in the UniData bin directory) and then make calls into the UniData
DLL. The .LIB file supplies interface information.
Because linking between caller and DLL is accomplished at runtime, either
the caller or the DLL can be modified independently. For UniData, this means
that you can upgrade your UniData version without the need to relink with
external routines, and you can update your external DLL without the need to
relink UniData.
A DLL is language-independent. Many software development environments
for Windows can produce a DLL.
Note: For information about linking code into a DLL, see the documentation for your
software development environment.
8-44 Developing UniBasic Applications
CALLC Features and Components
CALLC enables users to execute external functions from within a UniBasic
application. The external functions can be written in C, C++, or Borland
Delphi. This section describes the features of CALLC.
Note: UniData includes a series of CALLC examples, both external functions and
UniBasic programs. The examples are in the CALLC_DEMO folder located in the
UniData demo account. For more information, see Administering UniData on
Windows Platforms.
CALLC Syntax and Data Types
The UniBasic CALLC command has the following syntax:
rtn = CALLC function(arg1,arg2,...argn)
The following table lists the parameters of the syntax.
Parameter
Description
rtn
Return value from CALLC. Must be a valid data type.
function
The name of the external function being called.
arg1,....argn
Arguments to the external function. Each must be a valid data type.
CALLC Parameters
Valid data types for return values and arguments are listed in the following
table.
Data Type
Description
CHAR
A signed byte.
INT
An integer (32-bit signed).
POINTER
A 32-bit signed long word.
SHORT
A short (16-bit) integer.
LONG
A long integer.
Data Types for CALLC
Linking C Programs (Windows Platforms Only) 8-45
Data Type
Description
STRING
A pointer to a null-terminated character string in a 34K buffer.
CHAR_PTR
A pointer to a null-terminated character string.
INT_PTR
A pointer to a 32-bit signed long word.
SHORT_PTR
A pointer to a 16-bit integer.
LONG_PTR
A pointer to a 32-bit integer.
DESCRIPTOR
A pointer to a string descriptor.
NONE
Use for functions that do not return anything (for instance,
VOID).
Data Types for CALLC (continued)
E Type VOC Entries
An E (executable) type VOC entry identifies the DLL for an external function
being called using CALLC, and identifies the data types for its arguments
and return value.
The following table defines the attributes required for an E type VOC entry.
Attribute
Description
@ID
The function name.
Attribute 1
The VOC entry type. You must specify E.
Attribute 2
The location of the DLL. It must be a fully qualified path, a path
relative to the current working directory, or a name that can be
located by way of the user’s path environment variable.
Attribute 3
The function name in the DLL.
Attribute 4
The data type for the return value.
Attribute 5
The data type for the first argument.
Attributes 6 - n
The data types for the second through nth arguments.
Attributes of E Type VOC Entry
8-46 Developing UniBasic Applications
The following screen shows the VOC entry for a function named
callcpp_subr1:
:CT VOC callcpp_subr1
VOC:
callcpp_subr1:
E
CALLC_DEMO\CALLC_CPP\callcpp_test.dll
callcpp_subr1
INT
INT
SHORT
LONG
CHAR
STRING
POINTER
:
Notice that this function expects six arguments, and returns an INT. The
function is accessed from a dynamic linked library called callcpp_test.dll.
Warning: We recommend that you keep your development environment clearly
separate from your production environment when developing a CALLC application.
Separating environments is useful in any case, but can be critical because difficulties
in the external functions can terminate udt sessions and potentially damage data.
CALLC and UDT.OPTIONS 88
One function can call another in a stack-based architecture by using one of
the following conventions:

Pascal calling convention.

_cdecl calling convention.
The Pascal calling convention is the default for UniData.
Note: For C and C++, the default calling convention is _cdecl. For Delphi, the
default calling convention is Pascal. You can use the Pascal convention in C or C++,
and you can use the _cdecl convention in Delphi. For information about choosing a
calling convention, see the documentation for your development environment.
Linking C Programs (Windows Platforms Only) 8-47
Use UDT.OPTIONS 88 with UniData for Windows Platforms to let CALLC
function correctly with both _cdecl and Pascal calling conventions. The
following table describes the behavior of CALLC commands with this option
turned on or off.
UDT.OPTIONS 88
_cdecl Convention
Pascal Convention
OFF (default)
CALLC fails,
terminating the
UDT.
CALLC executes.
ON
CALLC executes.
CALLC fails,
terminating the udt.
UDT.OPTIONS 88
Warning: As the preceding table indicates, calling a function with the wrong
UDT.OPTIONS 88 setting almost certainly terminates a udt session and could
produce other undesirable results.
Using CALLC
To call an external function from UniBasic, perform the following procedure.
1. Write and Compile the External Function
You can code the external function in C, C++, or Delphi.
Guidelines for Writing C Functions
You might find the following guidelines helpful when writing external
functions in C:

Naming Variables – Avoid naming variables or functions with the
prefix U and an underscore (U_), such as U_unit and U_errout;
UniData uses U_ as an identifier for variable names and functions.

Passing Arguments – You cannot pass more than 22 arguments. Each
argument is limited to 255 characters.
8-48 Developing UniBasic Applications

Displaying Error Messages – To display error messages, use the
UniData C function U_errout. U_errout() has the same syntax as
U_preprint(), except the variable U_unit is replaced by 0. U_errout()
output goes to errout whereas U_preprint() output goes to stdout.
Syntax:
U_errout(0,"error message from the routine, value is %d",value);

Printing – To maintain screen integrity and I/O redirection, use
the UniData C function U_preprint instead of the C function
printf. The U_preprint function refreshes the screen, enabling the
C subroutine to properly manage screen I/O. This function
follows syntax similar to printf().
Syntax:
U_preprint pattern,arg1,arg2...

Ending the C Program – Do not use exit(). Instead, use U_done,
which performs various cleanup tasks, and then causes the C
program to terminate.
Steps for Developing the Function


Code the function using C, C++, or Borland Delphi. Make certain
that all the functions to be called from outside the program are
exported in one of the following ways:

A _declspec( dllexport ) statement.

An EXPORTS statement. The EXPORTS statement lists the
names and optionally the ordinal values of the functions
exported by the DLL. When ordinal values are specified, they
must be in the range 1 through n where n is the number of
functions exported by the DLL.
The maximum number of bytes allowed in a function return is 256.

Compile the function or functions and link the code into a DLL.
Warning: UniData for Windows Platforms takes full advantage of the Win32
environment. The UniData DLL is a 32-bit DLL, and any DLLs you call by way of
CALLC must also be 32-bit DLLs. You cannot call a 16-bit DLL from UniData.
Linking C Programs (Windows Platforms Only) 8-49

Create the VOC entry for every function that you can call from the
DLL. You will need to create an E type record in the VOC file in every
UniData account where you will be calling the functions. The VOC
entry contains information that enables UniData to locate and
execute the called function.
After the DLL and the E type VOC entry are created, the function can
be accessed from UniBasic via CALLC.
2. Write and Compile the UniBasic Program
The following example shows a portion of the sample UniBasic program that
calls an external function named ps:
.
.
.
PRINT "TURNING ON UDT.OPTIONS 88; REQUIRED FOR C"
PERFORM "UDT.OPTIONS 88 ON"
PRINT "THE ID OF MY CURRENT UNIDATA PROCESS IS: ":@USERNO
PRINT "PASSING THE ID TO THE C ROUTINE."
pid = @USERNO
pname = ''
cname = ''
ptime = ''
virt_mem = 0
RESULT = CALLC ps(pid, pname, cname, ptime, virt_mem)
PRINT "THE C ROUTINE RETURNED: ":RESULT
IF RESULT >= 0 THEN
PRINT
.
.
.
END ELSE
PRINT "AN ERROR HAS OCCURRED IN THE C ROUTINE."
END
PRINT "TURNING OFF UDT.OPTIONS 88 BEFORE CLOSING"
PERFORM "UDT.OPTIONS 88 OFF"
STOP
END
Notice the following points:

The function name in the CALLC statement matches the name in the
E type VOC entry.
8-50 Developing UniBasic Applications

By default, the calling convention for a C program is the _cdecl
convention. Therefore, UDT.OPTIONS 88 must be turned on.

Error handling is based on the RESULT from the C function rather
than the STATUS of the CALLC statement. The statement can
complete successfully (STATUS of 0) even if the C function has
encountered an error.
Note: The external function examples and UniBasic examples in this section are
taken from the sample programs installed with the current release of UniData on
Windows Platforms.
CallBasic Features and Components
You can write programs that execute UniBasic subroutines by using the
UniData CallBasic application programming interface (API). When you use
CallBasic, your UniBasic routines are called from an external program, and
UniBasic and UniData are invisible to the user.
CallBasic enables you to combine the power of programming languages such
as C, C++, and Delphi with the advantages offered by UniBasic subroutines.
For example, you can write an application in C to access the UniData
database, or you can retrofit existing applications to incorporate UniBasic
subroutines that let you access the database.
Note: UniData includes two CallBasic examples: both the external programs and the
UniBasic subroutines. The examples are in the CALLBASIC_DEMO folder located
in the UniData demo account. The UniBasic subroutines are in the BP file in the
UniData demo account. For more information, see Administering UniData on
Windows Platforms.
Functions of the CallBasic API
udtcallbasic_init
The C function udtcallbasic_init initializes the UniData environment, starting
a udt process to execute the CallBasic subroutine call.
Linking C Programs (Windows Platforms Only) 8-51
Syntax:
udtcallbasic_init(value1, value2)
int — value1;
int — value2;
Note: You must initialize the UniData environment with udtcallbasic_init before
calling a UniBasic subroutine.
Warning: You can call udtcallbasic_init only once in your program. If you attempt
to call it more than once, your program could cause a core dump.
The following table describes each parameter of the syntax.
Parameter
Description
value1
Names the type of UniData process that will be started. The default
is 0. Valid parameters include the following:
0 or 01 – U_PHANTOM – This parameter behaves like the ECL
PHANTOM command.
010 – U_PHANTOMHUSH – This parameter behaves like
U_PHANTOM, except that it suppresses the “Phantom process
started” and “Phantom process completed” messages.
0100 – U_REDIRECT – Use this parameter to override the terminal
attributes that UniData sets by default; it sends UniData output to a
file instead of the terminal screen.
0400 – U_UDT_SERVER – Sets a flag that, when a fatal error occurs,
returns control to the calling UniBasic program rather than aborting
(same as UDT.OPTIONS 41).
When UDT.OPTION 41 is used, an error will be returned in
EXECUTE instead of longjmp.
value2
Enter one of the following values to clear or not clear the screen
when CALLBASIC is initialized:
0 – Do not clear the screen
1 – Clear the screen
udtcallbasic_init Arguments
8-52 Developing UniBasic Applications
U_callbas
The C function U_callbas calls a UniBasic subroutine, passes arguments, and
sets a pointer to the return value.
Syntax:
int U_callbas (rtn, progname, argc, argv)
The following table describes each parameter of the syntax.
Parameter
Data Type
Description
rtn
char
The address of the buffer containing results from the
UniBasic subroutine. You cannot use this argument to
pass anything to the subroutine. The return values
include the following:
0 – The function executed successfully.
-1 – A UniBasic program error occurred in the RUN
process, you are out of memory, or the system is
performing a CHAIN.
-2 – A fatal error occurred or a STOP was encountered in
a UniBasic program.
progname
char
The pointer to the name of the subroutine.
argc
int
The number of arguments passed to the subroutine.
argv
char
The address of an array of pointers to arguments. You
cannot use these arguments to pass anything to the
subroutine.
U_callbas Function Arguments
Note: UniData allocates memory from the process heap every time you execute
U_callbas. In the C program, you must free the memory pointed to by the first
argument or the memory will be unavailable to the system. You must use the
HeapFree API call to free the memory. However, you must free memory only if the
function completes successfully.
udtcallbasic
This function calls a UniBasic subroutine, passing arguments, and returns a
pointer to the results.
Linking C Programs (Windows Platforms Only) 8-53
Syntax:
int udtcallbasic(rtn, progname, argc, arg[0], ..., arg[n])
The syntax of this function is required if the calling language is not C because
the definition of the return buffer is consistent between the external program
and the call with udtcallbasic. The user is responsible for allocating memory
for the buffer to store results. You can execute this function numerous times
in your application when you want to call a UniBasic subroutine.
The following table describes each parameter of the syntax.
Parameter
Data Type
Description
rtn
char
The address of the pointer to the value returned
from the UniBasic subroutine. You cannot use this
argument to pass anything to the subroutine. The
return values include the following:
0 – The function executed successfully.
-1 – A UniBasic program error occurred in the
RUN process, you are out of memory, or the
system is performing a CHAIN.
-2 – A fatal error occurred or a STOP was
encountered in a UniBasic program.
progname
char
The pointer to the name of the subroutine.
argc
int
The number of arguments passed to the
subroutine.
arg[0] ... arg[n]
char
The pointers to arguments passed to the
subroutine.
U_callbas Function Arguments
udtcallbasic_done
The UniData udtcallbasic_done C function clears all UniData-related
temporary files and space, and shuts down the UniData environment.
Syntax:
udtcallbasic_done(status)
int status;
8-54 Developing UniBasic Applications
status is returned, but it currently is not interpreted.
Using CallBasic
You must have the following components to use the CallBasic API:

Development environment – Your system should have a full
software development kit. (A base compiler is not sufficient.)

UniData.LIB file – You must link your external program with the
UniData.LIB file when you build the executable. Interface
information is contained in UniData.LIB.
Consult your host operating system documentation and your hardware
vendor if you have questions about your development environment.
1. Write, Compile, and Catalog the UniBasic Subroutine
Perform the following steps to create a UniBasic subroutine that will be called
from an external program:

Write the UniBasic subroutine.
Write a UniBasic subroutine that includes a RETURN statement. The
subroutine must return control to the external program. For example,
the following subroutine, EXAMPLE, returns a value in RTNVAL.
The remaining arguments, ARG1 and ARG2, pass data from the C
program to the EXAMPLE subroutine.
SUBROUTINE EXAMPLE(RETNVAL,ARG1,ARG2)
PRINT "THE FIRST ARG IS ":ARG1
PRINT "THE SECOND ARG IS ":ARG2
RETNVAL="RETURN"
RETURN
END
This subroutine is included in the BP file of the UniData demo
account.
Linking C Programs (Windows Platforms Only) 8-55

Compile the UniBasic subroutine.
To compile the UniBasic subroutine, use the ECL command BASIC.
For more information about this command, see Chapter 3, “Chapter
3: Creating and Running a Program,” or the UniData Commands
Reference.
In the following example, we compile the EXAMPLE subroutine:
:WHERE
D:\UniData\demo
:BASIC BP EXAMPLE
Compiling Unibasic: BP\EXAMPLE in mode 'u'.
compilation finished
:

Catalog the UniBasic subroutine.
To catalog the UniBasic subroutine, use the ECL command
CATALOG. Depending if you want to access the subroutine from
one account or many, you can catalog it directly, locally, or globally.
In the following example, we demonstrate globally cataloging the
EXAMPLE subroutine:
:CATALOG BP EXAMPLE
D:\UniData\sys\CTLG\e\EXAMPLE has been cataloged, do you want to
overwrite?(y/n)Y
:
2. Code, Compile, and Link the External Program
For information about compiling the external program, see the
documentation for your application development environment. Make
certain that:

Your declarations for the UniBasic functions use the correct syntax
for your programming language.

You link your code with the UniData.LIB file.
Write the external program that calls your UniBasic subroutine. Use the
CallBasic functions in the program as follows:

udtcallbasic_init() – Your program must execute this function once
and only once.
8-56 Developing UniBasic Applications

U_callbas() – You can execute this function numerous times in your
program, each time you want to call a UniBasic subroutine. The
syntax for U_callbas is supported if the calling language is C.

udtcallbasic() – Remember to allocate memory for the buffer to store
results. You can execute this function numerous times in your
program, each time you want to call a UniBasic subroutine.
Linking C Programs (Windows Platforms Only) 8-57

udtcallbasic_done() – Your program must execute this function once.
You must also include this function in any error exits in your
application that could be taken after udtcallbasic_init().
The following example (callbasic_example1.c) shows a program that
calls a UniBasic subroutine:
#include <stdio.h>
#define NT
#ifdef NT
#include <windows.h>
#endif /* NT */
/* Declare UniData callbasic functions */
#ifdef CPP /* for c++ */
extern "C" int udtcallbasic_init(int i, int j);
extern "C" int udtcallbasic(char *xbuf, char *ybuf, int i, char
*arg, ...);
extern "C" int udtcallbasic_done(int k);
extern "C" int U_callbas(char **xbuf, char *ybuf, int i, char
**zbuf);
#else /* for c */
extern int udtcallbasic_init();
extern int udtcallbasic_done();
extern int U_callbas();
#endif /* CPP */
#ifdef NT
extern int udtcallbasic();
#endif /* NT */
void main()
{
/* Declare variables */
char *rtn;
char arg0[BUFSIZ];
char arg1[BUFSIZ];
char *args[2]; int sts;
/* Initialize the UniData environment */
udtcallbasic_init(0,0);
/* Assign arguments for the UniBasic routine */
strcpy(arg0, "Plants");
strcpy(arg1, "Animals");
args[0] = arg0;
args[1] = arg1;
printf("Executing UniBasic subroutine using
U_callbas()...\n");
8-58 Developing UniBasic Applications
/* Call the UniBasic routine */
sts = U_callbas(&rtn, "EXAMPLE", 2, args);
if (sts == 0){
printf("Return value from UniBasic subroutine is
%s\n", rtn);
#ifdef NT
/* Variable rtn returned by UniData come
from the process heap, not the C-Runtime
They must be free'd with HeapFree().
*/
HeapFree(GetProcessHeap(), 0, rtn);
#else
free(rtn);
#endif /* NT */
}
#ifdef NT
/* Allocate memory for return variable */
rtn = (char *)malloc(256);
printf("\nExecuting UniBasic subroutine using
udtcallbasic()...\n");
/* Call the UniBasic subroutine using udtcallbasic. */
sts = udtcallbasic(rtn, "EXAMPLE", 2, args[0], args[1]);
if (sts == 0){
printf("Return value from UniBasic subroutine is
%s\n", rtn);
}
free(rtn);
#endif /* NT */
/* Close everything properly */
udtcallbasic_done(sts);
}
Linking C Programs (Windows Platforms Only) 8-59
The following segment from the makefile for callbasic_example1.c shows
linking with the UniData.LIB file:
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib
comdlg32.lib a\
dvapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib
odbccp32.lib\
/nologo /subsystem:console /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib
comdlg32.lib advapi\
32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib
odbccp32.lib c:\u\
nidata\bin\unidata.lib /nologo /subsystem:console /machine:I386
LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib
comdlg32.lib\
advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib
odbc32.lib\
odbccp32.lib c:\unidata\bin\unidata.lib /nologo
/subsystem:console\
/incremental:no /pdb:"$(OUTDIR)/callbasic_example1.pdb"
/machine:I386\
/out:"$(OUTDIR)/callbasic_example1.exe"
:
The sample program and makefile are included in the CALLBASIC_DEMO
folder in your UniData demo account.
8-60 Developing UniBasic Applications
3. Use the New Executable
To run the new executable and call the UniBasic subroutine, your working
directory must be a UniData account where the subroutine is cataloged. You
can execute your routine from the MS-DOS Command prompt, and you will
need to be sure to specify the full path name of the executable, or include its
location in your PATH. The following screen shows the results of executing
the callbasic_example1 executable from the demo directory:
:!callbasic_example1
UniData is running under a temporary license!
This license will expire in 6 days.
Executing UniBasic subroutine using U_callbas()...
THE FIRST ARG IS Plants
THE SECOND ARG IS Animals
Return value from UniBasic subroutine is RETURN
Executing UniBasic subroutine using udtcallbasic()...
THE FIRST ARG IS Plants
THE SECOND ARG IS Animals
Return value from UniBasic subroutine is RETURN
:
Note: If your UniBasic subroutine is globally cataloged, you can use CallBasic from
any UniData account. You do not need to be in the UniData account where the
subroutine was written.
Linking C Programs (Windows Platforms Only) 8-61
Chapter
Chapter 9: UniBasic Transaction
Processing
In This Chapter . . . . . . . . . . . . .
Transaction Processing Commands . . . . . .
Executing TP and Non-TP Transactions . . .
Starting a Transaction . . . . . . . . .
Committing a Transaction. . . . . . . .
Aborting a Transaction . . . . . . . . .
Testing for an Active Transaction . . . . .
Transaction Processing Programming Example
Transaction Processing Programming Problems .
Transaction Abort . . . . . . . . . .
Degraded Performance. . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9-3
9-4
9-4
9-5
9-6
9-8
9-9
9-10
9-11
9-11
9-15
9
Transaction processing (TP) combines a set of operations in a single logical
function so that the database is maintained in a consistent state throughout
an update, even in the event of a system failure.
To create a UniBasic transaction, you bind the operations by TRANSACTION
START and TRANSACTION COMMIT statements. If the transaction is
unsuccessful, none of the operations within the transaction take place. After
the transaction is initiated, UniData can recover the updated files.
This chapter introduces the UniBasic transaction processing commands.
“Appendix B: UniBasic Transaction Processing Concepts” explains the basic
concepts on which UniData transaction processing is based. For information
about using UniData locks to ensure database consistency, see Chapter 5,
“Chapter 5: Using UniData Locks.”
9-3
In This Chapter
This chapter consists of the following sections:

“Transaction Processing Commands”

“Transaction Processing Commands”

“Transaction Processing Programming Problems”
9-4 Developing UniBasic Applications
Transaction Processing Commands
The following UniBasic commands and the variable @TRANSACTION let
you create UniBasic programs with transaction processing safeguards:

TRANSACTION START

TRANSACTION COMMIT

TRANSACTION ABORT

@TRANSACTION
Warning: When including WRITE statements within a transaction, you must code
an ON ERROR clause that takes appropriate action to notify the user and stop the
transaction. If the transaction is not aborted by the ON ERROR clause, processing
continues, and the transaction commits inappropriately.
Executing TP and Non-TP Transactions
We recommend that you NOT execute at the same time applications that
perform transactions and those that do not. If both types of applications
make updates to the same UniData file at the same time, indexes based on
virtual fields might not be updated, and the only notification you will receive
will be a warning recorded in the sm.log file if you are using the Recoverable
File System (RFS) and a new record was created. If an existing record was
updated, or you are not running RFS, UniData does not generate a warning.
If you cannot avoid simultaneously updating records from non-TP and TP
applications, make sure you include a READU statement before the WRITE
statement. If you set the READU lock outside of the transaction, UniData
maintains the lock after the TRANSACTION COMMIT. If you set the
READU lock within the transaction, the lock is released at the
TRANSACTION COMMIT.
The following demonstrates a scenario that results in indexes based on
virtual fields not being updated because TP and non-TP applications are
running simultaneously:
1.
The TP application initiates a transaction by executing the UniBasic
TRANSACTION START command or by executing any UniData
SQL transaction-initiating command.
Transaction Processing Commands 9-5
2.
The TP transaction writes a new record to a recoverable file. (The
write is held pending the transaction commit.)
3.
The non-TP application executes a write, creating a record with the
same @ID as the one created in Step 2.
4.
When the TP transaction commits, UniData discovers that the record
now exists, so it changes its insert operation to an update. However,
virtual fields are resolved and their index entries are created when a
write is executed within a transaction, not when the transaction is
committed. Because the operation has become an update rather than
insert, no new alternate key index entry is made for the new record.
Starting a Transaction
Use the UniBasic TRANSACTION START command to initiate a transaction.
Syntax:
TRANSACTION START {THEN statement(s) | ELSE statement(s)}
The UniBasic TRANSACTION START command initiates a new transaction
and temporarily stores all updates until a the transaction is committed or
aborted. You must specify a THEN or an ELSE clause, but can specify both.
Note: UniBasic does not support nested transactions. When UniData encounters a
nested transaction, the ELSE clause executes.
STATUS Function Values
After you execute TRANSACTION START, the STATUS function returns one
of the values described in the following table.
Value
Description
0
Successful transaction start.
1
Indicates a nested start.
errno
Indicates an error message.
STATUS Values
9-6 Developing UniBasic Applications
For information about the UniBasic STATUS function, see the UniBasic
Commands Reference.
Example
In the following example, which is taken from the sample program in
“Appendix B: UniBasic Transaction Processing Concepts,” the
TRANSACTION START command starts the transaction process:
TRANSACTION START ELSE
IF STATUS() = 1 THEN
DISPLAY "A Transaction had already been started, NESTED
Transactions"
DISPLAY "are NOT Allowed. (Contact System Administrator)"
INPUT PAUSE,1_
END ELSE
DISPLAY "The Recoverable File System is not enabled."
END
END
Committing a Transaction
Use the UniBasic TRANSACTION COMMIT command to commit a
transaction.
Syntax:
TRANSACTION COMMIT {THEN statement(s) | ELSE
statement(s)}
The UniBasic TRANSACTION COMMIT command concludes the active
transaction. UniData writes all pending updates to the appropriate files. You
must specify a THEN or an ELSE clause, or both. If a TRANSACTION
COMMIT statement executes in the absence of an active transaction, UniData
executes the ELSE clause.
Successful Commit
UniData performs the following steps during a transaction commit:
1.
Writes all updates.
2.
Releases all records locked by this process.
3.
Executes the THEN clause, if included.
Transaction Processing Commands 9-7
4.
Enables the break key.
Unsuccessful Commit
If the transaction cannot commit and the ELSE clause executes, UniData
performs the following steps:
1.
Aborts the transaction without writing records.
2.
Releases all records locked inside the transaction.
3.
Returns the BREAK key to its state prior to the TRANSACTION
START.
STATUS Function Values
After you execute TRANSACTION COMMIT, the STATUS function returns
one of the values described in the following table.
Value
Description
0
Transaction committed.
1
Transaction not started.
3
Transaction cannot commit.
STATUS Values
Example
In the following example, taken from the sample program in “Appendix B:
UniBasic Transaction Processing Concepts,” the TRANSACTION COMMIT
command ends the transaction process and writes the new record to the
database:
TRANSACTION COMMIT
IF STATUS() = 1
DISPLAY "The
END ELSE
DISPLAY "The
END
9-8 Developing UniBasic Applications
ELSE
THEN
TRANSACTION was not started"
TRANSACTION could not be committed."
Aborting a Transaction
Use the UniBasic TRANSACTION ABORT command to abort active
transactions.
Syntax:
TRANSACTION ABORT
The UniBasic TRANSACTION ABORT command cancels the active
transaction, discarding the pending writes. As a result, none of the updates
associated with the transaction occur.
In addition to encountering a TRANSACTION ABORT statement, a
transaction could abort due to any of several conditions, including:

A program finishes before a transaction commit is issued (STOP or
END).

The program CHAINs to another program.

An error terminates the program before a transaction commit is
issued.

A user breaks out of the program before a transaction commit is
issued. This can be prevented programmatically by disabling the
interrupt key during the transaction by executing the ECL command
PTERM -BREAK OFF.

The user running the program is logged out, or the process is killed
before a transaction commit is issued.
The system handles these abort conditions in the same way it does
TRANSACTION ABORT.
Note: You should be aware of these various abort conditions and control the resulting
action from within the program where possible and appropriate. For example, if no
ON ERROR clause is included in a write statement and the write fails, the program
aborts, terminating the transaction.
Transaction Processing Commands 9-9
Example
In the following example, the transaction process aborts if var is 10:
TRANSACTION START
THEN PRINT "Transaction started."
ELSE PRINT "Transaction start failed, STATUS = ":STATUS(); STOP
READU var FROM file.var, record1
var += 2
IF var = 10 THEN TRANSACTION ABORT; GOTO ERR:
WRITE var TO file.var, record1
TRANSACTION COMMIT
THEN PRINT "Transaction committed."
ELSE PRINT "Transaction Aborted, STATUS = ":STATUS(); STOP
Testing for an Active Transaction
The variable @TRANSACTION lets you test for an active transaction. The
following table describes the possible values for @TRANSACTION.
Value
Description
1
A transaction is active.
0
A transaction is not active.
@TRANSACTION Values
9-10 Developing UniBasic Applications
Transaction Processing Programming Example
The following program segment is taken from the sample program in
Appendix B, “Appendix B: UniBasic Transaction Processing Concepts.” Note
that the WRITE commands are enclosed in a transaction, and that both or
neither are committed.
WRITE_RECORD:
* The record(s) have been updated. Make sure that if the
RECOVERABLE
* FILE System is operational that either BOTH records are updated,
or that
* None are (using Transaction processing commands).
TRANSACTION START ELSE
IF STATUS() = 1 THEN
DISPLAY "A Transaction had already been started, NESTED
Transactions"
DISPLAY "are NOT Allowed. (Contact System Administrator)"
INPUT PAUSE,1_
END ELSE
DISPLAY "The Recoverable File System is not enabled."
END
END
WRITE CLIENT.REC ON CLIENT_FILE,CLIENT_NUMBER
WRITE ORDER.REC ON ORDERS_FILE,ORDER_NUMBER
TRANSACTION COMMIT
IF STATUS() = 1
DISPLAY "The
END ELSE
DISPLAY "The
END
ELSE
THEN
TRANSACTION was not started"
TRANSACTION could not be committed."
END
RETURN
Transaction Processing Commands 9-11
Transaction Processing Programming Problems
Programming problems could, in some cases, degrade performance and, in
other cases, cause UniData to abort transactions. This section points out some
of these problems and suggests ways of overcoming them. Many problems
are caused or compounded by improper use of UniData locks. For more
information, see Chapter 5, “Chapter 5: Using UniData Locks.”
Transaction Abort
The following problems cause UniData to abort transactions:

Deadlocks
When two or more programs are waiting for each other to release
records, UniData aborts one of the transactions to unlock its records
and remove the deadlock. You can reduce the likelihood of
deadlocks occurring by imposing a protocol on the order data items
can be updated. For example, the protocol might let programs lock
record B only after locking record A. If all programs on the system
follow this protocol, deadlocks will be avoided.

Upgrading Locks
If two users share a lock on a record, and one or both tries to upgrade
to a U lock, one user receives the upgraded lock, and UniData aborts
the other transaction. Execute the LOCKED clause in the locking
statement to abort the transaction, but not the program.

Nested Transactions
UniData does not support nested transactions. A TRANSACTION
START within another active transaction executes the ELSE clause, if
specified, and aborts the transaction if none is specified. Use the
@TRANSACTION variable to find out if a transaction is active before
calling an external subroutine. For more information about
@TRANSACTION, see “Testing for an Active Transaction” on
page 9-10.
9-12 Developing UniBasic Applications

Called Programs and Subroutines
If a you start a transaction within a called program or subroutine,
and the subroutine stops abnormally, UniData could return control
to the calling program with the transaction still active. To avoid this,
include the ON.ABORT clause in the subroutine to stop program
execution in the event of a problem affecting a subroutine.

Transaction Processing Limitations
UniData does not support transaction processing outside of UniBasic
and UniData SQL. Although you can include EXECUTE statements
within a transaction, you cannot execute any of the following types
of operations with the EXECUTE:

UniData SQL (use EXECUTESQL)

UEntry/UReport

File-level operations on recoverable files (for example, the
UniBasic CLEARFILE commands and the following ECL
commands: CLEAR.FILE, DELETE.FILE, RESIZE,
CREATE.FILE, CREATE.INDEX, BUILD.INDEX,
DELETE.INDEX, CNAME).
If UDT.OPTION 35 is on when you issue an EXECUTE statement,
UniData creates a new user and processes it independently from the
original UniBasic program, causing unpredictable results in TP.
Make sure UDT.OPTION 35 is off when using TP.
Transaction Processing Programming Problems 9-13
Examples
Recovering from a deadlock – In this example, a deadlock results because
two programs are waiting for each other to release the same record (steps 3
and 4). UniBasic aborts Transaction 2 to break the deadlock when this
transaction tries to upgrade to an exclusive lock. However, the LOCKED
clause (LOCKED TRANSACTION ABORT) in the RECORDLOCKU
statement tells UniBasic to abort the transaction only, thus preventing the
program from aborting.
Step
Transaction 1
Transaction 2
1
TRAN1:
TRANSACTION START
THEN PRINT "Transaction
started."
READL var1 FROM file.var,
record1
ELSE PRINT "File not
found."
TRAN2:
TRANSACTION START
THEN PRINT "Transaction
started."
2
3
READL var1 FROM file.var,
record1
ELSE PRINT "File not
found."
RECORDLOCKU var1
LOCKED TRANSACTION ABORT;
GOSUB TRAN1
4
var += 2
WRITE var TO file.var,
record1
TRANSACTION COMMIT
THEN PRINT "Transaction
committed."
RECORDLOCKU var1
LOCKED TRANSACTION ABORT;
GOTO TRAN2
var += 2
WRITE var TO file.var,
record1
TRANSACTION COMMIT
THEN PRINT "Transaction
committed."
Avoiding Program Abort on Deadlock
9-14 Developing UniBasic Applications
Nested transactions – The next three examples demonstrate creating and
avoiding nested transactions. In this first program segment, the main routine
starts a transaction, and then calls a subroutine that contains a
TRANSACTION START. This always results in an aborted transaction
because transactions cannot be nested.
TRANSACTION START ELSE PRINT "Transaction error.";STOP
READU var FROM file.var, record1 ELSE PRINT "File not found."
IF var = "" THEN CALL SUBRZ
WRITE var TO file.var, record1
TRANSACTION COMMIT ELSE PRINT "Transaction aborted."
SUBROUTINE SUBRZ
TRANSACTION START ELSE PRINT "Transaction error.";STOP
READU var1 FROM file.var, record2 ELSE PRINT "File not found."
var1 += 2
WRITE var1 TO file.var, record2
TRANSACTION COMMIT ELSE PRINT "Transaction aborted."
The next program segment avoids the problem demonstrated in the previous
example. The subroutine tests @TRANSACTION for an active transaction
before starting its own transaction.
TRANSACTION START ELSE PRINT "Transaction error.";STOP
READU var FROM file.var, record1
IF var = "" THEN CALL SUBRZ
WRITE var TO file.var, record1
TRANSACTION COMMIT ELSE PRINT "Transaction aborted."
SUBROUTINE SUBRZ
IF @TRANSACTION = 1 THEN RETURN
TRANSACTION START ELSE PRINT "Transaction error.";STOP
READU var1 FROM file.var, record2
var1 += 2
WRITE var1 TO file.var, record2
TRANSACTION COMMIT ELSE PRINT "Transaction aborted."
In the following program, the subroutine tests for an active transaction with
@TRANSACTION before an EXECUTE statement:
TRANSACTION START
ELSE PRINT "Transaction not started."; STOP
READU var FROM file.var, record1
IF var = "" THEN CALL SUBRZ
WRITE var TO file.var, record1
TRANSACTION COMMIT ELSE PRINT "Transaction aborted."
SUBROUTINE SUBRZ
IF @TRANSACTION = 0 THEN EXECUTE "CLEAR.FILE file.var" ELSE
RETURN
RETURN
Transaction Processing Programming Problems 9-15
Degraded Performance
This section discusses causes and solutions of degraded performance.

Locked Records
If you lock records within a transaction, those records remain locked
until you commit or abort the transaction. If your program performs
a number of operations, especially requesting user input, you
increase the waiting period for other users trying to access these
records. To avoid this problem, put input statements before the
TRANSACTION START. If you must include requests for user
interaction within a transaction, include the WAITING clause in the
INPUT statement to time out if the user does not respond in a
reasonable amount of time.

Long Transactions
Performing a number of operations inside a transaction also
contributes to performance degradation. By minimizing the number
of operations in the transaction, you can improve performance.
Note: Protecting data by locking records and using transaction processing can
contribute to reduced efficiency and system performance.
Example
In the following example, the INPUT times out after five minutes if the user
does not respond:
READU var FROM file.var, record1 ELSE PRINT "File not found."
INPUT var WAITING 300
TRANSACTION START ELSE PRINT "Transaction not started." STOP
WRITE var TO file.var, record1
TRANSACTION COMMIT ELSE PRINT "Transaction aborted."
9-16 Developing UniBasic Applications
Chapter
Chapter 10: Null Value Handling
Representing Unknown Values . . . . . . . .
Turning Null Value Handling Off . . . . . .
Null Value Handling in UniBasic. . . . . . . .
Printing and Displaying . . . . . . . . .
Sorting and Indexing . . . . . . . . . .
Summary of Effects on Commands and Functions
The Null Value in Numeric Functions . . . .
The Null Value in Conditional Tests . . . . .
The Null Value in Conversion Functions. . . .
The Null Value in String Functions . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 10-3
. 10-3
. 10-4
. 10-4
. 10-4
. 10-5
. 10-6
. 10-8
. 10-14
. 10-15
10
This chapter introduces the null value handling in UniBasic. UniData
supports null value handling on both UNIX and Windows systems. You can
turn on null value handling, which makes the UniData RDBMS more
compliant with the standards defined by ANSI SQL ‘92. Compliance
improves compatibility with client and desktop tools.
10-3
Representing Unknown Values
If you turn null value handling on, and you have accepted the default
language group when installing UniData, the null value is represented by the
ASCII character 129. If you change language groups, a different character
could be assigned to represent the null value. For this reason, we recommend
that you use the @NULL variable to represent the null value in UniBasic.
Tip: To determine which ASCII value represents the null value on your system, use
the UniBasic command SEQ(@NULL).
Turning Null Value Handling Off
When you install UniData, null value handling is turned off by default (the
udtconfig parameter NULL_FLAG = 0). With null value handling turned off,
an empty string represents unknown or missing values, and UniBasic
programs that contain the variable @NULL will not compile. If you try to
execute object code containing @NULL, the program terminates with a fatal
error.
After turning null value handling on, we recommend that you not turn it off,
as the null character could have been introduced to your data. If you must
turn null value handling off, be sure to check your data and convert the null
value to another string (using a UniBasic program or virtual attribute) before
trying to execute queries, virtual attributes, or UniBasic programs. You will
also need to regenerate any indexes that could contain null values; if you do
not, records with null alternate key values cannot be retrieved.
10-4 Developing UniBasic Applications
Null Value Handling in UniBasic
The presence of the null value in data or function/command arguments
profoundly affects the results of UniBasic commands, functions, and
arithmetic operators. These effects differ according to whether null value
handling is turned on or off, and range from UniBasic ignoring the character
to generating a runtime error.
Printing and Displaying
The ASCII character that represents the null value is nonprinting, and the
environment variable NVLMARK, which specifies an alternative character to
represent the null value for display or printing in UniData and UniQuery, has
no effect in UniBasic. Although no comparable function exists in UniBasic,
you can use the UniBasic conversion commands, such as SWAP and
CONVERT, or the UniData SQL command NVL to convert the null value to
another string before printing.
Sorting and Indexing
Null Value Handling On – The null character is always sorted as the lowest
value, lower than all negative numbers.
Tip: Use the UniBasic command SETINDEX with the rop operator
NULL_VAL_ALT_KEY to set the alternate key index pointer to the null value (the
beginning of the index). The rop operator FIRST_ALT_KEY sets the pointer to the
first non-null key.
Null Value Handling off – The character that would represent the null value
is sorted normally, by ASCII value.
Null Value Handling in UniBasic 10-5
Summary of Effects on Commands and Functions
The following table summarizes the effects of the null value in data and
function or command arguments. Details are provided in the sections that
follow.
Type of Operation
Null Value Handling On
Null Value Handling Off
Numeric calculations
Result is the null value.
Original value is returned.
UniBasic returns the
message “Non-numeric
found when numeric
required.”
Relational operations
Follow the ANSI SQL
3-way logic rules.
No special handling of the
null value. Follow the ANSI
SQL 2-way logic rules.
Conversions
Result is the null value;
invalid for format code.
No special handling of the
null value; invalid for format
code.
String functions
No special handling of the
null value. In a parameter,
if numeric is required,
displays a warning
message and uses 0.
No special handling of the
null value. In a parameter, if
numeric is required,
generates runtime error.
Sorting and indexing
Null value is lowest
(smaller than all negative
numbers).
No special handling of the
null value. Sorting is based
on ASCII values.
Null Value Handling Summary
10-6 Developing UniBasic Applications
The Null Value in Numeric Functions
The presence of the null character in data affects numeric functions whether
nulls are turned on or off. The following table lists the UniBasic numeric
operators and functions.
Numeric Operators and Functions
+
-
*
/
^
ABS
ACOS
ASIN
ATAN
BITAND
BITNOT
BITOR
BITXOR
COS
DROUND
EXP
INT
LN
MOD
NEG
POWER
PWR
RND
SADD
SCMP
SDIV
SIN
SMUL
SQRT
SSUB
TAN
Numeric Operators and Functions Affected by the Null Value
Null Value Handling On
In Data – The result of any numeric function on data containing the null value
is the null value.
In Arguments – Any time UniBasic encounters a null value as a command or
function argument when a number is required, UniBasic prints a warning
message and uses 0 as the function argument.
Note: When a UniBasic numeric function encounters a non-null, nonnumeric
character, UniBasic issues a warning message: Non-numeric found when
numeric required; the command or function processes, using 0 in place of the
invalid character.
Null Value Handling in UniBasic 10-7
Examples
The following program demonstrates the principle that any arithmetic
operation on data that contains the null value results in the null value:
A = 100 + @NULL
GOSUB PRINTNULL
PRINT "100 + @NULL = ":A
A = 0
A = SIN(@NULL)
GOSUB PRINTNULL
PRINT "SIN(@NULL) = ":A
A = 0
A = 99/@NULL
GOSUB PRINTNULL
PRINT "99/@NULL = ":A
A = 0
A = 33/99
GOSUB PRINTNULL
PRINT "33/99 = ":A
STOP
PRINTNULL:
IF ISNV(A) THEN SWAP @NULL WITH "null value" IN A
RETURN
The preceding program prints the following:
100 + @NULL = null value
SIN(@NULL) = null value
99/@NULL = null value
33/99 = 0.3333
Null Value Handling Off
The null value is invalid. When a numeric function encounters the null value
as data or an argument, UniBasic displays a warning message and uses 0.
Note: UniBasic programs containing @NULL will not compile when null value
handling is turned off.
10-8 Developing UniBasic Applications
The Null Value in Conditional Tests
The presence of the null value in data affects conditional tests only if null
value handling is turned on; then it is evaluated to false (0). You perform
conditional tests in UniBasic with the following statements:

IF/THEN/ELSE

CASE
Within these statements, you can nest additional conditions using the
following keywords:

WHILE

UNTIL
Examples
The following examples demonstrate the effect of the null value in
IF/THEN/ELSE and CASE statements.
Null Value Handling in UniBasic 10-9
In this first example, COUNT.TO.TEN does not execute because X is the null
value. Instead, the program prints @NULL evaluates to 'false'.
X=@NULL
IF X THEN GOSUB COUNT.TO.TEN ELSE GOSUB NO.COUNT
STOP
COUNT.TO.TEN:
FOR X = 1 TO 10
PRINT X
NEXT X
RETURN
NO.COUNT:
PRINT "@NULL evaluates to 'false'"
RETURN
In the following program segment, CASE 1 executes because X is the
null value. The program segment prints Deleting corrupt record;
@ID is null.
X = @NULL
BEGIN CASE
CASE X = 1
GOSUB ADD_RECORD
CASE X = 2
GOSUB UPDATE_RECORD
CASE X = 3
FINISHED = 1
CASE 1
GOSUB DELETE_RECORD
END CASE
STOP
ADD_RECORD:
PRINT "Adding a record."
RETURN
UPDATE_RECORD:
PRINT "Updating a record."
RETURN
DELETE_RECORD:
PRINT "Deleting corrupt record; @ID is null."
RETURN
10-10 Developing UniBasic Applications
Comparison Operators Used in Conditional Tests
The following table lists the UniBasic comparison operators.
Comparison Operators
NOT
NOTS
LE, <=, =<
LES
LT, <
LTS
EQ, =
EQS
NE, #, <>,
><
NES
GE, >=, =>
GES
GT, >
GTS
AND
OR
#>
#<
Comparison Operators
Note: The logical operators NULL and NOT @NULL are both evaluated to the null
value, which produces a result of false in a conditional test.
Null Value Handling On
The results of comparisons using the logical operator AND are provided in
the following truth table (T, F, and N represent TRUE, FALSE, and @NULL,
respectively).
T
F
N
T
T
F
N
F
F
F
F
N
N
F
N
Null in Conditional Statements with AND
Null Value Handling in UniBasic 10-11
The results of comparisons using the logical operator OR are provided in the
following table (T, F, and N represent TRUE, FALSE, and @NULL,
respectively).
T
F
N
T
T
T
T
F
T
F
N
N
T
N
N
Null in Conditional Statements with OR
You can use the following sample program to test comparisons of the logical
operator OR. To use the program to test the logical operator AND, substitute
AND for OR in this program.
PROMPT @(-1)
PROMPT''
PRINT "First value: Enter true, false, or null: ":
INPUT answer
answer = UPCASE(answer)
GOSUB SET.VAL
val.one=value
PRINT "Second value: Enter true, false, or null: ":
INPUT answer
answer = UPCASE(answer)
GOSUB SET.VAL
val.two=value
BEGIN CASE
CASE (val.one OR val.two ) = 1
PRINT "Condition is true."
CASE ISNV(val.one OR val.two) = 1
PRINT "Condition is null"
CASE 1
PRINT "Condition is false."
END CASE
SET.VAL:
IF answer = "NULL" THEN value = @NULL
IF answer = "TRUE" THEN value = 1
IF answer = "FALSE" THEN value = 0
RETURN
10-12 Developing UniBasic Applications
The following example shows a sample run of this program. It demonstrates
that the comparison ‘true OR null’ produces a result of ‘true’.
:RUN BP null.compare
First value: Enter true, false, or null: true
Second value: Enter true, false, or null: null
Condition is true.
Examples
The following program segments demonstrate the effect of the null value on
comparisons.
In a comparison test, the null value is interpreted by UniBasic as false.
A comparison with the null value produces a result of null:
X = @NULL
IF NOT(X) THEN
PRINT "1a. NOT(NULL) is TRUE"
END ELSE
Y = NOT(X)
PRINT "1b. NOT(NULL) IS FALSE"
END
This program segment prints the following:
1b. NOT(NULL) IS FALSE
Any number is greater than the null value:
X = @NULL
IF (100 LE X) THEN
PRINT "2a. 100 LE NULL is TRUE"
END ELSE
PRINT "2b. 100 LE NULL IS FALSE"
END
PRINT "
100 LE NULL evaluates to ":(100 LE @NULL)
This program segment prints:
2b. 100 LE NULL IS FALSE
100 LE NULL evaluates to 0
Null Value Handling in UniBasic 10-13
The null value compared to the null value yields a result of false:
X = @NULL
IF (X EQ X) THEN
PRINT "3a. NULL EQ NULL IS TRUE"
END ELSE
PRINT "3b. NULL EQ NULL IS FALSE"
END
PRINT "
NULL EQ NULL evaluates to ":(@NULL EQ @NULL)
This program segment prints:
3b. NULL EQ NULL IS FALSE
NULL EQ NULL evaluates to 0
Anything AND the null value yields a result of false:
X = @NULL
IF (1 AND X) THEN
PRINT "4a. 1 AND NULL IS TRUE"
END ELSE
PRINT "4b. 1 AND NULL IS FALSE"
END
PRINT "
1 AND NULL evaluates to ":(1 AND @NULL)
This program segment prints:
4b. 1 AND NULL IS FALSE
1 AND NULL evaluates to
Anything OR the null value yields a result of true:
X = @NULL
IF (1 OR X) THEN
PRINT "5a. 1 OR NULL IS TRUE"
END ELSE
PRINT "5b. 1 OR NULL IS FALSE"
END
PRINT '
1 OR NULL evaluates to ":(1 OR @NULL)
This program segment prints:
5a. 1 OR NULL IS TRUE
1 OR NULL evaluates to 1
Null Value Handling Off
The UniBasic comparison operations follow ANSI 2-way logic, with the null
value being compared as its ASCII value (ASCII character 129 in the English
language group).
10-14 Developing UniBasic Applications
The Null Value in Conversion Functions
The following table lists the UniBasic conversion functions.
Conversion Functions
ICONV
OCONV
FMT
ICONVS
OCONVS
FMTS
Conversion Functions Affected by the Null Value
Null Value Handling On
In Data – For conversion functions ICONV/S, OCONV/S, and FMT/S, the
presence of the null value in data produces a result of the null value, and the
STATUS function return value is set to 5.
In Parameters – If a UniBasic program tries to use the null value as a
conversion specification, UniBasic generates a runtime error and returns the
input string. The STATUS function then returns 2, indicating an invalid
conversion specification.
Tip: Some string functions also perform conversions of various kinds while ignoring
the null value. Use the conversion functions in the preceding table when you want to
produce a null value when a null value is input; use the string functions to convert
between the null value and some other string.
Examples
The following example demonstrates that an ICONV conversion on data
containing the null value produces a result of the null value:
X = ICONV(@NULL,"D2/")
PRINT "STATUS FOR ICONV IS ":STATUS()
IF ISNV(X) THEN PRINT "ICONV on the null value produces the null
value."
This program produces the following results:
STATUS FOR ICONV IS 5
ICONV on the null value produces the null value.
Note: If the null value is found in a string to be converted, the UniBasic STATUS
function return value is set to 5.
Null Value Handling in UniBasic 10-15
This example demonstrates that @NULL in an argument produces no result,
but sets the STATUS return code to 2:
PRINT = ICONV(1234,"@NULL")
PRINT "STATUS FOR ICONV IS ":STATUS()
This program produces the following results:
STATUS FOR ICONV IS 2
Null Value Handling Off
In Data – The ASCII character that would represent the null value in data has
no effect on conversion functions.
In Parameters – The result is the null value; invalid format code.
The Null Value in String Functions
The following general guidelines apply to all string functions:
Null Value Handling On
In Data – The UniBasic string commands and functions are not affected by the
null value in data. Therefore, you can use them to manipulate the null
character just as you would any other character. Exceptions to this rule are
listed in the following sections.
The following list highlights some points to remember about the null value
in string functions:

ISNV and ISNVS test for the null value as a string or dynamic array
element.

SEQ(@NULL) returns the ASCII code representing @NULL on your
system.

You cannot use an expression like “IF CUSTOMER = @NULL” to
search for the null value. You must instead test for a return value of
1 from ISNV or ISNVS to determine if a variable or element is the null
value.
10-16 Developing UniBasic Applications

You cannot use NOT(@NULL) to test for the absence of the null value
because NOT(@NULL) evaluates to the null value. You must instead
test for a return value of 0 from ISNV or ISNVS.

You can assign the null value to a variable in the following way:
X=@NULL
In Parameters – Whenever a numeric parameter is required, and the null
value is encountered, UniBasic displays the warning message “Null value
found when numeric required”; UniBasic then continues processing using
0 as the parameter.
Null Value Handling Off
In Data – The ASCII character that would represent the null value has no
effect on conversion functions when encountered in data. You can use these
functions to locate and change any ASCII value, including nulls.
In Parameters – Whenever a numeric parameter is required, and the null
value is encountered, UniBasic displays the warning message “Non-numeric
found when numeric required”; UniBasic continues processing using 0 as
the function parameter.
Null Value Handling in UniBasic 10-17
Summary
The null value receives no special handling in string functions. It is processed
as any other character. If UniBasic encounters the null value in a command
parameter when a number is expected, it displays a warning message and
uses 0.
Type
Commands/Functions
String conversion
ASCII, CHANGE, CHAR, CHARS, CONVERT, EBCDIC,
REPLACE, SEQ, SEQS
DOWNCASE, UPCASE,
SOUNDEX
Special characters, including the null value, are ignored.
They remain unchanged in function output.
Testing for data type
ALPHA (alphabetic)
NUM, NUMS (numeric)
ISNV, ISNVS (null value)
Special characters, including the null value, are not
alphabetic or numeric.
Conditional tests
IF/THEN/ELSE, CASE, WHILE, UNTIL
For more information see “The Null Value in Conditional
Tests” on page 10-9.
Concatenation
: (colon), CAT, CATS, SPLICE
Insertion
[], FIELDSTORE, GROUPSTORE, INS, INSERT
Deletion
DEL, DELETE
Location
GROUP, MATCHFIELD, XLATE
FIND, FINDSTR, LOCATE, MATCH, MATCHES
Extraction
[ ], EXTRACT, FIELD, FIELDS, REMOVE, SUBSTRINGS,
TRIM, TRIMB, TRIMF, TRIMS
Counting
COUNT, COUNTS, DCOUNT
Returning System,
File, Printer, and User
Information
DIR, SYSTEM, GETPTR, GETPU, GETUSERGROUP,
GETUSERNAME
UniBasic String Function Types
10-18 Developing UniBasic Applications
Type
Commands/Functions
Measuring Length
LEN, LENS
The null value has a length of 1 byte.
Repeating
STR, STRS
SPACE, SPACES
These functions produce a string or array elements made
up of spaces.
UniBasic String Function Types (continued)
Examples
The following subroutine converts UniData delimiters and the null value to
printable characters:
* externally cataloged subroutine to convert the null value
* and UniData delimiters for printing
SUBROUTINE null.swap(A)
SWAP @NULL WITH "null value" IN A
SWAP @AM WITH "AM" IN A
SWAP @VM WITH "VM" IN A
SWAP @SM WITH "SM" IN A
RETURN
Testing for Data Type
The following program uses the function ISNV to test a string to see if it
consists of the null value:
A = 0
PRINT "1.a ISNV(0) = ":ISNV(A)
PRINT "2.a NOT(ISNV(0)) = ":NOT(ISNV(0))
A = "abc":@NULL:"def"
PRINT "1.b ISNV('abc':@NULL:'def') = ":ISNV(A)
PRINT "2.b NOT(ISNV('abc':@NULL:'def')) = ":NOT(ISNV(A))
A = @NULL
PRINT "1.c ISNV(@NULL) = ":ISNV(A)
PRINT "2.c NOT(ISNV(@NULL)) = ":NOT(ISNV(A))
Null Value Handling in UniBasic 10-19
This program produces the following results. Notice that ISNV executed on
a string that contains the null value and other characters produces a negative
result (0).
1.a
2.a
1.b
2.b
1.c
2.c
ISNV(0) = 0
NOT(ISNV(0)) = 1
ISNV('abc':@NULL:'def') = 0
NOT(ISNV('abc':@NULL:'def')) = 1
ISNV(@NULL) = 1
NOT(ISNV(@NULL)) = 0
The following program segment inserts the null value into or deletes the null
value from a multivalue in the QUANTITY attribute for a selected record in
the INVENTORY demo database file:
OPEN 'INVENTORY' TO INV_FILE ELSE
PRINT "OPEN error"
STOP
END
PRINT "Enter record to be modified: ":
INPUT rec
READU REC_ARRAY FROM INV_FILE,rec LOCKED PRINT "Record locked."
ELSE PRINT "Record not found."
LOCATE @NULL IN REC_ARRAY<6,1,0> SETTING POINT THEN
PRINT "The null value already exists in the array at position
":
PRINT POINT
PRINT "Deleting the null value."
DEL REC_ARRAY<6,POINT>
PRINT "@NULL deleted from the array at position ":POINT
PRT_ARRAY = CHANGE(REC_ARRAY,@NULL,"@NULL")
FOR X = 1 TO POINT
PRINT PRT_ARRAY<6,X>
NEXT X
WRITE REC_ARRAY TO INV_FILE,rec ON ERROR PRINT "WRITEerror."
END ELSE
REC_ARRAY<6,POINT> = @NULL
PRINT "@NULL placed in the array at position ":POINT
PRT_ARRAY = CHANGE(REC_ARRAY,@NULL,"@NULL")
FOR X = 1 TO POINT
PRINT PRT_ARRAY<6,X>
NEXT X
WRITE REC_ARRAY TO INV_FILE,rec ON ERROR PRINT "WRITEerror."
END
STOP
10-20 Developing UniBasic Applications
The following example shows a a sample run of the preceding program:
:RUN BP null.del.ins
Enter record to be modified: ?56070
@NULL placed in the array at position 5
400
500
394
399
@NULL
A second execution of this program produces the following results:
:RUN BP null.del.ins
Enter record to be modified: ?56070
The null value already exists in the array at position 5
Deleting the null value.
@NULL deleted from the array at position 5
400
500
394
399
Note: When a numeric parameter is required, and the null value is encountered,
UniBasic displays the warning message “Null value found when numeric
required”; UniBasic continues processing using 0 as the function parameter.
Sorting and Indexing
The following series of examples demonstrates the use of SETINDEX to set
the record pointer to the first null key in the PROD_NAME alternate key
index:
OPEN 'INVENTORY' TO inventory ELSE PRINT "Open error"
SETINDEX 'PROD_NAME', NULLVAL_ALT_KEY inventory
FOR X = 1 TO 5
READFWD rec FROM inventory THEN
PRINT rec<0>:", ":rec<3>:", ":rec<4>
END ELSE NULL
NEXT X
STOP
Null Value Handling in UniBasic 10-21
This program produces the following result when no null values exist in the
PROD_NAME index:
:RUN BP set.idx
10020, Adapter, A/C Adapter for notebook computers
10086, Adapter, Ethernet LC Card
10092, Adapter, Workgroup Hub
10082, CD Player, Portable Model
10104, CD Player, Personal Model, Bass Boost
After the null value is inserted into the PROD_NAME attribute for records
10008 and 56060, the same program produces the following results:
:RUN BP set.idx
10015, , Portable, B/W, 6 ppm
10238, , Super Deluxe Model
10020, Adapter, A/C Adapter for notebook computers
10086, Adapter, Ethernet LC Card
10092, Adapter, Workgroup Hub
10-22 Developing UniBasic Applications
Chapter
Chapter 11: Managing Named
Pipes
Points to Remember . . . .
OSOPEN . . . . . . . . .
Opening Named Pipes . . .
OSBREAD . . . . . . . .
Reading from a Named Pipe .
OSBWRITE . . . . . . . .
Writing to Named Pipes . .
OSCLOSE. . . . . . . . .
STATUS Function Return Values .
INMAT . . . . . . . . .
Troubleshooting . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
.
.
.
.
.
.
.
.
.
.
.
11-3
11-4
11-5
11-8
11-8
11-12
11-12
11-15
11-16
11-17
11-18
The UniBasic OSOPEN, OSBREAD, and OSBWRITE commands enable
UniBasic programs to interface, through named pipes, with RedBack
(UniData’s web application development software), other UniBasic
programs, or other non-UniData processes.
Note: You cannot use the READSEQ command to read a named pipe, nor can you
use the WRITESEQ or WRITESEQF command to write to a named pipe. You must
use OSBREAD and OSBWRITE to perform these tasks.
Three factors affect the operation of these commands:

Is the target of the command a named pipe or a file?

For a named pipe – Is it open for a complementary operation on the
other end? (When read access is requested, is the pipe open for
writing on the other end? Conversely, when write access is
requested, is the pipe open for reading on the other end?)

For a named pipe – Is data stored in the pipe, and is it of the length
specified in the command?
Processing can be modified to accommodate different circumstances. For
example, a single process can open the pipe for read-write access, and then
coordinate access programmatically. Also, a process can force the requested
access by executing the NODELAY option.
For UNIX, use the UNIX mkfifo command to create a named pipe, as in the
following example:
Note: To execute a UNIX command from the ECL prompt, precede it with the
UniData bang (!) command.
:!mkfifo pipe_file
:ls -l pipe_file
prw-rw-rwpipe_file
1 carolw
unisrc
0 Jul 28 09:55
For Windows platforms, use the following Win32 APIs to create a named
pipe:

CreateNamedPipe() – Creates an instance of a named pipe on the
local machine. For open mode, specify PIPE_ACCESS_DUPLEX.
You also can specify a timeout value, which the UniBasic OSOPEN
command uses to wait for an available instance.
11-3

ConnectNamedPipe() – Establishes a connection between the server
pipe instance and the client side.

DisconnectNamedPipe() – Returns the server pipe instance to a
listening state. It does not destroy the pipe. Use this API if your
server uses multiple pipe instances.

CloseHandle() – Destroys the pipe when the last pipe instance closes.
Use this API when you no longer need the pipe.
Points to Remember
Keep in mind the following points when accessing named pipes:

When a process reads from a pipe, the data is removed from the pipe.
This differs from a process reading from a file, in which case the data
is copied into a variable, but also remains in the file.

If the NODELAY keyword is not specified, and a process tries to
access (open, read, or write) a pipe that is not already open in the
opposite mode, the process waits for the pipe to be accessed in the
opposite mode before proceeding. This can give the appearance of a
hung process. (Opposite mode means that if you try to open a pipe
for reading, it also must be open for writing on the other end. If you
try to open a pipe for writing, it also must be open for reading on the
other end.)

UniData cannot temporarily close named pipes to manage the
operating system’s limitation on the maximum number of files
allowed to be opened at a time. Therefore, opening a large number of
named pipes at the same time can cause a process to try to open more
than the maximum number of files allowed by the operating system.

For UniData for UNIX only, the PIPE_BUF system variable
determines the length of the named pipe. For a write (OSBWRITE) to
be atomic, the length of data to be written must be less than or equal
to the value of the PIPE_BUF. Maximum length for PIPE_BUF is
determined by the operating system. For information about system
variables, see Administering UniData on UNIX.
The sections that follow describe the syntax and processing for OSOPEN,
OSBREAD, and OSBWRITE for use with named pipes.
11-4 Developing UniBasic Applications
OSOPEN
Syntax:
OSOPEN filename [READONLY | WRITEONLY] TO file.var
[NODELAY] [ON ERROR statements] {THEN statements [END] |
ELSE statements [END]}
Note: If filename is not a named pipe, the NODELAY keyword has no effect.
On UniData for UNIX, filename in the UniBasic OSOPEN command must
include the entire path unless the pipe or file resides in the current directory.
Note: The maximum length of the file name path cannot exceed 254 characters.
On UniData for Windows Platforms, use the filename format
\\computername\PIPE\pipename
where computername is a valid computer name or a period (.), which specifies
the local machine, and pipename is the name of a pipe.
The keywords READONLY, WRITEONLY, and NODELAY are used with
named pipes:

READONLY opens the pipe or file for read access only.

WRITEONLY opens the pipe or file for write access only.
Note: To specify that a pipe be open for both read and write access, omit the
READONLY and WRITEONLY keywords from the OSOPEN statement.

NODELAY forces a pipe to be opened immediately. This lets a
process continue even when the pipe is not open in the opposite
access mode. The application then must manage access to the pipe to
ensure that it is opened for the opposite process before reading from
or writing to it.
Note: If OSOPEN against a named pipe is successful, the file pointer is set to the
beginning of the file.
OSOPEN 11-5
Opening Named Pipes
The following table, which applies to UniData for UNIX only, summarizes
the actions UniBasic takes when OSOPEN is executed against a named pipe.
Mode
Open1
NODELAY
Action
read-write
no effect
no effect
Depends on whether the
operating system supports
read-write access mode:
read-only
write-only
STATUS2
■
OS supports read-write
mode – opens.
0
■
OS does not support
read-write mode –
executes ON ERROR or
aborts.
2
yes
no effect
Opens.
0
no
no
The process waits until the
pipe is opened in writeonly mode or read-write
mode on the other end.
0
after open
no
yes
Opens.
0
yes
no effect
Opens.
0
no
no
The process waits until the
pipe is opened in read-only
mode or read-write mode
on the other end.
0
after open
no
yes
ELSE clause executes.
3
Summary of OSOPEN Functionality with Named Pipes (UniData for UNIX Only)
1Is
the pipe open in the opposite mode?
2
What is the UniBasic STATUS function setting after this operation?
Opening a Pipe in Read-Write Access Mode
Processing depends on whether the operating system supports this access
mode.
11-6 Developing UniBasic Applications

OS supports read-write access mode – UniBasic forces the pipe to
open immediately.

OS does not support read-write access mode – The ON ERROR
clause executes if specified, or the program aborts; the UniBasic
STATUS function is set to 2.
Opening a Pipe in Read-Only Access Mode
Processing depends on whether the pipe is open for writing on the other end:

If the pipe is open for writing on the other end, UniBasic opens the
pipe for read-only access.

If the pipe is NOT open for writing on the other end:

NODELAY specified – UniBasic forces the pipe open
immediately in the requested mode.

NODELAY not specified – The process waits until the pipe is
opened in write-only mode on the other end.
Opening a Pipe in Write-Only Access Mode
Processing depends on whether the pipe is open for reading on the other end:

If the pipe is open for reading on the other end, UniBasic opens the
pipe for write-only access.

If the pipe is NOT open for reading on the other end:

NODELAY specified – The ELSE clause executes; STATUS is set
to 3.

NODELAY not specified – The process waits until the pipe is
opened in read-only mode on the other end.
More about Opening Named Pipes (Windows Platforms)
On UniData for Windows platforms, the following behavior occurs when
you attempt to open a named pipe:

If the named pipe exists, and if an instance is available, the OSOPEN
command opens the pipe immediately.
OSOPEN 11-7

If NODELAY is specified, and if the named pipe does not exist or no
instance is available, the ELSE clause executes, and STATUS is set to
4.

If NODELAY is not specified:

If all instances are busy, the process waits until an instance is
available, and then the pipe opens. If the timeout period expires,
the ELSE statement executes and the UniBasic STATUS function
is set to 4.

If the named pipe does not exist, the process waits for five
minutes before the ELSE statement executes.
11-8 Developing UniBasic Applications
OSBREAD
OSBREAD var FROM file.var [AT byte.expr] LENGTH length.expr
[NODELAY] [ON ERROR statements]
The UniBasic OSBREAD command accommodates reading from named
pipes:

The AT clause is not allowed. AT is not appropriate for use with
named pipes because they are always read with no offset.

The NODELAY keyword forces UniData to read the pipe
immediately, which lets a reading process continue even when the
pipe is not open for writing or no data exists in the pipe.
Note: If you do not specify NODELAY on an OSBREAD against a named pipe, the
process trying to read waits until the pipe is opened for writing, and data is written
to it, before reading from the pipe.
Reading from a Named Pipe
Keep these points in mind when writing programs that read from named
pipes:

Unlike a typical read, reading a named pipe removes the data.

Your application should check the length of var after reading a pipe.
The value returned could be shorter than length.expr.

Data is truncated if it is longer than length.expr.

On UniData for Windows Platforms, you can set a named pipe to
message-read mode by indicating -1 for length.expr. To set a named
pipe to byte-read mode, set length.expr to a value greater than -1. Data
in message mode always has a boundary, and it can be obtained in its
entirety. Data in byte mode has no boundary. The reader must
specify a length and have some kind of protocol with the server to
interpret it correctly.
OSBREAD 11-9
Summary for Reading Named Pipes
The combination of the following conditions and command options
determine the action taken by UniBasic when OSBREAD is executed against
a named pipe:

Presence or absence of data in the pipe.

Open/closed status of the pipe.

Presence or absence of the AT and NODELAY command options.
The following table, which applies to UniData for UNIX only, summarizes
actions taken by UniBasic for each possible combination of these factors.
AT
Data1
Open2
yes
no effect no effect no effect
NODELAY
Action
STATUS3
ON ERROR executes; if no
ON ERROR clause, the
program aborts.
2
ON ERROR executes
regardless of other conditions (presence of data;
open/closed status;
NODELAY).
no
yes
no effect no effect
UniBasic returns data, of
the length requested, from
the pipe.
0
no
no
no
no effect
UniBasic returns actual
content of the pipe
regardless of its length. Can
be an empty string.
0
no
no
yes
no
The process waits for data
to be written to the pipe, or
for the pipe to be closed; if
closed with no data written,
returns an empty string.
0
after
read
no
no
yes
yes
UniBasic returns actual
content of the pipe
regardless of its length. Can
be an empty string.
0
Summary of OSBREAD Functionality with Named Pipes (UniData for UNIX Only)
11-10 Developing UniBasic Applications
1
Does the pipe contain data of the length requested?
2
Is the pipe open in the opposite mode?
3
What is the UniBasic STATUS function setting after this operation?
Reading from a Named Pipe
When a process executes a read against a named pipe, the actions UniBasic
takes depend on whether the pipe contains data of the length requested in the
read statement.
Data of the Required Length
When the named pipe contains data of at least the length requested, UniBasic
returns the actual contents of the pipe, even if the pipe contains more data
than that requested. NODELAY has no effect in this case.
Data of Insufficient Length
When the named pipe contains insufficient data (the pipe is empty or the
length of the data is less than specified in the OSBREAD statement),
processing depends on whether the pipe is open for writing on the other end.
If the pipe is open for writing on the other end:

NODELAY specified – UniBasic returns actual content of the
pipe regardless of its length. Can be an empty string. The
STATUS function return value is set to 0.

NODELAY not specified – The process waits for data of the
length requested to be written to the pipe or for the pipe to be
closed. If the pipe is closed with no data or insufficient data
written to it, UniBasic returns the actual contents of the pipe. The
STATUS function return value is set to 0 in either case.
If the pipe is not open for writing, UniBasic returns the actual content of the
pipe regardless of its length. It can be an empty string. NODELAY has no
effect.
OSBREAD 11-11
More about Reading from a Named Pipe (Windows Platforms)
For UniData for Windows Platforms, the following behavior occurs when
you attempt to read message-type or byte-type named pipes.
Message-Type Pipes

If NODELAY is specified, and if a message exists in the pipe,
UniBasic returns the actual contents of the pipe regardless of its
length. If a message does not exist in the pipe, UniData returns
an empty string and the STATUS function return value is set to 5.

If NODELAY is not specified, and if a message exists in the pipe,
UniBasic returns the actual contents of the pipe regardless of its
length. If a message does not exist in the pipe, the process waits
until a complete message is sent to the pipe.
Byte-Type Pipes

If NODELAY is specified, and if the data has a length greater
than or equal to length.expr, UniBasic returns the contents of the
pipe according to the length requested. If the data has a length
less than length.expr, UniBasic returns the actual contents of the
pipe (even with zero length) and the STATUS function return
value is set to 5.

If NODELAY is not specified, and if the data has a length greater
than or equal to length.expr, UniBasic returns the contents of the
pipe according to the length requested. If the data has a length
less than length.expr, either the process waits until it receives data
with length length.expr, or a server end closes the pipe. In the case
of a server end close, UniBasic returns the contents of the pipe
perhaps with length less than length.expr, and the STATUS
function return value is set to 5.
11-12 Developing UniBasic Applications
OSBWRITE
Syntax:
OSBWRITE expr [ON | TO] file.var [AT byte.expr] [NODELAY]
[ON ERROR statements]
The UniBasic OSBWRITE command accommodates writing to a named
pipe:

The AT clause is not allowed because named pipes are always read
with no offset.

The keyword NODELAY forces UniData to write when a pipe is full.
Note: If you do not specify NODELAY, the writing process waits until the pipe is
opened for reading.
Writing to Named Pipes
The combination of the following conditions and command options
determine the action taken by UniBasic when OSBWRITE is executed against
a named pipe:

Presence and length of data in the pipe

Open/closed status of the pipe

Presence or absence of the AT and NODELAY command options
OSBWRITE 11-13
The following table summarizes the actions taken by UniBasic when
OSBWRITE is executed against a named pipe.
AT
EMPTY1
OPEN2
NODELAY
Action
STATUS3
yes
no
effect
no effect
no effect
ON ERROR executes; if no
ON ERROR clause, the
program aborts.
2
ON ERROR executes
regardless of other
conditions (presence of
data; open/closed status;
NODELAY).
no
no
effect
no
no effect
ON ERROR executes; if no
ON ERROR clause, the
program aborts.
6
no
yes
yes
no effect
UniBasic writes to the pipe. 0
no
no
yes
no
The process waits for
sufficient space to become
available in the pipe to
contain the data being
written.
0
after
writing
no
no
yes
yes
UniBasic first tries to write
to the pipe; when it runs
out of space, the ON
ERROR clause executes.
The program fails if ON
ERROR is not specified.
3
The INMAT function
returns the length of data
written to the pipe.
Summary of OSBWRITE Functionality with Named Pipes
1Is
enough space available in the pipe to receive the data?
2
What is the UniBasic STATUS function return value after this operation?
Writing to a Closed Named Pipe
If the pipe is not open for reading, the ON ERROR clause executes. The
program fails if ON ERROR is not specified.
11-14 Developing UniBasic Applications
Writing to an Open Named Pipe
Processing depends on whether the pipe contains sufficient space to
accommodate the data being written:

Sufficient space available
UniBasic writes to the pipe regardless of whether NODELAY is
specified.

Sufficient space NOT available
Processing depends on whether NODELAY is specified:

NODELAY specified – UniBasic first tries to write to the pipe;
when it runs out of space, the ON ERROR clause executes. If the
ON ERROR clause is not coded, the program aborts. The
UniBasic STATUS function is set to 3, and the INMAT function
returns the length of data written to the pipe.

NODELAY not specified – The process waits for sufficient space
to become available in the pipe to receive the data. When all the
data is written successfully, the INMAT function returns the
length of data written to the pipe.
Warning: On UniData for UNIX only, the length of data in expr must be less than
or equal to the value of the PIPE_BUF system variable for the write to be atomic.
PIPE_BUF determines the length of the named pipe. Maximum length is determined
by the operating system.
OSBWRITE 11-15
OSCLOSE
Syntax:
OSCLOSE file.var [ON ERROR statements]
The UniBasic OSCLOSE command closes a sequential file that was opened
with the OSOPEN statement.
The following table describes each parameter of the syntax.
Parameter
Description
file.var
Specifies the file to close.
ON ERROR statements Specifies statements to execute if the OSCLOSE statement
fails with a fatal error because the file is not open, an I/O
error occurs, or UniData cannot find the file.
If you do not specify the ON ERROR clause and a fatal
error occurs, the program terminates.
OSCLOSE Parameters
STATUS Function Return Values
After you execute OSCLOSE, the STATUS function returns one of the values
described in the following table.
Return Value
Meaning
0
The file is closed successfully.
5
The file did not close.
STATUS Function Values
In the following example, the program statement closes the file UNDEF:
OSCLOSE UNDEF
11-16 Developing UniBasic Applications
STATUS Function Return Values
After you execute OSOPEN, OSCLOSE, OSBREAD, or OSBWRITE, the
STATUS function returns one of the values described in the following table.
Return
Value
Description
0
Execution was successful.
1
Invalid file name or file variable.
2
User lacks permission to access the file or pipe (at operating system
level).
3
Execution against a named pipe:
OSBWRITE was executed against a pipe that is open for reading, but is
full; NODELAY was specified.
OSOPEN with the WRITEONLY option was executed against a pipe that
was not open for reading.
4
The file does not exist.
5
Undefined error.
6
OSBWRITE was executed against a named pipe that is not open for
reading.
UniBasic STATUS Function Return Values
STATUS Function Return Values 11-17
INMAT
Syntax:
INMAT()
After you write to a named pipe, the INMAT function return value contains
the number of bytes written. This information can help you determine how
much space was available in a pipe at the time of an unsuccessful write.
Note: For additional syntax and return values for INMAT, see INMAT in the
UniBasic Commands Reference.
11-18 Developing UniBasic Applications
Troubleshooting
The following information could be helpful in troubleshooting processes that
manage named pipes:
Symptom
Probable Cause
Data is only partially written to a file or a
named pipe.
On UniData for UNIX only, a process
has tried to write data to a named pipe
whose length is greater than
PIPE_BUF, and NODELAY was
specified in the write statement.
A named pipe is empty after a read.
Read operations work differently on
named pipes versus other file types.
Data read from a named pipe is
removed from the pipe. Data read from
other types of files is copied from the
file.
Troubleshooting Named Pipe Processes
Troubleshooting 11-19
Symptom
Probable Cause
A process that manages named pipes
appears to be hung.
If NODELAY is not specified, and a
process tries to access (open, read or
write) a pipe that is not already open in
the opposite mode, the process waits
for it to be accessed in the opposite
mode before proceeding.
A process executes the ELSE clause of
UniData cannot temporarily close
OSOPEN executed against a named pipe. named pipes to manage the operating
system’s limitation on the maximum
number of files allowed to be opened at
a time. Therefore, opening a large
number of named pipes at the same
time can cause a process to try to open
more than the maximum number of
files allowed by the operating system.
Data is only partially written to a named
pipe; and the ON ERROR clause
executes, or the program aborts.
The pipe already contained some data
when this process executed
OSBWRITE. When UniBasic runs out
of room in the pipe, the ON ERROR
clause executes, or the program aborts.
The UniBasic INMAT function returns
the number of bytes written.
Troubleshooting Named Pipe Processes (continued)
11-20 Developing UniBasic Applications
The following example demonstrates opening a named pipe, writing record
IDs to it, and closing it:
****************
OPEN.NAMED.PIPES:
****************
EOF = ''
SELECT FILE.PRIORITY.MAP
LOOP
READNEXT PRIORITY ELSE EOF = 1
UNTIL EOF
PRIORITY.LIST<-1>=PRIORITY
PIPE.NAME = 'SF.PIPE.':PRIORITY
OSOPEN PIPE.NAME TO PIPE(PRIORITY) ELSE
MSG.TXT = 'Unable to open ':PIPE.NAME
GOSUB 9999
STOP
END
REPEAT
...
************************
2110 * Write ID to pipe
************************
ID.LENGTH = LEN(OUT.ID)
PIPE.ID = OUT.ID:STR(' ',PIPE.WIDTH-ID.LENGTH)
OSBWRITE PIPE.ID TO PIPE(PRIORITY)
RETURN
...
***********
CLOSE.PIPES:
***********
FOR I = 1 TO DCOUNT(PRIORITY.LIST,@AM)
PRIORITY = PRIORITY.LIST<I>
OSBWRITE SHUTDOWN TO PIPE(PRIORITY)
PRINT "'SHUTDOWN' ISSUED TO SF.PIPE.":PRIORITY
NEXT I
Troubleshooting 11-21
This next example reads the IDs from the other end of the pipe:
PIPE.WIDTH
= 32
***************
OPEN.NAMED.PIPE:
***************
PIPE.NAME = 'SF.PIPE.':P.PRIORITY
OSOPEN PIPE.NAME TO PIPE ELSE
MSG.TXT = 'Unable to open ':PIPE.NAME
GOSUB 9999
@USER.RETURN.CODE=0
STOP
END
...
************
PROCESS.LOOP:
************
LOOP
OSBREAD IN.ID FROM PIPE LENGTH PIPE.WIDTH
CONVERT ' ' TO '' IN IN.ID
IF IN.ID = "SHUTDOWN" THEN
OSCLOSE PIPE
@USER.RETURN.CODE = 1
EXIT
END
READ INBOX.REC FROM IN.FILE, IN.ID THEN
IF INBOX.REC<MB$COMPL_FLAG> = 0 THEN
GOSUB 2005; * Process INBOX.REC
END
END
REPEAT
MSG.TXT = '"SHUTDOWN" received from SF.PH.READ.
program.'
GOSUB 9999; * Print Message
RETURN
11-22 Developing UniBasic Applications
Ending
Chapter
Chapter 12: Local functions and
subroutines
Defining local subroutines and functions . . .
Local subroutine declaration and boundary . .
Variable scope . . . . . . . . . . .
Other behaviors . . . . . . . . . .
Examples . . . . . . . . . . . . . .
Simple local subroutine . . . . . . .
Invalid GOTO statement . . . . . . .
Local subroutine with a local variable . .
Multiple local subroutines and external calls
Calling another local subroutine . . . .
Local subroutines with COMMON . . .
Conditionally compiling a program . . .
Subroutine without END statement . . .
Calling a local subroutine through @VAR .
Illegal ENTER statement . . . . . . .
Using $DEFINE . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 12-3
. 12-4
. 12-4
. 12-4
. 12-6
. 12-6
. 12-6
. 12-7
. 12-7
. 12-8
. 12-8
. 12-9
. 12-9
. 12-10
. 12-10
. 12-11
12
UniBasic supports subroutines and functions, which must be defined in
separate source files. UniData uses these subroutines and functions
externally. UniData uses these subroutines and functions externally. Before
UniData 8.1.0, a UniBasic source file could only contain one subroutine or
function. To call an external subroutine or function, it must first be compiled
and (optionally) cataloged.
12-2
Defining local subroutines and functions
Local subroutines are defined after the main program body using the
SUBROUTINE keyword within a source file. The local subroutine ends with
a RETURN or END statement.
To Declare a local function, use a DEFFUN statement. use the FUNCTION
keyword followed by the function code body to complete the local
subroutine. A local function ends with a RETURN or END statement, and is
referenced it in the same way as its external counterparts.
The COMMON statement is used within a local subroutine to access
variables defined in the corresponding COMMON statements in other
program blocks.
A local subroutine can only access variables declared within that local
subroutine. Variables defined in the main body of the program cannot be
accessed in a local subroutine unless passed as arguments.
GOTO and GOSUB statements within a local subroutine cannot go outside of
the local subroutine. Likewise, GOTO and GOSUB statements outside of the
local subroutine cannot go into the local subroutine.
Note: If using local subroutines and functions in a UniBASIC program at UniData
8.1.0, it is not possible to run that program in prior versions of UniData.
12-3 Developing UniBasic Applications
Local subroutine declaration and boundary
Local subroutines are allowed in any UniBasic source file, whether it be a
program, subroutine, or function file. Local subroutines are defined by the
SUBROUTINE keyword and are invoked using the CALL statement from the
same source file where they are defined. UniData resolves the subroutine
name after the CALL statement by first searching local subroutine definitions
when compiling a program. If that search fails, it is treated as an external
subroutine. Local subroutines must be put at the end of the main program
body.
To explicitly terminate a local subroutine, use an END statement or implicitly
terminate it by the next SUBROUTINE or FUNCTION statement. A missing
END statement does not result in an error when compiling the program.
UniData allows local functions in any UniBasic source file. Functions are
defined by the FUNCTION keyword and must be declared by the DEFFUN
statement. Local functions are called the same way as externally defined
functions.
In the following sections, “local subroutine” indicates both local subroutines
and local functions, unless otherwise explicitly distinguished.
Variable scope
In UniBasic, variables defined in a program, subroutine, or function body are
only accessible in that body unless you specify the variable in a COMMON
statement. COMMON and EQUATE statements appearing in the main body
of a program apply only to the main body. COMMON and EQUATE
statements appearing in a local subroutine only apply for that local
subroutine.
To access COMMON variables, explicitly specify them in a COMMON
statement in a local subroutine.
Other behaviors
In general, local subroutines behave the same as external subroutines, with
the following behaviors:
12-4

When using CALL @VAR, if the value in VAR specifies a locally
defined subroutine, UniData finds and runs the local subroutine.
However, an ENTER statement maintains its original semantics. For
example, ENTER can only enter an external subroutine. It does not
look for locally defined subroutines.

When UniData runs a program, it opens the object file and loads it
into the session’s memory space. UniData loads the program’s main
body and all of its local subroutines into memory at the same time.
This reduces overhead and improves performance.

$DEFINE and $UNDEFINE, just like variables and the EQUATE
statement, do not cross main and local subroutine boundaries.

If you created local subroutines by including some previous external
subroutines that have $COPYRIGHT statements in a single program
file, UniData preserves the original copyright notices in the object
files.
12-5 Developing UniBasic Applications
Examples
This section shows several examples of using local subroutines.
Simple local subroutine
This example shows a simple local subroutine.
A=1
CALL MySub(A); *Call a local subroutine
CRT “A is now 2: “:A
STOP
*Define the local subroutine
SUBROUTINE MySub(B)
B+=1
RETURN
Running the above program outputs:
A is now 2: 2
Invalid GOTO statement
The following example illustrates an invalid GOTO statement.
A = 1
GOTO 10; * invalid GOTO
20:
CALL MySub(A); * Call a local subroutine
CRT “A is now: “:A
STOP
*Define the local subroutine
SUBROUTINE MySub(B)
10:
B += 1
GOTO 20; *invalid GOTO
RETURN
UniData will display syntax error messages when you compile this program.
12-6
Local subroutine with a local variable
The following example illustrates a local subroutine with a local variable.
A = 1
CALL MySub(A); * Call a local subroutine
CRT “A is now 2: “:A
STOP
* Define the local subroutine
SUBROUTINE MySub(B)
A = 0; *local variable for MySub
B = A + B + 1
RETURN
The output from this program is:
A is now 2: 2
Multiple local subroutines and external calls
The following example illustrates multiple subroutines and external calls.
File SameNameSUBR:
SUBROUTINE Bad_Example(A)
A = “In global SameNameSUBR”
RETURN
*Suppose you catalog this subroutine as *SameNameSUBR
File: GOOD_EXAMPLE:
A = “”
CALL MySub(A); * Call a local subroutine
PRINT A
CALL SameNameSUBR(A); *Call a local subroutine
PRINT A
CALL *SameNameSUBR(A); *Call an external subroutine”
PRINT A
STOP
*Define the local subroutine
SUBROUTINE MySub (B)
B = “In local MySub”
RETURN
*Define another local subroutine
SUBROUTINE SameNameSUBR(B)
B = “In local SameNameSUBR”
RETURN
12-7 Developing UniBasic Applications
Running this program returns the following result:
In local MySub
In local SameNameSUBR
In global SameNameSUBR
Calling another local subroutine
The following example illustrates a local subroutine called by another local
subroutine.
A = 1
CALL MySub2(A); * Call a local subroutine
PRINT “A is now: “:A
STOP
*Define the local subroutine
SUBROUTINE MySub1(B)
B += 1
RETURN
*Define another local subroutine
SUBROUTINE MySub2(B)
B += 1
CALL MySub1(B)
RETURN
Running this program produces the following result:
A is now: 3
Local subroutines with COMMON
The following example illustrates a local subroutine using COMMON.
DEFFUN LocalFunc;
COMMON A,B
A = 1
B = 1
A = LocalFunc()
* Declare a local function
...
STOP
FUNCTION LocalFunc ; REM Define a local function
COMMON A,B
; REM must be specified to access A,B
RETURN (A+B)
END
12-8
Conditionally compiling a program
The following program can be conditionally compiled.
$INCLUDE INCLUDE UDO.H
CRT “START CODE”
CALL SR.LVR(LOAN_AMT,RATE)
CALL COOKIES(“Give”, “COOKIES”)
$IFDEF V112PLUS
; REM if compiled for earlier UV
; REM versions the two calls above for
; REM external subroutines
$INCLUDE BP SR.LVR
; REM making SR.LVR a local routine
; REM for V112PLUS
$INCLUDE BP COOKIES
; REM making COOKIES a local routine
; REM for V112PLUS
$ENDIF
Subroutine without END statement
The following example shows an existing subroutine without and END
statement.
...
STOP
; * end of main
$INCLUDE BP FOO(A)
END
; * include existing subroutine
; * here END is optional
SUBROUTINE SUB2
...
END
; * other local subs
Existing external subroutine:
SUBROUTINE FOO(A)
A=”COOKIES”
RETURN
12-9 Developing UniBasic Applications
; * end of source file
Calling a local subroutine through @VAR
This example illustrates calling a local subroutine through @VAR.
Myfunc = “LocalSub1”
CALL @Myfunc
...
SUBROUTINE LocalSub1
...
RETURN
END
; *will find and run LocalSub1
Illegal ENTER statement
The following example illustrates an illegal ENTER statement.
Myfunc = “LocalSub1”
ENTER @Myfunc
...
SUBROUTINE LocalSub1
...
RETURN
END
When executing this problem, UniData displays a runtime error stating that
the subroutine cannot be found, assuming there is no external subroutine
with the name “LocalSub1.”
12-10
Using $DEFINE
The following program illustrates using $DEFINE in a local subroutine.
*main body
$DEFINE DEFMAIN1
$DEFINE DEFMAIN2 “main”
$IFDEF DEFMAIN1
V1 = DEFMAIN2
CALL LocalSub1
$ENDIF
IF V1 <> DEFMAIN2 THEN
CRT “error: defmain2 changed by sub!”
END ELSE
CRT “good: defmain2 still defined”
END
STOP
*local subroutine
SUBROUTINE LocalSub1
$IFDEF DEFMAIN1
CRT “error: defmain1 should not be already defined”
$ENDIF
$DEFINE DEFMAIN2 “sub”
;* should not affect main
RETURN
END
12-11 Developing UniBasic Applications
Appendix
Appendix A: Sample
Program
The following sample program, UPDATE_ORDER,
demonstrates the use of some UniBasic commands in a simple
application. This program uses the demonstration database files
that are included with UniData. The program calls an external
subroutine, DISPLAY_MESSAGE, which is included at the end
of this appendix.
A
UPDATE_ORDER
**
**
**
**
**
**
**
**
**
**
Program
:
Programmer :
Created
:
Description:
:
:
:
:
:
:
UPDATE_ORDERS
Todd Roemmich
04/02/1996
Check and/or alter Order records
Display Screen and ask for Order #
Read record (if it exists) and display fields
Prompt for a command (Alter, Delete, or Quit)
A) Allow the user to change price or address
D) Delete the record
Q) Exit the program
*-------------- Include/Commons -----------------------*
*
Normally OPEN commands, EQUATEs, and DIMensions done
with INCLUDE files. --TER
EQU CLS TO @(-1)
GOSUB OPEN_FILES
*-------------- Main Logic ----------------------------GOSUB INITIALIZE
LOOP
GOSUB DISPLAY_SCREEN
GOSUB GET_ORDER_NUMBER
UNTIL ORDER_NUMBER[1,1] = 'Q'
GOSUB DISPLAY_DATA
IF RECORD_FOUND THEN GOSUB GET_RECORD_COMMAND
RELEASE
REPEAT
GOSUB EXIT
*------------ Subroutines -------------------------ALTER_RECORD:
* Create a new screen, and allow PRICE and ADDRESS to be changed.
*
Initialize variables and draw the screen
NEED.TO.WRITE = 0
DISPLAY CLS:@(15,5):"Alter ORDER":
DISPLAY @(10,8):"(Press RETURN to leave unchanged)"
DISPLAY @(8,9):"Old Price":@(42,9):"New Price (Enter 2 decimal
places)"
* Change the PRICE field (if desired)
FOR ENTRY = 1 TO NUM_ENTRIES
NEW.PRICE = ""
A-2
Developing UniBasic Applications
DISPLAY @(10,9+ENTRY):OCONV(ORDER.REC<7,ENTRY>,"MR2$,"):
INPUT @(45,9+ENTRY):NEW.PRICE
NEW.PRICE = OCONV(NEW.PRICE,"MCN")
IF NEW.PRICE # '' AND NUM(NEW.PRICE) THEN
ORDER.REC<7,ENTRY> = NEW.PRICE
NEED.TO.WRITE = 1
END
NEXT ENTRY
* Display the current ADDRESS information
DISPLAY @(21,12):"Change Address to: ":
DISPLAY @(21,13):"Street Line1:
":@(40,13):ADDRESS<2>
DISPLAY @(21,14):"Street Line2:"
DISPLAY
@(40,14):ADDRESS<3>:@(21,15):"City:":@(40,15):CLIENT.REC<6>
DISPLAY @(21,16):"State:":
DISPLAY
@(40,16):CLIENT.REC<7>:@(21,17):"Zip:":@(40,17):CLIENT.REC<8>
* Accept INPUT to change values of address
INPUT @(40,13):STREET1
IF STREET1 = '' THEN STREET1 = CLIENT.REC<4>
INPUT @(40,14):STREET2
IF STREET2 = '' THEN STREET2 = CLIENT.REC<5>
INPUT @(40,15):CITY
IF CITY = '' THEN CITY = CLIENT.REC<6>
INPUT @(40,16):STATE
IF STATE = '' THEN STATE = CLIENT.REC<7>
INPUT @(40,17):ZIP
IF ZIP = '' THEN ZIP = CLIENT.REC<8>
* Compare Old and New values (Write out new record if needed)
NEW_CLIENT = STREET1:STREET2:CITY:STATE:ZIP
OLD_CLIENT =
CLIENT.REC<4>:CLIENT.REC<5>:CLIENT.REC<6>:CLIENT.REC<7>:CLIENT.REC
<8>
IF (NEW_CLIENT #
* Re-assign values
CLIENT.REC<4>
CLIENT.REC<5>
CLIENT.REC<6>
CLIENT.REC<7>
CLIENT.REC<8>
OLD_CLIENT) OR NEED.TO.WRITE THEN
to CLIENT.REC
= STREET1
= STREET2
= CITY
= STATE
= ZIP
GOSUB WRITE_RECORD
END
RETURN
DELETE_RECORD:
* (Assuming the order #'s are on line 12)
A-3
READVU ORDER_LINE FROM CLIENT_FILE,CLIENT_NUMBER,12 THEN
LOCATE ORDER_NUMBER IN ORDER_LINE<1> SETTING POSITION THEN
DEL ORDER_LINE<1,POSITION>
END
WRITEV ORDER_LINE ON CLIENT_FILE, CLIENT_NUMBER, 12
END
*
DELETE ORDERS_FILE, ORDER_NUMBER
RELEASE CLIENT_FILE,CLIENT_NUMBER
RETURN
DISPLAY_DATA:
* Display the current information in the desired record. This is
* determined by the number the user entered (ORDER_NUMBER).
READU ORDER.REC FROM ORDERS_FILE,ORDER_NUMBER THEN
* Read with a lock so that no one else can modify it at the same
time.
RECORD_FOUND = 1
ORDER_DATE = OCONV(ORDER.REC<1>,"D4/")
ORDER_TIME = OCONV(ORDER.REC<2>,"MT")
CLIENT_NUMBER = ORDER.REC<3>
ADDRESS = ''
READU CLIENT.REC FROM CLIENT_FILE,CLIENT_NUMBER THEN
ADDRESS<1> = CLIENT.REC<3>
ADDRESS<2> = CLIENT.REC<4>
ADDRESS<3> = CLIENT.REC<5>
ADDRESS<4> = CLIENT.REC<6>:", ":CLIENT.REC<7>:"
":CLIENT.REC<8>
END
DISPLAY @(45,7):ADDRESS<1>
DISPLAY @(45,8):ADDRESS<2>
DISPLAY @(45,9):ADDRESS<3>
DISPLAY @(45,10):ADDRESS<4>
DISPLAY @(45,11):ADDRESS<5>
PRODUCT_LINE = ''
COLOR_LINE = ''
QUANTITY_LINE = ''
PRICE_LINE = ''
NUM_ENTRIES = DCOUNT(ORDER.REC<4>,@VM)
FOR ENTRY = 1 TO NUM_ENTRIES
PRODUCT_NUMBER = ORDER.REC<4,ENTRY>
COLOR = ORDER.REC<5,ENTRY>
QUANTITY = ORDER.REC<6,ENTRY>
PRICE = OCONV(ORDER.REC<7,ENTRY>,"MD2$,")
IF PRODUCT_LINE = '' THEN
PRODUCT_LINE = PRODUCT_NUMBER "R#10"
COLOR_LINE = COLOR "R#10"
QUANTITY_LINE = QUANTITY "R#10"
PRICE_LINE = PRICE "R#10"
END ELSE
PRODUCT_LINE := "
":PRODUCT_NUMBER "R#10"
A-4
Developing UniBasic Applications
COLOR_LINE := "
":COLOR "R#10"
QUANTITY_LINE := "
":QUANTITY "R#10"
PRICE_LINE := "
":PRICE "R#10"
END
NEXT ENTRY
ORDER_DATA = @(13,7):ORDER_DATE:@(13,8):ORDER_TIME
ORDER_DATA := @(13,10):CLIENT_NUMBER "R#5"
ORDER_DATA := @(13,11):PRODUCT_LINE:@(13,13):COLOR_LINE
ORDER_DATA := @(13,14):QUANTITY_LINE:@(13,15):PRICE_LINE
DISPLAY ORDER_DATA
END ELSE
MESSAGE ="**(Record Does Not Exist)**"
RECORD_FOUND = 0
CALL DISPLAY_MESSAGE(MESSAGE)
END
RETURN
DISPLAY_SCREEN:
* Display the starting screen
SCREEN
quit)"
SCREEN
SCREEN
SCREEN
"
SCREEN
= CLS:@(25,1):"ORDER MAINTENANCE":@(16,5):"(Enter Q to
:= @(5,6):"Order #: "
:= @(5,7):"Date: ":@(5,8):"Time: ":@(5,10):"Client #:"
:= @(5,11):"Product #: ":@(5,13):"Color: ":@(5,14):"Qty:
:= @(5,15):"Price: "
DISPLAY SCREEN
RETURN
GET_ORDER_NUMBER:
* Have the user enter a valid key to a record in the ORDERS file.
LOOP
DISPLAY @(15,6):
INPUT ORDER_NUMBER
ORDER_NUMBER = OCONV(ORDER_NUMBER,"MCU")
IF NUM(ORDER_NUMBER) OR ORDER_NUMBER[1,1] = "Q" THEN
VALID_ORDER_NUMBER = 1
END ELSE
VALID_ORDER_NUMBER = 0
MESSAGE = "Order # must be a Number, or the letter 'Q'"
CALL DISPLAY_MESSAGE(MESSAGE)
END
UNTIL VALID_ORDER_NUMBER
REPEAT
RETURN
GET_RECORD_COMMAND:
* Enter a valid command to act upon (the Desired record is already
A-5
shown).
DISPLAY @(7,22):"Enter A)lter, D)elete, or Q)uit: ":
INPUT COMMAND,1
COMMAND = OCONV(COMMAND[1,1],"MCU")
BEGIN CASE
CASE COMMAND = "A"
GOSUB ALTER_RECORD
CASE COMMAND = "D"
GOSUB DELETE_RECORD
CASE COMMAND = "Q"
FINISHED = 1
CASE 1
MESSAGE = "Valid options are A, D, or Q. Please try again"
CALL DISPLAY_MESSAGE(MESSAGE)
END CASE
RETURN
WRITE_RECORD:
* The record(s) have been updated. Make sure that if the
RECOVERABLE
* FILE System is operational that either BOTH records are updated,
or that
* None are (using Transaction processing commands).
TRANSACTION START ELSE
IF STATUS() = 1 THEN
DISPLAY "A Transaction had already been started, NESTED
Transactions"
DISPLAY "are NOT Allowed. (Contact System Administrator)"
INPUT PAUSE,1_
END ELSE
NULL
END
END
WRITE CLIENT.REC ON CLIENT_FILE,CLIENT_NUMBER
WRITE ORDER.REC ON ORDERS_FILE,ORDER_NUMBER
TRANSACTION COMMIT
IF STATUS() = 1
DISPLAY "The
END ELSE
DISPLAY "The
END
ELSE
THEN
TRANSACTION was not started"
TRANSACTION could not be committed."
END
RETURN
OPEN_FILES:
OPEN "CLIENTS" TO CLIENT_FILE ELSE
MESSAGE = "The CLIENT file could not be opened."
A-6
Developing UniBasic Applications
CALL DISPLAY_MESSAGE(MESSAGE)
STOP
END
OPEN "ORDERS" TO ORDERS_FILE ELSE
MESSAGE = "The ORDERS file could not be opened."
CALL DISPLAY_MESSAGE(MESSAGE)
STOP
END
RETURN
INITIALIZE:
DISPLAY CLS
PROMPT ''
RETURN
EXIT:
DISPLAY CLS
STOP
RETURN
A-7
DISPLAY_MESSAGE
The following example shows the external subroutine that is called by
UPDATE_ORDERS:
SUBROUTINE DISPLAY_MESSAGE(MESSAGE)
DISPLAY @(5,20):MESSAGE
DISPLAY @(5,21):"Press the (Return) key.":
INPUT PAUSE,1_
RETURN
A-8
Developing UniBasic Applications
Appendix
Appendix B: UniBasic
Transaction Processing
Concepts
This chapter describes some basic concepts on which UniData
transaction processing (TP) is based. Chapter 9, “Chapter 9:
UniBasic Transaction Processing” introduces the UniData TP
commands, including how to create and convert to files, and
describes some TP programming problems and proposes
solutions for those problems.
TP executes a set of statements as a single logical function. This
ensures that if a database is in a consistent state before a
transaction starts, the database maintains that consistent state
when the transaction completes. To create a transaction, you
must bind the operations by TRANSACTION START and
TRANSACTION COMMIT statements. When the transaction
commits, all of the operations take place as one, and UniData can
recover the updated file in the event of any failure. If a
transaction is aborted, none of the operations within the
transaction take place.
B
In This Appendix
This appendix explains some of the concepts on which UniData Transaction
Processing is based. The following sections are included:
B-2

“Transaction Processing Rules: The ACID Properties”

“Transaction Isolation”

“Transaction Processing Errors”

“What Are Isolation Levels?”

“Programming to Isolation Levels”
Developing UniBasic Applications
Transaction Processing Rules: The ACID Properties
The ACID properties in the following list distinguish transactions from nonTP database updates:

Atomicity

Consistency

Isolation

Durability
Atomicity
Logical operations grouped by transaction semantics are treated as one unit.
They will all succeed or all fail.
Consistency
The components of a logical operation all succeed or all fail. If a database is
in a consistent state before you apply a logical operation, it will be in a
consistent state afterwards. For example, writing a new record will guarantee
that the record and the indexes are both written. The record will not be
written without its associated indexes.
Isolation
Isolation means that operations are based on a consistent database rather
than on intermediate results from other operations. Isolation is controlled
within UniBasic applications. If locks are properly set and checked, all
transactions are properly isolated. In UniData SQL, a desired isolation level
is set by specifying the isolation level with the set transaction command. If no
isolation level is specified, a default isolation level is handled by the database
engine.
B-3
Durability
Durability means that completed transactions are preserved in the database
despite failures. If UniData stops running, the database recovers to the last
committed transaction when you restart UniData. Recovery techniques are
available to provide durability in the case of media failure.
B-4
Developing UniBasic Applications
Transaction Isolation
Even though transactions appear to process sequentially and atomically, they
actually occur serially. Isolation is determined by the degree to which
transaction errors are prevented. This section identifies transaction errors,
isolation levels, and provides templates for programming to each isolation
level.
Note: UniData SQL follows ANSI '92 standards, but UniBasic does not. For this
reason, the types of transaction errors that can occur and the isolation levels that can
be achieved differ between the two products. UniBasic and UniData SQL TP differ
in the following ways:
• UniData SQL always protects against lost updates and dirty reads, but could allow
phantoms.
• UniBasic always protects against dirty reads and phantoms, but could allow lost
updates.
Transaction Processing Errors
You can get unexpected results when transactions process concurrently. The
following sections describe these errors:

“Lost Updates”

“Dirty Reads”

“Unrepeatable Reads”
Lost updates, dirty reads, and unrepeatable reads can be prevented by
designing applications appropriately, as in the examples provided later in
this chapter.
Lost Updates
In a lost update, the updates from one transaction are overwritten by those of
another.
Note: UniData SQL protects against lost updates in all transactions.
B-5
Consider the following example:
Program Segment 1
Transaction Segment 2
TRANSACTION START
THEN PRINT “Transaction started.”
READ var FROM file.var, record1
ELSE PRINT “Record not found.”
READ var FROM file.var, record1
ELSE PRINT “Record not found”
var += 1
var += 2
WRITE var TO file.var, record1
WRITE var TO file.var, record1
TRANSACTION COMMIT
THEN PRINT “Transaction committed.”
Program Segment 1 and Transaction Segment 2 are run concurrently.
Program Segment 1 could lose updates because the record is not locked
between its READ and WRITE. In two separate write operations, Transaction
Segment 2 changes Record1’s value to 2, then to 3. The update in Program
Segment 1, made between the two writes, is lost.
Dirty Reads
A dirty read occurs when one of two scenarios takes place:

Within a transaction, a process reads an attribute value between two
updates made by a second transaction. Because the second
transaction changes the value after the read, this read is considered
dirty.

Within a transaction, a record is changed; a second transaction
process reads the record; then the first transaction process aborts and
rolls back, changing the record to a different value. The value
obtained by the second transaction is dirty.
Note: UniBasic protects against dirty reads in all transactions.
B-6
Developing UniBasic Applications
Consider the following example:
Program Segment 1
Transaction Segment 2
TRANSACTION START
THEN PRINT “Transaction started.” ELSE STOP
var = 1
WRITE var TO file.var, record1
READ var FROM file.var, record1
ELSE PRINT “Record not found.”
var += 2
WRITE var TO file.var, record1
TRANSACTION COMMIT
THEN PRINT “Transaction committed.”
Program Segment 1 and Transaction Segment 2 are run concurrently. Transaction Segment 2 writes a record; then Program Segment 1 reads it; finally,
Transaction Segment 2 makes additional changes to the record. The record
value obtained by Program Segment 1 is no longer current.
Unrepeatable Reads
Between two reads of a record in a transaction, another transaction updates
the record. As a result, the first transaction obtains a different value for the
same attribute on the second read. This is an unrepeatable read.
B-7
Consider the following example:
Program Segment 1
Transaction Segment 2
TRANSACTION START
THEN PRINT “Transaction started.”
READ var FROM file.var, record1
ELSE PRINT “Record not found.”
var = 2
WRITE var TO file.var, record1
READ var FROM file.var, record1
ELSE PRINT “Record not found.”
TRANSACTION COMMIT
THEN PRINT “Transaction committed.”
Program Segment 1 and Transaction Segment 2 run concurrently. Program
Segment 1 reads a record twice before and after Transaction Segment 2
updates it. The two read operations return different values for the record.
What Are Isolation Levels?
The isolation level determines the type of transaction errors (including lost
updates, dirty reads, and unrepeatable reads) that are allowed or not
allowed.
UniBasic TP does not implicitly enforce isolation levels. The application
program must provide protection from transaction errors by setting shared
locks on records being read and by setting exclusive locks on records being
updated. UniBasic TP does provide the following protection: records are
automatically locked during write operations, and all locks set during a
transaction are retained until the transaction is committed or aborted.
Unlike UniBasic, UniData SQL automatically initiates and commits
transactions and provides the level of isolation appropriate for the type of
operation being executed. For more information, see Using UniData SQL.
The following list describes four theoretical isolation levels:
B-8
Developing UniBasic Applications

Isolation level 0 exposes transactions to any type of TP error, but
cannot overwrite the updates from transactions at isolation level 1 or
higher. To achieve atomicity at this level, the application must set
exclusive locks on records being updated.

Isolation level 1 protects transactions against lost updates but allows
dirty reads and unrepeatable reads. When you execute a read
operation, the system automatically locks records being updated.

Isolation level 2 protects transactions against lost updates and dirty
reads, but allows unrepeatable reads. Note: UniBasic automatically
protects against dirty reads; therefore, if you program to protect
against lost updates, you achieve isolation level 2.

Isolation level 3 protects transactions against all three types of TP
errors: lost updates, dirty reads, and unrepeatable reads.
Note: The ANSI-standard isolation levels available through UniData SQL differ
from these because of the differences in product design described at the beginning of
this section.
Programming to Isolation Levels
This section provides program templates that demonstrate how to program
in UniBasic to achieve different levels of isolation.
Example: Programming to Isolation Level 0
The following program template provides no protection against TP errors. It
is programmed for isolation level 0. Unlike the theoretical situation depicted
by this example, UniBasic and UniData SQL provide some protection against
TP errors.
TRANSACTION START
THEN PRINT "Transaction started."
ELSE PRINT "Transaction start failed, STATUS = ":STATUS()
READ var FROM file.var, record1 ELSE PRINT "Record not
found."
var += 2
WRITE var TO file.var, record1
TRANSACTION COMMIT
THEN PRINT "Transaction committed."
ELSE PRINT "Transaction aborted, STATUS = ":STATUS()
B-9
Example: Programming to Isolation Level 2
The following program template illustrates programming for isolation level
2, which protects against lost updates and dirty reads. After the record is
locked by the second read (READU), the transaction is protected against lost
updates.
TRANSACTION START
THEN PRINT "Transaction started."
ELSE PRINT "Transaction start failed, STATUS = ":STATUS()
READ var FROM file.var, record1
.
.
.
REM "If another transaction changes record1 here, its
updates are
lost"
.
.
.
READU var FROM file.var, record1 ELSE PRINT "Record not
found."
var += 2
WRITE var TO file.var, record1
TRANSACTION COMMIT
THEN PRINT "Transaction committed."
ELSE PRINT "Transaction aborted, STATUS = ":STATUS()
Example: Programming to Isolation Level 3
The following program template illustrates programming for isolation level
3. All three types of transaction error are prevented because the record is
locked by READU the first time it is read.
TRANSACTION START
THEN PRINT "Transaction started."
ELSE PRINT "Transaction start failed, STATUS = ":STATUS()
READU var FROM file.var, record1 ELSE PRINT "Record not
found."
var += 2
WRITE var TO file.var, record1
TRANSACTION COMMIT
THEN PRINT "Transaction committed."
ELSE PRINT "Transaction aborted, STATUS = ":STATUS()
B-10 Developing UniBasic Applications