25.01.2015 Views

Using Caché Objects - InterSystems Documentation

Using Caché Objects - InterSystems Documentation

Using Caché Objects - InterSystems Documentation

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong><br />

Version 5.2<br />

01 September 2006<br />

<strong>InterSystems</strong> Corporation 1 Memorial Drive Cambridge MA 02142 www.intersystems.com


<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong><br />

<strong>Caché</strong> Version 5.2 01 September 2006<br />

Copyright © 2006 <strong>InterSystems</strong> Corporation.<br />

All rights reserved.<br />

This book was assembled and formatted in Adobe Page Description Format (PDF) using tools and information from<br />

the following sources: Sun Microsystems, RenderX, Inc., Adobe Systems, and the World Wide Web Consortium at<br />

www.w3c.org. The primary document development tools were special-purpose XML-processing applications built<br />

by <strong>InterSystems</strong> using <strong>Caché</strong> and Java.<br />

The <strong>Caché</strong> product and its logos are registered trademarks of <strong>InterSystems</strong> Corporation.<br />

The Ensemble product and its logos are registered trademarks of <strong>InterSystems</strong> Corporation.<br />

The <strong>InterSystems</strong> name and logo are trademarks of <strong>InterSystems</strong> Corporation.<br />

This document contains trade secret and confidential information which is the property of <strong>InterSystems</strong> Corporation,<br />

One Memorial Drive, Cambridge, MA 02142, or its affiliates, and is furnished for the sole purpose of the operation<br />

and maintenance of the products of <strong>InterSystems</strong> Corporation. No part of this publication is to be used for any other<br />

purpose, and this publication is not to be reproduced, copied, disclosed, transmitted, stored in a retrieval system or<br />

translated into any human or computer language, in any form, by any means, in whole or in part, without the express<br />

prior written consent of <strong>InterSystems</strong> Corporation.<br />

The copying, use and disposition of this document and the software programs described herein is prohibited except<br />

to the limited extent set forth in the standard software license agreement(s) of <strong>InterSystems</strong> Corporation covering<br />

such programs and related documentation. <strong>InterSystems</strong> Corporation makes no representations and warranties<br />

concerning such software programs other than those set forth in such standard software license agreement(s). In<br />

addition, the liability of <strong>InterSystems</strong> Corporation for any losses or damages relating to or arising out of the use of<br />

such software programs is limited in the manner set forth in such standard software license agreement(s).<br />

THE FOREGOING IS A GENERAL SUMMARY OF THE RESTRICTIONS AND LIMITATIONS IMPOSED BY<br />

INTERSYSTEMS CORPORATION ON THE USE OF, AND LIABILITY ARISING FROM, ITS COMPUTER<br />

SOFTWARE. FOR COMPLETE INFORMATION REFERENCE SHOULD BE MADE TO THE STANDARD SOFTWARE<br />

LICENSE AGREEMENT(S) OF INTERSYSTEMS CORPORATION, COPIES OF WHICH WILL BE MADE AVAILABLE<br />

UPON REQUEST.<br />

<strong>InterSystems</strong> Corporation disclaims responsibility for errors which may appear in this document, and it reserves the<br />

right, in its sole discretion and without notice, to make substitutions and modifications in the products and practices<br />

described in this document.<br />

<strong>Caché</strong>, <strong>InterSystems</strong> <strong>Caché</strong>, <strong>Caché</strong> SQL, <strong>Caché</strong> ObjectScript, <strong>Caché</strong> Object, Ensemble, <strong>InterSystems</strong> Ensemble,<br />

Ensemble Object, and Ensemble Production are trademarks of <strong>InterSystems</strong> Corporation. All other brand or product<br />

names used herein are trademarks or registered trademarks of their respective companies or organizations.<br />

For Support questions about any <strong>InterSystems</strong> products, contact:<br />

<strong>InterSystems</strong> Worldwide Customer Support<br />

Tel: +1 617 621-0700<br />

Fax: +1 617 374-9391<br />

Email: support@<strong>InterSystems</strong>.com


Table of Contents<br />

1 Introduction to <strong>Caché</strong> <strong>Objects</strong> ....................................................................................... 1<br />

1.1 <strong>Caché</strong> <strong>Objects</strong> Architecture ...................................................................................... 2<br />

1.2 Class Definitions and the Class Dictionary ............................................................. 3<br />

1.2.1 Creating Class Definitions .............................................................................. 3<br />

1.2.2 The Class Dictionary ...................................................................................... 3<br />

1.3 The <strong>Caché</strong> Class Library .......................................................................................... 4<br />

1.4 Development Tools .................................................................................................. 5<br />

1.4.1 <strong>Caché</strong> Studio ................................................................................................... 5<br />

1.4.2 <strong>Caché</strong> RoseLink ............................................................................................. 6<br />

1.4.3 SQL-based Development ................................................................................ 6<br />

1.4.4 XML-based Development .............................................................................. 6<br />

1.5 Client Interoperability .............................................................................................. 6<br />

1.5.1 Java ................................................................................................................. 6<br />

1.5.2 ActiveX / COM / .net ...................................................................................... 7<br />

1.5.3 <strong>Caché</strong> Server Pages ........................................................................................ 7<br />

1.5.4 C++ ................................................................................................................. 7<br />

1.5.5 XML ............................................................................................................... 8<br />

1.5.6 The <strong>Caché</strong> SQL Gateway ............................................................................... 8<br />

1.5.7 The <strong>Caché</strong> Activate ActiveX Gateway ............................................................ 8<br />

2 Object-Oriented Database Development ....................................................................... 9<br />

2.1 Classes and <strong>Objects</strong> .................................................................................................. 9<br />

2.2 Abstraction and Modeling ...................................................................................... 10<br />

2.3 Inheritance and Polymorphism .............................................................................. 10<br />

2.4 Encapsulation ......................................................................................................... 11<br />

2.5 Extensibility ........................................................................................................... 12<br />

2.6 Object Persistence .................................................................................................. 12<br />

2.7 Object Binding ....................................................................................................... 12<br />

3 The <strong>Caché</strong> Object Model .............................................................................................. 13<br />

3.1 Referring to an Object — OREF, OID, and ID ...................................................... 14<br />

3.1.1 OID and ID Values ....................................................................................... 15<br />

3.1.2 OREFs and Reference Counting .................................................................. 15<br />

3.2 Types of Classes ..................................................................................................... 16<br />

3.2.1 Transient Object Classes .............................................................................. 17<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong><br />

iii


3.2.2 Persistent Object Classes .............................................................................. 17<br />

3.2.3 Serial Object Classes .................................................................................... 18<br />

3.2.4 Data Type Classes ......................................................................................... 20<br />

3.3 Inheritance .............................................................................................................. 20<br />

3.3.1 Multiple Inheritance ..................................................................................... 22<br />

3.4 Class Compilation .................................................................................................. 22<br />

4 <strong>Caché</strong> Classes ................................................................................................................. 25<br />

4.1 Naming Conventions .............................................................................................. 26<br />

4.1.1 General Identifier Rules ............................................................................... 26<br />

4.1.2 Class Names ................................................................................................. 27<br />

4.1.3 Class Member Names ................................................................................... 27<br />

4.2 Class Keywords ...................................................................................................... 27<br />

4.3 Class Parameters .................................................................................................... 28<br />

5 Packages ......................................................................................................................... 31<br />

5.1 Overview of Packages ............................................................................................ 31<br />

5.2 Package Names ...................................................................................................... 32<br />

5.3 Defining Packages .................................................................................................. 33<br />

5.4 <strong>Using</strong> Packages ...................................................................................................... 33<br />

5.4.1 The IMPORT Directive ................................................................................ 34<br />

5.5 Packages and SQL ................................................................................................. 35<br />

5.6 Built-in Packages ................................................................................................... 35<br />

6 Methods .......................................................................................................................... 37<br />

6.1 Method Arguments ................................................................................................. 37<br />

6.1.1 Specifying Default Values ............................................................................ 38<br />

6.1.2 Calling By Reference ................................................................................... 38<br />

6.2 Method Return Values ............................................................................................ 39<br />

6.3 Method Visibility ................................................................................................... 39<br />

6.4 Method Language .................................................................................................. 40<br />

6.5 Method Keywords .................................................................................................. 40<br />

6.6 Instance and Class Methods ................................................................................... 42<br />

6.7 Kinds of Methods ................................................................................................... 42<br />

6.7.1 Code Methods ............................................................................................... 43<br />

6.7.2 Expression Methods ..................................................................................... 43<br />

6.7.3 Call Methods ................................................................................................ 44<br />

6.7.4 Method Generators ....................................................................................... 44<br />

iv<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


7 Properties ....................................................................................................................... 45<br />

7.1 Property Keywords ................................................................................................. 46<br />

7.2 Property Visibility .................................................................................................. 47<br />

7.3 Property Behavior .................................................................................................. 47<br />

7.4 Property Accessors ................................................................................................. 49<br />

7.5 Attribute Properties ................................................................................................ 50<br />

7.5.1 Data Type Properties .................................................................................... 50<br />

7.5.2 Object-Valued Properties .............................................................................. 51<br />

7.5.3 Collection Properties .................................................................................... 51<br />

7.5.4 Stream Properties ......................................................................................... 52<br />

7.5.5 Multidimensional Properties ........................................................................ 52<br />

8 Class Queries .................................................................................................................. 55<br />

8.1 Query Basics .......................................................................................................... 56<br />

8.1.1 The Structure of a Class Query .................................................................... 56<br />

8.1.2 Query Keywords ........................................................................................... 56<br />

8.1.3 Creating a Class Query Specification ........................................................... 57<br />

8.2 User-Written Class Queries ................................................................................... 58<br />

8.2.1 The querynameExecute Method ................................................................... 59<br />

8.2.2 The querynameFetch Method ....................................................................... 60<br />

8.2.3 The querynameClose Method ....................................................................... 62<br />

9 Indices ............................................................................................................................. 63<br />

9.1 Index Keywords ..................................................................................................... 63<br />

9.2 Index Collation ....................................................................................................... 65<br />

10 <strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript .................................................................... 67<br />

10.1 Executing Methods .............................................................................................. 67<br />

10.1.1 About Return Values ................................................................................... 68<br />

10.1.2 Executing Instance Methods ....................................................................... 68<br />

10.1.3 Executing Class Methods ........................................................................... 69<br />

10.1.4 Executing Methods <strong>Using</strong> In-Memory Instances ....................................... 69<br />

10.1.5 Error Conditions ......................................................................................... 70<br />

10.2 Creating New <strong>Objects</strong> .......................................................................................... 70<br />

10.3 Opening <strong>Objects</strong> ................................................................................................... 71<br />

10.4 Modifying <strong>Objects</strong> ............................................................................................... 71<br />

10.4.1 Modifying Reference Properties ................................................................ 72<br />

10.4.2 Modifying Embedded Object Properties .................................................... 73<br />

10.4.3 Modifying List Properties .......................................................................... 75<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong><br />

v


10.4.4 Modfiying Array Properties ........................................................................ 78<br />

10.4.5 Modifying Stream Properties ..................................................................... 80<br />

10.5 Saving <strong>Objects</strong> ..................................................................................................... 81<br />

10.6 Deleting <strong>Objects</strong> ................................................................................................... 81<br />

10.6.1 Deleting a Single Object ............................................................................. 82<br />

10.6.2 Deleting All <strong>Objects</strong> in an Extent ............................................................... 82<br />

10.7 Executing Queries ................................................................................................ 83<br />

10.7.1 Query Metadata Methods ........................................................................... 83<br />

10.7.2 Preparing Queries for Execution ................................................................ 84<br />

10.7.3 Executing Queries ...................................................................................... 85<br />

10.7.4 Processing Query Results ........................................................................... 85<br />

10.7.5 Closing Queries .......................................................................................... 86<br />

10.7.6 Example of <strong>Using</strong> a Class Query ............................................................... 86<br />

11 Data Types .................................................................................................................... 89<br />

11.1 Available Types .................................................................................................... 90<br />

11.2 Operation .............................................................................................................. 91<br />

11.2.1 <strong>Using</strong> Data Types in Classes ...................................................................... 91<br />

11.2.2 Parameters .................................................................................................. 91<br />

11.2.3 Keywords .................................................................................................... 93<br />

11.2.4 Data Formats and Translation Methods ...................................................... 96<br />

11.3 Enumerated Properties ......................................................................................... 97<br />

12 Object Persistence ........................................................................................................ 99<br />

12.1 The %Persistent Class .......................................................................................... 99<br />

12.2 The Persistence Interface ................................................................................... 100<br />

12.2.1 Saving <strong>Objects</strong> .......................................................................................... 100<br />

12.2.2 Opening <strong>Objects</strong> ....................................................................................... 103<br />

12.2.3 Deleting <strong>Objects</strong> ....................................................................................... 106<br />

12.2.4 Testing if <strong>Objects</strong> Exist ............................................................................ 107<br />

12.3 Object Extents .................................................................................................... 107<br />

12.3.1 The Extent Query ..................................................................................... 108<br />

12.4 Storage Definitions and Storage Classes ........................................................... 109<br />

12.4.1 The %CacheStorage Storage Class .......................................................... 110<br />

12.4.2 The %CacheSQLStorage Storage Class ................................................... 110<br />

12.5 Schema Evolution .............................................................................................. 110<br />

12.5.1 Resetting the Storage Definition .............................................................. 111<br />

13 <strong>Objects</strong> and SQL ........................................................................................................ 113<br />

vi<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


13.1 Inheritance and SQL .......................................................................................... 113<br />

13.1.1 How a Class is Projected to SQL ............................................................. 114<br />

13.1.2 Naming Rules for Projected Classes ........................................................ 114<br />

13.2 The Object-SQL Projection ............................................................................... 115<br />

13.2.1 Identity (OIDs Projected to SQL) ............................................................ 115<br />

13.2.2 Properties .................................................................................................. 116<br />

13.2.3 Methods .................................................................................................... 122<br />

13.2.4 SQL Triggers ............................................................................................ 122<br />

13.2.5 Relationships ............................................................................................ 123<br />

14 Relationships .............................................................................................................. 125<br />

14.1 Relationship Basics ............................................................................................ 126<br />

14.1.1 Relationship Keywords ............................................................................. 126<br />

14.1.2 Defining a Relationship ............................................................................ 127<br />

14.2 Dependent Relationships ................................................................................... 128<br />

14.3 In-Memory Behavior of Relationships .............................................................. 129<br />

14.4 Persistent Behavior of Relationships ................................................................. 131<br />

14.4.1 Referential Integrity .................................................................................. 131<br />

14.4.2 Persistent Behavior of Dependent Relationships ..................................... 131<br />

15 Streams ....................................................................................................................... 133<br />

15.1 Declaring Stream Properties .............................................................................. 133<br />

15.1.1 Projecting Streams to ODBC ................................................................... 134<br />

15.2 <strong>Using</strong> the Stream Interface ................................................................................. 134<br />

15.2.1 Reading and Writing Stream Data ............................................................ 135<br />

15.2.2 Copying Data between Streams ................................................................ 136<br />

15.2.3 Inserting Stream Data ............................................................................... 136<br />

15.2.4 <strong>Using</strong> Streams in Object Applications ...................................................... 137<br />

15.3 <strong>Using</strong> Streams with SQL ................................................................................... 137<br />

15.4 Streams and Visual Basic ................................................................................... 139<br />

16 Class Projections ........................................................................................................ 141<br />

16.1 Projection Definitions ........................................................................................ 141<br />

16.1.1 Adding a Projection to a Class ................................................................. 141<br />

16.2 Projection Classes .............................................................................................. 142<br />

16.2.1 The Projection Interface ........................................................................... 142<br />

16.2.2 The Standard Projection Classes .............................................................. 143<br />

16.2.3 Creating a New Projection Class .............................................................. 144<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong><br />

vii


17 Object Synchronization ............................................................................................. 145<br />

17.1 About Updates .................................................................................................... 145<br />

17.1.1 The GUID ................................................................................................. 146<br />

17.1.2 How Updates Work ................................................................................... 146<br />

17.1.3 The SyncSet and SyncTime <strong>Objects</strong> ........................................................ 147<br />

17.2 Running an Update ............................................................................................ 150<br />

17.2.1 Preparing for the Update .......................................................................... 150<br />

17.2.2 The Update Itself ...................................................................................... 151<br />

17.3 Other Topics ....................................................................................................... 153<br />

17.3.1 Translating Between GUIDs and OIDs .................................................... 154<br />

17.3.2 Manually Updating a SyncTime Table ..................................................... 154<br />

18 Method Generators ................................................................................................... 155<br />

18.1 Defining a Method Generator ............................................................................ 155<br />

18.2 How Method Generators Work .......................................................................... 156<br />

18.3 Method Generator Context ................................................................................. 157<br />

18.4 Implementing Method Generators ..................................................................... 158<br />

18.4.1 Method Generators for Other Languages ................................................. 159<br />

18.4.2 Specifying CodeMode within a Method Generator ................................. 159<br />

19 The <strong>Caché</strong> Data Population Utility .......................................................................... 161<br />

19.1 Data Population Basics ...................................................................................... 161<br />

19.1.1 Changes to the Class Definition ............................................................... 162<br />

19.1.2 Populating <strong>Objects</strong> ................................................................................... 162<br />

19.2 The POPSPEC Parameter .................................................................................. 163<br />

19.2.1 Populating Embedded <strong>Objects</strong> ................................................................. 165<br />

19.2.2 Populating Lists ........................................................................................ 165<br />

19.2.3 Populating Arrays ..................................................................................... 166<br />

19.2.4 Custom Populate Actions ......................................................................... 166<br />

19.3 Details ................................................................................................................ 167<br />

20 <strong>Using</strong> Callback Methods ........................................................................................... 169<br />

20.1 %OnAddToSaveSet ............................................................................................ 170<br />

20.2 %OnAfterSave ................................................................................................... 171<br />

20.3 %OnBeforeSave ................................................................................................. 172<br />

20.4 %OnClose .......................................................................................................... 172<br />

20.5 %OnConstructClone .......................................................................................... 173<br />

20.6 %OnDelete ......................................................................................................... 174<br />

20.7 %OnDetermineClass .......................................................................................... 174<br />

viii<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


20.8 %OnNew ............................................................................................................ 175<br />

20.9 %OnOpen ........................................................................................................... 175<br />

20.10 %OnRollBack .................................................................................................. 176<br />

20.11 %OnValidateObject .......................................................................................... 176<br />

20.12 OnPopulate ....................................................................................................... 177<br />

21 Object-Specific ObjectScript Features .................................................................... 179<br />

21.1 .. Syntax ............................................................................................................. 179<br />

21.2 ##Class Syntax ................................................................................................... 180<br />

21.2.1 Invoking a Class Method .......................................................................... 180<br />

21.2.2 Casting a Method ...................................................................................... 180<br />

21.3 ##this Syntax ...................................................................................................... 181<br />

21.4 ##super Syntax ................................................................................................... 183<br />

21.5 i% Syntax ............................................................................... 184<br />

22 Dynamic Dispatch ...................................................................................................... 187<br />

22.1 How Dynamic Dispatch Happens ...................................................................... 187<br />

22.2 Content of Methods Implementing Dynamic Dispatch ..................................... 188<br />

22.2.1 Return Values ............................................................................................ 189<br />

22.3 The Dynamic Dispatch Methods ........................................................................ 189<br />

22.3.1 %DispatchMethod .................................................................................... 189<br />

22.3.2 %DispatchClassMethod ........................................................................... 190<br />

22.3.3 %DispatchGetProperty ............................................................................. 190<br />

22.3.4 %DispatchSetProperty .............................................................................. 190<br />

22.3.5 %DispatchSetMultidimProperty .............................................................. 191<br />

22.3.6 %DispatchGetModified ............................................................................ 191<br />

22.3.7 %DispatchSetModified ............................................................................ 191<br />

23 Class Definition Classes ............................................................................................ 193<br />

23.1 Browsing Class Definitions ............................................................................... 194<br />

23.2 Altering Class Definitions .................................................................................. 195<br />

24 Internet Classes .......................................................................................................... 197<br />

24.1 Email .................................................................................................................. 197<br />

24.1.1 Mail Messages .......................................................................................... 197<br />

24.1.2 Sending Email .......................................................................................... 198<br />

24.1.3 Receiving Email ....................................................................................... 198<br />

24.2 FTP ..................................................................................................................... 199<br />

24.3 HTTP .................................................................................................................. 199<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong><br />

ix


24.4 URL Parsing ....................................................................................................... 199<br />

Appendix A: Object Concurrency ................................................................................. 201<br />

A.1 Object Concurrency Options ............................................................................... 201<br />

A.2 Version Checking ................................................................................................ 203<br />

Index ................................................................................................................................ 205<br />

x<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


List of Figures<br />

Embeddable <strong>Objects</strong> ........................................................................................................... 19<br />

Class Packages .................................................................................................................... 32<br />

Property Behavior ............................................................................................................... 48<br />

Two Unsynchronized Databases ....................................................................................... 147<br />

Two Databases, Where One Has Been Synchronized to the Other .................................. 148<br />

Two Synchronized Databases ........................................................................................... 149<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong><br />

xi


List of Tables<br />

<strong>Caché</strong> Class Library Packages .............................................................................................. 5<br />

Class Identifier Lengths ...................................................................................................... 26<br />

Collection <strong>Objects</strong> .............................................................................................................. 51<br />

<strong>Caché</strong> Data Type Classes .................................................................................................... 90<br />

Supported Parameters for System Data Type Classes ........................................................ 92<br />

CLIENTDATATYPE Values ............................................................................................... 94<br />

ODBCTYPE Values ............................................................................................................ 95<br />

SQLCATEGORY Values .................................................................................................... 96<br />

The Object-SQL Projection .............................................................................................. 115<br />

Sample Projection of an Array Property ........................................................................... 119<br />

Sample Projection of a List Property ................................................................................ 120<br />

Projection Classes ............................................................................................................. 144<br />

<strong>Objects</strong> Available to Method Generators .......................................................................... 157<br />

xii<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


1<br />

Introduction to <strong>Caché</strong> <strong>Objects</strong><br />

This document provides an in-depth look at the details of <strong>Caché</strong> <strong>Objects</strong>.<br />

If you are new to <strong>Caché</strong> <strong>Objects</strong>, you may find the <strong>Caché</strong> Web Applications Tutorial tutorial<br />

to be a useful introduction.<br />

<strong>Caché</strong> <strong>Objects</strong> is a set of technologies that give application developers the means to easily<br />

create high performance, object-based, database applications.<br />

The features of <strong>Caché</strong> <strong>Objects</strong> include:<br />

• A powerful object model that includes inheritance, properties, methods, collections,<br />

relationships, user-defined data types, and streams.<br />

• A flexible object persistence mechanism that allows objects to be stored within the native<br />

<strong>Caché</strong> database as well as external relational databases.<br />

• Control over the database aspects of persistent classes including indices, constraints, and<br />

referential integrity.<br />

• An easy-to-use transaction and concurrency model that includes the ability to load objects<br />

by navigation—simply referring to an object can “swizzle” it into memory from the<br />

database.<br />

• Automatic integration with <strong>Caché</strong> SQL via the <strong>Caché</strong> Unified Data Architecture.<br />

• Interoperability with Java, C++, and ActiveX.<br />

• Automatically-provided XML support.<br />

• A powerful, multi-user object development environment: <strong>Caché</strong> Studio.<br />

You can use <strong>Caché</strong> <strong>Objects</strong> in a myriad of ways including:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 1


Introduction to <strong>Caché</strong> <strong>Objects</strong><br />

• To define the database and/or business components of a transaction processing application.<br />

• To create a Web-based user interface using <strong>Caché</strong> Server Pages.<br />

• To define object-based stored procedures that are callable from ODBC or JDBC.<br />

• To provide object/relational access to legacy applications.<br />

1.1 <strong>Caché</strong> <strong>Objects</strong> Architecture<br />

<strong>Caché</strong> <strong>Objects</strong> contains the following major components:<br />

• The Class Dictionary—a repository of class definitions (often known as meta-data), each<br />

of which describes a specific class. This repository is stored within a <strong>Caché</strong> database.<br />

The Class Dictionary is also used by the <strong>Caché</strong> SQL Engine and is responsible for<br />

maintaining synchronized object and relational access to <strong>Caché</strong> data.<br />

• The Class Compiler—a set of programs that convert class definitions into executable<br />

code.<br />

• The Object Runtime System—a set of features built into the <strong>Caché</strong> virtual machine that<br />

support object operations (such as object instantiation, method invocation, and polymorphism)<br />

within a running program.<br />

• The <strong>Caché</strong> Class Library—a set of pre-built classes that come with every <strong>Caché</strong> installation.<br />

This includes classes that are used to provide behaviors for user-defined classes<br />

(such as persistence or data types) as well as classes that are intended for direct use within<br />

applications (such as e-mail classes).<br />

• The various Language Bindings—a combination of code generators and runtime components<br />

that provide external access to <strong>Caché</strong> objects. These bindings include the <strong>Caché</strong><br />

Java binding, the <strong>Caché</strong> ActiveX binding, and the <strong>Caché</strong> C++ binding.<br />

• The various Gateways—server-side components that give <strong>Caché</strong> objects access to<br />

external systems. These gateways include the <strong>Caché</strong> SQL Gateway and the <strong>Caché</strong> Activate<br />

ActiveX Gateway.<br />

2 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


1.2 Class Definitions and the Class Dictionary<br />

Every class has a definition that specifies what members (properties, methods, etc.) it contains<br />

as well as class-wide characteristics (such as superclasses). These definitions are contained<br />

within the <strong>Caché</strong> Dictionary, which is itself stored within the <strong>Caché</strong> database.<br />

1.2.1 Creating Class Definitions<br />

You can create class definitions in many ways:<br />

• <strong>Using</strong> <strong>Caché</strong> Studio. The primary means of working with <strong>Caché</strong> class definitions is with<br />

the <strong>Caché</strong> Studio Development Environment.<br />

• <strong>Using</strong> XML. Class definitions have an external, XML-based representation. Typically<br />

this format is used for storing class definitions externally (such as in source control systems),<br />

deploying applications, or simply for sharing code. You can also create new class<br />

definitions programatically by simply generating the appropriate XML class definition<br />

file and loading it into a <strong>Caché</strong> system.<br />

• <strong>Using</strong> an API. <strong>Caché</strong> includes a set of class definition classes that provide object access<br />

to the Class Dictionary. You can use these to observe, modify, and create class definitions.<br />

• <strong>Using</strong> SQL DDL. Any relational tables defined by DDL statements are automatically<br />

converted to equivalent class definitions and placed within the Class Dictionary.<br />

In addition, <strong>Caché</strong> includes a set of tools (such as the <strong>Caché</strong> RoseLink connection to the<br />

Rational Rose modeling tool) as well as Wizards (such as the <strong>Caché</strong> Activate Wizard) that<br />

automatically create class definitions.<br />

1.2.2 The Class Dictionary<br />

Class Definitions and the Class Dictionary<br />

Every <strong>Caché</strong> namespace contains its own Class Dictionary which defines the available classes<br />

for that namespace. There is a special “CACHELIB” namespace, installed as part of <strong>Caché</strong>,<br />

that contains the definitions and executable code for the classes of the <strong>Caché</strong> Class Library.<br />

These classes are referred to as system classes and all have names that start with a “%”<br />

character. (Strictly speaking, they have package names that start with “%” ).<br />

Every <strong>Caché</strong> namespace is automatically configured so that its Class Dictionary, in addition<br />

to containing its own classes, has access to the system class definitions and code within the<br />

CACHELIB namespace. By this mechanism, all namespaces can make direct use of the<br />

classes in the <strong>Caché</strong> Class Library.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 3


Introduction to <strong>Caché</strong> <strong>Objects</strong><br />

The Class Dictionary contains two distinct types of data:<br />

• Definition Data—the actual class definitions that users create.<br />

• Compilation Data—data generated as a result of compiling class definitions is also stored.<br />

This data includes the results of inheritance resolution; that is, it lists all the defined and<br />

inherited members for a given class. The Class Compiler uses this to make other compilation<br />

more efficient; applications can also use it (via the appropriate interface) to get<br />

runtime information about class members.<br />

The Class Dictionary stores its data in a set of globals (persistent arrays) whose names start<br />

with ^odd. The structure of these arrays may change with new versions of <strong>Caché</strong>, so applications<br />

should never directly observe or modify these structures.<br />

1.3 The <strong>Caché</strong> Class Library<br />

The <strong>Caché</strong> Class Library contains a set of pre-built classes. It is automatically installed with<br />

every <strong>Caché</strong> system within the CACHELIB database. You can view the contents of the <strong>Caché</strong><br />

Class Library using the online class documentation system provided with <strong>Caché</strong>.<br />

The <strong>Caché</strong> Class Library contains a number of packages, each of which contains a family of<br />

classes. Some of these are internal and <strong>Caché</strong> <strong>Objects</strong> uses them as part of its implementation.<br />

Other classes provide features which are designed for use in applications.<br />

The main packages within the <strong>Caché</strong> Class Library include:<br />

4 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Development Tools<br />

<strong>Caché</strong> Class Library Packages<br />

Package<br />

%Activate<br />

%Compiler<br />

%CSP<br />

%csr<br />

%Library<br />

%Net<br />

%Projection<br />

%SQL<br />

%Studio<br />

%SYSTEM<br />

%XML<br />

Description<br />

Classes used by the <strong>Caché</strong> Activate ActiveX gateway.<br />

Internal classes used by the <strong>Caché</strong> Class Compiler.<br />

Classes used by <strong>Caché</strong> Server Pages.<br />

A set of generated internal classes that implement the standard CSP<br />

rules.<br />

The core set of <strong>Caché</strong> “behavioral” classes (such as %Persistent). Also<br />

includes the various data types, collections, and stream classes.<br />

A set of classes providing various internet-related features (such as<br />

e-mail, HTTP requests, etc.)<br />

A set of projection classes that generate client-side code for user<br />

classes.<br />

Internal classes used by <strong>Caché</strong> SQL.<br />

Internal classes used by <strong>Caché</strong> Studio.<br />

The various System API classes accessible via the $System special<br />

variable.<br />

Classes used to provide XML and SAX support within <strong>Caché</strong>.<br />

1.4 Development Tools<br />

<strong>Caché</strong> includes a number of tools for developing object-based applications. In addition, it is<br />

quite easy to use <strong>Caché</strong> with other development environments.<br />

1.4.1 <strong>Caché</strong> Studio<br />

<strong>Caché</strong> Studio is an integrated, visual development environment for creating <strong>Caché</strong> class<br />

definitions. See the <strong>Caché</strong> Studio User's Guide for more details.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 5


Introduction to <strong>Caché</strong> <strong>Objects</strong><br />

1.4.2 <strong>Caché</strong> RoseLink<br />

<strong>Caché</strong> RoseLink is an add-in for the popular Rational Rose modeling tool that creates <strong>Caché</strong><br />

class definitions from and displays existing classes as UML diagrams. This allows developers<br />

to take advantage of the object-oriented power of UML without the headache of mapping to<br />

relational tables.<br />

1.4.3 SQL-based Development<br />

It is possible to develop <strong>Caché</strong> applications using SQL-based tools, since <strong>Caché</strong> automatically<br />

creates a class definition from any relational tables defined using SQL DDL statements. This<br />

approach does not exploit the full power of objects because you are limited by what DDL<br />

can express; however, it is useful for migrating legacy applications.<br />

1.4.4 XML-based Development<br />

It is possible to develop class definitions as XML documents and load them into <strong>Caché</strong>. You<br />

can do this using either <strong>Caché</strong>'s specific XML format for class definitions or you can use an<br />

XML schema representation.<br />

1.5 Client Interoperability<br />

<strong>Caché</strong> <strong>Objects</strong> can communicate with a variety of environments:<br />

1.5.1 Java<br />

<strong>Caché</strong> gives you the ability to use <strong>Caché</strong> objects within a Java environment as native Java<br />

classes. This gives Java-based applications direct, high-performance access to object and<br />

relational data stored within <strong>Caché</strong>. It also provides <strong>Caché</strong> applications with the ability to<br />

easily interoperate with Java.<br />

<strong>Caché</strong> supports the following connectivity with Java:<br />

• The <strong>Caché</strong> Java Binding—automatically exposes <strong>Caché</strong> classes as Java classes, complete<br />

with automatically generated accessor methods and sophisticated data caching. The full<br />

spectrum of object features are supported including custom data types, streams, collections,<br />

queries, and relationships.<br />

6 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


• The <strong>Caché</strong> EJB Binding—a version of the Java Binding that automatically creates<br />

Enterprise Java Bean components from <strong>Caché</strong> class definitions.<br />

• The <strong>Caché</strong> JDBC Driver—a high performance, native JDBC driver that provides relational<br />

access to data stored within a <strong>Caché</strong> database.<br />

For more information, see <strong>Using</strong> Java with <strong>Caché</strong>.<br />

1.5.2 ActiveX / COM / .net<br />

<strong>Caché</strong> gives you the ability to use <strong>Caché</strong> objects within a COM-enabled environment such<br />

as Windows or the .net framework. <strong>Caché</strong> objects are automatically projected as standard<br />

COM components. This gives COM-based applications (such as Visual Basic) direct, highperformance<br />

access to object and relational data stored within <strong>Caché</strong>. It also provides <strong>Caché</strong><br />

applications with the ability to easily interoperate with COM.<br />

<strong>Caché</strong> supports the following connectivity with COM:<br />

For more information, see <strong>Using</strong> ActiveX with <strong>Caché</strong>.<br />

Client Interoperability<br />

• The <strong>Caché</strong> ActiveX Binding—automatically exposes <strong>Caché</strong> classes as COM components,<br />

complete with sophisticated data caching. The full spectrum of object features are supported<br />

including custom data types, streams, collections, queries, and relationships.<br />

• The <strong>Caché</strong> ODBC Driver—a high performance, ODBC driver that provides relational<br />

access to data stored within a <strong>Caché</strong> database. This can be used with third-party control<br />

that only support relational access.<br />

1.5.3 <strong>Caché</strong> Server Pages<br />

<strong>Using</strong> <strong>Caché</strong> Server Pages (CSP) you can easily create high-performance, object-based, web<br />

applications.<br />

For more information, see the <strong>Using</strong> <strong>Caché</strong> Server Pages (CSP).<br />

1.5.4 C++<br />

<strong>Caché</strong> gives you the ability to use <strong>Caché</strong> objects within C++-based applications. <strong>Caché</strong> objects<br />

are projected as standard C++ components. The resulting C++ classes are portable across a<br />

variety of operating systems and make use of the C++ standard library. This gives C++-based<br />

applications direct, high-performance access to object and relational data stored within <strong>Caché</strong>.<br />

It also provides <strong>Caché</strong> applications with the ability to easily interoperate with C++.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 7


Introduction to <strong>Caché</strong> <strong>Objects</strong><br />

<strong>Caché</strong> supports the following connectivity with C++:<br />

• The <strong>Caché</strong> C++ Binding—automatically exposes <strong>Caché</strong> classes as COM components<br />

classes complete with sophisticated data caching. The full spectrum of object features<br />

are supported including custom data types, streams, collections, queries, and relationships.<br />

• The <strong>Caché</strong> ODBC Driver—a high performance, ODBC driver that provides relational<br />

access to data stored within a <strong>Caché</strong> database. This can be used within C++ applications<br />

on a variety of platforms.<br />

For more information, see <strong>Using</strong> C++ with <strong>Caché</strong>.<br />

1.5.5 XML<br />

<strong>Caché</strong> supports a rich variety of interconnectivity with XML including:<br />

• The ability to bi-directionally project objects as XML documents.<br />

• The ability to create class definitions from standard XML schemata and vice versa.<br />

• The ability to easily create object-oriented, portable Web Services using SOAP.<br />

• The ability to store class definitions as XML documents.<br />

For more information, see <strong>Using</strong> XML with <strong>Caché</strong>.<br />

1.5.6 The <strong>Caché</strong> SQL Gateway<br />

The <strong>Caché</strong> SQL Gateway gives <strong>Caché</strong> applications ODBC-based access to data stored in<br />

legacy relational databases. Specifically, the SQL Gateway works in conjunction with the<br />

<strong>Caché</strong> object persistence mechanism to provide seamless object and SQL access to externally<br />

stored relational data.<br />

For more information see <strong>Using</strong> the <strong>Caché</strong> SQL Gateway.<br />

1.5.7 The <strong>Caché</strong> Activate ActiveX Gateway<br />

<strong>Caché</strong> Activate is an ActiveX gateway that lets <strong>Caché</strong> applications use ActiveX / COM<br />

components within the <strong>Caché</strong> environment as if they were native components.<br />

For more information see <strong>Using</strong> <strong>Caché</strong> Activate.<br />

8 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


2<br />

Object-Oriented Database<br />

Development<br />

The use of object-oriented programming is widespread and growing. Most modern user<br />

interface code is object based and a large amount of new business logic is also being implemented<br />

as objects. Where there is a lag is within the database layer of applications. Most<br />

databases rely on older methodologies (such as the ISAM and relational models). <strong>Caché</strong><br />

brings the power of object-oriented programming to the database.<br />

In this chapter we will briefly discuss some of the key concepts of object-oriented programming<br />

and how they relate to database application development.<br />

2.1 Classes and <strong>Objects</strong><br />

A class is a template that describes the data and behavior of a specific entity within an<br />

application. For example, your application may define a Car class that specifies the characteristics<br />

shared by all cars within the application.<br />

An object is a specific instance of a class. For example, a “1972 Dodge Dart” is a specific<br />

instance of a Car.<br />

<strong>Objects</strong> may exist in the memory of a specific process or session (similar to variables) where<br />

they can be manipulated (data values changed, methods invoked). <strong>Objects</strong> can also be stored<br />

in and retrieved from a database.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 9


Object-Oriented Database Development<br />

Classes provide structure and organization to applications. In particular, classes make it<br />

possible to divide the implementation of large applications among developers or teams of<br />

developers with reasonable assurance that their work will function when later integrated.<br />

An important feature of <strong>Caché</strong> is that new classes can be defined at runtime; that is, an<br />

application can extend itself by creating and using new classes as it runs.<br />

2.2 Abstraction and Modeling<br />

Data abstraction is the process of taking the essential details of real-world entities (such as<br />

employees, invoices, documents) and defining from them representations that can be used<br />

within a software application.<br />

Object modeling is the process of taking a data abstraction and representing it as a set of<br />

classes, where each class defines the properties and behaviors of a specific entity. A set of<br />

behaviors defined for a class is typically referred to as an “interface” .<br />

Abstraction and modeling are at the heart of object-oriented programming. <strong>Using</strong> an object<br />

model, you can define application components that are analogs of the real-world entities your<br />

application works with. The beauty of an object model is that it is rich enough to model<br />

complex entities in a natural way: a book with a set of related chapters, for example. Contrast<br />

this with traditional relational database design where the data representation of real-world<br />

entities must be constrained to fit into 2–dimensional tables and there is no mechanism for<br />

associating behavior with data.<br />

2.3 Inheritance and Polymorphism<br />

Inheritance is the process of taking an existing class and specializing it (changing aspects of<br />

its behavior) for new circumstances. Specifically this is done by specifying one or more super<br />

classes when creating a new class. The new class will inherit all of the data and behavior of<br />

