Introduction to Caché Programming
This book is intended as an introduction for programmers who are not familiar with Caché or who are familiar with only some kinds of Caché programming. It is not a tutorial but rather a survey of the elements in the Caché toolkit, with information on how these elements fit together. After reading this book, you should have an idea of the options available to you and where to find more information on those options.
For this book, the emphasis is on server-side programming, rather than client-side programming. Via various Caché language bindings, a Caché server can work with clients written in many different languages. If you are creating or maintaining such clients, most of this book is probably inapplicable to you.
This chapter provides a high-level overview of the language elements you can use in Caché server-side programs and shows how Caché combines them. It discusses the following topics:
Introduction
Caché is a high-performance object database with several built-in general-purpose programming languages. It supports multiple processes and provides concurrency control. Each process has direct, efficient access to the data.
In Caché, you can write routines, classes, or a mix of these, as suits your preferences and the history of your application. In all cases, stored data is ultimately contained in structures known as globals (a later section in this chapter discusses these). Caché programming has the following features:
-
Routines and classes can be used interchangeably and can be written in more than one language.
-
Routines and classes can call each other.
-
Classes provide object-oriented features.
-
Database storage is an integrated part of all Cache programming languages.
-
Classes can persist data in a way that simplifies programming. If you use persistent classes, data is simultaneously available as objects, SQL tables, and globals.
-
You can access globals directly from either routines or classes, which means that you have the flexibility to store and access data exactly how you want.
Older Caché applications consist entirely of routines, because these applications were written before Caché supported class definitions. In contrast, some newer applications are written almost entirely in classes. You can choose the approach that is appropriate for your needs.
Routines
When you create routines in Caché, you can choose the programming language for each routine. The choices are as follows:
-
ObjectScript, which is a superset of the ISO 11756-1999 standard M programming language. If you are an M programmer, you can run your existing M applications on Caché with no change.
This is the most common language for Caché routines.
-
Caché MVBasic is an implementation of MultiValueOpens in a new tab. It includes commands, functions, and operators that are used in the various MultiValue implementations, and it supports multiple emulation modes so that you can use syntax that is familiar to you.
-
Caché Basic is an implementation of Basic.
Because it is useful to see what these languages look like, the following shows part of a routine written in ObjectScript:
SET text = ""
FOR i=1:5:$LISTLENGTH(attrs)
{
IF ($ZCONVERT($LIST(attrs, (i + 1)), "U") = "XREFLABEL")
{
SET text = $LIST(attrs, (i + 4))
QUIT
}
}
IF (text = "")
{
QUIT $$$ERROR($$$GeneralError,$$$T("Missing xreflabel value"))
}
The following shows part of a routine written in MVBasic:
#PRAGMA ROUTINENAME=ADDROUTINENAME
$OPTIONS CACHE
OPEN 'VOC' TO F.VOC ELSE STOP
BAD.LST = ''
* If there is an active select list use it, otherwise get file names from the VOC
IF SYSTEM(11) > 0 THEN
NBR.PROG.FILES = SYSTEM(11)
END ELSE
EXECUTE 'SELECT VOC WITH F6 LIKE B...' CAPTURING RES RETURNING NBR.PROG.FILES
END
IF NBR.PROG.FILES THEN
CRT 'THERE ARE ':NBR.PROG.FILES:' PROGRAM FILES'
EXECUTE 'SAVE-LIST PGM.FILES' PASSLIST
READLIST ALL.PROG.FILES FROM 'PGM.FILES' ELSE STOP
HOW.MANY = DCOUNT(ALL.PROG.FILES,@AM)
FOR I = 1 TO HOW.MANY
THIS.PROG.FILE = ALL.PROG.FILES<I>
CRT
...
The following shows part of a routine written in Caché Basic:
' display an ordered list of matches
' user can enter full or partial name, full or partial phone, or a valid date
' pick from a list of matches, and EDIT their choice
Option Explicit
dim id, name, phone, intdob, matches
public sub main()
dim done
do
getsubmit(id, done) ' let user submit a string for lookup
if id = 0 then continue do
display(id, "table") ' display the chosen person
edit(id) ' edit the chosen person
loop until done
end sub
private sub getsubmit(ByRef id as %Integer, ByRef done as %Boolean)
' ask user what to search for, and take appropriate action
dim submit
id = 0 : done = False
println : input "Lookup: ", submit : println
if (submit = "") then ' user entered nothing
done = True
exit sub
end if
' figure out what user entered
...
The next chapter provides an introduction to ObjectScript. This book does not discuss Caché MVBasic or Caché Basic in any detail.
Classes
Caché also supports classes. You can use the system classes and you can define your own classes.
In Caché, a class can include familiar class elements such as properties, methods, and parameters (known as constants in other class languages). It can also include items not usually defined in classes, including triggers, queries, and indices.
The following shows a class definition:
Class Sample.Employee Extends Person
{
/// The employee's job title.
Property Title As %String(MAXLEN = 50);
/// The employee's current salary.
Property Salary As %Integer(MAXVAL = 100000, MINVAL = 0);
/// A character stream containing notes about this employee.
Property Notes As %Stream.GlobalCharacter;
/// A picture of the employee
Property Picture As %Stream.GlobalBinary;
/// This method overrides the method in the Person class.
Method PrintPerson()
{
Write !,"Name: ", ..Name, ?30, "Title: ", ..Title
Quit
}
}
For each method, you can specify the programming language to use in the definition of that method, although typically all methods in a class use the same language, for simplicity. The default is ObjectScript; the next chapter provides an introduction to it. The other choices are Caché MVBasic, Caché Basic, and Transact-SQL. (There are additional options for client-side programming, not discussed in this book.)
You can use classes from within routines. For example, the following shows part of a routine, in which we refer to the Sample.Person class:
//get details of requested person; print them
showperson() public {
set rand=$RANDOM(10)+1 ; rand is an integer in the range 1-10
write "Your random number: "_rand
set person=##class(Sample.Person).%OpenId(rand)
write !,"This person's name: "_person.Name
write !,"This person's age: "_person.Age
write !,"This person's home city: "_person.Home.City
}
Similarly, a method can invoke a label in a routine. For example:
Method DemoRoutineCall(input) as %String
{
Set value=$$function^myroutine(input)
Quit value
}
The chapter “Basic Ideas in Class Programming” provides a brief introduction to class programming, for the benefit of readers who have not done this kind of programming. The chapters after that discuss classes in Caché and the unique capabilities of persistent classes in Caché.
Introduction to Globals
Caché supports a special kind of variable that is not seen in other programming languages; this is a global variable, which is usually just called a global. In Caché, the term global indicates that this data is available to all processes accessing this database. This usage is different from other programming languages in which global means “available to all code in this module.”
The contents of a global are stored in a Caché database. The next chapter introduces these more thoroughly; for now, it is important just to know the following points:
-
A global consists of a set of nodes (in some cases, only one node), identified by subscripts.
Each node can contain a value.
-
ObjectScript, Caché Basic, and Caché MVBasic include functions to iterate through the nodes of a global and quickly access values.
-
A global is automatically stored in the database. When you assign a value to a node of a global variable, the data is written immediately to the database.
-
You can see the contents of a global via an ObjectScript command or via the Management Portal.
Ways to Access Data
In Caché, a database contains globals and nothing else; even code is stored in globals, as described later in this book. At the lowest level, all access to data is done via direct global access — that is, by using commands and functions that work directly with globals.
Many currently running applications were developed long before Caché included support for classes. Some of these applications use direct global access. Other applications use custom APIs such as FileMan, which is in the public domain. These APIs, of course, internally use direct global access.
When you use persistent classes, discussed later in this book, you can create, modify, and delete stored data in either of the following ways:
-
By using methods such as %New(), %Save(), %Open(), and %Delete().
-
By using Caché SQL.
Internally, the system always uses direct global access.
Because object classes provide a more controlled interface, and because Caché persistent classes are projected to tables that can be queried via SQL, it is often desirable to add a class interface to existing applications. You can do so, if you understand the structure of the globals.
Implications of Using Globals
Globals are stored physically in a highly optimized structure, and the code that manages this structure is separately optimized for every platform that Caché runs on. These optimizations ensure that operations on globals have high throughput (number of operations per unit of time), high concurrency (total number of simultaneous users), efficient use of cache memory, and require no ongoing performance-related maintenance (such as frequent rebuilding, re-indexing, or compaction). The physical structure used to store globals is completely encapsulated; it is generally unnecessary to consider the physical data structure.
Global storage is sparse, meaning that only nodes with data values are stored in the database. This means that Caché often requires less than half of the space needed by a relational database.
Other benefits of globals include the following:
-
The hierarchical structure of globals typically models real-world data more closely than is possible with relational tables.
-
The sparse nature means that new fields can be added without any overhead and without rebuilding the existing database.
-
More data can be read or written with a single I/O operation, and data can be cached more efficiently.
Caché SQL
As noted previously, Caché provides an implementation of SQL, known as Caché SQL.
You can use Caché SQL within routines and within methods. To use SQL in these contexts, you can use either or both of the following tools:
-
Dynamic SQL (the %SQL.StatementOpens in a new tab and %SQL.StatementResultOpens in a new tab classes), as in the following example:
SET myquery = "SELECT TOP 5 Name,Home_City FROM Sample.Person ORDER BY Age" SET tStatement = ##class(%SQL.Statement).%New() SET tStatus = tStatement.%Prepare(myquery) SET rset = tStatement.%Execute() DO rset.%Display() WRITE !,"End of data"
You can use dynamic SQL in any context.
-
Embedded SQL, as in the following example:
&sql(SELECT COUNT(*) INTO :myvar FROM Sample.Person) Write myvar
The first line is embedded SQL, which executes a Caché SQL query and writes a value into a host variable called myvar.
The next line is ordinary ObjectScript; it simply writes the value of the variable myvar.
You can use embedded SQL in ObjectScript routines and in methods written in ObjectScript.
Macros
ObjectScript also supports macros, which define substitutions. The definition can either be a value, an entire line of code, or (with the ##continue directive) multiple lines. You use macros to ensure consistency. For example:
#define StringMacro "Hello, World!"
write $$$StringMacro
To give you an idea of what can be done in macros, the following example shows the definition of a macro that is used internally:
#define output1(%str,%lf,%indent) do output^%fm2class(%str,%lf,%indent,$$$display)
This macro accepts an argument, as many of them do. It also refers to another macro.
You can use macros in routines as well as in classes. Some of the system classes use them extensively.
Include Files
You can define macros in a routine and use them later in the same routine. More commonly, you define them in a central place. To do this, you create and use include files. An include file defines macros and can include other include files.
The following shows parts of a system include file:
/// Create a success %Status code
#define OK 1
/// Return true if the %Status code is success, and false otherwise
/// %sc - %Status code
#define ISOK(%sc) (+%sc)
/// Return true if the %Status code if an error, and false otherwise
/// %sc - %Status code
#define ISERR(%sc) ('%sc)
Here is another include file, in entirety:
#include %occCacheDirect
#include %occExtent
#include %occTransaction
#include %occInclude
#include %msql
#include %cspInclude
Then you can do the following:
-
Include the include file at the start of any routine. That routine can refer to the macros defined in the include file.
-
Include the include file at the start of any class. Methods in that class can refer to the macros.
-
Include the include file at the start of any method. That method can refer to the macros.
Note that you use slightly different syntax to include an include file within a class definition. See “Macros and Include Files in Class Definitions,” later in this book.
How These Code Elements Work Together
It is useful to understand how Caché uses the code elements introduced in this chapter.
The reason that you can use a mix of ObjectScript, Caché SQL, Caché MVBasic, macros, class definitions, routines, and so on is that Caché does not directly use the code that you write. Instead, when you compile your code, the system generates the code that it uses, which is OBJ code, used by the Caché virtual machine.
There are multiple steps; the preceding figure gives a general idea of them. It is not necessary to know the steps in detail, but the following points are good to remember:
-
The CSP engine converts CSP files into class definitions.
-
The class compiler uses the class definitions and generates INT code, Caché MVBasic code, and Caché Basic code, depending on the languages used to define the methods in the classes.
In some cases, the compiler first uses classes as the basis for generating and saving additional classes. You can look at these classes in Studio, but do not modify them. This occurs, for example, when you compile classes that define web services and web clients.
The class compiler also generates the class descriptor for each class. The system code uses this at runtime.
-
A preprocessor (sometimes called the macro preprocessor or MPP) uses the include files (not shown in the figure) and replaces the macros. It also handles the embedded SQL in the ObjectScript routines and the MVBasic routines.
These changes occur in a temporary work area, and your code is not changed.
-
Additional compilers create INT code for the ObjectScript routines and MVI code for the MVBasic routines. This layer is known as intermediate code. In this layer, all access to the data is done via direct global access.
Both INT code and MVI code are compact but human-readable. A later section of this book shows you how to find this code, which can be useful for diagnostic purposes.
Note that there is no intermediate code for Caché Basic.
-
INT code, MVI code, and Caché Basic are used to generate OBJ code.
The Caché virtual machine uses this code. Once you have compiled your code, the routines, INT code, and MVI code are no longer necessary for code execution.
-
After you compile your classes, you can put them into deployed mode. Caché has a utility that removes the class internals and the intermediate code for a given class; you can use this utility when you deploy your application.
If you examine the Caché system classes, you might find that some classes cannot be seen because they are in deployed mode.
All routines and class definitions are stored in the same Caché databases as the generated code. This fact makes the code easier to manage. Caché provides a robust set of source control hooks for Studio that InterSystems developers have used for many years. You can use these hooks as well; for an introduction, see “Development Tools,” later in this book.