A Closer Look at ObjectScript
Most methods and most routines are written in ObjectScript. This chapter gives an overview of this language, if you intend to use it or if you need to understand code that other people have written. This chapter discusses the following topics:
A method can contain the same statements, labels, and comments as routines do. That is, all the information here about the contents of a routine also applies to the contents of a method.
If you are writing classes rather than routines, start with the section “Variables.”
Routines
The following shows an example routine named demoroutine that is written in ObjectScript. This example gives us an opportunity to see some common commands, operators, and functions, and to see how code is organized within a routine.
; this is demoroutine
write "Use one of the following entry points:"
write !,"random"
write !,"input"
write !,"interesting"
quit
//this procedure can be called from outside the routine
random() public {
set rand=$RANDOM(10)+1 ; rand is an integer in the range 1-10
write "Your random number: "_rand
set name=$$getnumbername(rand)
write !, "Name of this number: "_name
}
//this procedure can be called from outside the routine
input() public {
read "Enter a number from 1 to 10: ", input
set name=$$getnumbername(input)
write !, "Name of this number: "_name
}
//this procedure can be called only from within this routine
getnumbername(number) {
set name=$CASE(number,1:"one",2:"two",3:"three",
4:"four",5:"five",6:"six",7:"seven",8:"eight",
9:"nine",10:"ten",:"other")
quit name
}
/* write some interesting values
this procedure can be called from outside the routine
*/
interesting() public {
write "Today's date: "_$ZDATE($HOROLOG,3)
write !,"Your installed version: "_$ZVERSION
write !,"Your username: "_$USERNAME
write !,"Your security roles: "_$ROLES
}
Note the following highlights:
-
The only identifier that actually starts with a caret (^) is the name of a global; these are discussed later in this chapter. However, in running text and in code comments, it is customary to refer to a routine as if its name started with a caret, because you use the caret when you invoke the routine (as shown later in this chapter). For example, the routine demoroutine is usually called ^demoroutine.
-
The routine name does not have to be included within the routine. When you view a routine in Studio, the routine name is displayed on the tab that displays the routine. For example:
Many programmers include the routine name as a comment at the start of the routine or as the first label in the routine.
-
The routine has multiple labels: random, input, getnumbername, and interesting.
Labels are used to indicate the starting point for procedures (as in this example), functions, and subroutines; these terms are defined later in this chapter. You can also use them as a destination for certain commands.
Labels are common in routines, but you can also use them within methods.
Labels are also called entry points or tags.
-
The random and input subroutines invoke the getnumbername subroutine.
-
WRITE, QUIT, SET, and READ are commands. The language includes other commands to remove variables, commands to control program flow, commands to control I/O devices, commands to manage transactions (possibly nested), and so on.
The names of commands are not case-sensitive, although they are shown in running text in all upper case by convention.
-
The sample includes two operators. The plus sign (+) performs addition, and the underscore (_) performs string concatenation.
ObjectScript provides the usual operators and some special operators not seen in other languages.
-
$RANDOM, $CASE, and $ZDATE are functions.
The language provides functions for string operations, conversions of many kinds, formatting operations, mathematical operations, and others.
-
$HOROLOG, $ZVERSION, $USERNAME, and $ROLES are system variables (called special variables in Caché). Most special variables contain values for aspects of the Caché operating environment, the current processing state, and so on.
-
ObjectScript supports comment lines, block comments, and comments at the end of statements.
We can execute parts of this routine in the Terminal, as a demonstration. First, the following shows a Terminal session, in which we run the routine itself. In these examples, SAMPLES> is the prompt shown in the Terminal. The text after the prompt on the same line is the entered command. The lines after that show the values that the system writes to the Terminal in response.
SAMPLES>do ^demoroutine
Use one of the following entry points:
random
input
SAMPLES>
When we run the routine, we just get help information, as you can see. It is not required to write your routines in this way, but it is common. Note that the routine includes a QUIT before the first label, to ensure that when a user invokes the routine, processing is halted before that label. This practice is also not required, but is also common.
Next, the following shows how a couple of the subroutines behave:
SAMPLES>do input^demoroutine
Enter a number from 1 to 10: -7
Name of this number: other
SAMPLES>do interesting^demoroutine
Today's date: 2010-11-30
Your installed version: Cache for Windows (x86-32) 2010.2 (Build 454U) Sun Oct 24 2010 17:14:03 EDT
Your username: UnknownUser
Your security roles: %All
SAMPLES>
Procedures, Functions, and Subroutines
Within an ObjectScript routine, a label defines the starting point for one of the following units of code:
-
Procedure (optionally returns a value). The variables defined in a procedure are private to that procedure, which means that they are not available to other code. This is not true for functions and subroutines.
A procedure is also called a procedure block.
-
Function (returns a value).
-
Subroutine (does not return a value).
InterSystems recommends that you use procedures, because this simplifies the task of controlling the scope of variables. In existing code, however, you might also see functions and subroutines, and it is useful to be able to recognize them. The following list shows what all these forms of code look like.
label(args) scopekeyword {
zero or more lines of code
QUIT returnvalue
}
Or:
label(args) scopekeyword {
zero or more lines of code
}
label is the identifier for the procedure.
args is an optional comma-separated list of arguments. Even if there are no arguments, you must include the parentheses.
The optional scopekeyword is one of the following (not case-sensitive):
-
Public. If you specify Public, then the procedure is public and can be invoked outside of the routine itself.
-
Private (the default for procedures). If you specify Private, the procedure is private and can be invoked only by other code in the same routine. If you attempt to access the procedure from another routine, a <NOLINE> error occurs.
returnvalue is an optional, single value to return. To return a value, you must use the QUIT command. If you do not want to return a value, you can omit the QUIT command, because the curly braces indicate the end of the procedure.
A procedure can declare variables as public variables, although this practice is not considered modern. To do this, you include a comma-separated list of variable names in square brackets immediately before scopekeyword. For details, see “User-defined Code” in Using Caché ObjectScript.
label(args) scopekeyword
zero or more lines of code
QUIT optionalreturnvalue
args is an optional comma-separated list of arguments. Even if there are no arguments, you must include the parentheses.
The optional scopekeyword is either Public (the default for functions) or Private.
label(args) scopekeyword
zero or more lines of code
QUIT
args is an optional comma-separated list of arguments. If there are no arguments, the parentheses are optional.
The optional scopekeyword is either Public (the default for subroutines) or Private.
The following table summarizes the differences among routines, subroutines, functions, and procedures:
Routine | Subroutine | Function | Procedure | |
---|---|---|---|---|
Can accept arguments | no | yes | yes | yes |
Can return a value | no | no | yes | yes |
Can be invoked outside the routine (by default) | yes | yes | yes | no |
Variables defined in it are available after the code finishes execution | yes | yes | yes | depends on nature of the variable |
The section “Variable Availability and Scope,” later in this chapter, has further details on variable scope.
In casual usage, the term subroutine can mean procedure, function, or subroutine (as defined formally here).
Variables
In ObjectScript, there are two kinds of variables, as categorized by how they hold data:
-
Local variables, which hold data in memory.
Local variables can have public or private scope.
-
Global variables, which hold data in a database. These are also called globals. All interactions with a global affect the database immediately. For example, when you set the value of a global, that change immediately affects what is stored; there is no separate step for storing values. Similarly, when you remove a global, the data is immediately removed from the database.
There are special kinds of globals not discussed here; see “Caret (^)” in the appendix “What’s That?”
Variable Names
The names of variables follow these rules:
-
For most local variables, the first character is a letter, and the rest of the characters are letters or numbers. Valid names include myvar and i
-
For most global variables, the first character is always a caret (^). The rest of the characters are letters, numbers, or periods. Valid names include ^myvar and ^my.var
Caché also supports a special kind of variable known as a percent variable; these are less common. The name of a percent variable starts with a percent character (%). Percent variables are special in that they are always public; that is they are visible to all code within a process. This includes all methods and all procedures within the calling stack.
When you define percent variables, use the following rules:
-
For a local percent variable, start the name with %Z or %z. Other names are reserved for system use.
-
For a global percent variable, start the name with ^%Z or ^%z. Other names are reserved for system use.
For details on percent variables and variable scope, see “Variable Availability and Scope,” later in this chapter; also see “Callable User-defined Code Modules” in Using Caché ObjectScript).
For further details on names and for variations, see “Syntax Rules” in Using Caché ObjectScript. Or see “Rules and Guidelines for Identifiers,” later in this book.
Variable Types
Variables in ObjectScript are weakly, dynamically typed. They are dynamically typed because you do not have to declare the type for a variable, and variables can take any legal value — that is, any legal literal value or any legal ObjectScript expression. They are weakly typed because usage determines how they are evaluated.
A legal literal value in ObjectScript has one of the following forms:
-
Numeric. Examples: 100, 17.89, and 1e3
-
Quoted string, which is a set of characters contained within a matched set of quotation marks ("). For example: "my string"
To include a double quote character within a string literal, precede it with another double quote character. For example: "This string has ""quotes"" in it."
Depending on the context, a string can be treated as a number and vice versa. Similarly, in some contexts, a value may be interpreted as a boolean (true or false) value; anything that evaluates to zero is treated as false; anything else is treated as true.
When you create classes, you can specify types for properties, for arguments to methods, and so on. The Caché class mechanisms enforce these types as you would expect. A later section of this book provides an overview of Caché data type classes.
Variable Length
There is a limit to the length of a value of a variable. If you have long strings enabled in your installation, the limit is 3,641,144 characters. If long strings are not enabled, the limit is 32,767 characters.
A later section of this book explains how to enable long string operations.
Variable Existence
You usually define a variable with the SET command. As noted earlier, when you define a global variable, that immediately affects the database.
A global variable becomes undefined only when you kill it (which means to remove it via the KILL command). This also immediately affects the database.
A local variable can become undefined in one of three ways:
-
It is killed.
-
The process (in which it was defined) ends.
-
It goes out of scope within that process.
To determine whether a variable is defined, you use the $DATA function. For example, the following shows a Terminal session that uses this function:
SAMPLES>write $DATA(x)
0
SAMPLES>set x=5
SAMPLES>write $DATA(x)
1
In the first step, we use $DATA to see if a variable is defined. The system displays 0, which means that the variable is not defined. Then we set the variable equal to 5 and try again. Now the function returns 1.
In this example and in previous examples, you may have noticed that it is not necessary to declare the variable in any way. The SET command is all that you need.
If you attempt to access an undefined variable, you get the <UNDEFINED> error. For example:
SAMPLES>WRITE testvar
WRITE testvar
^
<UNDEFINED> *testvar
Variable Availability and Scope
ObjectScript supports the following program flow, which is similar (in most ways) to what other programming languages support:
-
A user invokes a procedure, perhaps from a user interface.
-
The procedure executes some statements and then invokes another procedure.
-
The procedure defines local variables A, B, and C.
Variables A, B, and C are in scope within this procedure. They are private to this procedure.
The procedure also defines the global variable ^D.
-
The second procedure ends, and control returns to the first procedure.
-
The first procedure resumes execution. This procedure cannot use variables A, B, and C, which are no longer defined. It can use ^D, because that variable was immediately saved to the database.
The preceding program flow is quite common. Caché provides other options, however, of which you should be aware.
Summary of Variable Scope
Several factors control whether a variable is available outside of the unit of code that defines it. Before discussing those, it is necessary to point out the following environmental details:
-
A Caché instance includes multiple namespaces, including multiple system namespaces and probably multiple namespaces that you define.
A namespace is the environment in which any code runs. Namespaces are discussed later in more detail.
-
You can run multiple processes simultaneously in a namespace. In a typical application, many processes are running at the same time.
The following table summarizes where variables are available:
Variable availability, broken out by kind of variable... | Outside of unit of code that defines it (but in the same process) | In other processes in the same namespace | In other namespaces within same Caché instance |
---|---|---|---|
Local variable, private scope* | No | No | No |
Local variable, public scope | Yes | No | No |
Local percent variable | Yes | No | No |
Global variable (not percent) | Yes | Yes | Not unless global mappings permit this† |
Global percent variable | Yes | Yes | Yes |
*By default, variables defined in a procedure are private to the procedure, as noted before. Also, in a procedure, you can declare variables as public variables, although this practice is not preferred. See “User-defined Code” in Using Caché ObjectScript.
†Each namespace has default databases for specific purposes and can have mappings that give access to additional databases. Consequently, a global variable can be available to multiple namespaces, even if it is not a global percent variable. See the chapter “Namespaces and Databases.”
The NEW Command
Caché provides another mechanism to enable you to control the scope of a variable: the NEW command. The argument to this command is one or more variable names, in a comma-separated list. The variables must be public variables and cannot be global variables.
This command establishes a new, limited context for the variable (which may or may not already exist). For example, consider the following routine:
; demonew
; routine to demo NEW
NEW var2
set var1="abc"
set var2="def"
quit
After you run this routine, the variable var1 is available, and the variable var2 is not, as shown in the following example Terminal session:
SAMPLES>do ^demonew
SAMPLES>write var1
abc
SAMPLES>write var2
write var2
^
<UNDEFINED> *var2
If the variable existed before you used NEW, the variable still exists after the scope of NEW has ended, and it retains its previous value. For example, consider the following Terminal session, which uses the routine defined previously:
SAMPLES>set var2="hello world"
SAMPLES>do ^demonew
SAMPLES>write var2
hello world
Multidimensional Arrays
In ObjectScript, any variable can be a Caché multidimensional array (also called an array). A multidimensional array is generally intended to hold a set of values that are related in some way. ObjectScript provides commands and functions that provide convenient and fast access to the values; these are discussed in later sections.
You may or may not work directly with multidimensional arrays, depending on the system classes that you use and your own preferences. Caché provides a class-based alternative to use when you want a container for sets of related values; see “Collection Classes,” later in this book.
Basics
A multidimensional array consists of any number of nodes, defined by subscripts. The following example sets several nodes of an array and then prints the contents of the array:
set myarray(1)="value A"
set myarray(2)="value B"
set myarray(3)="value C"
zwrite myarray
This example shows a typical array. Notes:
-
This array has one subscript. In this case, the subscripts are the integers 1, 2, and 3.
-
There is no need to declare the structure of the array ahead of time.
-
myarray is the name of the array itself.
-
ObjectScript provides commands and functions that can act on an entire array or on specific nodes. For example:
kill myarray
You can also kill a specific node and its child nodes.
-
The following variation sets several subscripts of a global array named ^myglobal; that is, these values are written to disk:
set ^myglobal(1)="value A" set ^myglobal(2)="value B" set ^myglobal(3)="value C"
-
There is a limit to the possible length of a global reference. This limit affects the length of the global name and the length and number of any subscripts. If you exceed the limit, you get a <SUBSCRIPT> error. See the section “Maximum Length of a Global Reference” in Using Caché Globals.
-
There is a limit to the length of a value of a node. If long strings are not enabled in your installation, the limit is 32,767 characters. If long strings are enabled, the limit is much larger.
A later section of this book explains how to enable long string operations.
A multidimensional array has one reserved memory location for each defined node and no more than that. For a global, all the disk space that it uses is dynamically allocated.
Structure Variations
The preceding examples show a common form of array. Note the following possible variations:
-
You can have any number of subscripts. For example:
Set myarray(1,1,1)="grandchild of value A"
-
A subscript can be a string. The following is valid:
set myarray("notes to self","2 Dec 2010")="hello world"
Use Notes
For those who are learning ObjectScript, a common mistake is to confuse globals and arrays. It is important to remember that any variable is either local or global, and may or may not have subscripts. The following table shows the possibilities:
Example and Notes | ||
---|---|---|
Local | No subscripts | Set MyVar=10
Variables like this are quite common. The majority of the variables you see might be like this. |
Has subscripts |
Set MyVar(1)="alpha" Set MyVar(2)="beta" Set MyVar(3)="gamma" A local array like this is useful when you want to pass a set of related values. |
|
Global | No subscripts | Set ^MyVar="saved note"
In practice, globals usually have subscripts. |
Has subscripts | Set ^MyVar($USERNAME,"Preference 1")=42 |
Operators
This section provides an overview of the operators in ObjectScript; some are familiar, and others are not.
Operator precedence in ObjectScript is strictly left-to-right; within an expression, operations are performed in the order in which they appear. You can use explicit parentheses within an expression to force certain operations to be carried out ahead of others.
Typically you use parentheses even where you do not strictly need them. It is useful to other programmers (and to yourself at a later date) to do this because it makes the intent of your code clearer.
Familiar Operators
ObjectScript provides the following operators for common activities:
-
Mathematical operators: addition (+), subtraction (-), division (/), multiplication (*), integer division (\), modulus (#), and exponentiation (**)
-
Unary operators: positive (+) and negative (-)
-
String concatenation operator (_)
-
Logical comparison operators: equals (=), greater than (>), greater than or equal to (>=), less than (<), less than or equal to (<=)
-
Logical complement operator (')
You can use this immediately before any logical value as well as immediately before a logical comparison operator.
-
Operators to combine logical values: AND (&&), OR (||)
Note that ObjectScript also supports an older, less efficient form of each of these: & is a form of the && operator, and ! is a form of the || operator. You might see these older forms in existing code.
Unfamiliar Operators
ObjectScript also includes operators that have no equivalent in some languages. The most important ones are as follows:
-
The pattern match operator (?) tests whether the characters in its left operand use the pattern in its right operand. You can specify the number of times the pattern is to occur, specify alternative patterns, specify pattern nesting, and so on.
For example, the following writes the value 1 (true) if a string (testthis) is formatted as a U.S. Social Security Number and otherwise writes 0.
Set testthis="333-99-0000" Write testthis ?3N1"-"2N1"-"4N
This is a valuable tool for ensuring the validity of input data, and you can use it within the definition of class properties.
-
The binary contains operator ([) returns 1 (true) or 0 (false) depending on whether the sequence of characters in the right operand is a substring of the left operand. For example:
Set L="Steam Locomotive",S="Steam" Write L[S
-
The binary follows operator (]) tests whether the characters in the left operand come after the characters in the right operand in ASCII collating sequence.
-
The binary sorts after operator (]]) tests whether the left operand sorts after the right operand in numeric subscript collation sequence.
-
The indirection operator (@) allows you to perform dynamic runtime substitution of part or all of a command argument, a variable name, a subscript list, or a pattern. Caché performs the substitution before execution of the associated command.
Commands
This section provides an overview of the commands that you are most likely to use and to see in ObjectScript. These include commands that are similar to those in other languages, as well as others that have no equivalent in other languages.
The names of commands are not case-sensitive, although they are shown in running text in all upper case by convention.
Familiar Commands
ObjectScript provides commands to perform familiar tasks such as the following:
-
To define variables, use SET as shown previously.
-
To remove variables, use KILL as shown previously.
-
To control the flow of logic, use the following commands:
-
IF, ELSEIF, and ELSE, which work together
-
FOR
-
WHILE, which can be used on its own
-
DO and WHILE, which can be used together
-
QUIT, which can also return a value
There are other commands for flow control, but they are used less often.
-
-
To trap errors, use TRY and CATCH, which work together.
-
To write a value, use WRITE. This writes values to the current device (for example, the Terminal or a file).
Used without an argument, this command writes the values of all local variables. This is particularly convenient in the Terminal.
This command can use a small set of format control code characters that position the output. In existing code, you are likely to see the exclamation point, which starts a new line. For example:
write "hello world",!,"another line"
-
To read a value from the current device (for example, the Terminal), use READ.
-
To work with devices other than the principal device, use the following commands:
-
OPEN makes a device available for use.
-
USE specifies an open device as the current device for use by WRITE and READ.
-
CLOSE makes a device no longer available for use.
-
-
To control concurrency, use LOCK. Note that the Caché lock management system is different from analogous systems in other languages. It is important to review how it works; see “Locking and Concurrency Control,” later in this chapter.
You use this command in cases where multiple processes can potentially access the same variable or other item.
-
To manage transactions, use TSTART, TCOMMIT, TROLLBACK, and related commands.
-
For debugging, use ZBREAK and related commands.
-
To suspend execution, use HANG.
Commands for Use with Multidimensional Arrays
In ObjectScript, you can work with multidimensional arrays in the following ways:
-
To define nodes, use the SET command.
-
To remove individual nodes or all nodes, use the KILL command.
For example, the following removes an entire multidimensional array:
kill myarray
In contrast, the following removes the node myarray("2 Dec 2010") and all its children:
kill myarray("2 Dec 2010")
-
To delete a global or a global node but none of its descendent subnodes, use ZKILL.
-
To iterate through all nodes of a multidimensional array and write them all, use ZWRITE. This is particularly convenient in the Terminal. The following sample Terminal session shows what the output looks like:
SAMPLES>ZWRITE ^myarray ^myarray(1)="value A" ^myarray(2)="value B" ^myarray(3)="value C"
This example uses a global variable rather than a local one, but remember that both can be multidimensional arrays.
-
To copy a set of nodes from one multidimensional array into another, preserving existing nodes in the target if possible, use MERGE. For example, the following command copies an entire in-memory array (sourcearray) into a new global (^mytestglobal):
MERGE ^mytestglobal=sourcearray
This can be a useful way of examining the contents of an array that you are using, while debugging your code.
Special Variables
This section introduces some Caché special variables. The names of these variables are not case-sensitive.
Some special variables provide information about the environment in which the code is running. These include the following:
-
$HOROLOG, which contains the date and time for the current process, as given by the operating system. See “Date and Time Values,” later in this chapter.
-
$USERNAME and $ROLES, which contain information about the username currently in use, as well as the roles to which that user belongs.
write "You are logged in as: ", $USERNAME, !, "And you belong to these roles: ",$ROLES
-
$ZVERSION, which contains a string that identifies the currently running version of Caché.
Others include $JOB, $ZTIMEZONE, $IO, and $ZDEVICE.
Other variables provide information about the processing state of the code. These include $STACK, $TLEVEL, $NAMESPACE, and $ZERROR.
$SYSTEM Special Variable
The special variable $SYSTEM provides language-independent access to a large set of utility methods.
The special variable $SYSTEM is an alias for the %SYSTEM package, which contains classes that provide class methods that address a wide variety of needs. The customary way to refer to methods in %SYSTEM is to build a reference that uses the $SYSTEM variable. For example, the following command executes the SetFlags() method in the %SYSTEM.OBJOpens in a new tab class:
DO $SYSTEM.OBJ.SetFlags("ck")
Because names of special variables are not case-sensitive (unlike names of classes and their members), the following commands are all equivalent:
DO ##class(%SYSTEM.OBJ).SetFlags("ck")
DO $System.OBJ.SetFlags("ck")
DO $SYSTEM.OBJ.SetFlags("ck")
DO $system.OBJ.SetFlags("ck")
The classes all provide the Help() method, which can print a list of available methods in the class. For example:
SAMPLES>d $system.OBJ.Help()
'Do $system.OBJ.Help(method)' will display a full description of an individual method.
Methods of the class: %SYSTEM.OBJ
CloseObjects()
Deprecated function, to close objects let them go out of scope.
Compile(classes,qspec,&errorlog,recurse)
Compile a class.
CompileAll(qspec,&errorlog)
Compile all classes within this namespace
....
You can also use the name of a method as an argument to Help(). For example:
SAMPLES>d $system.OBJ.Help("Compile")
Description of the method:class Compile:%SYSTEM.OBJ
Compile(classes:%String="",qspec:%String="",&errorlog:%String,recurse:%Boolean=0)
Compile a class.
<p>Compiles the class <var>classes</var>, which can be a single class, a comma separated list,
a subscripted array of class names, or include wild cards. If <var>recurse</var> is true then
do not output the intial 'compiling' message or the compile report as this is being called inside
another compile loop.<br>
<var>qspec</var> is a list of flags or qualifiers which can be displayed with
'Do $system.OBJ.ShowQualifiers()'
and 'Do $system.OBJ.ShowFlags()
Locking and Concurrency Control
An important feature of any multi-process system is concurrency control, the ability to prevent different processes from changing a specific element of data at the same time, resulting in corruption. Consequently, ObjectScript provides a lock management system. This section provides a brief summary.
Also see “Locks, Globals, and Namespaces,” later in this book.
Basics
The basic locking mechanism is the LOCK command. The purpose of this command is to delay activity in one process until another process has signaled that it is OK to proceed.
It is important to understand that a lock does not, by itself, prevent other processes from modifying the associated data; that is, Caché does not enforce unilateral locking. Locking works only by convention: it requires that mutually competing processes all implement locking with the same lock names.
You can use the LOCK command to create locks (replacing all previous locks owned by the process), to add locks, to remove specific locks, and to remove all locks owned by the process.
For the purpose of this simple discussion, the LOCK command uses the following arguments:
-
The lock name. Lock names are arbitrary, but by universal convention, programmers use lock names that are identical to the names of the item to be locked. Usually the item to be locked is a global or a node of a global.
-
The optional lock type (to create a non-default type of lock). There are several lock types, with different behaviors.
-
An optional timeout argument, which specifies how long to wait before the attempted lock operation times out. By default, Caché waits indefinitely.
The following describes a common lock scenario: Process A issues the LOCK command, and Caché attempts to create a lock. If process B already has a lock with the given lock name, process A pauses. Specifically, the LOCK command in process A does not return, and no successive lines of code can be executed. When the process B releases the lock, the LOCK command in process A finally returns and execution continues.
The system automatically uses the LOCK command internally in many cases, such as when you work with persistent objects (discussed later in this book) or when you use certain Caché SQL commands.
The Lock Table
Caché maintains a system-wide, in-memory table that records all current locks and the processes that own them. This table — the lock table — is accessible via the Management Portal, where you can view the locks and (in rare cases, if needed) remove them. Note that any given process can own multiple locks, with different lock names (or even multiple locks with the same lock name).
When a process ends, the system automatically releases all locks that the process owns. Thus it is not generally necessary to remove locks via the Management Portal, except in the case of an application error.
The lock table cannot exceed a fixed size, which you can specify. For information, see “Monitoring Locks” in the Caché Monitoring Guide. Consequently, it is possible for the lock table to fill up, such that no further locks are possible. If this occurs, Caché writes the following message to the cconsole.log file:
LOCK TABLE FULL
Filling the lock table is not generally considered to be an application error; Caché also provides a lock queue, and processes wait until there is space to add their locks to the lock table.
However, if two processes each assert an incremental lock on a variable already locked by the other process, that is a condition called deadlock and it is considered an application programming error. For details, see “Avoiding Deadlock” in the chapter “Lock Management” in Using Caché ObjectScript.
Locks and Arrays
When you lock an array, you can lock either the entire array or one or more nodes in the array. When you lock an array node, other processes are blocked from locking any node that is subordinate to that node. Other processes are also blocked from locking the direct ancestors of the locked node.
The following figure shows an example:
![generated description: lock implicit](images/gorient_lock_implicit.png)
Introduction to Lock Types
When you create a lock, you specify a combination of lock type codes, which control the nature of the lock. This section discusses some of the key concepts of lock types.
Depending on the lock type, it is possible to create multiple locks with the same lock name. These locks can be owned by the same process or different processes, again depending on the lock type. The lock table displays information for all of them.
Any lock is either exclusive (the default) or shared. These types have the following significance:
-
While one process has an exclusive lock (with a given lock name), no other process can acquire any lock with that lock name.
-
While one process has a shared lock (with a given lock name), other processes can acquire shared locks with that lock name, but no other process can acquire an exclusive lock with that lock name.
In general, the purpose of an exclusive lock is to indicate that you intend to modify a value and that other processes should not attempt to read or modify that value. The purpose of a shared lock is to indicate that you intend to read a value and that other processes should not attempt to modify that value; they can, however, read the value.
Any lock is also either non-escalating (the default) or escalating. The purpose of escalating locks is to make it easier to manage large numbers of locks, which consume memory and which increase the chance of filling the lock table. You use escalating locks when you lock multiple nodes of the same array. For escalating locks, if a given process has created more than a specific number (by default, 1000) of locks on sibling nodes of a given array, Caché removes all the individual lock names and replaces them with a new lock at the parent level. For example, you might have 1000 locks of the form ^MyGlobal("sales","EU",salesdate) where salesdate represents dates. When the same process attempts to create another lock of this form (and these locks are all escalating), Caché removes all these locks and replaces them with a lock of the name ^MyGlobal("sales","EU"). The lock table maintains the lock count for this new lock. This lock count is currently 1001, but when you add additional lock names of the same form, the lock table increments the lock count for the lock name ^MyGlobal("sales","EU"). Similarly, when you remove lock names of the same form, the lock table decrements this lock count.
There are additional subtypes of locks that Caché treats in specific ways within transactions. For details on these and for more information on locks in general, see LOCK in the Caché ObjectScript Reference. For information on specifying the lock threshold (which by default is 1000), see “LockThreshold” in the Caché Parameter File Reference.
System Functions
This section highlights some of the most commonly used system functions in ObjectScript.
The names of these functions are not case-sensitive.
The Caché class library also provides a large set of utility methods that you can use in the same way that you use functions. To find a method for a particular purpose, use the InterSystems Programming Tools Index.
See also “Date and Time Values,” later in this chapter.
Value Choice
You can use the following functions to choose a value, given some input:
-
$CASE compares a given test expression to a set of comparison values and then returns the return value associated with the matching comparison value. For example:
SAMPLES>set myvar=1 SAMPLES>write $CASE(myvar,0:"zero",1:"one",:"other") one
-
$SELECT examines a set of expressions and returns the return value associated with the first true expression. For example:
SAMPLES>set myvar=1 SAMPLES>write $SELECT(myvar=0:"branch A",1=1:"branch B") branch B
Existence Functions
You can use the following functions to test for the existence of a variable or of a node of a variable.
-
To test if a specific variable exists, use the $DATA function.
For a variable that contains multiple nodes, this function can indicate whether a given node exists, and whether a given node has a value and child nodes.
-
To get the value of a variable (if it exists) or get a default value (if not), use the $GET function.
List Functions
ObjectScript provides a native list format. You can use the following functions to create and work with these lists:
-
$LISTBUILD returns a special kind of string called a list. Sometimes this is called $LIST format, to distinguish this kind of list from other kinds (such as comma-separated lists).
The only supported way to work with a $LIST list is to use the ObjectScript list functions. The internal structure of this kind of list is not documented and is subject to change without notice.
-
$LIST returns a list element or can be used to replace a list element.
-
$LISTLENGTH returns the number of elements in a list.
-
$LISTFIND returns the position of a given element, in a given list.
There are additional list functions as well.
If you use a list function with a value that is not a list, you receive the <LIST> error.
The system class %Library.ListOpens in a new tab is equivalent to a list returned by $LISTBUILD. That is, when a class has a property of type %Library.ListOpens in a new tab, you use the functions named here to work with that property. You can refer to this class by its short name, %ListOpens in a new tab.
Caché provides other list classes that are not equivalent to a list returned by $LISTBUILD. These are useful if you prefer to work with classes. For an introduction, see “Collection Classes,” later in this book.
String Functions
ObjectScript also has an extensive set of functions for using strings efficiently:
-
$EXTRACT returns or replaces a substring, using a character count.
-
$FIND finds a substring by value and returns an integer specifying its end position in the string.
-
$JUSTIFY returns a right-justified string, padded on the left with spaces.
-
$ZCONVERT converts a string from one form to another. It supports both case translations (to uppercase, to lowercase, or to title case) and encoding translation (between various character encoding styles).
-
$TRANSLATE modifies the given string by performing a character-by-character replacement.
-
$REPLACE performs string-by-string replacement within a string and returns a new string.
-
$PIECE returns a substring from a character-delimited string (often called a pieced string). Many older applications use character-delimited strings as a convenient format to contain related values, each of which is a substring within the larger string. The large string acts as a record, and the substrings are its fields.
In many cases, the delimiter is a caret. Thus in existing code, you might see pieced strings like this: "value 1^value 2^value 3"
The following demonstrates how to extract a substring:
SET mystring="value 1^value 2^value 3" WRITE $PIECE(mystring,"^",1)
-
$LENGTH returns the number of characters in a specified string or the number of delimited substrings in a specified string, depending on the parameters used.
For example:
SET mystring="value 1^value 2^value 3" WRITE !, "Number of characters in this string: " WRITE $LENGTH(mystring) WRITE !, "Number of pieces in this string: " WRITE $LENGTH(mystring,"^")
Working with Multidimensional Arrays
You can use the following functions to work with a multidimensional array as a whole:
-
$ORDER allows you to sequentially visit each node within a multidimensional array.
-
$QUERY enables you to visit every node and subnode within an array, moving up and down over subnodes.
To work with an individual node in an array, you can use any of the functions described previously. In particular:
-
$DATA can indicate whether a given node exists and whether a given node has child nodes.
-
$GET gets the value of a given node or gets a default value otherwise.
Character Values
Sometimes when you create a string, you need to include characters that cannot be typed. For these, you use $CHAR.
Given an integer, $CHAR returns the corresponding ASCII or Unicode character. Common uses:
-
$CHAR(9) is a tab.
-
$CHAR(10) is a line feed.
-
$CHAR(13) is a carriage return.
-
$CHAR(13,10) is a carriage return and line feed pair.
The function $ASCII returns the ASCII value of the given character.
$ZU Functions
In existing code, you might see items like $ZU(n), $ZUTIL(n), $ZU(n,n), and so on, where n is an integer. These are the $ZU functions, which are now deprecated and are no longer documented. They are still available, but users are encouraged to replace them with methods and properties in the Caché class library that perform the equivalent actions. There is a table of replacements in “Replacements for ObjectScript $ZUTIL Functions” in the Caché ObjectScript Reference.
Date and Time Values
This section provides a quick overview of date and time values in ObjectScript.
Local Time
To access the date and time for the current process, you use the $HOROLOG special variable. Because of this, in many Caché applications, dates and times are stored and transmitted in the format used by this variable. This format is often called $H format or $HOROLOG format.
$HOROLOG retrieves the date and time from the operating system and is thus always in the local time zone.
The Caché class library includes data type classes to represent dates in more common formats such as ODBC, and many applications use these instead of $H format.
UTC Time
Caché also provides the $ZTIMESTAMP special variable, which contains the current date and time as a Coordinated Universal Time value in $H format. This is a worldwide time and date standard; this value is very likely to differ from your local time (and date) value.
Date and Time Conversions
ObjectScript includes functions for converting date and time values.
-
Given a date in $H format, the function $ZDATE returns a string that represents the date in your specified format.
For example:
SAMPLES>WRITE $ZDATE($HOROLOG,3) 2010-12-03
-
Given a date and time in $H format, the function $ZDATETIME returns a string that represents the date and time in your specified format.
For example:
SAMPLES>WRITE $ZDATETIME($HOROLOG,3) 2010-12-03 14:55:48
-
Given string dates and times in other formats, the functions $ZDATEH and $ZDATETIMEH convert those to $H format.
-
The functions $ZTIME and $ZTIMEH convert times from and to $H format.
Details of the $H Format
The $H format is a pair of numbers separated by a comma. For example: 54321,12345
-
The first number is the number of days since December 31st, 1840. That is, day number 1 is January 1st, 1841. This number is always an integer.
-
The second number is the number of seconds since midnight on the given day.
Some functions, such as $NOW(), provide a fractional part.
For additional details, including an explanation of the starting date, see $HOROLOG in the Caché ObjectScript Reference.
Using Macros and Include Files
As noted earlier, you can define macros and use them later in the same routine. More commonly, you define them in include files. An include file is a document with the extension .inc in Studio.
To define a macro, use the #define directive or other preprocessor directive. For example:
#define mymacro "hello world"
To include an include file in a routine, use the #Include directive. For example:
#Include myincludefile
(Note that the syntax is slightly different in class definitions.)
To refer to a macro, use the following syntax:
$$$macroname
Or:
$$$macroname(arguments)
The preprocessor directives are documented in “ObjectScript Macros and the Macro Preprocessor” in Using Caché ObjectScript.
Both Studio and the Management Portal list the include files with the routines. For example, the Studio Workspace window shows include files within the Routines folder. Include files are not, however, actually routines because they are not executable.
Using Routines and Subroutines
To execute a routine, you use the DO command, as follows:
do ^routinename
To execute a procedure, function, or subroutine (without accessing its return value), you use the following command:
do label^routinename
Or:
do label^routinename(arguments)
To execute a procedure, function, or subroutine and refer to its return value, you use an expression of the form $$label^routinename or $$label^routinename(arguments). For example:
set myvariable=$$label^routinename(arguments)
In all cases, if the label is within the same routine, you can omit the caret and routine name. For example:
do label
do label(arguments)
set myvariable=$$label(arguments)
In all cases, the arguments that you pass can be either literal values, expressions, or names of variables.
Passing Variables by Value or by Reference
When you invoke code, you can pass values of variables to that code either by value or by reference. In most cases, these variables are local variables with no subscripts, so this section discusses those first.
As with other programming languages, Caché has a memory location that contains the value of each local variable. The name of the variable acts as the address to the memory location.
When you pass a local variable with no subscripts to procedure, function, or subroutine, you pass the variable by value. This means that the system makes a copy of the value, so that the original value is not affected. To pass the memory address instead, place a period immediately before the name of the variable in the argument list. For example:
do ^myroutine(.myarg)
To demonstrate this, consider the following procedure:
square(input) public
{
set answer=input*input
set input=input + 10
quit answer
}
Suppose that you define a variable and pass it by value to this procedure:
SAMPLES>set myvariable=5
SAMPLES>write $$square^demobyref(myvariable)
25
SAMPLES>write myvariable
5
In contrast, suppose that you pass the variable by reference:
SAMPLES>set myvariable=5
SAMPLES>write $$square^demobyref(.myvariable)
25
SAMPLES>write myvariable
15
There are other variations of variables in addition to local variables with no subscripts. The following table summarizes all the possibilities:
Kind of Variable | Passing by Value | Passing by Reference |
---|---|---|
Local variable with no subscripts | The default way in which these variables are passed | Allowed |
Local variable (with subscripts) | Cannot be passed this way (only the top node would be passed) | Required |
Global variable (with or without subscripts) | Required | Cannot be passed this way (data for a global is not in memory) |
Potential Pitfalls
The following items can confuse programmers who are new to ObjectScript, particularly if those who are responsible for maintaining code written by other programmers:
-
Within a routine or a method, every line must be indented by at least one space or one tab unless that line contains a label. That is, if there is text of any kind in the first character position, the compiler and Studio treat it as a label. Note that Studio displays labels in red, as seen in previous examples.
There is one exception: A curly brace is accepted in the first character position.
-
There must be exactly one space (not a tab) between a command and its first argument. Otherwise, Studio indicates that you have a syntax error:
Similarly, the Terminal displays a syntax error as follows:
SAMPLES>write 5 WRITE 5 ^ <SYNTAX> SAMPLES>
-
Operator precedence in ObjectScript is strictly left-to-right; within an expression, operations are performed in the order in which they appear. You can use explicit parentheses within an expression to force certain operations to be carried ahead of others.
Typically you use parentheses even where you do not strictly need them. It is useful to other programmers (and to yourself at a later date) to do this because it makes the intent of your code clearer.
-
For reasons of history, ObjectScript does not consider an empty string ("") to equal the ASCII NULL value. To represent the ASCII NULL value, use $CHAR(0). ($CHAR is a system function that returns an ASCII character, given its decimal-based code.) For example:
write "" = $char(0)
Similarly, when ObjectScript values are projected to SQL or XML, the values "" and $CHAR(0) are treated differently. For information on the SQL projections of these values, see “Null and the Empty String” in the chapter “Language Elements” in Using Caché SQL. For information on the XML projections of these values, see the chapter “Handling Empty Strings and Null Values” in Projecting Objects to XML.
-
Some parts of ObjectScript are case-sensitive while others are not. The case-insensitive items include names of commands, functions, special variables, namespaces, and users.
The case-sensitive items include names of most of the elements that you define: routines, variables, classes, properties, and methods. For more details, see “Syntax Rules” in Using Caché ObjectScript.
-
Most command names can be represented by an abbreviated form. Therefore, WRITE, Write, write, W, and w are all valid forms of the WRITE command. For a list, see “Table of Abbreviations” in the Caché ObjectScript Reference.
-
For many of the commands, you can include a postconditional expression (often simply called a postconditional).
This expression controls whether Caché executes the command. If the postconditional expression evaluates to true (nonzero), Caché executes the command. If the expression evaluates to false (zero), Caché ignores the command and continues with the next command.
For example:
Set count = 6 Write:count<5 "Print this if count is less than five" Write:count>5 "Print this if count is greater than five"
The preceding generates the following output: Print this if count is greater than five
Note:If postconditionals are new to you, you might find the phrase “postconditional expression” somewhat misleading, because it suggests (incorrectly) that the expression is executed after the command. Despite the name, a postconditional is executed before the command.
-
You can include multiple commands on a single line. For example:
set myval="hello world" write myval
When you do this, beware that you must use two spaces after any command that does not take arguments, if there are additional commands on that line; if you do not do so, a syntax error occurs.
-
The IF, ELSE, FOR, and DO commands are available in two forms:
-
A newer block form, which uses curly braces to indicate the block. For example:
if (testvalue=1) { write "hello world" }
InterSystems recommends that you use the block form in all new code.
-
An older line-based form, which does not use curly braces. For example:
if (testvalue=1) write "hello world"
-
-
As a result of the preceding items, ObjectScript can be written in a very compact form. For example:
s:$g(%d(3))'="" %d(3)=$$fdN3(%d(3)) q
The class compiler automatically generates compact code of the form shown above (although not necessarily with abbreviated commands as in this example). Sometimes it is useful to look at this generated code, to track down the source of a problem or to understand how something works.
Many older applications are written in the compact form shown here.
-
There are no truly reserved words in ObjectScript, so it is theoretically possible to have a variable named set, for example. However, it is prudent to avoid names of commands, functions, SQL reserved words, and certain system items; see “Syntax Rules” in Using Caché ObjectScript.
-
Caché allocates a fixed amount of memory to hold the results of string operations. If a string expression exceeds the amount of space allocated, a <MAXSTRING> error results. If long strings are not enabled, the limit is 32,767 characters. If long strings are enabled, the limit is much larger. A later section of this book explains how to enable long string operations.
For class definitions, the string operation limit affects the size of string properties. Caché provides a system object (called a stream) that you can use when you need to work with strings that exceed this limit. A later section of this book explains how to use the stream interface classes.
For More Information
The chapters after this provide more detail on the topics covered in this chapter. This information is taken from the following books:
-
Using Caché ObjectScript provides details on ObjectScript.
-
Using Caché Globals provides details on multidimensional arrays and globals.
-
Caché ObjectScript Reference provides reference information for the operators, commands, functions, special variables, and other parts of ObjectScript.
The Caché documentation also includes books on Caché MVBasic and Caché Basic, which this book does not discuss in detail.