its superclass. In addition the new class can add additional data and behavior or selectively<br />

replace (override) the behaviors inherited from its super class.<br />

Polymorphism is the ability to invoke the same method on objects of different types (but with<br />

a common superclass). Each object executes its own specific implementation of the method.<br />

This allows you to use new object types within existing applications without having to modify<br />

the applications.<br />

10 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Encapsulation<br />

<strong>Caché</strong> exploits inheritance in several ways, including:<br />

• Extents—persistent classes derived a common superclass are stored within a common<br />

extent in the database. For example, you can store instances of Person as well as its subclass,<br />

Student, in the same extent. When an application asks for a list of all persons, it<br />

will get the persons as well as the students (since a student is a person). If the application<br />

asks for students, then only student (with any additional properties they define) are<br />

returned. This automatic behavior is difficult to achieve using the relational model.<br />

• Method Generators—<strong>Caché</strong> includes the concept of method generators: methods that<br />

are, in fact, programs that create method implementations. <strong>Using</strong> method generators it<br />

is possible to create classes that automatically generate application-specific code for different<br />

circumstances.<br />

2.4 Encapsulation<br />

Encapsulation refers to the process of keeping the external representation of an<br />

entity—properties and methods—independent from the entity's actual implementation. The<br />

idea is that users of an entity (such as other parts of an application) can make use of it without<br />

knowledge of its internal workings and are immune from changes in its implementation.<br />

Object-oriented programming simplifies the process of encapsulation through the use of<br />

classes which, by definition, encapsulate behavior.<br />

An object-oriented database extends this concept to data stored within a database in two<br />

important ways:<br />

1. Properties encapsulate data: it is possible to change the database while maintaining the<br />

same property interface. For example, a property may be redefined to be no longer stored<br />

but to be the result of a calculation performed on other properties. Users of such a property<br />

would see no difference.<br />

2. Methods encapsulate behavior: when a persistent object is loaded from the database it is<br />

automatically associated with the methods defined for its class. You can change the<br />

implementation of such methods independently of both the data in the database and any<br />

users of these methods.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 11


Object-Oriented Database Development<br />

2.5 Extensibility<br />

One of the key features of object-oriented systems is that they are extensible: it is possible<br />

to tailor their behavior for specific applications.<br />

Typically such extensibility is achieved in the following ways:<br />

• Type definition: You can define new data types that represent application-specific data.<br />

• Event handling: An application can define a set of methods that are called when certain<br />

events occur. By providing such methods, the behavior of an application can be modified<br />

without having to change its core implementation.<br />

• Subclassing: An application can adapt previously developed components for new uses<br />

by creating subclasses of existing classes.<br />

2.6 Object Persistence<br />

Object persistence refers to the ability to store object instances within a database. <strong>Caché</strong> is<br />

unique in that such object can be fully queried using native (i.e., direct against the data) SQL<br />

queries.<br />

<strong>Caché</strong> can store objects natively; that is, objects are not first converted into a relational representation.<br />

Instead each persistent object has methods (generated automatically) that directly<br />

store and load objects into the <strong>Caché</strong> data engine. The persistence model automatically<br />

manages concurrency, transactions, and validation. In addition, <strong>Caché</strong> supports the idea of<br />

Schema Evolution: if you change your data model, it is still possible to use previously stored<br />

objects.<br />

2.7 Object Binding<br />

An object binding is a mechanism by which a <strong>Caché</strong> object can project itself as a native object<br />

in a different environment. For example, <strong>Caché</strong> supports a Java binding by which any <strong>Caché</strong><br />

object can be used within a Java environment as if it were a native Java class. Bindings exist<br />

for languages such as Java, C++, Perl, Python, XML, ActiveX, and the .net Managed Provider.<br />

12 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


3<br />

The <strong>Caché</strong> Object Model<br />

The <strong>Caché</strong> object model defines the behavior and features of both persistent (database) objects<br />

as well as transient objects (those not stored).<br />

The <strong>Caché</strong> object model supports the following features:<br />

• Persistence—<strong>Objects</strong> can be stored within the <strong>Caché</strong> database or in an external database.<br />

Stored objects are simultaneously projected as relational tables and can be queried using<br />

standard SQL.<br />

The object model is rich enough to define the complete information required for complex<br />

database applications (such as indices, storage structure, integrity constraints, etc.).<br />

• Properties—<strong>Objects</strong> can contain both simple (literal) or object-valued properties. Objectvalued<br />

properties can be references to external objects or embedded objects. There are<br />

also a variety of relationship, collection, and stream properties.<br />

• Custom Data Types—<strong>Objects</strong> can use application-specific data types which are themselves<br />

defined as classes.<br />

• Methods—<strong>Objects</strong> have behavior associated with them.<br />

• Polymorphism—Applications can easily adapt to new circumstances by providing specialized<br />

implementations of operations. The runtime system automatically invokes the<br />

correct implementation based on object type.<br />

• Inheritance—Applications can reuse code by deriving new components from pre-existing<br />

classes.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 13


The <strong>Caché</strong> Object Model<br />

Each class describes an entity, such as a person, a record, or an account. A class defines the<br />

data (properties), behavior (methods), and additional characteristics (such as how a persistent<br />

object is stored) for such an entity.<br />

An object is a specific instance of a class. An object can exist on disk, in memory, or in a<br />

client application.<br />

3.1 Referring to an Object — OREF, OID, and ID<br />

Within <strong>Caché</strong>, objects can either be on disk or in memory. An on-disk object is one that you<br />

have stored in the database; an in-memory object is one that you have loaded from the database<br />

and can manipulate. You can refer to an object in different ways, depending on whether it is<br />

loaded or stored:<br />

OREF<br />

OID<br />

An object reference. A value that refers to a specific, in-memory instance of an object.<br />

An OREF refers to an in-memory version of an object. Each time an object is brought<br />

into memory, it may have a different OREF value.<br />

An object identifier. A value that uniquely identifies a specific instance of an object<br />

stored in a database. An OID refers to an on-disk version of an object. An OID<br />

uniquely identifies a persistent object that is stored in the database. Once an object<br />

has received an OID value, that value does not change.<br />

ID<br />

An object identifier. A value that uniquely identifies a specific instance within a<br />

particular extent. An ID refers to an on-disk version of an object. It does not include<br />

any class-specific information.<br />

You can use a persistent object's OID to locate the object and bring it into memory; similarly,<br />

if you know a persistent object's extent, you can use its ID to locate it and bring it into<br />

memory. Once in memory, the system assigns it an OREF value that the application can use<br />

to refer to the object and access its contents. When a persistent object is stored in the database,<br />

the values of any of its reference attributes (that is, references to other persistent objects) are<br />

stored as OID values. For object attributes that do not have OIDs, the object's literal value is<br />

stored along with the rest of the persistent object's state.<br />

14 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Referring to an Object — OREF, OID, and ID<br />

3.1.1 OID and ID Values<br />

<strong>Caché</strong> makes a further distinction between a “universal” object identifier (OID) and a “local”<br />

object identifier (ID). An OID value uniquely identifies any object stored in a database.<br />

Specifically, an OID value contains a class name along with an ID value. An ID value uniquely<br />

identifies an object belonging to a specific “extent” . An extent is a set of related objects,<br />

such as all instances of a specific class or all instances of a class and its subclasses.<br />

<strong>Caché</strong> provides two versions of methods that refer to persistent objects: one using OID and<br />

the other using ID values. For example, using <strong>Caché</strong> ObjectScript you can open an object<br />

instance using an OID value:<br />

Set object = ##class(MyApp.Person).%Open(oid)<br />

Or you can open it using an ID value:<br />

Set object = ##class(MyApp.Person).%OpenId(id)<br />

The ID versions have “Id” added to their name. In practice, applications most often use ID<br />

values; there are few circumstances in which you need to locate an object in which you do<br />

not know what extent (set of objects) an object belongs to.<br />

Note that these two forms are also available in <strong>Caché</strong> Basic:<br />

obj = Open MyApp.Person(oid)<br />

obj = OpenId MyApp.Person(id)<br />

3.1.2 OREFs and Reference Counting<br />

References to in-memory objects (OREFs) automatically manage a reference count—the<br />

number of items (variables or objects) currently referring to an object. Whenever you set a<br />

variable or object property to refer to a object, the object's reference count is automatically<br />

incremented. When a variable stops referring to an object (if it goes out of scope, is killed,<br />

or is set to a new value) the reference count is decremented. When this count goes to 0, the<br />

object is automatically destroyed (removed from memory) and its %OnClose method (if<br />

present) is called.<br />

For example, consider the following method:<br />

Method Test()<br />

{<br />

Set person = ##class(Sample.Person).%OpenId(1)<br />

}<br />

Set person = ##class(Sample.Person).%OpenId(2)<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 15


The <strong>Caché</strong> Object Model<br />

This method creates an instance of Sample.Person and places a reference to it into the variable<br />

person. Then it creates another instance of Sample.Person and replaces the value of person<br />

with a reference to it. At this point, the first object is no longer referred to and is destroyed.<br />

At the end of the method, person goes out of scope and the second object is destroyed.<br />

You can test if a variable contains a valid OREF value using the ObjectScript $IsObject<br />

function:<br />

// Open an object that is in the database<br />

Set obj = ##class(Sample.Person).%OpenId(1)<br />

Write "Is (1) an object ",$IsObject(obj),!<br />

// Open an object that is NOT in the database<br />

Set obj = ##class(Sample.Person).%OpenId(-1)<br />

Write "Is (-1) an object ",$IsObject(obj),!<br />

3.2 Types of Classes<br />

<strong>Caché</strong> supports a variety of class types for specialized behaviors. Classes are divided into<br />

data type classes and object classes. Data type classes represent literal values such as strings,<br />

integers, and dates; refer to the chapter on Data Types for more details. Data type classes are<br />

used to create literal properties of other objects. They do not have properties and cannot be<br />

instantiated.<br />

Object classes may have properties and can be instantiated. Most object class are subclasses<br />

of a system class called %RegisteredObject, which automatically provides much of an object's<br />

basic behavior.<br />

An object class has a rich set of default behaviors, which include the following:<br />

• Its instantiation triggers the automatic allocation of system memory for its properties.<br />

• Its instantiation triggers the automatic creation of a reference handle (OREF) for it.<br />

• It supports polymorphism.<br />

Object classes are further divided, according to their database behavior, into transient objects,<br />

persistent objects, and serial objects. A transient object (derived directly from the<br />

%RegisteredObject class) has no storage behavior; it exists only in memory. Persistent classes<br />

(derived the the %Persistent class) can be independently stored in a database. Serial objects<br />

(derived from the %SerialObject class) can be embedded within other objects; a serial object<br />

can only be stored to the database when it is embedded within another, persistent object.<br />

16 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Types of Classes<br />

3.2.1 Transient Object Classes<br />

Instantiations of classes derived from %RegisteredObject are known as registered or transient<br />

objects. These objects have a full set of built-in methods for controlling their in-memory<br />

behavior. (Most applications use persistent classes and embeddable classes rather than<br />

inheriting directly from %RegisteredObject.)<br />

Once you create an instance of a registered object, you can refer to it by its OREF (object<br />

reference). This system assigned value is transient; the value exists only while the object is<br />

in memory and is not guaranteed to be constant over different invocations.<br />

Registered objects use system allocated memory to store their values, and they support<br />

polymorphism.<br />

3.2.2 Persistent Object Classes<br />

Instantiations of classes derived from %Persistent are known as persistent objects; these<br />

objects are registered objects that inherit from the %Persistent class and have their ClassType<br />

keyword set to “persistent” . Persistent objects have the ability to store themselves into the<br />

database.<br />

Loading a persistent object into memory from the database does not load any other objects<br />

that it refers to. As soon as a referenced object is referred to, however, it is automatically<br />

brought into memory (this is known as “swizzling” or “lazy loading” ).<br />

When a persistent object is used as a property, it is referred to as a “reference” property.<br />

For example, if Doctor and Patient are persistent classes, you can define an attribute in the<br />

Patient class that uses Doctor as its type:<br />

Property TheDoc As Doctor;<br />

The property, TheDoc, is a reference property. The first time this property is referenced:<br />

Set doc = patient.TheDoc<br />

the appropriate Doctor object is loaded from the database automatically. In this example, doc<br />

will contain an OREF to the loaded Doctor object.<br />

You can chain together as many references as you like within one expression:<br />

Set location = patient.TheDoc.Hospital.Address.City<br />

If any of the objects referenced in such an expression is missing (null), then the entire<br />

expression will return a null value ("").<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 17


The <strong>Caché</strong> Object Model<br />

3.2.3 Serial Object Classes<br />

Instantiations of classes derived from %SerialObject are known as serial or embeddable<br />

objects; these objects are registered objects that inherit from the %SerialObject class and have<br />

their ClassType keyword set to “serial” . These objects can exist independently in memory,<br />

but, when stored to the database, can only exist as embedded within a persistent object.<br />

The %SerialObject class provides an object with:<br />

• The ability to produce a string representing the state of the object (that is, its current<br />

property values). The creation of this string is known as serializing the object.<br />

• The ability to be swizzled by using that serialized string. Like a persistent object, a serial<br />

object can be used as a property type, but its on-disk behavior differs from that of a persistent<br />

object. When used as a property, an embeddable class is known as an embedded<br />

object.<br />

Embedded objects have different in-memory and on-disk representations:<br />

• In memory, an embedded object is represented as a separate object and is indistinguishable<br />

from a reference to any other kind of object. The in-memory value of an embedded object<br />

attribute is an OREF that refers to the object's in-memory representation.<br />

• On disk, an embedded object property is stored as part of its containing object. A serial<br />

object has no separate identity (OID) and cannot be referred to by other objects. The<br />

values of the embedded object's properties are serialized and stored along with all of the<br />

other object attributes.<br />

The following diagram depicts an embeddable object as it exists in memory and on disk:<br />

18 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Types of Classes<br />

Embeddable <strong>Objects</strong><br />

Note:<br />

<strong>Caché</strong> does not support polymorphic serial objects in persistent containers.<br />

3.2.3.1 Embedded <strong>Objects</strong> in Memory and on Disk<br />

When a new instance of an object containing an embedded object is created, the %New<br />

method (or the analogous New command in <strong>Caché</strong> Basic) automatically creates an in-memory<br />

instance of the object.<br />

For example, consider an Address class:<br />

Class MyApp.Address Extends %SerialObject [ClassType = serial]<br />

{<br />

Property Street As %String(MAXLEN=80);<br />

Property City As %String(MINLEN=3);<br />

Property State As %String(MINLEN=2);<br />

}<br />

Once defined, the Address class can be used as an embedded object attribute for a persistent<br />

object class such as Patient:<br />

Class MyApp.Patient Extends %Persistent [ClassType=persistent]<br />

{<br />

Property Name As %String;<br />

Property Home As Address;<br />

}<br />

In memory, this Home property can be used as any other object-valued property:<br />

// Open a persistent patient object<br />

Set patient = ##class(MyApp.Patient).%OpenId(22)<br />

// Get the patient's city and state from its Home address<br />

Set city = patient.Home.City<br />

Set state = patient.Home.State<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 19


The <strong>Caché</strong> Object Model<br />

3.2.4 Data Type Classes<br />

Data type classes define and control literal values. Unlike object classes, data types do not<br />

have any independent identity and can never be explicitly instantiated. Instead, they exist<br />

only as properties of the objects that contain them. Data types classes cannot contain properties.<br />

A data type is a class with its ClassType keyword set to “datatype” . A data type class may<br />

implement a specific set of methods belonging to the data type interface. This interface<br />

includes a number of operations designed for validation and SQL interoperability.<br />

For example, the class Patient can define an attribute Name using the system-provided data<br />

type %String:<br />

Class MyApp.Patient Extends %Persistent [ClassType = persistent]<br />

{<br />

Property Name As %String;<br />

}<br />

Name exists as a value within a specific instance of a Patient object. You can only refer to it<br />

as an attribute of the object:<br />

Set name = pat.Name<br />

When the object pat is destroyed, its Name property disappears along with the rest of its<br />

properties (if any).<br />

3.3 Inheritance<br />

The <strong>Caché</strong> object model lets you extend (inherit from) existing classes by means of the Super<br />

keyword. A class defined in this way is referred to as a subclass; the class or classes it is<br />

derived from are referred to as superclasses.<br />

In the <strong>Caché</strong> object model, a class definition has a Super keyword that indicates its list of<br />

superclasses. Within a <strong>Caché</strong> Studio class definition this list is expressed using the “Extends”<br />

keyword:<br />

Class User.MyClass Extends %Persistent [ ClassType = persistent ]<br />

{<br />

}<br />

Note:<br />

The “Extends %Persistent” and the “ClassType = persistent” are both required in<br />

the above definition.<br />

20 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


A class inherits all of the specifications of its superclasses, including properties, methods,<br />

class parameters, applicable class keywords, and the parameters and keywords of the inherited<br />

properties and inherited methods. Except for items marked as Final, the subclass can override<br />

(but not delete) many of the characteristics of its inherited components.<br />

In addition to a class inheriting methods from its superclasses, the class' properties inherit<br />

additional methods from system property behavior classes and, in the case of a data type<br />

attribute, from the data type class.<br />

For example, if there is a class defined called Person:<br />

Class MyApp.Person Extends %Persistent [ClassType = persistent]<br />

{<br />

Property Name As %String;<br />

Property DOB As %Date;<br />

}<br />

It's simple to derive a new class, Employee, from it:<br />

Class MyApp.Employee Extends Person [ClassType = persistent]<br />

{<br />

Property Salary As %Integer;<br />

Property Department As %String;<br />

}<br />

Inheritance<br />

This definition establishes the Employee class as a subclass of the Person class. In addition<br />

to its own class parameters, properties, and methods, the Employee class includes all of these<br />

elements from the Person class.<br />

Note:<br />

A class does not inherit the value of its superclass' ClassType keyword. You must<br />

explicitly specify this value in every subclass.<br />

You can use a subclass in any place in which you might use its superclass. For example, using<br />

the above defined Employee and Person classes, it is possible to open an Employee object<br />

and refer to it as a Person:<br />

Set x = ##class(MyApp.Person).%OpenId(id)<br />

Write x.Name // results in "Groucho Marx"<br />

We can also access Employee-specific attributes or methods:<br />

Write x.Salary // results in 22000<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 21


The <strong>Caché</strong> Object Model<br />

3.3.1 Multiple Inheritance<br />

By means of multiple inheritance, a class can inherit its behavior and class type from several<br />

superclasses. To establish multiple inheritance, use the SUPER keyword and then the classes<br />

from which the defined class inherits.<br />

When you compile the class, the class compiler establishes values for the class' members<br />

according to the list of superclasses; a class' members are its class parameters, methods, and<br />

properties, but not its class keywords. If multiple superclasses have members with the same<br />

name, a superclass listed later takes precedence.<br />

Note that the override does not occur for class keywords. For these, the subclass inherits only<br />

the keyword values of its first superclass.<br />

For example, if class X inherits from classes A, B, and C, its definition includes:<br />

Class X Extends (A, B, C) {<br />

}<br />

In this case, the values of class X's class parameter values, properties, and methods are<br />

inherited from class A (the first superclass listed), then class B, and, finally, class C. If class<br />

B has a class member with the same name as a member already inherited from class A, it<br />

overrides (replaces) the previously inherited member; likewise for the members of class C<br />

in relation to those of classes A and B. The class keywords for class X come exclusively from<br />

class A.<br />

3.4 Class Compilation<br />

<strong>Caché</strong> class definitions are compiled into application routines by the <strong>Caché</strong> Class Compiler.<br />

Classes cannot be used in an application before they are compiled.<br />

The <strong>Caché</strong> Class Compiler differs from the compilers available with other programming<br />

languages, such as C++ or Java, in two significant ways: first, the results of compilation are<br />

placed into a shared repository (database), not a file system. Second, it automatically provides<br />

support for persistent classes.<br />

Specifically, the Class Compiler does the following:<br />

1. It generates a list of dependencies—classes that must be compiled first. Depending on<br />

the compile options used, any dependencies that have been modified since last being<br />

compiled will also be compiled.<br />

22 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


2. It resolves inheritance—it determines which methods, properties, and other class members<br />

are inherited from super classes. It stores this inheritance information into the Class<br />

Dictionary for later reference.<br />

3. For persistent and serial classes, it determines the storage structure needed to store objects<br />

in the database and creates the necessary runtime information needed for the SQL representation<br />

of the class.<br />

4. It executes any method generators defined (or inherited) by the class.<br />

5. It creates one or more routines that contain the runtime code for the class. The Class<br />

Compiler will group methods according to language (<strong>Caché</strong> ObjectScript and Basic) and<br />

generates separate routines, each containing methods of one language or the other.<br />

If you specify the “Keep Generated Source” option with the Class Compiler, you can<br />

view the source for the routines using the View Other command within Studio.<br />

6. It compiles all of the generated routines into executable code.<br />

7. It create a class descriptor. This is a special data structure (stored as a routine) that contains<br />

all the runtime dispatch information needed to support a class (names of properties,<br />

locations of methods, etc.).<br />

There are several ways to invoke the Class Compiler:<br />

• From within <strong>Caché</strong> Studio using the option in the Build menu.<br />

• From the <strong>Caché</strong> command line (terminal) using the Compile method of the %SYSTEM.OBJ<br />

object:<br />

Do $System.OBJ.Compile("MyApp.MyClass")<br />

Class Compilation<br />

Note that if you use SQL DDL statements to create a table, the Class Compiler will be automatically<br />

invoked to compile the persistent class corresponding to the table.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 23


4<br />

<strong>Caché</strong> Classes<br />

A class specification defines the contents and behavior of a class. This chapter describes the<br />

details of <strong>Caché</strong> class definitions. A class definition is distinct from a compiled class that you<br />

can instantiate at run time. A class definition consists of a number of elements, called members,<br />

and associated values; the sum total of these defines the class.<br />

The members of a class definition includes properties (which represent its state) and methods<br />

(which is the code that implements its behavior); these are each discussed in their own subsequent<br />

chapters, Methods and Properties. The class definition for a data type contains only<br />

methods, since data type classes have only literal values (and do not have properties). There<br />

are also other components to a class specification, including class parameters and class keywords<br />

(see below).<br />

The primary means for defining classes is the <strong>Caché</strong> Studio. Alternatively, you can define<br />

classes programmatically using the <strong>Caché</strong> class definition classes or via an XML class definition<br />

file. If you define an SQL table using SQL DDL statements, <strong>Caché</strong> creates a corresponding<br />

class definition.<br />

Each class definition specifies a number of members (and values for them):<br />

• A unique class name (including the package the class belongs to).<br />

• Methods—operations that may be carried out on or by a specific class or instance.<br />

• Properties—the data associated with the instances of a class.<br />

• Class Queries—filters used to find class instances that meet specific criteria.<br />

• Indexes—storage structures that optimize access to frequently viewed data.<br />

• Class Keywords—values that specify overall class behavior, such if a class is final or<br />

abstract.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 25


<strong>Caché</strong> Classes<br />

• Class Parameters—values that customize the behavior of a class at compile time, typically<br />

through the use of method generators.<br />

4.1 Naming Conventions<br />

Class and class members (referred to as identifiers) follow specific naming conventions.<br />

These are detailed in this section.<br />

4.1.1 General Identifier Rules<br />

Every identifier must be unique within its context (i.e., no two classes can have the same<br />

name).<br />

The maximum length of the various class identifiers are listed in the following table:<br />

Class Identifier Lengths<br />

Identifier<br />

Package Name<br />

Full Class Name<br />

Class Name<br />

Member Name<br />

Limits<br />

Each package name in a project must be different somewhere in<br />

the first 189 characters from all other packages in the namespace.<br />

The full name of a class includes the package name, the class<br />

name, and the period separating the package name from the<br />

class name. It must be shorter than 220 characters.<br />

The length of a class name is not specified. For a given package<br />

name, the maximum length can be inferred from the preceding<br />

rules. Each class name within a package must be uniquely<br />

distinguished from the others in the first 60 characters.<br />

The name of a class member (property name, method name,<br />

index name, and so on) cannot be longer than 31 characters.<br />

Identifiers preserve case: you must exactly match the case of a name; at the same time, two<br />

classes cannot have names that differ only in case. For example, the identifiers “id1” and<br />

“ID1” are considered identical for purposes of uniqueness.<br />

Identifiers must start with an alphabetic character, though they may contain numeric characters<br />

after the first position. Identifiers cannot contain spaces or punctuation characters with the<br />

exception of package names which may contain the “.” character. On a Unicode system,<br />

identifiers may contain Unicode characters.<br />

26 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Certain identifiers start with the “%” character; this identifies a system item. For example,<br />

many of the methods and packages provided with the <strong>Caché</strong> library start with the “%”<br />

character.<br />

4.1.2 Class Names<br />

Every class has a name that uniquely identifies it. A full class name consists of two parts: a<br />

package name and a class name: the class name follows the final “.” character in the name.<br />

A class name must be unique within its package; a package name must be unique within a<br />

<strong>Caché</strong> namespace. For details on packages, refer to Packages.<br />

Because persistent classes are automatically projected as SQL tables, a class definition must<br />

specify a table name that is not an SQL reserved word; if the name of a persistent class is an<br />

SQL reserved word, then the class definition must also specify a valid, non-reserved word<br />

value for its SQLTableName keyword.<br />

4.1.3 Class Member Names<br />

Class Keywords<br />

Every class member (such as a property or method) must have a name that is unique within<br />

its class. Further, a member of a persistent cannot use an SQL reserved word as its identifier;<br />

it can define an alias, however, using the member's SQLName or SQLFieldName keyword<br />

(as appropriate).<br />

4.2 Class Keywords<br />

A class definition includes a set of keywords whose values specify the overall behavior of<br />

the class. Each keyword is optional and has a default value if not explicitly specified.<br />

Abstract<br />

ClassType<br />

Specifies that you cannot create instances of this class; for data type classes, specifies<br />

that the class cannot be used as a property type.<br />

Specifies the behavior of the class. Available values are datatype, persistent, and<br />

serial.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 27


<strong>Caché</strong> Classes<br />

ClientDataType<br />

Description<br />

For a data type class, specifies the type when exposed via ActiveX or Java. There is<br />

no default value. Data type classes must specify a client data type.<br />

Specifies a description of the class. The description is displayed by the online class<br />

documentation system. For an example refer to the description for the %Persistent<br />

class.<br />

Final<br />

Specifies that the class is final; no classes can be derived from it.<br />

SQLRowIdName<br />

Specifies an alternate field name for the object row ID projected to SQL. By default<br />

the row ID is called “ID” .<br />

SQLTableName<br />

Super<br />

Specifies the table name used to identify the class in its SQL projection. By default,<br />

<strong>Caché</strong> uses a class' name as the name of its SQL table.<br />

Specifies one or more superclasses for the class (classes that the class extends). By<br />

default, classes have no superclasses.<br />

For a complete list of class keywords, refer to the Class Definition Reference.<br />

4.3 Class Parameters<br />

A class parameter defines a special constant value for all objects of a given class. When you<br />

create a class definition (or at any point before compilation), you can set the values for its<br />

class parameters. By default, the value of each parameter is the null string; to set a parameter's<br />

value, you must explicitly provide a value for it. At compile-time, the value of the parameter<br />

is established for all instances of a class. This value cannot be altered at run time.<br />

Class parameters are typically used for the following purposes:<br />

28 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


• To provide parameterized values for method generator methods to use. A method generator<br />

is a special type of method whose implementation is actually a short program that<br />

is run at compile-time in order to generate the actual runtime code for the method. Many<br />

method generators use class parameters as a way of controlling the behavior of method<br />

generators.<br />

• To customize the behavior of the various data type classes (such as providing validation<br />

information).<br />

• To define user-specific information about a class definition. A class parameter is simply<br />

an arbitrary name/value pair; you can use it to store any information you like about a<br />

class.<br />

• To define a class-specific constant value.<br />

A subclass can define additional class parameters. A subclass can also override the value of<br />

an inherited class parameter. For instance, if a class property assigns a value to a data type<br />

class parameter, then subclasses of the object's class inherit that property's assigned value.<br />

For example, suppose class MyApp.A defines a parameter, XYZ with a value of 10:<br />

Class MyApp.A<br />

{<br />

Parameter XYZ = 100;<br />

}<br />

A subclass, such as MyApp.B, can override the value of this parameter:<br />

Class MyApp.A Extends MyApp.B<br />

{<br />

Parameter XYZ = 200;<br />

}<br />

Class parameters have a special behavior when used with data type classes. With data type<br />

classes, class parameters are used to provide a way to customize the behavior of the data type.<br />

When you define a property within a class, you specify the property's type using a data type<br />

class and you can provide specific values for any parameters defined by the data type class.<br />

For example, the %Integer data type class has a class parameter, MAXVAL, which specifies<br />

the maximum valid value for a property of type %Integer. If you define a class with the<br />

property NumKids as follows:<br />

Property NumKids As %Integer(MAXVAL=10);<br />

Class Parameters<br />

This specifies that the MAXVAL parameter for the %Integer class will be set to 10 for the<br />

NumKids property.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 29


<strong>Caché</strong> Classes<br />

Internally, this works as follows: the validation methods for the standard data type classes<br />

are all implemented as method generators and use their various class parameters to control<br />

the generation of these validation methods. In this example, this property definition generates<br />

content for a NumKidsIsValidDT method that tests whether the value of NumKids exceeds<br />

10. Without the use of class parameters, creating this functionality would require the definition<br />

of an IntegerLessThanTen class.<br />

30 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


5<br />

Packages<br />

<strong>Caché</strong> <strong>Objects</strong> supports “packages” ; a way to group related classes within a specific<br />

namespace. Packages provide the following benefits:<br />

• They give developers an easier way to build larger applications and to share code with<br />

one another.<br />

• They make it easier to avoid name conflicts between classes.<br />

• They give a logical way to represent SQL schemas within the Object Dictionary in a<br />

clean, simple way: A package corresponds to a schema.<br />

5.1 Overview of Packages<br />

A package is simply a way to group related classes under a common name. For example, an<br />

application could have an “Accounting” system and an “Inventory” system. The classes<br />

that make up these applications could be organized into an “Accounting” package and an<br />

“Inventory” package:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 31


Packages<br />

Class Packages<br />

Any of these classes can be referred to using their full name (which consists of package and<br />

classname):<br />

Do ##class(Accounting.Invoice).Method()<br />

Do ##class(Inventory.Item).Method()<br />

If the package name can be determined from context (see below), then the package name can<br />

be omitted:<br />

Do ##class(Invoice).Method()<br />

A package is simply a naming convention: it does not provide any fundamental capabilities<br />

beyond how classes are named.<br />

As with classes, a package definition exists within a <strong>Caché</strong> namespace. A package cannot<br />

span namespaces. It will be possible, in a future version, to refer to packages in other<br />

namespaces using subscript-level mapping.<br />

5.2 Package Names<br />

A package name is simply a string. It may contain “.” (period) characters but no other<br />

punctuation. Although packages may contain “.” characters, there is no true hierarchy of<br />

packages (the various <strong>Caché</strong> development tools do display packages as a hierarchy for convenience).<br />

If you give a class the name “Test.Subtest.TestClass” , then this indicates that the<br />

name of the class is “TestClass” and the name of the package is “Test.Subtest” (which is<br />

mapped to the SQL schema “Test_TestClass” , as described below).<br />

There are several limitations on the length and usage of package names:<br />

• A package name (including “.” characters) is limited to 31 characters or less.<br />

32 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


• Within a namespace, each package name must be unique (regardless of case).<br />

Defining Packages<br />

• Within a package, each class name must be unique within its first 25 characters.<br />

5.3 Defining Packages<br />

Packages are implied by the name of the classes. An easy way to create a package is to use<br />

the New Class Wizard (in the <strong>Caché</strong> Studio), which has a input box where you can type in<br />

the name of the new package. (You can also browse from a list of current packages.)<br />

When the last class in a package is deleted, the package is also automatically deleted.<br />

Alternatively, you can simply create a class. When you do so, the package automatically is<br />

defined. For example, to define a package, simply add the package name to the class name<br />

in its definition:<br />

Class Accounting.Invoice {<br />

}<br />

This defines a class called Invoice within the “Accounting” package.<br />

<strong>Using</strong> the <strong>Caché</strong> Studio you can view and edit additional characteristics for a package, such<br />

as its description, by right-clicking on the package name within the Project tab of the<br />

Workspace window.<br />

5.4 <strong>Using</strong> Packages<br />

There are two ways to use class names:<br />

• Use the fully-qualified name (i.e., Package.Class).<br />

• Use just the simple (short) class name and let the class compiler resolve which package<br />

it belongs to.<br />

<strong>Using</strong> fully-qualified names is simple:<br />

// create an instance of Lab.Patient<br />

Set patient = ##class(Lab.Patient).%New()<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 33


Packages<br />

Letting the compiler resolve names requires the #IMPORT directive in .MAC code or within<br />

a class definition. If no import is specified, “short” class names are assumed to be in the<br />

“User” or “%Library” packages:<br />

// create an instance of Person<br />

Set person = ##class(Person).%New()<br />

// this is the same as:<br />

Set person = ##class(User.Person).%New()<br />

5.4.1 The IMPORT Directive<br />

The #IMPORT directive lets you specify a package to search in for a particular class; for<br />

example:<br />

#import Lab<br />

// Look for "Patient" within Lab package.<br />

Set patient = ##class(Patient).%New()<br />

You may have any number of #IMPORT statements within a MAC routine:<br />

#import Lab<br />

#import Accounting<br />

// Look for "Patient" within Lab & Accounting packages.<br />

Set pat = ##class(Patient).%New()<br />

// Look for "Invoice" within Lab & Accounting packages.<br />

Set inv = ##class(Invoice).%New()<br />

The order of #IMPORT statements does not matter; It is an error to use a short class that is<br />

ambiguous. That is, if you have the same class name in two or more packages and import all<br />

of them, you will get an error when the compiler attempts to resolve the package name. To<br />

avoid this error, use full names.<br />

The class keyword, IMPORT, works in the same way: it specifies which packages to use for<br />

resolving class references within the class definition and also that an #IMPORT statement<br />

should be placed in the generated code. Note that if you do not have any #IMPORT statements,<br />

it is as if you had specified:<br />

#import User<br />

Once you have any #IMPORT statement, User is NOT automatically imported. Therefore,<br />

you may have to specify:<br />

#import MyPackage<br />

#import User<br />

34 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


The reason for this logic is because there are cases where you may NOT want User to be<br />

imported. <strong>Using</strong> the #IMPORT statement, you can do clever tricks:<br />

#import Customer1<br />

Do ##class(Application).Run()<br />

Now change App.MAC to:<br />

Packages and SQL<br />

#import Customer2<br />

Do ##class(Application).Run()<br />

When you recompile App.MAC, you will be using the Application class for the “Customer2”<br />

package. Such tricks require up-front planning: you have to consider code compatibility as<br />

well as the effects on your storage structures.<br />

5.5 Packages and SQL<br />

Every package corresponds to an SQL schema. For instance, if a class is called Team.Player<br />

(the Player class in the “Team” package), the corresponding table is “Team.Player” (the<br />

Player table in the “Team” schema).<br />

The default package is “User” , which corresponds to the “SQLUser” schema. Hence, a<br />

class called User.Person corresponds to a table called SQLUser.Person.<br />

If a package name contains periods, the corresponding table name uses an underscore in the<br />

place of each. For example, the class MyTest.Test.MyClass (the MyClass class in the<br />

“MyTest.Test” package) becomes the table MyTest_Test.MyClass (the MyClass table in the<br />

“MyTest_Test” schema).<br />

If an SQL table name is referenced without the schema name, the default schema name<br />

(SQLUser) is used. For instance, the command:<br />

Select ID, Name from Person<br />

is the same as:<br />

Select ID, Name from SQLUser.Person<br />

5.6 Built-in Packages<br />

For compatibility with earlier versions, there are two "built-in" packages:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 35


Packages<br />

• “%Library” —any %class (without a package name) is implicitly part of the “%Library”<br />

package.<br />

• “User” —any non-% class name without a package is implicitly part of the “User”<br />

package<br />

36 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


6<br />

Methods<br />

Methods are operations that are associated with and can be invoked upon an object. Methods<br />

are executed within a <strong>Caché</strong> process; when you are using <strong>Caché</strong> object on a client (such as<br />

a Java program), <strong>Caché</strong> automatically invokes methods within the appropriate server process.<br />

Every method has a name, a formal argument specification, and an implementation (code).<br />

A method name must be unique within its class.<br />

Note:<br />

The word “method” generally refers to instance methods, or methods that act on a<br />

specific instance of a class. <strong>Caché</strong> also supports class methods (methods that do not<br />

act on a specific instance).<br />

6.1 Method Arguments<br />

Methods operate on variables that they receive through their arguments. A method can take<br />

any number of arguments. A method's definition specifies the arguments that it takes and<br />

each argument's data type. You can also specify the default value for any missing argument.<br />

Additionally, a method definition specifies which arguments are called by reference and<br />

which are called by value. The default is to call an argument by value.<br />

For instance, here is a Calculate method taking three arguments:<br />

Method Calculate(count As %Integer, name, state As %String = "CA")<br />

{<br />

// ...<br />

}<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 37


Methods<br />

where count and state are declared as %Integer and %String, respectively. By default, arguments<br />

are of the %String data type, so that an argument of unspecified type is a %String. This is the<br />

case for name in the above example. Generally it is a good idea to explicitly specify the data<br />

type of of each method argument.<br />

6.1.1 Specifying Default Values<br />

To specify an argument's default value, set it equal to the desired value:<br />

Method Test(flag As %Integer = 0)<br />

{<br />

}<br />

When a method is invoked, it uses its default values (if specified) for any missing arguments.<br />

6.1.2 Calling By Reference<br />

By default, method arguments are passed by value. You can call an argument by reference<br />

using the ByRef modifier:<br />

/// Swap value of two integers<br />

Method Swap(ByRef x As %Integer, ByRef y As %Integer)<br />

{<br />

Set temp = x<br />

Set x = y<br />

Set y = temp<br />

}<br />

You can also specify an argument whose value is returned by reference but has no incoming<br />

value by using the Output modifier. This is equivalent to ByRef except that any incoming<br />

argument values are ignored.<br />

Method CreateObject(Output newobj As MyApp.MyClass) As %Status<br />

{<br />

Set newobj = ##class(MyApp.MyClass).%New()<br />

Quit $$$OK<br />

}<br />

In <strong>Caché</strong> ObjectScript, when calling a method which has arguments that are passed by reference<br />

(ByRef or Output), you must place a dot “.” before each of the arguments being called<br />

by reference, such as:<br />

Do obj.Swap(.arg1, .arg2)<br />

You can pass both simple variables and OREFs (object references) by reference using this<br />

syntax.<br />

38 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Method Return Values<br />

6.2 Method Return Values<br />

Each method's definition specifies whether it returns a value and, if so, the return value's type.<br />

The return type of a method is a class. If the return type is a data type class, then the method<br />

returns a literal value (such as a number or string). Otherwise, the method returns a reference<br />

(OREF) to an instance of a class.<br />

The syntax for specifying a return type is:<br />

Method MethodName() As ReturnType<br />

{<br />

}<br />

where Method is the method and ReturnType specifies the name of the class that Method<br />

