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