returns.<br />

For example, the following method returns an %Integer value:<br />

Method Add(x As %Integer, y As %Integer) As %Integer<br />

{<br />

Quit x + y<br />

}<br />

An example of a method that creates and returns an object instance is:<br />

Method FindPerson(id As %String) As Person<br />

{<br />

Set person = ##class(Person).%OpenId(id)<br />

Quit person<br />

}<br />

6.3 Method Visibility<br />

Instance methods can be either public or private. If private (specified with the PRIVATE<br />

keyword), they can only be accessed by methods of any instance of the class to which they<br />

belong; if public (the default), they can be accessed from any method or other call.<br />

Class methods can only be public. A class method using the PRIVATE keyword is still<br />

accessible from all methods or other calls; however, it does not appear in the generated <strong>Caché</strong><br />

Class Reference.<br />

In <strong>Caché</strong>, private methods are inherited and visible to subclasses of the class that defines the<br />

method. Other languages often call these protected methods.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 39


Methods<br />

6.4 Method Language<br />

You have the choice of implementation language when creating a method within <strong>Caché</strong>.<br />

Currently, there are three options: “Cache” (<strong>Caché</strong> ObjectScript), “Basic” , and “Java” .<br />

By default, a method uses the language specified by its class' Language keyword. You can<br />

override this on a method-by-method basis using the method's Language keyword:<br />

Class MyApp.Test {<br />

/// A Basic method<br />

Method TestB() As %Integer [ Language = basic]<br />

{<br />

'This is Basic<br />

Print "This is a test"<br />

Return 1<br />

}<br />

/// A Cache ObjectScript method<br />

Method TestC() As %Integer [ Language = cache]<br />

{<br />

// This is Cache ObjectScript<br />

Write "This is a test"<br />

Quit 1<br />

}<br />

}<br />

Within a class, it is possible to have methods implemented in different languages. All methods<br />

interoperate regardless of implementation language.<br />

<strong>Caché</strong> ObjectScript and Basic methods are compiled into executable <strong>Caché</strong> code; Java<br />

methods are treated differently. When you create a Java representation of a class (using the<br />

<strong>Caché</strong> Java Binding), all of its Java methods are generated directly within the Java source.<br />

This means that all Java methods run within a native JVM and have access to the complete<br />

Java environment.<br />

6.5 Method Keywords<br />

You can modify a method definition through the use of one or more method keywords. Each<br />

keyword is optional and has a default value if not explicitly specified.<br />

The major method keywords are:<br />

40 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


ClassMethod<br />

Description<br />

Method Keywords<br />

Specifies that the method is a class method. By default, methods are instance methods.<br />

Subclasses inherit and cannot modify the value of the ClassMethod keyword.<br />

Provides an optional description of the method; <strong>Caché</strong> does not use this description.<br />

By default methods have no description. Subclasses do not inherit the value of the<br />

Description method keyword.<br />

Final<br />

Specifies that subclasses cannot override the method. By default, methods are not<br />

final. The Final method keyword is inherited by subclasses.<br />

Private<br />

ReturnType<br />

SQLProc<br />

Specifies that the method can only be invoked by other methods of its class or subclasses.<br />

(If a method is not private, there are no restrictions on where it can be<br />

invoked.) Subclasses inherit the methods as private and can modify the value of the<br />

keyword. By default, a method is public. (Note that other languages often use the<br />

word “protected” to describe this kind of visibility and use “private” to prevent visibility<br />

from subclasses.)<br />

Specifies the data type of the value returned by a call to the method. Setting<br />

ReturnType to an empty string ("") specifies that there is no return value.<br />

The value of the ReturnType keyword is inherited by subclasses and can be modified<br />

by subclasses. If you do override the value of the ReturnType keyword, you are limited<br />

to changing it to a class that is a subclass of the original type.<br />

By default, a method has no return value.<br />

Specifies that the method is an SQL stored procedure. By default, a method has no<br />

stored procedures. Stored procedures are inherited by subclasses.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 41


Methods<br />

6.6 Instance and Class Methods<br />

Generally, the term “method” refers to an instance method, which is a method that is invoked<br />

from a specific instance of a class that performs some action related to that instance, such as<br />

performing business logic, displaying information about the instance, and so on. For example,<br />

suppose you have an instance of the Sample.Person class (included in the “Samples”<br />

namespace) in memory called MyPerson; you can then invoke its instance methods, such as<br />

its NinetyNine method (a example method that simply returns the value 99):<br />

Set x = MyPerson.NinetyNine()<br />

This line of code uses the NinetyNine method to set x equal to 99.<br />

In addition to instance methods, <strong>Caché</strong> supports class methods. A class method is not necessarily<br />

associated with a particular object and need not be invoked from an open object. As<br />

such, you can invoke a class method in either of two ways:<br />

• Without any object reference<br />

• From a specific instance in order to act on one or more objects of the class<br />

A class method can refer to other class methods, but cannot refer to any object properties or<br />

instance methods. For example:<br />

Set x = ##class(Invoice).%Open(oid)<br />

Set x = ##class(Patient).%New()<br />

The first example involves the %Open class method of the Invoice class, which takes the<br />

OID of a particular instance, opens that instance, and returns a handle to it; the second<br />

example uses the Patient class' the %New method to create a new Patient instance and return<br />

a handle to it.<br />

Class methods are important because you can invoke them without having an instance that<br />

is already open. For example, you can use the %New and %OpenId class methods to create<br />

or open an instance. Class methods are also useful for actions that involve multiple or all<br />

instances of a class.<br />

6.7 Kinds of Methods<br />

<strong>Caché</strong> supports four types of methods:<br />

42 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Kinds of Methods<br />

• Code Methods<br />

• Expression Methods<br />

• Call Methods<br />

• Method Generators<br />

The various method types control how the <strong>Caché</strong> class compiler interprets the Implementation<br />

of the method.<br />

6.7.1 Code Methods<br />

A code method is a method whose implementation is simply lines of code. This is the most<br />

typical type of method and is the default.<br />

For instance, the following method defines a Speak code method for the Dog class:<br />

Class MyApp.Dog Extends %Persistent [ClassType = persistent]<br />

{<br />

Method Speak() As %String<br />

{<br />

Quit "Woof, Woof"<br />

}<br />

}<br />

The method code can contain any valid <strong>Caché</strong> ObjectScript code (including embedded SQL<br />

and embedded HTML) or Basic code (depending on the method's Language keyword).<br />

Assuming dog refers to a Dog object, you could invoke this method as follows:<br />

Write dog.Speak()<br />

// yields: Woof, Woof<br />

6.7.2 Expression Methods<br />

An expression method is a method that may be replaced by the class compiler, in certain<br />

circumstances, with a direct in-line substitution of a specified expression. Expression methods<br />

are typically used for simple methods (such as those found in data type classes) that need<br />

rapid execution speed.<br />

For example, it is possible to convert the Speak method of the Dog class from the previous<br />

example into an expression method:<br />

Method Speak() As %String [CodeMode = expression]<br />

{<br />

"Woof, Woof"<br />

}<br />

Assuming dog refers to a Dog object, this method could be used as follows:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 43


Methods<br />

Write dog.Speak()<br />

Which could result in the following code being generated:<br />

Write "Woof, Woof"<br />

It is a good idea to give all formal variables of an expression method default values. This<br />

prevents potential inline substitution problems caused by missing actual variables at runtime.<br />

Note:<br />

<strong>Caché</strong> does not allow macros or call-by-reference arguments within expression<br />

methods.<br />

6.7.3 Call Methods<br />

A call method is a special mechanism to create method wrappers around existing <strong>Caché</strong><br />

routines. This is typically useful when migrating legacy code to object-based applications.<br />

Defining a method as a call method indicates that a specified routine is called whenever the<br />

method is invoked. The syntax for a call method is:<br />

Method Call() [ CodeMode = call ]<br />

{<br />

Tag^Routine<br />

}<br />

where “Tag^Routine” specifies a tag name within a routine.<br />

6.7.4 Method Generators<br />

A method generator is actually a small program that is invoked by the Class Compiler during<br />

class compilation. Its output is the actual runtime implementation of the method. Method<br />

generators provide a means of inheriting methods that can produce high performance, specialized<br />

code that is customized to the needs of the inheriting class or property. Within the <strong>Caché</strong><br />

library method generators are used extensively by the data type and storage classes.<br />

For details refer to the Method Generators chapter.<br />

44 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


7<br />

Properties<br />

Properties are the class members that define the state of an object. The manner in which you<br />

can access and manipulate the values of an object's properties depends on the object's class<br />

definition. Formally, there are two kinds of properties:<br />

• Attributes, which hold values<br />

• Relationships, which hold associations between objects<br />

Within <strong>Caché</strong>, attribute properties are simply referred to as “properties” while relationship<br />

properties are referred to as “relationships.”<br />

Many object languages such as Java and C++ do not have true properties, but rather private<br />

variables manipulated by public accessor methods. In <strong>Caché</strong>, properties are different than<br />

variables. There is a rich model for properties in which:<br />

• Properties can be literal values, collections of literal values, streams, references to persistent<br />

objects or embedded objects, or collections of references to persistent objects or<br />

embedded objects.<br />

• Properties automatically have a set of property methods supporting validation and storage.<br />

• Properties can transparently invoke sophisticated translations and other advanced functionality<br />

during data retrieval and storage.<br />

• Properties automatically pull embedded and persistent objects into memory as soon as<br />

they are referenced. This is called “swizzling” (sometimes referred to as “lazy loading”<br />

).<br />

Every property has a name, a type, an optional set of modifying keywords, and an optional<br />

set of parameters for its type. A property name must be unique within its class.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 45


Properties<br />

7.1 Property Keywords<br />

You can modify a property definition through the use of one or more property keywords.<br />

Each keyword is optional and has a default value if not explicitly specified.<br />

The available property keywords include:<br />

Calculated<br />

Description<br />

Specifies that the property has no in-memory storage allocated for it when the object<br />

containing it is instantiated. By default, a property is not calculated. Subclasses inherit<br />

the Calculated keyword and cannot override it.<br />

Provides an optional description of the property; <strong>Caché</strong> does not use this description.<br />

By default properties have no description. Subclasses do not inherit the value of the<br />

Description property keyword.<br />

Final<br />

Specifies that subclasses cannot override the property. By default, properties are not<br />

final. The Final property keyword is inherited by subclasses.<br />

InitialExpression<br />

Private<br />

Required<br />

Specifies an initial value for the property. By default, properties have no initial value.<br />

Subclasses inherit the value of the InitialExpression keyword and can override it.<br />

Specifies that the property is private. By default, properties are not private. Subclasses<br />

inherit the value of the Private keyword and cannot override it.<br />

Specifies that the property's value must be set before it can be stored to disk. By<br />

default, properties are not required. Subclasses inherit the value of the Required<br />

keyword and can override it.<br />

46 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Transient<br />

Property Visibility<br />

Specifies that the property is not stored in the database. By default, properties are<br />

not transient. Subclasses inherit the value of the Transient keyword and cannot<br />

override it.<br />

Type<br />

Specifies the name of the class associated with the property, which can be a data type<br />

class, a persistent class, or an embeddable class. By default, the type of a property is<br />

%String. Subclasses inherit the value of the Type keyword.<br />

7.2 Property Visibility<br />

Properties may be specified as being public or private. If they are public, they can be accessed<br />

anywhere. If they are private, they can only be accessed by instance methods of the object<br />

to which they belong.<br />

Note:<br />

Methods which are declared as classmethods do NOT have access to properties<br />

declared to be private, even if they have a reference to an object of the class they are<br />

declared in. Private properties are only accessible from within instance methods of<br />

the class..<br />

In <strong>Caché</strong>, private properties are always inherited and visible to subclasses of the class that<br />

defines the property. Other languages often call these protected properties.<br />

7.3 Property Behavior<br />

Properties have a number of methods associated with them automatically. These methods are<br />

not inherited via standard inheritance. Rather, they use a special property behavior mechanism<br />

to generate a series of methods for each property.<br />

Each property inherits a set of methods from two places:<br />

• A %Property class that provides certain built-in behavior, such as Get, Set, and validation<br />

code.<br />

• The data type class of the property's data type, if it is a data type property. Many of these<br />

methods are method generators.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 47


Properties<br />

Property Behavior<br />

The property behavior classes are system classes. You cannot specify or modify property<br />

behavior.<br />

For example, if we define a class Person with three properties:<br />

Class MyApp.Person Extends %Persistent [ClassType = persistent]<br />

{<br />

Property Name As %String;<br />

Property Age As %Integer;<br />

Property DOB As %Date;<br />

}<br />

The compiled Person class has a set of methods automatically generated for each of its<br />

properties. These methods are inherited from the system Property class as well as the data<br />

type class associated with the property. The names of these generated methods are the property<br />

name concatenated with the name of the method from the inherited class. For example, some<br />

of the methods associated with the DOB property are:<br />

Set x = person.DOBIsValid(person.DOB)<br />

Write person.DOBLogicalToDisplay(person.DOB)<br />

where IsValid is a method of the property class and LogicalToDisplay is a method of the<br />

%Date data type class.<br />

48 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Property Accessors<br />

7.4 Property Accessors<br />

The <strong>Caché</strong> dot syntax for referring to properties is an interface for a set of accessor methods<br />

to retrieve and set values. For each property, whenever the code refers to oref.Prop (where<br />

oref is an object and Prop is a property), it is executed as if a system-supplied PropGet or<br />

PropSet method were invoked. For example:<br />

Set person.DOB = x<br />

acts as if the following method was called:<br />

Do person.DOBSet(x)<br />

while:<br />

Write person.Name<br />

acts like:<br />

Write person.NameGet()<br />

It is important to point out that in most cases, there are no actual PropGet and PropSet<br />

methods; access for simple properties is implemented directly within the <strong>Caché</strong> virtual machine<br />

for optimal performance. You can, however, provide PropGet and PropSet methods for a<br />

specific property. In this case, these accessor methods will be invoked at runtime automatically.<br />

Typically, every property has process-private, in-memory storage allocated for it. The systemprovided<br />

property accessors use this storage directly. Note that this object storage is not held<br />

in the local variable symbol table and is not affected by the Kill command.<br />

If you need to access the in-memory stored value of a property from within an instance method<br />

of an object, you can use the following in-memory value syntax:<br />

Set i%Name = "Carl"<br />

This directly sets “Carl” as the in-memory value of the property Name, bypassing the NameSet<br />

accessor (if present).<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 49


Properties<br />

7.5 Attribute Properties<br />

An attribute property is a value associated with a specific object (as opposed to a relationship<br />

property which relates two or more objects). Such properties typically represent the state of<br />

a specific object instance.<br />

A property's type is determined by the class associated with the property (or by a keyword<br />

in the case of collection and multidimensional properties). By default, properties use the<br />

%String data type.<br />

There are several types of attribute properties, including:<br />

• Data Type Properties<br />

• Object-Valued Properties<br />

• Collection Properties<br />

• Stream Properties<br />

• Multidimensional properties<br />

7.5.1 Data Type Properties<br />

The simplest type of property is a data type property. This is a literal value whose behavior<br />

is controlled by the data type class associated with the property.<br />

For example, a class can define a Count property using the %Integer data type:<br />

Property Count As %Integer;<br />

Since %Integer is a data type class, Count is a data type property.<br />

You can use data type parameters to place constraints on the allowed values of data type<br />

properties. You can specify values for the data type class parameter as follows:<br />

Property Count As %Integer(MAXVAL = 100);<br />

To set a data type property value equal to the empty string, use code of the form:<br />

Set object.Property = $C(0)<br />

50 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Attribute Properties<br />

CAUTION:<br />

Do not set the property equal to the empty string using open- and close-quotes<br />

(""), as this can result in values that are not valid or errors.<br />

This is particularly significant if the application is using an external interface,<br />

such as Java.<br />

7.5.2 Object-Valued Properties<br />

An object-valued property is a reference to another object, either embedded or persistent. In<br />

either case, the value of the property is an OREF while in memory. On disk, a reference to a<br />

persistent object becomes an OID, while an embedded object becomes a single serialized<br />

string containing all of the embedded object's properties.<br />

For instance, if an object-valued property, Doc, is of the type Doctor (where Doctor is either<br />

a persistent object or an embeddable object), the definition of Doc is:<br />

Property Doc As Doctor;<br />

7.5.3 Collection Properties<br />

A collection property is one that contains a group (or collection) of individual elements, all<br />

of the same type. To specify a collection property, place the collection type at the start of the<br />

property definition. <strong>Caché</strong> supports two kinds of collections, called List and Array collections..<br />

The property type determines the contents of the collection. For example, the following code<br />

defines a Colors property which is a list collection of %String values:<br />

// Property Colors As List Of %String;<br />

Property Colors As List Of %String; // the current syntax<br />

Similarly, <strong>Caché</strong> supports object collections, as in the following Doctors property which is<br />

an Array of references to Doctor objects:<br />

Property Doctors As Array Of Doctor;<br />

When you create a collection, <strong>Caché</strong> gives it a set of methods through a collection object.<br />

<strong>Caché</strong> includes several kinds of collection objects, each one designed to operate on a different<br />

kind of collection:<br />

Collection <strong>Objects</strong><br />

Type<br />

Data type<br />

Object (persistent or embedded)<br />

Array property<br />

%ArrayOfDataTypes<br />

%ArrayOf<strong>Objects</strong><br />

List property<br />

%ListOfDataTypes<br />

%ListOf<strong>Objects</strong><br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 51


Properties<br />

You can manipulate a collection property using the methods of the collection object associated<br />

with it. For example, you can perform operations on the Colors property defined earlier, using<br />

the methods provided by the %ListOfDataTypes class:<br />

Do obj.Colors.Insert("Red")<br />

Do obj.Colors.Insert("Green")<br />

Do obj.Colors.Insert("Blue")<br />

7.5.4 Stream Properties<br />

Streams are used to create properties containing large (greater than 32K) amounts of character<br />

or binary data. Binary streams contain the same sort of data as type %Binary, and can hold<br />

large binary objects such as pictures. Character streams contain the same sort of data as type<br />

%String, and are intended for storing large amounts of text. Stream data can be stored in either<br />

an external file or a global, depending on how the property is defined. For example, the following<br />

code defines a binary stream stored as a file, and a character stream stored as a global<br />

(the LOCATION parameter is optional):<br />

Property Image As %FileBinaryStream(LOCATION = "C:/Images");<br />

Property Text As %GlobalCharacterStream(LOCATION = "^MyText");<br />

Streams provide numerous methods for manipulating the data they contain. See the Streams<br />

chapter for more detailed information.<br />

7.5.5 Multidimensional Properties<br />

Properties can be marked as being multidimensional. This gives the instance variable associated<br />

to the property all the characteristics of an array node, upon which array operations like<br />

$ORDER could work. For instance:<br />

Property abc [ MultiDimensional ];<br />

Property abc As %String [ MultiDimensional ];<br />

The following are examples of available operations on abc:<br />

Set x = $DATA(obj.abc)<br />

Set x = $DATA(obj.abc(3))<br />

Set x = $GET(obj.abc(3))<br />

Set x = $ORDER(obj.abc("hello",3))<br />

KILL obj.abc<br />

The property behavior and data type classes are not used to generate methods for multidimensional<br />

properties. Thus, a multidimensional property named Kids has no KidsGet, KidsSet,<br />

or KidsLogicalToDisplay methods. There is no validation on setting a property value.<br />

52 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Attribute Properties<br />

Multidimensional properties cannot be exposed through ActiveX or Java. Multidimensional<br />

properties cannot be stored in or exposed through SQL tables. Also, multidimensional properties<br />

are transient, unless the application includes code to store their data.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 53


8<br />

Class Queries<br />

Class queries provides a tool to look up instances of a class that meet specified criteria. They<br />

allow you to create named SQL statements that are part of a class structure so that applications<br />

can have pre-defined lookups. For example, you can look up instances by some property,<br />

such as by name, or provide a list of instances that meet a particular set of conditions, such<br />

as all the flights from Paris to Madrid.<br />

By creating a class query, you can avoid having to look up a particular object by its internal<br />

ID. Instead, you can create a query that looks based on any class properties that you want.<br />

These can even be specified from user input at runtime.<br />

A class query can be either in SQL or <strong>Caché</strong> ObjectScript. To create a query, use the <strong>Caché</strong><br />

Studio and create one either by hand or using the New Query Wizard.<br />

Class queries are designed for transparent use with the relational aspect of <strong>Caché</strong> classes. As<br />

stated, they use SQL syntax. When the application invokes the query, it returns values in a<br />

%Library.ResultSet object, which provides robust interface for processing data.<br />

Class queries provide logical comparisons for data values. You can compare whether the<br />

value is greater than, equal to, or less than a certain value and include only the objects with<br />

values that pass the specified test. You can also compare whether the value starts with a certain<br />

string and include these objects in the result set. A query can also perform multiple comparisons.<br />

Because the comparisons support any combination of nested AND and OR operators,<br />

you can require that an object meets one, some, or all of them in order to be added to the<br />

result set.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 55


Class Queries<br />

8.1 Query Basics<br />

The process for creating and using a class query is:<br />

1. Define the query. The simplest way to do this is using the New Query Wizard in the<br />

<strong>Caché</strong> Studio.<br />

2. Invoke the query. This can be using the %New method of the %Library.ResultSet class<br />

within server-based methods; using the Java or ActiveX result set classes that are part of<br />

the <strong>Caché</strong> Java and ActiveX bindings, respectively; or as a OBDC or JDBC stored procedure.<br />

3. Once the query has returned a %Library.ResultSet object (often known as a result set),<br />

fetch data from it. (Further information on the result set processing is available in the<br />

“Dynamic SQL” chapter of the <strong>Using</strong> <strong>Caché</strong> SQL guide.<br />

4. When processing is complete, close the result set.<br />

8.1.1 The Structure of a Class Query<br />

The simplest query is of type %SQLQuery and simply contains an SQL SELECT statement.<br />

Such a query performs all processing for the application. It is of the form:<br />

Query QueryName(Parameter As %String) As %SQLQuery<br />

{<br />

SELECT MyProperty, MyOtherProperty FROM MyClass<br />

WHERE (MyProperty = "Hello" AND MyOtherProperty = :Parameter)<br />

ORDER BY MyProperty<br />

}<br />

You can also create a query of type %Query, which allows the application to perform its own<br />

custom processing. Such a query is of the form:<br />

8.1.2 Query Keywords<br />

You can modify a query definition through the use of one or more query keywords. Each<br />

keyword is optional and has a default value if not explicitly specified. The major query keywords<br />

are:<br />

Description<br />

Provides an optional description of the query; <strong>Caché</strong> does not use this description.<br />

By default, queries have no description.<br />

56 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Query Basics<br />

Final<br />

Specifies that subclasses cannot override the query. By default, queries are not final.<br />

SQLQuery<br />

SQLProc<br />

Specifies the code to execute when the query is run. Use this keyword if the query<br />

uses SQL (that is, is not user-written).<br />

Specifies that the query can also be accessed as an SQL stored procedure. By default,<br />

a query is not projected as a stored procedure.<br />

Type<br />

Specifies whether the query contains SQL or <strong>Caché</strong> ObjectScript code. By default,<br />

the query contains SQL code.<br />

8.1.3 Creating a Class Query Specification<br />

A query needs to contains information about its contents and the order of the fields returned<br />

for each row of its results. To provide this information, user-written queries include what is<br />

called a specification.<br />

A specification has one or two parameters modifying the query. These parameters are:<br />

• CONTAINID — This optional parameter specifies which field, if any, contains the ID<br />

for a particular row.<br />

• ROWSPEC — This parameter provides information on the names, data types, headings,<br />

and order of the fields in each row of the query's result set.<br />

8.1.3.1 About CONTAINID<br />

CONTAINID should be set to the number of the column returning the ID (1, by default) or<br />

to 0 if no column returns the ID. If you create a query using the New Query Wizard, then<br />

Studio automatically assigns the appropriate value to CONTAINID, based on the order in<br />

that wizard's Columns page.<br />

Note:<br />

<strong>Caché</strong> does not validate the value of CONTAINID. If you specify a non-valid value<br />

for this parameter, <strong>Caché</strong> does not throw an error. This means that if your query<br />

processing logic depends on this information, you may experience inconsistencies<br />

if the CONTAINID parameter is set improperly.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 57


Class Queries<br />

8.1.3.2 About ROWSPEC<br />

A query's ROWSPEC provides information on the names, data types, headings, and order of<br />

the fields in each row. It is a quoted and comma-separated list of variable names and data<br />

types of the form:<br />

ROWSPEC = "Var1:%Type1,Var2:%Type2[:OptionalDescription]"<br />

The ROWSPEC specifies the order of fields as a comma-separated list. Each field's information<br />

consists of a colon-separated list of its name, its data type (if it is different than the property's<br />

assigned data type), and an optional heading. You can edit a ROWSPEC's code directly, or,<br />

for an existing query, by viewing the query Class Inspector window, expanding the list of<br />

parameters, and using the available dialog box.<br />

The number of elements in the ROWSPEC must match the number of fields in the query.<br />

Otherwise, <strong>Caché</strong> returns a “Cardinality Mismatch” error.<br />

8.1.3.3 A Class Query Specification Example<br />

The ByName query of the %Sample.Person sample class that comes with <strong>Caché</strong> has the following<br />

signature:<br />

Query ByName(name As %String = "")<br />

As %SQLQuery(CONTAINID = 1,<br />

ROWSPEC = "ID:%Integer,Name:%String(MAXLEN=30),DOB,SSN",<br />

SELECTMODE = "RUNTIME")<br />

[ SqlName = SP_Sample_By_Name, SqlProc ]<br />

{<br />

SELECT ID, Name, DOB, SSN<br />

FROM Sample.Person<br />

WHERE (Name %STARTSWITH :name)<br />

ORDER BY Name<br />

}<br />

Here, the CONTAINID parameter specifies that the first field is the ID (the default), and the<br />

first field specified in the SELECT statement is, respectively, ID. The ROWSPEC specifies<br />

that the fields are ID (treated as an integer), Name, DOB, and SSN; similarly, the SELECT<br />

statement contains the fields ID, Name, DOB, and SSN, in that order.<br />

8.2 User-Written Class Queries<br />

While simple %SQLQuery queries perform all result set management for you, this is not sufficient<br />

for certain applications. For such situations, <strong>Caché</strong> allows you to write queries in<br />

58 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


<strong>Caché</strong> ObjectScript. These queries are processed using the same %Library.ResultSet interface<br />

as queries written in SQL but are defined differently.<br />

To define a <strong>Caché</strong> ObjectScript query, specify %Query for the query type and provide necessary<br />

information for the related parameters (ROWSPEC and CONTAINID). For example, if a<br />

Book class has Author and Title properties, you could define an All query (to list all books)<br />

as follows:<br />

Query All() As %Query(CONTAINID = 1, ROWSPEC = "Title:%String,Author:%String")<br />

{<br />

}<br />

The actual query definition contains no other information, as the query processing code is<br />

defined in three class methods that are associated with it. These methods are:<br />

• querynameExecute — This method invokes the query. For queries that use the SELECT<br />

command, this method also prepares the query for subsequent traversal and data retrieval.<br />

• querynameFetch — Calling this method returns a row of the result set; each subsequent<br />

call returns the next row.<br />

• querynameClose — This method performs cleanup operations.<br />

where queryname is the name of the query.<br />

In addition, <strong>Caché</strong> automatically generates querynameGetInfo and querynameFetchRows<br />

methods from the row specification and querynameFetch method respectively. Your application<br />

does not call any of these methods directly — the %Library.ResultSet object uses them<br />

to process query requests.<br />

8.2.1 The querynameExecute Method<br />

The querynameExecute method provides all of the setup needed to retrieve data. For queries<br />

using SQL code, this typically includes declaring and opening a cursor. The<br />

querynameExecute method returns status information and uses a %Binary variable passed<br />

by reference as its first argument. This argument passes information between the query<br />

methods and contains all information needed by the various query methods.<br />

For example, if a query called ByName has no runtime parameters, the ByNameExecute<br />

method has the following signature:<br />

ClassMethod ByNameExecute(ByRef qHandle As %Binary) As %Status {<br />

// query setup code here<br />

Quit $$$OK<br />

}<br />

User-Written Class Queries<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 59


Class Queries<br />

If the query takes runtime parameters, they appear on the method argument list after the query<br />

handle. For example, if the ByName query takes a name parameter, its signature is:<br />

ClassMethod ByNameExecute(ByRef qHandle As %Binary, Name As %String)<br />

As %Status<br />

{<br />

// query setup code here<br />

Quit $$$OK<br />

}<br />

8.2.2 The querynameFetch Method<br />

The querynameFetch method fetches a single row of data and formats it into $List format.<br />

(If it is unable to fetch a row, it returns "" in lieu of data.)<br />

If you create the query using the Wizard, it creates a method signature for you. The significant<br />

elements of this signature are:<br />

• The method's first argument, the qHandle variable, is of type %Binary, is defined in<br />

querynameExecute, and is passed by reference.<br />

• The method's second argument, the Row variable, is either a %List of column values<br />

representing the current row being returned and "" if no row is returned. It is passed by<br />

reference.<br />

• The method's third argument, the AtEnd variable, is of type %Integer and indicates that<br />

subsequent calls will not return a valid row. It is passed by reference.<br />

• The method returns status information in a variable of type %Status.<br />

8.2.2.1 The First Argument — The qHandle Variable<br />

The first argument of a querynameFetch method is of type %Binary, is called qHandle, and<br />

is passed by reference. It contains all information from querynameExecute needed by<br />

querynameFetch and passes information from both methods to querynameClose.<br />

8.2.2.2 The Second Argument — The Row Variable<br />

The second argument of a querynameFetch method is of type %List, is called Row, and is<br />

passed by reference. If the querynameFetch method can retrieve the next row's data, it sets<br />

the row data variable to a list containing the active row's data. The value of each field is a<br />

single element in that list. If no more rows can be returned (that is, the query has reached the<br />

end of the rows), then the variable is set to "".<br />

60 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


The data in Row is the result of executing the query code. Usually, the data in Row is a simple<br />

transformation of a row of the result set. This, however, need not be the case. The code may<br />

perform arbitrary calculations on the result set data and return what it produces in Row. For<br />

example, a call to querynameFetch might return only the first 50 rows of the result set and<br />

convert the names to asterisks for privacy.<br />

8.2.2.3 The Third Argument — The AtEnd Variable<br />

The third argument of a querynameFetch method is of type %Integer, is called AtEnd, and<br />

is passed by reference. If AtEnd equals 1, further calls to querynameFetch will not return<br />

valid rows.<br />

For example, if the ByNameFetch method uses embedded SQL to retrieve data, SQLCODE=100<br />

indicates that no more data is available:<br />

If SQLCODE = 100<br />

{<br />

Set Row = ""<br />

Set AtEnd = 1<br />

}<br />

This is the simplest case, in which reaching the end of the result set causes the method to set<br />

AtEnd to 1. The method's code could alternately perform any processing that its application<br />

requires.<br />

8.2.2.4 Content of the querynameFetch Method<br />

The method code begins by performing tests to determine if it should return any more results.<br />

Usually, if the next fetch should occur, it uses the class query's ROWSPEC specification to<br />

perform processing; it then creates a %List object that is placed in the Row variable. If the<br />

return code from the result set indicates that no more fetches should occur, set the value of<br />

Row to ""; also, set the value of AtEnd to 1.<br />

8.2.2.5 A Sample querynameFetch Signature<br />

Continuing the ByName example, the ByNameFetch method would have the following signature:<br />

ClassMethod ByNameFetch(ByRef qHandle As %Binary,<br />

ByRef Row As %List, ByRef AtEnd As %Integer = 0)<br />

As %Status [ PlaceAfter = ByNameExecute ]<br />

{<br />

// Method code<br />

}<br />

User-Written Class Queries<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 61


Class Queries<br />

8.2.2.6 A Note on Method Compilation Order<br />

When Studio creates the querynameClose, querynameExecute, and querynameFetch<br />

methods, the querynameFetch and querynameClose methods include syntax of the form:<br />

[ PlaceAfter = querynameExecute ]<br />

The PlaceAfter method keyword ensures proper cursor declaration. The ability to control<br />

compilation order is an advanced feature that should be used with caution. <strong>InterSystems</strong> does<br />

not recommend general use of this keyword.<br />

8.2.3 The querynameClose Method<br />

The querynameClose method cleans up after data retrieval has finished. Typically, this<br />

includes removing variables from memory and closing cursors. The querynameClose method<br />

returns status information. Its sole argument is the qHandle variable of type %Binary; this is<br />

defined in querynameExecute and is passed by reference.<br />

For example, the signature of a ByNameClose method might be:<br />

ClassMethod ByNameClose(ByRef qHandle As %Binary) As %Status<br />

[ PlaceAfter = ByNameExecute ] {<br />

// query cleanup code here<br />

}<br />

62 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


9<br />

Indices<br />

Indexes provide a mechanism for optimizing searches across the instances of a persistent<br />

class; they define a specific sorted subset of commonly requested data associated with a class.<br />

They are very helpful in reducing overhead for performance-critical searches.<br />

Indexes automatically span the entire extent of the class in which they are defined. If a Person<br />

class has a subclass Student, all indexes defined in Person contain both Person objects and<br />

Student objects. Indexes defined in the Student class contain only Student objects.<br />

Indexes can be sorted on one or more properties belonging to their class. This allows you a<br />

great deal of specific control of the order in which results are returned. (See the Properties<br />

keyword, below.)<br />

In addition, indexes can store additional data that is frequently requested by queries based<br />

on the sorted properties. By including additional data as part of an index, you can greatly<br />

enhance the performance of the query that uses the index; when the query uses the index to<br />

generate its result set, it can do so without accessing the main data storage facility. (See the<br />

Data keyword below.)<br />

For additional information on indices, refer to the Index chapter within <strong>Using</strong> <strong>Caché</strong> SQL.<br />

9.1 Index Keywords<br />

You can modify an index definition through the use of one or more index keywords. Each<br />

keyword is optional and has a default value if not explicitly specified.<br />

The available index keywords include:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 63


Indices<br />

Data<br />

Specifies additional properties to store in the index. By default, no additional data is<br />

included.<br />

Description<br />

Extent<br />

Provides an optional description of the index; <strong>Caché</strong> does not use this description.<br />

By default, indexes have no description.<br />

Specifies that the index identifies which objects belong to the extent of the class.<br />

Extent indexes do not contain properties; they are strictly used to identify members<br />

of the extent. By default, an index is not an extent index. If you are using bitmap<br />

indices, there is no need to define an extent index: a bitmap extent index is automatically<br />

maintained whenever any bitmap index is defined.<br />

IDKey<br />

Specifies a property or set of properties to use as the ID portion of the OID instead<br />

of the default ID. An ID key must have unique values for each object in the class.<br />

These values cannot change over the life of the objects. By default, an index is not<br />

an ID key.<br />

PrimaryKey<br />

Properties<br />

Specifies that this index is reported as the Primary Key to SQL. Any index defined<br />

as a primary key must contain unique values for each object. By default, an index is<br />

not a primary key.<br />

Specifies the property or list of properties to include and sort upon in the index. An<br />

index must include at least one property.<br />

Type<br />

Specifies whether this is a standard index (the default) or a bitmap index.<br />

Unique<br />

Specifies that each object has a unique value for the property or combination of<br />

properties in the index. By default, an index is not constrained to unique values. You<br />

cannot use the unique keyword with a bitmap index.<br />

64 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Index Collation<br />

9.2 Index Collation<br />

Each property specified in the Attribute index keyword can also have an optional collation<br />

type. If a collation type is specified for a property, the value of that property for each instance<br />

is transformed in the specified way before the data is sorted and stored in the index. If no<br />

collation is specified then the collation value of the property is used. This value is inherited<br />

from the property's data type.<br />

If the Unique, PrimaryKey, or IDKey index keyword is specified then property collations cannot<br />

be overridden in an index. Furthermore, if IDKey is specified, the collation is always considered<br />

to be EXACT (both in the index and for comparisons and ordering within SQL, for any of<br />

the IDKey components).<br />

For example, a collation transformation, such as SQLUPPER, that performs a conversion to<br />

upper case makes searches using an index case insensitive.<br />

You can override the collation used by a specific index within the index definition:<br />

Index INDEXNAME On PROPERTYNAME As COLLATIONNAME;<br />

where<br />

• INDEXNAME is the name of index<br />

• PROPERTYNAME is the property being indexed<br />

• COLLATIONNAME is the type of collation being used for the index<br />

Different properties can have different collation types. For example, in the following example<br />

the F1 property uses SQLUPPER collation while F2 uses EXACT collation:<br />

Index Index1 On (F1 As SQLUPPER, F2 AS EXACT);<br />

<strong>InterSystems</strong> recommends the use of the following collation types:<br />

• EXACT — Performs no translation. Not recommended for use if your string data contains<br />

values in "canonical numeric" format (for example '123' or '-.57').<br />

• SQLSTRING — Strips trailing whitespace (spaces, tabs, and so on), and adds one leading<br />

blank space to the beginning of the string. It collates any value containing only whitespace<br />

(spaces, tabs, and so on) as the SQL empty string.<br />

• SQLUPPER — Converts all alphabetic characters to uppercase, strips trailing whitespace<br />

(spaces, tabs, and so on), and adds one leading blank space to the beginning of the string.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 65


Indices<br />

It collates any value containing only whitespace (spaces, tabs, and so on) as the SQL<br />

empty string.<br />

There are also a set of legacy collation types. These are not recommended for use with new<br />

code, as they are provide continued support for legacy systems. They are:<br />

• ALPHAUP — Removes all punctuation characters except question marks ( “” ) and<br />

commas ( “,” ), and translates all the lowercase letters to uppercase. Used mostly for<br />

mapping legacy globals.<br />

• MINUS — Makes the value numeric and changes its sign.<br />

• PLUS — Makes the value numeric.<br />

• SPACE — Translates the value into a string. Replaced by SQLSTRING.<br />

• STRING — Converts a logical value to upper case, strips all punctuation and white space<br />

(except for commas), and adds one leading blank space to the beginning of the string. It<br />

collates any value containing only whitespace (spaces, tabs, and so on) as the SQL empty<br />

string. Replaced by SQLUPPER.<br />

• UPPER — Translates all lower case letters into upper case letters. Used mostly for<br />

mapping legacy globals — replaced by SQLUPPER.<br />

66 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


10<br />

<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong><br />

ObjectScript<br />

This chapter is a guide to using objects within <strong>Caché</strong> ObjectScript programs. In addition to<br />

simple method execution, other relevant operations include:<br />

• Creating objects<br />

• Opening objects<br />

• Modifying objects<br />

• Saving objects<br />

• Deleting objects<br />

• Executing queries<br />

10.1 Executing Methods<br />

Most actions, including creating and modifying objects, involves methods. <strong>Caché</strong> supports<br />

two types of methods, instance methods and class methods. An instance method is invoked<br />

from a specific instance of a class and performs some action related to that instance; a class<br />

method is a method that can be invoked whether or not an object of its class is in memory.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 67


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

10.1.1 About Return Values<br />

Some methods return a value when executed; others do not. If a method has a return value<br />

and your application does not need it, you can invoke it using the syntax for methods that do<br />

not return a value. To invoke a method without checking its return value, use the Do command;<br />

to invoke a method and check use the Set command. Examples of invoking instance methods<br />

each way appear in the section Executing Instance Methods; examples of invoking class<br />

methods each way appear in the section Executing Class Methods.<br />

10.1.2 Executing Instance Methods<br />

To execute a instance method, you must first create (or open from the database) an instance<br />

of an object.<br />

To execute an instance method, use syntax similar to either of the following lines of code:<br />

Do oref.MethodName(arglist)<br />

Set value = oref.MethodName(arglist)<br />

where oref is a reference to the relevant object that is already in memory (see the section<br />

“Referring to an Object—OREFs and OIDs” in the chapter on the <strong>Caché</strong> object model for<br />

more information on OREFs), MethodName is the name of the method to execute, and arglist<br />

is the list of arguments passed to the method. value, in the second line of code, is a local<br />

variable that receives the return value of the method.<br />

For example, to execute the Admit method of a Patient object use the following code:<br />

Do pat.Admit()<br />

where pat is the OREF of the desired Patient object.<br />

Similarly, to execute the SearchMedicalHistory method of a Patient object use the following<br />

code:<br />

Set result = pat.SearchMedicalHistory("MUMPS")<br />

where pat is the OREF of the desired Patient object and result holds the value returned by<br />

SearchMedicalHistory.<br />

68 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Executing Methods<br />

10.1.3 Executing Class Methods<br />

You can execute a class method without having an object instance. You can also execute a<br />

class method from a method of the same class. In this case, you can invoke the method as<br />

you would an instance method.<br />

To execute a class method, use the following syntax:<br />

Do ##class(Classname).MethodName(arglist)<br />

Set value = ##class(Classname).MethodName(arglist)<br />

where classname is the name of the class with the relevant method,MethodName is the name<br />

of the method to execute, and arglist is the list of arguments passed to the method. value, in<br />

the second line of code, is a local variable that receives the return value of the method. The<br />

class name may contain a package name; if omitted, the class compiler determines the correct<br />

package name to use (this name resolution is explained in the Packages chapter).<br />

For example, you can execute the PurgeAll method of the ReportCard class:<br />

Do ##class(ReportCard).PurgeAll()<br />

Or you can use a fully-qualified class name:<br />

Do ##class(MyApp.ReportCard).PurgeAll()<br />

Similarly, you can execute the Exists method of the %File class (which determines if a file<br />

exists) using the following code:<br />

Set exists = ##class(%File).Exists("\temp\file.txt")<br />

where exists is a true (1) or false (0) value.<br />

10.1.4 Executing Methods <strong>Using</strong> In-Memory Instances<br />

If an instance of the class is in memory, you can execute a class or instance method using the<br />

syntax that appears in either of the following lines of code:<br />

Do oref.MethodName(arglist)<br />

Set value = oref.MethodName(arglist)<br />

where oref is the OREF of the relevant object, MethodName is the name of the method to<br />

execute, and arglist is the list of arguments passed to the method. value, in the second line<br />

of code, is a local variable that receives the return value of the method.<br />

For example, you can also execute the PurgeAll instance method of the ReportCard class<br />

using this syntax:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 69


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

Do rc.PurgeAll()<br />

where rc is the OREF of a ReportCard object.<br />

Similarly, you can execute the Exists class method of the %File class using the following<br />

code:<br />

Set exists = file.Exists("\temp\file.txt")<br />

where exists is the return value and file is the OREF of any %File object.<br />

10.1.5 Error Conditions<br />

When invoking a class or instance method, a runtime error can occur under the following<br />

conditions:<br />

• If you attempt to invoke a private method from outside of the object (that is, not from<br />

one of its other methods).<br />

• If you invoke a method that is not defined for a class or an object instance.<br />

• If you attempt to invoke a method for a class does not exist.<br />

10.2 Creating New <strong>Objects</strong><br />

The general syntax for creating new object instances is to use the class method %New:<br />

Set oref = ##class(Classname).%New()<br />

where oref is the OREF of the new object and Classname is the name of the class to create.<br />

Classname is case sensitive and may contain a package name.<br />

For example, to create a new Person object, use the following code:<br />

Set person = ##class(MyApp.Person).%New()<br />

where person is the OREF of the new Person object.<br />

You can pass an optional argument to the %New method. If present, this argument is passed<br />

to the object's %OnNew method. The use of this argument depends on the implementation<br />

of the specific class you are using.<br />

70 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Opening <strong>Objects</strong><br />

10.3 Opening <strong>Objects</strong><br />

You can open an already-existing persistent object using the following syntax:<br />

Set oref = ##class(Classname).%OpenId(id)<br />

where oref is the OREF of the opened object, Classname is its class, and id is the object<br />

identifier associated with the object you want to open. Classname is case sensitive.<br />

For example, to open a Sample.Person object with ID 22, use the following code:<br />

Set person = ##class(Sample.Person).%OpenId(22)<br />

Write person.Name,!<br />

where person is the new OREF of the Person object.<br />

Alternately, you can open an object using its full OID value:<br />

Set oref = ##class(Classname).%Open(oid)<br />

where oref is the OREF of the opened object, Classname is its class, and oid is the complete<br />

OID.<br />

Typically, an application will use the %OpenId method.<br />

Once you have opened an object, you can then view or modify its properties using methods<br />

as described in the section “Executing Methods.”<br />

10.4 Modifying <strong>Objects</strong><br />

Along with its methods, an object has properties, which define its state. Most properties hold<br />

values, such as a person's name or date of birth; there are also properties called relationships,<br />

which hold associations between objects.<br />

The general syntax for modifying the state of a property (that is, the data that defines its value)<br />

of an object is:<br />

Set oref.PropertyName = value<br />

where oref is the OREF of the specific object, PropertyName is the name of the property to<br />

associate the data with, and value is the actual data.For example, to assign a value to the<br />

Name property of a Person object, use the following code:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 71


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

Set person.Name = "Doe,Jane"<br />

where person is the OREF of the Person object and “Doe,Jane” is the name you want to<br />

assign to that person.<br />

This general syntax works for any data type property. There is special syntax, described in<br />

the sections below, for:<br />

• reference properties<br />

• embedded object properties<br />

• collections–list properties and array properties<br />

• streams<br />

10.4.1 Modifying Reference Properties<br />

The data stored in a reference property exists as an independent object that can be instantiated<br />

separately from the object containing the reference. To establish the link between the two<br />

objects, the referenced object must be associated with the other object's reference property<br />

through a <strong>Caché</strong> ObjectScript call (see below). Once this link exists, you can either modify<br />

the referenced object directly or use cascading dot syntax.<br />

10.4.1.1 Associating an Object with a Reference Property<br />

There are two ways to associate an object with a reference property: if the object is in memory,<br />

you can use its OREF, or, if it exists on disk, you can use its object identifier (ID).<br />

For a referenced object in memory, the syntax is:<br />

Set oref.PropertyName = RefOref<br />

where oref is the OREF of the specific object, PropertyName is the name of the property to<br />

associate the data with, and RefOref is OREF of the referenced object. For example, to assign<br />

a particular Person object as the owner in a Car object, use the following code:<br />

Set car.Owner = person<br />

where car is the OREF of the Car object and person is the OREF of the Person object.<br />

If the referenced object has been stored to disk and has an ID value, you can use the following<br />

syntax:<br />

Do oref.PropertyNameSetObjectId(RefId)<br />

72 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


where oref is the OREF of the specific object, PropertyName is the name of the property to<br />

associate the data with, and RefId is the ID of the referenced object. For every reference<br />

property there are SetObject (using OID value) and SetObjectId (using ID value) methods.<br />

For example, to assign a particular saved Person object as the owner in a Car object, use the<br />

following code:<br />

Do car.OwnerSetObjectId(PersonId)<br />

where car is the OREF of the Car object and PersonId is the ID of the saved Person object.<br />

10.4.1.2 Modifying Referenced <strong>Objects</strong> <strong>Using</strong> Cascading Dot Syntax<br />

Once an object is associated with the reference, the properties within that object can be populated<br />

or modified using cascading dot syntax as follows:<br />

Set oref.PropertyName.RefPropertyName = value<br />

where oref is the OREF of the specific object to associate the data with, PropertyName is the<br />

name of the reference property, RefPropertyName is the name of a data type property within<br />

the referenced object to associate the data with, and value is the actual data. For example, to<br />

set the value of the Name property of a Car object' s Owner property, use the following code:<br />

Set car.Owner.Name = "Doe,Jane"<br />

where car is the OREF of the Car object and “Doe,Jane” is the desired name.<br />

If any of the values referred to within a cascading dot syntax expression is null (""), then the<br />

entire expression evaluates to null. For example:<br />

Set car.Owner = ""<br />

Write car.Owner.Name // prints ""<br />

Modifying <strong>Objects</strong><br />

10.4.2 Modifying Embedded Object Properties<br />

The data stored in an embedded object property exists as part of a separate object in memory<br />

but can only be stored as data embedded within a separate, persistent object. There are two<br />

ways to modifying an embedded object property:<br />

• Instantiate a new instance of the embedded object and associate that instance with the<br />

embedded object property.<br />

• Directly create and populate the embedded object property using cascading dot syntax.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 73


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

10.4.2.1 Associating an Object with an Embedded Object Property<br />

One way to create an embedded object and populate its properties is to:<br />

1. Instantiate a new instance of the embedded object using the class' %New method.<br />

2. Associate the embedded object's OREF with a container object's property using the following<br />

syntax:<br />

Set oref.PropertyName = EmbeddedOref<br />

where oref is the OREF of the specific object to associate the data with, PropertyName<br />

is the name of the embedded object property, and EmbeddedOref is the OREF of the<br />

object to embed.<br />

3. Populate its properties using the general syntax for setting values.<br />

Note:<br />

Note that you can populate any of an object's properties before associating it with<br />

its container object.<br />

To instantiate a new instance of the embedded object, use its class' method for creating a new<br />

method. For classes that inherit from %Persistent, this is a %New class method. For example,<br />

to create a new Address object, use the following command:<br />

Set address = ##class(Address).%New()<br />

You can then associate it with the Home property of a Person object, use the following code:<br />

Set person.Home = address<br />

where address is the OREF of the new Address object and person is the OREF of the Person<br />

object.<br />

Setting the values for the embedded object's properties then requires only the basic syntax,<br />

such as:<br />

Set person.Home.State = "MA"<br />

10.4.2.2 Modifying Embedded <strong>Objects</strong> <strong>Using</strong> Cascading Dot Syntax<br />

You can also set the properties of the embedded object without explicitly instantiating it:<br />

Set oref.PropertyName.EmbPropertyName = value<br />

where oref is the OREF of the specific object to associate the data with,PropertyName is the<br />

name of the embedded object property,EmbPropertyName is the name of the property within<br />

the embedded object to associate the data with, and value is the actual data.<br />

74 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


For example, to populate the Home property of a Person object use the following code:<br />

Set person.Home.Street = "One Memorial Drive"<br />

Set person.Home.City = "Cambridge"<br />

Set person.Home.State = "MA"<br />

Set person.Home.Zip = 02142<br />

Modifying <strong>Objects</strong><br />

where person is the OREF of the Person object and their home address is “One Memorial<br />

Drive, Cambridge, MA 02142” .<br />

Note:<br />

Unlike reference properties, embedded object properties do not have to be associated<br />

with an object before using cascading dot syntax to set property values. This code<br />

works even if no object has been associated with the embedded object property.<br />

10.4.3 Modifying List Properties<br />

Lists are ordered collections of information. Each list element is identified by its position<br />

(slot) in the list. You can set the value of a slot's data or insert data at a slot. If you set a new<br />

value for a slot, that value is stored in the list. If you set the value for an already-existing slot,<br />

the new data overwrites the previous data and the slot assignments are not modified. If you<br />

insert data at an already-existing slot, the new list item increments the slot number of all<br />

subsequent slots. (Inserting a new item in the second slot slides the data currently in the second<br />

slot to the third slot, the object currently in the third slot to the fourth slot, and so on.)<br />

There are two types of list properties: lists of data types and lists of objects. Lists of objects<br />

can contain either embedded or persistent objects. These lists are populated in similar but<br />

slightly different ways.<br />

10.4.3.1 Populating Lists of Data Types<br />

You can add data to the end of the list using the following syntax:<br />

Do oref.PropertyName.Insert(data)<br />

where oref is the OREF of the specific object to associate the data with, PropertyName is the<br />

name of the list property, and data is the actual data. For example, you can add the value<br />

“yellow” to the end of a list of a person's favorite colors by using the following code:<br />

Do person.FavoriteColors.Insert("yellow")<br />

where person is the OREF of the Person object.<br />

You can modify data at slot n using the following syntax:<br />

Do oref.PropertyName.SetAt(data,n)<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 75


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

where oref is the OREF of the specific object to associate the data with,PropertyName is the<br />

name of the list property, and data is the actual data. For example, you can change a person's<br />

favorite colors from “red” , “blue” , and “green” to “red” , “yellow” , and “green” using<br />

the following code:<br />

Do person.FavoriteColors.SetAt("yellow",2)<br />

where person is the OREF of the Person object.<br />

You can insert data into slot n using the following syntax:<br />

Do oref.PropertyName.InsertAt(data,n)<br />

where oref is the OREF of the specific object to associate the data with,PropertyName is the<br />

name of the list property, and data is the actual data. Again, insert a new list item increments<br />

the value of all previously-existing list items. For example, you can change a person's favorite<br />

colors from “red” , “blue” , and “green” to “red” , “yellow” , “blue” , and “green” using<br />

the following code:<br />

Do person.FavoriteColors.InsertAt("yellow",2)<br />

where person is the OREF of the Person object.<br />

10.4.3.2 Populating Lists of Embedded <strong>Objects</strong><br />

You can populate a list of objects in several ways. You can create a new object, populate it,<br />

then add it to the end of the list using the following syntax:<br />

Do oref.PropertyName.Insert(ItemOref)<br />

where oref is the OREF of the specific object to associate the data with, PropertyName is the<br />

name of the list property, and ItemOref is the OREF of the object. For example, you can add<br />

a new vaccination record to a Patient object using the following code:<br />

Do pat.Vaccination.Insert(vac)<br />

where pat is the OREF of the Patient object and vac is the OREF of the Vaccination object.<br />

You can create a new object, populate it, then change an existing slot n using the following<br />

syntax:<br />

Do oref.PropertyName.SetAt(ItemOref,n)<br />

where oref is the OREF of the specific object to associate the data with, PropertyName is the<br />

name of the list property, and ItemOref is the OREF of the object. For example, you can<br />

76 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


change the second vaccination record associated with a Patient object using the following<br />

code:<br />

Do pat.Vaccination.SetAt(vac,2)<br />

where pat is the OREF of the Patient object and vac is the OREF of the new Vaccination<br />

object for slot 2.<br />

You can create a new object, populate it, then insert it into slotnusing the following syntax:<br />

Do oref.PropertyName.InsertAt(ItemOref,n)<br />

where oref is the OREF of the specific object to associate the data with,PropertyName is the<br />

name of the list property, and ItemOref is the OREF of the object. Again, insert a new list<br />

item increments the value of all previously-existing list items. For example, you can add a<br />

new vaccination record in the third slot of a Vaccination list of a Patient object using the<br />

following code:<br />

Do pat.Vaccination.InsertAt(vac,3)<br />

where pat is the OREF of the Patient object and vac is the OREF of the Vaccination object.<br />

10.4.3.3 Populating Lists of Persistent <strong>Objects</strong><br />

All of the syntax described for populating lists of embedded objects also applies for lists of<br />

persistent objects if the objects are in memory. In addition, you can populate lists of persistent<br />

objects with objects that are not in memory.<br />

You can populate a list of objects in several ways. You can add an object to the end of the<br />

list using the following syntax:<br />

Do oref.PropertyName.InsertObject(itemoid)<br />

where oref is the OREF of the specific object to associate the data with,PropertyName is the<br />

name of the list property, and itemoid is the OID of the object. For example, you can add a<br />

new dog to the list of pets owned by a person using the following code:<br />

Do per.Pets.InsertObject(DogOid)<br />

where per is the OREF of the Person object and DogOid is the OID of the Dog object.<br />

You can add an object to slot n using the following syntax:<br />

Do oref.PropertyName.SetObjectAt(ItemOid,n)<br />

Modifying <strong>Objects</strong><br />

where oref is the OREF of the specific object to associate the data with, PropertyName is the<br />

name of the list property, and ItemOid is the OID of the object. For example, you can change<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 77


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

the pet in the third slot in the list of pets owned by a person to a new dog using the following<br />

code:<br />

Do per.Pets.SetObjectAt(DogOid,3)<br />

where per is the OREF of the Person object and DogOid is the OID of the Dog object.<br />

You can insert an object into slot n using the following syntax:<br />

Do oref.PropertyName.InsertObjectAt(ItemOid,n)<br />

where oref is the OREF of the specific object to associate the data with, PropertyName is the<br />

name of the list property, and ItemOid is the OID of the object. Again, insert a new list item<br />

increments the value of all previously-existing list items. For example, you can add a new<br />

dog to the beginning of the list of pets owned by a person using the following code:<br />

Do per.Pets.InsertObject(DogOid,1)<br />

where per is the OREF of the Person object and DogOid is the OID of the Dog object.<br />

10.4.3.4 Modifying Properties of <strong>Objects</strong> in Lists<br />

Once an object has been associated with a particular slot, you can modify its properties as<br />

follows:<br />

Set oref.PropertyName.GetAt(n).ListPropertyName = data<br />

where oref is the OREF of the object containing the list; PropertyName is the name of the<br />

list property; the GetAt method finds and returns the value of the element specified by n; n<br />

is the slot in the list containing the object to update; ListPropertyName is the property to<br />

update; and data is the actual data to associate with the property.<br />

For example, to set the name of a person' s second pet, use the following code:<br />

Set per.Pets.GetAt(2).Name = "Rover"<br />

where per is the OREF of the Person object and Rover is the name of the pet.<br />

10.4.4 Modfiying Array Properties<br />

Arrays are unordered collections of information (unlike lists, which are ordered). An array<br />

consists of one or more name-value pairs, where the name of the element serves as a key and<br />

the value is data associated with that key. There is no syntactic difference between adding a<br />

new element and changing the data contained in an existing element.<br />

78 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


There are two types of array properties: arrays of data types and arrays of objects. Arrays of<br />

objects can contain either embedded or persistent objects. These arrays are populated in<br />

similar but slightly different ways.<br />

10.4.4.1 Populating Arrays of Data Types<br />

To add an element to an array of data types use the following syntax:<br />

Do oref.PropertyName.SetAt(data,key)<br />

where oref is the OREF of the specific object to associate the data with, PropertyName is the<br />

name of the list property, data is the actual data, and key is the key associated with the new<br />

element.<br />

For example, to add a new color to an array of RGB values accessed by color name in a<br />

Palette object, use the following code:<br />

Do palette.Colors.SetAt("255,0,0","red")<br />

where palette is the OREF containing the array, Colors is the name of the array property, and<br />

“red” is the key to access the value “255,0,0” .<br />

10.4.4.2 Populating Arrays of Embedded <strong>Objects</strong><br />

To add an element to an array of embedded objects, create the new object, populate it, then<br />

use the following syntax to add the object to the array:<br />

Do oref.PropertyName.SetAt(ElementOref,key)<br />

where oref is the OREF of the specific object to associate the data with,PropertyName is the<br />

name of the list property,ElementOref is the OREF of the new element, and key is the key<br />

associated with the new element. For example, to add a vaccination record accessed by date<br />

to a Vaccination array in a Patient object, use the following code:<br />

Do pat.Vaccination.SetAt(vac,"04/08/99")<br />

where pat is the OREF of the Patient object and “04/08/99” is the key to access the Vaccination<br />

object with the OREFvac.<br />

10.4.4.3 Populating Arrays of Persistent <strong>Objects</strong><br />

Modifying <strong>Objects</strong><br />

The syntax described for populating arrays of embedded objects also applies to arrays of<br />

persistent objects, if the objects are already in memory. In addition, you can populate arrays<br />

of persistent objects with objects that are not in memory. Use the following syntax to add the<br />

object to the array:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 79


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

Do oref.PropertyName.SetObjectAt(ElementOid,key)<br />

where oref is the OREF of the specific object to associate the data with, PropertyName is the<br />

name of the list property, ElementOid is the OID of the new element, and key is the key<br />

associated with the new element. For example, to add a dog accessed by name to an array of<br />

pets in a Person object, use the following code:<br />

Do per.Pets.SetObjectAt(DogOid,"Rover")<br />

where per is the OREF of the Person object and “Rover” is the key used to access the dog<br />

with the OIDDogOid.<br />

10.4.4.4 Modifying Properties of <strong>Objects</strong> in Arrays<br />

Once an object has been added to an array, you can modify its properties as follows:<br />

Set oref.PropertyName.GetAt(key).ArrayPropertyName = data<br />

where oref is the OREF of the object containing the list, PropertyName is the name of the<br />

array property, key identifies the object to update, ArrayPropertyName is the property to<br />

update, and data is the actual data associated with the property. For example, to set the type<br />

of Vaccination given to a patient on 02/23/98, use the following code:<br />

Set pat.Vaccination.GetAt("02/23/98").Type = "Polio"<br />

where pat is the OREF of the Patient object given a Polio vaccination on 2/23/98.<br />

10.4.5 Modifying Stream Properties<br />

To populate a stream property, use the following syntax:<br />

Do oref.PropertyName.Write(data)<br />

where oref is the OREF of the object containing the stream, PropertyName is the name of<br />

the stream property, Write is a method of the Stream class, and data is the actual data associated<br />

with the property. For example, to add a paragraph to a Note property of a Visit object,<br />

use the following code:<br />

Do visit.Note.Write(note)<br />

where visit is the OREF of the Visit object and note is the data entered by the doctor at the<br />

time of the visit.<br />

80 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Saving <strong>Objects</strong><br />

10.5 Saving <strong>Objects</strong><br />

You can save any persistent object using the following syntax:<br />

Do oref.%Save()<br />

where oref is the object reference of the object to save.<br />

The %Save method returns a status code which you can examine to determine the success<br />

or failure of the save:<br />

Set sc=oref.%Save()<br />

where sc is the status code returned by %Save and oref is the object reference of the object<br />

to save.<br />

The $$$ISOK macro returns 1 if the save was successful and 0 if it failed. Alternately, the<br />

$$$ISERR macro returns 1 if the save failed and 0 if the save was successful.<br />

If the save failed, the DisplayError^%apiOBJ call in the <strong>Caché</strong> Object Utility Library displays<br />

the text of the error message.<br />

The following syntax saves the object represented by oref and display any error messages if<br />

the save fails:<br />

Set sc = oref.%Save()<br />

If $$$ISERR(sc) {<br />

Do $System.Status.DisplayError(sc)<br />

}<br />

For example, the following code saves a Person object with OREFper and displays any errors<br />

occurring during the save:<br />

Set sc = per.%Save()<br />

If $$$ISERR(sc) {<br />

Do $System.Status.DisplayError(sc)<br />

}<br />

10.6 Deleting <strong>Objects</strong><br />

You can either delete a single object or all objects within an extent.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 81


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

10.6.1 Deleting a Single Object<br />

To delete an object from the database, use the %DeleteId method, which gives you the option<br />

of preserving error information generated during the deletion:<br />

Do ##class(Classname).%DeleteId(id)<br />

Set sc = ##class(Classname).%DeleteId(id)<br />

where classname is the class of the object to delete and id is the ID of the object to delete.<br />

sc, in the second line of code, is a local variable where the method can return a status code<br />

containing error information. For example, to delete an instance of Sample.Person object<br />

from disk, use the following code:<br />

Set sc = ##class(Sample.Person).%DeleteId(id)<br />

where id is the ID of the Sample.Person object to delete and sc is the variable for status/error<br />

information.<br />

Alternately, you can delete an object using its OID using either syntax below:<br />

Do ##class(Classname).%Delete(oid)<br />

Set sc = ##class(Classname).%Delete(oid)<br />

where classname is the class of the object to delete, oid is the OID of the object to delete,<br />

and sc receives a status code.<br />

For information on IDs, OIDs, and OREFs, see the section “Referring to an Object — OREF,<br />

OID, and ID” in the chapter “The <strong>Caché</strong> Object Model.”<br />

Note:<br />

Deleting an object does not remove any instances of that object from memory. If an<br />

object in memory is deleted, the last saved version is removed from the disk. The<br />

object is not permanently destroyed until the in-memory version is closed without<br />

being saved.<br />

10.6.2 Deleting All <strong>Objects</strong> in an Extent<br />

You can delete all instances of a class and its subclasses using the %DeleteExtent method,<br />

which gives you the option of preserving error information generated during the deletion:<br />

Do ##class(Classname).%DeleteExtent()<br />

Set sc = ##class(Classname).%DeleteExtent()<br />

where Classname is the name of the root class of the extent to delete. sc, in the second line<br />

of code, is a local variable where the method can return a status code containing error infor-<br />

82 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


mation. Again, this method deletes all the instances of the specified class, as well as all<br />

instances of its subclasses.<br />

For example, to delete all Person objects (including all instances of subclasses of Person)<br />

use the following code:<br />

Do ##class(Person).%DeleteExtent()<br />

The %DeleteExtent method will execute any %OnDelete callback methods, if present, for<br />

each object it deletes.<br />

You can also delete all instances of a class and its subclasses using the faster, but dangerous,<br />

%KillExtent method:<br />

Do ##class(Classname).%KillExtent()<br />

Executing Queries<br />

The %KillExtent method immediate kills all data associated with a class extent; no callbacks<br />

are executed and no referential actions are performed. You should only use the %KillExtent<br />

method on developmental systems on which you have no real data.<br />

10.7 Executing Queries<br />

<strong>Caché</strong> provides %ResultSet objects for executing queries and processing the results.<br />

This involves several steps:<br />

1. Preparing the query<br />

2. Executing the query<br />

3. Processing the query results<br />

4. Closing the query<br />

10.7.1 Query Metadata Methods<br />

To use a query, associate it with a %ResultSet object and you can then manipulate in a number<br />

of ways. The methods available for these operations are:<br />

• ContainsId checks if the query returns object ID values. It returns an integer value<br />

specifying the column number of the ID, if there is one; otherwise, it returns 0.<br />

• GetParamCount returns an integer specifying the number of arguments that the query<br />

takes.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 83


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

• GetParamName(n) returns a string specifying the name of the nth argument of the query.<br />

• GetColumnCount returns an integer specifying the number of columns that the query<br />

returns.<br />

• GetColumnName(n) returns a string specifying the name of the nth column (field).<br />

• GetColumnHeading(n) returns a string specifying the heading of the nth column (field).<br />

10.7.2 Preparing Queries for Execution<br />

To run a pre-defined class query, begin by instantiating a %ResultSet object as follows:<br />

Set rset = ##class(%ResultSet).%New()<br />

where rset is the OREF of the new %ResultSet object.<br />

Having instantiated the %ResultSet object, the next task is to associate it with a particular<br />

query in a particular class:<br />

Set rset.ClassName = class<br />

Set rset.QueryName = query<br />

where rset is the OREF of the %ResultSet object, class is the name of the class containing<br />

the query, and query is the name of the query to execute.<br />

For example:<br />

Set rset = ##class(%ResultSet).%New()<br />

Set rset.ClassName = "Sample.Person"<br />

Set rset.QueryName = "ByName"<br />

Do rset.Execute()<br />

While (rset.Next(.sc)) // go to the next row of the result set<br />

{<br />

If ($SYSTEM.Status.IsOK(sc)) // check if this succeeded without errors<br />

{<br />

Write rset.Data("Name"),! // perform busines logic<br />

}<br />

Else // if there was an error, break out of the While loop<br />

{<br />

Quit<br />

}<br />

}<br />

If $SYSTEM.Status.IsError(sc) // if there was an error, process that<br />

{<br />

// perform error processing<br />

}<br />

You can also use the %ResultSet object to prepare a dynamic SQL statement using the Prepare<br />

method:<br />

84 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Set rset = ##class(%ResultSet).%New()<br />

Do rset.Prepare("SELECT Name FROM Sample.Person WHERE Name %STARTSWITH 'A'")<br />

Do rset.Execute()<br />

While (rset.Next(.sc)) // go to the next row of the result set<br />

{<br />

If ($SYSTEM.Status.IsOK(sc)) // check if this succeeded without errors<br />

{<br />

Write rset.Data("Name"),! // perform busines logic<br />

}<br />

Else // if there was an error, break out of the While loop<br />

{<br />

Quit<br />

}<br />

}<br />

If $SYSTEM.Status.IsError(sc) // if there was an error, process that<br />

{<br />

// perform error processing<br />

}<br />

Executing Queries<br />

10.7.3 Executing Queries<br />

To execute a query, use the following syntax:<br />

Do rset.Execute(arglist)<br />

where rset is the OREF of the %ResultSet object and arglist is a comma-delimited list of<br />

arguments being passed to the query.<br />

10.7.4 Processing Query Results<br />

To access the first row of data, use the following syntax:<br />

Do rset.Next()<br />

where rset is the OREF of the %ResultSet object.<br />

The Next method advances the Result Set cursor to the next row of the Result Set. It returns<br />

0 when the cursor reaches the end of the Result Set (or if there is no data within the Result<br />

Set). The following code checks if the result set has data and exits the method if it has no<br />

data:<br />

If ('rset.Next()) {<br />

Quit<br />

}<br />

You can retrieve a column value for the current row using the Data property of the %ResultSet<br />

object. Data is a multidimensional property subscripted by column name (You must make<br />

sure that your query does not define two columns with the same name):<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 85


<strong>Using</strong> <strong>Objects</strong> with <strong>Caché</strong> ObjectScript<br />

Set code = rset.Data("Code")<br />

You can also read data stored in the current row by specifying the column number as follows:<br />

Set data = rset.GetData(n)<br />

where data contains the data stored in column n.<br />

Alternately, you can read data for a specific column name by using the GetDataByName<br />

method:<br />

Set data = rset.GetDataByName(fieldname)<br />

where data contains the data in the column named fieldname. This is less efficient than using<br />

the Data property as a method call is slower than a property reference.<br />

Once all of the relevant data has been retrieved from one row, execute the Next method to<br />

move to the next row of data. You can progress through each row, using GetData or<br />

GetDataByName to retrieve the relevant data.<br />

10.7.5 Closing Queries<br />

Once you have processed all a result set's rows, close it as follows:<br />

Do rset.Close()<br />

Set sc = rset.Close()<br />

sc, in the second line of code, is a local variable where the method can return a status code<br />

containing error information.<br />

The Close method releases any resources held by the %ResultSet object. Once the result set<br />

is closed, you can use the %ResultSet object to run and process other queries by changing<br />

the value of the ClassName and QueryName properties.<br />

Calling the Close method is optional; when the %ResultSet object is destroyed (goes out of<br />

scope) it will be automatically closed.<br />

10.7.6 Example of <strong>Using</strong> a Class Query<br />

The following code shows how to use a %ResultSet object to execute a class query and process<br />

the results:<br />

86 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Executing Queries<br />

// Create a Result object for the Sample.Person:ByName query<br />

Set rset = ##class(%ResultSet).%New("Sample.Person:ByName")<br />

Set columns = rset.GetColumnCount()<br />

// Execute the query<br />

Set sc = rset.Execute("A")<br />

// Now fetch the results<br />

While (rset.Next()) {<br />

Write "------------------------",!<br />

// loop over columns<br />

For col = 1:1:columns {<br />

Write rset.GetColumnName(col),": "<br />

Write rset.GetData(col),!<br />

}<br />

}<br />

Do rset.Close()<br />

You can also use the ActiveX and Java versions of the %ResultSet object to perform the same<br />

operation from an ActiveX or Java applications.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 87


11<br />

Data Types<br />

<strong>Caché</strong> supports a number of data types for variables, where each data type is itself a class.<br />

Each data type class represents a specific literal type, such as a string or integer. These data<br />

type classes determine the behavior of both object properties and fields in relational tables.<br />

<strong>Caché</strong> also allows you to create your own data types.<br />

Data type classes provide the following features:<br />

• They provide for SQL, ODBC, ActiveX, and Java interoperability by providing SQL<br />

logical operation, client data type, and translation information.<br />

• They provide validation for literal data values, which you can extend or customize by<br />

using data type class parameters.<br />

• They manage the translation of literal data for its stored (on disk), logical (in memory),<br />

and display formats.<br />

Data type classes differ from other classes in a number of ways:<br />

• They cannot be instantiated or stored independently.<br />

• They cannot contain properties.<br />

• They support a specific set of methods (called the data type interface), which is described<br />

below.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 89


Data Types<br />

11.1 Available Types<br />

<strong>Caché</strong> provides a library of the most common data types including strings, integer, floats,<br />

timestamps and so on. These are:<br />

<strong>Caché</strong> Data Type Classes<br />

Class Name<br />

%Binary<br />

%Boolean<br />

%Currency<br />

%Date<br />

%Float<br />

%Integer<br />

%List<br />

%Name<br />

%Numeric<br />

%Status<br />

%String<br />

%Time<br />

%TimeStamp<br />

Holds<br />

binary data<br />

a boolean value<br />

a currency value<br />

a date<br />

a floating point value<br />

an integer<br />

data in $List format<br />

a name in the form “<br />

Lastname,Firstname”<br />

a numeric values of<br />

varying precision<br />

an error status code<br />

a string<br />

a time value<br />

a value for a time and<br />

date<br />

Analogous SQL Type(s)<br />

BINARY, BINARY VARYING, RAW, VBINARY<br />

N/A<br />

MONEY, SMALLMONEY<br />

DATE<br />

DOUBLE, DOUBLE PRECISION, FLOAT,<br />

REAL<br />

BIT, INT, INTEGER, SMALLINT, TINYINT<br />

N/A<br />

N/A<br />

DEC, DECIMAL, NUMBER, NUMERIC<br />

N/A<br />

CHAR, CHAR VARYING, CHARACTER,<br />

CHARACTER VARYING, NATIONAL CHAR,<br />

NATIONAL CHAR VARYING, NATIONAL<br />

CHARACTER, NATIONAL CHARACTER<br />

VARYING, NATIONAL VARCHAR, NCHAR,<br />

NVARCHAR, VARCHAR, VARCHAR2<br />

TIME<br />

TIMESTAMP<br />

90 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Operation<br />

11.2 Operation<br />

This section describes the basic features and functionality of <strong>Caché</strong> data types.<br />

11.2.1 <strong>Using</strong> Data Types in Classes<br />

The principal function of data type classes is for specifying the types of properties within a<br />

class. The basic definition format is:<br />

Property City As %String;<br />

Because data types are part of the %Library package, you can simply call them without<br />

explicitly mentioning the package name.<br />

11.2.1.1 Validation Functionality<br />

Any property using a system data type (or a data type derived from a system data type) supports<br />

data validation through a generated method associated with the property. The validation<br />

method has a name of the form PropertyNameIsValidDT; for instance, an Age property of<br />

type %Integer has an associated AgeIsValidDT method. The method checks the validity of<br />

a value specified for the property.<br />

11.2.2 Parameters<br />

Data type classes support various parameters. These perform various actions and vary from<br />

data type to data type. These parameters include:<br />

• COLLATION — Specifies the manner in which property values are transformed for<br />

indexing.<br />

The allowable values for collation are discussed in the SQL Introduction.<br />

• DISPLAYLIST — Used in conjunction with the VALUELIST parameter for enumerated<br />

(multiple-choice) properties. DISPLAYLIST, if not null, represents the display values<br />

for the property corresponding with the logical values listed in VALUELIST. The display<br />

values are returned by the LogicalToDisplay method.<br />

• FORMAT — Specifies the format for the data type's display value. The value of FORMAT<br />

corresponds to the formatting option of the $FNUMBER function, which performs the<br />

formatting.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 91


Data Types<br />

• INDEXSUBSCRIPTS — If present, specifies the number of subscripts used by the<br />

property in indices, using a comma as a delimiter in the property value; the %CacheStorage<br />

class uses this number. A value of 2 specifies that the first comma piece of the property<br />

value is stored as the first subscript and the second comma piece of the property value is<br />

stored as the second subscript.<br />

• MAXLEN — Specifies the maximum number of characters the string can contain<br />

• MAXVAL — Specifies the maximum allowed logical value for the data type.<br />

• MINLEN — Specifies the minimum number of characters the string can contain.<br />

• MINVAL — Specifies the minimum allowed logical value for the data type.<br />

• ODBCDELIMITER — Specifies the delimiter character used to construct a %List value<br />

when it is projected via ODBC.<br />

• PATTERN — Specifies a pattern that the string must match. The value of PATTERN<br />

must be a valid <strong>Caché</strong> pattern-matching expression.<br />

• SCALE — Specifies the number of digits following the decimal point.<br />

• TRUNCATE — Specifies whether to truncate string to MAXLEN characters, where 1<br />

is TRUE and 0 is FALSE.<br />

• VALUELIST — Used for enumerated (multiple-choice) properties. VALUELIST is<br />

either a null string (“”) or a delimiter-separated list (where the delimiter is the first character)<br />

of logical values. If a non-null value is present, then the property is restricted to<br />

values in the list, and the validation code simply checks to see if the value is in the list.<br />

• XSDTYPE — Declares the XSD type used when projecting XML Schemas.<br />

The supported parameters for each of the system data types are:<br />

Supported Parameters for System Data Type Classes<br />

Data Type Class<br />

%Binary<br />

Supported Parameters<br />

MAXLEN, MINLEN<br />

%Boolean<br />

%Currency<br />

%Date<br />

%Float<br />

DISPLAYLIST, FORMAT, MAXVAL, MINVAL, VALUELIST<br />

DISPLAYLIST, FORMAT, MAXVAL, MINVAL, VALUELIST<br />

DISPLAYLIST, FORMAT, MAXVAL, MINVAL, SCALE,<br />

VALUELIST, XSDTYPE<br />

92 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Operation<br />

Data Type Class<br />

%Integer<br />

%List<br />

%Name<br />

%Numeric<br />

Supported Parameters<br />

DISPLAYLIST, FORMAT, MAXVAL, MINVAL, VALUELIST,<br />

XSDTYPE<br />

ODBCDELIMITER<br />

COLLATION, INDEXSUBSCRIPTS, MAXLEN, XSDTYPE<br />

DISPLAYLIST, FORMAT, MAXVAL, MINVAL, SCALE,<br />

VALUELIST<br />

%Status<br />

%String<br />

%Time<br />

%TimeStamp<br />

COLLATION, DISPLAYLIST, MAXLEN, MINLEN, PATTERN,<br />

TRUNCATE, VALUELIST, XSDTYPE<br />

DISPLAYLIST, FORMAT, MAXVAL, MINVAL, VALUELIST<br />

DISPLAYLIST, MAXVAL, MINVAL, VALUELIST<br />

11.2.3 Keywords<br />

To provide interoperability with client systems, data type classes include the following class<br />

keywords:<br />

• CLIENTDATATYPE — Specifies the Java or ActiveX type used when the data type is<br />

accessed via client applications. See CLIENTDATATYPE for the default for each data<br />

type.<br />

• ODBCTYPE — Specifies the ODBC type used when the data type is accessed via ODBC.<br />

See SQL and ODBC Methods for the default for each data type.<br />

• SQLCATEGORY — Specifies the SQL Category to use for the data type when the <strong>Caché</strong><br />

SQL engine performs operations upon it. See SQL and ODBC Methods for the default<br />

for each data type.<br />

11.2.3.1 CLIENTDATATYPE<br />

To use <strong>Caché</strong> data with any client system (such as Java or ActiveX), the data needs to be in<br />

a form that the client system can understand. To do this, <strong>Caché</strong> provides the CLIENT-<br />

DATATYPE class keyword, which specifies format information for how <strong>Caché</strong> projects a<br />

property to the client.<br />

The table below contains a list of CLIENTDATATYPE values and which classes use them:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 93


Data Types<br />

CLIENTDATATYPE Values<br />

Value<br />

BINARY<br />

CURRENCY<br />

DATE<br />

DOUBLE<br />

INTEGER<br />

LIST<br />

NUMERIC<br />

VARCHAR<br />

TIME<br />

TIMESTAMP<br />

Used for<br />

%Binary (or any property requiring that there is no Unicode<br />

conversion of data)<br />

%Currency<br />

%Date<br />

%Float<br />

%Boolean, %Integer<br />

%List<br />

%Numeric<br />

%Name, %String<br />

%Time<br />

%TimeStamp<br />

11.2.3.2 SQL and ODBC Methods<br />

Data type classes include a number of methods and class keywords designed to support<br />

interoperability with the <strong>Caché</strong> SQL relational database, as well as other relational databases.<br />

The OdbcToLogical and LogicalToOdbc methods translate logical data values to and from<br />

values used by the <strong>Caché</strong> SQL ODBC Interface. The ODBC value must match the ODBC<br />

type specified by the data type class' ODBCTYPE class keyword.<br />

The ODBCTYPE class keyword specifies the ODBC data type used when a property is projected<br />

to ODBC. The definition of the data type class specifies this value. Overriding it prevents<br />

a class from working properly with ODBC.<br />

The following ODBCTYPE values are used for the <strong>Caché</strong> system data types:<br />

94 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Operation<br />

ODBCTYPE Values<br />

Value<br />

BINARY<br />

CURRENCY<br />

DATE<br />

DOUBLE<br />

INTEGER<br />

NUMERIC<br />

TIME<br />

TIMESTAMP<br />

VARCHAR<br />

<strong>Caché</strong> Data Type<br />

%Binary<br />

%Currency<br />

%Date<br />

%Float<br />

%Integer, %Boolean<br />

%Numeric<br />

%Time<br />

%TimeStamp<br />

%String, %List, %Name<br />

11.2.3.3 The SQLCATEGORY Class Keyword<br />

The SQLCATEGORY class keyword specifies the SQL Category that <strong>Caché</strong> SQL uses to<br />

perform operations on the value of data of this data type. Operations controlled by the SQL-<br />

CATEGORY include comparison operations (such as greater than, less than, or equal to);<br />

other operations may also use it. The definition of the data type class specifies this value.<br />

Overriding it prevents a class from working properly with SQL.<br />

The following SQLCATEGORY values are used for the <strong>Caché</strong> system classes:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 95


Data Types<br />

SQLCATEGORY Values<br />

Value<br />

CURRENCY<br />

DATE<br />

DOUBLE<br />

INTEGER<br />

NAME<br />

NUMERIC<br />

STRING<br />

TIME<br />

TIMESTAMP<br />

<strong>Caché</strong> Data Type<br />

%Currency<br />

%Date<br />

%Float<br />

%Integer, %Boolean<br />

%Name<br />

%Numeric<br />

%String, %Binary, %List<br />

%Time<br />

%TimeStamp<br />

11.2.4 Data Formats and Translation Methods<br />

When handling data, <strong>Caché</strong> uses a number of different formats, depending on the situation.<br />

These have various purposes — such as for displaying data in a human-readable format or<br />

for manipulating data programmatically. <strong>Caché</strong> data types automatically convert data among<br />

these formats. If you create your own data type that is a subclass of a <strong>Caché</strong> data type, any<br />

property using your data type automatically includes the methods for converting among the<br />

various formats.<br />

You only need to know about these formats and conversions between them if you are either<br />

creating a data type not based on a system data type or performing non-standard data<br />

manipulation.<br />

The formats are:<br />

• Display — The format in which data can be input and displayed. For instance, a date in<br />

the form of “April 3, 1998” or “23 November, 1977.”<br />

• Logical — The in-memory format of data, which is the format upon which operations<br />

are performed. While dates have the display format described above, their logical format<br />

is as an integer; for the sample dates above, their values in logical format are 57436 and<br />

50000, respectively.<br />

• Storage — The on-disk format of data — the format in which data is stored to the database.<br />

Typically this is identical to the Logical format.<br />

96 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


• ODBC — The format in which data can be presented via ODBC or JDBC. This format<br />

is used when data is exposed to ODBC/SQL. The available formats correspond to those<br />

defined by ODBC.<br />

If a property uses a system data type (or a data type derived from a system data type), the<br />

property automatically includes methods for translation among the various data formats.<br />

Though you do not use these methods directly, they serve as the basis for a data type's propertyspecific<br />

methods (called property methods). They include:<br />

• DisplayToLogical — converts a variety of display values into appropriate logical values.<br />

• IsValidDT — performs data validation for the value associated with a property. The<br />

method returns 1 as the first character if the value is valid and 0 as the first character if<br />

it is not valid. When validation is enabled, the IsValidDT method is invoked automatically.<br />

• LogicalToDisplay — converts a logical value to a display value.<br />

• LogicalToOdbc — converts a logical value into an ODBC value (optional).<br />

• LogicalToStorage — converts a logical value into a storage value (optional).<br />

• OdbcToLogical — converts a logical value into a storage value (optional).<br />

Enumerated Properties<br />

• StorageToLogical — converts a database storage value into a logical value (optional).<br />

11.3 Enumerated Properties<br />

Properties can support multiple choice values, also known as enumerated values. To create<br />

such a properties, there are two data type class parameters: VALUELIST and DISPLAYLIST.<br />

To specify a list of valid values for a property, use its VALUELIST parameter. The form of<br />

VALUELIST is a delimiter-separated list of logical values, where the delimiter is the first<br />

character. For instance:<br />

Property Color As %String(VALUELIST = ",red,green,blue");<br />

In this example, VALUELIST specifies that valid possible values are “red” , “green” , and<br />

“blue” , with a comma as its delimiter. Similarly,<br />

Property Color As %String(VALUELIST = " red green blue");<br />

specifies the same list, but with a space as its delimiter.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 97


Data Types<br />

The property is restricted to values in the list, and the data type validation code simply checks<br />

to see if the value is in the list. If no list is present, there are no special restrictions on values.<br />

DISPLAYLIST is an additional list that, if present, represents the corresponding display values<br />

to be returned by the property's LogicalToDisplay method.<br />

This functionality works by convention: the data type class' LogicalToDisplay and IsValidDT<br />

methods must first check for the presence of these class parameters and include code to process<br />

these lists if present.<br />

98 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


12<br />

Object Persistence<br />

One of the most important features of <strong>Caché</strong> <strong>Objects</strong> is Object Persistence: the ability to store<br />

and retrieve objects within a database.<br />

Within <strong>Caché</strong>, object persistence is automatic and built-in: you do not need to write any<br />

persistence code; you do not need to provide any object/relational “mapping” ; and you do<br />

not have to bother with middleware and database connectivity tools.<br />

Every persistent object is automatically projected as an SQL table. This is described in the<br />

<strong>Objects</strong> and SQL chapter.<br />

<strong>Caché</strong> provides two flavors of object identity that you can use identify persistent objects: ID<br />

and OID. An ID is a simple literal value (by default, an integer) that is unique within a specific<br />

extent of objects (say the set of all Person objects). An OID is more general: it also includes<br />

the object's class name and is unique across a database of objects. In general practice, an<br />

application never needs to use the OID value; the ID value is usually sufficient. Within this<br />

chapter, we will use the ID variant of the various persistence methods (such as %OpenId)<br />

for simplicity.<br />

12.1 The %Persistent Class<br />

Object persistence is provided by the %Persistent class, which defines the methods of the<br />

Persistence Interface; and the Class Compiler, which manages Schema Evolution and SQL<br />

Projection.<br />

To be persistent, two things must be true about a class definition:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 99


Object Persistence<br />

1. Its primary (first) superclass must either be %Persistent or some other persistent class,<br />

and,<br />

2. It must specify that its ClassType is persistent.<br />

For example:<br />

Class MyApp.MyClass Extends %Persistent [ClassType = persistent]<br />

{<br />

}<br />

If you omit the ClassType = persistent or if %Persistent (or a subclass) is not the first<br />

class in the superclass list, then your class will not be persistent.<br />

12.2 The Persistence Interface<br />

The %Persistent class defines a set of methods, known as the Persistence Interface, that provides<br />

the means to work with object persistence. Among other things, the Persistence Interface<br />

provides the ability to save objects to the database, load objects from the database, delete<br />

objects, and test for existence. These are described in the following sections.<br />

12.2.1 Saving <strong>Objects</strong><br />

To save a persistent object to the database, invoke its %Save method:<br />

Set obj = ##class(MyApp.MyClass).%New()<br />

Set obj.MyValue = 10<br />

Set sc = obj.%Save()<br />

The %Save method returns a %Status value that indicates whether the save operation succeeded<br />

or failed (such as if it has invalid property values or violates a uniqueness constraint).<br />

Calling %Save on an object automatically saves all modified objects that can be “reached”<br />

from the object being saved: that is, all embedded objects, collections, streams, referenced<br />

objects, and relationships involving the object are automatically saved if needed. The entire<br />

save operation is carried out as one transaction: if any object fails to save, then the entire<br />

transaction fails and rolls back (no changes are made to disk; all in-memory object values<br />

are what they were prior to calling %Save).<br />

When an object is saved for the first time, the %Save method automatically assigns it an<br />

Object ID value (the ID is generated using the $Increment function unless the class is using<br />

a user-provided Object ID based on property values using an idkey index) that is used to later<br />

100 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


find the object within the database. Once assigned, you cannot alter the Object ID value for<br />

a specific object instance (even if it is a user-provided ID).<br />

You can find the Object ID value for an object that has been saved using the %Id method:<br />

// Open person "22"<br />

Set person = ##class(Sample.Person).%OpenId(22)<br />

Write "Object ID: ",person.%Id(),!<br />

In more detail, the %Save method does the following:<br />

The Persistence Interface<br />

1. First it constructs a temporary structure known as a “SaveSet.” The SaveSet is simply<br />

a graph containing references to every object that is reachable from the object being<br />

saved. The purpose of the SaveSet is to make sure that save operations involving complex<br />

sets of related objects are handled as efficiently as possible. The SaveSet also resolves<br />

any save order dependencies among objects.<br />

As each object is added to the SaveSet, its %OnAddToSaveSet callback method is<br />

called, if present.<br />

2. It then visits each object in the SaveSet in order and checks if they are modified (that is,<br />

if any of their property values have been modified since the object was opened or last<br />

saved). If an object has been modified, it will then be saved.<br />

3. Before being saved, each modified object is validated (its property values are tested; its<br />

%OnValidateObject method, if present, is called; and uniqueness constraints are tested);<br />

if the object is valid, the save proceeds. If any object is invalid, then the call to %Save<br />

fails and the current transaction is rolled back.<br />

4. Before and after saving each object, the %OnBeforeSave and %OnAfterSave callback<br />

methods are called, if present.<br />

These callbacks are passed an Insert argument which indicates whether an object is being<br />

inserted (saved for the first time) or updated.<br />

If either of these callback methods fails (returns an error code) then the call to %Save<br />

fails and the current transaction is rolled back.<br />

If the current object is not modified, then %Save does not write it to disk; it returns success<br />

because the object did not need to be saved and, therefore, there is no way that there could<br />

have been a failure to save it. In fact, the return value of %Save indicates that the save<br />

operation either did all that it was asked or it was unable to do as it was asked (and not<br />

specifically whether or not anything was written to disk).<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 101


Object Persistence<br />

Note:<br />

In a multiprocess environment, an application with improper concurrency controls<br />

can use %Save in a way that results in unexpected behavior. For more information,<br />

see the appendix “Object Concurrency Options.”<br />

12.2.1.1 Saving <strong>Objects</strong> and Transactions<br />

The %Save method automatically saves all the objects in its SaveSet as a single transaction.<br />

If any of these objects fail to save, then the entire transaction is rolled back.<br />

If you wish to save two or more unrelated objects as a single transaction, then you must<br />

enclose the calls to %Save within an explicit transaction: that is, you must start the transaction<br />

using the TSTART command and end it with the TCOMMIT command.<br />

For example:<br />

// start a transaction<br />

TSTART<br />

// save first object<br />

Set sc = obj1.%Save()<br />

// save second object (if first was save)<br />

If ($$$ISOK(sc)) {<br />

Set sc = obj2.%Save()<br />

}<br />

// if both saves are ok, commit the transaction<br />

If ($$$ISOK(sc)) {<br />

TCOMMIT<br />

}<br />

There are two things to note about this example:<br />

1. The %Save method knows if it being called within an enclosing transaction (because<br />

the system variable, $TLEVEL, will be greater than 0).<br />

2. If any of the %Save methods within the transaction fails, the entire transaction is rolled<br />

back (the TROLLBACK command is invoked). This means that an application must test<br />

every call to %Save within a explicit transaction and if one fails, skip calling %Save on<br />

the other objects and skip invoking the final TCOMMIT command.<br />

If you are saving objects from Java, and wish to save multiple objects within the same transaction<br />

(as in the above example) you can use the transactionStart and transactionCommit<br />

methods of the Java Database class.<br />

12.2.1.2 READONLY <strong>Objects</strong><br />

It is possible to define persistent objects that can be opened but not saved or deleted. To do<br />

this set the READONLY parameter for the class to 1:<br />

102 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


The Persistence Interface<br />

Parameter READONLY = 1;<br />

This is only useful for cases where you have objects that are somehow mapped to preexisting<br />

storage (such as existing globals or an external database). If you call the %Save method on<br />

a READONLY object, it will always return an error code.<br />

12.2.2 Opening <strong>Objects</strong><br />

You can open (load an object instance from disk into memory) an object using the %OpenId<br />

method. The %OpenId method takes an ID value as input and returns a reference (OREF)<br />

to an in-memory object or a null value ("") if it cannot find or otherwise open the object.<br />

For example:<br />

// Open person "10"<br />

Set person = ##class(Sample.Person).%OpenId(10)<br />

Write "Person: ",person,!<br />

// should be an object reference<br />

// Open person "-10"<br />

Set person = ##class(Sample.Person).%OpenId(-10)<br />

Write "Person: ",person,!<br />

// should be a null string<br />

Note that in <strong>Caché</strong> Basic, the OpenId command is equivalent to the %OpenId method:<br />

person = OpenId Sample.Person(1)<br />

PrintLn "Name: " & person.Name<br />

The %OpenId method takes optional second and third arguments. The second argument is<br />

the concurrency level (locking) used to open the object (see below). The third object is a<br />

%Status value, passed by reference, that indicates whether the call succeeded or failed.<br />

The %OpenId method calls the callback method %OnOpen if it is present. Note that the<br />

%OnNew callback is not called when an object instance is opened from the database.<br />

12.2.2.1 Polymorphism and %OpenId<br />

The %OpenId method behaves polymorphically — that is, it may return different types of<br />

objects depending on the ID value it is passed.<br />

For example, the extent (set of) Sample.Person objects includes instances of Sample.Person<br />

as well as instances of Sample.Employee (a subclass of Sample.Person). Calling %OpenId<br />

for the Sample.Person class could return an instance of either Sample.Person or<br />

Sample.Employee depending on what is stored within the database:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 103


Object Persistence<br />

// Open person "10"<br />

Set obj = ##class(Sample.Person).%OpenId(10)<br />

Write obj.%ClassName(),!<br />

// Sample.Person<br />

// Open person "110"<br />

Set obj = ##class(Sample.Person).%OpenId(110)<br />

Write obj.%ClassName(),!<br />

// Sample.Employee<br />

Note that the %OpenId method for the Sample.Employee class will not return an object if<br />

we try to open ID 10:<br />

// Open employee "10"<br />

Set obj = ##class(Sample.Employee).%OpenId(10)<br />

Write $IsObject(obj),! // 0<br />

// Open employee "110"<br />

Set obj = ##class(Sample.Employee).%OpenId(110)<br />

Write $IsObject(obj),! // 1<br />

This is because the %OpenId method for the Sample.Employee will only return instances<br />

of Sample.Employee (or its subclasses). In the above example, 10 refers to an instance of<br />

Sample.Person and cannot be opened as a Sample.Employee object.<br />

For more information on polymorphism and persistent objects see the section on Extents.<br />

12.2.2.2 Multiple Calls to %OpenId<br />

If %OpenId is called multiple times within a <strong>Caché</strong> process for a specific persistent object<br />

ID, only one object instance is created in memory: all subsequent calls to %OpenId will<br />

return a reference to the object already loaded into memory.<br />

This is demonstrated in the following example:<br />

' open and modify Person 1 in memory<br />

personA = OpenId Sample.Person(1)<br />

personA.Name = "Black,Jimmy Carl"<br />

' open Person 1 "again"<br />

personB = OpenId Sample.Person(1)<br />

PrintLn "NameA: " & personA.Name<br />

PrintLn "NameB: " & personB.Name<br />

12.2.2.3 Swizzling<br />

If you open (load into memory) an instance of persistent object, and use a persistent object<br />

it references, then this referenced object is automatically opened. This process is referred to<br />

as swizzling.<br />

104 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


For example, the following code opens an instance of Sample.Employee object and automatically<br />

(swizzles) opens its related Sample.Company object by referring to it using dot syntax:<br />

// Open employee "101"<br />

Set emp = ##class(Sample.Employee).%OpenId(101)<br />

// Automatically open Sample.Company by referring to it:<br />

Write "Company: ",emp.Company.Name,!<br />

When an object is swizzled, it is opened using the default concurrency value of Atomic (see<br />

next section). If you wish to change the concurrency level of a swizzled object use the<br />

%UpgradeConcurrency and %DowngradeConcurrency methods.<br />

A swizzled object is removed from memory as soon as no objects or variables refer to it.<br />

12.2.2.4 Concurrency<br />

The %OpenId method takes an optional concurrency argument as input. This argument<br />

specifies the concurrency level (type of locks) that should be used to open the object instance.<br />

For more information on the possible object concurrency levels, refer to Object Concurrency.<br />

If the %OpenId method is unable to acquire a lock on an object, it will fail.<br />

You can raise or lower the current concurrency setting for an object using the %Persistent<br />

class' %UpgradeConcurrency and %DowngradeConcurrency methods, respectively.<br />

12.2.2.5 Reloading an Object from Disk<br />

If you wish to reload an in-memory object with the values stored within the database, you<br />

can use the %Reload method provided by the %Persistent class.<br />

// Open person "1"<br />

Set person = ##class(Sample.Person).%OpenId(1)<br />

Write "Original value: ",person.Name,!<br />

// modify the object<br />

Set person.Name = "Black,Jimmy Carl"<br />

Write "Modified value: ",person.Name,!<br />

// Now reload the object from disk<br />

Do person.%Reload()<br />

Write "Reloaded value: ",person.Name,!<br />

12.2.2.6 Reading Stored Values<br />

The Persistence Interface<br />

Suppose you have opened an instance of a persistent object, modified its properties, and then<br />

wish to view the original value stored in the database before saving the object. The easiest<br />

way to do this is to use an SQL statement (SQL is always executed against the database; not<br />

against objects in memory).<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 105


Object Persistence<br />

For example:<br />

// Open person "1"<br />

Set person = ##class(Sample.Person).%OpenId(1)<br />

Write "Original value: ",person.Name,!<br />

// modify the object<br />

Set person.Name = "Black,Jimmy Carl"<br />

Write "Modified value: ",person.Name,!<br />

// Now see what value is on disk<br />

Set id = person.%Id()<br />

&sql(SELECT Name INTO :name<br />

FROM Sample.Person WHERE %ID = :id)<br />

Write "Disk value: ",name,!<br />

12.2.3 Deleting <strong>Objects</strong><br />

The Persistence Interface includes several methods for deleting objects from the database.<br />

12.2.3.1 The %DeleteId Method<br />

The %DeleteId method deletes a an object that is stored within a database:<br />

Set sc = ##class(MyApp.MyClass).%DeleteId(id)<br />

%DeleteId returns a %Status value indicating whether the object was deleted or not.<br />

If present, %DeleteId will call the %OnDelete callback method before deleting the object.<br />

%OnDelete returns a %Status value; if %OnDelete returns an error value, then the object<br />

will not be deleted, the current transaction is rolled back, and %DeleteId returns an error<br />

value.<br />

Note that the %DeleteId method is a class method and has no effect on any object instances<br />

that may be in memory.<br />

12.2.3.2 The %DeleteExtent Method<br />

The %DeleteExtent method deletes every object (and subclass of object) within its extent.<br />

Specifically it iterates the entire extent of objects and then invokes the %DeleteId method<br />

on each instance.<br />

12.2.3.3 The %KillExtent Method<br />

The %KillExtent method directly deletes the globals used to store an extent of objects. It<br />

does not invoke the %DeleteId method and performs no referential integrity actions. This<br />

method is simply intended to serve as a help to developers during the development process.<br />

106 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Object Extents<br />

(It is similar to the TRUNCATE TABLE command found in older relational database products).<br />

CAUTION:<br />

%KillExtent is intended for use only in a development environment and<br />

should not be used in a live application. %KillExtent bypasses constraints<br />

and user-implemented callbacks, potentially causing data integrity problems.<br />

12.2.4 Testing if <strong>Objects</strong> Exist<br />

There are two basic ways with which you can test if a specific object instance is stored within<br />

the database.<br />

The first way is to use the %ExistsId method provided by the %Persistent class. This is a<br />

class method that takes an ID value and returns a true value (1) if the specified object is<br />

present in the database and false (0) otherwise. For example:<br />

Write ##class(Sample.Person).%ExistsId(1),! // should be 1<br />

Write ##class(Sample.Person).%ExistsId(-1),! // should be 0<br />

The other way is to use an SQL statement and test the value of SQLCODE:<br />

&sql(SELECT %ID FROM Sample.Person WHERE %ID = '1')<br />

Write SQLCODE,! // should be 0: success<br />

&sql(SELECT %ID FROM Sample.Person WHERE %ID = '-1')<br />

Write SQLCODE,! // should be 100: not found<br />

12.3 Object Extents<br />

A set of object instances of a similar type (class) stored within the database is referred to as<br />

an extent.<br />

For a single persistent class (with no subclasses), an extent is the same as a table in relational<br />

database terms. For example, consider the persistent class MyApp.MyPerson:<br />

Class MyApp.MyPerson Extends %Persistent [classtype = persistent]<br />

{<br />

Property Name As %String;<br />

}<br />

The set of all instances of MyApp.MyPerson stored within the database is the MyApp.MyPerson<br />

extent. This is equivalent to a MyApp.MyPerson table with a Name column.<br />

Extents become more interesting when we consider subclasses. An object extent includes all<br />

instances of itself and any of its subclasses.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 107


Object Persistence<br />

If a persistent class, B, is a subclass of another persistent class, A, then all of the stored<br />

instances of B (as well as subclasses of B) belong to the superclass extent A as well as to the<br />

subextent B.<br />

The top-most class in a hierarchy of persistent classes (that is, the first superclass marked as<br />

persistent) is referred to as a root class and defines the top-level extent. The root class defines<br />

the primary storage used by all objects stored within the extent. If the root class specifies that<br />

its instances will be stored within the global ^MyApp.Data, then all subclasses in this extent<br />

are also stored there. Note that the root class of an extent can be marked as Abstract meaning<br />

that there are no actual instances of this class stored within its extent.<br />

An extent that includes objects of different subtypes is referred to as a multi-class extent.<br />

Note that as far as persistence is concerned only the primary (first) superclass of an object<br />

determines its type and what extent it belongs to.<br />

Suppose we define the following persistent objects:<br />

• MyApp.Person — a persistent class (directly derived from %Persistent).<br />

• MyApp.Teacher — a persistent class derived from MyApp.Person.<br />

• MyApp.Student — a persistent class derived from MyApp.Person.<br />

• MyApp.GradStudent — a persistent class derived from MyApp.Student.<br />

In this case, the root class is MyApp.Person as it is directly derived from %Persistent.<br />

MyApp.Person defines the top-level extent MyApp.Person.<br />

MyApp.Teacher defines the subextent MyApp.Teacher. All instances of MyApp.Teacher belong<br />

the to top-level MyApp.Person extent as well as the MyApp.Teacher subextent.<br />

Similarly, MyApp.Student defines the subextent MyApp.Student. All instances of MyApp.Student,<br />

as well as its subclass MyApp.GradStudent, belong the to top-level MyApp.Person extent as<br />

well as the MyApp.Student subextent.<br />

MyApp.GradStudent is a subclass of MyApp.Student. It defines the subextent<br />

MyApp.GradStudent. All instances of MyApp.GradStudent belong the to top-level MyApp.Person<br />

extent, the MyApp.Student subextent, and the MyApp.GradStudent subextent.<br />

12.3.1 The Extent Query<br />

Every persistent class automatically includes a class query called Extent that provides a set<br />

of all the object ID values within an object extent.<br />

108 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


For example, the following returns a set of all the object ID values for the Sample.Person<br />

class:<br />

Set rset = ##class(%ResultSet).%New("Sample.Person:Extent")<br />

Do rset.Execute()<br />

While (rset.Next()) {<br />

Write rset.Data("ID"),!<br />

}<br />

The Sample.Person extent include all instances of Sample.Person as well as its subclass<br />

Sample.Employee. If you want to only see the instances of Sample.Employee (and its subclasses)<br />

then use its Extent query:<br />

Set rset = ##class(%ResultSet).%New("Sample.Employee:Extent")<br />

Do rset.Execute()<br />

While (rset.Next()) {<br />

Write rset.Data("ID"),!<br />

}<br />

The Extent query is equivalent to the following SQL query:<br />

SELECT %ID FROM Sample.Person<br />

Storage Definitions and Storage Classes<br />

Note that you cannot rely on the order in which ID values are returned using either of these<br />

method: <strong>Caché</strong> may determine that it is more efficient to use an index that is ordered using<br />

some other property value to satisfy this request. You can add an ORDER BY %ID clause<br />

to your SQL query if you need to.<br />

12.4 Storage Definitions and Storage Classes<br />

The %Persistent class provides the high-level interface for storing and retrieving objects in<br />

the database. The actual work of storing and loading objects is performed by what is called<br />

a storage class.<br />

Every persistent (and serial) object uses a storage class to generate the actual methods used<br />

to store, load, and delete objects in a database. These internal methods are referred to the<br />

Storage Interface. The Storage Interface includes methods such as %LoadData, %SaveData,<br />

and %DeleteData. Applications never call these methods directly, instead they are called at<br />

the appropriate time by the methods of the Persistence Interface (such as %OpenId and<br />

%Save).<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 109


Object Persistence<br />

The storage class used by a persistent class is specified by a Storage Definition. A storage<br />

definition contains a set of keywords and values that define a storage class as well as additional<br />

parameters used by the storage interface.<br />

A persistent class may contain more than one storage definition but only one can be active<br />

at a time. The active storage definition is specified using the class' StorageStrategy keyword.<br />

By default, a persistent class has a single storage definition called “Default” .<br />

12.4.1 The %CacheStorage Storage Class<br />

%CacheStorage is the default storage class used by persistent objects. It automatically creates<br />

and maintains a default storage structure for a persistent class.<br />

Whenever you create a new persistent class, it will automatically use the %CacheStorage<br />

storage class. The %CacheStorage class lets you control certain aspects of the storage structure<br />

used for a class by means of the various keywords in the storage definition.<br />

Refer to the Storage Keywords section of the Class Definition Reference for details on the<br />

various storage keywords.<br />

12.4.2 The %CacheSQLStorage Storage Class<br />

The %CacheSQLStorage is a special storage class that uses generated SQL SELECT, INSERT,<br />

UPDATE, and DELETE statements to provide object persistence.<br />

The %CacheSQLStorage is typically used for:<br />

• Mapping objects to preexisting global structures used by older applications.<br />

• Storing objects within an external relational database using the SQL Gateway.<br />

The %CacheSQLStorage is more limited than %CacheStorage. Specifically, it does not<br />

automatically support Schema Evolution or multi-class extents.<br />

12.5 Schema Evolution<br />

The %CacheStorage storage class supports automatic schema evolution.<br />

When you compile a persistent (or serial) class that uses the default %CacheStorage storage<br />

class, the Class Compiler will analyze the properties defined by the class and automatically<br />

add them<br />

110 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


If you would like to see schema evolution in action, then try the following:<br />

Schema Evolution<br />

1. Start <strong>Caché</strong> Studio and create a new persistent class with one or more properties in it.<br />

2. Compile the class and then view the automatically generated storage definition (as XML<br />

text) for the class using the View Storage command on the View menu. Alternatively, you<br />

can see a more graphical representation of storage using the Class Inspector. Click on<br />

Storage in the Inspector, click in “Default” in the list of Storage definitions, click on<br />

Data Nodes in the keyword list, and click on the browse button (...) that appears. This<br />

invokes a graphical storage editor.<br />

Within the generated storage for your class, you will see the pseudo-property<br />

%%CLASSNAME. This is a placeholder for the class name of any future subclasses you<br />

may derive from your class and is used to tell the type of objects stored in the database.<br />

For the root class of an extent, this value is always empty.<br />

3. Add one or more new properties to your class and compile it again. Notice that these new<br />

properties have been added to your storage definition automatically and in way that is<br />

compatible to the previously existing storage.<br />

12.5.1 Resetting the Storage Definition<br />

During the development process, you may make many modifications to your persistent classes:<br />

adding, modifying, and deleting properties. As a result, you may end up with a fairly convoluted<br />

storage definition as the Class Compiler attempts to maintain a compatible structure<br />

after each change. If you would like to have the Class Compiler regenerate a cleaner storage<br />

structure, simply delete the storage definition for the class and recompile it.<br />

You can do this as follows:<br />

1. Open the class in <strong>Caché</strong> Studio,<br />

2. Right-click on the default Storage definition in the Class Inspector,<br />

3. Invoke the Delete command in the popup menu, and,<br />

4. Compile the class. This will cause the Class Compiler to generate a new storage definition<br />

for the class.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 111


13<br />

<strong>Objects</strong> and SQL<br />

A key aspect of <strong>Caché</strong> functionality is that it makes data visible both as objects and relational<br />

tables: data that composes a set of class instances is projected as one or more relational tables.<br />

In its relational form, this data is available for use with <strong>Caché</strong> SQL, which provides highperformance<br />

relational access to the <strong>Caché</strong> data engine.<br />

This chapter describes how objects are projected for SQL access; it assumes knowledge and<br />

experience with SQL. For more information on classes and their definition, see the chapter<br />

Classes.<br />

The interconnection of <strong>Caché</strong> SQL and <strong>Caché</strong> objects allows your application to refer to a<br />

class instantiation either as a particular object or as a part of a relational table. Generally, a<br />

class definition's relational projection is a table: instantiations are rows within the table and<br />

properties are columns within the table.<br />

Note:<br />

Certain features of a <strong>Caché</strong> class definition may not be accessible as both object and<br />

relational features, due to the nature of the object and relational models (rather than<br />

the limitations of <strong>Caché</strong> or any other tools).<br />

13.1 Inheritance and SQL<br />

Since inheritance is not part of the relational model, the Class Compiler projects a “flattened”<br />

representation of the class as a relational table. The projected table contains all the appropriate<br />

fields for the class, including those that are inherited. Hence, the projection of a subclass is<br />

a table composed of:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 113


<strong>Objects</strong> and SQL<br />

• All the columns in the projection of the superclass (that is, those based on all the properties<br />

in its superclass' extent)<br />

• Additional columns based on properties only in the subclass<br />

• A subset of the rows in the superclass' table that consists only of the instances of the<br />

subclass<br />

For example, the projection of a persistent class called Sample.Employee that is derived from<br />

the Sample.Person class is a table containing all the fields defined by both the<br />

Sample.Employee and Sample.Person classes. This is illustrated in the following SQL queries.<br />

First list all instances of Sample.Person and its properties:<br />

SELECT * FROM Sample.Person<br />

Now list all instances of Sample.Employee and its properties:<br />

SELECT * FROM Sample.Employee<br />

Typically, the table of a subclass has more columns and fewer rows than its parent. There<br />

are more columns in the subclass since it usually adds additional properties when it extends<br />

the parent class; there are often fewer rows since there are often fewer instances of the subclass<br />

than the parent.<br />

13.1.1 How a Class is Projected to SQL<br />

To create a class that is automatically accessible from SQL, define it based on the %Persistent.<br />

When you compile the class, the <strong>Caché</strong> Class Compiler automatically generates the runtime<br />

information needed for relational access. The class' instances are then available as the rows<br />

of a relational table.<br />

13.1.2 Naming Rules for Projected Classes<br />

<strong>Caché</strong> itself places no restrictions on class names. However, SQL tables cannot have names<br />

that are SQL Reserved Words; hence, if you create a persistent class with a name that is a<br />

reserved word, the <strong>Caché</strong> Class Compiler will generate an error message. In this case, you<br />

must either rename the class or specify a table name for the projection that differs from the<br />

class name; to do this, use the SQLTABLENAME keyword, which has the following syntax:<br />

SQLTABLENAME = TableNameForSQL;<br />

114 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


The Object-SQL Projection<br />

13.2 The Object-SQL Projection<br />

This section describes the manner in which the various elements of a class are projected from<br />

an object-based form to one that is SQL-based.<br />

Fundamentally, a persistent <strong>Caché</strong> class is projected to a unique SQL table, where each of<br />

its instances is projected as a row within that table. Other items are projected as listed below.<br />

The Object-SQL Projection<br />

From (Object Concept)...<br />

Package<br />

Class<br />

OID<br />

Data type property<br />

Reference property<br />

Embedded object<br />

List property<br />

Array property<br />

Stream property<br />

Index<br />

Method<br />

To (Relational Concept)...<br />

Schema<br />

Table<br />

Identity field<br />

Field<br />

Reference field<br />

Set of Fields<br />

List field<br />

Child table<br />

BLOB<br />

Index<br />

Stored procedure (class methods only)<br />

See the appropriate section below for more information on each individual topic.<br />

You can also include SQL triggers in your classes; for information on their projection, see<br />

the section on SQL Triggers below.<br />

13.2.1 Identity (OIDs Projected to SQL)<br />

Each object that inherits from %Persistent is uniquely identified by its OID (object identifier);<br />

in the projection, each instance is uniquely identified by the value of its entry in the generated<br />

identity (ID) column in the class' table. The value of the instance's entry in the identity column<br />

is the same as that of the ID portion of its OID.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 115


<strong>Objects</strong> and SQL<br />

The identity column has the name “ID” , unless the object has a property named “ID” ; in<br />

that case, the identity column has “ID1” as its name. (If there are ID and ID1 properties, the<br />

identity column has “ID2” as its name, and so on.)<br />

13.2.1.1 ID Column Constraints<br />

Though the ID appears as a column in the projection, there is no need to create an index based<br />

on it, as it is already the IDKEY for the table; further, it has no equivalent property, so an index<br />

created specifically for a class' SQL projection is not meaningful for the class itself.<br />

Additionally, you cannot perform UPDATE or INSERT operations on it, since <strong>Caché</strong> generates<br />

ID values automatically. For instance, here is the syntax to add a new instance of a<br />

class through its projection (where the ID column is created from the class OID):<br />

INSERT INTO PERSON (FNAME, LNAME)VALUES (:fname, :lname)<br />

and not:<br />

INSERT INTO PERSON (ID, FNAME, LNAME)VALUES (:id, :fname, :lname)<br />

Because the ID field is handled automatically, you do not need to refer to it explicitly.<br />

13.2.2 Properties<br />

When projected, most properties appear in a relational table as columns (fields), where the<br />

column takes its name from the property; the exception is arrays, which are projected as child<br />

tables by default.<br />

All a class' properties are projected, aside from the following exceptions:<br />

• Transient properties<br />

• Calculated properties<br />

• Private properties<br />

• Multidimensional properties<br />

For information on managing the projection of transient or calculated properties, see Transient<br />

and Calculated Properties.<br />

116 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


The Object-SQL Projection<br />

13.2.2.1 Property Names and Column Names<br />

If you want to give a property's projected column a different name, use the property keyword<br />

SQLFIELDNAME. For instance, if there is a %String property called “select,” you would define<br />

its projected name with the following syntax:<br />

Property select As %String [ SqlFieldName = selectfield ];<br />

If a property's name is an SQL reserved word, you need to specify a different name for its<br />

projection.<br />

13.2.2.2 Column Numbers for Properties<br />

<strong>Caché</strong> automatically assigns a unique column number for each property. If you wish to control<br />

column number assignments, you can specify the column for a property's projection. To do<br />

this, use the property keyword SQLCOLUMNNUMBER, as in the following example:<br />

Property RGBValue As %String [ SqlColumnNumber = 3 ];<br />

The value you specify for SQLCOLUMNNUMBER must be an integer greater than 1 (one) and<br />

specifies the property's column number in the projected table. If you use the<br />

SQLCOLUMNNUMBER keyword without an argument, <strong>Caché</strong> assigns a column number that<br />

is not preserved and that has no permanent position in the table's columns.<br />

If any property has an SQL column number specified, then <strong>Caché</strong> assigns column numbers<br />

for the other properties. The starting value for the assigned column numbers is the number<br />

following the highest SQL column number specified.<br />

The value of the SQLCOLUMNNUMBER keyword is inherited.<br />

13.2.2.3 Data Type Properties<br />

Data type properties are projected as fields using the property's SQL category (defined with<br />

the SQLCATEGORY keyword) and any of its parameters. Both relational and object access<br />

use the same data type classes, invoke the same data type methods to perform data validation<br />

and conversions, and use data type parameter values in the same way. This is true for both<br />

system-provided and user-written data type classes.<br />

For example, the following property definition:<br />

Property Name As %String(MAXLEN = 30);}<br />

is projected as a field in a relational table containing a string with a maximum length of 30<br />

characters.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 117


<strong>Objects</strong> and SQL<br />

13.2.2.4 Reference Properties<br />

Reference properties (that is, references to other persistent objects) are projected as fields<br />

containing the ID portion of the OID of the referenced object. For instance, suppose a customer<br />

object has a Rep property that refers to a SalesRep object (who is the customer' s sales representative).<br />

If a particular customer has a sales representative with an ID of 12, then the entry<br />

in the Rep column for that customer is also 12. Since this value matches that of the particular<br />

row of the ID column of the referenced object, you can use this value when performing any<br />

joins or other processing.<br />

Note that within <strong>Caché</strong> SQL you can use a special reference syntax to easily use such references<br />

without cumbersome JOIN syntax. For example:<br />

SELECT Company->Name FROM Sample.Employee ORDER BY Company->Name<br />

13.2.2.5 Embedded Object Properties<br />

An embedded object property is projected as multiple columns in the parent class' table. One<br />

column in the projection contains the entire object in serialized form (including all delimiters<br />

and control characters). The rest of the columns are each for one property of the object.<br />

The name of the column for the object property is the same as that of the object property<br />

itself. The other column names are made up of the name of the object property, an underscore,<br />

and the property within the embedded object. For instance, suppose a class has a Home<br />

property containing an embedded object of type Address; Home itself has properties that<br />

include Street and Country. The projection of the embedded object then includes the columns<br />

named “Home_Street” and “Home_Country” . (Note that the column names are derived<br />

from the property, Home, and not the type, Address.)<br />

For example, the sample class Sample.Person, includes a Home property which is an<br />

embedded object of type Sample.Address. You can use the component fields of Home via<br />

SQL as follows:<br />

SELECT Name, Home_City, Home_State FROM Sample.Person<br />

WHERE Home_City %STARTSWITH 'B'<br />

ORDER BY Home_City<br />

Embedded objects can also include other complex forms of data:<br />

• The projection of a reference property includes a read-only field that includes the object<br />

reference as described in “Reference Properties” .<br />

• The projection of an array is as a single, non-editable column that is part of the class'<br />

table.<br />

118 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


The Object-SQL Projection<br />

• The projection of a list is as a list field as one of its projected fields; the list field is as<br />

described in List Properties.<br />

13.2.2.6 Array Properties<br />

The projection of an array property is as a child table. The name of this child table is a concatenation<br />

of the name of the class containing the array property and the name of the array<br />

property itself (unless either the container class or array property have SQL names declared).<br />

For example, a Person class with an array property called Siblings has a projection as a child<br />

table called “Person_Siblings” .<br />

The child table contains the following three columns:<br />

• One contains the ID of each instance of the parent class; the name of this column is that<br />

of the class containing the array (Person, in the example). Note that this column's values<br />

are not unique to the child, but refer to the those that are unique to the parent.<br />

• One contains the identifier for each array member; its name is always element_key.<br />

• One contains array members for all the instances of the class; its name is that of array<br />

property (Siblings, in the example).<br />

Continuing the example of the Person class with an array property called Siblings, the projection<br />

of Person includes a Person_Siblings child table with the following entries:<br />

Sample Projection of an Array Property<br />

Person (ID)<br />

10<br />

10<br />

12<br />

12<br />

12<br />

12<br />

12<br />

element_key<br />

C<br />

T<br />

B<br />

C<br />

G<br />

M<br />

P<br />

Siblings<br />

Claudia<br />

Tom<br />

Bobby<br />

Cindy<br />

Greg<br />

Marsha<br />

Peter<br />

If an instance of the parent class holds an empty collection (one that contains no elements),<br />

that instance's ID does not appear in the child table, such as the instance above where ID<br />

equals 11.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 119


<strong>Objects</strong> and SQL<br />

Note:<br />

There is no Siblings column in the parent table.<br />

For the column(s) containing the array members, the number and contents of the column(s)<br />

depend on the kind of array:<br />

• The projection of an array of data type properties is a single column of data.<br />

• The projection of an array of reference properties is a single column of object references.<br />

• The projection of an array of embedded objects is as multiple columns in the child table.<br />

The structure of these columns is as is described in the section Embedded Object Properties.<br />

Together, the ID of each instance and the identifier of each array member comprise a unique<br />

index for the child table. Also, if a parent instance has no array associated with it, it has no<br />

associated entries in the child table.<br />

13.2.2.7 List Properties<br />

A list property is projected as a string in $LIST format, which means that all the elements<br />

within the collection are concatenated and projected as a single string. For example, suppose<br />

the Person class has a Cars list property, where Cars is a list of strings that are license plate<br />

numbers. This property appears as a single column called Cars and might appear in the Person<br />

projection as follows (looking at a partial view of the table):<br />

Sample Projection of a List Property<br />

ID<br />

1<br />

2<br />

3<br />

Cars<br />

324WLI,395BCE<br />

253COM<br />

512SEL,710LRR,526SJL<br />

The list field contains a string constructed by concatenating the individual elements of the<br />

list:<br />

New cars<br />

&sql(SELECT Cars INTO :cars<br />

FROM Person<br />

WHERE ID = :id)<br />

Given an id value of 1, cars has a value equivalent to a list based on the “324WLI” and “<br />

395BCE” strings.<br />

120 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


If the list for a particular instance contains no elements, it is projected as an empty string (and<br />

not an SQL NULL value).<br />

13.2.2.8 Stream Properties<br />

Both character stream properties and binary stream properties are projected as BLOBs (binary<br />

large objects).<br />

Refer to the chapter on Streams for more information.<br />

13.2.2.9 Transient and Calculated Properties<br />

Since transient and calculated properties are not stored on disk, by default, they have no SQL<br />

projection.<br />

For the projection to include these columns, use the SQLCOMPUTED keyword, which allows<br />

you to specify the code that calculates the value of an SQL computed field for the property;<br />

you can then add code to the property definition that calculates the value of the projected<br />

field. A property projected as a computed field can provide parallel object and SQL functionality<br />

through parallel computations.<br />

There are two ways to include code for a property's SQL computed field:<br />

• Write code that performs the calculations required to determine the computed field's<br />

value and associate it with the SQLCOMPUTECODE keyword.<br />

• Write code that performs the calculations required to determine the computed field's<br />

value and place it in a class method. Call this class method both from the code associated<br />

with the SQLCOMPUTECODE keyword (for the SQL projection) and from the property's<br />

Get method (for the object itself).<br />

With either approach, this code can include values based on other properties and systembased<br />

values. It can invoke class methods and user-defined functions.<br />

Two Types of Computed SQL Fields<br />

The Object-SQL Projection<br />

The kind of computed SQL field described above is only one of the two types of computed<br />

SQL fields:<br />

• Always Computed (described above) — The field value is not stored in the database, but<br />

calculated when needed upon retrieval.<br />

• Triggered Computed — The field value is stored in the database and is calculated upon<br />

INSERT/UPDATE if needed.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 121


<strong>Objects</strong> and SQL<br />

The way to determine if a field is Always Computed or Triggered Computed is by the<br />

TRANSIENT and/or CALCULATED property keywords. If the property is transient or calculated,<br />

and has SQLCOMPUTED, the compute type is Always. If the property is not transient, not<br />

calculated, and has SQLCOMPUTED, the compute type is Triggered.<br />

An Example<br />

Suppose a class has a Name property of the form last,first. You might use the value stored<br />

in this property to create the calculated fields FirstName and LastName (each containing the<br />

first and last name, respectively). To do this, the code to set the value of the computed<br />

LastName field would be:<br />

Set {LastName}=##class(MyClass).GetLastName({Name})<br />

where MyClass is the class being projected and GetLastName is a class method which has<br />

the following code in it:<br />

Quit $Piece(Name,",")<br />

This code simply returns the first element in the Name property that appears before the “,”<br />

delimiter.<br />

13.2.3 Methods<br />

A class' instance methods are only accessible via the object representation of a class, and not<br />

via SQL. This is because an SQL query does not cause objects to be instantiated their (hence,<br />

their methods are not accessible).<br />

13.2.4 SQL Triggers<br />

Because <strong>Caché</strong> SQL supports the use of triggers, any trigger associated with a class is included<br />

as part of the class' SQL projection.<br />

For more information on triggers, see the section Triggers in <strong>Using</strong> <strong>Caché</strong> SQL.<br />

Triggers are code segments executed when specific events occur in <strong>Caché</strong> SQL. <strong>Caché</strong> supports<br />

triggers based on the execution of INSERT, UPDATE, and DELETE commands. The<br />

specified code will be executed either immediately before or immediately after the relevant<br />

command is executed, depending on the trigger definition. Each event can have multiple<br />

triggers as long as they are assigned an execution order.<br />

Triggers are not fired by the persistence methods used by the default storage class,<br />

%CacheStorage.<br />

122 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Triggers are, however, fired by the persistence methods used by the legacy storage class,<br />

%CacheSQLStorage as it uses SQL statements internally to implement its persistent behavior.<br />

13.2.4.1 Trigger Keywords<br />

The Object-SQL Projection<br />

A class definition may include a list of trigger definitions. A trigger definition consists of the<br />

following information:<br />

Code<br />

Event<br />

Name<br />

Order<br />

Time<br />

The <strong>Caché</strong> ObjectScript code to be executed when the specified trigger event occurs.<br />

Triggers fully support all <strong>Caché</strong> ObjectScript functionality including embedded SQL<br />

and object syntax.<br />

The event with which this trigger is associated. Possible events include INSERT,<br />

UPDATE, DELETE. Together with the Time value, this determines when a trigger<br />

is invoked.<br />

The name of the trigger. This must be unique within the extent of the trigger.<br />

A number indicating the order in which trigger actions are to be carried out when<br />

more than one trigger is defined for a specific event. If only one trigger is associated<br />

with a specific event its order must be set to 1. If more than one trigger is associated<br />

with a specific event, each must have a unique order value.<br />

Together with the Event keyword, Time determines when a trigger is invoked. The<br />

possible values of Time are BEFORE and AFTER.<br />

13.2.5 Relationships<br />

The SQL projection of a relationship depends on its cardinality value: The single-valued side<br />

(ONE or PARENT) is projected as a simple designative reference field. For example, the<br />

Employee table will have a field called TheCompany whose value is the ID of the related<br />

Company:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 123


<strong>Objects</strong> and SQL<br />

SELECT ID,Name,Title,TheCompany->Name<br />

FROM Employee<br />

ORDER BY Name<br />

The multi-valued side is not projected as a field (Multi-valued relationships are state-less on<br />

disk and SQL does not deal with in-memory objects). Instead, you must perform a simple<br />

join based on the multi-valued side's ID value and the single-valued side's reference field:<br />

SELECT c.Name, e.Name<br />

FROM Company c, Employee e<br />

WHERE e.TheCompany = c.ID<br />

ORDER By 1,2<br />

A single-valued relationship with cardinality of ONE is projected to SQL as a FOREIGNKEY<br />

with NOACTION specified for UPDATE and DELETE.<br />

If the cardinality of the single-valued relationship is PARENT then the table projected from<br />

the class containing the relationship is “adopted” as a child table by the table projected from<br />

the type class of the single-valued relationship.<br />

124 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


14<br />

Relationships<br />

A relationship is an association between two objects, each of a specific type; to create a<br />

relationship between two objects, each must have a relationship property, which defines its<br />

half of the relationship.<br />

For example, a Company class may define a one-to-many relationship with an Employee<br />

class. In this case, there may be zero or more Employee objects associated with each Company<br />

object.<br />

<strong>Caché</strong> relationships have the following characteristics:<br />

• Relationships are binary, that is a relationship is defined either between two, and only<br />

two, classes or between a class and itself).<br />

• Relationships can only be defined for persistent classes.<br />

• Relationships must be bi-directional, that is both sides of a relationship must be defined.<br />

• Currently, <strong>Caché</strong> supports two types of relationship: one-to-many (independent) and<br />

parent-to-children (dependent). One-to-one and many-to-many relationships are not<br />

supported at this time.<br />

• Relationships automatically provide referential integrity; unlike a reference (or objectvalued)<br />

property, the value of the relationship is constrained to be correct (that is, there<br />

are no dangling references).<br />

• Relationships automatically manage their in-memory and on-disk behavior.<br />

• Relationships provide superior scaling and concurrency over object collections.<br />

• Relationships are visible to SQL as foreign keys. See the Object-SQL Projection for more<br />

information on this topic.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 125


Relationships<br />

14.1 Relationship Basics<br />

The fundamental aspects of using relationships are relationship keywords and the action of<br />

defining a relationship.<br />

14.1.1 Relationship Keywords<br />

There are three keywords or modifiers required to define a relationship property:<br />

TYPE<br />

The type (class name) of the related class. This must be a persistent class.<br />

INVERSE<br />

CARDINALITY<br />

The name of the corresponding relationship property in the related class (the “other<br />

side” of the relationship).<br />

The cardinality of this side of the relationship. This is either “ONE” , “MANY” ,<br />

“PARENT” , or “CHILDREN” — where the class' inverse has the complementary<br />

cardinality, such as MANY with ONE or PARENT with CHILDREN<br />

Because each relationship is defined using a relationship property, which is a particular kind<br />

of property, some other property keywords are available for use in them, including<br />

“DESCRIPTION” , “FINAL” , “REQUIRED” , “SQLFIELDNAME” and “PRIVATE” .<br />

Some property keywords, such as “MULTIDIMENSIONAL” , do not apply. See the Property<br />

Keywords section of the Class Definition Reference for more information.<br />

14.1.1.1 About Cardinality<br />

The value of “CARDINALITY” defines how the relationship “appears” from this side as<br />

well as whether it is an independent relationship (ONE-MANY) or dependent relationship<br />

(PARENT-CHILDREN).<br />

For example, suppose we have a Book object that is related to MANY Chapter objects:<br />

The relationship Book.Chapters has cardinality of MANY. Each Chapter is related to ONE<br />

Book so the Chapter.Book relationship has cardinality of ONE.<br />

In the case of a dependent relationship, such as an Invoice object with dependent LineItem<br />

objects,<br />

126 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Relationship Basics<br />

the relationship Invoice.Items has cardinality of CHILDREN because Items is a collection of<br />

children from the point of view of Invoice. LineItem.TheInvoice has cardinality of PARENT<br />

because TheInvoice refers to the parent of this LineItem.<br />

The cardinality value of a relationship must correspond to the cardinality value of the inverse<br />

relationship as defined in the following table:<br />

ONE<br />

MANY<br />

PARENT<br />

CHILDREN<br />

MANY<br />

ONE<br />

CHILDREN<br />

PARENT<br />

14.1.2 Defining a Relationship<br />

To create a relationship, there are a pair of complementary relationship properties. In the<br />

<strong>Caché</strong> Studio you can define a relationship property using the New Property Wizard and<br />

checking the Relationship option. See the Relationships section of the Studio documentation<br />

for details.<br />

When you define a relationship property, Studio automatically creates the inverse relationship<br />

property in the associated class.<br />

You can also define a relationship property directly in Studio. The syntax is:<br />

Relationship Name As Type<br />

[Inverse = related_class,<br />

Cardinality = cardinality_type<br />

where<br />

• “TYPE” , “INVERSE” and “CARDINALITY” are required.<br />

• related_class specifies the other half of the relationship and must be a persistent class.<br />

• cardinality_type, the value of cardinality, indicates the complementary cardinality of<br />

related_class (and can have a value of ONE, MANY, PARENT, or CHILDREN).<br />

For example, here are the definitions of two related classes, Company and Employee. Company<br />

is:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 127


Relationships<br />

Class MyApp.Company Extends %Persistent [ClassType = persistent]<br />

{<br />

Property Name As %String;<br />

/// a Company has MANY Employees<br />

Relationship Employees As Employee [Inverse = TheCompany, Cardinality = MANY];<br />

}<br />

and Employee:<br />

class MyApp.Employee Extends %Persistent [ClassType = persistent]<br />

{<br />

Property Name As %String;<br />

Property Title As %String;<br />

/// an Employee has ONE Company<br />

Relationship TheCompany As Company [inverse = Employees, cardinality = ONE];<br />

}<br />

14.2 Dependent Relationships<br />

A dependent relationship, defined by having cardinality of PARENT on one side and CHIL-<br />

DREN on the other, has the following additional characteristics:<br />

• The existence of the child objects is dependent on the parent; if a parent object is deleted<br />

then all of its children are automatically deleted.<br />

• Once associated with a particular parent object, a child object can never be associated<br />

with a different parent. This is because the persistent ID value of the child object is based<br />

in part on the parent's persistent ID.<br />

• As much as possible, instances of child objects are clustered with parent objects on disk<br />

making disk access as optimal as possible (few page accesses are required to retrieve the<br />

children for a parent).<br />

• A dependent relationship is projected to SQL as a parent-child table.<br />

• The two classes are linked at compile time, as opposed to a ONE-MANY relationship,<br />

where the classes are compiled independently. Consequently, parent/child relationships<br />

may only be defined between two different classes. The two sides of a dependent relationship<br />

cannot be defined within a single class, or within a base class and its derived class.<br />

For example, here are the definitions for two related dependent classes, Invoice and LineItem.<br />

Invoice is:<br />

128 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


An Invoice class<br />

class MyApp.Invoice Extends %Persistent [ClassType = persistent]<br />

{<br />

Property CustomerName As %String;<br />

/// an Invoice has CHILDREN that are LineItems<br />

Relationship Items As LineItem [inverse = TheInvoice, cardinality = CHILDREN];<br />

}<br />

and LineItem:<br />

/// A LineItem class<br />

class MyApp.LineItem Extends %Persistent [ClassType = persistent]<br />

{<br />

Property Product As %String;<br />

Property Quantity As %Integer;<br />

In-Memory Behavior of Relationships<br />

/// a LineItem has a PARENT that is an Invoice<br />

Relationship TheInvoice As Invoice [inverse = Items, cardinality = PARENT];<br />

}<br />

14.3 In-Memory Behavior of Relationships<br />

Programmatically, relationships behave as properties. Single-valued relationships (cardinality<br />

of “ONE” or “PARENT” ) behave like atomic (non-collection) reference properties. Multivalued<br />

relationships (cardinality of “MANY” or “CHILDREN” ) are instances of the<br />

%RelationshipObject class which has a collection-like interface.<br />

For example, you could use the Company and Employee objects defined above in the following<br />

way:<br />

// create a new instance of Company<br />

Set company = ##class(Company).%New()<br />

Set company.Name = "Chiaroscuro LLC"<br />

// create a new instance of Employee<br />

Set emp = ##class(Employee).%New()<br />

Set emp.Name = "Weiss,Melanie"<br />

Set emp.Title = "CEO"<br />

// Now associate Employee with Company<br />

Set emp.TheCompany = company<br />

// Save the Company (this will save emp as well)<br />

Do company.%Save()<br />

// Close the newly created objects<br />

Do company.%Close()<br />

Do employee.%Close()<br />

Relationships are fully bi-directional in memory; any operation on one side is immediately<br />

visible on the other side. Hence, the code above is equivalent to the following, which instead<br />

operates on the company:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 129


Relationships<br />

Do company.Employees.Insert(emp)<br />

Write emp.TheCompany.Name<br />

// this will print out "Chiaroscuro LLC"<br />

You can load relationships from disk and use them as you would any other property. When<br />

you refer to a related object from the ONE side, the related object is automatically swizzled<br />

into memory in the same way as a reference (object-valued) property. When you refer to a<br />

related object from the MANY side, the related objects are not swizzled immediately; instead<br />

a transient %RelationshipObject collection object is created. As soon as any methods are called<br />

on this collection, it builds a list containing the ID values of the objects within the relationship.<br />

It is only when you refer to one of the objects within this collection that the actual related<br />

object is swizzled into memory.<br />

Here is an example that displays all Employee objects related to a specific Company:<br />

// open an instance of Company<br />

Set company = ##class(Company).%OpenId(id)<br />

// iterate over the employees; print their names<br />

Set key = ""<br />

Do {<br />

Set employee = company.Employees.GetNext(.key)<br />

If (employee '= "") {<br />

Write employee.Name,!<br />

}<br />

} While (key '= "")<br />

In this example, closing company removes the Company object and all of its related Employee<br />

objects from memory. Note, however, that every Employee object contained in the relationship<br />

will be swizzled into memory by the time the loop completes. To reduce the amount of<br />

memory that this operation uses—perhaps there are thousands of Employee objects—then<br />

modify the loop to “unswizzle” the Employee object after displaying the name, by calling<br />

the %UnSwizzleAt method:<br />

Do {<br />

Set employee = company.Employees.GetNext(.key)<br />

If (employee '= "") {<br />

Write employee.Name,!<br />

// remove employee from memory<br />

Do company.Employees.%UnSwizzleAt(key)<br />

}<br />

} While (key '= "")<br />

130 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


14.4 Persistent Behavior of Relationships<br />

Though there are two sides to every relationship, only one side actually has its relationshiprelated<br />

information stored to disk. This is the single-valued side, the PARENT or ONE of<br />

the relationship.<br />

When you open an instance of the multi-valued side of a relationship, <strong>Caché</strong> treats its relationship<br />

property as a standard object reference; it opens and swizzles into memory the<br />

PARENT or ONE object. When you open an instance of the single-valued side of a relationship,<br />

<strong>Caché</strong> uses a query to locate and create a list of all the relevant CHILDREN or MANY<br />

instances; to improve performance of this operation, on the multi-valued side, create an index<br />

on the relationship property. This also improves performance of the Execute, Fetch, and<br />

Close methods. When defining a relationship property, Studio asks if you want such an index.<br />

14.4.1 Referential Integrity<br />

Relationships maintain referential integrity by enforcing constraints and invoking referential<br />

actions. When a relationship is saved, <strong>Caché</strong> checks for the existence of the target of its reference.<br />

If the target does not exist, then <strong>Caché</strong> returns an error and the save operation fails.<br />

When an object is deleted and there are objects related to it, the related objects are deleted<br />

(parent/child cardinality) or the delete operation fails (one/many cardinality).<br />

14.4.2 Persistent Behavior of Dependent Relationships<br />

With parent/child relationships, deleting the parent kills the children. Further, the storage of<br />

children is subordinate to that of the parent, in a structure similar to the following:<br />

^Inv(1)<br />

^Inv(1, "invoice", 1)<br />

^Inv(1, "invoice", 2)<br />

^Inv(1, "invoice", 3)<br />

...<br />

Persistent Behavior of Relationships<br />

Again, by associating the storage nodes of the CHILDREN with that of their PARENT, as<br />

is depicted above, <strong>Caché</strong> can read and write more quickly.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 131


15<br />

Streams<br />

Streams are used to create properties containing large (greater than 32K) amounts of data.<br />

<strong>Caché</strong> contains several stream classes that provide access to different data sources using a<br />

common stream interface defined by the %Stream.Object class. Streams are implemented via<br />

a family of classes that include the following members:<br />

• %Stream.Object — the base class for all stream classes.<br />

• %Library.AbstractStream — base class for all streams that can be used as object properties.<br />

• %Library.GlobalCharacterStream — class for character streams stored in global nodes.<br />

• %Library.GlobalBinaryStream — class for binary streams stored in global nodes.<br />

• %Library.FileCharacterStream — class for character streams stored in an external file.<br />

• %Library.FileBinaryStream — class for binary streams stored in an external file.<br />

• %Library.File — class for manipulating external files as streams.<br />

15.1 Declaring Stream Properties<br />

<strong>Caché</strong> supports both binary streams and character streams. Binary streams contain the same<br />

sort of data as type %Binary, and are intended for very large binary objects such as pictures.<br />

Similarly, character streams contain the same sort of data as type %String, and are intended<br />

for storing large amounts of text. Character streams, like strings, may undergo a UNICODE<br />

translation within client applications.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 133


Streams<br />

Stream data may be stored in either an external file or a <strong>Caché</strong> global, depending on how the<br />

stream property is defined. The %FileCharacterStream and %FileBinaryStream classes are<br />

used for streams stored as external files, while %GlobalCharacterStream and<br />

%GlobalBinaryStream are used for streams stored as globals. All four classes can use the<br />

optional LOCATION parameter to specify a default storage location.<br />

In the following example, the JournalEntry class contains four stream properties (one for each<br />

of the four basic stream classes), and specifies a default storage location for two of them:<br />

Class testPkg.JournalEntry Extends %Persistent<br />

{<br />

Property DailyText As %FileCharacterStream;<br />

Property DailyImage As %FileBinaryStream(LOCATION = "C:/Images");<br />

Property Text As %GlobalCharacterStream(LOCATION = "^MyText");<br />

Property Picture As %GlobalBinaryStream;<br />

}<br />

In this example, data for DailyImage will be stored in a file (with an automatically generated<br />

name) in the C:/Images directory, while the data for the Text property will be stored in a global<br />

named ^MyText.<br />

15.1.1 Projecting Streams to ODBC<br />

Streams are projected to ODBC as BLOBs. Their ODBC type is LONG VARCHAR (or<br />

LONG VARBINARY). The ODBC driver/server uses a special protocol to read/write BLOBs.<br />

Typically you have to write BLOB applications by hand, since the standard reporting tools<br />

do not support them.<br />

15.2 <strong>Using</strong> the Stream Interface<br />

All streams inherit a set of methods and properties used to manipulate the data they contain.<br />

Some commonly used methods include the following:<br />

• Read — Read a specified number of characters starting at the current position in the<br />

stream.<br />

• Write — Append data to the stream, starting at the current position. Overwrites existing<br />

data if the position is not set to the end of the stream.<br />

• Rewind — Move to the beginning of the stream.<br />

• MoveTo — Move to a given position in the stream.<br />

• MoveToEnd — Move to the end of the stream.<br />

134 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


• CopyFrom — Copy the contents of a source stream into this stream.<br />

• NewFileName — Specify a filename for a %FileCharacterStream or %FileBinaryStream<br />

property.<br />

Commonly used properties include the following:<br />

• %Location — The default storage location (directory or global) where the stream will<br />

store its persistent data. If no location is declared for a stream, the %Location for globals<br />

will default to ^ooClassNameS, and the %Location for files will default to the current<br />

<strong>Caché</strong> directory.<br />

• AtEnd — Set to true when a Read encounters the end of the data source.<br />

• Id — The unique identifier for an instance of a stream within the extent specified by<br />

%Location.<br />

• Size — The current size of the stream (in bytes or characters, depending on the type of<br />

stream).<br />

The following sections provide concrete examples using these methods and properties. For<br />

detailed information on individual Stream methods and properties, see the Class Reference<br />

entries for the classes listed at the beginning of this chapter.<br />

15.2.1 Reading and Writing Stream Data<br />

At the core of the Stream interface are the methods Read, Write, and Rewind and the properties<br />

AtEnd and Size.<br />

The following example reads data from the Person.Memo stream and writes it to the console,<br />

100 characters at a time. The value of len is passed by reference, and is reset to 100 before<br />

each Read. The Read method attempts to read the number of characters specified by len,<br />

and then sets it to the actual number of characters read:<br />

Do person.Memo.Rewind()<br />

While (person.Memo.AtEnd = 0) {<br />

Set len = 100<br />

Write person.Memo.Read(.len)<br />

}<br />

Similarly, you can write data into the stream:<br />

Do person.Memo.Write("This is some text. ")<br />

Do person.Memo.Write("This is some more text.")<br />

<strong>Using</strong> the Stream Interface<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 135


Streams<br />

15.2.2 Copying Data between Streams<br />

All streams contain a CopyFrom method which allows one stream to fill itself from another<br />

stream. This can be used, for example, to copy data from a file into a stream property:<br />

// open a text file using a %Library.File stream<br />

Set file = ##class(%File).%New("\data\textfile.txt")<br />

Do file.Open("RU") ; same flags as OPEN command--use "U" for streams<br />

// Open a Person object containing a Memo stream<br />

// and copy the file into Memo<br />

Set person = ##class(Person).%New()<br />

Do person.Memo.CopyFrom(file)<br />

// save the person object and close it, then close the file<br />

Do person.%Save()<br />

Do person.%Close()<br />

Do file.%Close()<br />

15.2.3 Inserting Stream Data<br />

Streams have both a temporary and a permanent storage location. All inserts go into the<br />

temporary storage area, which is only made permanent when you save the stream. If you start<br />

inserting into a stream, then decide that you want to abandon the insert, the data stored in the<br />

permanent location will not be altered.<br />

If you create a stream, start inserting, then do some reading you can call MoveToEnd and<br />

then continue appending to the temporary stream data. However after you save the stream,<br />

the data is moved to the permanent storage location. If you then reload the stream and start<br />

inserting, it will insert into the temporary storage area, rather than appending to the permanently<br />

stored data.<br />

If this is the behavior you want you will need to create a temporary stream, for example:<br />

Set test = ##class(Test).%OpenId(5)<br />

Set tmpstream = ##class(%GlobalCharacterStream).%New()<br />

Do tmpstream.CopyFrom(test.text)<br />

Do tmpstream.MoveToEnd()<br />

Do tmpstream.Write("append text")<br />

Set test.text = tmpstream<br />

Do tmpstream.%Close()<br />

// Now do whatever you want with the test object<br />

In this example, we create a temporary stream of the required type, then copy from the stream<br />

stored in the Test object, which will put this data in the temporary storage area of our new<br />

stream. Then we append to this stream and put its oref into the Test object and close it to keep<br />

the reference counts correct.<br />

136 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


<strong>Using</strong> Streams with SQL<br />

15.2.4 <strong>Using</strong> Streams in Object Applications<br />

Stream properties are manipulated via a transient object that is created by the object that owns<br />

the stream property. Streams act as literal values (think of them as large strings). Two object<br />

instances cannot refer to the same stream.<br />

In the following example, a long memo is created and then written to the console:<br />

// create object and stream<br />

Set p = ##class(Person).%New()<br />

Set p.Name = "Mo"<br />

Do p.Memo.Write("This is part one of a long memo")<br />

// ...<br />

Do p.Memo.Write("This is part 10000 of a long memo")<br />

Do p.%Save()<br />

Do p.%Close()<br />

// read object and stream<br />

Set p = ##class(Person).%Open(oid)<br />

Do p.Memo.Rewind() // not required first time<br />

// write contents of stream to console, 100 characters at a time<br />

While (p.Memo.AtEnd = 0) {<br />

Set len = 100<br />

Write p.Memo.Read(.len)<br />

}<br />

Do p.%Close()<br />

15.3 <strong>Using</strong> Streams with SQL<br />

Stream fields have the following restrictions within SQL:<br />

• You cannot index on a stream value.<br />

• You cannot use a stream value in a WHERE clause.<br />

• You cannot UPDATE/INSERT multiple rows containing a stream; you must do it row<br />

by row.<br />

Within embedded SQL you can read a stream as follows:<br />

• Select the stream:<br />

&sql(SELECT Memo FROM Person INTO :memo WHERE ID = 1)<br />

This will fetch the stream's ID value into memo.<br />

• Open a %Stream.Object object from this ID value:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 137


Streams<br />

Set stream=##class(%Stream.Object).%Open(memo)<br />

Since the %Open method is fully polymorphic, the actual type of stream returned by<br />

%Open depends on what type is stored with the Person object<br />

• Read and process the stream data, closing the stream when done:<br />

Write stream.Read(100)<br />

Do stream.%Close()<br />

Writing a stream is similar. There are several ways to do this. In the first, we get an oref value<br />

and use it to insert the stream into the table:<br />

//Create a stream and write data to it<br />

Set stream = ##class(%GlobalCharacterStream).%New()<br />

Do stream.Write("Do re mi fa sol...")<br />

//Save the stream, record the stream oref in the %qstrhandle<br />

//structure, and turn the stream into a string representation<br />

//of an oref:<br />

Do stream.SaveStream()<br />

Set %qstrhandle(1,stream) = stream<br />

Set stream = stream_""<br />

//INSERT this string-oref value into the table, closing the<br />

//stream when done:<br />

&sql(INSERT INTO Person (Name,Memo) VALUES (:name, :stream))<br />

Kill %qstrhandle(1,stream)<br />

The second approach is similar, but uses %qacn instead of 1 in the %qstrhandle array. There<br />

is no need to save the stream before passing the oref. The filer will actually look for<br />

%qstrhandle($g(%qacn,1),stream)<br />

//Create a stream and write data to it<br />

Set stream = ##class(%GlobalCharacterStream).%New()<br />

Do stream.Write("Do re mi fa sol...")<br />

//Record the stream oref in the %qstrhandle structure, and<br />

//turn the stream into a string representation of an oref.<br />

Set %qacn=100<br />

Set %qstrhandle(%qacn,stream) = stream<br />

Set stream = stream_""<br />

//INSERT this string-oref value into the table, closing<br />

//the stream when we are done:<br />

&sql(INSERT INTO Person (Name,Memo) VALUES (:name, :stream))<br />

Kill %qstrhandle(%qacn,stream)<br />

You can also insert a literal value directly into the table:<br />

//Define the literal value and write it into the table<br />

Set stream = "Do re mi fa sol..."<br />

&sql(INSERT INTO Person (Name,Memo) VALUES (:name, :stream))<br />

Kill %qstrhandle(1,stream)<br />

138 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Streams and Visual Basic<br />

15.4 Streams and Visual Basic<br />

To make it easy to use streams efficiently within Visual Basic applications, the <strong>Caché</strong> ActiveX<br />

Binding projects stream properties using two special ActiveX stream objects:<br />

CacheActiveX.CharStream and CacheActiveX.BinaryStream. From your Visual Basic application<br />

you simply make calls to the methods provided by these objects.<br />

For example, suppose you have a Person object containing a character stream property, Memo.<br />

In addition to the standard stream methods Read and Write, you can use the client-side<br />

method Data, which will fetch the entire stream in one operation:<br />

' Visual Basic code<br />

Dim person As Object<br />

Dim memo As String1<br />

' Open a Person object and copy its memo into a local variable<br />

Set person=Factory.OpenId(id)<br />

memo=person.Memo.Data<br />

The GetPicture method is a similar mechanism that efficiently copies binary data containing<br />

a bitmap image into a picture control. For example, if the Person object has a binary stream<br />

property, Picture, containing a bitmap image (such as a .jpg or .png file), you can display this<br />

image in Visual Basic as follows:<br />

' Visual Basic code<br />

Dim person As Object<br />

Dim memo As String<br />

' Open a Person object and show its picture in an Image control<br />

Set person=Factory.OpenId(id)<br />

Image1.Picture = person.Picture.GetPicture<br />

The SetPicture method can be used to copy an image in the other direction, from an Image<br />

control to a binary stream property:<br />

' Visual Basic code<br />

person.Picture.SetPicture Image1.Picture<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 139


16<br />

Class Projections<br />

Class projections provide a way to extend the behavior of the <strong>Caché</strong> Class Compiler. A class<br />

projection associates a class definition with a projection class. A projection class (derived<br />

from the %Projection.AbstractProjection class) provides methods that are invoked by the Class<br />

Compiler when it compiles and removes a class definition.<br />

Projection classes are used by <strong>Caché</strong> to automatically generate additional code when a class<br />

is compiled as well as perform needed cleanup when a class is deleted. The projection<br />

mechanism is used by the Java, EJB, and C++ projections (hence the origin of the term<br />

“projection” ) to automatically generate the necessary client binding code (Java or C++)<br />

whenever a class is compiled.<br />

16.1 Projection Definitions<br />

Every class definition may optionally include one or more projection definitions. Each definition<br />

has a unique name and specifies the projection class to be used for the projection as<br />

well as parameter values that are passed to the projection methods (allowing each projection<br />

to have customized behavior).<br />

16.1.1 Adding a Projection to a Class<br />

You can associate a projection with a class by adding a projection definition to its class definition.<br />

In <strong>Caché</strong> Studio you can do this using the Projection statement within a class definition:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 141


Class Projections<br />

class MyApp.Person extends %Persistent [ClassType = persistent]<br />

{<br />

Projection JavaClient As %Projection.Java(ROOTDIR="c:\java");<br />

}<br />

This example defines a projection named “JavaClient” that will use the %Projection.Java<br />

projection class. When the methods of the projection class are called they will be passed the<br />

value of the ROOTDIR parameter.<br />

Note that if you omit the ROOTDIR parameter, your Java or C++ classes will be created<br />

under the default directory specified by your <strong>Caché</strong> configuration. To set this default directory,<br />

go to the Advanced Settings page of the Portal ([Home] > [Configuration] > [Advanced Settings]);<br />

in the Projections category, for setting you want, click Contents; on the Configuration Settings<br />

page that appears ([Home] > [Configuration] > [Advanced Settings] > [Configuration Settings]),<br />

click Add New Item to display a page where you can specify the default directory for a<br />

namespace.<br />

A class may have any number of uniquely named projections. In the case of multiple projections,<br />

the methods of each projection class will be invoked when a class is compiled or deleted.<br />

The order in which multiple projections are handled is undefined.<br />

16.2 Projection Classes<br />

The behavior of a projection is provided by its corresponding projection class. The projection<br />

class implements a set of methods (the projection interface, q.v.) that are called in response<br />

to certain events during a class' life-cycle.<br />

16.2.1 The Projection Interface<br />

Every projection class implements the projection interface. This interface consists of the<br />

following methods:<br />

CreateProjection<br />

The CreateProjection method is a class method that is invoked by the Class Compiler<br />

after it completes the compilation of a class definition. This method is passed the<br />

name of the class being compiled as well as an array containing the parameter values<br />

(subscripted by parameter name) defined for the projection.<br />

142 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


RemoveProjection<br />

The RemoveProjection method is a class method that is invoked when either 1) a<br />

class definition is deleted or 2) at the start of a recompilation of the class. This method<br />

is passed the name of the class being removed, an array containing the parameter<br />

values (subscripted by parameter name) defined for the projection, and a flag indicating<br />

whether the method is being called as part of a recompilation or because the class<br />

definition is being deleted.<br />

When a class definition containing a projection is compiled the following events occur:<br />

1. If the class has been compiled previously, it will be “uncompiled” before the new<br />

compile begins (that is, all the results of the previous compilation are removed). At this<br />

time, the compiler invokes the RemoveProjection method for every projection with a<br />

flag indicating that a recompilation is about to occur.<br />

Note that you cannot call methods of the associated class from within the<br />

RemoveProjection method as the class will not exist at this point.<br />

Also note that if you add a new projection definition to a class that had been previously<br />

compiled (without the projection) then the compiler will call the RemoveProjection<br />

method on the next compilation even though the CreateProjection method has never<br />

been called. Implementors of the RemoveProjection method have to plan for this possibility.<br />

2. After the class is completely compiled (i.e., it is ready for use), the compiler will invoke<br />

the CreateProjection method for every projection.<br />

When a class definition is deleted, the RemoveProjection method is invoked for every projection<br />

with a flag indicating that a deletion has occurred.<br />

16.2.2 The Standard Projection Classes<br />

Projection Classes<br />

In order to easily provide additional services (such as Java support) <strong>Caché</strong> provides the following<br />

projection classes as part of its standard class library:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 143


Class Projections<br />

Projection Classes<br />

Class<br />

%Projection.Java<br />

%Projection.CPP<br />

%Projection.EJB<br />

%Projection.WebService<br />

Description<br />

Generates a Java client class to enable access to the class<br />

from Java.<br />

Generates a C++ client class to enable access to the class<br />

from C++.<br />

Generates a set of Enterprise Java Bean client classes to<br />

enable access to the class from an EJB server. In addition,<br />

any other files, such as deployment descriptors are created.<br />

Generates a set of auxiliary <strong>Caché</strong> class that implement the<br />

SOAP interface for a class.<br />

16.2.3 Creating a New Projection Class<br />

You can create a new projection class by deriving a new subclass of the<br />

%Projection.AbstractProjection class, implementing the projection interface methods, and<br />

defining any class parameters you require:<br />

Class MyApp.MyProjection Extends %Projection.AbstractProjection<br />

{<br />

Parameter MYPARM;<br />

/// This method is invoked when a class is compiled<br />

ClassMethod CreateProjection(cls As %String, ByRef params)<br />

{<br />

}<br />

/// This method is invoked when a class is 'uncompiled'<br />

ClassMethod RemoveProjection(cls As %String, ByRef params, deleted as %Boolean)<br />

{<br />

}<br />

}<br />

144 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


17<br />

Object Synchronization<br />

Object synchronization is a feature of <strong>Caché</strong> objects that allows “occasionally connected”<br />

systems to synchronize databases. By this process, each database updates its objects. Object<br />

synchronization offers complementary functionality to <strong>Caché</strong> system tools that provide high<br />

availability and shadowing. Object synchronization is not designed to provide support for<br />

real-time updates; rather, it is most useful for a system that needs updates at discrete intervals.<br />

For example, a typical object synchronization application would be in an environment where<br />

there is a master copy of a database on a central server and secondary copies on client<br />

machines. Consider the case of a sales database, where each sales representative has a copy<br />

of the database on a laptop computer. When Mary, a sales representative, is off site, she makes<br />

updates to her copy of the database. When she connects her machine to the network, the<br />

central and remote copies of the database are synchronized. This can occur hourly, daily, or<br />

at any interval.<br />

This chapter includes the following sections:<br />

• About Updates<br />

• Running an Update<br />

• Other Topics<br />

17.1 About Updates<br />

Object synchronization between two databases involves updating each of them with data<br />

from the other. However, <strong>Caché</strong> does not support bidirectional synchronization as such.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 145


Object Synchronization<br />

Rather, updates from one database are posted to the other; then updates are posted in the<br />

opposite direction. For a typical application, if there is a main database and one or more local<br />

databases (as in the previous sales database example), it is recommended that updates are<br />

from the local to the main database first, and then from the main database to the local one.<br />

For object synchronization, the idea of client and server is by convention only. For any two<br />

databases, you can perform bidirectional updates; if there are more than two databases, you<br />

can choose what scheme you use to update all of them (such as local databases synchronizing<br />

with a main database independently).<br />

This section addresses the following topics:<br />

• The GUID<br />

• How updates work<br />

• The SyncSet and SyncTime objects<br />

17.1.1 The GUID<br />

To ensure that updates work properly, each object in a database should be uniquely distinguishable.<br />

To provide this functionality, <strong>Caché</strong> gives each individual object instance a GUID<br />

— a Globally Unique ID. The GUID makes each object universally unique.<br />

The GUID is optionally created, based on the value of the GUIDENABLED parameter. If<br />

GUIDENABLED has a value of 1, then a GUID is assigned to each new object instance.<br />

Consider the following example. Two databases are synchronized and each has the same set<br />

of objects in it. After synchronization, each database has a new object added to it. If the two<br />

objects share a common GUID, object synchronization considers them the same object in<br />

two different states; if each has its own GUID, object synchronization considers them to be<br />

different objects.<br />

17.1.2 How Updates Work<br />

Each update from one database to another is sent as a set of transactions. This ensures that<br />

all interdependent objects are updated together. The content of each transaction depends on<br />

the contents of the journal for the “source” database. The update can include one or more<br />

transactions, up to all transactions that have occurred since the last synchronization.<br />

Resolution of the following conditions is the responsibility of the application:<br />

• If two instances that share a unique key have different GUIDs. This requires determining<br />

if the two records describe a single object or two unique objects.<br />

146 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


About Updates<br />

• If two changes require reconciliation. This requires determining if the two changes were<br />

to a common property or to non-intersecting sets of properties.<br />

17.1.3 The SyncSet and SyncTime <strong>Objects</strong><br />

When two databases are to be synchronized, each has transactions in it that the other lacks.<br />

This is illustrated in the following diagram:<br />

Two Unsynchronized Databases<br />

Here, database A and database B have been synchronized at transaction 536 for database A<br />

and transaction 112 for database B. The subsequent transactions for each database need to<br />

be updated from each to the other. To do this, <strong>Caché</strong> uses what is called a SyncSet object.<br />

This object contains a list of transactions that are used to update a database. For example,<br />

when synchronizing database B to database A, the default contents of the SyncSet object are<br />

transactions 547, 555, 562, and 569. Analogously, when synchronizing database B to database<br />

A, the default contents of the SyncSet object are transactions 117, 124, 130, and 136. (The<br />

transactions do not use a continuous set of numbers, because each transaction encapsulates<br />

multiple inserts, updates, and deletes — which themselves use the intermediate numbers.)<br />

Each database holds a record of its synchronization history with the other. This record is<br />

called a SyncTime table. For database, its contents are of the form:<br />

Database Namespace Last Transaction Sent Last Transaction Received<br />

------------------------------------------------------------------------------<br />

B User 536 112<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 147


Object Synchronization<br />

Note:<br />

The numbers associated with each transaction do not provide any form of a timestamp.<br />

Rather, they indicate the sequence of filing for transactions within an individual<br />

database.<br />

Once synchronization of database A with database B is completed, the two databases might<br />

appear as follows:<br />

Two Databases, Where One Has Been Synchronized to the Other<br />

Because the transactions are being added to database B, they result in new transaction numbers<br />

in that database.<br />

Analogously, the synchronization of database B with database A results in 117, 124, 130,<br />

and 136 being added to database A (and receiving new transaction numbers there):<br />

148 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


About Updates<br />

Two Synchronized Databases<br />

Note that database B's transactions that have come from database A (140 through 162) are<br />

not updated back to database A. This is because the update from B to A uses a special feature<br />

that is part of the synchronization functionality. It works as follows:<br />

1. Each transaction in a database is labeled with what can be called “a database of origin.”<br />

In this example, database B's transaction 140 would be marked as originating in database<br />

A, while its transaction 136 would be marked as originating in itself (database B).<br />

2. The SyncSet.AddTransactions method, which bundles a set of transactions for synchronization,<br />

allows you to exclude transactions that originate in a particular database. Hence,<br />

when updating from B to A, AddTransactions excludes all transactions which originate<br />

in database A — since those have already been added to the transaction list for database<br />

B.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 149


Object Synchronization<br />

This functionality prevents creating infinite loops in which two databases continually update<br />

each other with the same set of transactions.<br />

17.2 Running an Update<br />

This process involves preparing for the update and then running the update itself.<br />

17.2.1 Preparing for the Update<br />

Object synchronization requires that the sites have data with matching sets of GUIDs. If you<br />

are starting with an already-existing database that does not yet have GUIDs assigned for its<br />

records, you need to assign a GUID to each instance (record) in the database, and then make<br />

sure there are matching copies of the database on each site. In detail, the process is:<br />

1. For each class being synchronized, set the value of the OBJJOURNAL parameter to 1 or<br />

2. This activates the logging of filing operations (that is, insert, update, or delete) within<br />

each transaction; this information is stored in the ^OBJ.JournalT global.<br />

If OBJJOURNAL equals 1, then the property values that are changed in filing operations<br />

are stored in the system journal file; during synchronization, data that needs to be synchronized<br />

is retrieved from that file.<br />

If OBJJOURNAL equals 2, then the property values that are changed in filing operations<br />

are stored in the ^OBJ.Journal global; during synchronization, data that needs to be<br />

synchronized is retrieved from that global. Note that storing information in the global<br />

increases the size of the database very quickly.<br />

2. For each class being synchronized, set the value of its GUIDENABLED parameter to 1;<br />

this tells <strong>Caché</strong> to allow the class to be stored with GUIDs. You can do this in Studio,<br />

adding the following line to the class definition:<br />

Parameter GUIDENABLED = 1;<br />

Note that if this value is not set, the synchronization does not work properly. Also, you<br />

must set GUIDENABLED for serial classes, but not for embedded objects.<br />

3. For each class being synchronized, give each object instance its own GUID by running<br />

the AssignGUID method:<br />

Set Status = ##Class(%Library.GUID).AssignGUID(classname,displayoutput)<br />

where<br />

150 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


• classname is a quoted string the name of class whose instances are receiving GUIDs,<br />

such as "Sample.Person".<br />

• displayoutput is an integer where zero specifies that no output is displayed and a nonzero<br />

value specifies that output is displayed.<br />

and where the method returns a %Status value.<br />

4. Recompile the class.<br />

5. Put a copy of the database on each site.<br />

Running an Update<br />

17.2.2 The Update Itself<br />

This involves updating one database from another. The database providing the updates is<br />

known as the “source database” ; the database receiving the updates is known as the “target<br />

database.” To perform the actual synchronization, the process is:<br />

1. Each time you wish to synchronize the two databases, go to the instance with the source<br />

database. On the source database, create a new SyncSet using the %New method of the<br />

%SYNC.SyncSet class:<br />

Set SrcSyncSet = ##class(%SYNC.SyncSet).%New("unique_value")<br />

The integer argument to %New, unique_value, should be an easily identified, unique<br />

value. This ensures that each addition to the transaction log on each site can be differentiated<br />

from the others.<br />

2. Once you have created the SyncSet, you can invoke the AddTransactions method of<br />

the SyncSet class:<br />

Do SrcSyncSet.AddTransactions(FirstTransaction,LastTransaction,ExcludedDB)<br />

where<br />

• FirstTransaction is the first transaction number to synchronize.<br />

• LastTransaction is the last transaction number to synchronize.<br />

• ExcludedDB specifies a namespace within a database whose transactions are not<br />

included in the SyncSet.<br />

This method collects the synchronization data and puts it in a global, ready for export.<br />

If you wish to simply synchronize all transactions since the last synchronization, invoke<br />

the method in the following form:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 151


Object Synchronization<br />

Do SrcSyncSet.AddTransactions(,,ExcludedDB)<br />

This gets all transactions, beginning with the first unsynchronized transaction to the most<br />

recent transaction. The method uses information in the SyncTime table to determine the<br />

values.<br />

ExcludedDB is a $List assembled from the code:<br />

Set ExcludedDB = $ListBuild(##class(%SYS.System).InstanceGUID(),$ZUtil(5))<br />

where<br />

• ##class(%SYS.System).InstanceGUID() returns the value of the GUID that has<br />

been established for the local <strong>Caché</strong> instance.<br />

• $ZUtil(5) returns the value of the current namespace for the local <strong>Caché</strong> instance.<br />

3. After running AddTransactions, run the ErrCount method to determine how many<br />

errors were encountered. If there have been errors, the SyncSet.Errors query provides<br />

more detailed information.<br />

4. Export the data to a local file using the ExportFile method:<br />

Do SrcSyncSet.ExportFile(file,displaymode,bUpdate)<br />

where<br />

• file is the file to which the transactions are being exported; it is a name with a relative<br />

or absolute path.<br />

• displaymode specifies whether or not output appears for the method's actions, where<br />

“d” specifies that output appears and “-d” specifies that it is silent.<br />

• bUpdate is a boolean that specifies whether or not the SyncTime table is updated<br />

(where the default is 1, meaning True). It may be helpful to explicitly set this to 0 at<br />

this point, and then set it to 1 after the source receives assurance that the target has<br />

indeed received the data and performed the synchronization.<br />

5. Move the exported file from the source database's machine to the target database's machine.<br />

You can do this using various facilities available within <strong>Caché</strong>, such as Web services.<br />

6. Create a SyncSet object on the target machine using the SyncSet.%New method. Use<br />

the same value for the argument of %New as on the source machine — this is what<br />

identifies the source of the synchronized transactions.<br />

7. Read the SyncSet object into the <strong>Caché</strong> instance on the target machine using the Import<br />

method:<br />

152 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Set Status = TargetSyncSet.Import(file,lastSync,maxTS,displaymode,errorlog,diag)<br />

where<br />

• file is the file containing the data for import.<br />

• lastSync is the last synchronized transaction number (default from synctime table).<br />

• maxTS is the last transaction number in the SyncSet object.<br />

• displaymode specifies whether or not output appears for the method's actions, where<br />

“d” specifies that output appears and “-d” specifies that it is silent.<br />

• errorlog provides a repository for any error information (and is called by reference<br />

to provide information for the application).<br />

• diag provides more detailed diagnostic information about what is happening when<br />

importing<br />

This method puts data into the target database. It behaves as follows:<br />

a. If the method detects that the object has been modified on both the source and target<br />

databases since the last synchronization, it invokes the<br />

%ResolveConcurrencyConflict callback method; like other callback methods, the<br />

content of %ResolveConcurrencyConflict is user-supplied. (Note that this can<br />

occur if either the two changes both modified a common property or they were to<br />

non-intersecting sets of properties.) If the %ResolveConcurrencyConflict method<br />

is not implemented, then the conflict remains unresolved.<br />

b. If, after the Import method executes, there are unsuccessfully resolved conflicts,<br />

these remain in the SyncSet object as unresolved items. It is the responsibility of the<br />

application programmer to take the appropriate action regarding the remaining conflicts;<br />

this may involve resolution, leaving the items in an unresolved state, etc.<br />

If this process completes, the Import method returns success.<br />

Other Topics<br />

8. Once the first database updates the second database, perform the same process in the<br />

other direction so that the second database can update the first one.<br />

17.3 Other Topics<br />

This section addresses other actions related to object synchronization. These include:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 153


Object Synchronization<br />

• Translating between GUIDs and OIDs<br />

• Manually updating a SyncTime table<br />

17.3.1 Translating Between GUIDs and OIDs<br />

If you have the GUID or OID for an object and wish to ascertain its OID or GUID, there are<br />

two methods available for this operation:<br />

• %GUID.%GUIDFind(guid) is a class method of the %GUID class that takes a GUID<br />

of an object instance and returns the OID associated with that instance.<br />

• %Persistent.%GUID(oid) is a class method of the %Persistent class that takes an OID<br />

of an object instance and returns the GUID associated with that instance; the method can<br />

only be run if the class' GUIDENABLED parameter is TRUE. This method dispatches<br />

polymorphically and determines the most-specific-type class if the OID does not contain<br />

that information. If the instance has no GUID, the method returns an empty string.<br />

17.3.2 Manually Updating a SyncTime Table<br />

To perform a manual update on the SyncTime table for a database, invoke the SetlTrn method,<br />

which sets the last transaction number:<br />

Set Status=##class(%SyncTime).SetlTrn(syncSYSID, syncNSID, ltrn)<br />

where<br />

• syncSYSID is the ID of the source database's system. If the %occTransaction.inc header<br />

file is included, this value is available through the $$$txSIDlocal macro.<br />

• syncNSID is the ID of the relevant namespace. The $ZUtil(5) function returns the value<br />

of the current namespace.<br />

• ltrn is the last transaction number to be updated. You can get this value by invoking the<br />

SyncSet's GetLastTransaction method.<br />

154 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


18<br />

Method Generators<br />

A powerful feature of <strong>Caché</strong> Object is the ability to define method generators: small programs<br />

that are invoked by the Class Compiler to generate the runtime code for a method.<br />

Method generators are used extensively within the <strong>Caché</strong> Class Library. For example, most<br />

of the methods of the %Persistent class are implemented as method generators. This makes<br />

it possible to give each persistent class customized storage code, instead of less efficient,<br />

generic code. Most of the <strong>Caché</strong> data type class methods are also implemented as method<br />

generators. Again, this gives these classes the ability to provide custom implementations that<br />

depend on the context in which they are used.<br />

You can use method generators within your own applications. A common usage is to define<br />

one or more “utility” superclasses that provide specialized methods for the subclasses that<br />

use them. The method generators within these utility classes create special code based on the<br />

definition (properties, methods, parameter values, etc.) of the class that uses them. Good<br />

examples of this technique are the %Populate and %XML.Adaptor classes provided within the<br />

<strong>Caché</strong> library.<br />

18.1 Defining a Method Generator<br />

A method generator is simply a method of a <strong>Caché</strong> class that has its CodeMode keyword set<br />

to “objectgenerator” :<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 155


Method Generators<br />

Class MyApp.MyClass Extends %RegisteredObject<br />

{<br />

Method MyMethod() [ CodeMode = objectgenerator ]<br />

{<br />

Do %code.WriteLine(" Write """ _ %class.Name _ """")<br />

Do %code.WriteLine(" Quit")<br />

Quit $$$OK<br />

}<br />

}<br />

When the class MyApp.MyClass is compiled, it ends up with a MyMethod method with the<br />

following implementation:<br />

Write "MyApp.MyClass"<br />

Quit<br />

Note:<br />

The value of CodeMode in the previous example is “objectgenerator” , since this<br />

method generator uses the preferred, object-based, method generator mechanism.<br />

Prior to version 5 of <strong>Caché</strong>, there was a different preferred mechanism, in which the<br />

value of CodeMode was “generator” . While the older mechanism is preserved for<br />

compatibility, new applications should use “objectgenerator” .<br />

18.2 How Method Generators Work<br />

The operation of a method generator is straightforward. When you compile a class definition,<br />

the Class Compiler does the following:<br />

1. It resolves inheritance for the class (builds a list of all inherited members).<br />

2. It makes a list of all methods specified as method generators (by looking at the CodeMode<br />

keyword of each method).<br />

3. It gathers the code from all method generators, copies it into one or more temporary<br />

routines, and compiles them (this makes it possible to execute the method generator code).<br />

4. It creates a set of transient objects that represent the definition of the class being compiled.<br />

These objects are made available to the method generator code.<br />

5. It executes the code for every method generator.<br />

If present, the compiler will arrange the order in which it invokes the method generators<br />

by looking at the value of the GenerateAfter keyword for each of the methods. This<br />

keyword gives you some control in cases where there may be compiler timing dependencies<br />

among methods.<br />

156 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Method Generator Context<br />

6. It copies the results of each method generator (lines of code plus any changes to other<br />

method keywords) into the compiled class structure (used to generate the actual code for<br />

the class).<br />

Note that the original method signature (arguments and return type), as well as any method<br />

keyword values, are used for the generated method. If you specify a method generator<br />

as having a return type of %Integer, then the actual method will have a return type of<br />

%Integer.<br />

7. It generates the executable code for the class by combining the code generated by the<br />

method generators along with the code from all the non-method generator methods.<br />

18.3 Method Generator Context<br />

The key to implementing method generators is understanding the context in which method<br />

generator code is executed. As described in the previous section, the class compiler invokes<br />

the method generator code at the point after it has resolved class inheritance but before it has<br />

generated code for the class. When it invokes method generator code, the class compiler<br />

creates the following object instances and makes them available to the method generator<br />

code:<br />

<strong>Objects</strong> Available to Method Generators<br />

Object<br />

%code<br />

%class<br />

%method<br />

%compiledclass<br />

%compiledmethod<br />

Description<br />

An instance of the %Stream.MethodGenerator class. This is a<br />

stream into which you write the code for method.<br />

An instance of the %Dictionary.ClassDefinition class. It contains<br />

the original definition of the class being compiled.<br />

An instance of the %Dictionary.MethodDefinition class. It contains<br />

the original definition of the method being compiled.<br />

An instance of the %Dictionary.CompiledClass class. It contains<br />

the compiled definition of the class being compiled. It contains<br />

information about the class after inheritance has been resolved<br />

(such as the list of all properties and methods, including those<br />

inherited from superclasses).<br />

An instance of the %Dictionary.CompiledMethod class. It contains<br />

the compiled definition of the method being generated.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 157


Method Generators<br />

In addition to these objects, an array variable, %parameter, is provided. This array contains<br />

the values of any class parameters indexed by parameter name. For example,<br />

%parameter("MYPARM"), contains the value of the MYPARM class parameter for the current<br />

class. This variable is provided as an easier alternative to using the list of parameter definitions<br />

available via the %class object.<br />

18.4 Implementing Method Generators<br />

To implement a method generator, do the following:<br />

1. Define a method and set its CodeMode keyword to “objectgenerator” .<br />

2. In the body of the method, write code that will generate the actual method code when<br />

the class is compiled. This code will used the %code object to write out the code. It will<br />

most likely use the other available objects as inputs to decide what code to generate.<br />

The following is an example of a method generator that creates a method that lists the names<br />

of all the properties of the class it belongs to:<br />

ClassMethod ListProperties() [ CodeMode = objectgenerator ]<br />

{<br />

For i = 1:1:%compiledclass.Properties.Count() {<br />

Set prop = %compiledclass.Properties.GetAt(i).Name<br />

Do %code.WriteLine(" Write """ _ prop _ """,!")<br />

}<br />

Do %code.WriteLine(" Quit")<br />

Quit $$$OK<br />

}<br />

This generator will create a method with an implementation similar to:<br />

Write "Name",!<br />

Write "SSN",!<br />

Quit<br />

Note the following about the method generator code:<br />

1. It uses the WriteLine method of the %code object to write lines of code to a stream<br />

containing the actual implementation for the method. (You can also use the Write method<br />

to write text without an end-of-line character).<br />

2. Each line of generated code has a leading space character. This is required as <strong>Caché</strong><br />

ObjectScript does not allow commands within the first space of a line. This would not<br />

be the case if our method generator is creating Basic or Java code.<br />

158 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Implementing Method Generators<br />

3. As the lines of generated code appear within strings, you have to be very careful about<br />

escaping quotation mark characters by doubling them up ("").<br />

4. To find the list of properties for the class, it uses the %compiledclass object. It could use<br />

the %class object, but then it would only list properties defined within the class being<br />

compiled; it would not list inherited methods.<br />

5. It returns a status code of $$$OK, indicating that the method generator ran successfully.<br />

This return value has nothing to do with the actual implementation of the method.<br />

18.4.1 Method Generators for Other Languages<br />

At this time, method generators are implemented using <strong>Caché</strong> ObjectScript.<br />

You can, however, generate code for different languages by setting the Language keyword<br />

to the desired language. The available choices are “cache” , “basic” , and “java” . (A Java<br />

method only appears in the Java class projected for this class definition).<br />

For example, the following is the stub of a method generator that generates Java code for a<br />

Java-projected class:<br />

Method JavaStub() As %Integer [ CodeMode = objectgenerator, Language = java ]<br />

{<br />

// WriteLine statements with Java code to be generated<br />

}<br />

18.4.2 Specifying CodeMode within a Method Generator<br />

By default, a method generator will create a “code” method (that is, the CodeMode keyword<br />

for the generated method is set to “code” ). You can change this using the CodeMode property<br />

of the %code object.<br />

For example, the following method generator will generate an ObjectScript expression method:<br />

Method Double(%val As %Integer) As %Integer [ CodeMode = objectgenerator ]<br />

{<br />

Set %code.CodeMode = "expression"<br />

Do %code.WriteLine("%val * 2")<br />

}<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 159


19<br />

The <strong>Caché</strong> Data Population Utility<br />

<strong>Caché</strong> includes a utility for creating random test data for persistent classes. The creation of<br />

such data is known as data population; the utility for doing this, known as the <strong>Caché</strong> populate<br />

utility, is useful for testing persistent classes before deploying them within a real application.<br />

It is especially helpful when testing how various parts of an application will function when<br />

they are working against a large set of data.<br />

The populate utility takes its name from its principal element — the %Populate class, which<br />

is part of the <strong>Caché</strong> class library. Classes that inherit from %Populate contain a method called<br />

Populate, which allows you to generate and save class instances containing random data<br />

values. You can also customize the behavior of the %Populate class to provide data for your<br />

application's needs.<br />

Along with the %Populate class, the populate utility uses %PopulateUtils. %Populate provides<br />

the interface to the utility, while %PopulateUtils is a helper class.<br />

19.1 Data Population Basics<br />

It is quite simple to populate a class with test data:<br />

1. Include the populate utility functionality as part of the class definition by making<br />

%Populate one of its superclasses.<br />

2. Compile the class.<br />

3. Invoke the populate utility, which creates test data.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 161


The <strong>Caché</strong> Data Population Utility<br />

19.1.1 Changes to the Class Definition<br />

To use the populate utility, simply add %Populate to the end of a class' list of superclasses,<br />

so that it inherits the interface methods. For example, if a class inherits directly from<br />

%Persistent, its new superclass list would be:<br />

Class MyApp.MyClass Extends (%Persistent,%Populate) ...<br />

Do not use %Populate as a primary superclass; that is do not list it as the first class in the<br />

superclass list.<br />

When using the New Class Wizard within the <strong>Caché</strong> Studio, you can specify that a new class<br />

supports “automatic data population” . This is equivalent to adding the %Populate class to<br />

the superclass list.<br />

Once you have changed the class definition, re-compile the class so that you can populate<br />

instances with data.<br />

19.1.2 Populating <strong>Objects</strong><br />

To create new instances of a class and populate them with dummy data, simply run the<br />

Populate class method inherited from the %Populate class at the command line in the <strong>Caché</strong><br />

terminal:<br />

Do ##class(Person).Populate()<br />

By default, Populate creates 10 new objects, populates their literal properties with random<br />

data, and saves them in the database. It also populates reference properties if the referenced<br />

class contains any objects, collections of data types, collections of persistent objects, and<br />

indexes defined for the class.<br />

If you prefer, you can specify the number of objects to create:<br />

Do ##class(Person).Populate(num)<br />

where num is the number of objects that you want.<br />

Note:<br />

After using the populate utility to create sample data, you must manually delete the<br />

class extent (set of all object instances) in order to have an empty set of objects. You<br />

can do this using either the %DeleteExtent() method (safe) or the %KillExtent()<br />

method (fast) of the persistent interface. For more information, see the section<br />

Deleting <strong>Objects</strong> in the chapter “Object Persistence.”<br />

162 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Unless otherwise configured, <strong>Caché</strong> uses random data to populate the class. In cases with<br />

defined constraints, such as a minimum or maximum length, some of the generated data may<br />

not pass validation, so that individual objects will not be saved. In these situations, Populate<br />

may create fewer than the specified number of objects. To allow you to check this, Populate<br />

returns the number of objects actually populated:<br />

Set objs = ##class(Person).Populate(100)<br />

// objs is set to the number of objects created.<br />

// objs will be less than or equal to 100<br />

The populate utility is aware of the MAXVAL and MINVAL parameters and automatically<br />

creates data that obeys these conditions for properties of type %Integer.<br />

19.1.2.1 Object-valued Properties<br />

The populate utility can automatically generate values for object-valued properties —<br />

embedded objects as well as references to other persistent objects. This allows you to test or<br />

demonstrate more complex objects and classes.<br />

To enable population for these properties:<br />

1. Include %Populate among the superclasses of the referenced class.<br />

2. Create one or more instances of the referenced class.<br />

The POPSPEC Parameter<br />

This ensures that there are valid objects for the object-valued properties to point to.<br />

19.2 The POPSPEC Parameter<br />

The populate utility can provide data for properties containing embedded objects, lists, arrays,<br />

and combinations of these with object-valued properties; it also allows you to customize the<br />

data of certain data type properties.<br />

To perform this kind of data population, provide a value for a property's POPSPEC parameter<br />

by having it reference a method for generating data. If POPSPEC has a value, then the<br />

Populate method invokes the referenced method to generate a value for the property. This<br />

method can be user-provided or one of the methods of the %PopulateUtils class (provided<br />

within the <strong>Caché</strong> library). Methods of %PopulateUtils provide data such as the names of<br />

people, states, countries, or street address in the United States. Refer to the class' documentation<br />

for a complete list of available methods.<br />

To use POPSPEC, set its value as part of a property definition, as follows:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 163


The <strong>Caché</strong> Data Population Utility<br />

Property PropertyName As %String(POPSPEC = "MethodToInvoke()");<br />

where PropertyName is the property for which you are generating data and MethodToInvoke<br />

is the method that Populate will call to generate the data.<br />

For example, to specify that a property is the name of a city in the United States, add the<br />

following POPSPEC parameter value to its definition:<br />

Property City As %String(POPSPEC = "USCity()");<br />

When you run the Populate method, it will invoke the USCity method of the %PopulateUtils<br />

class to generate a random city name for this property.<br />

The value of the POPSPEC parameter is a string specifying a method name in one of the<br />

following ways:<br />

• POPSPEC = “Method()” — Populate invokes the method “Method” from the<br />

%PopulateUtils class.<br />

• POPSPEC = “.Method()” — Populate invokes the method “Method” from the current<br />

object instance (the one being populated).<br />

• POPSPEC = “##class(Class).Method()” — Populate invokes the method “Method”<br />

from the “Class” class.<br />

Where applicable, you can include parameter values within the parentheses following the<br />

method name. If you are specifying a string value, use two pairs (““””) of quotation marks<br />

around the value that you specify.<br />

Property Name As %String(POPSPEC = ".MyName(""X"")");<br />

For example, suppose a class definition contains the following code:<br />

Property PName As %String(POPSPEC = "PName:Name(""F"")");<br />

Property Code As %String(POPSPEC = ".MakeCode()");<br />

Property SSN As %String(POPSPEC = "##class(MyApp.Utils).MakeSSN()");<br />

Property Number As %Integer;<br />

Populate generates values for the PName property by calling the method:<br />

Set .. PName = ##class(%PopulateUtils).Name("F")<br />

Populate generates values for the Code property by calling the local class' MakeCode method:<br />

Set ..Code = obj.MakeCode()<br />

where obj is the current object instance being created. (This assumes that you have defined<br />

a MakeCode method in the class.)<br />

164 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Populate generates values for the SSN property by calling the MakeSSN method of the<br />

MyApp.Utils class:<br />

Set ..SSN = ##class(MyApp.Utils).MakeSSN()<br />

Populate generates values for the Number that are random integers.<br />

The POPSPEC Parameter<br />

Note:<br />

There is also a POPSPEC parameter defined at the class level that controls data<br />

population for an entire class. This is an older mechanism (included for compatibility)<br />

that is replaced by the property-specific POPSPEC parameter.<br />

19.2.1 Populating Embedded <strong>Objects</strong><br />

The Populate method creates data for an embedded object by calling its PopulateSerial<br />

method (which is generated by the %Populate class). This is done by default as long as the<br />

embedded object inherits from the %Populate class and is equivalent to:<br />

Property Home As Address(POPSPEC="##class(Address).PopulateSerial()");<br />

19.2.2 Populating Lists<br />

To populate a list, the syntax for setting the value of POPSPEC is:<br />

Property PropName As TypeName (POPSPEC="MethodName:MaxNo") [Collection= list];<br />

where PropName is a list of type TypeName; MethodName is the populating method for the<br />

property; and MaxNospecifies the maximum number of elements in the list. If maxnum is not<br />

specified, it defaults to 10.<br />

The value ofMethodName depends on the type of list:<br />

• Lists of data types typically use the %PopulateUtils method corresponding to their data<br />

type.<br />

• Lists of persistent objects should use an empty value for MethodName to take advantage<br />

of the default automatic reference population (as occurs for simple, non-collection reference<br />

properties).<br />

• Lists of embedded objects must use the PopulateSerial method.<br />

In the following example, there are lists of several types of data. Colors is a list of strings,<br />

Kids is a list of references to persistent objects, and Addresses is a list of embedded objects:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 165


The <strong>Caché</strong> Data Population Utility<br />

Property Colors As %String(POPSPEC="ValueList("",Red,Green,Blue"")")<br />

[Collection = list];<br />

Property Kids As Person(POPSPEC=":5")<br />

[Collection = list];<br />

Property Addresses As Address(POPSPEC=":3")<br />

[Collection = list];<br />

Note:<br />

In Studio, each property definition needs to appear on a single line.<br />

For the Colors property, the Populate method generates data using the ValueList method of<br />

the PopulateUtils class, and follows basic POPSPEC syntax. For the Kids property, there is<br />

no specified method, which results in automatically generated references. For the Addresses<br />

property, there is<br />

19.2.3 Populating Arrays<br />

To populate an array, the syntax for setting the value of POPSPEC is:<br />

Property Prop As TypeNm (POPSPEC="MethodNm:MaxNo:KeySpec") [Collection= array];<br />

where Prop is an array of type TypeNm; MethodNm is the populating method for the property;<br />

MaxNospecifies the maximum number of elements in the list; and KeySpec is the populating<br />

method used to generate the key for each element of an array. If MaxNo is not specified, it<br />

defaults to 10. If KeySpec is not specified, it defaults to String.<br />

In the following example, there are arrays of several types of data and different kinds of keys<br />

being generated as well:<br />

Property Tix As %Integer(POPSPEC="Integer():20:Date()") [Collection = array];<br />

Property Reviews As Review(POPSPEC=":3:Date()") [Collection = array];<br />

Property Actors As Actor(POPSPEC=":15:Name()") [Collection = array];<br />

The Attendance property has its data generated using the ValueList method of the PopulateUtils<br />

class; its keys are generated using the Date method of the PopulateUtils class. The Tix property<br />

has no specified method, which results in automatically generated references , and has its<br />

keys also generated using the Date method. The Actors property has no specified method,<br />

which results in automatically generated references, and has its keys generated using the<br />

Name method of the PopulateUtils class.<br />

19.2.4 Custom Populate Actions<br />

To perform custom actions with the Populate method, use the OnPopulate callback method.<br />

166 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Details<br />

19.3 Details<br />

This section describes how %Populate works internally. The %Populate class contains two<br />

method generators: Populate and PopulateSerial. Each persistent or serial class inheriting<br />

from %Populate has one or the other of these two methods included in it (as appropriate).<br />

We will describe only the Populate method here. The Populate method is a loop, that is<br />

repeated for each of the requested number of objects.<br />

Inside the loop, the code:<br />

• Creates a new object<br />

• Sets values for its properties<br />

• Saves and closes the object<br />

A simple property with no overriding POPSPEC parameter has a value generated using code<br />

with the form:<br />

Set obj.Description = ##class(%PopulateUtils).String(50)<br />

While using a library method from %PopulateUtils via a “Name:Name()” specification would<br />

generate:<br />

Set obj.Name = ##class(%PopulateUtils).Name()<br />

An embedded Home property might create code like:<br />

Do obj.HomeSetObject(obj.Home.PopulateSerial())<br />

The generator loops through all the properties of the class, and creates code for some of the<br />

properties, as follows:<br />

• It checks if the property is private, is calculated, is a collection, or has an initial expression<br />

(in that order). If any of these are true, the generator exits.<br />

• If the property is has a POPSPEC override, the generator uses that and then exits.<br />

• If the property is a reference, on the first time through the loop, the generator builds a<br />

list of random IDs, takes one from the list, and then exits. For the subsequent passes, the<br />

generator simply takes an ID from the list and then exits.<br />

• If the property name either “Name” , “SSN” , “Company” , “Title” , “Phone” , or<br />

“Zip” , the generator then uses the corresponding library method “Name” , “SSN” ,<br />

“Company” , “Title” , “USPhone” , or “USZip” . It then exits.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 167


The <strong>Caché</strong> Data Population Utility<br />

• If the property type is %String, %Integer, %Date, or %Name, the generator then uses the<br />

corresponding library method “String(MAXLEN)” , “Integer(MINVAL,MAXVAL)”<br />

, “Date” , or “Name” . It then quits.<br />

• Otherwise, the generator sets the property's value to “” .<br />

Refer to the %PopulateUtils class for a list of available methods.<br />

168 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


20<br />

<strong>Using</strong> Callback Methods<br />

Callback methods are called by system methods to allow additional user written processing<br />

during specific events. To distinguish these types of methods, they are given names of the<br />

form %OnEvent or OnEvent, where Event describes the event that triggers the callback.<br />

If an event supports a callback method, the method controlling the event automatically executes<br />

the callback method it if it exists. You should never explicitly execute callback methods.<br />

<strong>Caché</strong> supports the following callback methods:<br />

• %OnAddToSaveSet<br />

• %OnAfterSave<br />

• %OnBeforeSave<br />

• %OnClose<br />

• %OnConstructClone<br />

• %OnDelete<br />

• %OnDetermineClass<br />

• %OnNew<br />

• %OnOpen<br />

• %OnRollBack<br />

• %OnValidateObject<br />

• OnPopulate<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 169


<strong>Using</strong> Callback Methods<br />

Since different types of classes support different events, not all callback methods are available<br />

to all classes. For example, callback methods triggered by saving or deleting an object cannot<br />

be accessed by registered objects since these objects cannot be saved or deleted.<br />

With the exception of %OnDetermineClass, these methods can be used to perform any<br />

processing desired at the relevant event.<br />

%OnDetermineClass is a special case. When %Open or %Delete is given a partially-formed<br />

OID, it calls the %OnDetermineClass method, if the user has provided one, to obtain the<br />

actual class name. If %OnDetermineClass indicates the object belongs to a different class,<br />

%Open or %Delete will dispatch to the new class' %Open or %Delete method. If there is<br />

no %OnDetermineClass method, %Open and %Delete simply assume the current class is<br />

the correct class.<br />

20.1 %OnAddToSaveSet<br />

This instance method is called by the %AddToSaveSet method. The %AddToSaveSet<br />

method itself is called on an object whose %Save method has been invoked, as well as all<br />

other objects referenced by that object that have been loaded into memory. %AddToSaveSet<br />

can also be invoked directly from %OnAddToSaveSet. If %OnAddToSaveSet modifies<br />

another object, then it is the responsibility of %OnAddToSaveSet to invoke %AddToSaveSet<br />

on that modified object.<br />

When you invoke %Save on an object, called, for example, MyPerson, <strong>Caché</strong> generates a<br />

list of objects that MyPerson references. A SaveSet is the list of objects consisting of the<br />

object to be saved and all the objects that it references. In the example, the SaveSet might<br />

include referenced objects MySpouse, MyDoctor, and so on.<br />

The syntax of %OnAddToSaveSet is:<br />

ReturnValue %OnAddToSaveSet(SaveDepth As %Integer, Insert As %Integer,<br />

CallCount As %Integer)<br />

where:<br />

170 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


%OnAfterSave<br />

ReturnValue<br />

SaveDepth<br />

Insert<br />

CallCount<br />

A %Status code, where an error code causes the save to fail and the<br />

transaction to be rolled back.<br />

An integer value passed in from %AddToSaveSet that represents<br />

the internal state of SaveSet construction. If you use<br />

%OnAddToSaveSet to add any other records to the SaveSet, pass<br />

this value to %AddToSaveSet without change.<br />

A flag indicating if the object being saved is being inserted into the<br />

extent (1) or that it is already part of the extent (0).<br />

The number of times that %OnAddToSaveSet has been called for<br />

this object. Due to the networked nature of object references it is<br />

possible that %AddToSaveSet can be invoked on the same object<br />

multiple times.<br />

You can update objects, create new objects, delete objects and ask objects to include themselves<br />

in the current SaveSet by calling %AddToSaveSet. If you modify the current instance<br />

or any of its descendants, you must let the system know that you have done this by calling<br />

%AddToSaveSet for the modified instance(s) and passing a value of 1 for the method's<br />

Refresh argument.<br />

None of the modification restrictions imposed on %OnAfterSave, %OnBeforeSave, or<br />

%OnValidateObject are in place for %OnAddToSaveSet.<br />

If you delete an object using %OnAddToSaveSet, you need to call %RemoveFromSaveSet<br />

to clean up any dangling references to it.<br />

This method is available to the %Library.RegisteredObject class and its subclasses.<br />

20.2 %OnAfterSave<br />

This instance method is called by the %Save method just after an object is saved. This method<br />

allows you to perform actions outside the scope of the object being saved, such as queueing<br />

a later notification action; an example of this is a bank using the deposit in excess of a certain<br />

amount to cause it to send the customer an explanation of its deposit policies.<br />

Its syntax is:<br />

ReturnValue %OnAfterSave(Insert as Boolean)<br />

where:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 171


<strong>Using</strong> Callback Methods<br />

ReturnValue<br />

Insert<br />

A %Status code, where returning a failure status from %OnAfterSave<br />

causes %Save to fail and ultimately roll back the transaction.<br />

A flag indicating if the object being saved is being inserted into the<br />

extent (1) or that it is already part of the extent (0).<br />

This method is available to the %Library.Persistent class and its subclasses.<br />

20.3 %OnBeforeSave<br />

This instance method is called by the %Save method just before an object is saved. This<br />

method allows you to request user confirmation before completing an action and can also<br />

serve to ensure the validity of an instance's data and relationships prior to saving the instance<br />

to disk.<br />

Its syntax is:<br />

ReturnValue %OnBeforeSave(Insert as Boolean)<br />

where:<br />

ReturnValue<br />

Insert<br />

A %Status code, where an error code causes the save to fail.<br />

A flag indicating if the object being saved is being inserted into the<br />

extent (1) or that it is already part of the extent (0).<br />

This method is available to the %Library.Persistent class and its subclasses.<br />

20.4 %OnClose<br />

This instance method is called by the %Close method just before an object is closed. One<br />

use of this method is to release resources that <strong>Caché</strong> does not directly control, but are logically<br />

related to the application, such when an object represents a communication device and closing<br />

the object invokes the %OnClose code to drop the connection.<br />

172 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


%OnConstructClone<br />

Its syntax is:<br />

ReturnValue %OnClose()<br />

where:<br />

ReturnValue<br />

A %Status code. The return value is not used by %Close.<br />

This method is available to the %Library.RegisteredObject class and its subclasses.<br />

20.5 %OnConstructClone<br />

This instance method is called by the %ConstructClone method immediately after the<br />

structures have been allocated for the cloned object and all the data has been copied into it.<br />

The method allows you to perform any additional actions related to the cloned object, such<br />

as taking out a lock or resetting any of the clone's property values.<br />

Its syntax is:<br />

ReturnValue %OnConstructClone(Object As %RegisteredObject, Deep As<br />

%Boolean)<br />

where:<br />

ReturnValue<br />

Object<br />

Deep<br />

A %Status code, where an error code prevents the clone from being<br />

created.<br />

The OREF of the object that was cloned.<br />

How “deep” the cloning process is, where 0 specifies that the clone<br />

points to the same related objects as the original; 1 causes objects<br />

related to the object being cloned to also be cloned, so that the clone<br />

gets its own set of related objects.<br />

This method is available to the %Library.RegisteredObject class and its subclasses.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 173


<strong>Using</strong> Callback Methods<br />

20.6 %OnDelete<br />

This class method is called by the %Delete method just before an object is deleted. This<br />

method can be used to ensure that deleting an object does corrupt data integrity, such as by<br />

ensuring that an object designed to contain other objects is only deleted when it is empty.<br />

Its syntax is:<br />

ReturnValue %OnDelete(OID AS %ObjectIdentity)<br />

where:<br />

ReturnValue<br />

OID<br />

A %Status code, where an error code stops the deletion.<br />

An object identifier for the object being deleted.<br />

This method is available to the %Library.Persistent class and its subclasses.<br />

20.7 %OnDetermineClass<br />

This class method is called by the %Delete, %Oid, and %Open methods when one of these<br />

receives a partially-formed OID as an argument. The purpose of %OnDetermineClass is to<br />

supply a class name to its calling method.<br />

Its syntax is:<br />

ReturnValue %OnDetermineClass(OID As %ObjectIdentity, ByRef Class As<br />

%String)<br />

where:<br />

ReturnValue<br />

OID<br />

Class<br />

A %Status code, where behavior depends on the calling routine. If<br />

invoked by %Open, the attempt to open the object fails, no OREF is<br />

returned, and the error status is passed back to the caller. If invoked<br />

by %Delete, the attempt to delete the object fails.<br />

The instance with the partially-formed name<br />

A string in which the name of the instance's class is placed.<br />

This method is available to the %Library.Persistent class and its subclasses.<br />

174 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


%OnNew<br />

20.8 %OnNew<br />

This instance method is called by the %New method at the point when all the storage for an<br />

object has been allocated and properties with initial values have had their values set. This<br />

method allows you to pass initialization information to a new instance, such as the location<br />

where an object resides on disk or a unique identifier.<br />

Its syntax is:<br />

ReturnValue %OnNew(InitialValue As %CacheString)<br />

where:<br />

ReturnValue<br />

InitialValue<br />

A %Status code, where an error stops the creation of the object.<br />

A string that the method uses in setting up the object.<br />

This method is available to the %Library.RegisteredObject class and its subclasses.<br />

20.9 %OnOpen<br />

This instance method is called by the %Open method just before an object is opened. It<br />

allows you to verify the state of an instance compared to any relevant entities; for instance,<br />

you can use the method also to ensure the function of an object's real-world analog.<br />

Its syntax is:<br />

ReturnValue %OnOpen()<br />

where:<br />

ReturnValue<br />

A %Status code, where an error stops the opening of the object.<br />

This method is available to the %Library.Persistent class and its subclasses.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 175


<strong>Using</strong> Callback Methods<br />

20.10 %OnRollBack<br />

This instance method is called by the %Save method if an object cannot be saved. This<br />

method holds code that performs rollback-related actions on objects other than the one being<br />

rolled back. At the time when this method is invoked, the state of the object may be inconsistent<br />

with other objects; you can use %OnRollBack to correct this condition.<br />

Its syntax is:<br />

ReturnValue %OnRollBack()<br />

where:<br />

ReturnValue<br />

A %Status code, where an error stops the rollback operation.<br />

This method is available to the %Library.Persistent class and its subclasses.<br />

20.11 %OnValidateObject<br />

This instance method is called by the %ValidateObject method just after all validation has<br />

occurred. This allows you to perform content-dependent validation, such as where valid values<br />

for one property vary according to the value of another property.<br />

Its syntax is:<br />

ReturnValue %OnValidateObject()<br />

where:<br />

ReturnValue<br />

A %Status code, where an error causes the validation to fail.<br />

This method is available to the %Library.RegisteredObject class and its subclasses.<br />

176 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


OnPopulate<br />

20.12 OnPopulate<br />

This instance method is called by the Populate method after assigning values to an object's<br />

properties but before the object is saved to disk. This method provides additional control over<br />

the generated data. If an OnPopulate method exists, then the Populate method calls it for<br />

each object that it generates.<br />

Its syntax is:<br />

ReturnValue %OnPopulate()<br />

where:<br />

ReturnValue<br />

A %Status code, where a failure status causes the instance being<br />

populated to be discarded.<br />

For example, if you have a stream property, Memo, and wish to assign a value to it when<br />

populating, you can provide an OnPopulate method:<br />

Method OnPopulate() As %Status<br />

{<br />

Do ..Memo.Write("Default value");<br />

QUIT $$$OK<br />

}<br />

This method is available to the %Library.Populate class and its subclasses. See the chapter<br />

The <strong>Caché</strong> Data Population Utility for more information on data population generally.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 177


21<br />

Object-Specific ObjectScript<br />

Features<br />

ObjectScript includes a number of features that provide special functionality within object<br />

methods. These are:<br />

• .. Syntax — For accessing a property or calling a method of the current object<br />

• ##Class Syntax — For invoking a class method or for casting an object reference as<br />

another class to call a method<br />

• ##this Syntax — For getting a handle to the OREF of the current instance, such as for<br />

passing it to another class or for another class to refer to the current instance's members.<br />

• ##super Syntax — For invoking a superclass method from within a subclass method<br />

• i% Syntax — For referencing an instance variable from within its own<br />

Get or Set accessor method, or bypassing its Get or Set method<br />

21.1 .. Syntax<br />

The .. syntax provides a mechanism for referencing another method or property of the current<br />

object. For example, suppose there is a Bricks property of type %Integer:<br />

Property Bricks As %Integer;<br />

A CountBricks method can then refer to Bricks with .. syntax:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 179


Object-Specific ObjectScript Features<br />

Method CountBricks()<br />

{<br />

Write "There are ",..Bricks," bricks.",!<br />

}<br />

Similarly, a WallCheck method can refer to CountBricks and Bricks with .. syntax:<br />

Method WallCheck()<br />

{<br />

Do ..CountBricks()<br />

If ..Bricks < 100 {<br />

Write "Your wall will be small."<br />

}<br />

}<br />

21.2 ##Class Syntax<br />

The ##class syntax allows you to:<br />

• Invoke a class method when there is no existing or open instance of a class.<br />

• Cast a method from one class as a method from another.<br />

Note:<br />

##class is case insensitive.<br />

21.2.1 Invoking a Class Method<br />

To invoke a class method, the syntax is either of the following:<br />

>Do ##class(Package.Class).Method(Args)<br />

>Set localname = ##class(Package.Class).Method(Args)<br />

It is also valid to use ##class as part of an expression, as in<br />

Write ##class(Class).Method(args)*2<br />

without setting a variable equal to the return value.<br />

A frequent use of this syntax is in the creation of new instances:<br />

>Set LocalInstance = ##class(Package.Class).%New()<br />

21.2.2 Casting a Method<br />

To cast a method of one class as a method of another class, the syntax is either of the following:<br />

180 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


##this Syntax<br />

>Do ##class(Package.Class1)Class2Instance.Method(Args)<br />

>Set localname = ##class(Package.Class1)Class2Instance.Method(Args)<br />

You can cast both class methods and instance methods.<br />

For example, suppose that two classes, MyClass.Up and MyClass.Down, both have Go methods.<br />

The code of MyClass.Up.Go is:<br />

Method Go()<br />

{<br />

Write "Go up.",!<br />

}<br />

and the code of MyClass.Down.Go is:<br />

Method Go()<br />

{<br />

Write "Go down.",!<br />

}<br />

You can then create an instance of MyClass.Up and use it to invoke the MyClass.Down.Go<br />

method:<br />

>Set LocalInstance = ##class(MyClass.Up).%New()<br />

>Do ##class(MyClass.Down)LocalInstance.Go()<br />

Go down.<br />

It is also valid to use ##class as part of an expression, as in<br />

Write ##class(Class).Method(args)*2<br />

without setting a variable equal to the return value.<br />

A more generic way to refer to other methods are the $ZObjMethod and $ZObjClassMethod<br />

functions, which are for instance and class methods, respectively. These provide a mechanism<br />

for referring to packages, classes, and methods programmatically.<br />

21.3 ##this Syntax<br />

The ##this syntax provides a handle to the OREF of the current instance, such as for passing<br />

it to another class or for another class to refer to the current instance's members. When an<br />

instance refers to its own members, the .. syntax is preferred.<br />

Note:<br />

##this is case sensitive and must be in all lowercase.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 181


Object-Specific ObjectScript Features<br />

For example, suppose there is an application with an Accounting.Order class and an<br />

Accounting.Utils class. The Accounting.Order.CalcTax method calls the<br />

Accounting.Utils.GetTaxRate and Accounting.Utils.GetTaxableSubtotal methods, passing the<br />

current instance's city and state values to GetTaxRate and passing the list of items ordered<br />

and relevant tax-related information to GetTaxableSubtotal. CalcTax then uses the values<br />

returned to calculate the sales tax for the order. Hence, its code is something like:<br />

Method CalcTax() As %Numeric<br />

{<br />

Set TaxRate = ##Class(Accounting.Utils).GetTaxRate(##this)<br />

Write "The tax rate for ",..City,", ",..State," is ",TaxRate*100,"%",!<br />

Set TaxableSubtotal = ##class(Accounting.Utils).GetTaxableSubTotal(##this)<br />

Write "The taxable subtotal for this order is $",TaxableSubtotal,!<br />

Set Tax = TaxableSubtotal * TaxRate<br />

Write "The tax for this order is $",Tax,!<br />

}<br />

The first line of the method uses the ##Class syntax (described above) to invoke the other<br />

class' method; it passes the current object to that method using the ##this syntax. The second<br />

line of the method uses the .. syntax (also described above) to get the values of the instance's<br />

City and State properties. The action on the third line is similar to that on the first line.<br />

The Accounting.Utils class' GetTaxRate method can then use the handle to the passed-in<br />

instance to get handles to various properties — for both getting and setting their values:<br />

ClassMethod GetTaxRate(OrderBeingProcessed As Accounting.CustOrder) As %Numeric<br />

{<br />

Set LocalCity = OrderBeingProcessed.City<br />

Set LocalState = OrderBeingProcessed.State<br />

// code to determine tax rate based on location and set<br />

// the value of OrderBeingProcessed.TaxRate accordingly<br />

Quit OrderBeingProcessed.TaxRate<br />

}<br />

The GetTaxableSubtotal method also uses the handle to the instance to look at its properties<br />

and set the value of its TaxableSubtotal property.<br />

Hence, the output at the <strong>Caché</strong> terminal from invoking the CalcTax method for MyOrder<br />

instance of the Accounting.Order class would be something like:<br />

>Do MyOrder.CalcTax()<br />

The tax rate for Cambridge, MA is 5%<br />

The taxable subtotal for this order is $79.82<br />

The tax for this order is $3.99<br />

182 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


##super Syntax<br />

21.4 ##super Syntax<br />

Suppose that a subclass method overrides a superclass method. From within the subclass<br />

method, you can use the ##super() syntax to invoke the overridden superclass method.<br />

Note:<br />

##super is case sensitive and must be in all lowercase.<br />

For example, suppose that the class MyClass.Down extends MyClass.Up and overrides the<br />

Simple class method. If the code for MyClass.Up.Simple is:<br />

ClassMethod Simple()<br />

{<br />

Write "Superclass.",!<br />

}<br />

and the code for MyClass.Down.Simple is:<br />

ClassMethod Simple()<br />

{<br />

Write "Subclass.",!<br />

Do ##super()<br />

}<br />

then the output for subclass method, MyClass.Down.Simple, is:<br />

>Do ##Class(MyClass.Down).Simple()<br />

Subclass.<br />

Superclass.<br />

><br />

##super also works with methods that accept arguments. For example, if the code for<br />

MyClass.Up.SelfAdd is:<br />

ClassMethod SelfAdd(Arg)<br />

{<br />

Write Arg,!<br />

Write Arg + Arg<br />

Quit<br />

}<br />

then its output is:<br />

>Do ##Class(MyClass.Up).SelfAdd(2)<br />

2<br />

4<br />

><br />

If the code for MyClass.Down.SelfAdd is:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 183


Object-Specific ObjectScript Features<br />

ClassMethod SelfAdd(Arg1 As %Integer)<br />

{<br />

Do ##super(Arg1)<br />

Write !<br />

Write Arg1 + Arg1 + Arg1<br />

Quit<br />

}<br />

then its output is:<br />

>Do ##Class(MyClass.Down).SelfAdd(2)<br />

2<br />

4<br />

8<br />

><br />

A more generic way to refer to other methods are the $ZObjMethod and $ZObjClassMethod<br />

functions, which are for instance and class methods, respectively. These provide a mechanism<br />

for referring to packages, classes, and methods programmatically.<br />

21.5 i% Syntax<br />

When you instantiate a class with a persistent property, <strong>Caché</strong> creates what is called an<br />

instance variable, which holds the property's value. When you set or refer to a property value,<br />

<strong>Caché</strong> invokes what are called “Get” and “Set” accessor methods, which ultimately refer<br />

to the instance variable. The accessor methods have names of the form Get<br />

and Set, where is the name of property being accessed.<br />

For example, if a class contains an LName property of type %String:<br />

Property LName As %String;<br />

then displaying the property's value for a particular instance<br />

> Write MyClass.Up.LName<br />

Pepperidge<br />

actually goes through the MyClass.Up.LNameGet accessor method to get the value from<br />

the property's instance variable.<br />

Similarly, the typical call to set a property's value:<br />

>Set MyClass.Up.LName = "Blutarsky"<br />

actually invokes the MyClass.Up.LNameSet method, passing in the new value to set the<br />

value of the instance variable and, thereby, that of LName.<br />

184 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


You can override a property's default accessor methods. In the last screen of the New Property<br />

Wizard in the Studio, you can select the check boxes for creating a custom Get method, Set,<br />

or both; if so the Studio creates signatures for the custom methods. Within the custom methods,<br />

you can perform any special processing that your application requires.<br />

Invoking a Set method from within a Set method creates a recursive series of references that<br />

results in a stack overflow; likewise for Get methods. To avoid this situation, <strong>Caché</strong> provides<br />

a mechanism for working with a property's instance value that circumvents the accessor<br />

methods. This mechanism refers to a property's instance variable directly by using syntax of<br />

i%, where is the name of property being accessed.<br />

For example, to directly set the value of the LName instance variable, the “i%” syntax is:<br />

Set i%LName = "Blutarsky"<br />

i% Syntax<br />

This directly sets “Blutarsky” as the value of the instance variable LName, bypassing the<br />

LNameSet accessor. The advantage of this mechanism is that it gives you direct control over<br />

the instance variable's value. This must be used with caution and only under appropriate circumstances,<br />

since the ability to circumvent any checking done in the property's Set method<br />

can result in properties containing non-valid values.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 185


22<br />

Dynamic Dispatch<br />

<strong>Caché</strong> classes can include support for what is called “dynamic dispatch.” If dynamic dispatch<br />

is in use and a program references a property or method that is not part of the class definition,<br />

then a method (called a “dispatch method” ) is called that attempts to resolve the undefined<br />

method or property. For example, dynamic dispatch can return a value for a property that is<br />

not defined or it can invoke a method for a method that isn't implemented. The dispatch destination<br />

is dynamic in that it does not appear in the class descriptor and is not resolved until<br />

runtime.<br />

22.1 How Dynamic Dispatch Happens<br />

<strong>Caché</strong> makes a number of dispatch methods available that you can implement. Each of these<br />

methods attempts to resolve an element that is missing under different circumstances.<br />

If you implement a dispatch method, it has the following effects:<br />

• During application execution, if <strong>Caché</strong> encounters an element that is not part of the<br />

compiled class, it invokes the dispatch method to try to resolve the encountered element.<br />

• The application code that uses the class does not do anything special to make this happen.<br />

<strong>Caché</strong> automatically checks for the existence of the dispatch method and, if that method<br />

is present, invokes it.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 187


Dynamic Dispatch<br />

22.2 Content of Methods Implementing Dynamic<br />

Dispatch<br />

As the application developer, you have control over the content of dispatch methods. The<br />

code within them can be whatever is required to implement the methods or properties that<br />

the class is attempting to resolve.<br />

Code for dynamic dispatch might include attempts to locate a method based on other classes<br />

in the same extent, package, database, on the same file system, or by any other criteria. If a<br />

dispatch method provides a general case, it is recommended that the method also create some<br />

kind of log for this action, so that there is a record of any continued operation that includes<br />

this general resolution.<br />

For example, the following implementation of %DispatchClassMethod allows the application<br />

user to invoke a method to perform whatever action was intended:<br />

ClassMethod %DispatchClassMethod(Class As %String, Method As %String, args...)<br />

{<br />

WRITE "The application has attempted to invoke the following method: ",!,!<br />

WRITE Class,".",Method,!,!<br />

WRITE "This method does not exist.",!<br />

WRITE "Enter the name of the class and method to call",!<br />

WRITE "or press Enter for both to exit the application.",!,!<br />

READ "Class name (in the form 'Package.Class'): ",ClassName,!<br />

READ "Method name: ",MethodName,!<br />

}<br />

IF ClassName = "" && MethodName = "" {<br />

// return a null string to the caller if a return value is expected<br />

QUIT:$QUIT "" QUIT<br />

} ELSE {<br />

// checking $QUIT ensures that a value is returned<br />

// if and only if it is expected<br />

IF $QUIT {<br />

QUIT $ZOBJCLASSMETHOD(ClassName, MethodName, args...)<br />

} ELSE {<br />

DO $ZOBJCLASSMETHOD(ClassName, MethodName, args...)<br />

QUIT<br />

}<br />

}<br />

By including this method in a class that is a secondary superclass of all classes in an application,<br />

you can establish application-wide handling of calls to non-existent class methods.<br />

188 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


The Dynamic Dispatch Methods<br />

22.2.1 Return Values<br />

None of the dispatch methods have specified return values. This is because each should provide<br />

output that is of the same type of the call that originally created the need for the dispatch.<br />

If the dispatch method cannot resolve the method or property, it can use $ZUTIL(96,3) to<br />

throw a or error —<br />

or whatever else may be appropriate.<br />

22.3 The Dynamic Dispatch Methods<br />

The following methods may be implemented to resolve unknown methods and properties:<br />

• %DispatchMethod<br />

• %DispatchClassMethod<br />

• %DispatchGetProperty<br />

• %DispatchSetProperty<br />

• %DispatchSetMultidimProperty<br />

• %DispatchGetModified<br />

• %DispatchSetModified<br />

22.3.1 %DispatchMethod<br />

This method implements an unknown method call. Its syntax is:<br />

Method %DispatchMethod(Method As %String, Args...)<br />

where its first argument is the name of the referenced method and the second argument is an<br />

array that holds all the arguments passed to the original method. Since the number of arguments<br />

and their types can vary depending on the method being resolved, the code in<br />

%DispatchMethod needs to handle them correctly (since the class compiler can not make<br />

any assumptions about the type). The Args... syntax handles this flexibly.<br />

Because %DispatchMethod attempts to resolve any unknown instance method associated<br />

with the class, it has no specified return value; if successful, it returns a value whose type is<br />

determined by the method being resolved and whether the caller expects a return value.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 189


Dynamic Dispatch<br />

%DispatchMethod can also resolve an unknown multidimensional property reference —<br />

that is, to get the value of a property. However, only direct multidimensional property references<br />

are supported for dynamic dispatch. $DATA, $ORDER, and $QUERY are not supported,<br />

nor is a SET command with a list of variables.<br />

22.3.2 %DispatchClassMethod<br />

This method implements an unknown class method call. Its syntax is:<br />

ClassMethod %DispatchClassMethod(Class As %String, Method As %String, Args...)<br />

where its first two arguments are the name of the referenced class and the name of the referenced<br />

method. Its third argument is an array that holds all the arguments passed to the original<br />

method. Since the number of arguments and their types can vary depending on the method<br />

being resolved, the code in %DispatchClassMethod needs to handle them correctly (since<br />

the class compiler can not make any assumptions about the type). The Args... syntax handles<br />

this flexibly.<br />

Because %DispatchClassMethod attempts to resolve any unknown class method associated<br />

with the class, it has no specified return value; if successful, it returns a value whose type is<br />

determined by the method being resolved and whether the caller expects a return value.<br />

22.3.3 %DispatchGetProperty<br />

This method gets the value of an unknown property. Its syntax is:<br />

Method %DispatchGetProperty(Property As %String)<br />

where its argument is the referenced property. Because %DispatchGetProperty attempts<br />

to resolve any unknown property associated with the class, it has no specified return value;<br />

if successful, it returns a value whose type is that of the property being resolved<br />

22.3.4 %DispatchSetProperty<br />

This method sets the value of an unknown property. Its syntax is:<br />

Method %DispatchSetProperty(Property As %String, Value)<br />

where its arguments are the name of the referenced property and the value that was being<br />

attempted to be assigned to it.<br />

190 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


The Dynamic Dispatch Methods<br />

22.3.5 %DispatchSetMultidimProperty<br />

This method sets the value of an unknown multidimensional property. Its syntax is:<br />

Method %DispatchSetMultidimProperty(Property As %String, Value, Subs...)<br />

where its first two arguments are the name of the referenced property and the value that was<br />

being attempted to be assigned to it. The third argument, Subs, is an array that contains the<br />

subscript values. Subs has an integer value that specifies the number of subscripts, Subs(1)<br />

has the value of the first subscript, Subs(2) has the value of the second, and so on. If no subscripts<br />

are given, then Subs is undefined.<br />

Only direct multidimensional property references are supported for dynamic dispatch. $DATA,<br />

$ORDER, and $QUERY are not supported, nor is a SET command with a list of variables.<br />

Note:<br />

Note that there is no %DispatchGetMultidimProperty dispatch method. This is<br />

because a multidimensional property reference is identical to a method call. Thus,<br />

such a reference invokes %DispatchMethod, which must include code to differentiate<br />

between method names and multidimensional property names.<br />

22.3.6 %DispatchGetModified<br />

This method gets the value of the modified flag for an unknown property. Its syntax is:<br />

Method %DispatchGetModified(Property As %String)<br />

where its argument is the name of the referenced property.<br />

22.3.7 %DispatchSetModified<br />

This method sets the value of the modified flag for an unknown property. Its syntax is:<br />

Method %DispatchSetModified(Property As %String, Value)<br />

where its arguments are the name of the referenced property and the value to set for its<br />

modified flag.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 191


23<br />

Class Definition Classes<br />

The <strong>Caché</strong> Library includes a number of class definition classes that provide object access<br />

to the <strong>Caché</strong> Unified Dictionary. <strong>Using</strong> these classes you can programmatically examine<br />

class definitions, modify class definitions, create new classes, and even write programs that<br />

automatically generate documentation. These classes are contained within a “%Dictionary”<br />

package.<br />

Note:<br />

There is an older set of class definitions classes defined within the “%Library”<br />

package. These are maintained for compatibility with existing applications. New<br />

code should make use of the classes within the “%Dictionary” package. Make sure<br />

that you specify the correct package name when using these classes or you may<br />

inadvertently use the wrong class.<br />

There are two parallel sets of class definition classes: those that represent defined classes and<br />

those that represent compiled classes.<br />

A defined class definition represents the definition of a specific class. It includes only information<br />

defined by that class; it does not include information inherited from superclasses. In<br />

addition to providing information about classes in the dictionary, these classes can be used<br />

to programmatically alter or create new class definitions.<br />

A compiled class definition includes all of the class members that are inherited from super<br />

classes. A compiled class definition object can only be instantiated from a class that has been<br />

compiled. You cannot save a compiled class definition.<br />

This chapter discusses defined class definitions exclusively, though the operation of the<br />

compiled class definitions is similar.<br />

The family of class definition classes that represent defined classes includes:<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 193


Class Definition Classes<br />

Class<br />

%Dictionary.AbstractDefinition<br />

%Dictionary.ClassDefinition<br />

%Dictionary.ForeignKeyDefinition<br />

%Dictionary.IndexDefinition<br />

%Dictionary.MethodDefinition<br />

%Dictionary.ParameterDefinition<br />

%Dictionary.PropertyDefinition<br />

%Dictionary.QueryDefinition<br />

%Dictionary.TriggerDefinition<br />

Description<br />

Abstract base class for all class definition classes.<br />

Represents a class definition. Contains class keywords<br />

as well as collections containing class member<br />

definitions.<br />

Represents a foreign key definition within a class.<br />

Represents an index definition within a class.<br />

Represents a method definition within a class.<br />

Represents a parameter definition within a class.<br />

Represents a property definition within a class.<br />

Represents a query definition within a class.<br />

Represents an SQL trigger definition within a class.<br />

23.1 Browsing Class Definitions<br />

You can use the class definition classes to browse through the class definitions within the<br />

<strong>Caché</strong> dictionary using the same techniques you would use in a database application: you<br />

can use the %ResultSet object to iterate over sets of classes and you can instantiate persistent<br />

objects that represent specific class definitions.<br />

For example, from within a <strong>Caché</strong> process you can get a list of all classes defined within the<br />

dictionary for the current namespace by using the %Dictionary.ClassDefinition:Summary<br />

query:<br />

Set result = ##class(%ResultSet).%New("%Dictionary.ClassDefinition:Summary")<br />

Do result.Execute()<br />

While (result.Next()) {<br />

Write result.Data("Name"),!<br />

}<br />

You can just as easily invoke this query from an ActiveX or Java client using the client<br />

ResultSet object.<br />

This %Dictionary.ClassDefinition:ClassInfo query will return all of the classes visible<br />

from the current namespace (including classes in the system library). You can filter out<br />

unwanted classes using the various columns returned by the<br />

%Dictionary.ClassDefinition:Summary query.<br />

194 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


You can get detailed information about a specific class definition by opening a<br />

%Dictionary.ClassDefinition object for the class and observing its properties. The ID used to<br />

store %Dictionary.ClassDefinition objects is the class name:<br />

Set cdef = ##class(%Dictionary.ClassDefinition).%OpenId("Sample.Person")<br />

Write cdef.Name,!<br />

// get list of properties<br />

Set count = cdef.Properties.Count()<br />

For i = 1:1:count {<br />

Write cdef.Properties.GetAt(i).Name,!<br />

}<br />

Altering Class Definitions<br />

You can also do this easily from an ActiveX or Java client. Note that you must fully-qualify<br />

class names with their package name or the call to %OpenId will fail.<br />

23.2 Altering Class Definitions<br />

You can modify an existing class definition by opening a %Dictionary.ClassDefinition object,<br />

making the desired changes, and saving it using the %Save method.<br />

You can create a new class by creating a new %Dictionary.ClassDefinition object, filling in its<br />

properties and saving it. When you create %Dictionary.ClassDefinition object you must pass<br />

the name of the class via the %New command. When you want to add a member to the class<br />

(such as a property or method) you must create the corresponding definition class (passing<br />

its %New command a string containing "classname.membername") and add the object to the<br />

appropriate collection within the %Dictionary.ClassDefinition object.<br />

For example:<br />

Set cdef = ##class(%Dictionary.ClassDefinition).%New("MyApp.MyClass")<br />

If %SYSTEM.Status.IsError(cdef) {<br />

Do DecomposeStatus^%apiOBJ(%objlasterror,.Err)<br />

Write !, Err(Err)<br />

}<br />

Set cdef.ClassType = "persistent"<br />

Set cdef.Super = "%Persistent,%Populate"<br />

// add a Name property<br />

Set pdef = ##class(%Dictionary.PropertyDefinition).%New("MyClass:Name")<br />

If %SYSTEM.Status.IsError(pdef) {<br />

Do DecomposeStatus^%apiOBJ(%objlasterror,.Err)<br />

Write !,Err(Err)<br />

}<br />

Do cdef.Properties.Insert(pdef)<br />

Set pdef.Type="%String"<br />

// save the class definition object<br />

Do cdef.%Save()<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 195


24<br />

Internet Classes<br />

The <strong>Caché</strong> class library includes classes that provide an easy-to-use interface for a number<br />

of useful Internet protocols. <strong>Using</strong> these classes your applications can:<br />

• Send and receive email messages using the SMTP and POP protocols.<br />

• Send and receive files using the FTP protocol.<br />

• Make HTTP requests to an external HTTP Server.<br />

• Parse URL strings into constituent pieces.<br />

This chapter describes the these classes. You can also refer to the documentation for the<br />

specific classes.<br />

24.1 Email<br />

Basically, email (electronic mail) works by sending messages (which have a specific format)<br />

across the internet using a set of standard protocols. <strong>Caché</strong> provides classes that support two<br />

of these protocols (SMTP and POP3). In addition, <strong>Caché</strong> provides a class, %Net.MailMessage,<br />

that represents an object version of an email message.<br />

24.1.1 Mail Messages<br />

The %Net.MailMessage class is used to represent email messages for both sending and<br />

receiving email. It contains properties that correspond to the various parts of an email message.<br />

These properties are divided into those that represent the header (whom the message is<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 197


Internet Classes<br />

addressed to , its subject, etc.) and those that represent the content or body of the message.<br />

The content properties are inherited from the %Net.MailMessagePart class.<br />

24.1.1.1 Message Headers<br />

The header information of the %Net.MailMessage class includes the following:<br />

• To—the list of email addresses this message will be sent (or was sent) to.<br />

• From—the email address this message is sent from. When sending email, your application<br />

must provide a value for this property.<br />

• Date—the date that this message was retrieved as reported by the mail server.<br />

• Subject—a string containing the subject for this message.<br />

• Sender—the actual sender of the message as reported by the mail server.<br />

• Cc—the list of carbon copy addresses this message will be sent (or was sent) to.<br />

24.1.1.2 Message Content<br />

The content of an email message can vary from a simple block of text to complex data represented<br />

using MIME.<br />

24.1.2 Sending Email<br />

<strong>Caché</strong> supports sending email messages using SMTP (Simple Mail Transport Protocol).<br />

24.1.3 Receiving Email<br />

A <strong>Caché</strong> application can download and process email messages from a mail server using the<br />

POP3 protocol.<br />

Note:<br />

<strong>Caché</strong> is not and does not contain a mail server. In order to process in-coming email<br />

messages within <strong>Caché</strong> you must have a running mail server. What <strong>Caché</strong> provides<br />

is the ability to connect to and interact with a mail server using the POP3 protocol.<br />

The interface for interacting with a mail server is provided by the %Net.FetchMailProtocol<br />

class. This abstract class includes a set of protocol-neutral methods for interacting with a mail<br />

server. Actual communication with a mail server using a specific protocol is provided by a<br />

subclass of %Net.FetchMailProtocol. <strong>Caché</strong> supports the POP3 protocol (the only one supported<br />

at this time) using the %Net.POP3 class.<br />

198 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


FTP<br />

24.2 FTP<br />

<strong>Caché</strong> includes a class, %Net.FtpSession, for establishing a session with an FTP (File Transfer<br />

Protocol) server from within <strong>Caché</strong> and download and uploading files.<br />

24.3 HTTP<br />

<strong>Caché</strong> includes a class, %Net.HttpRequest, for making HTTP (Hyper Text Transport Protocol)<br />

requests from within a <strong>Caché</strong> application. The response to such a request is wrapped within<br />

a %Net.HttpResponse object.<br />

24.4 URL Parsing<br />

<strong>Caché</strong> includes a utility class, %Net.URLParser, for parsing URL strings into their component<br />

parts.<br />

This class is quite simple, it contains one class method, Parse, that takes a string containing<br />

a URL value and returns, by reference, an array containing the parts of the URL subscripted<br />

by part name. For example:<br />

Set url = "http://www.intersys.com/main.cspQUERY=abc#anchor"<br />

Do ##class(%Net.URLParser).Parse(url,.components)<br />

Upon return, components will contain an array of the parts of this URL:<br />

Element<br />

components(“scheme”)<br />

components(“netloc”)<br />

components(“path”)<br />

components(“query”)<br />

components(“fragment”)<br />

Value<br />

http:<br />

www.intersys.com<br />

/main.csp<br />

QUERY=abc<br />

anchor<br />

Description<br />

The transport scheme specified by<br />

this URL.<br />

The network address of the URL.<br />

The file path of the URL.<br />

The query string associated with<br />

the URL.<br />

The fragment (following the #<br />

character) for the URL.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 199


Internet Classes<br />

For more information refer to the documentation for the %Net.URLParser class.<br />

200 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


A<br />

Object Concurrency<br />

This appendix provides information on:<br />

• Object concurrency options<br />

• Version checking<br />

A.1 Object Concurrency Options<br />

Many of the methods of the %Persistent class allow you to specify an optional concurrency<br />

setting in order to determine how locks are used for concurrency control.<br />

The possible concurrency settings are:<br />

Setting<br />

0<br />

Meaning<br />

No Locking<br />

Description<br />

No locks are used.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 201


Object Concurrency<br />

Setting<br />

1<br />

2<br />

3<br />

4<br />

Meaning<br />

Atomic<br />

Shared<br />

Shared /<br />

Retained<br />

Exclusive<br />

Description<br />

When an object is loaded, the %LoadData method<br />

creates a shared lock for the object, provided that the<br />

object occupies more than one storage node in the<br />

database. No locks are held for objects stored in a single<br />

node. %LoadData releases the lock after it finishes<br />

loading the object. When an object is initially saved to the<br />

database (inserted), %SaveData holds an exclusive lock<br />

during the save, provided that the object occupies more<br />

than one storage node in the database. %SaveData holds<br />

no locks for objects stored in a single node. When a<br />

previously saved object is saved to the database<br />

(updated), %SaveData holds an exclusive lock during the<br />

save.<br />

When an object is loaded, the %LoadData method<br />

creates a shared lock for the object. %LoadData releases<br />

the lock after it finishes loading the object.When an object<br />

is initially saved to the database (inserted), %SaveData<br />

holds an exclusive lock during the save, provided that the<br />

object occupies more than one storage node in the<br />

database. %SaveData holds no locks for objects stored<br />

in a single node.When a previously saved object is saved<br />

to the database (updated), %SaveData holds an exclusive<br />

lock during the course of the save.<br />

A shared lock is acquired either when an existing object<br />

is opened or when a new object is initially saved to the<br />

database (inserted); the lock is released when the object<br />

is destructed. Additionally, when an object is initially saved<br />

to the database, %SaveData holds an exclusive lock<br />

during the save, provided the object occupies more than<br />

one storage node in the database.<br />

An exclusive lock is acquired either when an existing<br />

object is opened or when a new object is initially saved<br />

to the database (inserted); the lock is released when the<br />

object is destructed.<br />

It is important to set concurrency options at an appropriate level for your application. Consider<br />

the following scenario:<br />

1. Process A opens an object.<br />

2. Process B deletes that object from disk.<br />

202 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Version Checking<br />

3. Process A saves the object using %Save and receives a success status.<br />

Examining the data on disk shows that the object was not written to disk.<br />

This is an example of concurrent operations that occurred without adequate concurrency<br />

control. In this example, Process B should not have been allowed to delete the object if Process<br />

A really intended to write the object back to disk. Since Process A did not modify the object,<br />

no physical write occurred and unexpected results were observed. Had Process A modified<br />

the object, then %Save would have succeeded as before but the object would have been<br />

written to disk. These inconsistent results are just one example of allowing concurrent access<br />

without adequate controls in place at the application level. (The inconsistent behavior<br />

exhibited by a successful call to %Save and the object not appearing on disk is not a bug in<br />

%Save. It is a concurrency failure which, by definition, will leave the database in an inconsistent<br />

state.)<br />

The solution to this problem is to have Process A open the object with concurrency 3 or 4;<br />

in this case, Process B would then either have been denied access (failed with a concurrency<br />

violation) or would have had to wait until Process A released the object.<br />

A.2 Version Checking<br />

<strong>Caché</strong> implements version checking using a class parameter called VERSIONPROPERTY.<br />

All persistent classes have this parameter. When defining a persistent class, the procedure<br />

for enabling version checking is:<br />

1. Create a property of type %Integer that holds the updatable version number for each<br />

instance of the class.<br />

2. Set the value of the property's InitialExpression keyword to 0.<br />

3. Set the value of VERSIONPROPERTY to the name of that property. The value of<br />

VERSIONPROPERTY cannot be changed to a different property by a subclass.<br />

This incorporates version checking into updates to instances of the class.<br />

When version checking is implemented, the property specified by VERSIONPROPERTY is<br />

automatically incremented each time an instance of the class is updated (either by objects or<br />

SQL). Prior to incrementing the property, <strong>Caché</strong> compares its in-memory value to its stored<br />

value. If they are different, then a concurrency conflict is indicated and an error is returned;<br />

if they are the same, then the property is incremented and saved.<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 203


Object Concurrency<br />

Note:<br />

You can use this set of features to implement optimistic concurrency.<br />

To implement a concurrency check in an SQL update statement for a class where<br />

VERSIONPROPERTY refers to a property called InstanceVersion, the code would be something<br />

like:<br />

SELECT InstanceVersion,Name,SpecialRelevantField,%ID<br />

FROM my_table<br />

WHERE %ID = :myid<br />

// Application performs operations on the selected row<br />

UPDATE my_table<br />

SET SpecialRelevantField = :newMoreSpecialValue<br />

WHERE %ID = :myid AND InstanceVersion = :myversion<br />

where myversion is the value of the version property selected with the original data.<br />

204 <strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong>


Index<br />

A<br />

accessor methods, 184<br />

E<br />

Extent, Object, 107<br />

S<br />

SaveSet (<strong>Objects</strong>), 101<br />

Swizzling, 17<br />

<strong>Using</strong> <strong>Caché</strong> <strong>Objects</strong> 205

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!