Professional Documents
Culture Documents
June 2003
VVEERRSSAAN
NTT DDaattaabbaassee FFuunnddaam
meennttaallss M
Maannuuaall
June 2003
VERSANT Database Fundamentals Manual
2
VERSANT Database Fundamentals Manual
Table of Contents
Chapter 1: System Description ..............................................................................................................8
Chapter 2: Objects.............................................................................................................................34
Object Types.................................................................................................................................35
Object Elements ............................................................................................................................37
Object Characteristics.....................................................................................................................38
Object Status.................................................................................................................................42
Object Relationships.......................................................................................................................44
Chapter 4: Transactions......................................................................................................................55
3
VERSANT Database Fundamentals Manual
Name Rules...................................................................................................................................96
Session Firewall .............................................................................................................................99
Database Connection Rule ............................................................................................................ 100
Schema Evolution ........................................................................................................................ 101
Mixing Releases........................................................................................................................... 106
Application Failure ....................................................................................................................... 107
Error Messages that are Incomplete................................................................................................ 108
4
VERSANT Database Fundamentals Manual
5
VERSANT Database Fundamentals Manual
6
VERSANT Database Fundamentals Manual
7
VERSANT Database Fundamentals Manual
8
VERSANT Database Fundamentals Manual
A key part of Versant's application framework is Versant Developer Suite 6.0, a complete, e-infrastructure
software that simplifies the process of building and deploying transactional, distributed applications. As a
standalone database, the Versant ODBMS is designed to meet customers' requirements for high
performance, scalability, reliability and compatibility with disparate computing platforms and corporate
information systems.
Versant ODBMS has established a reputation for exceeding the demands of mission critical enterprise
business applications, providing reliability, integrity, and performance. The efficient multi-threaded
architecture, internal parallelism, balanced client-server architect and efficient query optimization of 6.0.5
ODBMS delivers unsurpassed levels of performance and scalability.
This release focuses on three key areas, designed to meet the expanding requirements for today's traditional
and Internet-based database solutions: internationalization, scalability, and productivity.
The Versant Developer Suite includes the Versant ODBMS, the C++ and Java language interfaces, the XML
toolkit and Asynchronous Replication framework. The main advantage is that all Versant products included in
the Versant Developer Suite, work cohesively together, conform to the same compiler (C++, JDK) versions
and adopt the same release schedule. From an ease-of-use perspective, this is a significant step in the right
direction.
This release of Versant ODBMS will support system, backup and archive files larger than 2GB.
Query
Extensions to the query capabilities, which with specified criteria will return result(s) in near constant time,
irrespective of number of objects. In 6.0.5 release, Versant introduced the concept of Virtual Attributes that
allows you to define derived attributes that are dynamically computed from real class attributes.
These derived attributes or “virtual attributes” can have indices defined on them to improve query
performance.
9
VERSANT Database Fundamentals Manual
1. Multi- attribute
The ability to define a virtual attribute composed from a number of actual attributes. This will help
improve query performance for multi- attribute predicates.
2. Case Insensitive
3. Internationalized Strings
The ability to perform queries based on international character sets (see later).
Security
Enable user authorization independent of operating system while maintaining backward compatibility with
existing applications. The following functionality has been added:
• Removed OS user dependency during user authentication (excludes DBA's and DBSA)
Versant has certified that 8-bit dean (UTF-8) character encodings, are stored and manipulated correctly
internally, it is possible to use such encodings for:
• Database names
• User names & passwords
• String attribute values
The query enhancements make it possible to build and use indexes and query based on the character
encoding and language (locale). For example it would be possible to encode strings as UTF -8 and query
these based on the German locale (U and Ü are treated the same). Likewise it would be possible to build an
index based on this specific locale to improve performance.
10
VERSANT Database Fundamentals Manual
Embeddability Enhancements
By introducing a number of new APIs it is now possible to manage a Versant environment completely via API
from C, C++ or Java. This functionality is significant for those wishing to embed Versant in their own
applications.
VXML Toolkit
The Versant XML Toolkit (VXML) adds XML/object mapping support to the Versant Developer Suite product.
Via command line tools or Java APIs users can generate XML from defined object graphs and likewise
generate objects from XML.
Integrated Installation
A common, integrated installation offers a single GUI interface to install data-management and connectivity
components. This integrated installation eliminates version conflicts when installing and deploying
components of the Developer Suite.
11
VERSANT Database Fundamentals Manual
VERSANT Features
List of Features
Following is a raw list of VERSANT features.
General Description — VERSANT is an object database management system that includes all features needed
for scalable, production databases in a distributed, heterogeneous workgroup environment.
Data as Objects — VERSANT models data as objects. The VERSANT implementation of objects allows:
• Custom definition of complex data types.
• Encapsulation of data and code.
• Inheritance of data and code.
• Code reuse.
• Polymorphism.
• Unique identification of objects.
12
VERSANT Database Fundamentals Manual
13
VERSANT Database Fundamentals Manual
• Include relevant VERSANT header files and then compile and link with a VERSANT compiled
code library to create an executable program.
14
VERSANT Database Fundamentals Manual
• Use the JVI Enhancer that post-processes the compiled class byte-code to provide transparent
persistence.
Scalable Operation
VERSANT is scalable, which means that it uses distributed resources in such a way that performance does not
decrease as the system grows.
Client/server model
Your application is the database client and runs on a client machine. A client application can access
multiple databases concurrently with other client applications.
The database that provides objects to an application is called the "server." One of the roles of the server is
to process queries. A server can support concurrent access by numerous users.
To VERSANT, the terms "client" and "server" refer to roles and not machines or processes. The client and
server can be on the same machine or on different machines.
Because queries are executed on the platform containing the data and because locks are set at the object
level, network traffic is reduced to a short query message from the client to the server and the return of
only desired objects from the server to the client. Processing queries on servers balances database
processing responsibilities between the client and the server. It can result in major performance gains in a
large, distributed database environment by taking full advantage of available platform and network
resources including parallel and scalable processors.
By contrast, a file/server query causes all objects of a class to be locked and passed over a network even
if only one object is desired.
15
VERSANT Database Fundamentals Manual
Locks
The VERSANT locking model provides for high concurrency of multiple users.
Objects are kept in one or more database volumes, which are storage places on disk. Database volumes
can be files or raw devices and can be added incrementally either locally or at distributed locations.
Two-phase
Two-phase commits
To ensure data integrity when multiple, distributed databases are used, VERSANT performs updates with
two-phase commits. Two-phase commits involve a procedure in which multiple databases communicate
with each other to confirm that all changes in a unit of work are saved or rolled back together.
Lazy updates
Changes to class definitions do not paralyze operations. Instead, instances are updated the next time they
are accessed, which is called a "lazy update." You can create or drop leaf classes, rename leaf or non-leaf
classes, and create or drop attributes and methods in leaf or non-leaf classes.
Schema management
To facilitate the use of distributed databases, you can ask an object the name of its class and then inspect
its class definition. Routines are also provided for creating and modifying classes at runtime and for
synchronizing class definitions among multiple databases.
Distributed Databases
VERSANT supports distributed databases with the following features.
16
VERSANT Database Fundamentals Manual
Object migration
Objects can be migrated while applications still have transparent access to them. Object migration is
possible, because objects have identifiers that stay with the object for its lifetime which means that the
physical locations of objects are hidden from the application.
Recovery
To ensure data integrity when multiple, distributed databases are used, VERSANT performs updates with
two-phase commits. Two-phase commits involve a procedure in which multiple databases communicate
with each other to confirm that all changes in a unit of work are saved or rolled back together.
Heterogeneity
Objects can be moved among heterogeneous platforms and managed in databases on numerous
hardware platforms to take advantage of available resources in a network.
Schema management
Class definitions can be managed at run time on both local and remote databases. Class definitions are
stored with objects, which allow access to objects with applications that are running on different
platforms and are using multiple interface languages.
Replication
Advanced functionality includes group checkouts, which can replicate data on numerous databases.
Expansion
Databases can be created, deleted, and expanded on local and remote platforms. Database volumes can
span devices and platforms.
Backup
Data on one machine can be backed up to remote sites, tapes, or files. Multiple distributed databases can
be backed up to save their state at a given point in time. This gives transactional consistency across
multiple databases.
Security
Access to databases and system utilities is controlled through user authorization, which may be
customized.
17
VERSANT Database Fundamentals Manual
Session database
VERSANT implements the concept of a session database, which can be local or remote, which handles
basic record keeping and logging for a session.
Connection database
Applications can connect to any number of local or remote databases and then manage objects in them as
if they were local. You can work on objects in any number of databases at the same time in a distributed
transaction.
Workgroup Support
VERSANT supports workgroups with the following features.
Locking
Locks allow multiple applications to access the same objects simultaneously in a cooperative, controlled,
and predictable manner. To maximize concurrency, locks are applied at the object level.
There are two basic kinds of VERSANT locks. Short locks reserve objects during a short transaction
typically lasting only seconds or minutes. Persistent locks have the same features as a short lock, plus
persistence from session to session and the ability to set multiple levels of locking priorities. They also
specify queuing options when the object you want is not available, and notification of lock related actions
performed by other applications.
Object level locking provides for maximum access to data while providing read and modification
guarantees.
VERSANT allows you to create two kinds of databases: group databases, which are accessible to many
users, and personal databases, which are only accessible to one user at a time.
Checkouts
Checking out of objects provide local access to objects over long periods of time.
Personal checkouts copy objects from a group to a personal database, which is useful when you want to
update a set of objects over a long period of time without network traffic or local logging or locking.
During the checkout period, VERSANT places persistent locks which hold even if you disconnect from
the source database. Checked out objects are tracked in long transactions.
18
VERSANT Database Fundamentals Manual
Group checkouts copy objects from one group database to another group database and are useful when
you want to replicate a set of objects in many locations that may be physically far apart.
Versions
Versioning allows you to trace the evolution of an object over time and is also a way of allowing multiple
users to work on the same data.
Configuration Management
Configuration management allows customized sets of object versions.
Variety of transactions
VERSANT supports multiple kinds of atomic work units, including short transactions, checkpoints,
savepoints, and long transactions.
Short transactions are for use when objects are needed for short periods of time, and long transactions
are for use when objects are needed for hours or days.
Standard interfaces
Multiple standard language interfaces allow workgroup members to access data from applications written
in multiple languages.
The language specific interfaces map all capabilities and programming styles of a particular language to
the object database model. Any action or data type that can be expressed in the interface language may
become a part of a VERSANT database schema. Thus, programming languages are tightly bound to the
database, but the database is not tightly bound to a particular language. There is no special VERSANT
database language.
VERSANT object databases are most commonly used with languages that implement the concept of
either class or template and either delegation or inheritance. VERSANT can also be used with languages,
such as C, which are not object oriented.
In its present release, VERSANT provides language specific interfaces for C++ and C. Each
language-specific interface consists of several libraries of precompiled routines and, for typed languages,
predefined data types. An interface can include its own development tools, and can be used with other
vendors' software development products.
19
VERSANT Database Fundamentals Manual
Libraries
You can use VERSANT with custom and third party libraries of code and data types.
Tools
For situations requiring unusual types of access, you can define your own locks.
Object migration
Because object are persistently and uniquely identified, objects can be redistributed in a system of
databases for performance improvements without affecting application code making object references.
Performance
VERSANT performance features include the following.
During a database session, VERSANT maintains an object cache in virtual memory on the client machine
and a page cache in shared memory on server machines with a database in use. There is one object cache
per application, and one page cache per operating database.
This approach combines the best of a page management scheme and an object management scheme,
because the object cache provides for fast access to objects needed in the current transaction while the
page cache provides fast access to objects used in preceding transactions and to objects stored on the
same page as a recently accessed object.
Memory management
VERSANT provides numerous mechanisms for managing application memory, including explicit pinning
and releasing of data.
Process tuning
VERSANT allows you to set operating parameters for application and server processes.
20
VERSANT Database Fundamentals Manual
Clustering
You can cluster instances of a class on disk storage, which will improve query performance.
Raw devices
You can use either raw devices or files for data storage.
Indexing
You can create indexes on attributes, which will improve query performance.
You can turn locking and logging off to improve performance. Turning locking off is safe in a personal
database, because there can be only one user of a personal database at a time. Turning logging off is safe
if used in conjunction with checkouts, because original copies of objects remain in their source
databases.
You can use multiple processes or threads in a session. You can also establish multiple sessions.
Navigational queries
Local access
You can checkout objects to provide local access, which reduces network traffic.
21
VERSANT Database Fundamentals Manual
Storage Architecture
Each database consists of a number of volumes, which are storage places on disk. A volume can be either a
file or a raw device.
System Volume
The System Volume for each database is automatically created as a part of the initial process of creating a
database. It is used for storing class descriptions and for storing object instances.
Data volumes
The Logical Log Volume and Physical Log Volume are used to record transaction activities and provide
information for roll back and recovery. Logical log and physical log volumes are created when a database
is created.
The maximum number of databases that can be combined to form a distributed database system is 216.
22
VERSANT Database Fundamentals Manual
For the C and C++ interfaces, applications and VERSANT libraries are stored as files. When you create
classes or an application, you write a program that includes appropriate VERSANT .h specification files.
You then compile your program and then link it with appropriate VERSANT .a library files. With the C++
Interface, you must also insert your classes into a database with the Schema Change Utility sch2db.
23
VERSANT Database Fundamentals Manual
Software Structure
Internally, VERSANT is composed of several software modules. You do not need to know about these
software modules, for they operate invisibly. Thus, this section is provided for your interest only.
VERSANT Manager
From a developer's viewpoint, programs using language interfaces drive all database and object
management actions by communicating with a module called "VERSANT Manager." VERSANT Manager
then communicates with a module called "VERSANT Server."
VERSANT Manager performs object caching, object validation, and management of queries, long
transactions, schema, links, versions, checkouts, and checkins.
VERSANT Server
VERSANT Server performs object retrieval, object update, page caching, query support, and
management of storage classes, indexes, short transactions, logging, and locking.
Between a VERSANT Server and an operating system is a Virtual System Layer specific to the hardware
platform. The Virtual System Layer provides portability across hardware boundaries.
Internal communications occur through network layers. The network layers translate messages as
appropriate to the network protocol. Because of this form of internal communication, a VERSANT
Manager may communicate transparently with all parts of a distributed database network.
24
VERSANT Database Fundamentals Manual
Language Interfaces
Language-specific interfaces map the capabilities and styles of a programming language to the object
database model. Any action or data type that can be expressed in the interface language can become part of a
VERSANT database schema. This means that you use standard language statements to manipulate VERSANT.
There is no special VERSANT database language.
VERSANT object databases are most commonly used with languages that implement the concept of either
class or template and either delegation or inheritance. VERSANT can also be used with languages, such as C,
which are not object oriented.
In its present release, VERSANT provides language specific interfaces for C++ and C. Each language-specific
interface consists of several libraries of precompiled routines and, for typed languages, predefined data types.
An interface can include its own development tools, and can be used with other vendors' software
development products.
C/VERSANT Interface
The C/VERSANT interface can take advantage of most object model capabilities, including the ability to
define embedded, association, inheritance, and version relationships. C/VERSANT also implements all
VERSANT database functionality as both functions and utilities.
There is, however, no messaging concept in the pure C environment: when you define methods they are
executed as functions. Also, there is no runtime binding to functions, inheritance is limited to virtual
inheritance, and there is no concept of private versus public functions.
C++/VERSANT Interface
The C++/VERSANT interface implements the full object model. Methods are provided to define embedded,
association, inheritance, and version relationships, either at compile time or at runtime. C++/VERSANT also
implements complete VERSANT database functionality as both functions and utilities.
C++/VERSANT functionality is available through standard C++. This includes dynamic access to the database
schema for runtime type checking, locating subclass and superclass information, and identifying class
attributes. Any persistent class can have transient and persistent instances simultaneously.
25
VERSANT Database Fundamentals Manual
A VERSANT database stores schema information in class objects and data in instance objects. The
information in the class objects is slightly different depending on what language defined the class, but the
objects are accessible to all supported language interfaces.
Classes defined by C++ may have multiple inheritance, the attribute names of superclasses may be the
same as attribute names in derived classes, and classes may be embedded as attributes. Associations
among objects may be defined with links.
In order to allow multiple inheritance and class embedding, VERSANT assigns a unique database name to
each attribute. This name normally consists of the class name concatenated with the attribute name. If the
attribute type is another class, the database attribute name is a concatenation of the class name, the
embedded class name, and the attribute name.
For C++/VERSANT applications, the storage layout of an object is defined at compile time. Even though
the storage layout may be different among different C++ compilers, at run time the compiler assumes that
the object has a fixed size and layout, and that each attribute is of a particular domain.
J/VERSANT Interface
JVI (Java Versant Interface) implements the full object model. Methods are provided to define embedded,
association and inheritance relationships, either at compile time or at runtime. JVI also implements complete
VERSANT database functionality as both functions and utilities.
JVI functionality is available through standard Java. This includes dynamic access to the database schema for
runtime type checking, locating subclass and superclass information, and identifying class attributes. Any
persistent class can have transient and persistent instances simultaneously.
For more information about the JVI language binding, refer to the JVI documentation.
26
VERSANT Database Fundamentals Manual
Standards
Versant Object Technology Corporation is involved in efforts to define industry standards. Versant is
committed to and/or working on the following:
• ANSI (American National Standards Institute) X3H2 Structured Query Language, X3H7
Object-Oriented Information Systems, X3J4 Object-Oriented COBOL Task Group, X3J16 C++.
• PDES/STEP.
27
VERSANT Database Fundamentals Manual
VERSANT provides a lot of features. It is up to you to decide how best to use them for your needs.
If you are an experienced database developer or object programmer, then you will immediately
appreciate the power and flexibility that VERSANT offers.
If you are new to databases and/or object programming, the number of VERSANT features is so large
that VERSANT may be overwhelming at first. Accordingly, no matter what your background, we
recommend training, either by VERSANT or by third party vendors. Although our customers and industry
studies report order of magnitude gains in productivity with the use of object programming and object
databases, most also report that an initial period of training in the concepts involved has a high payoff.
Versant also offers on-site consulting, which can help during the start of the programming phase of a
project.
For information about VERSANT training classes or on-site consultation, please call the Versant Training
and Consulting Group.
Once you understand the underlying concepts, you will find VERSANT to be logical and predictable.
VERSANT is an expression of the object model of data with familiar and natural models for database
management and interface usage. However, the object model, normal database procedures and
protocols, and VERSANT must all be understood in order to create efficient application programs.
VERSANT has most features and options found in modern database systems.
For example, VERSANT has four different ways to retrieve objects, seven types of locks, and numerous
transaction alternatives. Among other things, you can move objects among databases, change schemas,
tune performance, create classes at run time, invoke database administration utilities at run time, and
move databases after they have been created.
If you do not have a database background, you must understand that VERSANT provides database
features that go far beyond mere object storage. To benefit from these features, you must know how to
use them. For example, you must know when to use update locks or else, in a workgroup environment,
your program will tend to get deadlock errors. You must use group operations to retrieve and update
large numbers of objects, to reduce network traffic. You must use cursor queries instead of simple
28
VERSANT Database Fundamentals Manual
queries to reduce the size of the result set. Also, you must remember to turn locking and logging on
when using VERSANT in a production environment, and, as with any production system, you must
remember to perform frequent database backups.
VERSANT supports all computational and control features found in its interface languages. It also extends
these languages to provide database, memory management, and process control features.
For example, VERSANT has numerous functions that control memory, five options for session memory
workspaces, low-level and high-level variable-length storage types, provision for controlled use of shared
memory by multiple processes, and multiple language, hardware, and compiler interfaces.
If you have a database background, you must learn about the best way to implement various tasks. For
example, you must know that although links are always safe, you must not use pointers to unpinned
objects.
VERSANT implements the object model of data, which provides an extraordinary amount of power and
flexibility.
For example, VERSANT allows you to define your own data types and establish inheritance, embedded,
and association relationships among objects. The advantages of using objects include dramatic gains in
programming productivity and the ability to create applications that were not previously practical.
However, these gains depend upon an understanding of the object model.
If you have a relational database background, you must learn the differences between is-a (inheritance),
has-a (association), and contains-a (embedded) relationships, and that most objects are found by
following links rather than by performing queries.
If you have a procedural programming background, you must learn about messages and classes and learn
that switch statements are rarely needed.
VERSANT extends operating system functionality, which provides important benefits in a heterogeneous,
workgroup environment.
For example, VERSANT provides heterogeneity among platforms, compilers, and languages and provides
controlled use of shared memory. It uses a client/server model, which means that VERSANT is scalable
and provides for the beneficial use of additional hardware resources.
No matter what your background, the extensions that VERSANT provides must be understood to be
29
VERSANT Database Fundamentals Manual
VERSANT is a complete database system with orthogonal session, locking, interface, usage, versioning,
and object management models. Because they are orthogonal, the usage, database, and interface models
provide flexibility; however, they must be used together in a consistent manner in order to achieve
results. For example, you must be in a session before you can interface with a database.
Some elements of VERSANT, such as the data model and database administration, are language
independent. Other elements, such as the interface model, rules, conventions, and usage are language
specific. Also, VERSANT users have, inevitably, different backgrounds and skill levels.
Because interface dependent and interface independent elements are orthogonal but interrelated, a
systematic presentation of VERSANT is necessarily arbitrary. There is no sequential, lock-step way to
explain VERSANT or, for that matter, any other database system.
As with any programming project, the principal phases of a VERSANT application development effort are
analysis/design, development/testing, and deployment/ maintenance. Following are brief comments
related to each of these project phases.
Analysis
You must have a firm understanding of your situation and project goals before beginning development of
your application.
Design
Experience suggests that the design phase of software development is even more critical to the success
of object oriented projects than it is to procedural programming projects. The reason is that the object
programming development lifecycle becomes shorter with a higher percentage of the time spent
understanding the problem to be solved and designing the needed classes. If you have a good design for
the classes in your application, implementation proceeds far more smoothly than with non-object
oriented languages. We recommend using a design methodology such as Booch and Rumbaugh along
with a CASE tool to record your design.
Development/Testing
During the Development phase of software development, it is critical to understand that database
transactions and the support of multiple users are significant considerations. Experience suggests that
you cannot "retrofit" transactions and concurrency into an application that assumes all of its data is in
memory and, thus, ready to be modified at will. Also, you should carefully identify whether there are any
bottleneck objects that must be write-locked by many transactions and will thus be a disproportionate
30
VERSANT Database Fundamentals Manual
1. Review of the object model (classes and associations) in the light of implementation issues.
3. Transaction model (how do you maximize concurrency and define units of work.)
5. Usage model (how users interact with the application and what tasks are most performed.)
6. Performance tuning.
Testing is a key component of any development project. Test to be certain that each of your classes do
what you expect. Test your database code not just with a single user, but also with the use load you
expect in production. Often, multi-user testing uncovers subtle flaws in an application's locking and
data-sharing strategy.
Class-level unit testing is essential early in development to catch minor design problems before they turn
into major rework headaches. Integration testing and multi-user testing is essential to ensure the system
works as you intend. Acceptance testing is critical to ensure that the application meets the user's
expectations.
Because design and development work interact, you will probably want to iterate through the design and
development stages numerous times. You will probably also want to iterate through development and
deployment, where a "semi-final" deployed product results from each iteration. For example,
performance tuning is an issue that is often considered during development/deployment iterations.
Deployment/Maintenance
You should design and implement your application with deployment and maintenance in mind. How will
users build, maintain, backup and restore your database? Where will your software reside on the system?
How will you reliably move your software from your development environment to your user's machine?
How will you handle upgrades to your software and database schema?
VERSANT provides tools and services to assist with all these tasks, and you can use third party tools with
VERSANT, but the primary responsibility to ensure that each phase is successful rests with you, the
software engineer and project manager.
Based upon our experience with customers, following are ten tips for success in using VERSANT. This is not
31
VERSANT Database Fundamentals Manual
meant as a definitive list of success tips, just a few suggestions based upon common problems.
Most projects do not spend enough time mapping logical designs to physical designs. The most
important thing is to identify the various objects that will be used and the operations that will be
performed on them.
Everything runs fast with fifty objects. Many design issues, bugs, and performance problems tend to
surface only when databases similar in size to the target production environment are used.
When an application is deployed, concurrency issues are often the most important factor in performance.
Developers tend to have high performance machines. It is important to determine what running an
application will be like for end users with more modest machines. For example, an application may run
quickly on a machine with a large amount of physical memory but become sluggish on a smaller machine
due to virtual memory paging.
We suggest that you write monitoring code that can be turned on and off and also write external scripts
that log resource consumption in various sectors of your programs. Data of this kind is extremely useful
both in debugging and in performance tuning.
You can use scripts to perform database administration, run batch jobs, prepare reports, and run long test
programs when you are not around. One of the most important factors in a successful deployment is
rigorous testing under a wide variety of conditions. Sometimes, problems don't show up until a program
has been running for days or weeks or tested with extremely large numbers of objects or users. If you run
your machine constantly, every time you run into a problem, you can add a unit test to a growing suite of
automated tests that can be re-run every time you make a change of any kind.
Keep short transactions short and hold locks for a minimum amount of time.
Open transactions hold locks, so avoid "think times" in which a short transaction is open while the
application waits for user input. Holding key objects locked will block other users from doing work.
Alternatives are to use timeouts during screen input/output or to gather all input from the user before
32
VERSANT Database Fundamentals Manual
updating objects.
In a multi-user environment, you cannot assume that you will always be the next person to convert a read
lock to a write lock. For example, programming the following sequence of events is deadlock prone:
begin transaction
read objects
write lock selected objects
update selected objects
end transaction
Update locks allow you to read objects without blocking other readers. They have the additional
advantage of guaranteeing you the next write lock.
Training in object languages and VERSANT typically pays for itself ten times over. There is so much
power and flexibility in using objects that taking time to just learn what can be done is time well spent.
33
VERSANT Database Fundamentals Manual
Chapter 2: Objects
VERSANT databases model data as objects. This chapter provides an explanation of the VERSANT object data
model.
Object Types.................................................................................................................................35
Object Elements ............................................................................................................................37
Object Characteristics.....................................................................................................................38
Object Status.................................................................................................................................42
Object Relationships.......................................................................................................................44
34
VERSANT Database Fundamentals Manual
Object Types
Software objects are a response to programming issues inherent in large and complex software applications.
Objects are useful if you want to organize large amounts of code, handle complex data types, model graph
structured data, perform navigational queries, and/or make frequent modifications to applications.
The term "object" has numerous meanings, which are clear in context. Following is listing of object types.
Object
A database "object" is a software construction that can encapsulate data and references to other objects.
Usually, the generic term "object" refers to an object containing data, but it can also refer to other kinds of
objects.
Instance object
A database object that holds data is called an "instance object". In a VERSANT database, an object has a
class that defines what kinds of data are associated with the object. Elemental (immediate) values are
stored as typed values.
Class object
When you create a class, VERSANT creates a special kind of object, called a "class object" or "schema
object," that stores your data type definition in a database.
You can define classes to contain any kind of data, including values, images, sounds, documents, or
references to other objects. Only a very few data types are difficult to implement in object databases. For
example, bitfields can cause problems in heterogeneous systems, and pointers to functions are difficult to
implement although easy to avoid.
C++/VERSANT — Objects are defined in a class using normal C++ mechanisms. VERSANT creates for
each class an associated runtime type identifier object that contains additional information needed by
C++. This runtime type identifier object is an instance of the C++/VERSANT class PClass. Each instance
object has an is_a pointer to its type identifier object in order to identify its class.
Versioned object
To track the evolution of the states of an instance object, VERSANT allows you to convert an instance
object to a "versioned object." There can be numerous versions of a particular instance object.
35
VERSANT Database Fundamentals Manual
Transient object
A transient object exists only while the program that created it is running.
Persistent object
A "persistent object" is an object can be stored in a VERSANT database. Persistent objects must derive
from the VERSANT PObject class.
When a complex object is read from a database, only those portions of it which are examined by the user
will actually be loaded into memory, allowing "large" objects to be lazily instantiated on a per object basis.
Performance options are provided so that you can explicitly control how referenced objects are read in.
C++/VERSANT — Both transient and persistent objects are dereferenced with the same syntax.
Stand-alone
Stand-alone object
C++/VERSANT — A stand-alone object is one created with the C++ new operator, with a C++/VERSANT
new operator or function, or an object defined as a local or global variable.
Embedded object
Smart object
A smart object automatically selects the correct version of an object based upon a pre-selected
configuration.
36
VERSANT Database Fundamentals Manual
Object Elements
Although objects are used as if they were a single structure, the implementation of objects involves several
discrete elements.
Object attributes
C++/VERSANT — The parts of an object that store data and associations with other objects are called
"attributes." An attribute is persistent when the object containing it is persistent. An attribute can be a
literal whose elemental value is self-contained, another object with its own attributes, an object that is an
array of other objects, or a reference to another object. Attributes are first defined in normal C++ class
files. After class files have been compiled, attribute definitions are loaded into a database and stored in
class objects. Actual data are stored in instance objects.
Object methods
C++/VERSANT — The parts of an object that store executable units of program code are called methods.
Methods are defined and implemented in normal C++ class files. After the class files have been compiled,
methods are linked with an application.
Attributes of class Coord are the coordinates x and y. Methods of class Coord are set(), which sets the
values of the coordinates, get_x(), which returns the value of the x coordinate, and get_y(), which
returns the value of the y coordinate.
37
VERSANT Database Fundamentals Manual
Object Characteristics
Object identity
One of the strongest concepts in object technology is object identity, because it makes possible such
features as persistent references to other objects and the ability to migrate objects among distributed
databases without having to change code that accesses the objects.
VERSANT assigns each persistent object a unique identifier called its logical object identifier or loid.
Logical object identifiers are composed of two parts: a database identifier and an object identifier.
The database identifier portion of a loid is an identifier for the creation database that is unique among all
databases in a system of databases. This identifier is created by VERSANT when the database is created.
To ensure that database identifiers are unique, all databases in a system of databases have their
identifiers stored in a single database system file named osc-dbid. When you create a database, your
system must be able to modify the system file, but once a database is created you do not need to be
connected to a network to perform tasks such as creating or managing objects in a local database.
The object identifier portion of a loid is an identifier that is unique within the creation database. This
identifier is created by VERSANT when the object is created. Because the object portion of the identifier
is based only upon other objects within the creation database, you only have to be connected to the
creation database when you create an object.
Because an object identifier is built upon a database identifier unique within a system of databases and an
object identifier unique within a database, it is guaranteed to be unique among all objects in a system.
The overhead in memory of a VERSANT object identifier, on 32-bit cpu architectures, is approximately
twenty-four bytes per persistent object, and four bytes for a non-persistent object. A link requires eight
bytes. For a particular database system, you can address 2^64 objects in 2^16 databases.
Object migration
The ability to migrate objects makes distributed databases practical. After an object has been created,
you may want to migrate it in order to place it physically closer to where it is most often used, which will
reduce network traffic, or migrate it because disk space is filling up on the machine containing the
creation database.
Object and database identifiers do not change when an object or database is moved or changed.
Identifiers are never reused, even after an object or database has been deleted. This means that code that
38
VERSANT Database Fundamentals Manual
references an object does not have to be changed each time an object is migrated.
Each persistent object must have a class object associated with it that contains its class definition. When
you migrate an object to a new database, if it is not already defined in the target database, a copy of its
class object is also migrated.
Because migrating an object instance includes migration of its definition in a class object, a particular class
object may be duplicated in numerous databases. However, once a class object has been migrated, it
exists as an independent object in that database. This means that to use a migrated object, you do not
have to be connected to its creation database.
If classes of the same name are defined differently in different databases, an attempt to migrate an object
with a different class definition will be automatically blocked. You can then use any of a variety of
mechanisms, such as a "synchronize class" routine, to resolve the differing class definitions.
Dynamic binding
When you define a new class that specializes its base class, you may want to redefine methods, such as a
"print yourself" method, for the new class. Inherited methods can be redefined in sub-classes.
Polymorphism
To use a method, you call an object method with a message. For a method to respond, the message must
have both the correct name and the correct signature. The number and data types of the arguments for a
method are called its signature.
You can overload a method to work for several classes and a varying number of arguments; this is called
polymorphism and can simplify control structures significantly. In effect, polymorphism binds a message
to a particular method at run time in a manner similar to case statements.
Situation modeling
An important advantage of using the object model is its ability to model situations in a realistic manner
than can be understood by others. Using classes to classify individual elements of a situation and then
using embedded, inheritance, and reference relationships to relate classes to one another results in a
model that directly reflects the real situation. This is much more understandable than a flat system of
normalized tables whose logic must be reassembled each time they are used in each application that
accesses them.
Schema modification
After you have completed a complex application, you will almost certainly want to extend or modify its
underlying classes to accomplish new tasks.
The set of object definitions associated with a particular database is called the "schema" of that database.
39
VERSANT Database Fundamentals Manual
You can add to and change the schema of a database at any time. In many cases, you will be able to
redefine classes by extending them to hold additional data, perform new tasks, and perform existing
tasks differently without changing applications that use the classes.
Predefined types
VERSANT predefines many elemental and class types that you can use with embedded, inheritance, and
association relationships. The following types available in all interfaces.
Vstr — A vstr provides low overhead, variable length storage for one or many elemental values.
Link vstr — A link vstr provides variable length storage for one or many references to other objects.
Bi-link vstr — A bi-link vstr provides variable length storage for one or many bi-directional references.
The C++/VERSANT interface also provides facilities to create the following types:
Parameterized — Parameterized types allow single definitions for classes that differ only by the data type
of their contents. This is useful for classes, such as sets, that have identical functionality and differ only in
the data type of their contents.
Smart — Smart objects automatically select the correct version of an object based upon a pre-selected
40
VERSANT Database Fundamentals Manual
configuration.
VERSANT also allows you to use third party class libraries to define your classes.
41
VERSANT Database Fundamentals Manual
Object Status
When you create or access a database object, VERSANT maintains information about its current status.
Following is a list of the status information maintained about each object.
When you create an object, VERSANT notes whether it is transient or persistent. Within the scope of a
transaction, transient objects are treated the same as persistent objects, but when you perform a commit,
only persistent objects are saved to a database.
Locks provide access guarantees to objects. For example, a write lock guarantees that you are the sole
user of an object, which is important if you want to update the object.
When you change an object, you must mark it as "dirty," which means that it will be updated at the next
commit. This improves performance significantly, because at the time of a commit VERSANT does not
have to compare the contents of each object you have used with its original contents in order to
determine which objects need to be updated.
You may want to track the evolution of the states of an object over time. To track object states, you can
convert regular, non-versioned objects into versioned objects.
When a non-versioned object is updated, its old data is overwritten. When a versioned object is checked
out for update, the old object is preserved and a new object is created with the new data. The new is
called a "child" of the original "parent" object. Versioned objects that share the same parent are called
"siblings."
Each version of an object has a unique object identifier, a number, and a version status, such as transient,
working or released.
All versions of an object are maintained by VERSANT in a version graph. Versioned objects behave
differently from non-versioned objects under certain circumstances, such as during a checkout and
checkin. For example, versioned objects are checked out with a null lock and become new objects.
42
VERSANT Database Fundamentals Manual
Object configuration
You can group specific versions of different objects into "configurations," and then automatically select
the correct object versions using a "smart object."
To improve workgroup functionality, an object can be checked out from a group database accessible to
many users to a personal database accessible to only one user. When an object is checked out, a copy of
it is made and placed into the personal database and a persistent lock is placed on the original object. This
allows you to work on an object for an extended period of time without network traffic.
Unless the object is a versioned object, the checkout copy in the personal database shares the same
object identifier as the source object in the group database. This replication ends when the possibly
changed object is checked back into the group database. Versioned objects become new objects when
they are checked out and when they are checked in.
When you checkout an object, you can set persistent object locks which will survive short transaction
commits and specify various other options, such as a request to be notified if another user is trying to
access the objects you have checked out.
C/VERSANT and C++/VERSANT — To manage memory efficiently and to improve access to objects of
current interest, VERSANT maintains an object cache in virtual memory that contains all the objects
accessed during a transaction. An object in the cache can be "pinned," which means that it will not be
swapped out of the object cache back to its source database. A pinned object is not guaranteed to be in
process memory, just in virtual memory.
Normally, pinning occurs automatically when you access an object, but you can explicitly unpin objects if
you are accessing a large number of objects and virtual memory is limited. VERSANT also allows you to
set nested pin regions to make it easier to pin and unpin specific sets of objects.
43
VERSANT Database Fundamentals Manual
Object Relationships
An object that contains an aggregation of embedded objects provides referential integrity and avoids
orphaned data. Using embedded objects can improve performance, because access to an aggregation of
objects is done in one step, and only one entity is locked. Embedding predefined types such as dates or
custom designed sets can also save a lot of programming time.
Some C++/VERSANT classes can be used only as embedded objects, while others can be used as
stand-alone objects.
When you define objects, you can specify a hierarchical structure of base and derived classes. In this
hierarchy, descendant objects "inherit" data and methods from their ancestors. This means that you can
store data in an instance as if it had all of the attributes that are in its ancestors, and you can manipulate an
instance as if it had all of the methods that are available to its ancestors.
Inheritance is an efficient way to describe a situation: base classes describe once what is common to all of
its derived classes, then each derived class describes only what is new or different about itself. Because
derived classes inherit both attributes and methods, inheritance enables stable data and code definitions
to be reused, extended, and customized.
An inheritance relationship is called an "is-a" relationship, because the derived class is a special form of its
base class.
C++/VERSANT — In C++, there can be two kinds of inheritance: single inheritance and multiple
inheritance. Single inheritance describes an object related to ancestors in a stack structure. Multiple
inheritance describes an object related to ancestors in a tree of stacks.
An important feature of the object model is the ability to associate objects that are logically related but
that are of different types. The ability to associate objects allows you to create graph structured data
relationships. For example, while inheritance is useful to define an employee as a kind of person, you
must create an association to relate an employee to a department.
44
VERSANT Database Fundamentals Manual
C++/VERSANT — The association of objects is similar to using a C++ pointer from one object to another,
assuming that both objects have been assigned a virtual memory address. However, pointers are not
valid for objects in a database but not in memory, and pointers are unreliable for objects that are not
pinned in virtual memory.
To create persistent database pointers, C++/VERSANT uses "links." Links are sometimes called "smart
pointers," because they are valid regardless of the location of the object, whether in memory or in a
database, and they remain valid even if you move the object from one database to another. The
transparency of links is important, because it allows you to write code that does not depend upon the
memory or database location of objects. Link relationships are called "has-a" relationships, because one
object "has a" link to another object.
There are several performance advantages to using links. Just as the fastest way to find an object in
memory is to use a pointer, it is much faster to find an object in a database using a link rather than a
query. This means you can move quickly among related objects by traversing the graph of relationships
rather than performing repeated queries and joins. Links also improve performance because linked
objects are retrieved only when you decide that you actually want the object by dereferencing the link.
You can use either links or arrays of links as attributes. You can create one-to-one, one-to-many,
many-to-one, and many-to-many associations using the predefined VERSANT link and link vstr data
types. VERSANT also has data types for "bi-links," which provide referential integrity and cascaded
deletes, and for containers, arrays, sets, lists, and dictionaries, which allow you to associate many objects
into a single structure.
45
VERSANT Database Fundamentals Manual
Chapter 3: Sessions
This chapter explains basic session concepts.
46
VERSANT Database Fundamentals Manual
Session Boundaries
You must start a session to use VERSANT databases, methods, data types, and persistent objects. The only
exceptions are methods that set session and environment parameters.
C++/VERSANT
When you are using C++/VERSANT, transient objects of VERSANT data types and transient objects of
classes derived from PObject that were created before a session should not be modified in a session.
Transient objects of VERSANT data types and transient objects of classes derived from PObject that
were created or modified during a session should be deleted before the end of the session.
47
VERSANT Database Fundamentals Manual
When you start a session, VERSANT creates the following session memory elements. The object cache, cache
table, and session tables may be created in client machine memory. The server page cache is in shared
memory on the machine containing the session database. All memory areas are maintained for you by
VERSANT.
Object cache
A cached object descriptor table tracks the location of all objects referenced during a session.
Session tables
Various session information tables track your processes, connected databases, and long and short
transactions.
Associated with the session database and each connection database is a page cache for recently accessed
objects.
48
VERSANT Database Fundamentals Manual
Session Elements
Session database
A session database is used to manage short transactions that occur during a session and to coordinate
two-phase commits. It serves as the target of checkouts and is the initial default database.
Either a personal database or a group database can serve as a session database. Only one personal
database can be accessed in a session, which must be the session database. More than one group
database can be accessed from a session database.
Specification of a session database does not change the database association of an object. For example,
when you commit a long transaction while ending a session, objects are returned to their source
databases regardless of which session database you are using.
To change the database used as the session database, you must end the session and start a new session.
Session name
The session name is used as the name of the short transactions that occur during the session.
Default database
A database designated as the default database is the location of new persistent objects and is used by
many functions when a null database name is specified as a parameter. Initially the default database is the
session database, but you can change the default after a session has started.
Connected databases
Once a session has started, you can connect to other databases. Each database connection starts a new
server process on the database machine.
Short transaction
When you start a "standard" session, VERSANT begins keeping track of your activities in a short
transaction.
Long transaction
A session is always in a single long transaction. A long transaction object in the session database tracks
49
VERSANT Database Fundamentals Manual
50
VERSANT Database Fundamentals Manual
Session Types
When you begin a session, depending upon your interface language, you may be able to specify the kind of
session that you want.
Standard session
In a standard session, you are always in a short transaction and in a long transaction, and you are using
the standard lock model.
Almost all tasks can be performed using short transactions with commits, checkpoint commits, rollbacks,
and savepoints. The standard VERSANT locking model satisfies most concurrency requirements.
If you do not make a specification for session type when you start a session, you will start a standard
session.
Shared session
In a shared session, you can use multiple processes, because the object cache is created in shared
memory.
A shared session is useful if you have a parallel processing machine or if it is likely that related
transactions may encounter delays, such as waits for locks, user input, or a printing activity. In a shared
session, when one process is delayed, others can continue.
Each time an application process joins a shared session, a corresponding server process will automatically
be started for each existing database connection (including the connection to the session database), and
if any application process in the session makes a new database connection, connections are made and
server processes are started as needed for all other processes in the session.
Multiple threads in a session are usually more efficient than multiple processes, so using multiple threads
is recommended over shared sessions.
See the chapter "Multiple Processes in a Single Session" for more information.
51
VERSANT Database Fundamentals Manual
You can start a session in which you can place one or more threads. If you start this kind of session, you
can also start additional sessions, and each of the additional sessions can have zero or many threads in it.
See the chapter "Multiple Thread(s) in Multiple Session(s)" for more information.
You can start a shared session in which no latches are held on the object cache when a process reads or
writes to a database. During this time, other processes can continue to locate and process objects in the
cache at memory dereference speed, although you will get an error if more than one process tries to read
or write to a database at the same time. This option is called the "single writer / multiple reader" option.
An optimistic locking session suppresses object swapping, prevents automatic lock upgrades, and
provides automatic collision notification.
52
VERSANT Database Fundamentals Manual
Units of Work
Following is the hierarchy of possible VERSANT units of work in various kinds of sessions. (Some interfaces
do not allow all types of sessions, such as shared or thread sessions.)
Long transaction
Standard session
Short transaction
Savepoint
Standard session with threads
Short transaction
Savepoint
Shared session with multiple processes
Short transaction
Savepoint
Many long transactions can co-exist and do not depend on the kind of session that you start.
Sessions can be a sequence of none to many. Short transactions are a sequence of one to many within a
session. Savepoints are a sequence of none to many within a short transaction.
53
VERSANT Database Fundamentals Manual
Session Operations
Begin session
Start a session, start a short transaction, and start or join a long transaction.
c o_beginsession()
c++ beginsession()
End session
End a session, commit the current short transaction, disconnect the application process from all
databases, close all session memory workspaces, and commit, rollback, or continue the current long
transaction.
c o_endsession()
c++ endsession()
End process and end session
End a session if it has not already ended, terminate the application process, and either commit or roll back
the current short transaction, depending upon the option supplied.
c o_exit()
c++ exit()
54
VERSANT Database Fundamentals Manual
Chapter 4: Transactions
Short Transactions .........................................................................................................................56
Short Transaction Description ......................................................................................................56
Short Transaction Actions............................................................................................................57
Short Transaction Usage Notes ....................................................................................................59
Short Transaction Hierarchy.........................................................................................................60
Short Transaction Effect on Memory .............................................................................................60
Long Transactions ..........................................................................................................................62
Long Transaction Description.......................................................................................................62
Long Transaction Actions ............................................................................................................62
Long Transaction Usage ..............................................................................................................63
55
VERSANT Database Fundamentals Manual
Short Transactions
Transactions are an essential database concept, because they ensure that data is always in a known state,
even in a distributed database environment.
As soon as you start a session, VERSANT begins keeping track of your activities in two kinds of transactions, a
short transaction and a long transaction. For information about long transactions, see the chapter "Long
Transaction".
Atomic — When a short transaction ends, the results of all actions taken in the transaction are either
saved or abandoned. This is an important database feature, because it ensures that data is always in a
consistent and known state.
Durable — Results are either saved permanently or abandoned permanently: no further undo or redo
operations are possible.
Independent — When you are operating on locked objects in a transaction, no other user can intrude
upon your work. While in a transaction, you can operate on your objects as if you were the sole user of a
database.
Coordinated — Objects in a transaction are locked, which means that your work is coordinated with
other users in a workgroup environment.
Distributed — A two phase commit protocol ensures that data is always in a known state even when you
are working with objects in numerous databases in a distributed database environment.
Ever-present — You are always in a short transaction. When you end a short transaction, another is
automatically started for you.
If you are an advanced user, you may want to create special kinds of short transactions. If you have a special
need you can start sessions in which you can use multiple processes in a transaction.
A session is also a unit of work. For information about sessions, see the chapter "Session."
56
VERSANT Database Fundamentals Manual
A commit saves your actions to the databases involved and releases short locks. A commit also
releases objects from cache memory, erases all savepoints, asserts persistent locks, and starts a
new short transaction. A commit makes no changes to transient objects.
Changes made in a short transaction are not visible to other users until you commit them. This
means that others cannot be confused by seeing partial changes. However, objects that have
been flushed to their database (by swapping, queries, or deletions) will be visible to users using
null locks in dirty reads.
c o_xact()
c++ commit()
xact()
Checkpoint commit short transaction
A checkpoint commit performs a save and holds object locks. A checkpoint commit also
maintains objects in cache memory, erases all savepoints, asserts persistent locks, and starts a
new short transaction. A checkpoint commit makes no changes to transient objects.
Changes made in a short transaction are not visible to other users until you commit them. This
means that others cannot be confused by seeing partial changes if they are using read locks.
However, objects that have been flushed to their database (by swapping, queries, or deletions)
will be visible to users using null locks in dirty reads.
c o_xact()
c++ checkpointcommit()
xact()
Rollback short transaction
A rollback abandons your actions, releases short locks, and restores databases to conditions at
the last commit or checkpoint commit. A rollback also releases objects from cache memory,
erases all savepoints, and starts a new short transaction. A rollback makes no changes to
transient objects.
If your application or machine crashes, VERSANT will automatically roll back the current
incomplete short transaction.
57
VERSANT Database Fundamentals Manual
c o_xact()
c++ rollback()
xact()
Begin session
Beginning a session starts a short transaction. During a session, you are always in a short
transaction.
c o_beginsession()
c++ beginsession()
End session
Setting a savepoint creates a snapshot of database conditions to which you can selectively
return without ending the short transaction. Setting a savepoint makes no changes to databases,
locks, transient objects, or the memory cache.
When a savepoint places a record of current conditions in the database log, changes to an
object are visible to other users if they access the object with a null lock in a dirty read.
You can set as many savepoints as you want in a normal short transaction.
Using savepoints is a way of working down a set of instructions toward a solution and then
backtracking if some sub-set of the solution fails.
Savepoints are not compatible with optimistic locking, however, because setting a savepoint
flushes objects to their databases, which resets locks.
c o_savepoint()
c++ savepoint()
Undo to savepoint
Undoing a savepoint returns database conditions to the immediately previous savepoint; if there
is no immediately previous savepoint, conditions will be restored to those that existed at the last
commit.
Undoing a savepoint makes no changes to databases, locks, or transient objects, but it does
invalidate the object memory cache.
58
VERSANT Database Fundamentals Manual
c o_undosavepoint()
c++ undosavepoint()
Turn logging on
To use short transactions in your application, you must enable logging in your database. See the
VERSANT Database Administration Manual for information on turning logging on and off.
Turn locking on
To use short locking in your application, you must enable locking. See the VERSANT Database
Administration Manual for information on turning locking on and off.
Keep short transactions short, because locks held by transactions block other users and work not
committed will be lost if the application or machine crashes.
Commit a short transaction only when the states of the objects you are working with are internally
consistent.
Build short transactions into your application from the beginning. Transactions are essential to data
consistency and workgroup concurrency, yet they can be hard to retrofit into an application as they are a
major structural element.
If you have an attribute of a persistent object that contains a link, link vstr, bilink, or bilink vstr, the
attribute cannot contain a link to a transient object at the time of a commit.
59
VERSANT Database Fundamentals Manual
For example:
Changes in transactions 1, 3, and 4 are committed to the database, while changes made in transaction 2 are
discarded.
60
VERSANT Database Fundamentals Manual
Afterwards for C
Can use o_object in a variable yes yes yes yes no
Can use a vstr yes no yes no no
Can use pointer to persistent obj no no yes no no
61
VERSANT Database Fundamentals Manual
Long Transactions
Each time you check out a non-versioned object with a read or write lock, that object becomes associated with
the current long transaction. When you are done with the checked out objects, you can return them to their
source databases in a single step by ending the long transaction.
When non-versioned objects are checked out with a read or write lock, object identity is maintained. This
means that links to checked out objects are still valid. If a linked object is also checked out with an object
containing the link, the link will point to the object in the checkout database. If the linked object is not
checked out, the link will point to the object in its original database.
When you start a session, you can start a new long transaction by specifying a new long transaction name or
join an existing long transaction by specifying the name of an existing long transaction. If you do not specify a
long transaction name, then a default name is used and you either create or join a long transaction with the
default name. The default name is your login user name.
You can create numerous long transactions to track differing sets of checked out objects. A session takes
place within a single long transaction that can last any length of time.
Committing, rolling back, or continuing a long transaction has no effect on the tracking of checked out objects
and persistent locks on objects not associated with that long transaction.
See the chapter "Moving Objects" for information about checkins and checkouts.
62
VERSANT Database Fundamentals Manual
When you start a session, you can begin or join a long transaction.
c o_beginsession()
c++ beginsession()
Commit long transaction
A long transaction commit returns write-locked, non-versioned objects associated with that long
transaction to their source database and drops the copies made in the personal database used for the
checkout.
A long transaction commit also drops all persistent locks on non-versioned objects that were set during
the long transaction, including those persistent locks set explicitly without making a checkout.
c o_endsession()
c++ endsession()
Rollback long transaction
A long transaction rollback drops all checked out, non-versioned objects associated with that long
transaction from the checkout database and leaves the checked out objects in their current state in their
source databases.
A long transaction rollback also drops all persistent locks set during the long transaction, including those
persistent locks set explicitly without making a checkout.
c o_endsession()
c++ endsession()
Continue long transaction
Continuing a long transaction means that you can rejoin it at a later time with no changes to persistent
locks or to the checkout status of objects associated with the long transaction.
c o_endsession()
c++ endsession()
Information about a long transaction, including the objects associated with the long transaction and their
source databases, are stored and maintained for you by VERSANT in a long transaction object. This long
transaction object is created and updated in the database used as a session database. Thus, access to a
particular long transaction depends on your choice of a session database.
63
VERSANT Database Fundamentals Manual
You do not have to end a long transaction to check in objects. You can override long transaction tracking
of objects with an explicit checkin of particular objects. However, you cannot check in objects not
associated with the current long transaction.
If an object checked out in a long transaction has been previously, explicitly checked in before the long
transaction ends, then ending the long transaction does not change the object state but it does release all
persistent locks.
Committing a long transaction will fail if you are not connected to all source databases for the
checked-out objects.
You can define any number of long transactions to keep track of discrete sets of objects. These long
transactions can be in the same or different personal databases.
Once you have created multiple, named long transactions, you can move from one long transaction to
another within an application by stopping the current database session and starting a new session. You
must keep track of the long transaction names you have used.
While in a session, you can work with all objects that have been checked out to your current session
database in all of the long transactions you have created, although accessing an object in a long
transaction other than the one in which it was checked out does not change its association with the long
transaction used to check it out. When you end a long transaction, only the objects associated with that
long transaction are returned to their source databases and have their persistent locks released.
Successive sessions in a long transaction can be started with any session option that is valid for your
platform and interface, such as the nestable transaction, shared object cache, and custom locking
options.
You cannot access a checked out object from its source database.
Once you have checked out an object with a persistent read or write lock in a long transaction, you
cannot access it from the target/session database in its source database in either that long transaction or
in any other long transaction, unless it is a versioned object.
If you have checked an object out with a null lock, you can still access it in its source database, because a
checkout with a null lock creates a new object with its own identity and existence.
64
VERSANT Database Fundamentals Manual
Long transaction tracking of checked-out objects occurs whether logging is on or off in either source or
target databases. However, changes to a database cannot be rolled back if logging is off under the
following conditions:
If a commit fails after a checkout, the status of long transaction tracking is indeterminate; copies of the
checked-out objects may or may not have been created in the session database.
If a commit fails after a checkin, the status of objects returned by the checkin is indeterminate; the copies
in the session database may or may not have been deleted.
If a commit fails after a checkout, the status of persistent locks is indeterminate; they may or may not have
been set.
If a commit fails after a checkin, the status of objects being returned by the checkin is indeterminate; the
original objects may or may not have been updated to the states of the checked-out objects.
If your personal database crashes, persistent locks in source databases are maintained. During recovery
after a crash, if logging was on, long transactions are reconstructed, and objects are restored to their state
at the last short commit.
If you are working on checked out objects in a personal database and a group database crashes, you can
continue to work on your checked-out objects. When the group database recovers, it remembers which
objects you have checked out and what locks you placed on them.
65
VERSANT Database Fundamentals Manual
Chapter 5: Locks
Short Locks ...................................................................................................................................67
Lock Overview ...........................................................................................................................67
Locks and Transactions ...............................................................................................................67
Short Lock Features ....................................................................................................................68
Short Lock Types........................................................................................................................69
Short Lock Interactions................................................................................................................70
Short Lock Actions......................................................................................................................72
Short Lock Protocol ....................................................................................................................75
Short Locks and Queries..............................................................................................................78
Short Intention Lock Description ..................................................................................................79
Short Intention Lock Mode ..........................................................................................................80
Short Lock Precedence ...............................................................................................................81
Short Lock Interactions................................................................................................................82
Short Locks and the First Instance.................................................................................................83
Persistent Locks .............................................................................................................................85
Persistent Lock Description..........................................................................................................85
Persistent Lock Mode .................................................................................................................85
Persistent Lock Level...................................................................................................................87
Persistent Lock Queuing..............................................................................................................87
Persistent Lock Event Notification .................................................................................................88
Persistent Lock Precedence .........................................................................................................90
Persistent Lock Request Handling.................................................................................................90
Persistent Lock Actions ...............................................................................................................93
66
VERSANT Database Fundamentals Manual
Short Locks
Lock Overview
Locks provide guarantees which allow multiple processes to access the same objects in the same database at
the same time in a cooperative, controlled, and predictable manner.
If you were to be the sole user of a database, you would not have to be concerned with locks. And, at times
when you are the sole user of a database, you may want to turn locking off for the database and ignore locks
entirely. However, locks are essential in a multi-user environment.
Short transactions are, by definition, units of work in which concurrency is provided by short locks.
Typically, for concurrency reasons, short locks are held for short amounts of time.
Short locks are released when a short transaction ends with a commit or rollback. Accordingly, for
convenience in managing short locks, short transactions typically define units of work that take a short
amount of time, such as seconds or minutes. However, since VERSANT provides mechanisms for
upgrading and downgrading locks on specific objects and for writing specific objects to a database,
"short" transactions do not have to be short.
Ending a session also ends the current short transaction (you can specify ending a session with either a
short transaction commit or rollback.)
Long transactions manage checkin/checkout operations that take a long amount of time, such as many
minutes, hours, days, or weeks. A persistent or long lock manages concurrency during a long transaction.
A persistent lock has all the features of a short lock plus added functionality, such as the ability to survive
the end of a short transaction, database session, and a system disruption. Persistent locks are not tightly
coupled with long transaction checkouts. You can also set a persistent lock if you want to use an object
during an extended period of time without checking an object out.
67
VERSANT Database Fundamentals Manual
For more information on persistent locks, see the section "Persistent Lock."
A lock on an object provides an access guarantee. The access guarantee is provided by blocking certain
kinds of actions by other users.
Locks can be set on all kinds of objects: instance objects, versioned objects, and class objects.
Placing a lock on a class object has the effect of placing the same lock on all instances of a class. This is far
faster than retrieving all objects of a class and placing individual locks on each object.
Locking an object does not lock objects which are the targets of links in the locked object.
In a particular transaction, an object can have only one lock on it at a time. If you separately request
different locks on the same object, the lock placed depends upon the relative precedence of the locks.
For example, if you request a read lock on an object for which you already hold a write lock, the read lock
request is ignored; if you request a write lock on an object for which you already hold a read lock, the
read lock is replaced by a write lock.
The precedence of locks is: write > update > read > null. An exception to this rule occurs if you are
using multiple processes in the same session. In this case, different processes can place different locks if
they are compatible. For example, different processes can request and receive a read lock and an update
lock on the same object, but the net effect is the same to outside users: the highest lock prevails.
68
VERSANT Database Fundamentals Manual
Locking an object also prevents the class object from being changed.
When you place a read, update, or write lock, VERSANT internally places a special kind of lock on the
class object which prevents it from being changed while the instance object is locked.
Strict two
two-phase
-phase locking is used.
VERSANT uses a strict, two-phase locking strategy. By "strict" is meant that locks are set before work
starts rather than at commit time when locks might not be available. Strict locking prevents other
applications from modifying an object while you are using it and is the appropriate strategy in a multiple
user environment. By "two-phase" is meant that all locks are gathered in one phase and then released in a
second phase, when the transaction ends.
By default, VERSANT uses an "implicit" locking strategy in which methods automatically obtain locks as
needed. For example, marking an object as dirty automatically obtains a write lock. You can override the
VERSANT implicit locking strategy in several ways. You can explicitly upgrade locks, change the default
lock, and/or change the lock wait time.
A write lock guarantees that you are the sole user of an object and that you are looking at the current
state of an object. It is useful when you want to update an object. A write lock provides its guarantee by
blocking all other requests for a write, read, or update lock on a particular object. A request for a write
lock is blocked if the object already has a read or update lock.
c WLOCK
c++ WLOCK
Short update lock
An update lock allows you to read an object and get the next available write lock on it. It is useful if you
want to look at an object now while knowing that you will later want to update it. An update lock provides
its guarantee by blocking all other requests for a write or update lock. It does not block other requests for
a read lock, but if another user tries to change a read lock to a write lock, that request may cause a
deadlock error that they must handle or have their application terminated. A request for an update lock is
69
VERSANT Database Fundamentals Manual
A read lock guarantees that an object will not be changed while you are looking at it. It is useful when you
want to look at an object but not change it. A read lock provides its guarantee by blocking all other
requests for a write lock. It does not block other requests for a read or update lock. A request for a read
lock is blocked if the object already has a write lock.
c RLOCK
c++ RLOCK
Short null lock
A null lock, also called a "snapshot" lock, provides no access guarantees and, strictly speaking, is not a
lock at all. Specifying a null lock is useful when you want to look at the current state of an object without
placing a lock or waiting for other locks to be released. Looking at the current state of an object without
placing a lock is sometimes called a "dirty read," because there are no guarantees that the object will not
be changed while you are looking at it. That is, a null lock does not block other requests for a write, read,
or update lock. A request for a null lock is never blocked.
c NOLOCK
c++ NOLOCK
70
VERSANT Database Fundamentals Manual
When you request a short lock, the system immediately tries to obtain it. A short lock is granted on an object if
the following conditions are true:
• There are no incompatible short locks. For example, a read lock is not granted on an object that already
has a write lock.
Since the lock mode of a persistent lock specifies a matching short lock, this condition is usually similar to
the first condition. It differs if you have turned locking off for a database, since turning locking off for a
database disables short locks but has no effect on persistent locks. This means, for example, that if
locking has been turned off, you will not be able to set a short write lock, but you will be able to set a
persistent write lock which will have the same effect as a short write lock.
If your request succeeds, the system places your short lock immediately.
If your request for a short lock is blocked by an incompatible short or persistent lock, your application pauses
either until the object becomes available or until your request times out. If your request times out, you will
receive a "lock timeout" error. The length of time a request waits is determined by the value of the
lock_wait_timeout parameter in the Server Process Profile.
Deadlocks
Potential deadlocks are detected immediately and a "would-cause-deadlock" error is sent to the user creating
the potential deadlock. For example, if two users have read locks on an object and then both request a write
lock, the first user to request a write lock is blocked, and the second user receives a "would-cause-deadlock"
error message. (In this case, the first user would receive the lock if a lock wait time-out did not occur.)
VERSANT directly handles complex, single-database deadlocks with any number of clients and any number of
locked objects. (An example of a "complex" deadlock is "A waiting for B waiting for C waiting for D waiting for
A".) VERSANT uses timeout mechanisms to detect multiple database deadlocks.
71
VERSANT Database Fundamentals Manual
The following set a short lock implicitly or allow you to specify a short lock in a parameter.
c o_acquireilock() set intention lock
o_acquireplock() set persistent lock
o_checkin() check in objects
o_checkinreturnobjs() check in objects
o_createvsn() create version
o_deleteobj() delete object
o_deletevsn() delete version
o_dropattr() drop attribute
o_dropclass() drop class
o_dropinst() drop instances
o_gcheckout() check out objects
o_gdeleteobjs() delete objects
o_getclosure() find object and linked objects
o_greadobjs() get objects
o_locateobj() get object
o_locatevsn() get version
o_new_attr() create attribute
o_pathpcheckout() find and checkout object
o_pcheckout() find and checkout object
o_preptochange() set object dirty
o_preptoversion() create versioned object
o_refreshobj() refresh object
o_refreshobjs() refresh objects
o_renameattr() rename attribute
o_select() find object
o_setdirty() dirty object
c++ (type*)() cast link
acquireilock() set intention lock
72
VERSANT Database Fundamentals Manual
73
VERSANT Database Fundamentals Manual
74
VERSANT Database Fundamentals Manual
When a session begins, the default short lock is a read lock. You can reset the default.
c o_beginsession() begin session
o_setdefaultlock() set default lock
c++ beginsession() begin session
set_default_lock() set default lock
No effect
checkpoint commit
pin object
release object
set savepoint
undo savepoint
unpin object
write objects
zap object cache
A "deadlock" occurs when two transactions both hold read locks on the same object, both transactions then
attempt to upgrade their locks to a write lock, and neither transaction can continue because both are waiting
for the other to release their read lock.
To avoid deadlocks and maximize concurrency, the following voluntary locking protocol is recommended:
• If you want to snapshot read the current state of an object, then request a null lock. You will
then get the object with no waiting and no blocking. Of course, the state of the object you get
may become obsolete almost immediately.
• Use a read lock for objects that you know you do not want to modify. This allows others also to
read the object.
75
VERSANT Database Fundamentals Manual
• Use an update lock to read objects that you may later modify. This allows others also to read the
object. If you decide to change the object, then request a write lock at that time. This keeps your
write lock as short as possible.
• Never upgrade directly from a read to a write lock, because this can create a deadlock situation.
Also, do not upgrade from a read lock to an update lock: get an update lock right from the
beginning.
Time 2 — You want to update the same instance, so you ask for a write lock on it. The request is blocked and
your application waits until the request can be granted or the request times out.
Time 3 — Other users release their read locks by committing or rolling back their transactions.
Time 4 — If your request has not timed out, your application resumes, and you obtain a write lock on that
instance.
Time 6 — You commit the change and release the write lock.
76
VERSANT Database Fundamentals Manual
Time 1 — Someone has a write lock on an instance of a class, and many others have read locks on other
instances of the same class.
Time 2 — You want to create a report using the latest state of all objects at a particular moment in time. You
request a read lock on the class object, but your request is blocked by the write lock on one instance of the
class. Your application waits until the request can be granted or until the request times out.
If the request times out, if you are creating a report, you might want to consider asking for a null lock on the
instances to gain immediate access, although you would not be guaranteed to be looking at the latest state of
the objects. You may also get an inconsistent set of objects.
Time 3 — The user with a write lock on the instance executes a commit, which releases the write lock.
Time 4 — If your request has not timed out, your read lock on the class object is now granted and coexists
with other read locks on instances of that class. This means that no other user can modify any instance of the
class.
Time 5 — You can now print your report knowing that you are dealing with the latest state of all objects at that
moment in time.
Time 1 — Numerous users are using a particular object, and they all have read locks on that object. But
everyone has decided to follow recommended locking protocol for updates.
Time 2 — Someone decides to first read and then update an object and requests an update lock. The request
is granted even though other users also have read locks.
Time 3 — You decide that you want to read and then update the same object, so you request an update lock.
The request is blocked because an update lock already exists on that object. Your application waits.
Time 4 — The first user requests a write lock. This lock upgrade request is blocked by existing read locks by
other users.
Time 5 — Other users with read locks finish and the user with the update lock gets a write lock.
Time 6 — The other user finishes, and the object becomes available. If your request has not timed out, your
application resumes and acquires an update lock. You are now guaranteed to be the next person to get a write
lock on that object.
The following example illustrates lock interactions and lock precedences. In the example, "You" and "UserB"
77
VERSANT Database Fundamentals Manual
both try to use the same object. In the example, even though you follow the recommended protocol, your
work is disrupted because "UserB" does not follow the recommended protocol.
Time 1
Time 2
Time 3
Time 4
Time 5
Unless you handled the error and rolled back your transaction, your application continues to get a "would
cause deadlock" error. Even though you followed the recommended protocol, UserB gets the write lock
and continues.
78
VERSANT Database Fundamentals Manual
Depending on your interface language, there may be several forms of a select routine. Some will set the
default lock on a class, and others will let you set an instance and/or class lock.
You can set an inheritance flag in a select statement. If set to TRUE, the inheritance flag will cause the query to
evaluate instances of both the specified class and also its subclasses. If you do set the inheritance flag, your
choice of short lock will be set on both the class object of the query class and on the class object of each
subclass instance returned by the query.
If you want to lock all objects of the classes involved in the query, then specify a read or write lock in the
query method. The effect will be the same as setting a read or write lock on all instances of the class and
subclasses involved in the query, although, to improve performance, VERSANT actually only sets a read or
write lock on the class objects for the instances returned.
If you do not want to lock objects returned by the query until you dereference them, then do not specify a
short lock mode. In that case, VERSANT will set a special kind of read lock, called an "intention read lock", only
on the class object. The effect is to prevent the class objects from being changed while you are looking at their
instances. If you have specified either an intention lock or have specified a null lock in a query statement, then
you must place instance locks separately, which will be done automatically when you dereference the links.
You do not need to know about intention locks unless you have special concurrency needs, because
VERSANT automatically sets them on class objects whenever you set a lock on an instance object.
The purpose of intention locks is to prevent changes to class objects while you are using an instance of the
class. If your concurrency needs are unusual, then you may need to know about intention locks.
You can place a normal or intention lock on a class object. Intention locks have the same effect on a class
object that a normal lock has on an instance object, except that intention locks do not block one another. For
example, an intention read lock on a class object prevents it from changing, but an intention read lock does
not block an intention write lock.
To maximize access to objects, an intention lock on a class object has no direct effect on instance objects. For
example, an intention read lock on a class object does not block a write lock on an instance of that class.
However, intention locks on class objects have an important indirect effect on instances, because to place a
lock on an instance, a corresponding intention lock must also be set on the class object.
Intention locks are set implicitly on class objects when you request a lock on an instance. For example, if you
set a read lock on an object, VERSANT will set an intention read lock on the class object.
79
VERSANT Database Fundamentals Manual
If you use a query routine without specifying a lock mode argument, then the intention lock equivalent of the
current default short lock is placed on the class objects for the instances returned.
You should not set intention locks on instances. Although setting an intention lock has the same effect as a
normal lock, this is not good practice. However, as soon as you try to do something meaningful, such as using
the "get attribute" routine in C/VERSANT or dereference an object in C++/VERSANT, an appropriate lock is
set.
An intention read lock on a class object is compatible with an intention write lock on the class, and a write
lock on an instance of the class. An intention read lock is useful if you want to prevent others from
changing a class object while you are using instances of the class.
An intention read lock is set implicitly by VERSANT on a class object when you request a read lock of an
instance of the class.
A request for an intention read lock on a class object is blocked only if it has a write lock.
If you have an intention read lock on a class object, other users can still use instances of the class in a
normal manner. They will also still be able to read the class object.
The terms "intention read lock" and "intention share lock" are synonymous.
c IRLOCK
c++ IRLOCK
Intention write lock
An intention write lock on a class object prevents other users from reading or updating it but allows them
to use instances of the class. An intention write lock is useful when you want to prevent the class object
from being changed or read.
An intention write lock is set implicitly by VERSANT on a class object when you request a write lock on an
instance of the class.
A request for an intention write lock on a class object is blocked if it has a read, update, or write lock. It
will not be blocked by another intention read or intention write lock.
80
VERSANT Database Fundamentals Manual
The terms "intention write lock" and "intention exclusive lock" are synonymous.
c IWLOCK
c++ IWLOCK
Read with intention to write lock
A read with intention to write lock prevents a class object from being changed and has the same effect as
placing read locks on all instances of the class.
The terms "read with intention to write lock" and "share with intention exclusive lock" are synonymous.
c RIWLOCK
c++ RIWLOCK
81
VERSANT Database Fundamentals Manual
Interactions of normal locks occur when a process requests a lock on a object that already has a lock: to
be granted, the guarantees of the new lock must be compatible with the guarantees of any existing locks.
These interactions are the same for both instance and class objects.
Interactions of normal and intention locks occur when a process requests a lock on an instance and the
system then attempts to place a matching intention lock on its class object: to be granted, the guarantees
of the new intention lock must be compatible with the guarantees of any existing locks.
The interactions of normal locks are relatively straightforward. For example, multiple read locks on the same
object are compatible, but an object can have only one write lock.
The interactions of normal and intention locks are more logically indirect. Because instance locks cause class
object intention locks:
• A lock on an instance object can block a request for a lock on a class object.
For example, a read lock on an instance of a class blocks a request for a write lock on a class object,
because a read lock on an instance also sets an intention read lock on the class object, and an intention
read lock blocks a write lock.
• A lock on a class object can block a request for a lock on an instance object.
For example, a write lock on a class object blocks a read lock on an instance of the class because to get a
read lock on an instance, you must also get an intention read lock on the class object. However, a write
lock blocks an intention read lock.
82
VERSANT Database Fundamentals Manual
write lock Blocks write, read/intention write, intention write, update, read,
intention read
read/intention write Blocks write, read/intention write, intention write, update, and read
lock locks.
intention write lock Blocks write, read/intention write, update, and read locks.
update lock Blocks write, read/intention write, intention write, and update locks.
read lock Blocks write, read/intention write, and intention write locks.
Normally, when you create or modify an instance of a class, VERSANT acquires an "Intention Write Lock" on
the class object in order to prevent the class definition from being changed while you are creating or
modifying an instance of that class. An Intention Write Lock allows multiple transactions to create or modify
instances.
Beginning with Release 6.0.0, the creation of the first instance of an empty class is treated as a special case.
83
VERSANT Database Fundamentals Manual
When inserting the first instance, VERSANT acquires the much stronger Write lock on the class object. This
stronger lock will block all other transactions trying to insert instances.
This change eliminate all possibilities of transaction inconsistencies when inserting the first instance of a class,
but it creates the possibility of a deadlock or lock timeout should multiple transactions try to simultaneously
insert instances on a previously empty class.
You may need to be aware of this behavior if you are installing a new production database or working in a
development environment, because this behavior may mean that you may need to add application code to
handle a deadlock or lock timeout. Alternately, whenever you define a class, you might want to insert a
dummy instance and commit the change to initialize the class. The dummy instance can later be deleted, after
other instances have been created.
This situation rarely occurs in production applications, because the case of an empty class is not common.
84
VERSANT Database Fundamentals Manual
Persistent Locks
Because persistent locks imply a short lock, see also the section "Short Lock."
As with a short write lock, a persistent write lock guarantees that you are the sole user of an object.
A checkout of an object with a write lock creates a copy of the locked object in your personal database
and places a persistent write lock on the object in the group database. Other users cannot check out the
same object.
A write checkout does not change the identity of the object. When you check in a write locked object,
the copy in the personal database replaces the source object.
85
VERSANT Database Fundamentals Manual
c O_PL_WRITE
c++ O_PL_WRITE
Persistent read lock
As with a short read lock, a persistent read lock guarantees that an object will not change.
A checkout of an object with a read lock creates a copy of the object in the personal database and places
a persistent read lock on the object in the group database. Other users can also check out an object that
has a persistent read lock, but they cannot modify it.
A read checkout does not change the identity of the object. When you check in a read locked object, the
copy in the personal database is dropped, and the lock on the source object is released.
c O_PL_READ
c++ O_PL_READ
Persistent null lock
As with a short null lock, a persistent snapshot lock provides no access guarantees and is really no lock at
all.
A checkout of an object with a null lock creates a copy in your personal database without regard to its
current lock state and without placing a lock yourself. It is useful when you want just to view data in its
current state without modifying it and do not want to wait for release of any lock placed by another user.
A null lock checkout creates an unlocked copy of the object with a new object identifier. When you end
the long transaction with which the checked out object is associated, the copy in the personal database is
not dropped. Explicitly checking in an object checked out with a null lock is the same as migrating an
object to a new database.
If you request a null lock in association with a checkout, any requests for a hard or soft lock level or for a
particular queuing option are ignored.
86
VERSANT Database Fundamentals Manual
c O_PL_NOLOCK
O_PL_SNAPSHOT
c++ O_PL_NOLOCK
O_PL_SNAPSHOT
A hard level means that your persistent lock cannot be broken by another application process.
c O_PL_HARD
c++ O_PL_HARD
Persistent soft lock
A soft level means that your persistent lock can be broken by another application process.
c O_PL_SOFT
c++ O_PL_SOFT
When you try to commit a transaction involving an object with a broken lock, an error occurs.
If the object is not available for locking, VERSANT will keep trying to obtain the lock until the request
times out.
If the object becomes available before the request times out, then the lock request will be granted.
If the object is not available after the request times out, then the error message OM_LO_BE_INCOMPAT will
be returned, which means that there is already an incompatible persistent lock on the object.
The length of the time-out period is determined by the value of the server process parameter
lock_wait_timeout in the database server profile file profile.be.
c O_PL_WAIT
c++ O_PL_WAIT
Persistent lock reserve
If the object is not available for locking, VERSANT will immediately return the error message
87
VERSANT Database Fundamentals Manual
OM_LO_BE_INCOMPAT, which means that there is already an incompatible persistent lock on the object,
and then place a lock reservation on the object.
Placing a lock reservation on an object means that a notification message O_PL_OBJ_READY will be sent
when the object becomes available.
Getting the O_PL_OBJ_READY message does not mean that you now have a lock: you must repeat your
lock request and that request will be evaluated again in the context of any other locks that exist at the
moment when the request is made.
c O_PL_RESERVE
c++ O_PL_RESERVE
Persistent lock give up
If the object is not available for locking, VERSANT will immediately return the error message
OM_LO_BE_INCOMPAT, which means that there is already an incompatible persistent lock on the object,
and make no further attempts to acquire the lock.
c O_PL_NO_BLOCK
c++ O_PL_NO_BLOCK
88
VERSANT Database Fundamentals Manual
The persistent lock event notification options can be combined with the logical OR operator.
Event notifications are normally sent in the form of electronic mail, but you can customize event
notification. For instructions on how to customize event notification, please call Versant Technical
Support.
The From: field identifies the user who generated the event. The To: field identifies the user affected by
the event.
The system evaluates your outstanding message requests each time a short transaction ends with a
commit or rollback.
You can also monitor objects for object related events by using a separate "monitor object" statement.
Monitoring an object tells you if another application process has versioned an object, read an object, or
committed an update on an object previously checked out in write mode. The read option includes, but is
not limited to, notification when an object is checked out in read mode.
Monitor object
You can also monitor objects for object related events by using a "monitor object" function or method.
c o_monitorobj()
c++ monitorobj()
st monitorOnEvents:
89
VERSANT Database Fundamentals Manual
C/VERSANT — Some functions require use of a an "intent" parameter. This parameter is reserved for future
use.
Turning locking off for a database disables short locks but has no effect on persistent locks. This means that
you can set persistent locks even if short locking has been turned off in a group and/or personal database. For
example, if you have a persistent write lock on an object, even if short locking has been turned off in its
database, other users will be blocked by the persistent write lock just as if you had set a short write lock.
Persistent locks are always enabled, even if you have turned locking off in the server process profile for a
database.
Persistent locks are not set immediately upon request. Rather, they are granted in two steps:
The system immediately tries to set a short lock that matches the lock mode specified in your persistent
lock request and then logs your request for a persistent lock.
At the next commit or checkpoint commit, the system tries to set your persistent lock.
A short lock matching the lock mode specified in your persistent lock request is granted if all of the following
conditions are true:
• There are no incompatible short locks.
• Either there are no persistent locks with an incompatible lock mode, or all persistent locks with
an incompatible lock mode have a soft lock level and you have requested a hard lock level.
• Either you have reserved the object with a persistent lock compatible with all active persistent
locks, or there are only persistent lock reservations with lock modes incompatible with other
active locks.
If you are not immediately granted a short lock matching your persistent lock mode parameter, what happens
90
VERSANT Database Fundamentals Manual
If you are granted a short lock that matches your persistent lock mode parameter:
• A subsequent short transaction rollback will drop both the persistent lock request and the
matching short lock.
• A subsequent short transaction commit or checkpoint commit records the persistent lock in the
database.
• A subsequent undosavepoint has no effect on either the short lock or the persistent lock
request.
If at commit time the requested objects have still not been locked at a harder level by another user, then your
entire persistent lock, including the level, wait, and event requests, is granted and logged.
After your persistent lock request has been granted, your queuing options are irrelevant and what happens
next depends on your lock level and your notification requests.
If you have a hard lock, then your lock continues until you release it.
Soft lock level
If you have a soft lock, then your lock can be broken at any time by a request made by another application
process for an incompatible hard lock. An error will be raised if you try to checkin an object with a broken
lock or if you try to commit a long transaction with which a broken lock is associated.
The notifications you will receive depend upon your notification options:
Lock broken
If you specified the "lock broken" option when you made the lock request, then you will be notified
immediately if your lock is broken. An error will also be raised if you try to check in an object with a
broken lock or if you try to commit a long transaction with which a broken lock is associated.
Request pending
If you specified the "request pending" option when you made the lock request, then you will be notified if
another user is waiting for your lock.
91
VERSANT Database Fundamentals Manual
If your persistent lock request is not granted, your lock level options become irrelevant and what happens
next depends on your queuing and notification requests.
If you specified the "wait" option, VERSANT will keep trying to obtain the lock until the request times out.
If the object is not available after the request times out, you will receive the error message
OM_LO_BE_INCOMPAT.
No wait option
If you specified the "no wait" option, you will immediately receive the error message
OM_LO_BE_INCOMPAT.
If you request another persistent lock for the same object in the same short transaction, the system may or
may not retry to gain the lock:
• The system will retry if you request a lock level with higher precedence than you already have;
for example, if you have a persistent read lock, the system retries if you request a persistent
write lock.
• The system ignores a request for a lock with a lower precedence level than a lock that has
already been granted. For example, if you have a write lock, a request for a read lock is ignored.
• A request to unlock a persistent lock, if last in a sequence of requests in a short transaction, is
always granted at the next commit. However, an unlock request does not immediately release
any existing short locks in case it is followed by a higher level request.
Upon receiving this request, the system immediately logs your request and then tries to get a short read lock.
• If a short read lock exists:
You immediately get a short read lock. If nothing else changes, at the next commit, you get your
persistent read lock.
• If a short write lock exists:
Then you are blocked, but the system keeps trying until timed out. If timed out, you receive a "lock
92
VERSANT Database Fundamentals Manual
timeout" error.
• If a persistent read lock exists:
You immediately get a short read lock. If nothing else changes, at the next commit, you get your
persistent read lock.
• If a persistent hard write lock exists:
You are blocked, and the system keep trying until timed out.
• If a persistent soft write lock exists:
You immediately get a short read lock. If nothing else changes, at the next commit, you get your read
persistent lock. As soon as the other user performs a checkin or long transaction commit, that user
receives an error message.
93
VERSANT Database Fundamentals Manual
94
VERSANT Database Fundamentals Manual
Name Rules...................................................................................................................................96
Session Firewall .............................................................................................................................99
Database Connection Rule ............................................................................................................ 100
Schema Evolution ........................................................................................................................ 101
Overview ................................................................................................................................ 101
Adding a Class ......................................................................................................................... 101
Deleting a Class ....................................................................................................................... 101
Renaming a Class ..................................................................................................................... 102
Changing an Inheritance Tree .................................................................................................... 102
Adding, Deleting or Renaming an Attribute ................................................................................. 103
Changing an Attribute's Data Type ............................................................................................. 103
Propagating Schema Changes.................................................................................................... 104
Verifying Schema ..................................................................................................................... 105
Mixing Releases........................................................................................................................... 106
Application Failure ....................................................................................................................... 107
Error Messages that are Incomplete................................................................................................ 108
95
VERSANT Database Fundamentals Manual
Name Rules
Name length
VERSANT does not allow spaces in names, including login and database names.
Name of a class
When objects are checked out or migrated, class names cannot conflict with existing class names in the
target database.
Name of an attribute
96
VERSANT Database Fundamentals Manual
C/VERSANT and C++/VERSANT — When using a variable to supply a name, you must first allocate
memory for the variable with malloc() or the equivalent for your operating system. Remember to add
one character for the null terminator.
For example:
newAttrName = malloc(sizeof("ANewNameOfAnyLength")+1);
o_err o_renameattr(...
C/VERSANT and C++/VERSANT — Several methods and functions, such as o_classnameof(), return
names to a vstr. When you are finished using the names returned in the vstr, remember to deallocate the
vstr memory using a routine such as o_deletevstr().
When you begin a session with the PDOM beginsession() method, the database specified becomes the
session workspace and the default database. You can later change the default database by using the
PDOM::set_default_db() method.
Changing the default database does not change which database is being used as a session workspace. It
does change the database in which objects are created, since creating a new, persistent object with
O_NEW_PERSISTENT() creates that object in the current default database. Logical object identifiers for
new objects are generated from the session database, even if it is not the default database.
C++/VERSANT — C++/VERSANT allows you to nest Collection classes to any depth. For example, you
can create a dictionary whose values are a list:
VEIDictionary<o_u4b,VIList<myObject> >
C++/VERSANT — Methods which query or index on an attribute need a precise database attribute name
even when referring to a private attribute. This precise name qualifies the attribute name with the class
name, as in Employee::age. The query and index methods that need a precise name are select(),
97
VERSANT Database Fundamentals Manual
Precise attribute names are needed by these methods, because a derived class could conceivably inherit
attributes with the same name from numerous superclasses along one or more inheritance paths. Even if
there is no initial ambiguity, classes may later be created which do cause ambiguity.
98
VERSANT Database Fundamentals Manual
Session Firewall
You must start a session before using VERSANT functions, methods, and persistent objects.
The only exceptions are functions and methods that set session parameters.
99
VERSANT Database Fundamentals Manual
To connect with the session database, use a "begin session" function or method.
After beginning a session, to connect with other databases, use a "connect database" function or method.
Connecting to a database does not change the default database. To change the default database, use a "set
default database" function or method.
100
VERSANT Database Fundamentals Manual
Schema Evolution
Overview
For each class of object that is stored in a VERSANT database, the class definition is also stored. This stored
definition (schema) must be identical to the class definition in your program files to avoid unpredictable
results. In addition, when you change a class definition, existing instances of the changed class must adapt to
the new definition. This section tells how to update stored class and instance objects with evolving class
definitions.
Most kinds of schema changes do not require explicit conversion of instances. Each time your applications
access an object, VERSANT automatically adjusts the object's structure to the current class definition as
needed. This "lazy updating" feature spreads the cost of instance conversions over time, and avoids the need
to shut out applications while global conversions are performed. The exceptional schema changes that
require global conversions are noted below.
Lazy updating does not alter object identifiers, so references to affected objects remain valid after the
conversion. For this reason, lazy updating is preferable to manual conversion involving the creation of new
objects.
Each programming language requires different mechanisms for communicating schema changes to a
VERSANT database. The following summarizes the basic mechanisms and provides an overview of the
language-specific variants. For more language-specific detail, please see the reference manual for your
language interface.
Adding a Class
Adding a new class has no impact on existing objects in the database, so no special precautions are necessary.
The new class is defined in the usual language-specific way. The class name must be unique within the
relevant databases.
If the new class is intended to become a superclass of any existing class, and must therefore be inserted into
an inheritance chain, each intended subclass must be redefined and its instances must be converted. For
details, see "Changing an Inheritance Tree" below.
c o_defineclass()
c++ Use schcomp utility to generate .sch file from .imp file
Deleting a Class
Deleting a class requires a language-specific drop-class method or function, shown below. All instances of the
101
VERSANT Database Fundamentals Manual
target class are deleted from the database, as well as subclasses and their instances.
With the C++/VERSANT interface, deleting a class also deletes any class with an attribute containing a link to
the class being dropped. For example, if class B has an attribute Link<A> that references an instance of A,
then dropping A also drops B. This prevents dangling references.
c o_dropclass()
c++ dropclass()
util dropclass
Renaming a Class
With the C or C++ interfaces, however, renaming a class invalidates instances in a way that cannot be
accommodated via lazy updating, as most other schema changes can. Therefore, a multi-step conversion is
required:
1. Add a new class that has the desired class name.
2. Create and run a program to create instances of the new class based on the instances of the old
class.
3. Repair any references to the old instances.
4. Delete the old class, and with it the old instances.
For example, suppose you want to rename the Book class so its name is Publication:
2. Create and run a program to create instances of Publication based on instances of Book.
4. Delete the Book class, and with it, all instances of Book.
1. Rename the class whose inheritance chain is being altered (see "Renaming a Class" above).
2. Add a new class that has the original class name and the new inheritance chain.
3. Create and run a program to create instances of the new class based on the instances of the old class.
4. Repair any references to the old instances.
102
VERSANT Database Fundamentals Manual
For example, suppose a hypothetical Book class currently inherits from Object. You want it to inherit from a
newly created Product class, resulting in the following inheritance chain:
Object
Product
Book
2. Add a new Book class that has Product as its superclass. (Product attributes, such as price, might also
need to be removed from Book's definition.)
3. Create and run a program to create instances of Book based on instances of OldBook.
103
VERSANT Database Fundamentals Manual
3. Create and run a program that casts each old-attribute value to the new data type, and then stores the
resulting value in the new attribute.
For example, to change the data type of Book's price attribute from int to float:
2. Add a new attribute named price, with float as its data type.
3. Create and run a program that casts each book's oldprice integer as a float, and stores the result in the
price attribute.
When the data type is inferred, no special action is required: VERSANT accommodates values of any
recognized data type automatically. If the database contains old instances, however, this results in a mix of old
and new data types for the attribute, which may cause problems for your applications. In that case, you would
need to create an informal program to fetch each old object, recast the target attribute's value to the new data
type, and then update the object in the database.
Class changes that are propagated in this way do not affect object identifiers, so references to the affected
objects within an application remain valid even after the class is changed. The few exceptions to this rule are
noted in the discussions of specific kinds of changes above.
Instances of a changed class, or of its subclasses, are aligned with the new class definition only as they are
accessed. This lazy updating mechanism spreads the cost of schema evolution over time, and avoids the need
to perform a global sweep that would shut out normal database usage.
No special action is required when migrating or checking in objects. If an object is moved to a database in
which its class has not been loaded, VERSANT automatically migrates the class object as well as the instance.
If the class is already defined in both the source and target databases, an error is generated unless the two
class definitions are identical.
Because the C language does not support class definitions, the C/VERSANT interface requires that you
create a C program to implement each set of schema changes. The C/VERSANT interface provides a
104
VERSANT Database Fundamentals Manual
specific method for implementing each type of schema change, such as o_dropclass() or
o_renameattr().
C++
In the C++/VERSANT interface, the Schema Change Utility (sch2db) is used to propagate changes in
schema (.sch) files to a database.
To reload a class definition in multiple databases, you can run the Schema Change Utility separately on
each database, or run it on one database and then reconcile differences in other databases with the
synclass() method.
For details about the Schema Change Utility and the synclass() method, see the C++/VERSANT
Reference Manual.
Verifying Schema
At any given moment, the schema as known to the database may or may not be identical to the schema as
known to your application code, depending on how recently and how rigorously you and other developers on
your team have synchronized these two views of the schema. Because of this potential for mismatch,
especially on large, multi-developer projects, it's a good idea to confirm the schema before testing or
deploying your applications.
Each of the VERSANT interfaces for class-based languages, such as C++ provides a facility for confirming the
schema. In each case, the nature of the facility is tailored to the development environment.
C++/VERSANT
The Schema Change Utility (sch2db), which is normally used to change class definitions in a VERSANT
database so they match the definitions in schema files, can also be used to report mismatches without
making any changes. This reporting feature is enabled with the -n flag on the command line that is used
to invoke sch2db.
105
VERSANT Database Fundamentals Manual
Mixing Releases
Compatibility of Releases
The following describes the compatibility of this release with previous releases.
C++ objects may be shared among applications created with Visual C++, and C Set++ compilers and with
xlC compiler independent releases.
To invoke convertdb:
convertdb databasename
You must end all long transactions and then stop the database with stopdb before and after running
convertdb.
Applications linked with the VERSANT Release 5 two process system library, liboscfe.a, can access,
create, update, and delete objects in VERSANT Release 6 databases if you create a new .oscxxyyzz file.
On the machine containing the Release 6 database, the installation process will have created a file
/etc/.osc06yyzz, where yy is your minor release number and zz is your maintenance release number.
The purpose of this file is to tell VERSANT at runtime the location of its software root directory.
After making this change, your applications compiled with previous releases will only be able to access
6.yy.zz databases on the target machine, although they can continue to access databases created with
previous releases that exist on other machines.
Applications created with previous releases and linked with the VERSANT one process system library,
libosc.a, cannot access VERSANT 6.0.0 databases.
106
VERSANT Database Fundamentals Manual
Application Failure
If an application terminates abnormally and you are running with logging turned OFF, you will probably have
to reset the database. This is because with recovery features turned off, VERSANT will not be given an
opportunity to restore the database to a consistent state. To reset the database:
If logging and recovery are turned ON, then the next time you try to use the database, VERSANT will
automatically rollback all incomplete transactions that may be corrupting the database and restore conditions
to a consistent state.
VERSANT uses shared memory and semaphores for its inter-process communications. If an application does
not finish normally, shared memory resources may not be released. To release shared memory resources after
a failed application:
2. If stopdb fails for some reason, manually remove shared memory, if any, by using the UNIX ipcrm
command.
4. If step 1 does not succeed, you will have to forcibly stop the database. For information on how to forcibly
stop a database, call Versant Customer Support.
You can use the UNIX ipcs command to verify that all shared memory and semaphores have been released.
107
VERSANT Database Fundamentals Manual
If error numbers are displayed without an accompanying error message, then either your VERSANT
installation is incomplete or needed environment variables have not been set correctly. See the "Installation"
chapter in your Release Notes.
108
VERSANT Database Fundamentals Manual
109
VERSANT Database Fundamentals Manual
Object migration
Object checkout
A checkout creates an object in a personal database that is a copy of an object in a group datatabase.
Object checkin
A checkin moves a new or previously checked out object from a personal database to a group database.
110
VERSANT Database Fundamentals Manual
Object Migration
Migrating an object is useful when you want to distribute data to take advantage of available hardware,
reduce network traffic by placing data near users, and/or move data from a development environment to a
production environment.
Object identity
When you migrate an object, the object identifier of the object is not changed. This means that you do
not have to change code that references an object or update other objects that have links to the migrated
object even though the object has been moved to a new location.
Locks
Migration routines acquire a write lock if the object does not already have one. This ensures that you are
the sole user of the object when you migrate it.
Schema
When you migrate an object, copies of class and superclass objects are made in the target database. If a
class or superclass exists with the same name in the target database but with a different definition, an
error will be returned. To resolve differences in schema definitions, you can use the "synchronize"
method or other interface specific mechanisms.
Checked-out
Checked-out objects
You cannot migrate a checked-out object. An attempt to migrate a checked-out object will cause an error.
To migrate a checked-out object, you must first return it to its source database and then move it from
there.
Versioned objects
You cannot migrate a versioned object. An attempt to migrate a versioned object will cause an error.
Checkin routines
You cannot use a checkin routine to migrate an object from a group database to another database. You
111
VERSANT Database Fundamentals Manual
can use a checkin to explicitly return a checked out object to its source database.
Commits
A migration is not persistent until committed. If multiple objects are migrated in a transaction, either all
the objects will be migrated if the transaction commits successfully or all objects will be returned to their
original databases.
When you check in or migrate objects created in a personal database to a group database, make sure that
the checked in or migrated objects do not reference objects left in a personal database.
A checkin can migrate a new object from a personal database to a group database.
c o_checkin()
c++ checkin()
112
VERSANT Database Fundamentals Manual
Object Checkout
Checkouts are useful when you want to work on particular objects for a long period of time with a minimum of
network traffic and while optionally being disconnected from other databases. Also, because the original
objects remain in their source databases, you can safely elect to turn off transaction management and
recovery in your personal database and thus bypass locking and logging overhead. The net result can be a
significant improvement in performance.
When you check out an object, VERSANT will automatically associate it with the current long transaction.
Later, you can return all objects associated with that long transaction simply by ending the long transaction.
A checkout of a non-versioned object with a persistent read or write lock copies it from a group to a personal
database. The copy object has the same logical object identifier as the source object.
A checkout of a non-versioned object with a null lock creates a new object in a personal database that is a
copy of the source object. The new object a logical object identifier that is different from that of the source
object.
A checkout of a versioned object creates a new version of the object in a personal database. The new object
gets a logical object identifier that is different from that of the source object.
Group checkout of objects — Group checkouts allow you to replicate frequently used objects in multiple
group databases. For information on group checkouts, please call Versant Technical Support.
A group checkout copies specified objects to a personal database and optionally copies linked objects to
a given number of link levels.
In a group checkout, specified and linked objects can come from any number of different databases. The
group checkout method will first search a specified database and then continue to search all connected
databases, stopping as soon as all desired objects have been found.
113
VERSANT Database Fundamentals Manual
Even though you can use group checkout objects from multiple databases, performance is much better if
all of the objects in a particular group checkout belong to the same database. Thus, to improve
performance, you might want to make a separate group checkout for each database from which you want
objects.
c o_gcheckout()
c++ gcheckout()
Checkout found objects
A predicate checkout copies objects that satisfy a search predicate to a personal database.
You cannot use a predicate checkout also to checkout linked objects, but you can specify whether you
want instances of subclasses also to be evaluated and checked out. If you need to check out linked
objects, you can use a multi-level select query to identify the objects that you want to checkout and then
check them out with a group checkout.
Checkout non-versioned
non-versioned objects
When you check out an object, you can place a persistent read, write, or null lock on the original object in
the group database.
Persistent write or read lock — A checkout of a non-versioned object with a persistent read or write lock
copies it from a group database to the personal database used as a session database. Also, the requested
persistent lock is set on the source object in its source database. The copy of the source object that is
created in your personal database will have the same object identifier as the original.
After the checkout, your queries will see the object in your personal database and not in the source
database, but queries made by others will see the object in the source database.
After the checkout, you can upgrade or release a persistent lock on the copy in your personal database. If
you upgrade the lock, the upgrade will occur on the source object. If you release the lock, the checkout
of that object will be rolled back. For a lock upgrade or release request to succeed, you must be
connected to the source group database at the time of the request.
A checked out, non-versioned object with a persistent read or write lock becomes associated with the
current long transaction. This means that later you can check in all such objects to their source databases
114
VERSANT Database Fundamentals Manual
If you attempt to delete a non-versioned object which has been checked out in write or read mode, an
error will occur. To delete the object, you must check it in and then delete it from its source database.
If an object has been previously checked out with a persistent read or write soft lock, you can override
the soft lock with a hard lock and then check out the object. To break the soft lock, you must first use an
explicit "set persistent lock" routine to acquire the necessary hard lock. This routine will fail if there is a
conflicting persistent hard lock on the object, as hard locks are not breakable. A persistent write-hard
lock will break a persistent read-soft lock or a write-soft lock, and a read-hard lock will break a write-soft
lock. A read-hard lock will not break a read-soft lock, because there is no contention.
Persistent null lock — A checkout of a non-versioned object with a null lock creates a new object in a
personal database that is a copy of the source object. By definition, no persistent lock will be set on the
source object.
The new object will have a new object identifier and have no connection to the original object. This
means that a query based upon search conditions will see two similar objects, one in your personal
database and one in the source database, and that ending a long transaction will not perform a checkin of
the copy object.
A checkout of a versioned object always creates a new version of the object in a personal database. A
persistent lock will not be set on the source object, even if requested, because the checked out object is
new and exists only in the personal database.
To use versioning behavior with checkouts, you must convert an object to a versioned object with the
prepare to version routine before checking it out. If you invoke a prepare to version routine on a checked
out object, an error will be returned.
For both non-versioned and versioned objects, existing locks are tested against the lock requested by the
checkout. If there is a blocking lock, the checkout will not occur. If there is no blocking lock, the checkout
will occur.
You can check out a mixture of versioned and non-versioned objects. The behavior that occurs will
automatically differ depending upon the type of object involved.
Turning locking off for a database disables short locks but has no effect on persistent locks. This means
that a checkout can set persistent locks even if short locking has been turned off in a group and/or
personal database. For example, if you have a persistent write lock on an object, even if short locking has
been turned off in its database, other users will be blocked by the persistent write lock just as if you had
set a short write lock.
115
VERSANT Database Fundamentals Manual
Since the purpose of group databases is to allow access to objects by multiple users, it is rarely safe to
turn short locking off in a group database. However, for performance reasons you may want to turn short
locking off in a personal database if you are accessing the objects with only a single application process. If
you are accessing objects in your personal database with multiple processes, then you should normally
leave short locking on.
Checkout non-versioned
non-versioned objects with read and write locks
If you check out a non-versioned object with a read or write lock but do not check out its linked objects,
the links from the checked out object to the linked objects will continue to point to the target objects in
their source databases. This means, that accessing a linked object will require a remote access and a
remote database connection.
If you check out both a non-versioned object and its linked non-versioned objects in read or write mode,
links will point to the target objects in their new locations in the personal database, because links are
based upon object identifiers and checking out objects in read or write mode does not change identifiers.
This means that accessing a checked out linked object will not require a remote access or remote
database connection.
This behavior for read and write mode checkouts occurs regardless of when the checkouts occur,
whether in one operation or in separate checkouts of objects and their linked objects. A link always points
to the current location of an object.
Since null lock and versioned object checkouts create new objects, links are handled somewhat
differently than in read and write mode checkouts.
If you check out an object with a null lock or a versioned object but do not check out its linked objects,
the links in the new object will continue to point to the target objects in their source databases.
If you check out a null locked or versioned object and also checkout its linked objects in a single group
checkout, the links in the new objects will be adjusted to point to the new linked objects created in the
personal database.
If you check out a null locked or versioned object and later checkout its linked objects in a separate
operation, the links in the new object will point to the original objects in their source databases.
116
VERSANT Database Fundamentals Manual
A long transaction tracks checked out objects using long transaction objects in the databases that are the
sources of the checked out objects.
Each time an object is checked out, a long transaction object in its database is updated. Long transaction
objects are maintained even if logging has been turned off in a database. However, logging must be
turned on to roll back a checkout or checkin.
For each object, a checkout will also copy its class object and the class objects for superclasses, linked
objects, and embedded objects if they do not already exist in the personal database. These class
definitions are needed so that you can access the checked out object, create new objects of the same
class, create new subclasses, and use embedded classes normally.
If a class already defined in the personal database has the same name but a different definition, the
checkout will return an error, and you must stop and synchronize the definitions in order to make the
checkout.
When a non-versioned object is checked out with a read or write lock, VERSANT will prevent changes
from being made to class and superclass objects during the period of the checkout. Although VERSANT
will allow you to change the class objects in the personal database, you must resolve any differences in
class definitions before you can make a checkin. This means that you cannot use checkout/checkin
mechanisms to change schema, even if you explicitly checkout a class object.
There are numerous ways to evolve schema in different databases in a consistent manner. One way is to
change a class definition in one database and then use an interface specific "synchronize class" routine or
utility to change it in another. Another way is to change class definitions separately in all databases
involved.
A checkout is not persistent until committed. This means that a checkout is atomic: either all objects
involved in a checkout are checked out when the transaction commits or none are.
117
VERSANT Database Fundamentals Manual
Object Checkin
A checkin is useful if you have been working on objects in a personal database while being disconnected from
other databases and now want to update other databases with your results.
Checking in a new non-versioned object migrates it from a personal database to a group database and drops
the original object in the personal database.
Checking in a non-versioned object previously checked out with a read or write lock returns it to its source
database and drops the copy in the personal database.
Checking in a non-versioned object previously checked out with a null lock migrates it from a personal
database to a group database and drops the object in the personal database.
Checking in a versioned object creates a new, child version of the object in a group database and leaves the
parent object in the personal database.
When you end a long transaction, you can commit, rollback, or continue the current long transaction.
Committing a long transaction checks in all objects that have been checked out in the current long
transaction and releases all locks held on the checked out objects.
Rolling back a long transaction drops the checked out objects from the personal database and releases all
locks held on the checked out objects.
Continuing a long transaction leaves the checked out objects in the personal database and maintains
persistent locks from one session to another.
c o_endsession()
c++ endsession()
Check in specified objects
An explicit checkin will check in specified objects. These objects can either be new objects that you want
118
VERSANT Database Fundamentals Manual
to migrate to the group database or objects that have been previously checked out.
An explicit checkin allows you either to hold locks on previously checked out objects or to set locks on
new objects being checked in.
A checkin always returns a checked out object to its source database even if you specify a different
database in the checkin routine.
c o_checkin()
o_checkinreturnobjs()
c++ checkin()
Checkin non
non-versioned
-versioned objects
Checkins of non-versioned objects do not change object identity, although what happens differs
depending upon whether the object is a new object or an object previously checked out with a persistent
write or read lock.
Write lock — If a non-versioned object has been checked out with a write lock, a checkin will replace the
original object in the source database with the checked out object and delete the copy in the personal
database.
Read lock — If an object has been checked out with a read lock, a checkin will not change the original
object in the group database but will drop the copy in the personal database.
New object or null lock — If an object has been checked out with a null lock or was created in a personal
database, ending a long transaction will have no effect on it, but you can use either an explicit "check in"
or a "migrate object" routine to migrate it from the personal database to a group database.
Broken lock — If you checked out an object with a persistent read or write lock with a soft level and if the
soft lock has been broken by another user requesting a hard lock, you will not be able to checkin or
migrate that object.
If a versioned object has been checked out, ending its long transaction will have no effect on that version,
because checking it out created an entirely new version in the personal database with only a child-parent
relationship with the source object in the group database.
If you use an explicit "check in" routine on a previously checked out versioned object, a new version of
119
VERSANT Database Fundamentals Manual
that object will be created in the group database. VERSANT will traverse the version graph to find the
original parent object in the group database and, by default, use it for the checked-in version. However,
you can specify a new parent as a parameter in the checkInTo: message. The checkin will not delete the
versioned object in the personal database.
After a checkout of a versioned object, the original parent object may be deleted in the source group
database. If the parent has been deleted, a checkin of a versioned object will create a new version
derivation graph with the checked in object as the root of this graph.
Deleting a versioned object from a personal database has no effect on the version derivation graph in the
group database.
You cannot use checkins to change class definitions. If you try to checkin an object whose class name is
the same as a class that exists in the target database but whose definition is different, an error will be
raised. You will then have to resolve the differences in definitions.
After a checkin, class and superclass objects for both checked out and new objects remain in the personal
database even if all of their instances have been checked in or migrated.
Checkins always return checked out objects to their source database even if you specify a different
database in an explicit "check in" routine.
You cannot use a checkin to delete an object. If you check an object out in read or write mode, an
attempt to delete it will cause an error. If you handle the error and continue, the next checkin caused by
ending the long transaction will restore the original object in its source database and release its persistent
lock.
A checkin is not persistent until committed. If you rollback a checkin or if the system crashes before a
checkin is committed, then the checkin will have no database effect. In other words, a checkin is atomic:
either all objects involved in a checkin are checked in when the transaction ends or none are.
120
VERSANT Database Fundamentals Manual
Configuration management is a low-level framework upon which you can build a configuration management
system. It uses a "smart object" architecture for connecting objects logically to create different versions of the
same data.
121
VERSANT Database Fundamentals Manual
Object Versioning
Overview
Versioning purpose
One of the purposes of versioning is to allow the tracking of the evolution of object states. An object or a
group of objects can have any number of versions whose interrelationships are automatically logged.
Another purpose of versioning is to make a particular object always available for checkout to a personal
database regardless of the locks that have been set on it. Constant availability is consistent with locking,
because checking in a versioned object always creates a new version of that object.
VERSANT tracks the version derivation history of objects across databases in a database system, even when
objects are checked out, by using a version derivation graph.
The first version of an object becomes the root of the object's version derivation graph. The derivation graph
branches when sibling versions are created and merges when a single version is created from multiple parent
versions.
Each node of the derivation graph references all predecessors and successors, points to the versioned object
itself, and contains some descriptive information about the versioned object, such as creation time and creator
identification.
The coordination of version derivation across database boundaries is achieved through the concept of a
"virtual" version derivation graph, which is an extension of a version derivation graph for a single database. In
a virtual version graph, related versions in different databases are connected through virtual links
automatically created by checkout and checkin operations. These virtual links are then used by the Version
Manager module of the VERSANT Manager to find a proper parent for a version during subsequent
checkouts and checkins.
Automatic maintenance of the version graph means that you do not have to keep track of derivation histories
across databases, which is important since you may not know what other applications are doing.
You can override the default handling of version derivations by explicitly naming a parent version in a target
database. If you do specify a parent during a checkin, the Version Manager records the new derivation link
and does not traverse the derivation hierarchy.
VERSANT can resolve version relationships at runtime with dynamic binding to a generic instance. One of the
versions is a default version, and the current default is recorded in the generic instance. Initially the default
122
VERSANT Database Fundamentals Manual
version is the most recent version in the derivation graph. However, you can set the default.
The default version is recorded in a "generic instance" associated with the versioned object. There is only one
generic object instance for a versioned object. At runtime you can use the generic instance to find the version
that has most recently been specified as the default version. An application program can also statically bind to
a specific version. When the last version in a version tree for a database is deleted, the generic version is also
deleted, even if some versions have been checked out.
When you perform a query on a versionable class, all versioned object that meet the selection criteria are
returned.
Versioning Actions
Create versioned object
Is versioned object
Any object can be explicitly or implicitly changed from a normal object to a "versioned" object, and you
can ask any object whether or not it is a versioned object. Once an object is versioned, it is automatically
handled differently from a non-versioned object.
Each version of an object has a unique object identifier, a version status, and can have parent, child,
and/or sibling relationships to other versions.
You can also create a versioned object implicitly by creating a child version of it with a "create version"
routine.
Versioned objects are persistent objects in the full sense. When you query the database by selection, in
addition to any non-versioned objects, any versioned objects that meet the search predicate are
returned. You can then use various routines to determine if an object is versioned and what its version
status is.
Likewise, when you create a database index, it will index all objects of a class, including any versioned
objects.
Versioned objects cannot be migrated. A versioned object is always associated with the database in
which it was first created.
123
VERSANT Database Fundamentals Manual
Is versioned object
c o_isvsninst()
c++ isvsninst()
Create version
Create version with checkout
Create version with checkin
After an object is versioned, additional versions can be created either explicitly with a "create version"
routine, or automatically via checkout and checkin procedures. Each version is given a version number,
assigned a status, and its parents and children are stored in a version graph. VERSANT stores each
complete version, regardless of how similar it may be to its predecessors.
When you create the first version of an object, the original object becomes the root of the object's version
tree, and a generic object instance is created.
If you try to create a version of a non-versioned object, the object will automatically be changed to a
versioned object before the new version is created.
You cannot version a checked out non-versioned object. After a non-versioned object has been checked
out and you want to make a version of it, you must check it in, make it a versioned object, and optionally
check it out again.
124
VERSANT Database Fundamentals Manual
Create version
c o_createvsn()
c++ createvsn()
A new version of an object is called a "child," while the original object is called a "parent." Versions that
share the same parent are called "siblings."
Each new version of an object has a child relationship to its source object. Once an object has been
versioned, further versions can be explicitly created or implicitly created by use of checkouts and
checkins.
You can find all children or parents of any versioned object or the generic instance.
You can explicitly add a parent version to an object. This is useful, say, if you have merged two parent
objects and want to designate the new parent version as a parent of a particular child.
125
VERSANT Database Fundamentals Manual
Versions have status levels assigned to them to provide update constraints. You can use routines to set
and get their status.
Transient version — A transient version is treated as an ordinary object and it can be updated or deleted.
When a transient version is deleted its description is also deleted from the version derivation graph.
Accordingly, a transient version can only occur as a leaf on a version graph, and you cannot downgrade a
non-leaf version to a transient version.
Working version — When a version becomes stable, it can be promoted to a working version, which is
frozen and cannot be updated. A working version can be deleted to recover space, although its
derivation history persists in a version graph because there may still be versions derived from it.
Released version — A version can be upgraded to released status which means that it cannot be updated
or deleted.
You can downgrade or demote a version's status. For example, if you want to delete a released version,
downgrade it to a working version and then delete it.
A version cannot be modified if there are versions derived from it. That is, if a child version is derived
from a transient version, the parent transient version is automatically upgraded to a working version.
126
VERSANT Database Fundamentals Manual
Get version
You can bring a versioned object into memory using its version number or version status.
Get version
c o_locatevsn()
c++ locatevsn()
When you create the first version of an object, the original object becomes the root of the object's version
graph and a generic object instance is created.
The generic object stores the version derivation graph for a versioned object and also stores the version
number of the default version. There is one generic object instance for each versioned object. When the
last version in a version tree for a database is deleted, the generic version is also deleted, even if some
versions have been checked out.
When you get a versioned object (with a "locate object" routine or by dereferencing it,) the generic object
is brought into the object cache along with the versioned object.
If the default version number is greater than zero, the default version is statically bound to the
corresponding version of the object.
If the default version number is less than zero, representing most recent transient version, most recent
working version, most recent released version, or most recent version, then the default version is
dynamically bound at run time to the corresponding versioned object instance.
Initially the default version number is the most recent version in the derivation graph. You can set this
127
VERSANT Database Fundamentals Manual
An application can statically bind to any version of an object, including the default version.
Making a new version automatically locks the generic object which contains the version graph. Until the
lock on the generic object is released, an application using the generic object to dynamically bind to the
default version is locked out. This short lock is released at the end of the short transaction.
Review your applications to see if they access objects that have been or will be versioned. Unless you
want to create new versions with each checkin, you may need to change your application code to either
statically bind to a specific version or dynamically bind to the default version.
Is generic object
c o_isgeninst()
c++ isgeninst()
128
VERSANT Database Fundamentals Manual
Delete version
You can delete a versioned object without removing its description in the version graph. Alternately, you
can delete an entire hierarchy by deleting the generic instance.
Delete version
c o_deletevsn()
c++ deletevsn()
Get version number
You can get the version number of a version instance. If the instance involved is a generic instance, the
version number of the current default version is returned.
Versioning Example
Version management across database boundaries
Following is an example of version management involving three databases, a personal database, db1, a group
database, db2, and a personal database, db3. The example shows the coordination of versions among
databases for a single object with multiple versions.
129
VERSANT Database Fundamentals Manual
In the above example, there are three version derivation hierarchies corresponding to two personal databases
and a group database. The original object becomes, through checkouts and checkins, three different
versioned objects, one in each database.
The Version Manager uses virtual link information to maintain a derivation history across the databases.
t0 At time zero there is one version object, db1/v1, in personal database one.
t2 At time two, the child version in database one, object db1/v2, is checked into the
group database as object db2/v1. Because object db1/v2 is a versioned object, the
checkin does not delete it from the personal database.
t3 To illustrate parallel version derivation, at time three a second child version of the
object in database one is created as object db1/v3. This object is a sibling to object
db1/v2 and shares the parent object db1/v1.
t4 At time four, the child version in group database two, is checked out from the
group database into another personal database as object db3/v1.
t5 At time five, a child version is created in the second personal database as object
db3/v2.
130
VERSANT Database Fundamentals Manual
t6 At time six, a child version is created in the first personal database as object
db1/v4. Its parent is db1/v2.
t7 At time seven, a parallel child version is created in the second personal database as
object db3/v3. This object is a sibling to object db3/v2 and shares the parent object
db3/v1.
t8 At time eight, a child version, object db3/v4, is created in the second personal
database with parent object db3/v2 and grandparent object db3/v1.
t9 At time nine, the child version object db3/v4 is checked into the group database.
The checkin creates a version object db2/v2 from the parent object db2/v1.
t10 At time ten, a similar situation occurs when the latest version in the group database,
object db2/v2, is checked out to the first personal database. Again, the Version
Manager traverses up the derivation hierarchy to find that the parent object db2/v1
was derived from object db1/v2. Thus, in personal database one, object db1/v2 is
made the parent of the checked-out object, which becomes object db1/v5.
131
VERSANT Database Fundamentals Manual
Configuration Management
Overview
VERSANT configuration management is a low-level framework upon which you can build a higher-level
configuration management system with policies appropriate for each of your applications.
VERSANT configuration management has a "smart object" architecture for versioning objects. Smart objects
serve as intelligent proxies, which, when dereferenced yield the correct version of the original objects (e.g.
the latest or released).
Smart objects are useful when large composite objects require versioning of individual components and it is
not practical to maintain multiple versions for the entire object.
• A configuration table
• A resolve function
A resolve function processes the configuration table to return the specific version based on your current
configuration. This is similar to implementing a many-to-many association, except that only one pair of
the association is valid for any specific configuration.
Important Note —
When we talk about versions in a configuration, we actually mean that we have different objects
(non-versioned, with no generic object or version tree) whose only connection is that they are
pointed to by the same smart object. The smart object connects these objects together logically,
indicating that they contain different versions of the same logical data. In a configuration, a
member object is just a regular object, not a versioned object with a generic object and version
graph.
Configuration Object — A configuration object is an object that you define to identify a unique configuration.
Configuration objects are normally developed in a tree hierarchy that identifies all configurations associated
132
VERSANT Database Fundamentals Manual
Configuration List — A configuration list is an ordered list of configuration objects which specifies the order
used when binding a reference from a smart object to a target object. To make resolution of higher priority
items faster, the configuration lists are in reverse order of priority, with the highest priority configuration
appearing last. A configuration list and a smart object are required to resolve a target object or versioned
object.
Configuration Table — A configuration table links configuration objects with the member objects in a smart
object.
Member Object — A member object is an object associated with a specific smart object. A member object
can be associated with at most one smart object.
Member List — A member list is the set of member objects that associated by one or more similarities and are
linked to a single smart object. For example, member objects may have the same parent. A member list
typically contains versions of a single, specific object.
Target Object — A target object is the object resolved dynamically based your priority scheme (configuration
list) and the configuration table in the smart object.
Smart Object — A smart object (C interface) or smart link (C++ interface) is an object that dynamically binds a
configuration object to a target object. This binding is done at run time, based on the configuration list
specified by the user.
Management Application — A management application manages the configurations and smart objects. Most
of the functions and methods described in this document are used only by a management application.
User Application — A user application uses configuration lists and smart objects, to dynamically resolve the
target objects.
Architecture
133
VERSANT Database Fundamentals Manual
Notice that there are two different ways that targets can be specified as members without mapping them to a
configuration.
The first way is to specify a NULL configuration, as is the case with T3.
The second way is to take advantage of the fact that the two columns are implemented as two separate
vstrs. This allows you to make the target column longer than the configuration column and store the
unmapped targets in the extra portion of the target column. In the above, T4 and T5 are specified as
unmapped members.
Since you are responsible for managing the table, you can decide on a target by target basis which of these
two methods to use.
Resolving — Resolving is the process by which a smart object is dynamically bound to a target object in the
context of a configuration list. You can think of it as a function which maps a smart object and a configuration
list to a target object. It is defined just as you might expect when you consider that a configuration list is a
prioritized list of configurations:
resolve(smart object S, configuration list L) returns target object
{
configuration C = highest priority in L with an entry in the S table
return the target object T associated with C in the S table
}
The most simple and general purpose algorithm for implementing the resolve operation is a nested loop of
order MN, where M is the size of the configuration list and N is the size of the smart object's table:
for (L in configuration list, highest priority first)
for (configurations T in table)
if (L == T) return target associated with T
return NULL
Since this could become expensive during heavy dereferencing, two optimizations to this algorithm are
134
VERSANT Database Fundamentals Manual
available.
Resolve can be described by considering the smart object S1. If S1 is resolved with the configuration list {C1,
C2, C4}, it would resolve the target object T2. Note that the highest priority, C4, was not associated with a
target object in S1. Since C2 was associated with T2, it was not necessary to look for a match with to a lower
priority configuration such as C1.
Optimize Caching
Each smart object caches the most recent result of the resolve operation. This is highly effective on the front
end where normally a single configuration list is used repeatedly for long periods of time.
Using this hash table, we can implement the following linear time algorithm:
for (configurations T in table)
if (priority(T) is defined
&& priority(T) is the highest priority so far)
result = T
You can further optimize this algorithm if you are willing to accept the following restriction:
• The relative order of the configurations in the configuration lists and in the tables of smart objects must
always be the same.
In other words, the entries in the tables must be sorted in order of increasing configuration priority. This
implies that the first case where priority (Tx) is defined is also the highest priority. This being the case, the
loop can be modified to terminate early as follows:
for (configurations T in table, highest priority first)
if (priority(T) is defined)
return T
return NULL
Since you may or may not feel the extra optimization is worth the restriction, the option to specify whether or
not the restriction has been accepted is provided.
To avoid the expense associated with an extra level of indirection, the user can replace references to S2 with
references to S1 as they are encountered, in a lazy incremental fashion. You may also consider using queries
to preemptively find and fix objects that reference S2. Since such an operation can be expensive, it can be
done in the background during non-peak time periods.
Forwarding can easily be limited to one level by resolving multiple levels of forwarding at merge time.
Locking
Smart objects are read with the default lock mode. This implies that the query and gwrite remote procedure
calls send the default lock to the backend. To avoid deadlock, the standard order for obtaining locks is smart
object first, target object next. In the forwarding case where there are multiple smart objects, locks are
obtained in order, following the forwarding chain. Query is an exception to this rule, it reads the target first
but has special logic to avoid deadlock.
Optimistic Locking
The smart objects can come under heavy contention in a multi-user environment. To increase system
performance a timestamp attribute is provided to support optimistic locking. To prevent potential deadlocks,
you must specify the smart objects before the target objects when performing a group read.
When a group write is performed the objects are partitioned into two groups: smart objects and non-smart
objects. Locks are first obtained for the smart objects.
Group read makes the assumption that you have specified the smart objects first and then the targets. If this is
not done there can be a deadlock.
For more information on optimistic locking refer to the VERSANT Database Fundamentals Manual.
Query Support
Each member object maintains a backpointer to its smart object to accommodate execution of a query for a
particular configuration list. Since you maintain the smart object's, you must also maintain the backpointer via
a VERSANT supplied API. This API implements a "set smart object" operation and must be invoked by the user
whenever a member is added to or removed from a smart object.
If you are not interested in using the query support, you can choose to save space by not maintaining the
backpointer. In this case, you can also elect to store only the members that are associated with a configuration
136
VERSANT Database Fundamentals Manual
You can also decide whether to return matches that are not bound to a smart object by setting a flag to the
select APIs.
If you want to resolve the smart objects returned as a result of your query, specify the
O_CM_RETURN_TARGETS option in your query.
Configuration Lists
The configuration list model can be used to implement an efficient delta versioning system.
For example, consider the scenario where you are changing 10 objects in a database of 100,000 "released"
objects. A configuration object C1 can represent the original unchanged configuration of the database and C2
can represent your new configuration.
To test the configuration changes, set your configuration list to {C1, C2} and update the tables in the smart
objects of your 10 changed objects to map C2 to the new objects in addition to the original mapping of C1 to
the old objects. Users without access to the changes would keep their configuration list set to {C1}.
In the future, if you wanted to make an additional change to 1 of your 10 changed objects, you could create a
new configuration C3, set your configuration list to {C1, C2, C3}, and add an entry mapping C3 to the new
version of the target object.
Later you might want to make changes to 5 more previously unchanged objects (configuration C1). You
would then map the four configuration {C1, C2, C3, C4} and the database into a tree similar to the following
illustration:
The path from the current version (C3) to the original root version (C1) would then be used as the
137
VERSANT Database Fundamentals Manual
Notice that the number of smart objects with entries for a particular version tends to be much greater for the
lower priority configuration objects than for the high ones. In our example, there were 100,000 smart objects
with entries for C1, 10 smart objects with entries for C2, and 1 smart object with an entry for C3. This makes
sense when you think of C1 as a root configuration shared by everyone, and C3 as a leaf configuration
representing a small change used by one person.
Usage
Session Options
Routines
C/VERSANT o_beginsession()
C++/VERSANT PDOM::beginsession()
To use configuration management routines, you must specify one or both of the following in the options
parameter, or you will get the error OM_CM_WRONG_MODE.
O_CONFIG Enable configuration management routines.
O_CONFIG_ORDERED Assume that the configurations in configuration lists and tables of
smart objects are always in the same relative order.
Each session has a default configuration list associated with it. When NULL is passed to a routine which takes a
configuration list, or when a routine involves a configuration list but provides no way to specify one, this
default configuration list is used. Until you set it otherwise, the default configuration list is NULL.
Routines
C/VERSANT o_greadobjs()
o_pathselect()
o_pathselectcursor()
NOTE: you cannot use the following configuration management
options in: o_select(), because o_select() does not take a query
option.
138
VERSANT Database Fundamentals Manual
C++/VERSANT PDOM:greadobjs()
PDOM::select_with_vstr()
PClass:select()
VCursor::VCursor()
If you have enabled configuration management when you started your session, you can specify one or more
of the following in the options parameter. Before doing a select, you must make sure that the appropriate
smart objects are flushed in addition to instances of the class being selected.
O_CM_VALIDATE
If specified, a server performing a query or group read will return to the client application an
object that satisfies the find or get criteria:
1. if it is a smart object.
2. if it is the target of a smart object.
3. if it does not have a smart object.
An object will not be returned if it has a smart object but is not a target of its smart object when
resolved with the default configuration list.
O_CM_MUST_HAVE_SMART
If specified, a server performing a query or group read will return an object that satisfies the find
or get criteria:
1. if it is a smart object.
2. if it has a smart object (it does not have to be a target of a smart object, it just has to have a
smart object.)
If used with O_CM_MUST_HAVE_SMART, then only objects that have smart objects will be
returned.
If used with O_CM_VALIDATE, then only targets that are results of the resolve operation on their
smart objects will be returned.
139
VERSANT Database Fundamentals Manual
O_CM_DONT_RESOLVE
If specified, a server performing a query or group read will not resolve a smart object. By default,
when a smart object is encountered, the server will resolve it in the default configuration and
return both the original smart object and the resulting target object.
Thread Options
Routines
C/VERSANT o_setthreadoptions()
C++/VERSANT VSession:setthreadoptions()
If you have enabled configuration management when you started your session, you can specify one or more
of the following in the options parameter.
O_CONFIG_RESOLVE
If specified for a thread, smart objects will be transparently resolved when passed an argument
to functions.
O_DROP_RLOCK_TIMESTAMP_ONLY
If specified for a thread, a group read or single object fetch will obtain a read lock on all fetched
objects and then, when the entire read has finished, drop the read locks on smart objects and on
objects containing a time stamp.
This option can be used only in an optimistic locking session. If used in a strict locking session, it
will return an error.
The purpose of this option is to ensure that a group of objects are read consistently in an
optimistic locking session. Because a read lock is applied during the duration of the operation, a
lock conflict can occur even in an optimistic locking session.
If smart objects are involved, the following occurs to the smart objects:
1. The smart objects are fetched, and the read locks are dropped.
During the time between the dropping of locks on the smart objects and the time of the resolve
operation, there is a window of time during which the smart objects could be modified by
140
VERSANT Database Fundamentals Manual
The following methodology represents one way safely to update optimistically locked smart
objects.
1. Turn the O_DROP_RLOCK_TIMESTAMP_ONLY option off and set the default lock mode to
write lock.
6. Turn the O_DROP_RLOCK_TIMESTMP_ONLY option on and reset the default lock mode.
C++/VERSANT
Link<type>::get_configurations() Get configuration objects.
Link<type>::get_targets() Get target objects.
Link<type>::invalidate_cache() Invalidate cache entries related to a smart obj.
Link<type>::is_smart() Is this a smart object.
Link<type>::resolve() Resolve smart object.
Link<type>::new_smart() Create a smart object.
Link<type>::resolve_target() Resolve smart object.
141
VERSANT Database Fundamentals Manual
142
VERSANT Database Fundamentals Manual
143
VERSANT Database Fundamentals Manual
o_upgradelock() Upgrade the lock on smart and the result of its resolve
to the specified lock mode and options.
o_xactwithvstr() Commit, checkpoint commit, or rollback changes to
objects in the session database. If the smart objects are
not dirty, operate only on the results of their resolve;
otherwise, operate on both.
C++/VERSANT
Link<type>::operator->() Resolve smart object and get target object.
Link<type>::operator*() Resolve smart object and cast link to target object.
Link<type>::migrateobj() Move the smart object and all the member objects
associated with it to the new database.
Link<type>::setattr() Set a write lock on the target of the resolve if it does not
already have one, mark it as dirty, and set the specified
attribute to the new value in the specified buffer.
Link<type>::unpinobj() Clear any pins on the smart object and the result of its
resolve that have been set in the current pin region.
LinkAny::deleteobj() Delete smart and target object.
LinkAny::migrateobj() Move the smart object and all the member objects
associated with it to the new database.
LinkAny::setattr() Set a write lock on the target of the resolve if it does not
already have one, mark it as dirty, and set the specified
attribute to the new value in the specified buffer.
LinkAny::unpinobj() Clear any pins on the smart object and the result of its
resolve that have been set in the current pin region.
PDOM::acquireplock() If one or more of the objects specified are smart,
transparently apply the resolve operation on them and
acquire the lock on their targets.
PDOM::checktimestamps() Determine the objects that have changed since copies
have been read into the object cache. If some of the
objects are smart, resolve them and check if either of
the smart or target objects have been modified.
PDOM::downgradelock() Downgrade the lock on the target object.
PDOM::downgradelocks() If some of the objects specified are smart, downgrade or
release locks on the smart objects as well as their
resolves without flushing them from the application
cache.
PDOM::gdeleteobjs() Delete smart and target objects.
PDOM::getclosure() Evaluate the objects objs in the database dbname and
for the specified number of levels follow all links to their
target objects. If a returned object is a smart object, then
follow the links from the result of the resolve operation.
144
VERSANT Database Fundamentals Manual
PDOM::refreshobj() Update the smart object and the result of its resolve in
the object memory cache with their current state in the
source database.
PDOM::refreshobjs() Release the memory space used by the target of the
resolve operation on the smart object.
PDOM::refreshobjs() Update the smart object and the result of its resolve in
the object memory cache with their current state in the
source database.
PDOM::releaseobjs() Release the memory space used by the target of the
resolve operation on the smart object.
PDOM::upgradelock() Upgrade the lock on the smart object and the result of
its resolve to the specified lock mode with the specified
options.
PDOM::xactwithvstr() Commit, checkpoint commit, or rollback changes to
objects in the session database. If the smart objects are
not dirty, operate only on the results of their resolve;
otherwise, operate on both.
PObject::migrateobj() Move the smart object and all the member objects
associated with it to the new database.
PObject::migrateobjs() Move the smart object and all the member objects
associated with it to the new database.
PObject::unpinobj() Clear any pins on the smart object and the result of its
resolve that have been set in the current pin region.
#include <omapi.h>
#include <oscerr.h>
/*
* o_cmaddconfig: Add the specified (config, target) pair to
* smart's table at a specified position. A position
145
VERSANT Database Fundamentals Manual
o_err
o_cmaddconfig
(
o_object smart,
o_object config,
o_object target,
o_4b position
)
{
o_err err;
o_vstr *configVstr;
o_vstr *targetVstr;
o_object *newConfigObjects;
o_object *newTargetObjects;
o_4b configVstrSize;
o_4b targetVstrSize;
o_4b i;
/*
* Create the new config/target object list
*/
146
VERSANT Database Fundamentals Manual
if (position == -1)
{
/* Construct the new list of configuration objects */
for (i=0; i < configVstrSize/sizeof(o_object); i++)
{
newConfigObjects[i] = ((o_object *)*configVstr)[i];
}
/* Insert config at the end of the configuration list */
newConfigObjects[i] = config;
147
VERSANT Database Fundamentals Manual
*targetVstr = o_newvstr(targetVstr,
targetVstrSize+sizeof(o_object),
(o_u1b *)newTargetObjects);
free(newConfigObjects);
free(newTargetObjects);
return O_OK;
}
/*
* o_cmremoveconfig : Remove the entry associated with config from
* smart's table. If there is a target associated
* with config, make sure that the target is not
* removed from smart's target array.
*/
o_err
o_cmremoveconfig
(
o_object smart,
o_object config
)
{
o_vstr *configVstr;
o_vstr *targetVstr;
o_err err = O_OK;
o_4b i;
148
VERSANT Database Fundamentals Manual
break;
}
}
/*
* o_cmsetconfig: Sets the target associated with config in smart's
* table, making sure that the old target is not removed
* from smart's target array.
*/
o_err
o_cmsetconfig
(
o_object smart,
o_object config,
o_object target
)
{
o_err err = O_OK;
o_vstr *configVstr;
o_vstr *targetVstr;
o_object oldTarget;
o_object oldConfig;
o_4b i;
149
VERSANT Database Fundamentals Manual
/*
* Insert the old target at the end of the target vstr.
* Associate this target object with a NULL configuration
* object
*/
oldConfig = (o_object)NULL;
err = o_cmaddconfig(smart, oldConfig, oldTarget, -1);
if (err != O_OK)
return err;
return O_OK;
}
}
/* copy it to a vstr */
o_vstr attrsVstr = o_newvstr(&attrsVstr,
sizeof(attrPtrs), (o_u1b *)attrPtrs);
if (attrsVstr == NULL)
return o_errno;
150
VERSANT Database Fundamentals Manual
/* free vstr */
o_deletevstr(&attrsVstr);
return O_OK;
}
/* parse args */
if (argc != 2)
{
printf("usage: %s <dbname>\n", argv[0]);
return;
}
dbname = argv[1];
/* populate database */
{
o_4b i;
/* create the configuration objects */
for (i = 0; i < sizeof(configObjects)/sizeof(o_object); ++i)
{
configObjects[i] = o_createobj("point", NULL, TRUE);
if (configObjects[i] == NULL)
{
151
VERSANT Database Fundamentals Manual
err = o_errno;
goto error;
}
}
printf("o_cmgettable\n");
err = o_cmgettable(smart, &configVstr, &targetVstr, TRUE);
if (err != O_OK)
goto error;
err = o_preptochange(smart);
if (err != O_OK)
goto error;
152
VERSANT Database Fundamentals Manual
/* test o_cmaddconfig */
{
/* insert the (config1, target1) pair into smart's table */
printf("o_cmaddconfig, 6\n");
err = o_cmaddconfig(smart, config1, target1, 6);
if (err != O_OK)
goto error;
153
VERSANT Database Fundamentals Manual
/*
* re-test o_cmaddconfig, this time inserting items at the end
* of the table
*/
{
/* insert the (config2, target2) pair into smart's table*/
printf("o_cmaddconfig, -1\n");
err = o_cmaddconfig(smart, config2, target2, -1);
if (err != O_OK)
goto error;
configVstrSize =
o_sizeofvstr(configTestVstr)/sizeof(o_object);
154
VERSANT Database Fundamentals Manual
targetVstrSize =
o_sizeofvstr(targetTestVstr)/sizeof(o_object);
/* test o_cmremoveconfig */
{
printf("o_cmremoveconfig\n");
configVstrSize =
o_sizeofvstr(configTestVstr)/sizeof(o_object);
targetVstrSize =
o_sizeofvstr(targetTestVstr)/sizeof(o_object);
155
VERSANT Database Fundamentals Manual
/* test o_cmsetconfig */
{
o_object newTarget = o_createobj("point", NULL, TRUE);
configVstrSize =
o_sizeofvstr(configTestVstr)/sizeof(o_object);
targetVstrSize =
o_sizeofvstr(targetTestVstr)/sizeof(o_object);
/* Check if the target associated with config1 is
newTarget */
for (i = 0; i < configVstrSize; ++i)
{
if (((o_object *)*configTestVstr)[i] == config1)
{
if (((o_object *)*targetTestVstr)[i] !=
newTarget)
printf(" Error in o_cmsetconfig\n");
break;
}
}
/* Check if the last item in the target table is
target1 */
156
VERSANT Database Fundamentals Manual
i = targetVstrSize;
if (((o_object *)*targetTestVstr)[i-1] != target1)
printf(" Error in o_cmsetconfig \n");
157
VERSANT Database Fundamentals Manual
158
VERSANT Database Fundamentals Manual
The best way to handle objects created with C++/VERSANT is with C++ applications. The best way to handle
objects created with C/VERSANT is with C applications.
To handle both C and C++ objects in the same application, the easiest way is to write in C++ and use the C++
interface to handle the C++ objects and simultaneously use the C Interface to handle the C objects. The only
times that it makes sense to mismatch interfaces is when you want a C object to reference a C++ object, or
vice versa.
Use the following approach to access C objects with the C++ Interface:
..your C++ compiler will think that the C/VERSANT functions in omapi.h should have a C++ linkage,
which will result in linker errors for each C/VERSANT function referenced.
If your C++ object uses simple inheritance, you can use casting to a struct pointer to access the fields.
159
VERSANT Database Fundamentals Manual
Objects deriving from PObject have one attribute of type PClass* that will show at the top of your
objects.
You cannot run the methods on an object created with the C Interface since the internal C++ pointers are
not initialized properly.
You can use /VERSANT to access and manipulate all C++/VERSANT collection classes if you implement
hash functions in both C++ and that operate in the two environments in the same way. In other words,
for each member, your hash function must return the same hash value in both C++ and . This
requirement requires care, because native C++ and hash functions work differently.
You can compute an elemental hash value by sending #vElementalHash to an instance of Integer,
Float, Double, Character, ByteString, or PObject. You can compute a combined hash value by
using #vElementalCombinedHash:.. in the PVirtual class.
With these hashing methods, you can develop a sharable persistent collection that involves hash look up.
A sample program which allows a user to manage a VVSet<Type> collection can be found in the
VERSANT filein/demos directory.
You can browse through the class definitions in a database by sending the message #metaInfoFor: to
the /VERSANT VMetaType class. You will receive a VMetaType instance that describes the
corresponding database schema without your having to import the corresponding class. This is
particularly useful if you need to view schemas defined from C++/VERSANT which are not intended for
object sharing.
You can store transient, runtime information or relationships in attributes defined as having the
#VSI_ST_NIL storage transform.
Attribute names added by C++/VERSANT, such as __what and __vptr also have a #VSI_ST_NIL
storage transform, but you should not use them to store runtime information, because future VERSANT
releases may suppress these attributes when classes are imported into /VERSANT.
If you want to use an attribute to store transient, runtime information, then create and reserve an attribute
in the class when you define it in C++.
You can perform queries over imported classes using the same /VERSANT methods that you would use
160
VERSANT Database Fundamentals Manual
for any other persistent class. However, there are some restrictions and differences in usage.
When you import a class, the data types defined for the class in its database are often mapped to
corresponding /VERSANT data types (see the following section for information on how types are
mapped.) For example,
• When you perform a path query, you must use the actual database attribute names and types
known to the database rather than the variable names and types associated with the class as it
appears in your Image. To get actual database names corresponding to an instance variable
name, you can send the following message to the class with the instance variable:
#databaseAttributeNameFor:
• When you perform a normal query, you can use instance variable names as they appear in the
class definitions in your Image. For normal queries, /VERSANT will do any necessary
conversions between instance variable names and database schema attribute names.
• If you are querying on an elemental data type, because of possible variants in the way can map
values (to SmallInteger or LargeInteger), you must prefix the value key with a type
indicator in a pair of angle brackets.
For example, if you are querying on an age attribute in a Person class, and the age attribute has the
database type o_u1b, then you must specify the query string as age=<VSI_ST_U1B>55 rather than
age=55.
161
VERSANT Database Fundamentals Manual
For example, for an embedded VTime attribute, all the following predicate strings are both valid and
equivalent to one another:
"Use actual seconds of the VTime attribute."
'time = 811836204'
"Use the Timestamp formatted string."
'time = <VSI_ST_VTIME>''9/22/95 11:04pm'''
"Use term key substitution."
| timestamp pred |
timestamp := Timestamp readFrom: '9/22/95 11:04pm'.
p := VPredicate from: 'time = <VSI_ST_VTIME>:x'.
p valuesAt: #(#x) put: (Array with: timestamp).
Due to language differences, the mapping between a database data type and type may be supported only
within a given range.
For example, suppose that a database contains a class with an attribute of data type o_u1b (a one-byte,
unsigned integer). When an object of that class is read from a database, the data type o_u1b will be mapped
to the class SmallInteger. When that object is written back to a database, VERSANT will attempt to
convert the value of type SmallInteger back to type o_u1b. This can cause problems, because a
SmallInteger can contain a larger value than o_u1b. Similarly, a Character can have value from 0 to
the largest SmallInteger, while the corresponding database data type char can only assum the values
between -128 and 127.
In general, you must take responsibility for choosing the right C++ data type when creating classes whose
instances will be used by both C++ and applications.
The following table lists all the type mapping supported in the current /VERSANT interface. The first column
of the table list the data type that you might be using in your C++/VERSANT application when define the
persistent classes. The second column lists the data type that the corresponding first column would be
converted to. The third column is the name of the class that the corresponding database data type would be
mapped to. The fourth column gives the corresponding storage transform symbol. For basic elemental data
types, the supported data range is also listed.
162
VERSANT Database Fundamentals Manual
163
VERSANT Database Fundamentals Manual
VDate and VTime are C++/VERSANT classes used for attributes. When brought into , attributes are
instantiated as instances of Date and Timestamp.
These two embedded types are handled specially, because they are widely used in C++/VERSANT
applications. Mapping these types to the Date and Timestamp class allows objects to share the behavior of
the two existing classes. Also, it eliminates the need to duplicate the effort in providing methods for
manipulating related attributes in each application specific imported class.
Methods are available to do date and timestamp value conversion between Date to VDate and between
Timestamp and VTime.
164
VERSANT Database Fundamentals Manual
165
VERSANT Database Fundamentals Manual
vElementalHash — Answer an integer value that can be used as a hash index in a collection.
PVirtual
storageTransform — Answer the derived storage transformation symbol for the attribute.
166
VERSANT Database Fundamentals Manual
Character
167
VERSANT Database Fundamentals Manual
168
VERSANT Database Fundamentals Manual
Standard VERSANT read, update, and write locks provide consistent and coherent access guarantees in a
multiple user environment. These guarantees are necessary and appropriate under most circumstances.
Sometimes, however, you may want to read a large number of objects but update only a few of them. Or, you
may be in a situation where there is only a small chance that the objects you want to work with will be updated
by others. In this situation, holding a lock on all objects you access can interfere with the work of others.
Accordingly, VERSANT provides optimistic locking features that allow you to work with objects without
holding locks.
You do not have to use optimistic locking features to safely access and update unlocked objects, but the side
effects of the alternative approaches might not be right for your situation. For example, you can safely
checkout an object without a lock, update it, and then check it in. However, checking in an unlocked object
will create a new object with a new logical object identifier, which may not be what you want. Or, you could
version the object you want to access, which means that you will get immediate access, but that each change
will create a new version, which only defers the issue of which version is the "correct" one.
To use VERSANT optimistic locking features to work with unlocked objects, do the following.
Following an optimistic locking protocol will ensure the coherence and consistency normally provided by
locks.
Optionally use performance related methods, which will allow fine-grain control over the objects you
want to change.
Optionally use event notification methods, which will let you know when objects held with optimistic
locks are modified.
169
VERSANT Database Fundamentals Manual
By doing the above, you will get a shorter duration of locking, which will improve performance and improve
concurrency of access among multiple users. At the same time, the time stamp validation at commit, delete,
and group write time will still keep databases consistent.
170
VERSANT Database Fundamentals Manual
To make a class "optimistic locking ready," add a time stamp attribute to the class definitions for the
objects you will be accessing.
If an object has a time stamp attribute, the VERSANT commit, delete, group write, and check time stamp
methods will automatically use the time stamp to detect obsolete copies in the object cache and prevent
you from using the obsolete copies as the basis for changing the database object.
An obsolete copy will occur if, while you are working with an unlocked copy of an object, another user
updates the original object in the database. If your object has a time stamp attribute, you will
automatically be told if your commit, delete, and group write method will overwrite changes made by
another user since you read your object. An obsolete copy will also occur if you try to modify an object
that another user has already deleted.
Time stamps are a convenience feature. You could avoid having obsolete copies by first refreshing every
unlocked object you wanted to change before proceeding to a commit. However, this would be costly,
both because of the necessary fetching of objects and because of the time required to make detailed
evaluations of each attribute in each object.
Once you add a time stamp attribute to an object, the time stamp attribute will be updated, no matter
who does the updating and no matter the kind of session used. When an object with a time stamp
attribute is created, its time stamp attribute is set to 0. Thereafter, each update written to a database will
increment the attribute by 1 during the normal commit and group write process.
You can rely on normal commit, group write, and delete methods to tell you if you have an obsolete copy,
or you can detect obsolete copies in your object cache by using the "check time stamp" method before
your commit.
When you detect an obsolete copy by checking time stamps, what you do next is up to you. For example,
you might want to refresh your copy of the object, inspect it, and then reapply some or all of your
modifications. Or you may simply want to abandon your update.
The procedures to add a time stamp attribute to a class is tightly bound to your interface language.
C/VERSANT
To define a time stamp attribute in a C class, add the following lines to your attribute descriptions:
171
VERSANT Database Fundamentals Manual
Then, before calling o_defineclass(), add the following lines to your class definition struct:
typedef struct
{
O_TS_TIMESTAMP,
.
.
} clsInstance;
C++/VERSANT
To define a time stamp attribute in a C++ class, add the following lines to your class definitions:
class SampleClass : public PObject
{
public:
O_TS_TIMESTAMP;
.
.
};
In other kinds of sessions, as soon as you mark an object dirty, VERSANT will attempt to acquire a write
lock on that object. In an optimistic locking session, all implicit lock upgrades are suppressed.
Suppression of implicit lock upgrades allows you to continue working with your objects, as long as you
like, without setting and holding a lock, which is the point of using optimistic locking. Of course, a write
lock will still automatically be set on dirty objects when you commit.
An optimistic locking session will also suppress object swapping and provide automatic detection of
obsolete copies.
c o_beginsession() with O_OPT_LK
c++ beginsession() with O_OPT_LK
Check time stamps
Check time stamps to detect any obsolete copies in the object cache.
You must be careful to check both the objects marked dirty and the objects inter-related to them. When
172
VERSANT Database Fundamentals Manual
you consider inter-related objects, you should consider both hierarchial relationships (such as subclass
objects) and logical relationships. An example of a logical relationship is when, say, you want to change B
only if A is greater than 10. In this case, you should check both B (the changed object) and A (the logically
related object) in order to prevent changing B if the value of A has changed since you read it.
c o_checktimestamps()
c++ checktimestamps()
Downgrade short lock
Release locks on specified objects without flushing objects from the object cache.
c o_downgradelocks()
c++ downgradelocks()
Delete objects
Delete objects in a vstr or collection. This method enhances performance by deleting a group of objects
with the least possible number of network messages.
c o_gdeleteobjs()
c++ gdeleteobjs()
Checkpoint commit short transaction with group
Perform a checkpoint commit on objects in a vstr or collection and maintain objects in the object cache.
c o_xactwithvstr()
c++ xactwithvstr()
Commit and retain cache
Rollback and retain cache
Most commit and rollback routines allow you optionally to prevent flushing of the object cache.
(Exceptions are the C++/VERSANT commit() and rollback() methods.)
However, in an optimistic locking session, a better way to handle the object cache is to use commit and
rollback routines that operate only on objects in a vstr or collection. However, if you use a group commit
or rollback, you must be careful to place in the vstr or collection all objects marked dirty and all objects
inter-related to them. When you consider inter-related objects, you should consider both hierarchial
relationships (such as subclass objects) and logical relationships. An example of a logical relationship is
when, say, you want to change B only if A is greater than 10.
173
VERSANT Database Fundamentals Manual
An option in o_xact() and xact() allow you to prevent flushing the entire object cache after a commit.
This might be useful, but normally you would use the commit with vstr routine in an optimistic locking
session.
When you fetch an object, many routines will place a read lock on the object. If you are using optimistic
locking, you will want to drop the read lock on the fetched object as soon as possible.
One way to drop a read lock is to track the objects fetched and then explicitly drop the lock with a routine
such as the C/VERSANT o_downgradelock() function.
If you know that you always want to downgrade read locks on fetched objects, you can specify automatic
lock downgrading. This approach does not require you to keep track of fetched objects and reduces
network calls.
To automatically downgrade read locks, use a "set thread options" routine with the O_DROP_RLOCK
option. Following is a description of the O_DROP_RLOCK option.
O_DROP_RLOCK
For this thread, drop read locks when objects are retrieved from a database.
This option will have no impact on intention read locks set on the class objects corresponding to the
instance objects retrieved.
Read locks are held on instance objects during the duration of the read operation (which protects you
from seeing uncommitted objects) and then dropped when the read operation is finished.
174
VERSANT Database Fundamentals Manual
If you want to always drop read locks after fetching objects, this option reduces network calls, because it
causes locks to be dropped by the database server process. By comparison, dropping locks with a down
grade lock routine requires a separate network message from the application process to the server
process.
o_locateobj() — Get object. A read lock will be dropped if the lockmode parameter is
RLOCK or IRLOCK.
o_pathselectcursor() — Find object with cursor. Read locks will be dropped if the
instlock parameter is specified as RLOCK or IRLOCK and O_CURSOR_RELEASE_LOCK is
not specified in the options parameter. If you toggle O_DROP_RLOCK in the middle of a
cursor select, the new option will take effect at the next retrieval of objects.
o_greadobjs() — Get objects. Read locks are dropped if the lockmode parameter if
RLOCK or IRLOCK. This behavior is the same as setting the options parameter to be
O_DOWN_GRADE_LOCKS parameter, except that only read and intention read locks are
dropped.
o_getclosure() — Get object closure. Read locks are dropped if the lockmode
parameter is RLOCK or IRLOCK.
175
VERSANT Database Fundamentals Manual
RLOCK or IRLOCK.
o_refreshobjs() — Refresh objects. Read locks are dropped if the lockmode parameter
is RLOCK or IRLOCK.
o_getattr() — Get attribute value. Read locks are dropped if the default lock mode is
RLOCK or IRLOCK.
o_getattroffset() — Get attribute offset. Read locks are dropped if the default lock
mode is RLOCK or IRLOCK.
C++/VERSANT thread option methods
setthreadoptions() — Set thread options. The default is to not drop read locks when
objects are fetched. Different threads in the same session may have different settings for
thread options.
locateobj() — Get object. A read lock will be dropped if the lockmode parameter is
RLOCK or IRLOCK.
select() — Find object with cursor. Read locks will be dropped if the instlock
parameter is specified as RLOCK or IRLOCK and O_CURSOR_RELEASE_LOCK is not specified
in the options parameter. If you toggle O_DROP_RLOCK in the middle of a cursor select,
the new option will take effect at the next retrieval of objects.
fetch() — Get object with cursor. Read locks will be dropped if the instlock parameter
VCursor() is specified as RLOCK or IRLOCK and O_CURSOR_RELEASE_LOCK is not
specified in the options parameter. If you toggle O_DROP_RLOCK in the middle of a cursor
fetch, the new option will take effect at the next retrieval of objects.
greadobjs() — Get objects. Read locks are dropped if the lockmode parameter if RLOCK
or IRLOCK. This behavior is the same as setting the options parameter to be
O_DOWN_GRADE_LOCKS parameter, except that only read and intention read locks are
dropped.
getclosure() — Get object closure. Read locks are dropped if the lockmode parameter
176
VERSANT Database Fundamentals Manual
is RLOCK or IRLOCK.
refreshobjs() — Refresh objects. Read locks are dropped if the lockmode parameter is
RLOCK or IRLOCK.
get_attribute_value() — Get attribute value. Read locks are dropped if the default
lock mode is RLOCK or IRLOCK.
getattroffset() — Get attribute offset. Read locks are dropped if the default lock mode
is RLOCK or IRLOCK.
177
VERSANT Database Fundamentals Manual
If you perform multiple reads in separate operations and have set the O_DROP_RLOCK option on, your objects
may not be consistent, because operations may have been performed on your objects between your reads. If
you are going to use the O_DROP_RLOCK option, you must be able to tolerate multiple read inconsistencies.
Example of Multiple Read Inconsistency — the danger of using implicit read lock downgrade mechanisms.
Object cache (what you see) Server cache (what database sees)
Time 1 Object A: state 1, no lock
Object B: state 1, no lock
Time 2 You read Object A with a read lock. Object A: state 1, read lock
Object B: state 1, no lock
Time 3 You see Object A with state 1. Because Object A: state 1, read lock
automatic downgrading has been Object B: state 1, no lock
specified, your read lock is
downgraded.
Time 4 Another session modifies Object A and Object A: state 2, write lock
Object B and commits the changes. Object B: state 2, write lock
Time 5 You see Object A with state 1, but to the Object A: state 2, no lock
database it as state 2. Object B: state 2, no lock
Time 6 You read Object B with a read lock. Object A: state 2, no lock
Object B: state 2, read lock
Time 7 You see Object A with state 1 and Object A: state 2, no lock
Object B with state 2. Object B: state 2, no lock
What you see at Time 7 is inconsistent with both the initial and the current database states, because the
objects were retrieved at different times and their read locks were dropped at different times.
178
VERSANT Database Fundamentals Manual
In other kinds of sessions, locks ensure that your objects and databases are coherent and consistent, although
you must set the right locks at the right time. In an optimistic locking session, you must take responsibility for
coherence and consistence by following an optimistic locking protocol.
In other kinds of sessions, locks ensure that your objects and databases are coherent and consistent, although
you must set the right locks at the right time. In an optimistic locking session, you must take responsibility for
coherence and consistence by following an optimistic locking protocol.
When you use optimistic locking, you should observe the following protocol.
Fetch your objects from their databases with read locks and pins.
The group of objects you bring into your object cache is called the "optimistic transaction set."
Unless you change the default lock or override the default lock in a group read method, copying an
object from a database will normally set a read lock and pin the object in your object cache.
Read locks are necessary to ensure consistency among the objects in your optimistic transaction set. For
example, if you did not fetch your objects with a read or stronger lock, a subclass object might be
updated while you were fetching a superclass object, which would compromise consistency.
Pins are necessary to avoid automatic flushing. Normally, if you run out of virtual memory for your cache,
VERSANT will swap unpinned objects back to their databases at its own discretion. This could cause
problems, because the next time you touched a swapped object, you would get a new copy which might
not be consistent with the rest of your original optimistic transaction set.
3. Downgrade locks.
Once you have a consistent group of objects, use a "downgrade locks" routine to drop locks without
changing the pin status.
179
VERSANT Database Fundamentals Manual
Alternately, for a thread, you can set automatic lock downgrading on.
Using normal procedures, browse and update the optimistic transaction set.
Because you are in an optimistic locking session, when you mark an object dirty, VERSANT will not set a
write lock on the object.
During this time, if you touch an object not fetched during the read phase, a read lock may be set by
default. In C++ , this can happen, for example, by dereferencing an object outside the optimistic
transaction set. During this stage, you can, if you want, set the default lock to a null lock, but it is better to
avoid fetching additional objects to prevent introducing an object not consistent with the original set of
objects.
Per normal behavior, check in, check out, schema evolution, and versioning methods will continue to
implicitly set write locks on class objects. If you call these methods in an optimistic locking session, these
implicit locks will be held until your enclosing short or long transaction ends.
For performance reasons, you will probably want to place all objects that you change into a vstr or
collection. This will allow you to perform a commit, group write, or group delete only on the desired
group of objects (as explained in a following step). If you place only the objects that you change into a
vstr or collection (rather than all objects in the optimistic transaction set), be sure that you also place in
the group all objects with inter-dependencies with the changed objects. Keeping inter-dependent
objects together is important. For example, if you change an object, you will want to make sure that the
time stamps for its associated objects are also checked, either at the time of a group commit or in an
explicit time stamp checking step.
Before proceeding to a commit, you should use the "check time stamp" routine to determine which of
your changed objects have become obsolete.
The check time stamp method will set the default lock on all objects checked, so if you do find objects
that are obsolete, you may want to refresh your copy with the "refresh objects" routine.
At any time, you can save or roll back your updates with a transaction commit or rollback. You can either
choose a method that will keep your objects in your cache, so that you can continue working with them,
or choose a method that will flush objects to their databases.
a. In the application process, VERSANT increments the time stamp (if it exists) of each dirty
180
VERSANT Database Fundamentals Manual
object by one.
b. VERSANT sends dirty objects from the object cache to their source databases in batches.
c. On each dirty object, VERSANT sets a write lock on its source object in its source database.
d. For each dirty object, VERSANT compares the time stamp in the dirty object with the time
stamp in the source object.
e. If the time stamp of the dirty object is greater than the time stamp of the source object by
one, VERSANT writes the dirty object to the database.
If the time stamp of the dirty object is not greater than the time stamp of the source object
by one, VERSANT does not write the dirty object to the database.
Steps a, d, and the check made in ‘e’ constitutes a "time stamp validation sequence." If all objects pass the
time stamp validation, then the commit succeeds. If one or more objects fail the time stamp validation,
then the commit fails.
At commit time, VERSANT will normally scan all objects in your object cache looking for those marked
dirty and will flush your object cache at the end of the transaction. To improve performance, you can
optionally use a method that will commit or rollback changes only to objects in a vstr or collection. This
allows you to limit the scope of the objects to be locked during the commit and whose time stamps are to
be compared.
Rolling back the transaction will restore the objects to their values before they were modified. Refreshing
the failed objects will load the latest committed objects from the server.
If you refresh the objects, you should use a read lock. The refreshed objects will be different from the
timestamp failed objects or they will have been deleted (in which case, you will see an "object not found"
error.)
For example, suppose that you have two applications, Application_1 and Application_2, who are
both working with the same two objects, ObjectA and ObjectB. Now, suppose the following sequence
of events occurs.
181
VERSANT Database Fundamentals Manual
Alternately, Application_1 could perform a "rollback and retain." For C and C++, this means using the
option O_ROLLBACK_AND_RETAIN with a transaction routine such as o_xact() or xact(). In this case,
Application_1 will see the following:
ObjectA
timestamp = 0
value = X_2
ObjectB
timestamp = 0
value = Y
182
VERSANT Database Fundamentals Manual
If Application_1 refreshes each object, then Application_1 will see the following:
ObjectA
timestamp = 1
value = X_2
ObjectB
timestamp = 1
value = Y_1
In the above, note that the timestamps for dirty objects are incremented even though they fail timestamp
validation. This is intended behavior and will only be experienced by the application whose commit fails
because of timestamp failures.
183
VERSANT Database Fundamentals Manual
If you start any kind of session other than an optimistic locking or custom locking session, you will use
"strict locking." Strict locking sessions use standard VERSANT locks to ensure database compatibility in a
multi-user environment. All types of sessions use a two-phase commit protocol to ensure consistency
among multiple databases.
Optimistic locking sessions are compatible with strict locking sessions run by other users. Once you have
defined a time stamp attribute, it will be updated by commits and group writes performed in any kind of
session.
Intra-ses
Intra-session
-session compatibility
Check out, check in, schema evolution, and versioning methods implicitly set write locks on VERSANT
system objects, regardless of the type of session. This means that if you use a check out, check in,
schema evolution, or versioning method in an optimistic locking session, you will really be running with a
mixture of optimistic and strict locking. This will cause no problems, as long as you stay aware of what is
happening.
Savepoints are not compatible with optimistic locking, because setting a savepoint flushes objects to their
databases, which resets locks.
The C++/VERSANT link and link vstr classes automatically obtain locks on database objects when you
first touch an object within a transaction. This may cause confusion when using optimistic locking.
Delete behavior
Query behavior
If you use a query, remember that a query first flushes all dirty objects and marks them as clean before
184
VERSANT Database Fundamentals Manual
selecting objects. This means that if you update an object, perform a query, and then update the object
again, you must mark the object dirty a second time to ensure that the second update is written to the
database.
185
VERSANT Database Fundamentals Manual
Examples
Optimistic Locking, C++/VERSANT Example
The following C++/VERSANT example assumes that the Employee class has a time stamp attribute:
#include <cxxcls/vdate.h>
#include <stream.h>
#include "person.h"
#include "departme.h"
class Employee : public Person
{
protected:
// attributes
Link<Department> department;
o_u4b number;
o_float salary;
PString job_description;
VDate last_raise;
public:
O_TS_TIMESTAMP;
// constructor and destructor
Employee(
const PString& name,
Department* department,
o_u4b number,
o_float salary,
const PString& job_description);
virtual ~Employee();
// accessors
virtual Department& get_department() const;
virtual o_u4b get_number() const;
virtual o_float get_salary();
virtual PString get_job_description() const;
// modifiers
virtual void set_department(Department* department);
virtual void set_number(o_u4b number);
virtual void set_salary(o_float salary);
virtual void set_job_description(
const PString& job_description);
// inherits key methods from Person
};
186
VERSANT Database Fundamentals Manual
187
VERSANT Database Fundamentals Manual
// Create instances
// note the placement of the constructor arguments
Department* rd = new Persistent Department("RD");
Employee* john = new Persistent Employee(
"John Brown",rd,100022,35000.00,"Engineer");
Employee* lisa = new Persistent Employee(
"Lisa Dilon",rd,100023,45000.00,"Test Pilot");
Employee* james = new Persistent Employee(
"James Lee",rd,100024,61500.00,"Supervisor");
cout << "\n\t Created instances for Department
and Employee classes.\n"
<< endl;
// Commit the Transaction
::dom->commit();
// Find all employees in group database:
LinkVstr<Employee> staff =
PClassObj<Employee>.select(groupDB,TRUE,PPredicate());
cout << "\n\t Selected all Employees instances.\n" << endl;
toPin = TRUE;
::dom->greadobjs( staff, groupDB, RLOCK, toPin );
cout << "\n\t All employees objects are read.\n" << endl;
::dom->downgradelocks( staff, NOLOCK, &lBackVstr );
cout << "\n\t Locks are downgraded.\n" << endl;
cout << "\n\t Output from " << concurrentProgram << endl;
cout << "\n\t ********************* " << endl;
sprintf( cmd, "%s %s", concurrentProgram,
groupDB, concurrentProgram);
system(cmd);
cout << "\n\t ********************* " << endl;
//
// Take the first object selected. Conversion from Employee_link
// to Employee* happens.
//
Employee* worker = staff[0];
// Update an Employee object:
worker->set_salary(50000.00);
cout << "\n\t Updated an Employee instance. \n" << endl;
for( i = 1; i < staff.size(); i++ )
{
if (staff[i] != NULL_LINK)
lVstr.add(staff[i]);
}
callDestructor = FALSE;
try
{
::dom->gdeleteobjs(
lVstr, groupDB, ×tampVstr, &deletedVstr, callDestructor );
}
catch( PError& error )
188
VERSANT Database Fundamentals Manual
{
cout << "\n\t gdeleteobjs() comes across";
switch( error.errnum )
{
case OB_NO_SUCH_OBJECT:
cout << "\n\t\t<Error 5006>, OB_NO_SUCH_OBJECT." << endl;
break;
case SM_E_KEY_NOT_FOUND:
cout << "\n\t\t<Error 1009>, SM_E_KEY_NOT_FOUND." << endl;
break;
case OM_TR_INVALID_TS:
cout << "\n\t\t<Error 4036>, OM_TR_INVALID_TS:" << endl;
cout << "\n\t\tSome objects have been updated in the
database." << endl;
break;
case SM_LOCK_TIMEDOUT:
cout << "\n\t\t<Error 2903>, SM_LOCK_TIMEDOUT: ";
cout << "\n\t\tLock wait timed out." << endl;
break;
default:
cout << "\n\t\t<Error " << error.errnum << ">" << endl;
break;
}
for( i = 0; i < timestampVstr.size(); i++ )
{
if (timestampVstr[i])
{
cout << "\n\t\tObject " \
<< ((Link<Employee>)(timestampVstr[i]))->get_number();
cout << " was updated and commited before." << endl;
cout << "\n\t\tRefresh this object." << endl;
}
}
for( i = 0; i < deletedVstr.size(); i++ )
{
if (deletedVstr[i])
{
cout << "\n\t\tObject " \
<< ((Link<Employee>)(deletedVstr[i]))->get_number();
cout << " was deleted already." << endl;
cout << "\n\t\tRollback this object." << endl;
}
}
}
end_try; // for cfront only !!
// Release vstr memory
lBackVstr.release();
lVstr.release();
189
VERSANT Database Fundamentals Manual
if (deletedVstr.size() > 0)
{
cout << "\n\t Roll back the changes on objects which have ";
cout << "already been deleted." << endl;
cout << "\n\t Roll back with the O_ROLLBACK_AND_RETAIN and ";
cout << "O_RELEASE_VSTR options " << endl;
::dom->xactwithvstr(
(O_ROLLBACK_AND_RETAIN | O_RELEASE_VSTR),
0, deletedVstr, ×tampVstr, &lBackVstr );
cout << "\n\t Rolled back " << deletedVstr.size();
if (deletedVstr.size() > 1)
cout << " objects." << endl;
else
cout << " object." << endl;
}
lBackVstr.release();
if (timestampVstr.size() > 0)
{
VstrAny movedVstr;
cout << "\n\t Refresh the objects which have already
been updated." << endl;
::dom->refreshobjs( timestampVstr, groupDB, RLOCK, &movedVstr );
cout << "\n\t Refreshed " << timestampVstr.size();
cout << " Employee instances. Their current salary are:\n"
<< endl;
for (i = 0; i < timestampVstr.size(); i++)
{
cout << "\n\t "
<< ((Link<Employee>)(timestampVstr[i]))->get_number();
cout << "\t"
<< ((Link<Employee>)(timestampVstr[i]))->get_salary();
cout << endl;
}
// Delete the Employee object again:
// Since refreshobjs() sets RLOCK on the objects, gdeleteobjs()
// should succeed.
::dom->gdeleteobjs(
timestampVstr, groupDB, &lBackVstr,
&deletedVstr, callDestructor );
cout << "\n\t Deleted the Employee instances again. \n" << endl;
}
// Release vstr memory
lBackVstr.release();
timestampVstr.release();
deletedVstr.release();
lVstr.add(staff[0]);
// check the time stamp value
try
{
o_opts options;
190
VERSANT Database Fundamentals Manual
options[0] = O_INPUT_VSTR;
::dom->checktimestamps(
options, lVstr, ×tampVstr, &deletedVstr );
}
catch( PError& error )
{
cout << "\n\t checktimestamps() comes across";
switch( error.errnum )
{
case OB_NO_SUCH_OBJECT:
cout << "\n\t\t<Error 5006>, OB_NO_SUCH_OBJECT:" << endl;
break;
case SM_E_KEY_NOT_FOUND:
cout << "\n\t\t<Error 1009>, SM_E_KEY_NOT_FOUND:" << endl;
break;
case OM_TR_INVALID_TS:
cout << "\n\t\t<Error 4036>, OM_TR_INVALID_TS:" << endl;
cout << "\n\t\tObject(s) has(ve) been updated in the
database." << endl;
cout << endl;
break;
case SM_LOCK_TIMEDOUT:
cout << "\n\t\t<Error 2903>, SM_LOCK_TIMEDOUT: ";
cout << "\n\t\tLock wait timed out." << endl;
break;
default:
rethrow;
}
for( i = 0; i < timestampVstr.size(); i++ )
{
if (timestampVstr[i])
{
cout << "\n\t\tObject " \
<< ((Link<Employee>)(timestampVstr[i]))->get_number();
cout << " was updated and commited before." << endl;
cout << "\n\t\tRefresh this object." << endl;
}
}
for( i = 0; i < deletedVstr.size(); i++ )
{
if (deletedVstr[i])
{
cout << "\n\t\tObject " \
<< ((Link<Employee>)(deletedVstr[i]))->get_number();
cout << " was deleted already." << endl;
cout << "\n\t\tRollback this object." << endl;
}
}
}
end_try; // for cfront only !!
191
VERSANT Database Fundamentals Manual
rolledBack = FALSE;
if (deletedVstr.size() > 0)
{
cout << "\n\t Roll back the changes on objects which have ";
cout << "already been deleted." << endl;
cout << "\n\t Roll back with the O_ROLLBACK_AND_RETAIN and ";
cout << "O_RELEASE_VSTR options." << endl;
::dom->xactwithvstr(
(O_ROLLBACK_AND_RETAIN | O_RELEASE_VSTR), 0,
deletedVstr, ×tampVstr, &lBackVstr );
cout << "\n\t Rolled back " << deletedVstr.size();
if (deletedVstr.size() > 1)
cout << " objects." << endl;
else
cout << " object." << endl;
rolledBack = TRUE;
}
lBackVstr.release();
if (timestampVstr.size() > 0)
{
o_bool moved;
moved = FALSE;
cout << "\n\t Refresh the objects which have ";
cout << "already been updated." << endl;
::dom->refreshobj( timestampVstr[0], RLOCK, &moved );
cout << "\n\t Refreshed an Employee instance." << endl;
cout << "\n\t Its current salary is ";
cout << ((Link<Employee>)(timestampVstr[0]))->get_salary()
<< endl;
// Update the Employee object again:
worker->set_salary(50000.00);
cout << "\n\t Updated the Employee instance again. \n" << endl;
}
// Release vstr memory
lBackVstr.release();
timestampVstr.release();
deletedVstr.release();
if (!rolledBack)
{
// If the above update has to be rolled back because the object
// has already been deleted, then do not commit the transaction.
// Otherwise, commit the transaction.
try
{
cout << "\n\t O_COMMIT_AND_RETAIN and O_INPUT_VSTR commit.\n"
<< endl;
::dom->xactwithvstr(
(O_COMMIT_AND_RETAIN | O_INPUT_VSTR), NULL,
lVstr, ×tampVstr, &deletedVstr );
}
192
VERSANT Database Fundamentals Manual
193
VERSANT Database Fundamentals Manual
194
VERSANT Database Fundamentals Manual
The information in this chapter is provided for situations when you want to control memory usage explicitly. It
also contains caution statements and recommendations for the case of running an application with one
process.
195
VERSANT Database Fundamentals Manual
Overview
When you start a session, VERSANT uses three kinds of memory.
Object cache
The application memory area includes an object cache for objects accessed in the current transaction and
related object cache management tables.
The database memory area includes a page cache for recently accessed objects and related database
cache management tables.
Process memory
Either one or two processes (depending upon circumstances) are used to manage database and
application memory.
C++/VERSANT note — C++/VERSANT default memory management is suitable for most situations. For
example, VERSANT automatically caches and pins objects in application virtual memory when you
dereference them. Also, VERSANT automatically maintains a page cache in server shared memory.
196
VERSANT Database Fundamentals Manual
Object Cache
To improve access to objects in a short transaction, when you start a database session, VERSANT creates an
object cache.
Also created are various information tables to track your processes, connected databases, and long and short
transactions. One of these information tables is the cached object descriptor table, sometimes abbreviated as
"cod table." It is a hash table keyed on logical object identifiers.
Usually the object cache is in process virtual memory on the machine running the application. If you start a
special kind of session, called a "shared session", the object cache will be in shared memory where it can be
accessed by multiple processes in an application.
Normally, the object cache contains objects accessed in the current transaction and is cleared whenever a
transaction commits or rolls back.
When an application requests an object, VERSANT first searches the object cache, then the server cache for
the database to which the object belongs, and then the database itself.
Since VERSANT uses an object cache, both transient and persistent VERSANT objects and transient
non-VERSANT objects created within a session are no longer valid when the session ends. Similarly, instances
created outside a session are no longer valid when a session begins. For this reason, you should avoid using in
a session any objects that were created outside a session.
Also, since most VERSANT classes make use of the object cache, you should avoid using instances of
VERSANT classes outside of a session. Exceptions are the Vstr<type> and PString classes which have an
alternative storage mechanism when the object cache does not exist. However, even for these exceptions,
you cannot use in a session any instances created outside of a session.
197
VERSANT Database Fundamentals Manual
C++/VERSANT example — For example, if you create an instance of PString before a session, it will be
invalid when the session begins. If you create an instance during a session, it will be invalid after the session
ends.
main()
{ dom = new PDOM;
// example of creating a PString before a session
PString s1 = "before session";
{ // example of creating a PString in a session
dom -> beginsession( "mydb", NULL );
// string s1 is now invalid
PString s2 = "inside session";
dom -> endsession();
// string s2 is now invalid
}
}
Pinning objects
When you access a persistent object by dereferencing a pointer or using the locateobj() method, it is
retrieved from its database, placed in the object cache, and "pinned" in the cache. "Pinning" means that the
object will physically remain in the cache until the pin is released.
Pins are released when a transaction commits or rolls back. Pins are held when a transaction checkpoint
commits.
You can explicitly unpin a persistent object with the unpinobj() method. If you unpin an object, it becomes
available for movement back to its database. Whether or not it will actually be removed from memory and
returned to its database depends upon available virtual memory and internal VERSANT logic. When you
unpin an object, a pointer to the object may become invalid, as its location in memory is no longer guaranteed.
Unpinning an object does not abandon any changes made to the object, and regardless of its location, all
transaction safeguards of object integrity still apply after an object has been unpinned.
When the object cache is cleared, persistent objects marked as dirty are written to their database, and all
persistent objects are dropped from object cache memory. To save changes but hold persistent objects in the
object cache, you can perform a checkpoint commit.
Because clearing the cache removes all persistent objects from memory, all pointers to them are invalid.
However, after a commit or rollback, you can still use links and pointers to transient objects created or
198
VERSANT Database Fundamentals Manual
Releasing objects
You can explicitly release an object from the object cache with the releaseobj() method, even if the object
was previously pinned or marked as dirty.
Releasing an object can be useful if you have no further use for the object in the current transaction.
Releasing an object does not return it to its database, it just releases it from the object cache. If you have
previously modified the object in the current transaction, its state will be indeterminate if it was previously
unpinned, since it may or may not have been moved back to its database before being released. If you have
previously explicitly written changes to the database with a group write, the changes will be saved to its
database at the next commit or checkpoint commit.
Because releasing an object can cause its state to become indeterminate, it is usually used only with objects
accessed with a null lock.
If you need to use a released object later in a transaction, you should explicitly retrieve it from its database
again, with locateobj(), or by selecting and dereferencing it.
The memory table associated with an object cache is called the "cached object descriptor table," which is
sometimes abbreviated to "cod table." It is a hash table keyed on logical object identifiers.
The cached object table contains an entry for each object accessed in the current database session. These
entries are maintained regardless of the location of the object, either in memory or on disk, and survive the
ending of individual short and long transactions.
The cached object table entry for an object is called the object's "cached object descriptor" and, among other
things, contains space for a pointer to the object, if it is in memory, and the object's unique identifier, its logical
object identifier. The term "cached object descriptor" is often abbreviated as "cod", and the term "logical
object identifier" is often abbreviated as "loid".
When you use a link, you are using an indirect reference to a persistent object through the cached object
descriptor for the object. If the object is in the object cache, dereferencing a link uses the pointer entry in the
cod. If the object is on disk, dereferencing a link uses the logical object identifier entry in the cod.
199
VERSANT Database Fundamentals Manual
The pointer and loid entries in a cached object descriptor can be in one of four states:
For each object, besides a loid and a pointer, the cached object descriptor table also maintains the following
information about the status of an object in the object cache:
Status of lock level When you access an object, you implicitly or explicitly set a null, read, update, or write
lock. The C++/VERSANT default, which can be changed, is to set a read lock.
Status of pinning When you access an object in C++/VERSANT, you implicitly pin the object in the
object cache. You can override this behavior with explicit pin and unpin methods. You can access objects with
pointers only if they have been pinned. To access an unpinned object, you must use an object link.
Status of update When you change an object, you must mark it as dirty so that its database will be
updated at the next commit.
Status of newWhen you create a new persistent object, it is automatically marked for creation in the default
database at the next commit.
Status of deleted When you delete an object, it is automatically marked for deletion from its database at
the next commit.
200
VERSANT Database Fundamentals Manual
Because of the information maintained in the cached object descriptor table, when you access an object, all
relevant information about the object is immediately known to the application. The intermediate level of
indirection of cached object descriptors also allows VERSANT to manage memory as needed without
affecting the application.
The size of the cached object table is limited only by available memory. There is one cached object table for
each active database session.
Since a session has only one short transaction active at any time, only that active transaction can access its
cached object table. Since each transaction has its own object cache, it is possible to have duplicate copies of
the same object residing in different caches of different transactions conducted by different applications.
When a transaction ends with a commit or rollback, cached objects and their short locks are released, but the
cached object descriptors remain in the cached object table. Exceptions to this behavior are parent
transactions that inherit locks when a descendant terminates, or when you perform a checkpoint commit that
saves changes but maintains locks.
When a database session ends, both objects and cached object descriptors are freed, and all short locks are
released.
Cached object descriptors and their states are transparent to an application. You do not need to be concerned
with the cached object descriptor table except in the following situations:
Large transaction By default, when you access an object, C++/VERSANT pins it in the object cache until
the transaction ends.
If you are accessing an extremely large number of objects in a transaction, then it is conceivable that you
might run out of virtual memory. In this case, you might want to control pinning explicitly.
If you do unpin an object, you need to remember that you then cannot access it with a pointer.
Long session The cod table maintains an entry for each object accessed in a session. The cod table is not
cleared until a session ends.
In a continuous session over an extremely long period of time, it is conceivable that you might run out of
memory for the cod table. In this case, you might want to clear the cached object descriptor table.
Clearing the cached object descriptor table is not necessary until after you have accessed several million
objects in a session.
If you do clear the cached object descriptor table, you need to remember that you lose all information about
the object, including its update status.
201
VERSANT Database Fundamentals Manual
202
VERSANT Database Fundamentals Manual
To improve access to objects used frequently by multiple applications, when you start a database, either
explicitly with the startdb utility or implicitly by requesting a database connection with connectdb() or
beginsession(), VERSANT creates a page cache for that database.
The database page cache contains disk pages for objects recently requested by an application. The database
cache is in shared virtual memory on the machine containing the database so that server processes associated
with multiple applications can access the same pages.
Also associated with the database page cache are tables which tracks its contents, track which objects have
been locked by which application, and log file buffers.
Pages in the database cache, like objects in the object cache, can have either a "dirty" or "clean" status, which
means that a page either does or does not contain objects that have been updated.
Pages are always written to their database when a session ends or a database is stopped. You can also control
whether dirty pages are immediately saved to disk when a commit or direct group write occurs by enabling
the server process profile parameter commit_flush.
In any case, if logging is turned on, log files automatically track all changes so that the timing of page writing
does not affect database integrity.
203
VERSANT Database Fundamentals Manual
Software Modules
To understand how VERSANT uses processes, you need to know that VERSANT is structured into several
modules.
VERSANT Manager
The portion of VERSANT that runs on the same machine as a client application is called VERSANT Manager.
• Manipulates classes,
• Distributes requests for queries, updates, links, versions, checkouts, and checkins to server processes
that manage databases,
• Establishes connections to databases which may be on the same machine or another machine,
Application programs communicate with VERSANT Manager through language specific interfaces. VERSANT
Manager, in turn, communicates with one or more VERSANT Server modules which manage databases.
VERSANT Manager functionally consists of several subsidiary software modules. The VERSANT Schema
Manager, creates and manipulates class definitions. The VERSANT Query Processor supports queries for
particular objects and iteration over classes of objects. Other modules cache objects and provide session, long
transaction, and distributed transaction support.
VERSANT Manager is structurally divided into two parts: one part is associated with an application and an
object cache and another part is associated with a VERSANT Server and a server page cache. The structural
organization, like the functional organization, is transparent to developers and users, but it provides the
204
VERSANT Database Fundamentals Manual
To create an application capable of accessing a VERSANT database, you link VERSANT Manager into your
application. It then appears to your application that VERSANT Manager moves objects from the database into
and out of your application. This happens automatically, and your application need not explicitly control
object movement as objects will be supplied as your application accesses them.
VERSANT Server
The portion of VERSANT that runs on the machine where data is stored or retrieved from is called VERSANT
Server. VERSANT Server:
• Evaluates objects on disk or in the server page cache per queries sent by client applications,
• Performs disk and storage management tasks such as object retrieval, object update, page caching,
manages storage classes, indexes, short transactions, logging, and locking,
• Locks objects,
• Manages indexes.
The term "VERSANT Server" refers to a software module and should not be confused with the term "server",
which refers to a machine running VERSANT Server software.
VERSANT Server is the base level of a VERSANT object database management system. It interfaces with the
operating system to retrieve and store data in database volumes and communicates with VERSANT
Managers. At the VERSANT Server level an object is a simple physical data structure consisting of a fixed
number of fields. Each VERSANT Server accesses one database.
When a client application begins a database session, it automatically runs both VERSANT Manager and
VERSANT Server.
Between a VERSANT Server and an operating system is a Virtual System Layer specific to the hardware
205
VERSANT Database Fundamentals Manual
platform. The Virtual System Layer isolates operating system code and provides portability across hardware
boundaries. To allow portability, you must use VERSANT elemental and/or Class Library data types in your
programs and recompile your programs as appropriate for each platform.
The Network Layer translates messages to the appropriate network protocol and moves objects as network
packets.
206
VERSANT Database Fundamentals Manual
Overview
Beginning with Release 5, database connections start threads rather than processes. This database server
architecture is invisible to applications, but the multi-threaded design saves systems resources (because a
thread context is smaller than a process context) and improves performance (because thread context
switching is faster than process context switching.)
The maximum number of concurrent connections to a database is determined by the server process
transaction parameter. There is no limit on the number of concurrent connections, except those imposed
by memory and system resources.
Startup process
When you start a database, either with the startdb utility or by making a connection request, a startup
process is created. This process creates an initial block of shared memory, sets up database control
tables, mounts the database system volume, performs database physical and logical recovery, and, in
general, prepares the database for use. The startup process then forks a cleanup process and then dies.
Cleanup process
The cleanup process stays in existence as long as the database is running. At regular intervals, it wakes up
and removes unused system resources, such as dead threads. If a dead thread is found, the cleanup
process will clean up the uncommitted transaction and any database resources left by the thread.
The cleanup process also responds to the first request for a database connection.
Server process
When the cleanup process receives the first request for a database connection, it starts the server
process. The server process then stays in existence as long as the database is running.
When it receives a connection request, the server process creates a thread that becomes associated with
the application session that requested the connection.
207
VERSANT Database Fundamentals Manual
Server thread
All database requests by a particular session are handled by the server process thread created for it. The
server thread remains in existence as long as the session that created it remains in existence, even if that
session itself has no application threads in it.
Following is a detailed description of what happens when an application requests a database connection.
1. The application sends a connection request to the TCP/IP inetd daemon on the machine containing the
database.
2. The inetd daemon starts the ss daemon associated with VERSANT in its configuration tables.
3. The ss daemon checks to see whether the database has already been started.
If the database has not been started, the ss daemon invokes the startup process, which forks the cleanup
process and then terminates. The cleanup process then starts the server process and forwards the
connection request to it.
If the database is already running, the ss daemon forwards the connection request to the server process
and then terminates.
5. The server thread gets its port number and gives it to the ss daemon. The ss daemon then sends the
port number to the application. The application process then sets up a connection between the server
thread and the application.
7. When the database is stopped with the stopdb utility, the server process and cleanup process terminate.
Backwards compatibility
For backwards compatibility, separate server processes are started for applications linked with libraries
previous to Release 5.
208
VERSANT Database Fundamentals Manual
An application and a database will run in a single process if all of the following are true:
1. The application has been linked with the VERSANT single process library.
4. The database is the "one-process database." The "one-process database" is the database associated with
the first connection made by the process that satisfies rules 1-3 above.
In a process, there can be only a single one process database at a time. However, if all connections to the
one-process database are dropped by the application process (in all of its sessions,) then a different database
can be used as a one-process database.
Per the above rules, if there are multiple threads in a session, they will all share the one-process connection
associated with the session.
For a particular process, all one-process connections must be to the same database. The database by
definition is the "one-process database" for that application process (including all of its sessions.)
Each connection to a database other than the one-process database will, of course, start a separate server
thread on the connection database.
The first time a one process linked application connects to a database that satisfies the above conditions
(linking + same machine + dba + single 1P database), the connecting session will get a one process
connection to that database. The application may create more sessions, which will also get one process
connections if they connect to the same database. Any connection to any other database, even if it meets the
three conditions, will be made in two process mode. This remains true until all of the application's one process
connections have been terminated. In other words, a database can have more than one application process
with a one process connection, but an application can have only one one-process database.
209
VERSANT Database Fundamentals Manual
• Application Process 1 has a session connected in one process mode to DatabaseA. It closes that
connection, and then connects in one process mode to DatabaseB.
Running with the one process model will improve performance, but it will slow linking and create a potential
for database corruption. The potential for database corruption exists because an abnormal application exit
may (or may not) leave the database in an indeterminate state. An example of an abnormal exit is one caused
by use of Ctrl-C. This is an operating system issue for all databases running with a single image mode.
There is no automatic cleanup mechanism for threads using the one process model, because if you get an
exception, the whole process dies. Leftover application resources will be freed if you do not catch the
exception and the application process terminates. If you catch the exception, you can either explicitly end the
session in which the thread was running or you can attempt to continue to use the session. If the exception
was raised because data structures in the session were corrupted, continuing to use the session is likely to
result in another exception.
210
VERSANT Database Fundamentals Manual
When you start your database session with beginsession(), if you specify a session database on a different
machine, you will run in two processes regardless of which VERSANT library you choose. Also, each
connection to a group database after the session connection will also start a process on the machine
containing the database.
This will block signals, such as Ctrl-C, which can terminate an application abnormally.
Signal blocking is enabled by turning on the signal_block parameter in your application process profile
file. See the VERSANT Database Administration Manual for more information.
You may want to enable commit flushing, which will cause the database cache to be flushed to disk after
each commit. However, turning on commit flushing will negate some of the performance gain achieved
by running with one process if you are accessing the same objects in successive transactions (because
the objects will have to be retrieved from disk with each access).
Commit flushing is enabled by turning on the commit_flush parameter in your database server process
profile file. See the VERSANT Database Administration Manual for more information.
• Use only objects that you have checked out, which will enable you to recopy the original objects if your
application terminates abnormally.
• Immediately stop the session database with the stopdb utility, which will release its shared memory.
• If you have been using only checked out objects, when you next start a session using that database, roll
back your long transaction and check out needed objects again. This will restore the database to a known
state.
• If you have been using objects not checked out from another database, proceed with caution.
211
VERSANT Database Fundamentals Manual
The primary reason for the above recommendations is that the separate memory areas for application and
database caches are managed with different mechanisms.
• An application owns and controls its object cache, which is normally in process virtual memory. (The
exception is the case of a shared session.)
If any application exits abnormally, the application cache is released, and transaction mechanisms ensure
that no problems related to the object cache occur when the application restarts.
• A database owns and controls its page cache, which is always in shared virtual memory. Page caches are
for the use of any application using the database, and VERSANT swaps objects between page caches and
disks at its own discretion.
There is no way for you to control database page caches from within an application. Database shared
memory is released only when a database is explicitly stopped with the stopdb utility or when it times
out per the db_timeout parameter in the server process profile file. (For more information on stopdb
and db_timeout, see the VERSANT Database Administration Manual.)
If you run with two processes and an application exits, normally or abnormally, the database page cache
is not affected. A separate process associated with the database will remain to run cleanup after the
terminated process.
Process alternatives
212
VERSANT Database Fundamentals Manual
In this case, the application is running with two processes. The "client machine" and the "server machine"
could be the same machine with the only difference being that the connection would be internal, rather than
through a network connection.
213
VERSANT Database Fundamentals Manual
Multiple users can connect to the same group database. The following example shows two applications using
the same group database as their session database.
214
VERSANT Database Fundamentals Manual
For information about sessions in general, see the chapter "Sessions." For information about specific routines,
see your interface reference manual.
The terms "multiple process session" and "shared session" are used to refer to a session in which multiple
processes are allowed. (The term "shared session" refers to the fact that the multiple process share the object
cache, which is in shared memory.)
As this is written, C/VERSANT and C++/VERSANT support multiple process sessions. J/VERSANT Interface
(JVI) does not support multiple process sessions.
215
VERSANT Database Fundamentals Manual
Any process in a transaction can end that transaction with a commit or rollback.
Only the process that began the session can end it. Before the session can end, all other processes in the
session must have ended.
If you are using nested transactions, before you can commit or rollback a particular transaction, all descendant
transactions must have terminated. If you try to end a transaction and there are child processes started in the
context of that transaction still running, you will be blocked until they end.
In a simple application, you should have each process be responsible for a mutually exclusive set of objects.
In this case, you only have to worry about the timing of your commits and rollbacks (which clear the cache).
One approach to maintaining the cache is to perform checkpoint commits when all objects are ready to be
saved and then explicitly release from the cache those objects you no longer need.
In a more complex application, multiple processes may need to read and update the same objects. Since the
processes operate in different transactions, locks will protect you.
216
VERSANT Database Fundamentals Manual
You can optionally combine a multiple process session with other session options, such as nestable
transaction and custom locking options.
A performance option is the "single writer, multiple reader option," specified with O_MRSW_SHARE, in which no
latches are held on the object cache when a process reads or writes to a database.
To start a session in which you can use multiple processes, specify the O_SHARE or O_MRSW_SHARE option to
the following routines.
c o_beginsession()
c++ beginsession()
VSession()
As with all sessions, ending a multiple process session will commit and terminate the session root transaction;
disconnect the application process from all group databases; commit, rollback, or detach from the current
long transaction depending upon options supplied to "end session;" and end the session.
You cannot end a multiple process session until all child processes in the session have been either terminated
with an "exit" routine or detached from the session with a "detach session" routine. The detach session
mechanism is only relevant to child processes, because the preferred way to end the session root process is to
end the session. (The exit routine will also end the session root process.)
Just as in nestable transaction sessions, you cannot end a multiple process session with an end session routine
until all transactions except for the session root transaction have ended. As with nestable transaction sessions,
if the exit routine is called when the current transaction is not the session root transaction, the exit routine will
still terminate the calling process.
Ending a session also automatically closes all connections made in that session.
If you also used the nestable transaction option when you started the session, you must end the session in the
session root transaction with no child transactions.
217
VERSANT Database Fundamentals Manual
c o_endsession()
c++ endsession()
~VSession()
Exit process
In multiple process sessions, exit routines will terminate the calling process and record the normal or abnormal
ending of the process in the VERSANT session process table.
If you specify the rollback option, the exit routine rolls back the short transaction, continues the long
transaction, and ends the session before terminating the application process.
If you also used the nestable transaction option when you started the session and are in the session root
transaction, no child transactions exist, and you specify the commit option, the exit routine commits the short
transaction, continues the long transaction, and ends the session before terminating the application process.
If you also used the nestable transaction option when you started the session and are in the session root
transaction, no child transactions exist, and you specify the rollback option, the exit routine rolls back the
short transaction, continues the long transaction, and ends the session before terminating the application
process.
If you also used the nestable transaction option when you started the session, in the following cases, the exit
routine will return an error, which is ignored because an exit routine cannot return. The result of these cases is
an abnormal termination, which is similar to a system crash:
• If you are in the session root transaction and child transactions or nested processes exist.
• If you are in the root process but not in the session root transaction.
• If you are in a child process but not in the process root transaction.
If you are in a session and running with multiple processes, you can detach a child process without ending the
session, as long as the session root process remains, by using the detachsession() routine. Once the
process has been detached, you can then terminate it normally or call the joinsession() routine at a later
time.
c o_exit()
c++ exit()
218
VERSANT Database Fundamentals Manual
Start process
To start a process that will join a session, you can use either VERSANT routines or UNIX fork and exec
routines. When you start a new application process, the spawned process is called the "child process," and the
spawning process is called the "parent process."
The following routines will start a child process using VERSANT mechanisms.
c o_forkprocess()
c++ forkprocess()
When a process joins a session, its presence is recorded in a session process table.
Because the object cache in a multiple process is in shared memory, all processes access the same object
cache.
Each time a child process joins a session, a server process associated with that child process is started on all
connected databases. Similarly, once multiple processes are running in a multiple process session, each time a
database connection is made with a connect database routine, a server process on the connection database is
started for each process in the session.
The following routines will join the current process to the session of the parent process and implicitly start a
nested subtransaction under the current transaction of the parent process and make it the current transaction
within the child process.
c o_joinsession()
c++ joinsession()
No further work involving VERSANT software or databases may be performed by the detached process
unless the join session routine is called again. You must call the detach session routine in the process root
transaction in a child process that is currently part of a session.
c o_detachsession()
c++ detachsession()
219
VERSANT Database Fundamentals Manual
Following is an example of the appropriate sequence of calls when multiple processes are operating in a
multiple process session. The routines are shown as C/VERSANT commands.
The root process, P1, calls "begin session" with the "nest" and "share" options. This allows the use of
nestable transactions and multiple processes in the session.
When the session begins, a transaction, T1, is started in P1 and becomes the current transaction.
If you have linked the application with the VERSANT two process libraries, the "begin session" call also
starts a server process, S1, on the database, DB1, being used as the session workspace. Regardless of
your choice of linked libraries, a server process is started if your session database is on another machine.
220
VERSANT Database Fundamentals Manual
The root process P1 connects to a secondary group database, DB2, which in turn starts its own server
process, S2, to handle the connection.
The root process P1 starts a nestable transaction, T2, which becomes the current transaction within P1.
The child process P2 calls "join session" to become part of the session and uses the session workspace. A
nestable transaction T3 is implicitly started in process P2. T3 is a direct descendant of T2, the current
transaction within the parent process P1.
All transactions and all processes now have access to objects in two databases: the session database and
the group database to which a connection was made at time t2.
If you are running the application with the two-process model, there are now the following processes are
now running:
P1 The session root process.
S1 The server process for the session database DB1 that is associated with the session
root process.
S2 The server process for the first connection database DB2 that is associated with the
session root process.
P2 The child process.
S3 The server process for the session database DB1 that is associated with the child
process.
S4 The server process for the first connection database DB2 that is associated with the
child process.
Process 2 at time 6: o_begintransaction()
The child process P2 calls "begin transaction", which starts a new transaction, T4. The new transaction
becomes the current transaction in P2.
221
VERSANT Database Fundamentals Manual
The child process P2 calls "end transaction" which terminates transaction T4 and causes child process P2
to revert to transaction T3.
The child process P2 calls "exit" which terminates the process root transaction T3, the server processes S3
and S4 associated with P2, and the child process P2.
The parent process P1 calls "end transaction" and performs a "commit" on transaction T2. The commit
proceeds only if child process P2 has terminated normally. Process P1 is now in session root transaction
T1.
The parent process P1 calls "end session", which closes the connections to databases DB1 and DB2, stops
server processes S1 and S2, terminates transaction T1, and terminates the application and manager
process P1.
In the following example correct and incorrect sequences are shown, and generic names for routines are
used.
This example shows that if you are starting from scratch, the preferred approach is to start a new process with
the VERSANT "fork process" routine and then join it to an existing session with "join session". Then, you must
take care to match "begin session" with "end session" and "join session" with either "exit" or "detach session" in
222
VERSANT Database Fundamentals Manual
The parent process starts a process outside the session. Using "fork process" creates an entry for the new
process in the VERSANT session process table.
Not Recommended. You can start a new session with a forked process, but it could paralyze the parent
session because "fork process" records the new process in the VERSANT session process table for the
parent. This means that you cannot end the parent session until VERSANT knows that the child process
has been terminated. Thus, if you started a second session with a child process, to end the first session
the child process would have to end the second session, then join the first session, and then detach or
terminate in a way that got recorded in the session process table for the first session.
Not Recommended.
This is the recommended procedure. The transaction in which "fork process" is called becomes the parent
of the process root transaction for the child process.
223
VERSANT Database Fundamentals Manual
To detach the child process from the session, call "detach session". To terminate the child process, call
"exit". The child process must be in its root transaction to detach or terminate normally.
A root process can end the session and continue by using "end session" or it can end the session and
terminate by using "exit". To do either, the session root process must be in its root transaction.
The following approach might be useful if you have two existing programs that you want to adapt to using
VERSANT. This situation is similar to the previous example. This example again shows that you must take care
to match "begin session" with "end session," and "join session" with either "exit" or "detach session" in the
appropriate processes.
The entry of the second process is noted in the VERSANT session process table.
Error. Only the process that started a session can end it.
PROCESS 1: end session 1
Blocked. Because the second process was noted in the VERSANT process table, the session cannot end
until the second process ends. The first process waits until the second process terminates.
Even though the second process was not started by VERSANT, you can terminate it with "exit".
224
VERSANT Database Fundamentals Manual
A root process can end a session and continue by using "end session" or it can end a session and
terminate by using "exit". To do either, the session root process must be in its root transaction.
You may want to use UNIX mechanisms to create additional processes and then join them to a session.
This example shows that if you use the UNIX "fork" command, you should immediately follow it with UNIX
"exec" and then "join session".
225
VERSANT Database Fundamentals Manual
226
VERSANT Database Fundamentals Manual
The VERSANT thread management strategy for the C++ interface has changed to comply with ODMG
standards.
The class previously used to manage threads (VThread) is no longer required. Instead, all of the thread
management operations have been added to the VSession class. Although the VThread class and
methods are still supported by VERSANT for compatibility, we strongly recommend that new applications
be created using VSession for thread management.
227
VERSANT Database Fundamentals Manual
Following are your alternatives for threads and sessions. As this is written, the VERSANT C, C++, and Java
interfaces support of multiple threads and multiple sessions.
When you use multiple threads in a single session, you must take care to coordinate the threads so that they
do not interfere with one another, because all threads access the same object cache.
When multiple threads access the same object cache, all threads are in the same short transaction and share
the same locks. Accordingly, when using "thread unsafe" methods, such as a commit, you must take care to
coordinate your work so that one thread does not interfere with the work of other threads, usually by
developing a "single writer, multiple reader" approach.
Also, you can start multiple sessions even if your operating system doesn't support multiple threads.
When you place each thread into a separate session, VERSANT coordinates the work of the threads and uses
locks and separate object caches to ensure that the threads do not interfere with one another.
A session is a unit of work. Opening multiple sessions allows you to define multiple units of work that are
distinct. This can be useful when you want to create a complex application that responds to numerous events
of a similar kind. For example, this approach is useful when you want to monitor and respond to hits on
switches or a web site.
228
VERSANT Database Fundamentals Manual
Process
A process consists of memory areas, data structures, and resources. A process always has at least one
resource called a "thread."
Thread
A thread is the part of a process that performs work.
By definition, when you start a process, you also create a thread, so a process always has at least one thread.
A process can start additional threads with mechanisms specific to its operating system.
When a process starts additional threads, each additional thread has some memory areas and data structures
of its own, but starting an additional thread does not require as many resources as starting another process.
When the process that created a thread ends, that thread ends also.
Session
A session is a VERSANT unit of work. Associated with a session are an object cache, a set of database
connections, a set of locks, a long transaction, and various session and cache support tables.
A thread can use VERSANT routines that access and manage persistent objects and databases only when it is
in a session.
Threads can open and close sessions at will, although a thread can be in only one session at a time.
A thread may associate with a database session and may exit, leaving the session open for a subsequent
thread.
229
VERSANT Database Fundamentals Manual
Short transaction
As a unit of work, a session can contain a sequence of one or many short transactions.
Each short transaction in each session maintains its own set of locks. Accordingly, within the scope of the
standard VERSANT lock model, a commit or rollback made in a short transaction in one session has no effect
on a short transaction in another session.
When multiple threads access the same session, all threads in the session are in the same database
transaction. This means that if one thread commits or rolls back a transaction, then the cache is cleared, and
the transaction is ended for all threads in that transaction.
Long transaction
A session occurs within the scope of a long transaction.
A long transaction can span a sequence of sessions and processes. The purpose of a long transaction is to
track objects checked out to personal database from a group database. You can start and continue multiple
long transactions by giving each of them a unique name.
Although multiple, simultaneous sessions can exist within the same long transaction, this is definitely not
recommended, because it can lead to unintended results. For example, if you end one of many sessions in the
same long transaction with a long transaction commit or rollback (rather than continuing it,) then all checked
out objects associated with that long transaction will be returned to their source databases. If you are using
any of those objects in another of your sessions, the results could be unpredictable.
Object cache
When you create multiple sessions, each session maintains its own object cache in application memory.
If multiple threads are in the same session, the object cache is accessible by all threads in that session. This
means that objects acquired by one thread can be accessed and changed by other threads.
In a simple application that uses multiple threads in the same session, you should have each thread be
responsible for a mutually exclusive set of objects. In this case, you only have to worry about the timing of
your commits and rollbacks (which clear the cache). One approach to maintaining the cache is to perform
checkpoint commits when all objects are ready to be saved and then explicitly release from the cache those
objects you no longer need.
In a more complex application that uses multiple threads in the same session, the multiple threads may need
to read and update the same objects. Since all threads operate in the same transaction, they share the same
locks, which means that it is possible for one thread to be reading an object while another is updating it. More
than one thread can update the same object at the same time. Since locks will not protect you, you must avoid
conflicts either by your own application programming logic or by using thread exclusion mechanisms set on
230
VERSANT Database Fundamentals Manual
individual objects or groups of objects or on attributes within an object. In either case, you must protect the
objects so that one thread changes an attribute only when all other threads are excluded.
Short lock
If you are using multiple sessions, it is as if each session were being run by a different user. This means that an
object write locked by one thread in one session cannot be accessed by another thread in another session,
even though the threads belong to the same process. In other words, lock requests by one thread in one
session can be blocked by locks held by another thread in another session.
However, if you are using multiple threads in a single session, then locks acquired by one thread are also held
by other threads, which means that locked objects can be updated by any thread and are not useful for thread
exclusion.
231
VERSANT Database Fundamentals Manual
Session Management
Session Restrictions
• You cannot start a session with both the shared session option, O_SHARE, and the multiple
threads option, O_THREAD.
• Custom locking cannot be used with the multiple threads option, O_THREAD.
• Nested transactions cannot be used with the multiple threads option, O_THREAD.
• You cannot mix the use of VERSANT C and C++ thread related routines in the same session.
• A threaded process should not fork. Forking a threaded process will duplicate all threads in the
process. This will lead to unpredictable results and must be avoided.
Session Mechanisms
Start session
The mechanisms for starting a session are:
c o_beginsession()
c++ beginsession()
VSession()
When you start a session, you must specify parameters to the routine that starts the session. Following is an
explanation of these parameters in the context of multiple threads and/or multiple sessions.
By default, the name of the long transaction that you will start or join is your username. However, we
strongly recommend that you specify a unique long transaction name for each session that you start.
If you do not specify a unique long transaction name for each of multiple concurrent sessions, problems
will arise if you check out or check in objects. If long transactions in multiple sessions have the same
name, committing or rolling back the long transaction in one session will commit or rollback the long
transaction with the same name in all other sessions, which could lead to unpredictable results unless you
232
VERSANT Database Fundamentals Manual
Each session requires a database to be used as a persistent workspace. You can use the same database as
a session database for each of many sessions, or use a different session database for each session. As
with all database connections, you must be a registered user of a database in order to use a database as a
session database.
Session name
By default, the name of your first session will be your username, but you will probably want to supply a
session name that will enable you to distinguish among sessions created later. In any case, when you start
additional sessions, the session names must be unique within the scope of a particular application
process.
Session options
Sessions also allow you to specify the kind of session that you want in one or more session options.
Following is an explanation of session options in the context of multiple threads and/or multiple sessions.
When you specify multiple options, use the | symbol to concatenate them. Option declarations last for
the duration of the session.
Null option
If you specify NULL, you will start a "standard session" using standard VERSANT short transactions and
locks.
If you start a standard session, you will be able to start additional sessions (indeed, the NULL option is the
usual case for multiple sessions.)
However, if you start a standard session, you will not be able to place multiple threads or processes in the
session.
Share option
If you specify O_SHARE, you will start a "shared session" in which you can place multiple processes (rather
than threads.)
If you use O_SHARE, you will not be able to start additional sessions or place additional threads in the
session (only processes.)
233
VERSANT Database Fundamentals Manual
If you specify O_MRSW_SHARE|O_SHARE, you will start a "high performance shared session" in which you
can place multiple processes.
In a high performance, shared session, no latches are held on the object cache when a process reads
from a database. This is also called the "single writer / multiple reader" option.
For information on shared sessions, see the chapter "Multiple Processes in a Single Session."
If you specify O_NEST, you will start a "nestable transaction session" that allows nesting of transactions.
You cannot use the nestable transaction option with the multiple thread (O_THREAD) or multiple process
option (O_SHARE.)
For information on nested transactions, see the chapter "Nestable Transaction Session."
If you specify O_OPT_LK, you will start an "optimistic locking session" that suppresses object swapping,
prevents automatic lock upgrades, and provides automatic collision notification.
You can use the optimistic locking option with any other session option.
If you specify O_THREAD, you will start a "multiple threads" session that allows use of one or many threads
in the same session.
If you use the O_THREAD option, you will be able to start additional sessions, but you will not be able to
use the O_SHARE option in order to place multiple processes in the session (you can use only multiple
threads)
Multi-session
Multi-session option
By default, a thread will join the session it starts. If you want to start a session but not join it, specify the
"do not attach" option, O_MULTI_SESSION.
234
VERSANT Database Fundamentals Manual
You must specify the O_MULTI_SESSION option if you want to use the same thread to start multiple
sessions that will be available for immediate use at a later time. It is an error to start more than one session
in a particular thread without specifying this option.
If you specify O_READ_ACCESS, you will be able to view but not change data.
The read access option can be used with all other session options.
This access mode is not currently supported, but may be supported in future releases. At present, this
mode is the same as O_READ_ACCESS|O_WRITE_ACCESS.
The write access option can be used with all other session options.
If you specify O_READ_ACCESS|O_WRITE_ACCESS, you will be able to view and edit data.
This access mode is the default and does not have to be explicitly specified. It is compatible with all other
session options.
C++ Notes
If you want to use more than one thread or if you want to start more than one session, you must start your
sessions by creating transient instances of class VSession with VSession().
Because the case of a single process in a single session is a subset of the general case of multiple threads
/ multiple sessions, you can use VSession() to start a single session that will have a single thread (the
primary thread associated with the application process.)
The beginsession() method, which starts a session and places the starting thread into the session,
continues to be available for backwards compatibility with previous VERSANT releases. However, if you
use beginsession(), you will be able to start only a single session, and your thread can leave the
session only by ending the session.
A thread can use VERSANT routines that access and manage persistent objects and databases only when
it is in a session. If you create a session with VSession(), we recommend that you invoke VERSANT
235
VERSANT Database Fundamentals Manual
However, for backwards compatibility, even if you start a session with VSession(), you can continue to
use the global variable ::dom of type PDOM* and invoke database methods on the ::dom instance, as
VERSANT is able to associate a thread with its current session.
C++/VERSANT example
The following starts a session by creating a transient instance of the VSession class.
o_opt options;
options[0] = O_THREAD;
VSession* session1 = new VSession(
"longtrans1"
"db1",
"session1",
options);
After the session has started, you can send PDOM methods to an instance of VSession. For example:
session1 ->commit();
Although we recommend that you send database methods to an instance of VSession, you can send
PDOM methods to the dom global variable, as usual. For example:
::dom->commit();
If you use the dom variable, it will delegate the method to the correct VSession object for you. For
example, if you want, say, thread15 in session4 to perform a commit, the thread sends the message
with normal syntax:
::dom->commit();
In this case, the dom variable will automatically associate thread15, which called commit(), with
session4 and cause a commit only on the current short transaction in session4.
End session
The mechanisms for ending a multiple session are:
c o_endsession()
c++ endsession()
~VSession()
A thread does not have to be in a session to end it, but it can be.
If one thread tries to end a session while another thread is still in that session, an error will be raised.
236
VERSANT Database Fundamentals Manual
As with all sessions, ending a multiple session or a session with multiple processes or threads will commit and
terminate the current short transaction, close all database connections made in that session, and commit,
rollback, or continue the current long transaction.
C++/VERSANT — If you created the session with VSession(), you must close it with ~VSession().
User data
Number of threads
User data
237
VERSANT Database Fundamentals Manual
Thread Management
Restriction
In a multi-threaded application, in a thread other than main thread of execution user cannot
create PError objects before calling a method of VSession or PDOM.
See your C++/VERSANT Reference Manual for details.
238
VERSANT Database Fundamentals Manual
Session pointer
Session name
User data
User data
239
VERSANT Database Fundamentals Manual
The following methods will replace the default error handling methods in C++/VERSANT.
c++ vpp_thread_terminate()
c++ vpp_thread_unexpected()
240
VERSANT Database Fundamentals Manual
Following are usage notes related to the use of multiple threads in the same session. Behavior varies
depending upon the interface you are using.
C/VERSANT
For the C/VERSANT interface, all entry functions into the C/VERSANT kernel library are protected by a
mutex latch. This means that one thread cannot update an object while any other C/VERSANT library
function is reading or updating it.
C++/VERSANT
The C++/VERSANT interface is built on top of the C/VERSANT library. Most C++/VERSANT methods
call the corresponding C library routine to perform database operations. However some of the
operations, like dereferencing a link, may not call C/VERSANT if the object is already in cache.
Attach a thread to the current session by using VSession::set_session(). This will initialize the thread
local variables used by C++/VERSANT.
For example, a database commit method is thread unsafe, because a commit performed by one thread will
clear the object cache even if other threads are currently accessing objects in the cache.
If you use a thread-unsafe method, you must coordinate the threads with the thread synchronization
mechanism provided by your operating system thread package. Typically a mutex and a thread counter is
used to synchronize the threads. An example is provided at the end of this note.
For operating systems that support conditional variables, a preferred synchronization mechanism is the
conditional variable. Your synchronization mechanism must ensure that only one thread (the thread executing
the thread-unsafe method) is accessing objects in the cache at the time the unsafe method is invoked.
241
VERSANT Database Fundamentals Manual
For example, if you delete an object using one thread, then that object will no longer be available even if
another thread is currently accessing the object. Because the delete changes only a single object rather than
all objects, the delete method is considered "safe with exceptions" rather than "unsafe."
If you use a "thread safe with exceptions" method, you must protect the objects affected by the method by
using a "one writer / multiple readers" exclusion mechanism (MR/SW) for each object or groups of objects. In
a MR/SW mechanism, threads that will execute only thread-safe methods enter the critical region as readers
and the thread that will execute the thread-safe-with-exceptions methods will enter the region as a writer.
Based on this scheme, the writer can execute thread-restricted methods only when no reader is in the critical
242
VERSANT Database Fundamentals Manual
region.
Similar exclusion mechanisms can be implemented on individual attributes of objects. For example, a vstr or
link vstr used as an attribute of an object could be protected rather than the entire enclosing object to increase
the concurrency. An iterator over a link vstr would enter as a reader; a method that adds to or removes an
element from the link vstr would enter as a writer.
The following C++/VERSANT routines are "safe with exceptions." The following list is not complete since all
modifier methods on vstr, link vstr, and collection classes change the object and hence are thread-restricted.
243
VERSANT Database Fundamentals Manual
For example, if you perform a checkpoint commit with one thread, objects will still be available in cache to
other threads and all threads will see the same object states. If two threads try to dereference the same link at
the same time, the end-impact on the cache is the same regardless of the order of execution.
All methods not noted above as thread-unsafe or thread-restricted are thread-safe. For example, the following
methods are thread safe.
244
VERSANT Database Fundamentals Manual
testthrd.h
The following declares the class TestThread which will be used in the example program.
#include <cxxcls/pobject.h>
#include <cxxcls/vsession.h>
class TestThread : public PObject
{
o_u4b thread_id;
o_u4b session_id;
o_4b myid;
public:
TestThread( o_4b _myid );
void set_myid( o_4b );
void print();
};
testthrd.cxx
245
VERSANT Database Fundamentals Manual
void
TestThread::print()
{
cout << "thread_id = " << thread_id << "\t"
<< "session_id = " << session_id << "\t"
<< "myid = " << myid << endl;
}
schema.imp
main.cxx
#include <stdlib.h>
#include <unistd.h>
#include <iostream.h>
#include <cxxcls/pobject.h>
#include "testthrd.h"
int t1_start();
int t2_start(char*);
o_opts options;
options[0] = O_MULTI_SESSION | O_THREAD;
246
VERSANT Database Fundamentals Manual
// create 2 threads
int status;
thread_t departed;
while( thr_join(0,&departed,(void**)&status)==0 )
{
cout << "thread " << departed
<< " returned with return value "
<< status << endl;
}
delete s1;
delete s2;
delete s3;
return 0;
}
int t1_start()
{
int thr_id = thr_self();
cout << "tid " << thr_id << ": join Session1..." << endl;
//VThread mythrd("Session1");
VSession::set_session("Session1");
247
VERSANT Database Fundamentals Manual
cout << "tid " << thr_id << ": doing commit..." << endl;
::dom->commit();
//mythrd.set_session("Session3");
VSession::set_session("Session3");
// create some persistent objects
// those objects should have a session_id=2 and
// thread_id=thr_id, they will be in database gdb
cout << "tid " << thr_id << ": doing commit..." << endl;
::dom->commit();
//mythrd.set_session("Session1");
VSession::set_session("Session1");
LinkVstr<TestThread> l = PClassObject<TestThread>::Object().select(
NULL, 0, NULL_PREDICATE);
l.release();
248
VERSANT Database Fundamentals Manual
::dom->commit();
cout << "******** thread " << thr_id << " done ********" << endl;
return 0;
}
//VThread mythrd;
//mythrd.set_session("Session2");
VSession::set_session("Session2");
cout << "tid " << thr_id << ": create 10 instances" << endl;
for(int i=0; i<10; i++)
O_NEW_PERSISTENT(TestThread)(i);
cout << "tid " << thr_id << ": doing commit..." << endl;
::dom->commit();
LinkVstr<TestThread> l = PClassObject<TestThread>::Object().select(NULL,
0, NULL_PREDICATE);
cout << "tid " << thr_id << ": connect to " << dbname << endl;
249
VERSANT Database Fundamentals Manual
h_sess->connectdb(dbname);
cout << "tid " << thr_id << ": doing commit..." << endl;
::dom->commit();
l = PClassObject<TestThread>::Object().select(dbname, 0,
NULL_PREDICATE);
size = l.size();
cout << "tid " << thr_id << ": selected " << size
<< " objects back" << endl;
for(i=0; i<size; i++)
l[i]->print();
l.release();
cout << "tid " << thr_id << ": disconnect from gdb..." << endl;
h_sess->disconnectdb(dbname);
cout << "******** thread " << thr_id << " done ********" << endl;
thr_exit(0);
return 0; // to make the compiler happy
}
makefile
250
VERSANT Database Fundamentals Manual
schema.cxx: schema.imp
clean:
-rm $(TEMP_FILES) $(INSTANCE_FILE) schema.cxx $(EXEC) $(CXX_TO_NULL)
-rm -rf Templates.DB
run_demo: all
-stopdb -f $(PDB1)
-removedb $(PDB1)
-createdb $(PDB1)
sch2db -y -D $(PDB1) schema.sch
-stopdb -f $(GDB1)
-removedb $(GDB1)
-createdb $(GDB1)
sch2db -y -D $(GDB1) schema.sch
a.exe $(PDB1) $(GDB1)
251
VERSANT Database Fundamentals Manual
Following is a listing of the sample program. This program is available in the VERSANT directory
..demo/cxx/threads or ..demo\cxx\threads.
class.h
#ifndef __CLASS_H
#define __CLASS_H
#ifdef __SVR4
#include <thread.h>
#define vpp_procSleep(x) sleep(x)
#endif
#if defined(WIN32)
#include <windows.h>
#define vpp_procSleep(x) Sleep(1000 * x)
#define vpp_mutexInit(x) InitializeCriticalSection(x)
#define vpp_mutexLock(x) EnterCriticalSection(x)
#define vpp_mutexUnlock(x) LeaveCriticalSection(x)
#define vpp_thrExit(x) ExitThread(x)
#define vpp_getTid() GetCurrentThreadId()
typedef CRITICAL_SECTION vpp_mut;
typedef DWORD vpp_thr;
#endif /* WIN32 */
#include <cxxcls/pobject.h>
252
VERSANT Database Fundamentals Manual
#endif
class.cxx
#include "class.h"
vpp_mut foo_mutex;
o_4b Test::callCount = 0;
void Test::foo()
{
vpp_mutexLock(&foo_mutex);
Test::callCount++;
vpp_mutexUnlock(&foo_mutex);
}
void TestTest::foo()
{
vpp_mutexLock(&foo_mutex);
Test::callCount++;
vpp_mutexUnlock(&foo_mutex);
}
main1.cxx
#include <stdlib.h>
#if !defined(_WIN32)
#include <unistd.h>
#endif /* _WIN32 */
#include "class.h"
#ifdef __SVR4
#include <thread.h>
typedef thread_tvpp_tid;
#define vpp_thrYield()thr_yield()
#endif
#if defined(_WIN32)
#include <windows.h>
typedef DWORDvpp_tid;
#define vpp_thrYield()Sleep(0)
#endif /* _WIN32 */
#include <cxxcls/vsession.h>
253
VERSANT Database Fundamentals Manual
vpp_mut count_mutex;
extern vpp_mut foo_mutex;
int number_of_threads;
const int objects_per_thread = 50;
void my_terminate()
{
// increate the counter so that the main thread knows this thread is
// finished
vpp_mutexLock(&count_mutex);
count++;
vpp_mutexUnlock(&count_mutex);
::vpp_thread_terminate();
}
void my_unexpected()
{
// increate the counter so that the main thread knows this thread is
// finished
vpp_mutexLock(&count_mutex);
count++;
vpp_mutexUnlock(&count_mutex);
::vpp_thread_unexpected();
}
vpp_mutexLock(&sync_mutex);
vpp_mutexUnlock(&sync_mutex);
254
VERSANT Database Fundamentals Manual
int me = atoi(arg);
int objval;
vpp_mutexLock(&count_mutex);
count++;
vpp_mutexUnlock(&count_mutex);
//mythread.cleanup();
vpp_thrExit(0);
return 0; // make the compiler happy
LinkVstr<Test> lv = PClassObject<Test>::Object().select(
gdbname,
1, // Yes, we want sub class instances
NULL_PREDICATE);
255
VERSANT Database Fundamentals Manual
lv[i]->foo();
lv.release();
vpp_mutexLock(&count_mutex);
count++;
vpp_mutexUnlock(&count_mutex);
//mythread.cleanup();
vpp_thrExit(0);
return 0; // make the compiler happy
}
int k = rand();
PPredicate pred = PAttribute("Test::value") == (o_4b) k;
LinkVstr<Test> lv = PClassObject<Test>::Object().select(
gdbname,
1, // Yes, we want sub class instances
pred);
//mythread.cleanup();
vpp_thrExit(0);
return 0; // make the compiler happy
}
256
VERSANT Database Fundamentals Manual
vpp_mutexUnlock(&sync_mutex);
LinkVstr<Test> lv;
try
{
lv = PClassObject<Test>::Object().select(
gdbname,
1, // Yes, we want sub class instances
NULL_PREDICATE);
lv.release();
vpp_mutexLock(&count_mutex);
count++;
vpp_mutexUnlock(&count_mutex);
//mythread.cleanup();
vpp_thrExit(0);
return 0; // make the compiler happy
}
257
VERSANT Database Fundamentals Manual
vpp_mutexInit(&sync_mutex);
vpp_mutexInit(&count_mutex);
vpp_mutexInit(&foo_mutex);
switch (operation)
{
case 'c':
f = database_create;
break;
case 'r':
f = database_deref_random;
break;
case 'd':
f = database_deref;
break;
case 'e':
f = database_error;
break;
default:
fprintf(stderr,"usage error\n");
exit(-1);
}
vpp_mutexLock(&sync_mutex);
o_opts option;
option[0] = O_THREAD ;
258
VERSANT Database Fundamentals Manual
{
char *which = new char[8];
sprintf(which, "%d", i);
tHandle = CreateThread(
NULL,/* security attributes */
0,/* stack size, default */
(LPTHREAD_START_ROUTINE)f,
which,/* arguments */
0,/* creation flags, default */
&tid/* thread id returned */
);
if (tHandle == NULL)
{
printf("CreateThread fails with error %d\n",
GetLastError());
exit(-1);
}
#endif
fprintf(stderr,"started a thread with id %d\n",tid);
259
VERSANT Database Fundamentals Manual
vpp_mutexUnlock(&sync_mutex);
makefile.sol
CCFLAGS= -mt -I`oscp -p`/h -g
LIBS= -L`oscp -p`/lib/sun.4.0 -L`oscp -p`/lib -lcxxcls -loscfe -lsocket -lnsl
a.out : class.o schema.o main1.o
CC $(CCFLAGS) class.o schema.o main1.o $(LIBS)
260
VERSANT Database Fundamentals Manual
261
VERSANT Database Fundamentals Manual
Since application performance depends upon the particular task and machines involved, this chapter cannot
be viewed as definitive, only as a guide to things you might want to try.
Unless otherwise noted, interface routines are referred to with generic names. Where functions or methods
are listed, the usual order is C and C++. However, this entire chapter depends upon your having access to the
reference manual for the interface you are using.
262
VERSANT Database Fundamentals Manual
Performance Statistics
To tune an application, you need to know where it is spending time. There are numerous ways to collect
statistics, which are covered in the chapter "Statistics Collection." Following is one way to do it.
In your program, make a call to a "write statistics" routine after the point where you want to gather
statistics. Depending upon your interface, this routine is o_autostatswrite(), autostatswrite(),
or autoStatsWriteOnDBs:.
To avoid collecting unwanted statistics about VERSANT routines, disable the low level collection points
built into VERSANT by setting the VERSANT_STAT_FUNCS environment variable to an empty string.
3. Specify
Specify statistics to collect.
See the chapter "Statistics Collection" for some suggestions on statistics to collect. Commonly used
statistics are:
fe_real_time — Seconds elapsed since the fe_real_time statistic was turned on.
be_data_located — The number of pages read from the server cache, including data, table, and index
pages, but not log pages. This statistic is a measure of "cache hits," which are a lot less expensive than
disk reads.
be_data_reads — The number of pages not found in the server cache, including data, table, and index
pages, but not log pages. This statistic is a measure of "cache misses," in which needed information had to
be read from disk.
be_net_write_time — Seconds spent sending data from the server process to the application. This
statistic is expensive to collect.
Tell VERSANT to collect statistics by setting the environment variable VERSANT_STAT_FILE to the name
of a file.
When doing initial performance tuning during development, writing statistics to file is usually preferable
263
VERSANT Database Fundamentals Manual
to viewing with a direct connection, because writing to file shows statistics for an application's operations
rather than for time slices. Writing to file also allows you to preserve statistics so that they can be looked
at in different ways.
Run your application within the scope of the above environment variables and collect statistics for the
activities of your application.
When you view your statistics, you should see some very useful information about where your
application is spending its time.
In the above, column 0 for fe_real_time shows which operations are taking the most time, and column
1 shows the time spent reading data.
In this case, the application is spending most of its time reading data pages during operation #1 on object
#20384. With this knowledge, you can now investigate what is special about this particular object and
operation. In this case, you might also want to immediately check the setting of the database profile
parameter max_page_buffs (which sets the maximum number of pages used by the server process
page buffer) and increase it.
Another useful tool, particularly for tuning application code, is a profiler such as Pure Software's Quantify
264
VERSANT Database Fundamentals Manual
By running a profiled version of the application, you can see how much each function contributes to the
total time which it took the application to run. Just seeing what percentage various VERSANT operations
contribute to the total cost of a user operation can be worth the effort of using a profiler.
265
VERSANT Database Fundamentals Manual
Data Modeling
If your application is spending a lot of time reading data and/or passing data across a network, consider the
following suggestions related to your data model.
During the design stage, you might want to use a "pure" object model to describe your situation. During
the development stage, you may need to modify your object model to reflect how data is used rather
than how it is structured.
The process of structuring data in terms of how it is used is sometimes called "finding your real objects."
To find "real objects," walk your model with your tasks in mind, consider how your data is actually
created, found, and updated, and make adjustments based upon the real world of disk reads and
network traffic.
Adjusting a model to reflect data use can have a major impact on performance, because pure object
models seldom consider the physical limitations of hardware. For example, if you have tiny objects
everywhere, you are going to pay large performance costs. Your data model will have minuscule
performance implications once all needed objects are in memory (where they can be accessed in
microseconds,) but it may have considerable impact on disk reads and network traffic.
Although you may look for "real objects" with performance in mind, almost certainly you will make
changes that will also improve the reusability, extensibility, and scaleability of your model. It is almost
always a good idea to base your model on data usage rather than data structure.
If you look at your data model and see lots of tiny objects, consider combining the small objects into a
larger object. Reading a single large object is cheaper than reading two smaller objects, because it
reduces the number of disk find and network message operations required.
For example, an employee object could logically either embed a name or contain a link to a separate
name object. In this case, if you will need the employee name whenever you need the employee object, it
would be faster to embed the name.
In general, if two objects are always accessed together, consider combining them into a single object in
order to improve access speed.
266
VERSANT Database Fundamentals Manual
It is certainly possible to overdo embedding. If you look at your data model and see repetitious data in
your objects, consider using links.
For example, if each employee has a department, you would not want to embed the same information
about a department in each employee object associated with that department. (It would take too much
disk space, be costly to change information about the department, and you may not need to look at
department information each time you look at an employee.) In this case, it would be better to relate an
employee to a department with a link.
If you walk your data model and see the need for queries to assemble needed information about a root
object, then you should stop and reevaluate your data model.
It is much faster to find an object with a link than to find an object with a query. By definition, a query
traverses a database to find and evaluate a group of starting point objects, while a link contains all the
information needed to go directly to a particular object, regardless of its current location (on disk or in
memory.)
In general, once you have a root object, you should be able to follow links to all information related to that
object. To do this, you need to build data relationships into your data model.
There is virtually no penalty for adding links to an object, even if they are rarely used. This is because a
link takes negligible storage space (about 8 bytes per link on disk) and thus does not appreciably slow the
retrieval of a root object
For example, suppose that several objects need to reference a particular object, but you want to be able
to change the object to which they point. One way to model this is to have each object contain a link to a
generic object that then points to the desired target object. Another way to structure this is for each
object to contain an identical link to the desired object. If you use a generic object approach, it will be
easier to change the references, but you will pay a cost each time you need to travel to the target object,
because separate fetches are involved.
In general, consider creating data structures that have a root object containing direct links to all other
objects needed. You can then read all your objects with two messages to the server: one to find and
return the root object and a second to group read all the other objects.
267
VERSANT Database Fundamentals Manual
As cheap as links are, it is not always possible to directly link root objects with target objects. Instead, you
may need to create a graph or tree structure.
For example, suppose that a person has garages that can contain cars that have colors. If the only
question you will ever ask is what colors belong to a person, then you could create a direct link from a
person to a color, or even embed color literals in the person object. However, if at some other time you
also need to know which garages have which cars, then you will want to create a tree structure.
In general, shallow, wide trees are better than deep trees, because they minimize the number of fetches
required to move from a root object to a leaf object. Wide trees are created with link arrays. At each step
downwards, you "cold traverse" a database to get an array of links, bring the array into memory, "hot
traverse" the array in memory to get the next array object, and then cold traverse again to move to the
next level. Wide trees are practical, because link arrays take relatively small amounts of memory (about 8
bytes per link on disk.)
It is better to know where you are going than to have to stop and ask for directions.
Suppose that you have an object with links to many other objects. You are only really interested in one of
these other objects, but in order to determine which one, you have to consult information in each of
them. In this case, consider placing the decision making information in the initial object. This can be as
simple as keeping a list of links sorted so you can just pick the first one, or it might involve using a
dictionary that allows you to go directly to the object that you want.
Use bi-directional
bi-directional links with care.
268
VERSANT Database Fundamentals Manual
Memory Management
• To improve access times, get the objects you want into memory as fast as possible, and
then keep them there until they are no longer needed.
Following are concepts that are necessary to implementing memory management strategies.
Memory caches
Object cache — To improve access to objects used in a transaction, an object cache is created on the
machine running the application. An object cache will expand as needed and use both real and virtual
memory. If you are using a shared session, the cache will be in shared memory; otherwise, it will be in
application memory.
Object cache table — To improve access to objects used in a session, a cached object descriptor table is
created on the machine running the application. It contains information about the current location
(memory or disk, application machine or database machine) of all objects referenced during a session.
Session tables — Various session information tables track your processes, connected databases, and long
and short transactions. These tables are created on the machine running the application.
Server page cache — To improve access to objects by all users of a database, a server page cache is
created in shared memory on the machine containing the database. The page cache will have a fixed size
and can use both real and virtual memory.
Server page cache tables — Associated with the server page cache are information tables that map
objects to their physical locations, contain index information, and maintain other information about the
state of the database.
269
VERSANT Database Fundamentals Manual
Object locations
You can improve application performance dramatically by understanding the places an object can be and
taking steps to control its location.
If not in one of the above places, an object can also be in a checkout database on the application machine
(if you have checked the object out.)
Pinning behavior
If you have pinned an object, then it is in real or virtual memory on the application machine. If you try to
pin more objects than can fit into real and virtual object cache memory, you will get an error.
If you unpin an object after it has been pinned, then it can be in any of several places. If ample space
remains in the object cache, it will probably remain there. If the object cache fills up and the object is
clean, then it will be dropped from memory. If the object cache fills up and the object is new or dirty, then
it will be flushed to the machine containing its source database.
C++/VERSANT automatically pins objects whenever they are dereferenced. C/VERSANT does not
automatically pin objects.
A "hot" traversal finds an object already in application cache memory (real or virtual.) Hot traversals are
very fast.
A "warm" traversal finds an object that is not in application cache memory, but that is in server page cache
memory.
A "cold" traversal finds an object in a database on disk. A cold traversal is slower than a warm or hot
traversal, because more needs to be done to find the object. For example, if a database containing an
object is remote, a cold traversal sends a request across the network to the database, and then the
database server process finds the object, brings the data page containing the object into page cache
memory, and sends the object back across the network to the application.
270
VERSANT Database Fundamentals Manual
fe_vm_maj_faults — Virtual memory major page faults (not supported by some operating systems)
se_cache_free — Bytes free in the application heap, which include the object cache, cached object
descriptor table, and internal data structures. The heap grows when the number of free bytes runs out.
se_cache_used — Bytes used in the application heap, which include the object cache, cached object
descriptor table, and internal data structures.
Once you have determined your model for object cache usage, you should examine these statistics and
make sure things are really working the way you intended.
During development, test databases are often small, which can obscure issues that will appear when you
deploy to a large database and cannot increase cache sizes proportionally. For example, operating
systems will have their own layer of caching which you cannot turn off.
According, during development, consider using a raw device for your database system volumes, which
will bypass operating system caching and simulate that fact that the chances of getting a cache hit in a
very large database is, by definition, low.
When measuring scalability it is important to avoid comparing a hot case with a cold case.
Use commit and rollback routines that maintain the object cache.
By default, the object cache is flushed whenever a transaction is committed or rolled back. Since this is
often undesirable, several options are provided for retaining objects between transactions. In C and C++,
these options are used with the o_xact() and xact() routines.
O_COMMIT_AND_RETAIN — Commit new and dirty objects and keep all objects in the object cache.
271
VERSANT Database Fundamentals Manual
O_CHECKPOINT_COMMIT — Commit new and dirty objects, keep all objects in the object cache, and
retain locks.
O_ROLLBACK_AND_RETAIN — Rollback all changes and keep all clean objects in the object cache.
O_RETAIN_SCHEMA_OBJ — Retain schema objects in the object cache after the transaction ends. (This
will improve performance the next time you get or select instance objects of the classes involved.)
For even finer grained control over what objects are affected by ending a transaction, consider using a
commit routine that operates on objects in a vstr or collection rather than on the entire object cache. In C
and C++, the routines are o_xactwithvstr() and xactwithvstr().
For example, suppose that you want to commit some set of objects and retain some other set. Following
is one way to do this.
1. Create a simple collection, such as a link vstr, of objects to commit. Call it, say,
"commitset". (If you want to commit all dirty objects, in C or C++, you can easily construct
a set of modified objects by using a VERSANT routine that iterates over cached object
descriptors marked dirty.)
2. Create a set of objects to retain after the commit. A set is needed if the number of objects is
large. Call it, say, "retainset".
4. Iterate through all of the objects in the cached object descriptor table and check each one.
You should retain all class objects and all those in your retainset. Make a set, say
"releaseset", from all other objects.
If you are dealing with more objects than can fit into object cache memory, what you should do depends
upon your hardware and network resources.
Your first step should be to subdivide your operations. Your basic strategy should be to keep all objects
needed for a particular operation in object cache memory on your application machine. You do this by
pinning needed objects and unpinning unneeded objects. Your rule of thumb should be to pin no more
272
VERSANT Database Fundamentals Manual
Your next step should be to decide what you want to do with the temporarily unneeded objects.
If your server connection is cheap and/or you are accessing objects in random order, using server
resources will probably outperform using virtual memory. To encourage unneeded objects to be
dropped or flushed, set the swap_threshold parameter in your application profile to be slightly smaller
than the size of your real memory (you may need to experiment to get it right.) Generally, setting
swap_threshold is enough, but in drastic cases, you can force unneeded objects to be dropped by
releasing them.
If your connection to the server is expensive and objects are accessed in sequential passes, then virtual
memory may work best. To encourage unneeded objects to remain on the application machine, choose a
high value for the swap_threshold parameter. In this case, VERSANT will expand and use virtual
memory until resources are no longer available.
In most cases, the balancing of the use of client versus server resources needs to be performed
empirically. (Of course, the best solution is to get more memory for your application machine.)
As a rule of thumb, you should use the object cache for both the current working set of objects and any
frequently used objects. If recently accessed objects are likely to be accessed again, it may make sense to
cache them as well.
If memory constraints prevent you from caching an object, it may still be useful to cache a link to it.
Caching a link costs almost nothing. A good way to do this is to replace a query on the server with a
dictionary lookup held in memory on the application machine. With a four byte key, this only costs you a
megabyte for each 43,690 objects. In this case, it is not a big deal to use virtual memory and allow the
dictionary to be swapped out.
Keeping objects out of the object memory cache can be at least as important as keeping objects in the
cache. If your object cache grows too big, it will trigger virtual memory page faults which can dramatically
decrease performance.
Keeping your cache clean is a very common issue. If you don't keep your cache clean, you will initially get
good performance and then start to see bad performance when real memory runs out and you begin to
thrash virtual memory.
To determine whether page faults are occurring, monitor the fe_vm_maj_faults statistic. You can also
monitor se_cache_used and se_objs to determine when real memory is exhausted. If you run out of
real and virtual memory in the application process heap, you will see error number 4161,
OM_HEAP_NOMEM, "Out of frontend heap memory".
273
VERSANT Database Fundamentals Manual
Unpin unneeded objects — Unpin objects that are not needed. This will allow VERSANT to drop or flush
unneeded objects, although it does not guarantee that the objects will be removed from memory.
Release unneeded objects — To force VERSANT to drop unneeded objects immediately, release them.
However, be sure that you do not release a dirty object or a class object.
Zap unneeded objects — If you reference a very large number (say, a million or more) of objects in a
session, the cached object descriptor (cod) table, which maintains information about all objects accessed
in the session, may grow to a significant size. To both drop an object and clear its cod table entry, you can
zap it with a "zap cods" interface routine. If you need to reclaim space in the application process heap and
have not been zapping objects as you go, you can commit or rollback a transaction with a "zap cods"
option that will both clear the entire table and drop all objects from cache memory.
If you zap objects, be sure that you do not zap a dirty object, an object that is the target of a link in a dirty
object, or a class object.
It is faster for a database server process to retrieve an object from a database than from virtual memory,
because a server process has access to information about object locations and can be smarter about
caching. This means that you should always set the size of the server page cache to be as large as
possible but still smaller than available real memory.
The size of the server page cache is set by the value of the max_page_buffs parameter in the server
process profile for a database, where each page specified in max_page_buffs equals 16K of memory.
If you are also simultaneously running other programs on the database machine, remember to allow for
their needs, because the page cache is a fixed size. Also, remember to allow for the server page cache
support tables, which have a major impact on performance. For example, even if you have a database
which is many orders of magnitude larger than your physical memory, you may still get a major
improvement in performance by increasing your server cache size if it means that the support tables can
remain in real memory.
The statistics which are most relevant to the server page cache are be_data_reads, be_data_located,
be_data_writes, and be_vm_maj_faults. If your server cache is working well, the values for
be_data_reads and be_data_writes will be low, while be_data_located is likely to be high. If
values for be_vm_maj_faults starts increasing, this means that there is not enough physical memory.
This could be because the max_page_buffs is set higher than physical memory or it could mean that
another application on that machine is using more memory than you anticipated.
If your application will access only a single database that is located on the same machine as the
274
VERSANT Database Fundamentals Manual
application, consider linking your application with the single process library.
Running your application in a single process can improve performance dramatically (by 20-50%.)
However, if you run in a single process, it is possible for a runaway pointer bug in your application code
to corrupt data structures and crash the database.
275
VERSANT Database Fundamentals Manual
Disk Management
Improving disk input and output can increase performance dramatically. Particularly important can be your
storage strategy, which can include clustering and use of partitions.
See also the chapter "Query Indexes," as indexes can significantly improve disk access by queries.
The faster the disk drive on which a database resides, the better.
Multiple disk drives can also increase performance. Ideally, the physical log, the logical log, and the
system volumes should all be on different physical devices.
It is better to use raw devices for a database than operating system files. They are 5% to 10% faster, and
they are more predictable, because they do not implement additional buffering outside of VERSANT.
In general, consider all available network and storage resources available to you and then place your
databases where they can best take advantage of your resources.
Storing related objects physically close to each other can greatly improve the speed of accessing them
together. If two related pieces of information are stored on the same disk page, accessing one will bring
both into the backend cache in a single operation.
While tuning clustering, it is generally a good idea to temporarily set the database max_page_buffs
profile parameter as low as possible. However, do not set it so low that the pages necessary for a single
operation don't fit in the cache and thrashing ensues, which would distort your statistics. Using a small
cache while tuning minimizes the possibility of cache hits and allows you to see the true number of reads
for each operation. You can then look at the be_data_reads statistic and try to get it as low as possible
via clustering.
Use the following formula to determine a rough goal for the avg delta be_data_reads statistic:
(number of objects)(average object size + 20)/16K +
(number of objects)/809 + 2
Keep in mind that you may not be able to achieve this goal, particularly if multiple access patterns force
you to trade off each specific case in order to optimize the average case. In any event, without a cache hit
you can never hope for better than two reads, one for the data page and one for the object location table
276
VERSANT Database Fundamentals Manual
page. Once you have tuned clustering to your satisfaction, don't forget to restore the max_page_buffs
parameter to its original state.
If you use an NFS partition to mount and access your databases, performance may suffer. VERSANT does
not need NFS to access databases.
Cluster Classes
Cluster classes if instances are frequently accessed together.
The default VERSANT strategy places first priority on clustering all instances within a class, but does not
group particular sets of classes. With the default strategy, each class is in a separate "cluster," and
instances are stored on a set of data pages within the cluster.
If instances of one or more class are usually accessed together, you can use an interface specific routine
to suggest to VERSANT that instances of the desired classes be stored in the same cluster. This will place
them in physical proximity on the storage media and thus reduce the time required to find and retrieve
instances of the classes if they are being used as a group.
The clustering routines do not force a clustering. Instead, they suggest to a database that a clustering
should be performed. If a class already has been assigned storage, no action will be taken for that class.
Thus, it is important to run a clustering routine before instances are created, or else VERSANT will ignore
the hint.
To cluster classes after they have been created, you can migrate all objects of the classes to be clustered
to an interim database, drop and then recreate the classes in the original database, specify clustering
hints, and then import instances from the interim database.
Once you have asked VERSANT to cluster related classes, it will improve performance if you create your
objects and then use a group write routine to store them in the same order in which you will access them.
This will improve the chances that related objects will be stored on the same or contiguous pages.
When concurrently loading objects, turn the single_latch server process profile parameter
temporarily "on". Although this reduces concurrency, it also ensures that the database server process will
277
VERSANT Database Fundamentals Manual
not release a latch on the database cache acquired by a group write routine until the routine has
completely finished.
If you have multiple access patterns, then you will probably have to experiment with different orderings
and clusterings.
If you have a root object that is always accessed first, it may be useful to put related objects on both sides
of it, not just after it. On the average, any given object is more likely to be in the middle of a page than at
the beginning.
This technique of creating objects in a useful order has an important side effect. Each time an object is
stored, VERSANT updates an object location table that associates the object identifier with the object
physical location. When you use a link to find an object, VERSANT first find the object identifier in the
location table, and then uses that information to find the object physically. Each page in the table holds
809 associations, and look ups will proceed more quickly if all needed object identifiers are found on the
same page.
A group write isolated operation works like a normal group write, but it suggests that the storage
manager isolate the given objects on their own page. This has two effects:
• First, the Storage Manager always starts with a new page to store the objects, rather than
trying to use the "current" page for the storage file or trying to refill existing pages.
• Second the Storage Manager marks all pages used as "NO REFILL"; this means that they will
not be used for normal refill.
However, the "group write clustered" operation is not considered refill and the page will be used if the
parent object is on the page. A page will continue to be freed if it has no objects stored on it.
A "group write clustered operation" works like a normal group write, but the Storage Manager places the
given objects near an existing parent object.
278
VERSANT Database Fundamentals Manual
First, the parent object is looked up to find its physical location. The object must exist in the database and
it must be in the same storage file as the child objects. If both these conditions are met, the Storage
Manager attempts to store the child objects on the same page as the parent object. If the page does not
have enough space, new pages will be allocated and the objects will be stored there.
Storage Architecture
Class space
Class space is the storage space allocated for instance objects of a class. A class space consists of logical
partitions.
Logical partitions
A logical partition is a storage segment that consists of one or more disk volumes. VERSANT treats a
partition as a continguous space, even when it spans disk volumes.
Disk volumes
A disk volume provides space for one or more partitions. Allocating a partition to a volume does not
reserve space on the volume; instead, partitions expand and take volume space as needed.
279
VERSANT Database Fundamentals Manual
The following shows the class space hierarchy in its simplest form.
The following shows the possibilities that you can define. Note that the following diagram is simplified. In fact,
any number of classes can use the same partition, any number of partitions can use the same volume, and a
partition can use any number of volumes.
Storage Strategies
When a database needs new pages, VERSANT picks a partition to use for the new pages. There are two
allocation algorithms that VERSANT can use:
First-available — The first-available algorithm uses the first partition that has enough space for the new
pages.
Round-robin — The round-robin algorithm uses the next partition (after the last partition used) that has
enough space for the new pages.
280
VERSANT Database Fundamentals Manual
By default VERSANT uses the following storage strategy for instances of a class, indexes defined on attributes
of a class, and internal system information.
By default, VERSANT creates the same number of logical partitions as the number of database volumes
that have been defined. In other words, VERSANT defines a logical partition for the system volume and
for each storage volume.
2. First-available allocation
By default, data pages are allocated first to the system volume and then to the storage volumes in the
order in which they were created.
You can override the default storage strategy with either the C/VERSANT function o_partition() or the
system utility dbtool. See the "Database Utilities" chapter of your VERSANT Database Administration Manual
for information on dbtool. See your C/VERSANT Reference Manual for information on o_partition().
VERSANT internally triggers a checkpoint when either its physical or logical log volume is filled. A
checkpoint is an expensive operation in which all data is flushed to the system volumes and the log files
are cleared. Depending on the amount of data in memory, checkpoints can take up to several seconds or
more. The unlucky operation that triggers a checkpoint will take much longer than otherwise identical
operations.
Small log file sizes lead to more frequent but less expensive checkpoints. Larger log files will reduce the
number of checkpoints, but they will be more expensive when they do happen. Larger log files give you
better throughput and average response time, but increase the worst case response time.
One option is to use huge log files which won't fill up and then, at low usage times, force a checkpoint by
stopping the database with the stopdb utility. However, since stopping a database clears the server page
cache as well as the log files, this approach is useful only if there are times when the database is not in
use, such as at the end of a day. When you restart the database, all traversals will be cold, because the
page cache will initially be empty.
If you want to force a checkpoint at each commit, set the server process parameter commit_flush to ON.
281
VERSANT Database Fundamentals Manual
Placing the logical and physical log volumes on raw devices can improve performance if you are
triggering a large number of checkpoints.
If the log file is on a raw device, it must be big enough to hold information for the biggest transaction that
will occur. If it's on a file system, it will temporarily grow if necessary (disk space permitting), so being big
enough to hold the largest transaction is not quite as crucial.
282
VERSANT Database Fundamentals Manual
Message Management
In most cases, VERSANT runs in at least two process: an application process and a server process for each
database connection, including the connection to the session database. (The only time that VERSANT runs in
one process is if you are accessing only a personal database and have linked your application with the single
process library.)
If you are accessing a database on a remote machine, communications between the application process and a
server process will occur over a network, which will lead to potential performance problems if network traffic
is heavy or otherwise slow. However, even when you are accessing a local database, you should try to reduce
the number of messages that need to be exchanged between application and server processes.
To assess the number of network messages that are occurring, collect and examine the be_net_rpcs
statistic.
Keep in mind that the number of network messages (or "RPCs") reported will tend to be one higher than
if you were not collecting statistics. This is because each line of statistics output involves a single RPC to
get the value of any server statistics being collected.
Use group operations (group read, group write, and group delete) whenever possible.
In addition to the normal benefit of reducing network traffic, group operations contain optimizations
which make them inherently more efficient than multiple iterations of their single object counterparts.
If you use a group write or group delete, you can specify options that will release objects from memory
and/or clear the cached object descriptor table. Remember, however, that these objects will still be a part
of the current transaction and subject to roll back, so you should not downgrade their locks. If you
perform a group write, downgrade locks, and then roll back, you will get unpredictable results. (If you
want to save changes and release locks, instead use a "commit with vstr" or "commit with collection"
routine.)
Rather than repeat an operation, save its value from the first time and reuse it.
Although storing objects in the object cache is the most obvious use of this technique, there are other
additional possibilities. As appropriate, consider caching non-object information (such as numbers, links,
and query results) in local variables or transient data structures. If it enables you to meaningfully reduce
283
VERSANT Database Fundamentals Manual
your number of network messages, you should also consider caching information in persistent objects in
a session database on your local machine.
Checkout objects.
If network traffic is a problem and concurrent access to objects is not, you can checkout objects to
provide local access, which reduces network traffic.
284
VERSANT Database Fundamentals Manual
Multiple Applications
If the database will be serving multiple clients, new issues and opportunities come into play.
To gauge contention for shared resources, collect and examine the following statistics.
be_real_time — Seconds elapsed since the be_real_time statistic was turned on.
Even when it is not strictly necessary, concurrent access to a single database may be desirable since it can
improve performance on the database machine by reducing context switching and increasing utilization
of disk, processor, and shared data structures.
When dealing with multiple client applications, it is important to distinguish between response time and
throughput. In general, adding an additional client will increase throughput on the machine containing
the database and decrease response time as experienced by the applications.
If you are adding clients to a database, reduce latch contention by turning the single_latch server
process profile parameter to "off."
If disk, processor, or data structure utilization approaches 100%, consider adding additional databases.
If your objective is to maximize response time and if the nature of the data allows it, consider distributing
objects over multiple databases.
For example, a single database of telephone numbers could be partitioned into ten databases, based on
the last digit of the phone number. This is inherently more efficient than running multiple clients against a
single database since each database can have its own set of resources and be completely independent of
285
VERSANT Database Fundamentals Manual
In addition to partitioning data between multiple databases, you can also replicate objects in several
databases. This has the same advantage of eliminating bottlenecks and works particularly well in a widely
distributed environment. Each client can then use the database to which it is closest. Asynchronous
replication is best implemented with event notification and "move object" features. The only downside is
that there will be a small lag time between when an update is made to one database and when the
change is propagated to the other databases.
286
VERSANT Database Fundamentals Manual
Application Programming
In a multi-user environment, it is important to develop and implement a transaction, locking, and server
usage model as early as possible in your development process.
For example, if multiple applications will be updating objects, it is important to keep your transactions as
short as possible to minimize lock contention. In your data model, try to make sure that all objects needed
for a particular operation can be accessed as a group and then released quickly. If you are going to use
optimistic locking, you must be sure that all applications involved use a consistent application protocol
and access objects in ways and times that maximize concurrency.
In general, your transaction model, locking model, and client/server models should be designed in, as
retrofitting rarely works well.
Depending upon your platform and release, you may be able to use multiple processes or threads in a
transaction. In brief, it makes sense to use processes or threads if one operation, such as printing, is input
or output bound while others, such as a group read, are not.
Turn off locking if only one application will access the database.
If it is possible that one application will attempt to modify an object that is being used by another
application, some form of locking is necessary.
If only one application will access a database, you can safely turn locking off.
If only a few applications will access a database and if the chances of them conflicting is low (say, for
example, that most applications are just reading data,) then consider using optimistic locking.
If you need to use strict locking, don't lock any more objects than necessary.
If a group of objects is always locked together, consider using an application protocol that avoid placing a
separate lock on every object in the group. For example, you could use the convention that none of the
objects in a container can be modified unless a write lock is obtained on the container object. When using
a "single lock plus a protocol" technique, be careful not to implicitly lock a group of objects which should
not always be locked together. The reduced concurrency typically will more than negate the advantage
of reducing the number of locks.
287
VERSANT Database Fundamentals Manual
If concurrent access to objects is an issue, gather and examine the following statistics.
be_lock_wait_time — The number of seconds that applications spent waiting for locks.
be_lock_timeouts — The number of timeouts that occurred due to waiting for locks.
If your application will have multiple clients running on a single machine, consider using shared sessions.
This allows multiple client processes to share a single session with a single frontend cache. A single large
cache can make much more efficient use of memory by eliminating the redundant storage of objects
associated with multiple smaller caches. This can result in a better hit ratio and increased performance.
The major disadvantage of shared sessions is that inter-client concurrency will be reduced due to latch
contention for shared object cache resources. This contention can be alleviated greatly by also using the
"many readers, single writer" option. As its name implies, this option imposes the restriction that only the
root process is allowed to write to the database. It allows you to access the object cache even while
concurrent database reads and writes are taking place. Since database reads is the most common
operation, we recommend using this option.
When looking at statistics for a shared session, keep in mind the distinction between per process statistics
with an fe_ prefix and per session statistics with an se_ prefix. Use the se_latch_wait_time statistic
to gauge how much time is lost due to latch contention.
Logging should be "on" if you want your database to be recoverable, if you want to do rollbacks, or if you
want to use log rollforward. In other words, it should be "on" for the vast majority of production
applications. If you don't care about any of these things, turn logging "off" for a quick, easy, and
substantial performance increase.
Turning logging off is safe if used in conjunction with checkouts, because original copies of objects
remain in their source databases. In this case, a database can be corrupted, but you may not care.
Optimize queries.
See also the "Queries" and "Indexes" chapters for numerous suggestions related to optimizing queries. In
brief, using links to navigate to objects and phrasing of queries to use indexes properly can significantly
288
VERSANT Database Fundamentals Manual
Under some circumstances, you might consider the order in which you connect to databases.
When you dereference a link, databases are searched for the target object in the following order:
2. Other databases in the order in which you connected to them, starting with your session
database. (If you connect and disconnect a number of times, the order will change.)
This approach will improve performance only if all of the following are true:
By default, for each application, VERSANT reserves 1024 logical object identifiers (LOIDs) to reduce
amount of requests going to the server every time you create new persistent objects.
If you know ahead of time that you are going to create a large number of objects, use a "set loid capacity"
routine to reserve a specific number of logical object identifiers. This can improve performance by
reducing the number of server requests.
For example, suppose that you want to create 34 million new objects. If you reserve your logical object
identifiers ahead of time, then you will make just one call to the server to reserve object identifiers, rather
than 33,236 calls.
Although not strictly a performance tip, you should recognize that the characteristics of objects will
change over time; however, interfaces should persist. So that all applications in an system operate in
consistent ways, avoid defining attributes (C++ data members) as public. Always use access methods
(get methods) and modifier methods (set methods) to manage data.
289
VERSANT Database Fundamentals Manual
290
VERSANT Database Fundamentals Manual
This chapter explains how to collect performance monitoring statistics. For information on how to use these
statistics, see the chapter "Performance Tuning."
Statistics Types
To help you measure and analyze performance, you can collect the following kinds of statistics about
applications and databases.
You can monitor application activity, such as the number of objects read into the object cache.
You can monitor session information, such as the number of dirty objects in the object cache. Unless you
are in a shared session, session information and application process information are the same.
You can monitor connections made to a database, such as the number of seconds that applications spend
waiting for locks. You can also monitor the time spent in VERSANT functions by using predefined
function names in statistics tools and routines. You can monitor the time spent in your own functions by
setting enter and exit points that trigger statistics collection.
You can monitor database information, such as the number of active transactions.
Derived statistics
You can derive your own statistics using combinations of other statistics along with numerical operators
and statistical functions.
Statistics Viewing
You can collect statistics in a file and view them with the command line utility vstats. When you collect
291
VERSANT Database Fundamentals Manual
statistics to a file, you can either view them in real time or later, at your leisure.
You can also collect statistics in memory. When you collect statistics in memory, you can view them in real
time with the vstats utility or bring them into your application with a "get statistics" routine.
292
VERSANT Database Fundamentals Manual
Following is a brief overview of statistics collection, which you can use if you want a "quick start" way of
beginning.
There are three ways to access VERSANT performance monitoring functionality: with a direct connection,
with automatic profiling, and with interface routines.
The first step in using a direct connection is identifying the statistics of interest. Since there are so many
statistics available, it is often useful to view a listing of all statistics. To receive a long, complete list of
statistics names, each with a one line explanation, invoke the following from a command line:
vstats -summary
Now suppose we are interested in the performance of a database page cache. From the list of all
statistics, we might choose the db_data_reads and db_data_located statistics as the ones which we
would like to examine.
The first step in viewing a statistic is to make sure that collection of that statistic is turned on. The most
direct and convenient way to do this is by invoking vstats as follows:
vstats -database db1 -on db_data_reads \
db_data_located
Once a statistic is turned on, we can then view its value with the -stats option to vstats:
vstats -database db1 -stats “db_data_reads db1” \
“db_data_located db1”
293
VERSANT Database Fundamentals Manual
By default, a new line of values is printed every 5 seconds until you type Ctrl-C to break.
In the previous example, we viewed the per-database statistics db_data_reads and db_data_located.
Per connection versions of these statistics, called be_data_reads and be_data_located, are also
available. To view per connection statistics, it is necessary to specify a connection identifier in addition to
a database name. Connection identifiers are specified with the -id option to vstats.
To get information about all the current connections to a database, use the -connections option to
vstats:
vstats -database db1 -connections
This will cause a list of all connections to the specified database to be printed. For example:
VERSANT Utility VSTATS Version 6.0.5
Copyright (c) 1989-2002 VERSANT Object Technology
Connection ID to database 'db1':
Connection ID = 20334
User Name = 'george'
Session Name = 'example session'
Long Transaction = '228.0.9220'
Server Process = 'gamehendge':20334
Client Process = 'gamehendge':20332
Protocol = TCP/IP
Server Port = 192.70.173.25:5019
Client Port = 192.70.173.25:1041
Connection ID = 20341
User Name = 'george'
Session Name = 'vstats -connections'
Long Transaction = '228.0.2052'
Server Process = 'gamehendge':20341
Client Process = 'gamehendge':20339
Protocol = TCP/IP
Server Port = 192.70.173.25:5019
Client Port = 192.70.173.25:1043
294
VERSANT Database Fundamentals Manual
From the resulting output, we can see that there are two connections. Connection 20334 is for an
example program and connection 20341 is for the vstats command which we used to list the
connections.
To view the be_data_reads and be_data_located statistics for the example program, we must do
two things. First, we have to use the -on command for vstats to turn on these statistics for the example
program's connection:
vstats -database db1 -id 20334 \
-on be_data_reads be_data_located
Once the statistics are on, we can use the -stats command to view them:
vstats -database db1 -id 20334 \
-stats “be_data_reads db1” “be_data_located db1”
As before, the -stats command will periodically print rows of statistic values. For example:
VERSANT Utility VSTATS Version 6.0.5
Copyright (c) 1989-2002 VERSANT Object Technology
0 = be_data_reads db1
1 = be_data_located db1
0 1
===== =====
23 123
28 155
32 188
It is not necessary to limit yourself to the atomic statistics listed by the vstats -summary command. The
-stats command understands standard arithmetic operations and a few built in functions. For example,
to print the page cache hit ratio for the example program:
vstats -database db1 -id 20334 \
-stats “delta be_data_located db1 \
/ (delta be_data_located db1 \
+ delta be_data_reads db1)”
295
VERSANT Database Fundamentals Manual
Note the use of the standard arithmetic +, /, and () operators. Note also the use of the built in delta
function which returns the difference between an expression's current value and it's value on the
previous line. Since the delta function requires two lines of output before it can return a value, the first
line of output for this expression is blank. Finally, note the NaN ("not a number") value on the third line.
This will happen when a divide by zero error occurs due to there being no change in either of the two
atomic statistics. This is completely logical, since a read hit ratio is undefined over a time period where no
reads occur.
The easiest way to generate a statistics profile file is by setting the VERSANT_STAT_FILE environment
variable before running your application. For example:
setenv VERSANT_STAT_FILE /tmp/app.vs
This will cause the application to automatically create the specified file and write statistics to it whenever
certain database operations take place. Although it is beyond the scope of this overview, you should be
aware that there are other environment variables which can be set to limit the statistics which are written
to this file, thus increasing performance. By default, all application and server statistics are written to the
file for all operations.
296
VERSANT Database Fundamentals Manual
Before running your application, be sure to turn on any per database server statistics which you are
interested in viewing. Application statistics and per connection server statistics will be turned on
automatically. As before, use the vstats -on command. For example:
vstats -database db1 -on
In the above example, all statistics will be turned on for all current connections, since no statistics or
connection identifiers were specified.
Once the environment variables are set and the statistics are turned on, simply run your application, and
the profile file will be automatically generated.
You can examine the contents of the profile file with the vstats utility. The syntax for examining
statistics in a profile file is almost identical to the syntax used in the direct connection model. The only
difference is that a file name is specified instead of a database name. So, for example, to show the change
in fe_real_time, the db_data_located for db1 and the be_data_located for db2, you could use
the following command:
vstats -filename /tmp/app.vs -stats \
“delta fe_real_time” “db_data_located db1” \
“be_data_located db2”
Note that the database server statistics require database names, while the application statistic does not.
The above command will produce output similar to the following.
VERSANT Utility VSTATS Version 6.0.5
Copyright (c) 1989-2002 VERSANT Object Technology
0 = delta fe_real_time
1 = db_data_located db1
2 = be_data_located db2
0 1 2
===== ===== =====
- 59 3
0.055 59 3 o_xact(db1,db2)
0.216 60 4
0.269 60 4 o_xact(db1,db2)
In the above output, there is an extra column which is not present when using the direct connection
model. It provides information about what database operation that line of values is associated with. For
each operation, there are two lines of output, one which shows values before the operation and one
which shows values after the operation. To reduce screen clutter, only the second of these two lines is
labeled with the operation. Note also how a list of database names is provided along with each operation
name. This shows for which databases server side statistics were collected and written to the profile file.
297
VERSANT Database Fundamentals Manual
It is possible that the statistics collection points built into the beginning and end of various low level
database operations will prove to be inadequate. It is easy to specify additional points in your application
where statistics should be written to the profile file. Simply insert the "write statistics automatically"
routine function into your application code wherever you would like statistics to be sampled and written
to file. If you would like to write statistics samples at the beginning and end of one of your application's
operations, use the "write statistics on entering function" and "write statistics on exiting function" routines.
To turn statistics on, use the "turn statistics collection on" routine.
To turn statistics off, use the "turn statistics collection off" routine.
To get the current values of statistics which are on, use the "get statistics" routine.
To get a list of connection identifiers which can be passed to these functions, use the "get activity information"
routine.
298
VERSANT Database Fundamentals Manual
Following are the ways you can collect performance and monitoring statistics about a VERSANT application
and send them to a file.
To collect any statistic for any database to which your application has made a connection, you can call
"begin collection" routines from within the application. Statistics will be collected only for the activities of
your own application and only for the duration of your session.
To collect any statistic for any database to which your application makes a connection, you can set the
environment variable VERSANT_STAT_FILE to the name of a file and then run your application within its
scope. Statistics will be collected only for the activities of your own application and only for the duration
of your session.
You may want to filter the statistics collected to reduce file size and improve application performance. To
filter the statistics collected and set other collection parameters, you can use the VERSANT_STAT_STATS,
VERSANT_STAT_FUNCS, VERSANT_STAT_TIME, VERSANT_STAT_DBS, and VERSANT_STAT_FLUSH
environment variables. See the chapter "VERSANT Configuration Parameters" in the VERSANT Database
Administration Manual for more information.
Collecting statistics with environment variables is the same as invoking a "begin automatic collection"
routine from within an application each time you make a database connection. Using a routine to begin
automatic collection allows greater control, but it requires you to modify your application code.
To collect any statistic for any database and any application, you can invoke the vstats utility from the
command line. Statistics will be collected only for connections in existence at the time vstats is invoked.
By default, the vstats utility sends statistics to stdout. To send statistics to a file, you can pipe the
output of stdout. See the VERSANT Database Administration Manual for a complete reference to
vstats.
c o_autostatsbegin()
env VERSANT_STAT_FILE
util vstats
299
VERSANT Database Fundamentals Manual
To stop collection started with a "begin automatic collection" routine, you can either end your session or
use an explicit "end automatic collection routine."
c o_autostatsend()
o_endsession()
c++ endsession()
Collect statistics on function entry
If you use a "collect statistics in a file" routine or the VERSANT_STAT_FILE environment variable to collect
statistics, you can use a "collect statistics on function entry" routine to collect and write statistics when
entering a non-VERSANT function. If you use one of these routines, you must also use a "collect statistics
on exiting" routine.
c o_autostatsenter()
Collect statistics on function exit
If you use a "collect statistics in a file" routine or the VERSANT_STAT_FILE environment variable to collect
statistics, you can use a "collect statistics on function exit" routine to both collect and write statistics when
exiting a non-VERSANT function. If you use a "collect on entering" routine, you must also use a "collect on
exiting" routine.
c o_autostatsexit()
Collect statistics and write to file
A "collect and write" routine will collect the current set of statistics and write them to file along with a
specified comment.
Use "collect on entering" and "collect on exiting" routines to add collection points at the beginning and
end of your functions. Use "collect and write" any other time that you want to collect statistics.
c o_autostatswrite()
View statistics in file
You can view statistics written to a file by using the vstats utility. The text that you will see will be
formatted and filtered per command line parameters (the text is not human readable when viewed with a
text editor.) You can view the statistics file in real time by invoking vstats with the -stdin parameter,
or you can view statistics at a later time by invoking vstats with the -file parameter.
util vstats
300
VERSANT Database Fundamentals Manual
Following are the ways that you can collect statistics and send them to memory.
To collect any database and/or connection statistic associated with a particular database, you can set the
stat server process configuration parameter for that database. After setting stat, specified database
statistics will be collected whenever the database starts, and specified connection statistics will be
collected whenever any application connects to the database.
To collect any statistic for a particular database connection already in existence, you can invoke a "collect
statistics" routine from within an application. Until the database stops and restarts, calling these methods
will override any setting made in the stat server process configuration parameter that applies to the
specified connection. Their effect on the specified connection will persist even after the end of the
session in which they are called. Invoking these methods has no effect on statistics collection separately
being performed by vstats, VERSANT_STAT_FILE, or beginAutoCollectionOfStatistics:.
c o_collectstats()
util vstats
cfg stat
You can override stat by calling a general or specific "turn off" routine.
c o_collectstats()
View statistics in memory
To view in real time any statistic being collected for any database and any application, you can specify a
"direct connection" to one or more databases when you invoke the vstats utility. When you invoke
vstats, you can ask for statistics by connection or database. Statistics are printed to stdout.
util vstats
To view any statistic for a particular database and particular database connection, you can invoke a "get
statististics" routine from within an application. Statistics are sent to an array from which you can fetch
them. Values are sent for all collected statistics at the moment when the routine is invoked.
c o_getstats()
301
VERSANT Database Fundamentals Manual
The vstats utility and the "turn on collection," "turn off collection,", and "get statistics" routines allow you
to set or get statistics for a specified database connection. Following are ways that you can get
information about current connections, locks, and transactions so that you can specify the required
parameters.
c o_getactivityinfo()
o_gettransid()
c gettransid()
util vstats
dbtool
The dbtool utility can also get information about objects and storage volumes assoicated with a particular
database.
302
VERSANT Database Fundamentals Manual
You can derive useful statistics using combinations of other statistics along with numerical operators and
statistical functions. To predefine derived statistics, you can create definitions in the statistics configuration
file. When it starts, vstats will read and use the configuration file to collect and compute the derived
statistics.
The name of the configuration file depends upon your operating system.
unix .vstatsrc
win vstats.ini
303
VERSANT Database Fundamentals Manual
Statistics Suggestions
Following are some suggestions for which statistics to collect. See the following section, "Statistics Names," for
the formula used by derived statistics.
304
VERSANT Database Fundamentals Manual
305
VERSANT Database Fundamentals Manual
To tune logging:
306
VERSANT Database Fundamentals Manual
Overhead — When statistics collection is turned on, the profiling overhead will be two remote procedure
calls and, possibly, a potential file write each time a collection point is encountered. Since this could be a
significant overhead, you can limit collection to specific databases and/or connections and change parameters
for collection and write intervals.
1. To collect statistics from within an application, first turn on statistics collection with the
turnOnCollectionOfStatistics: method in VStatistics.
You can turn off collection of some or all statistics by using the turnOffCollectionOfStatistics:
method.
2. To retrieve statistics from within an application, use the getStatisticsOf: method in VStatistics.
4. To add collection points for your own methods, use the autoStatsEnteringFunction: and
autoStatsExitingFunction: methods in VStatistics.
5. To find information about locks, transactions, and connections, use the getActivityInfoOn: method
in VStatistics. Support classes for the getActivityInfoOn: method are the classes VLockInfo,
VXactInfo, and VConnectInfo.
307
VERSANT Database Fundamentals Manual
308
VERSANT Database Fundamentals Manual
Statistics Names
Each statistic has a name and stores a numeric value. The symbolic names are used as parameters in functions
and methods.
Following are statistics that you can collect and the names of functions for which you can collect statistics.
For example:
shown — o_releaseobjs
use — CAPI_RELEASEOBJS
When statistic names are specified to the vstats Statistics Tool or used in the environment variable
VERSANT_STAT_FUNCS:
• Enter the name with the leading "o_" prefix (as shown.)
309
VERSANT Database Fundamentals Manual
For example:
shown — o_releaseobjs
use — o_releaseobjs
310
VERSANT Database Fundamentals Manual
For example:
shown — fe_net_bytes_read
use — STAT_FE_NET_BYTES_READ
When statistic names are specified to the vstats Statistics Tool or used in the environment variable
VERSANT_STAT_FUNCS:
For example:
shown — fe_net_bytes_read
use — fe_net_bytes_read
311
VERSANT Database Fundamentals Manual
312
VERSANT Database Fundamentals Manual
For example:
shown — se_cache_free
use — STAT_SE_CACHE_FREE
When statistic names are specified to the vstats Statistics Tool or used in the environment variable
VERSANT_STAT_FUNCS:
For example:
shown — se_cache_free
use — se_cache_free
313
VERSANT Database Fundamentals Manual
314
VERSANT Database Fundamentals Manual
315
VERSANT Database Fundamentals Manual
For example:
shown — be_data_located
use — STAT_BE_DATA_LOCATED
When statistic names are specified to the vstats Statistics Tool or used in the environment variable
VERSANT_STAT_FUNCS:
316
VERSANT Database Fundamentals Manual
For example:
shown — be_data_located
use — be_data_located
317
VERSANT Database Fundamentals Manual
318
VERSANT Database Fundamentals Manual
For example:
shown — db_bf_llog_flushes
use — STAT_DB_BF_LLOG_FLUSHES
When statistic names are specified to the vstats Statistics Tool or used in the environment variable
VERSANT_STAT_FUNCS:
For example:
shown — db_bf_llog_flushes
use — db_bf_llog_flushes
319
VERSANT Database Fundamentals Manual
For each of these, there are both per database (db_*) and per connection (be_*) versions of each statistic.
320
VERSANT Database Fundamentals Manual
In addition, each statistic is available for all latches or for an individual type of latch (e.g. *_sda, *_heap,
*_voldev, etc.).
The exception to this is the latch_released statistic which is not available for the individual latch types.
Also, there are two derived statistics: be_latch_holds and db_latch_holds. These are defined to be the
number of latches granted minus the number released (latch_granted - latch_released) and give an
indication of how many latches are currently being held. The values of these statistics are not absolute, but are
relative to the number of latches held when the statistic is turned on. The value will be 0 if the same number
of latches, positive if more, and negative if less.
For example:
shown — be_latch_granted
use — STAT_BE_LATCH_GRANTED
When statistic names are specified to the vstats Statistics Tool or used in the environment variable
VERSANT_STAT_FUNCS:
For example:
shown — be_latch_granted
use — be_latch_granted
321
VERSANT Database Fundamentals Manual
322
VERSANT Database Fundamentals Manual
323
VERSANT Database Fundamentals Manual
324
VERSANT Database Fundamentals Manual
325
VERSANT Database Fundamentals Manual
326
VERSANT Database Fundamentals Manual
327
VERSANT Database Fundamentals Manual
Concepts
Garbage objects
Garbage objects are objects which are not system objects and are not reachable.
Database root objects are objects named by loid on the command line and all instances of database root
classes. All database roots objects are reachable.
Database root classes are those classes named on the command line.
If a versioned object is reachable, its generic instance and all of its versions are reachable.
System objects are defined to be instances of any class whose name begins with o_ and also instances of
these classes:
class, attribute, method, lo_class, pl_class, char, loid_db, db_dir, dba_uid,
tombstone_sysclass
328
VERSANT Database Fundamentals Manual
The best way to use the garbage collector is to define one or more classes to be database root classes. Make
sure that all of your objects that you want to keep are installed in some instance of one of the database roots
classes.
If several different sets of objects are stored in the same database, and each set of objects has different
database roots classes and objects, you must use the union of all these database roots classes and objects on
the command line. Objects created by different language interfaces may be used and garbage collected
together in the same database as long as this union policy is used.
All C++ objects derive from PObject, and thus will be considered roots, and therefore not deleted.
Limitations
• Only a single database is garbage collected. If objects in the database are reachable but only via
objects in another database, they will be thought to be garbage.
• The garbage collector runs in the default long transaction and does not know about any objects
not visible to and available to the default long transaction.
• The garbage collector must be the only program accessing the database.
Caution
The requirement that the garbage collection be the only program accessing the database is not enforced.
However, it is thought to be safe if other programs that only read non-garbage objects are connected to
the database.
• If a database is very large and the object relationships in the database are very complex, then
garbage collection may fail with an "out of heap" error message. This means that you have run
out of system swap space. Before using garbage collection, you should maximize available swap
space.
329
VERSANT Database Fundamentals Manual
The garbage collector is inherently dangerous, as are commands such as dropinst, dropclass, and
removedb.
In particular, if you name no roots on the command line, the garbage collector will remove all objects in the
database except system objects.
330
VERSANT Database Fundamentals Manual
331
VERSANT Database Fundamentals Manual
Overview
Event notification mechanisms allow you to monitor objects and classes and receive a message when a
specified object is modified or deleted or when an instance of a specified class is created, modified, or
deleted. You can also define your own events and receive a message if the event occurs on a specified object
or an instance of a specified class.
Event
System event
A "system event" is an event pre-defined by VERSANT. For example, system events occur when a class or
instance object is created, modified, or deleted.
User event
Event message
Event registration
To receive an event message when a particular event occurs, you must "register" interest in the event
with the database involved.
Event notification
When a registered event occurs, VERSANT sends an event message to a database queue on the
database where the event occurred. These messages stay in the queue until you explicitly ask for them.
Transient Mode
In transient mode, VERSANT keeps a list of event registrations and a list of generated events in memory.
When the database stops, the lists are lost. This is the default mode.
332
VERSANT Database Fundamentals Manual
Persistent Mode
In persistent mode, VERSANT stores the list of event registrations and the list of generated events in the
database. When the database stops, the lists remain.
To specify persistence, use the following parameters in your application server profile file profile.be.
event_msg_mode Specify persistent event messages.
event_registration_mode Specify persistent event registrations.
You should set these parameters to Transient Mode for compatibility with existing/legacy applications.
New applications can use either mode, in any combination. For instance, you can set your new
application to use persistent event registration with transient event messages. You should avoid using
old_transient with new applications, though, as it will be phased out in later releases.
For more information on setting mode, see the chapter "Database Profiles" in your VERSANT Database
Administration Manual.
Event registrations are dynamically allocated and kept in the server database. Theoretically, there is no
limit to the number of events that you can register, but practically, the number is limited by available
shared memory.
Also, as the number of registrations is increased against a specific object on an event, the time to search
for those interested registrations will also increase. And, when an event is raised, it will trigger an event
for each interested registration, which could potentially generate a large number of events. If an
extremely large number of events are raised, potentially some of them may not be able to be delivered
due to limitations on the message queue.
Because event notification is tightly bound to interface languages, following are separate event
notification sections, for C and C++ .
You can use the dbtool utility to display current event notification status information. You can see the
following information:
• Event message queue information, such as the number of events left in the event queue.
• A list of all event registration entries and their status, "active" or "not active".
333
VERSANT Database Fundamentals Manual
For more information, see the dbtool reference in the "Database Utilities" chapter of your VERSANT
Database Administration Manual.
334
VERSANT Database Fundamentals Manual
There is one event message queue and one event delivery daemon per database. Both the event message
queue and the event delivery daemon must be running for you to use event notification functions.
For each database on which you want to use event notification, open the database profile file
profile.be and create an event delivery daemon entry.
The event delivery daemon entry can have either of the following two formats:
event_daemon code_path
or
event_daemon MANUAL
In the first form, code_path is the full path name of object code that will start an event delivery daemon.
You must separately write this code, which is operating system specific. This code must be on the same
machine as the database.
In the second form, you must manually start an event delivery daemon after the database starts. If there is
no event_daemon entry, "event_daemon MANUAL" is the default.
335
VERSANT Database Fundamentals Manual
You must start the event daemon before doing anything else. Otherwise, event defining requests from an
application will remain pending until the daemon is started.
When a database starts, it will configure itself per the parameters in the file profile.be.
If there is an event_daemon entry in profile.be, the server process will create an event message
queue, if one does not already exist.
Automatic startup
If the database process sees the entry event_daemon code_path, the server process will invoke the
code located at code_path, which will start an event delivery daemon.
Manual startup
If the database process see the entry event_daemon MANUAL, no event delivery daemon will be started
and you must manually start an event delivery daemon before proceeding.
Null startup
If the database process sees no event_daemon entry in profile.be, manual startup behavior occurs.
Follow normal procedures to start a session and then connect to the database on which you want to
enable event notification.
Next, enable event notification by running the o_initen() function or initen() method with the
O_EV_DAEMON option. This will identify you to the database server process as the owner of the event
delivery daemon and enable event notification.
After event notification has been enabled, the database will create a database event message queue, if it
does not already exist. Then the event daemon will issue a request to open the database message queue
to get a handle, and start to read requests. The database will immediately begin monitoring itself for
registered events and, as appropriate, produce messages and put them on the event message queue. At
this point, all event notification functions are available both for you and for all other users. This means
that you and others can now register events, read events, raise events, and so on.
Note that it is the responsibility of an application to activate event notification. You must take care that
your application waits for notification from the event daemon that it has received a handle to the database
message queue before proceeding to register, read, and raise events. Otherwise, the application may
attempt to perform actions before event notification has been activated. Alternately, you can build in
336
VERSANT Database Fundamentals Manual
logic that causes the application to retry its action until event notification has been activated.
4. Register event.
Register the events that you wish to monitor with o_defineevent() or defineevent().
5. Get events.
Read events from the event message queue with o_readevent() or readevent().
When you are finished with event notification, your application should disable its event notifications with
o_disableevent() or disableevent() and optionally disable event notification for the database with
o_initen() or initen() and kill the event notification daemon.
c o_initen()
c++ initen()
337
VERSANT Database Fundamentals Manual
This function allows you to send an event message to the event delivery daemon:
• Without registering,
• Without specifying a target object, and
• Without waiting for a commit for your message to be processed.
c o_sendeventtodaemon()
c++ sendeventtodaemon()
338
VERSANT Database Fundamentals Manual
Register event
Register for an application the objects and events to be monitored by a database. Afterwards, you
will receive an event message whenever a specified event occurs to a specified object.
c o_defineevent()
c++ defineevent()
If you have linked your application with the single process library libosc.a, then o_initen() or initen() must
be invoked within a database session by the dba user who created the database.
339
VERSANT Database Fundamentals Manual
When invoked, o_initen() and initen() act immediately if the application is using the one process model.
Otherwise, they will act at the next transaction commit.
If you want to suspend the operation of the event message daemon, you can call o_initen() or initen()
with the O_EV_DISABLE option. After the event delivery daemon has been disabled, all event
notification functions (except o_initen() and initen() using the O_EV_ENABLE option and o_dropevent()
and dropevent()) will receive an error.
If you have used VERSANT mechanisms to start an event delivery daemon and it terminates while a
database is still running, the database cleanup process will attempt to bring up another copy of the
daemon. However, when the database starts a new daemon, it will not restart event monitoring until you
again invoke o_initen() or initen() with the O_EV_DAEMON option.
If a database is shut down, the event delivery daemon will be terminated, but the event message queue
will remain. When the database is next started and a new daemon is created, the daemon will
immediately pick up any messages left in the message queue when the database stopped, but to
reactivate event notification you must perform the normal startup procedures as noted above.
If a machine crashes because of an event such as a power failure, the event message queue and all
messages in it will be lost. When the database restarts, a new event message queue will automatically be
created, after you perform the normal startup procedure.
Before you can make a registration, the event notification daemon must be running and some application,
not necessarily your application, must have called o_initen() or initen().
The objects to be monitored can be either class objects or instance objects. You can more precisely
specify objects to be monitored by specifying a predicate, which will narrow the group of instances to
which a raised event can apply. A registration can optionally specify when to evaluate this predicate, at
the time when event is raised or when the originating transaction of the event commits.
The events of interest defines a subset of all events that can happen to the target objects. Only a raised
event falling into this subset will send an event message.
You make registrations at any time during a session. The server process for the specified database will
begin event monitoring as soon as an application makes a registration, rather than waiting until the next
commit, so that you can see changes before making a commit.
Only after all applications monitoring an object drop their interests or terminate does a database stop
340
VERSANT Database Fundamentals Manual
monitoring the object. Shutting the database down cancels all event registrations.
At any time during a session, you can suspend event monitoring with o_disableevent() or disableevent(),
reactivate monitoring with o_enableevent() or enableevent(), or terminate monitoring with
o_dropevent() or dropevent(). If your application or the database terminates abnormally, monitoring for
the application or database will be terminated.
Information about event registrations is kept in shared memory managed by the database, which means
that it is available to all applications connected to that database.
Database event message queues have a maximum size, which is defined by the operating system on
which the database resides. If the queue is full when an event message is generated, it will be lost.
Event registering, releasing, deactivating, or re-activating has no impact on locks. Use of event
notification does not require that locking be turned on, which means that it can be used in an optimistic
locking environment.
The objects you want to monitor need not be in your application object memory cache, as only logical
object identifiers (their loids) are sent to a database.
The order in which the pairs of event number are specified in the events parameter determines the
order in which events are evaluated. This means that performance will be better if events with a higher
frequency of occurrence are kept in front of the list.
For example, a vstr of {[10, 20], [4,4], [98,99]} covers event numbers 4, 98, 99, and 10 thru 20. In
this case, there is an expectation that the frequency of occurrences of events 10 thru 20 will be higher
than that of event 4, which in turn is expected to occur more often than events 98 and 99.
The "define event" routines take effect immediately without waiting till the end of the transaction. The
registration will be saved in shared memory on the machine containing the database and will be
accessible by all server processes on that machine.
The event notification recipients, defined in definer_info, will be notified of an event on a registered
object if the event falls into the event ranges of interest and the predicate is evaluated to TRUE.
You can invoke the "define event" routines more than once with identical parameters, but this practice is
341
VERSANT Database Fundamentals Manual
not recommended. If you double register, on each qualified event you will receive only one event
message, but to drop, disable, or enable the registration you will need to invoke the appropriate function
twice.
System events
A system event is an event pre-defined by VERSANT. System events include status changes to a
database such as modification and deletion of instances, creation, modification, or deletion to any
instances under a class object, or a schema evolution of a class object. Another category of system events
are tags that indicate the start and end of events raised from a database within a transaction. The time a
system event is raised is determined by the system.
In an events parameter, you can specify the following numbers recognized by VERSANT.
0xC0000000
Events are added to the database event message queue when the transaction raising the event is
committed. Otherwise, all events related to the transaction will be discarded. However, an event is added
immediately if it is raised with an explicit use of the "send event to daemon routine," either
o_sendeventtodaemon() or sendeventtodaemon().
342
VERSANT Database Fundamentals Manual
343
VERSANT Database Fundamentals Manual
Multiple operations
In a transaction, multiple operations may be applied to the same object and cause multiple events to
occur. What happens depends on whether the object is an instance object or a class object.
For instance objects, the following shows the net effect of two system events on the same object. Only
relevant sequences are shown.
First event Second event Message sent
object modified object modified One obj modified message.
object modified object deleted One obj deleted message.
For class objects, the following shows the net effect of two system events on the same object. Only
relevant sequences are shown, and in the following the word "object" refers to a class object.
344
VERSANT Database Fundamentals Manual
Defined Events
A "defined event" is an event defined in an application. It is bound to an object when you register it with
o_defineevent() or defineevent().
A defined event may or may not involve changes to an object. Defined events are known only to the
application, and VERSANT does not attempt to interpret defined events. For example, you can define an
event to occur when a routine affecting an object completes.
A defined event is identified by a number. You can specify your own event number pairs using any
non-negative number, 0 through 2147483647.
You can register the same event to more than one object. However, when registered to different objects,
an event may have different meanings when applied to one object or another. For example, an event on
class C1 could mean a temperature increase, while an event on class C2 could mean a temperature drop.
For system events, the server process will monitor registered objects. For defined events, the application
process will monitor registered objects and notify the server when a defined event on an object occurs.
When an event occurs, the server first evaluates each registration for the object and determines whether
to evaluate the predicate immediately or upon commit.
Regardless of when evaluation is performed, a registration qualifies a raised event only if the registration
is active, event has occurred on a registed object is of interest, and the evaluation of the predicate, if any,
is satisfactory.
Notifications are stored in an operating system message queue, one per database. This queue is
automatically created by VERSANT when the database is started. Messages for an application remain in
the queue until the application requests its messages with a "get event message" routine,
o_readevent() or readevent().
If the message queue runs out of space and a message is sent to it, by default the following will happen.
1. All event messages generated in the transaction that have already been delivered are not affected.
(There is no "rollback" of message delivery once a message in a string of messages has been
delivered.)
2. Each time an undeliverable message is encountered, the event daemon will retry sending it three
times. After three tries, the database server will write the event message to the file LOGFILE in the
database directory.
345
VERSANT Database Fundamentals Manual
3. If the event message queue clears during the time the event message queue is delivering a string of
messages, all subsequent messages will be delivered as usual.
To improve performance when the event message queue runs out of space, you can override the default
behavior by invoking o_initen() or initen() with the O_EV_NO_RETRY option. If you use
O_EV_NO_RETRY, the following will happen when the message queue runs out of space:
1. The error EV_EVENT_LOST is returned. As events are delivered only when a transaction is
committed, it is unnecessary to rollback the transaction upon receiving this error.
2. The first event undeliverable message generated by the transaction is written to the file LOGFILE in
the database directory.
3. All other undeliverable event messages generated by the transaction are discarded.
4. All deliverable event messages generated by the transaction are delivered, but the database server
will try to deliver the message only once.
To restore default behavior, you can invoke o_initen() or initen() again with the option
O_EV_RETRY_SEND.
On UNIX platforms, the following parameters are used to configure a message queue:
MSGTQL The maximum number of messages, systemwide.
MSGMNB The maximum size in bytes of a particular queue for a particular database.
MSGMAX The size of the largest message that can be stored.
MSGMNI The maximum number of message queues, systemwide.
Values for these UNIX parameters are defined in the file sys/msg.h. To change a value, you must
configure a new UNIX kernel.
The event notification scheme for long transactions that was used in previous releases (and which used
e-mail for notification) can still be used alongside this scheme for short transactions.
Because this event notification scheme operates at run time, it does not change database format or affect
object compatibility.
346
VERSANT Database Fundamentals Manual
Alarm.h
/*
* Alarm.h
*
* Alarm class declaration.
*/
#ifndef ALARM_H
#define ALARM_H
#include <cxxcls/pobject.h>
class Alarm: public PObject
{
private:
int level;
public:
//
// Constructors & destructors
//
Alarm(int j=0):level(j) {};
~Alarm() {};
//
// Accessors
//
int getLevel() {return level;};
//
// Misc
//
friend ostream &operator<<(ostream &o, Alarm &e);
};
#endif
Daemon.cxx
/*
* Daemon.cxx
*
* A sample event dispatching daemon process.
*/
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <iostream.h>
#include <strstream.h>
#include "Alarm.h"
#define AUX_LEN 100
//
347
VERSANT Database Fundamentals Manual
o_ptr eventQueue;
int lengthEvent = 0;
o_event_msg *receivedEvent = 0;
int myPid = getpid();
int definerPid;
dom = new PDOM;
dom->beginsession(DBNAME, NULL);
//
// Specify that I am an event delivery daemon
//
dom->initen(DBNAME, O_EV_DAEMON | O_EV_ENABLE, 0, 0);
dom->openeventqueue(DBNAME, &eventQueue);
//
// Allocate buffers for received events
//
lengthEvent = sizeof(struct o_event_msg) + AUX_LEN;
receivedEvent = (o_event_msg *) malloc(lengthEvent);
//
// Signal only not for me
//
while(TRUE)
{
dom->readevent(eventQueue, lengthEvent, receivedEvent, 0);
definerPid = atoi((char *)O_EV_DEFINER_INFO(receivedEvent));
if (definerPid != myPid)
kill(definerPid,SIGUSR2);
}
}
Element.cxx
/*
* Element.cxx
*
* Implementation of Element class.
*/
#include "Element.h"
LinkVstr<Element> Element::getBad(int aLevel)
{
PAttribute alarmAttr =
PAttribute("Element::history\tAlarm::level");
348
VERSANT Database Fundamentals Manual
LinkVstr<Element> matchingObj =
PClassObject<Element>::Object().select(
NULL,
FALSE,
alarmAttr >= (o_4b)aLevel
);
return matchingObj;
}
LinkVstr<Element> Element::getGood(int aLevel)
{
PAttribute alarmAttr =
PAttribute("Element::history\tAlarm::level");
PPredTerm predicate = (alarmAttr < (o_4b)aLevel);
//
// None of them have received an alarm of level2
// or no alarm at all.
//
predicate.set_flags(O_ALL_PATHS|O_EMPTY_TRUE);
LinkVstr<Element> matchingObj =
PClassObject<Element>::Object().select(
NULL,
FALSE,
predicate
);
return matchingObj;
}
void Element::addHistory(Alarm *a)
{
dirty();
history.add(a);
}
ostream &operator<<(ostream &o, Element &e)
{
return o << e.name << '@' << e.address;
}
Element.h
/*
* Element.h
*
* Element class declaration.
*/
#ifndef ELEMENT_H
#define ELEMENT_H
#include <iostream.h>
#include <iomanip.h>
#include <cxxcls/pobject.h>
#include <cxxcls/pstring.h>
class Alarm;
class Element: public PObject
349
VERSANT Database Fundamentals Manual
{
private:
PString name;
int address;
LinkVstr<Alarm> history;
public:
//
// Constructors & destructors
//
Element(char *aName,int anAddr):
name(aName),address(anAddr) {}
~Element() {};
//
// Mutators
//
void addHistory(Alarm *a) ;
//
// Queries
//
static LinkVstr<Element> getGood(int aLevel);
static LinkVstr<Element> getBad(int aLevel);
//
// Misc
//
friend ostream &operator<<(ostream &o, Element &e);
};
#endif
MakeAlarm.cxx
/*
* MakeAlarm.cxx
*
* Modify Element class instances to raise event.
*/
#include <iostream.h>
#include <iomanip.h>
#include "Alarm.h"
#include "Element.h"
int main()
{
char *DBNAME = getenv("DBNAME");
if (DBNAME == NULL) {
cout << "DBNAME environment variable not set" << endl;
exit(1);
}
//
// Start a session on DBNAME
//
dom = new PDOM;
dom->beginsession(DBNAME, NULL);
350
VERSANT Database Fundamentals Manual
//
// Parameters for the alarm
//
int aLevel,anAddr;
char anEl[255];
int goOn = 0;
do
{
printf("Alarm level:");
scanf("%i", &aLevel);
printf("Element name:");
scanf("%s", anEl);
printf("Element address:");
scanf("%i", &anAddr);
//
// Get the equipment
//
LinkVstr<Element> elements =
PClassObject<Element>::Object().select(
NULL,
FALSE,
(PAttribute("Element::name") == anEl) &&
(PAttribute("Element::address") == (o_4b)anAddr)
);
if (elements.size() == 0)
{
cout << "Bad input" << endl << flush;
return -1;
}
//
// Create the Alarm and commit
//
elements[0]->addHistory(new(
PClassObject<Alarm>::Pointer()) Alarm(aLevel));
dom->commit();
printf("Continue:");
scanf("%i", &goOn);
}
while (goOn == 1)
;
//
// Commit and end session
//
dom->endsession();
return 0;
}
# link for interpretation with ObjectCenter
# Notice: you must either run "getocinit" and "source init"
# or you must add steps here to load Versant libraries.
# Otherwise you will get a long list of undefined symbols.
351
VERSANT Database Fundamentals Manual
# compile
Monitor.cxx
/*
* Monitor.cxx
*
* Register and wait for events.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <iostream.h>
#include "Alarm.h"
#define PID_LEN 20
o_bool theEnd;
//
// Notification through signals.
//
static void sig_handler()
{
printf("BIP BIP BIP BIP BIP\n");
theEnd = TRUE;
}
int main()
{
signal(SIGUSR2,(SIG_PF)sig_handler);
char *DBNAME = getenv("DBNAME");
if (DBNAME == NULL) {
cout << "DBNAME environment variable not set" << endl;
exit(1);
}
Vstr<o_u4b> events;
LinkVstrAny classes;
loid regID;
o_4b badObjIndex = -1;
char processID[PID_LEN];
int myPid = getpid();
int pidLen;
//
// Start a session on DBNAME
//
dom = new PDOM;
dom->beginsession(DBNAME, NULL);
theEnd = FALSE;
sprintf(processID, "%i", myPid);
pidLen = strlen(processID) + 1;
//
// Define the event
//
352
VERSANT Database Fundamentals Manual
classes.add(dom->locateclass("Element", DBNAME));
PPredicate pred= (PAttribute("Element::history\tAlarm::level")
== (o_4b)2) && (PAttribute("Element::name") == "WACSEM");
events.add(O_EV_CLS_INST_MODIFIED);
events.add(O_EV_CLS_INST_MODIFIED);
dom->defineevent(DBNAME, classes, events, pred, NOLOCK,
EV_EVAL_AT_COMMIT, pidLen, (o_u1b *)processID, ®ID,
&badObjIndex);
//
// Wait for the SIGUSR2
//
printf("I am doing something very important\n");
while(!theEnd)
;
//
// Only reached after the signal
//
dom->endsession();
}
Populate.cxx
/*
* Populate.cxx
*
* Create instances of Element class.
*/
#include <iostream.h>
#include <iomanip.h>
#include "Alarm.h"
#include "Element.h"
int main()
{
char *DBNAME = getenv("DBNAME");
if (DBNAME == NULL) {
cout << "DBNAME environment variable not set" << endl;
exit(1);
}
//
// Start a session on DBNAME
//
dom=new PDOM;
dom->beginsession(DBNAME, NULL);
PClass *pe = PClassObject<Element>::Pointer();
PClass *pa = PClassObject<Alarm>::Pointer();
//
// Create 3 elements
//
Element *e1 = new(pe) Element("FETEX-150", 100);
Element *e2 = new(pe) Element("FETEX-150", 200);
Element *e3 = new(pe) Element("WACSEM", 300);
353
VERSANT Database Fundamentals Manual
//
// Commit and end session
//
dom->endsession();
return 0;
}
Statistics.cxx
/*
* Statistics.cxx
*
* Gather simple statistics about Element objects.
*/
#include <iostream.h>
#include <iomanip.h>
#include "Alarm.h"
#include "Element.h"
int main(int argc, char *argv[])
{
char *DBNAME = getenv("DBNAME");
if (DBNAME == NULL) {
cout << "DBNAME environment variable not set" << endl;
exit(1);
}
if (argc < 2) {
cout << "Usage: Statistics <level>" << endl;
exit(2);
}
//
// Start a session on DBNAME
//
dom = new PDOM;
dom->beginsession(DBNAME, NULL);
int aLevel = atoi(argv[1]);
//
// Get the bad equipments
//
LinkVstr<Element> bads = Element::getBad(aLevel);
cout << "Bad equipments" << endl << flush;
for (int i = 0; i < bads.size(); i++)
cout << *bads[i] << endl << flush;
bads.release();
//
// Get the good equipments
//
LinkVstr<Element> goods = Element::getGood(aLevel);
cout << "Good equipments" << endl << flush;
for (i = 0; i < goods.size(); i++)
cout << *goods[i] << endl << flush;
354
VERSANT Database Fundamentals Manual
goods.release();
//
// Commit and end session
//
dom->endsession();
return 0;
}
The order in which the pairs of event number are specified in the events parameter determines the order in
which events are evaluated. This means that performance will be better if events with a higher frequency of
occurrence are kept in front of the list.
For example, an array of {[10, 20], [4,4], [98,99]} covers event numbers 4, 98, 99, and 10 thru 20. In this
case, there is an expectation that the frequency of occurrences of events 10 thru 20 will be higher than that of
event 4, which in turn is expected to occur more often than events 98 and 99.
The registerInterestsAbout: method takes effect immediately without waiting till the end of the
transaction. The registration will be saved in shared memory on the machine containing the database and will
be accessible by all server processes on that machine.
The event notification recipients, defined in another parameter, will be notified of an event on a registered
object if the event falls into the event ranges of interest and the predicate is evaluated to true.
You can invoke registerInterestsAbout: more than once with identical parameters, but this practice is
not recommended. If you double register, on each qualified event you will receive only one event message,
but to drop, disable, or enable the registration you will need to invoke the appropriate function twice.
System events
A system event is an event pre-defined by VERSANT. System events include status changes to a database
such as modification and deletion of instances, creation, modification, or deletion to any instances under a
class object, or a schema evolution of a class object. Another category of system events are tags that indicate
the start and end of events raised from a database within a transaction. The time a system event is raised is
determined by the system.
Events are added to the database event message queue when the transaction raising the event is committed.
Otherwise, all events related to the transaction will be discarded. However, an event is added immediately if it
355
VERSANT Database Fundamentals Manual
In the events parameter, you can specify the following symbols for system events recognized by VERSANT.
#O_EV_OBJ_DELETED
This event will be applied to both instance and class objects contained in the objects parameter.
For an instance object, a message is sent when it is deleted. For a class object, a message is sent when
the class is dropped.
This event is similar to an object modified event, except that a message is sent only on deletion.
#O_EV_OBJ_MODIFIED
This event will be applied to both instance and class objects contained in the objects parameter.
For an instance object, a message is sent when it is modified. For a class object, a message is sent when
schema evolution occurs, such as when an attribute is added or dropped or when a subclass is dropped.
#O_EV_CLS_INST_DELETED
This event will be applied only to class objects contained in the objects parameter.
If you delete all instances of a class, as with o_dropclass() or o_dropinsts(), you will receive a
message for each deleted instance, which could be overwhelming if the class contained many instances.
Also, the server for the originating transaction trying to drop all instances could conceivably run short of
memory trying to store all deferred events for each deleted instance.
#O_EV_CLS_INST_MODIFIED
This event will be applied only to class objects contained in the objects parameter.
#O_EV_CLS_INST_CREATED
This event will be applied only to class objects contained in the objects parameter.
356
VERSANT Database Fundamentals Manual
#O_EV_BEGIN_EVENT
This event will be applied to both instance and class objects contained in the objects parameter.
#O_EV_END_EVENT
This event will be applied to both instance and class objects contained in the objects parameter.
Multiple operations
In a transaction, multiple operations may be applied to the same object and cause multiple events to
occur. What happens depends on whether the object is an instance object or a class object.
For instance objects, the following shows the net effect of two system events on the same object. Only
relevant sequences are shown.
357
VERSANT Database Fundamentals Manual
A defined event may or may not involve changes to an object. Defined events are known only to the
application, and VERSANT does not attempt to interpret defined events. For example, you can define an
event to occur when a method affecting an object completes.
SmallInteger values 0-9 are reserved for configuring the event notification system:
3-9 — reserved
You can specify your own event number pairs using any non-negative number, 10 through 2^29 - 1 =
536870911.
You can register the same event to more than one object. However, when registered to different objects,
an event may have different meanings when applied to one object or another. For example, an event on
class C1 could mean a temperature increase, while an event on class C2 could mean a temperature drop.
When an event occurs, the server first evaluates each registration for the object and determines whether
to evaluate the predicate immediately or upon commit.
358
VERSANT Database Fundamentals Manual
Regardless of when evaluation is performed, a registration qualifies a raised event only if the registration
is active, event has occurred on a registered object is of interest, and the evaluation of the predicate, if
any, is satisfactory.
For each application, notifications are stored in an operating system message queue, one per database.
This queue is automatically created by VERSANT when the database is started.
If a message queue overflows available space, messages are lost until space is again available. On UNIX
platforms, the following parameters are used to configure a message queue:
MSGTQL The maximum number of messages, systemwide.
MSGMNB The maximum size in bytes of a particular queue for a particular database.
MSGMAX The size of the largest message that can be stored.
MSGMNI The maximum number of message queues, systemwide.
Values for these UNIX parameters are defined in the file sys/msg.h. To change a value, you must
configure a new UNIX kernel.
To read a message, an application must request its messages with the readAndSendEvents method in
VEventDaemon.
Because this event notification scheme operates at run time, it does not change database format or affect
object compatibility.
359
VERSANT Database Fundamentals Manual
The following statistics related to event notification can be collected per database connection. See the
"Statistics Collection" chapter for information on statistics collection.
STAT_BE_EV_DEFINED The number of invocations of
registerInterestsAbout:.
STAT_BE_EV_SYS_RAISED The number of system events raised (but not
necessarily delivered).
STAT_BE_EV_SYS_DELIVERED The number of system events delivered, not including
the system events O_EV_BEGIN_EVENT and
O_EV_END_EVENT.
STAT_BE_EV_USER_RAISED The number of user defined events raised (but not
necessarily delivered).
STAT_BE_EV_USER_DELIVERED The number of user defined events delivered.
The following statistics can be collected per database:
STAT_DB_DISK_FREE Bytes of storage space available for any use (space in
free extents)
STAT_DB_DISK_RESERVED Bytes of storage space reserved by classes (space in
allocated extents and free bytes in data pages)
STAT_DB_EV_DEFINED The number of invocations of registerInterestsAbout:.
STAT_DB_EV_SYS_RAISED The number of system events raised (but not
necessarily delivered).
STAT_DB_EV_SYS_DELIVERED The number of system events delivered, not including
the system events O_EV_BEGIN_EVENT and
O_EV_END_EVENT.
STAT_DB_EV_USER_RAISED The number of user defined events raised (but not
necessarily delivered).
STAT_DB_EV_USER_DELIVERED The number of user defined events delivered.
360
VERSANT Database Fundamentals Manual
While synchronous database replication is useful for achieving fault tolerance and data safety, asynchronous
replication is useful for achieving performance in a distributed environment.
For example, suppose that you have numerous regional offices accessing a common database. If you used
asynchronous replication to maintain ten replicated local databases, users in each regional office would be
making local database connections and the number of users per database would be reduced. In some
situations, this approach would dramatically improve performance.
Copy objects
Copy objects to a target database without changing their logical object identifiers.
c o_moveobjs()
c++ migrateobjs()
it with a combination of event notification and object copying methods and procedures. Typically,
maintenance will be done by setting up a process for each database which uses event notification to watch for
any changes. When a change is made, it propagates it to the other databases using routines such as
o_moveobjs() or copyAllFromDB:.
Instead of using event notification to propagate changes as they happen, it is also possible to write a program
which propagates a batch of changes all at once. For example, if you have, say, a thousand salesmen carrying
around copies of a master database on notebook computers, at the end of the day they could return to their
offices, plug into the network, and run a program which copies new objects back to the master database.
One disadvantage of asynchronous replication is that there is a lag time between when a change is made in
one database and when it becomes visible in the other databases. As a result, there is a window where the
databases are inconsistent. This can lead to a situation where different users at different times make
conflicting updates to an object without knowing that it has been changed by another user. If you are dealing
with such a situation, you might want to refer to the chapter on "Optimistic Locking" which explains how you
can create and use timestamps to coordinate updates to unlocked objects.
For some applications, the disadvantages of asynchronous replication will not matter, but for others it will.
Because of the great flexibility available, it is often possible to come up with workarounds for dealing with the
inherent problems.
The code for the following example is for C++/VERSANT. Utility names are for UNIX.
The central element in the following approach is the event notification daemon process. It acts not only as an
event dispatcher, but also as an object replication server.
The database name and the database identifier must be unique to the network system, so the same
osc-dbid database system identifier file should be used for all databases involved. See also the chapter
"Create a Database" and the references to the setdbid and createdb utilities in the VERSANT Database
Administration Manual.
2. Follow normal event notification procedures to initialize the environment, register events, and provide for
cases when the replica database is down.
362
VERSANT Database Fundamentals Manual
4. Retrieve the logical object identifier for the changed object from the o_event_msg struct of the
received event.
For example:
o_event_msg event;
::dom->readevent(...); // fetch an event
loid objId = event->obj_event_raised; // get loid
LinkAny object;
if ((object = ::dom->getcod(objId)) != NULL) {
// Remember, the system will generate 'begin-event' and
// 'end-event' events whose obj_event_raised fields
// will be filled with all 0's. So the check is
// recommended.
LinkVstrAny objvstr, dummy;
objvstr.add(object);
try {
::dom->migrateobjs(
objvstr,
DBNAME1, // source db
DBNAME2, // replication db
&dummy,
O_MV_SYNC);
::dom->commit();
}
catch (PError &err) {
// migratation failed
}
end_try;
// migratation succeeded
Depending upon your needs, either the event notification daemon or any other application can copy objects
to the replication database. In a real application, you might also want to copy objects in groups, rather than
one at a time, or copy objects only if they met some condition. You can use these routines to implement
periodic synchronization using either a push or a pull model.
363
VERSANT Database Fundamentals Manual
You might want the daemon to utilize a busy-waiting event handling loop since the option O_EV_IPC_NOWAIT
is specified. It is possible to put the daemon to sleep for a while before pinging the server. However, if there is
heavy event traffic, the event queue may overflow while the daemon is sleeping.
If the server goes down, you might also want to notify client applications. When the server comes back, the
daemon will need to re-initialize the event notification environment and possibly notify client applications.
364
VERSANT Database Fundamentals Manual
365
VERSANT Database Fundamentals Manual
If a hard disk crashes, you can restore to your last database backup, but you will lose the last committed
transaction unless you are using roll forwarding or some form of database replication.
Following are the ways you can avoid losing the last committed transaction.
If continuous operation is your first priority, then you probably want to use synchronous database
replication in which VERSANT ensures that changes made to one database are also made to another
database. Together, the two databases kept in synchronization are called a "replica pair."
With synchronous database replication, if a crash occurs to one database, VERSANT will continue to use
the remaining database. When the crashed database comes back, VERSANT will automatically
resynchronize it with the database that did not crash. For more information about synchronous database
replication, see the chapter "Fault Tolerant Server."
If you want to keep a history of database states, then you probably want to use a combination of database
backups plus roll forward archiving. In this case, you can always restore to a previous, historical state.
If performance is an issue, then you also probably want to use roll forward archiving to a local tape or disk
drive, because this avoids network traffic without compromising the ability to recover from a destroyed
machine.
366
VERSANT Database Fundamentals Manual
To use roll forward, you do the following for each database on which you want to use roll forwarding.
When a database is created, by default, roll forward is not enabled. An on-line backup must first be
performed before roll forward can be enabled.
To backup a database and turn on roll forward, you can use the command line vbackup utility. Once a
backup has been completed and roll forward has been enabled, VERSANT will never throw away any log
records which have not been archived.
2. Create a backup.
After roll forward has been enabled, make an online backup, so that there is a consistent state from which
to roll forward. This is done using the command line vbackup utility.
Once you have begun the online backup process, turn on roll forward archiving, using the command line
vbackup utility. After roll forward archiving is turned on, VERSANT will start archiving log records
created since the last backup. You can simultaneously backup multiple database logs to a single tape, or
use multiple tape drives up to one per database.
Once roll forward has been enabled, you can safely stop and start archiving temporarily, as long as roll
forwarding remains on. As long as roll forwarding is on, you are always guaranteed that no log records
will be discarded until they have been archived.
There are many reasons why you may need to suspend roll forward. For example, you may need to
change a tape or use the roll forward tape drive to make an online backup. Or, roll forward may be
interrupted by a tape device problem or network crash.
367
VERSANT Database Fundamentals Manual
Following are possible database states. The vbackup command options that will move you from one state to
another are indicated in italics.
368
VERSANT Database Fundamentals Manual
• To perform backups, roll forward archiving, and restores on a database, you must be the dba
user who created the database.
• You do not have to suspend roll forward archiving to perform an online backup if you use a
device for the backup that is different from the roll forward device.
• If you need to spread roll forward archiving over several tapes or devices, the tape or device
files will be marked with a sequence number. When you use the files to restore a database, the
sequence number will be checked to ensure that they are applied in the proper order.
• If a database goes down, roll forward archiving for that database will be stopped, but roll
forward archiving for other databases will continue unless they are writing to the same output
media. If multiple databases are being archived to the same output media, a failure to any of the
databases will cause log archiving to be stopped for all of the databases.
• If a database goes down, when it returns, you will have to restart roll forward archiving for that
database.
• If roll forward archiving is stopped, either explicitly or by a process termination, and then it is
restarted, nothing will be lost, and there will be no reason to do a fresh incremental backup.
However, if roll forward archiving is stopped, you should restart the roll forward archiving
process as soon as possible. If the database runs out of space in its log files while roll forward
archiving is suspended, you will get an error message, and the database will shut down.
• Roll forward archiving has no effect on the normal writing of log files to a database.
• If a database must be restored from backup and log archives, only log archives created after the
latest incremental backup are utilitized.
• If you turn off roll forward with the -off option to vbackup, VERSANT will stop saving log
records for archiving, and they will be discarded after a transaction ends.
369
VERSANT Database Fundamentals Manual
If you need to roll forward, use the vbackup command line utility to restore first from your last complete
backup. After you have restored from your last complete backup, you will be given the opportunity first to
restore from your incremental backups and then from your roll forward archives. You will not have to invoke
vbackup repeatedly: all restore steps are performed in a single invocation.
Archiving with one device — If you have only one tape drive or external drive, a typical sequence of events is
the following.
For example:
vbackup -level 0 -device tape1 -rollforward -backup db1 db2
After the backup, the command prompt will return. The backup does not have to be a full backup, just
whatever is appropriate.
For example:
vbackup -device tape1 -log db1 db2
After this command, the command prompt will not return and roll forward archiving will continue until
you end the archiving process by closing the window or pressing <Enter> to exit vbackup.
3. Temporarily stop roll forward archiving (by pressing <Enter>) to change a tape. During this time, all log
records will be saved until you start archiving again.
For example:
vbackup -level 1 -device tape1 -backup db1 db2
For example:
vbackup -device tape1 -log db1 db2
370
VERSANT Database Fundamentals Manual
Archiving with multiple devices — Suppose that you have three tape drives and two databases. In this case, a
typical sequence of events for databases db1 and db2 is the following.
After the backup, the command prompt will return. The backup does not have to be a full backup, just
whatever is appropriate.
3. Open another window and begin roll forward archiving on database db2 using tape2:
vbackup -device tape2 -log db2
Restoring after a crash — For the above examples, to restore after a crash, do the following.
1. If the databases were completely demolished, first recreate the database directories and support files.
The user who recreates the databases must be the same user who created the original databases. In this
case:
makedb -g db1
makedb -g db2
If additional tapes are needed for the full backup, you will be prompted for them. Also, you will be asked for
incremental backup and roll forward tapes as each step is completed.
371
VERSANT Database Fundamentals Manual
372
VERSANT Database Fundamentals Manual
Concepts
This section describes the functionality of "VERSANT Fault Tolerant Server," a software module that
supplements standard VERSANT functionality by performing synchronous database replication. Synchronous
database replication mirrors the contents of one database in another in a predictable and orderly manner. This
provides either local or geographically remote redundancy, which protects against the unavailability of a
database in the case of a system failure.
Besides synchronous database replication, you can also protect yourself against system failure with roll
forward archiving and/or asynchronous database replication. For information about roll forward archiving, see
the chapter "Roll Forward Archiving." For information about asynchronous database replication, see the
chapter "Event Notification."
When you use synchronous database replication, the two databases kept in synchronization are called a
"replica pair." If a crash occurs to one database, VERSANT will continue to use the remaining database. When
the crashed database comes back, VERSANT will automatically resynchronize the database with the database
that did not crash and return to fully replicated operation. The resynchronization will occur while the database
that did not crash continues to be updated.
373
VERSANT Database Fundamentals Manual
Procedures
You may want to create a new database directory for the database to be used for synchronous
replication.
• The database that you will use for replication cannot exist before you run the createreplica utility.
To maximize protection against system failure, you will probably want to locate the synchronous
replication database on a machine different than the primary database.
If you decide to use a new database directory for the synchronous replication database, use makedb
normally to create the database directory and the database profile files. Per normal practice, after the
profile files have been created, you can edit the server process profile file to specify non-default values
for such parameters as system and log volume sizes.
For example, to use makedb to create database directories and profile files for a group database named
replicadb:
makedb -g replicadb
See "Database Profiles" chapter in VERSANT Database Administration Manual for information on makedb
and server process profiles.
For each VERSANT installation that will use the primary database, create or edit a file that names the
primary and synchronous replication databases. The replica file should be named replica and be placed
in the VERSANT software root directory for that installation.
You must create a replica file before creating a synchronous replication database.
Once a replica file and a replica database have been created, VERSANT will automatically mirror the
contents of the named databases each time a transaction commits.
For each pair of primary and synchronous replication databases, the replica file should contain a line that
names the databases. Use the following syntax for the primary and synchronous replication database,
where node is the name of the machine containing the database:
primarydb@node replicadb@node
374
VERSANT Database Fundamentals Manual
For example, if the VERSANT software root directory is /usr/local/versant, the file
/usr/local/versant/replica will contain the following entry for a database named db1 located on a
machine named alpha and its synchronous replication database replicadb1 on a machine named
beta:
db1@alpha replicadb1@beta
A connection made to either database in a replica pair will automatically cause a connection to be made to
the other database in the pair.
The ordering of the names of the databases determines the order in which changes will be written.
Changes to the primary database will be written before changes are written to the synchronous
replication database. By default, reads are done from the primary database. The database from which
reads are made can be changed by calling o_setpreferreddb() or setpreferreddb(). If locks are
asserted, they will be asserted in both databases.
The replica file can contain separate lines for any number of pairs of databases and synchronous
replication databases.
You must use database@node syntax in the replica file to indicate the machines containing the databases
to be mirrored.
In the replica file, any line starting with a # sign is considered to be a comment.
Before beginning replication, you must use the createreplica utility to create the synchronous
replication database and load it with the contents of a primary database.
See the "Utilities" chapter in the VERSANT Database Administration Manual for a reference to the
createreplica utility.
Once you have created a replica file and synchronous replication database, you can access either
database using normal syntax and procedures.
Each time you connect with either database of a replica pair with a begin session or connect database
routine, VERSANT will look for the database name in the replica file and, if the name is found,
automatically make a connection with the other database of the pair.
When you perform an action on an object in either database of a replica pair, the action will be performed
first on the object in the first database named in the replica pair in the replica file and then performed on
the object in the second database. For example, if you lock an object in the second database, the lock will
first be set in the database first named in the replica pair and then set in the second database.
375
VERSANT Database Fundamentals Manual
When synchronous replication begins, by default a transaction will not report an error if a synchronous
replication database is down. For example, if you try to make a connection to one of the databases in the
replica pair and the connection fails because that database is down, the connection to the other database
will succeed and access will be subject to standard operation during failure mode.
If you want to receive an error message if either of the replicated databases fail, then you can enable
replica error reporting at the transaction level by doing the following.
A. Enable reporting for each database you want to monitor in the current database session.
B. Enable error reporting for a specific transaction by adding the option O_REPORT_ERROR to
the commit option supplied to o_xact() or xact().
For example:
o_xact( O_COMMIT | O_REPORT_ERROR, NULL);
If a replica database being monitored is down, a commit will return error 4035:
OM_TR_DBDOWN.
At the next database session, the default behavior of no reporting will be restored until
o_setreplicareporting() or setreplicareporting() is called again.
6. Administer b
both
oth databases in a normal manner.
Replica databases are normal databases. This means that you must manage both primary and
synchronous replication databases as independent entities.
For example, when you grant access privileges on a primary database, also grant privileges on the
synchronous replication database.
If the synchronous replication database runs out of space, use the addvol utility to add space as you
would with any other database. If you run out of space in a primary database and add volumes to it,
VERSANT will not check to see if the synchronous replication database needs space and will not
automatically add space to it.
376
VERSANT Database Fundamentals Manual
By default, when either database in a replication pair goes down, the other starts a polling process. When
the database that failed returns, the polling process will detect the return and resynchronize it.
If you want to turn automatic synchronization off, you can use the ftstool utility.
If you want to do manual synchronization, you can use the polling utility:
polling dbname
See the "Utilities" chapter in the VERSANT Database Administration Manual for a reference to ftstool
and polling.
You can use the offline comparedb utility to compare the contents of a primary and synchronous
replication database.
See the "Utilities" chapter in the VERSANT Database Administration Manual for a reference to
comparedb.
You can stop replication and remove a synchronous replication database with the removereplica utility.
See the "Utilities" chapter in the VERSANT Database Administration Manual for a reference to
removereplica.
377
VERSANT Database Fundamentals Manual
Usage
Restrictions
Following are restrictions on the use of synchronous database replication.
One of the replica pair must be a group database. This means that checkins and checkouts cannot be
replicated.
Both cursor queries and event notification operate on a single database and, thus, are not fault tolerant.
Savepoints
Nested transactions
Join session
You cannot use multiple processes in a session if synchronous database replication is in use.
You cannot change schema when a database in a pair of replica databases is down.
Some routines do not make sense for a pair of databases. For example, routines that manage serial
numbers or user lists, that get information about a database, or that run a utility on a database can be
used only on one database at a time.
378
VERSANT Database Fundamentals Manual
You cannot perform checkpoint commits or use the dropinst utility if synchronous database replication
is in use.
Failure Scenarios
Following are recovery procedures in the case that one or both databases are not repairable.
3a. If the down database is the secondary database, then create a new replica database with the same name
as the database that is not repairable:
createreplica UPdb DOWNdb
3b. If the down database is the primary database, then switch the order of the database names in the replica
file (so that the secondary database is seen as the primary database.) Then create a new replica database
with the same name as the database that is not repairable:
createreplica UPdb DOWNdb
2. Use the vbackup utility to restore the database previously backed up to a backup tape or file. (You do
not have to backup both databases, just one or the other).
vbackup -restore db1
379
VERSANT Database Fundamentals Manual
You can avoid the network partitioning problem through application design or by using the optional
replication error reporting. This allows you to know in an application that a replica failure has occurred and
then restrict updates to only one server. Three different update scenarios need to be considered:
1. If all applications execute on clients remote to replicated servers, then the network failure will
probably cause all databases to be inaccessible and neither database will be updated. In this
case, you don’t need to use replica error reporting.
2. If applications doing updates run on only one server, then parallel object updates will not occur.
In this case, you don’t need to use replica error reporting.
3. If applications doing updates run on both servers, then a potential for parallel updates exists and
error reporting must be used to prevent the applications on one server from updating during a
replication failure.
You may wish also to perform on-line backups and log archiving for a replicated database. In this case, you
would only use the backup and log archives in a case where a failure to both databases occurs. If you choose
to on-line backup and log archive only one database in the replicated pair, then a failure of the database being
archived followed by a failure of the other database in the replicated pair will only allow a database restore to
the latest log archive on the database where archiving was occurring. The most conservative approach is to
perform on-line backups and log archiving on both databases all of the time. A more moderate approach is to
only archive one database, and if the database being archived fails, immediately perform an on-line backup
and begin log archiving on the remaining up database.
You should never attempt a database restore or log rollforward using vbackup to one of the databases in an
actively replicated pair.
380
VERSANT Database Fundamentals Manual
Cautions
• You must create a replica file and enter the primary and synchronous replication database
names before initializing a synchronous replication database.
• Synchronous replication databases are normal databases. This means that you must manage
both primary and synchronous replication databases as independent entities.
For example, if either a primary or synchronous replication database runs out of space, use the addvol
utility to add space as you would with any other database. If you run out of space in a primary database
and add volumes to it, VERSANT will not check to see if the synchronous replication database needs
space and will not automatically add space to it.
• You must use database@node syntax in the replica file to indicate the machines containing the
databases to be mirrored.
• If you forget to include a pair of primary and synchronous replication database names in the
replica file, no replication will occur and error E7220 will be generated by the createreplica
utility.
• If you make a syntax error in the replica file, you will get an error message.
• If you make a consistency error in a replica file, you will not get an error message, but you will
have problems later. All replication files in a network must be the same, or else several types of
problems can occur.
For example, if one replica file on one installation of VERSANT specifies a particular primary and
synchronous replication database pair, but another installation does not, then changes made by some
applications will be replicated but changes made by other applications will not be replicated.
When you manage objects, actions are applied in the order in which a pair of databases are named in the
replica file. For example, if you ask for a write lock on an object, it is first set on the object in the first
database named in the primary/replica pair and then set on the object in the second database named in
the primary/replica pair.
If two applications use different replica files in which a particular database is designated as primary in one
and as replica in the other, a deadlock can occur if both applications try to update the same object.
381
VERSANT Database Fundamentals Manual
• Using stopdb -f is the same as a crash. Because replication is occurring, VERSANT will
automatically attempt to restart the database stopped with stopdb -f.
For example, suppose that db1 and db2 are a replica pair, both are running, and you stop db2. This will
result in the following sequence:
2. VERSANT immediately starts trying to restart and reconnect to db2 (it will continue trying as
long as db1 is running).
3. As soon as VERSANT can restart and reconnect to db2, if automatic synchronization is on, it
will resynchronize db2 with db1.
4. The restarted db2 becomes available again and resumes its role as part of the replica pair.
Mechanisms
382
VERSANT Database Fundamentals Manual
383
VERSANT Database Fundamentals Manual
It includes:
• Database Users
• User Authentication
• User Privileges
Database Users............................................................................................................................ 385
Users ...................................................................................................................................... 385
Database User Management utility (dbuser) ................................................................................ 386
User Authentication ..................................................................................................................... 388
Database Administrator Authentication ....................................................................................... 388
Database User Authentication.................................................................................................... 388
Backward Compatibility............................................................................................................. 389
Customized User Authentication ................................................................................................ 390
User Authentication Program Structure.................................................................................... 390
User Authentication Process................................................................................................... 392
User Authentication Sample Programs, UNIX ........................................................................... 395
User Authentication Sample Programs, Windows NT................................................................. 402
User Privileges............................................................................................................................. 408
Utility Access Privileges............................................................................................................. 408
384
VERSANT Database Fundamentals Manual
Database Users
Users
A user is a role adopted by a person who creates, administers or accesses Versant databases. Each user is
identified by a user name that is stored in the database. A user name can be of 32 characters in length.
Versant has three kinds of users:
The dbsa is the user who installs Versant on each machine in a network.
The dbsa
• Owns the osc-dbid file for a database system;
• Owns all VERSANT software root directories, including bin, h, and lib subdirectories and all files
under those directories, for all installations in a network; this ownership extends to all versions
of VERSANT installed on a system of databases;
• Owns all system information files for all installations in a network;
• Owns all database root directories for all installations in a network.
The dba owns the database and related files and directories.
There can be only one dba for a database at any point of time. After a database has been created, its dba can
be changed by changing the ownership of its database directory. On Windows platforms, this can be done by
editing the entry in the groups.flg or personal.flg file in the database directory.
Versant relies on Operating System mechanisms to verify the dba's identity and it does not do password-
based user authentication for the dba.
385
VERSANT Database Fundamentals Manual
The database users are the list of VERSANT maintained logical users who have access to a particular database.
The database user may or may not have a valid operating system user account. The database user list for a
particular database can be created and maintained by the dba of that database. A database user can change
their password using the database user management utility (dbuser).
The database users access the database using applications and utilities.
-add Add a user to the database access list. You must also supply a -n or -P option.
-delete Delete a user from the database access list. You must also supply a -n or -P option.
-chpasswd Change the password of a user. You must also supply -n option.
386
VERSANT Database Fundamentals Manual
Only the Database Administrator (dba) of a database has the privilege of adding/deleting users to/from the
users list.
For example,
• To add a user Fred in the database EmployeeDB by running dbuser as shown below:
dbuser -add -n Fred -m rw EmployeeDB
Enter Fred's password:
Confirm Fred's password:
• To make the database publicDB available for public access
dbuser -add -P publicDB
• To delete a user Allen from the database EmployeeDB
dbuser -delete -n Allen EmployeeDB
• To list all the user information in the user list of the database EmployeeDB
dbuser -list EmployeeDB
• A database user can change the password associated with the user information.
387
VERSANT Database Fundamentals Manual
User Authentication
Authentication is the means of identifying uniquely the database users. User identification is the basis of
Versant's authorization mechanism and the users are allowed access to the system when identified as
'authorized' users by the system.
To prevent unauthorized use of a database, Versant provides user identification via different methods for the
database users:
The Dba can choose to add a user with no password, in which case the authentication mechanism at the
server-side for that user is Operating System based. For users with a password, password based
authentication is followed at the server-side.
The client is authenticated at the server during the initial connect (o_beginsession or o_connectdb). Once
the connection has been established, no further authentication is done during the life of that connection.
The client has a secret password, known only to the client and the server. The password cannot exceed 256
characters. The client sends this password to the server to prove its identity. The server verifies the client's
password with the information stored in the database to authenticate the client.
To protect password confidentiality, Versant encrypts the password before sending it across the network.
The following api is used by the client to set the user information required for user authentication. The user
388
VERSANT Database Fundamentals Manual
information is specific for the caller thread of this api. The user information is sent to the server during a
connect (o_beginsession() or o_connectdb()).
o_err o_setuserlogin(o_userInfo userinfo /* user information */);
struct o_userInfo {
char username[O_NAME_SIZE];
char password[O_PASSWD_SIZE];
};
If the user information is not set using this api before connecting to a database, the user name from the
operating system will be picked up as the default.
In case the user entry in the database has no password, the server assumes Operating System authentication
and performs the corresponding check for thatuser during the initial connect. When performing Operating
System level authentication, it is required that the user exists as a valid Operating System user, both on the
client and the server side. Failing to satisfy both the conditions would result in an authentication failure. For
the case where, the user entry in the database has a valid password, only password-based authentication is
performed. Failing to provide the correct password would result in an authentication failure. The examples
below describe the authentication procedure.
3. db2tty -d <dbname> (If the user invoking the db2tty process is same as the user1, this command will
be authenticated based on Operating System)
4. db2tty -d <dbname> (If the user invoking the db2tty process is same as the user2, this command will
fail as it performs password based authentication)
5. dbuser -ch passwd -n user1 -passwd user1-password (The user invoking this command must
be user1, for it to succeed)
6. db2tty -d <dbname> -u user1 -p user1-password (Will succeed from any machine and as any
OS user)
Backward Compatibility
After conversion of a database to this release, the users in the database have no passwords. The
authentication mechanism for such users is again same as described above.
All pre 6.0 clients can connect to a 6.0 database provided the user entry in the database has no passwords.
Once the password changes to a valid password the pre 6.0 client cannot access the database.
389
VERSANT Database Fundamentals Manual
Create two authentication programs: an application authentication program that will run on the machine
containing the application, and a database authentication program that will run on the machine containing
the database.
These programs will communicate with each other through channels established by VERSANT.
These programs can perform any actions that you want in order to come to a decision as to whether a
particular user is valid.
On the application machine, set the environment variable VERSANT_AUTH to point to the application
authentication program.
Run your application in two processes by linking with the VERSANT two process system library for your
platform: liboscfe.a, liboscfe.lib, or liboscfe.so.
After these steps, VERSANT will run the authentication programs everytime a database connection request is
made, either with an interface routine or with a database utility.
390
VERSANT Database Fundamentals Manual
can send any of the following operation codes to the VERSANT process to which they are connected.
O_AUTH_READ
"Send to me one data packet you receive from your database connection."
O_AUTH_WRITE
"Write my data packet to your database connection."
O_AUTH_GRANTED
"This user is ok."
O_AUTH_DENIED
"This user is not ok."
O_AUTH_SET_TIMEOUT
"Set the timeout period to a specified number of seconds."
The structure of a packet that can be sent with these operation codes is:
typedef struct o_auth_packet {
o_u2b opcode; /* operation type */
o_u4b len; /* length of data in byte */
o_ptr data; /* packet data */
) o_auth_packet;
To an authentication process, the communication pipe appears to be open file descriptor 0 and 1. The
authentication processes reads a packet from file 0 and writes a packet to file 1.
While reading a packet, the opcode and len fields of the packet are first retrieved from the connection and
then data of the packet up to len bytes is read.
In the simplest case, an authentication program on an application machine should do the following to prepare
an authentication program on the database machine.
391
VERSANT Database Fundamentals Manual
1. Send O_AUTH_SET_TIMEOUT.
2. Send O_AUTH_READ.
The database administrator for a database must still add and delete users with the VERSANT dbuser utility.
The authentication programs are responsible for validating that a user is valid, but VERSANT will only extend
to that user the privileges established by the database administrator.
To use VERSANT, a machine must have TCP/IP software installed. When a machine with TCP/IP starts, a
TCP/IP inetd daemon process is started that will listen for service requests.
A user logs into the operating system, starts a VERSANT application, and makes a request to start a
session (which requires a database connection.)
When it receives a "begin session" or "connect database" request, VERSANT checks to see if the
VERSANT_AUTH environment variable has been set on its machine.
If the VERSANT_AUTH environment variable has been set, VERSANT reads VERSANT_AUTH, looks up the
specified authentication program, forks a process, starts the authentication program, and redirects stdin
and stdout so that they serve as a channel of communication between the application process and the
authentication program.
The VERSANT application may be using VERSANT files on a local or remote machine. The check is made
on the machine containing the VERSANT_ROOT location parameter in use by the application.
392
VERSANT Database Fundamentals Manual
When it starts, the application authentication program sends an O_AUTH_WRITE packet containing a
password to the application process. This message means, "please forward this password to the
authentication program on the machine containing the database." (The username is sent by the
VERSANT connection request.)
After sending a username, the application authentication program sends an O_AUTH_READ packet to the
application process. This message means, "when you get a message from the VERSANT server process,
forward it to me."
6. The application sends a connection request and username to the database machine.
The request for a database connection and a password are sent to the inetd daemon process on the
machine containing the database. (The same procedure is followed whether the database machine is
remote or local.)
The inetd daemon process will read its TCP/IP configuration files and respond to the connection
request by starting a VERSANT ss.d (system services) process and then connect the application with the
ss.d process.
The ss.d process will start the database, if it is not already running, and then fork a database server
process.
Once the connection request has been sent, control of the application process passes to the application
authentication program, and the only action that the application process can perform is to pass messages
to and from the authentication program.
The database server process will check its server process profile file. If it sees a line beginning with the
keyword authentication_program, it forks a process, starts the program specified, and redirects
stdin and stdout so that they serve as a channel of communication between the server process and the
server authentication program.
393
VERSANT Database Fundamentals Manual
The authentication programs on the application and database machines are now active and can
communicate with each other through the connection established by VERSANT. The authentication
programs can send any of the following operation codes to the VERSANT process to which they are
connected: O_AUTH_READ, O_AUTH_WRITE, O_AUTH_SET_TIMEOUT, O_AUTH_GRANTED, and
O_AUTH_DENIED. (This discussion assumes the default timeout. To change it, you would send
O_AUTH_SET_TIMEOUT.)
When it starts, the database authentication program sends the packet operation code O_AUTH_READ to
the database server process. This message means, "when you get a message from the VERSANT
application process, forward it to me."
At this point, control of the database server process has passed to the database authentication program,
and the only action that the server process can perform is to pass messages to and from the
authentication program.
The authentication programs now conduct any kind of dialogue that you desire, communicating with
each other through VERSANT. In this dialogue, each program can send a packet with O_AUTH_WRITE
and then stand by to receive a packet with O_AUTH_READ.
For example, to send a message to the application authentication program, the database authentication
program sends, on stdout, an O_AUTH_WRITE packet to the VERSANT server process. The VERSANT
server process then forwards the packet to the VERSANT application process. If the application process
has received an O_AUTH_READ packet, it will give the packet to the application authentication program.
If the server authentication program decides that the user is okay, it sends the message
O_AUTH_GRANTED to the VERSANT server process and then terminates.
When it receives O_AUTH_GRANTED, the VERSANT server process forwards the message to the
394
VERSANT Database Fundamentals Manual
VERSANT application process and returns. Its first action is to check whether the user is on its own list of
authorized users. If the user is on the list, then it stays alive in order to service database requests from the
application. If the user is not on its list, it sends an error to the application, closes the connection, and
terminates.
When it receives O_AUTH_GRANTED, the VERSANT application process forwards the message to the
application authentication program and returns.
If authorization is denied...
If the server authentication program decides that the user is not okay, it sends the message
O_AUTH_DENIED to the VERSANT server process and then terminates.
When it receives O_AUTH_DENIED, the VERSANT server process forwards an error message to the
VERSANT application process and terminates.
When it receives O_AUTH_DENIED, the VERSANT application process forwards the error message to the
authentication program, returns, and either handles the error or terminates with an error.
On Unix, the application communicates with the authentication program through a Unix pipe which is
duplicated onto the standard input and standard output handles of the authentication program. The program
simply used standard i/o routines to read and write from the pipe.
Following is a sample user authentication program to run on the machine running an application.
395
VERSANT Database Fundamentals Manual
It is a stripped down example that simply passes a plain-text password to an authentication program running
on the machine containing a database.
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <omapi.h>
/*
* File descriptor 0 is opened for reading from Versant kernel.
* File descriptor 1 is opened for writing to Versant kernel.
*/
#define IN 0
#define OUT 1
#define PASSWORD "simple-but-unsafe"
#define SYSLOG(msg) syslog(LOG_ERR, "(FE): %s", (msg))
int main(
int argc,
char *argv[]
)
{
o_auth_packet packet;
SYSLOG("Start");
/* write an O_AUTH_WRITE packet that contains the password */
packet.opcode = O_AUTH_WRITE;
packet.len = strlen(PASSWORD);
write(OUT, &packet, sizeof(o_auth_packet));
write(OUT, PASSWORD, packet.len);
/* write an O_AUTH_READ packet to server and wait for an reply */
packet.opcode = O_AUTH_READ;
packet.len = 0;
write(OUT, &packet, sizeof(o_auth_packet));
read(IN, &packet, sizeof(o_auth_packet));
/* verify the reply packet */
switch (packet.opcode)
{
case O_AUTH_GRANTED:
/* forward the packet to kernel */
SYSLOG("Access granted");
write(OUT, &packet, sizeof(o_auth_packet));
break;
case O_AUTH_DENIED:
/* forward the packet to kernel */
SYSLOG("Access denied");
write(OUT, &packet, sizeof(o_auth_packet));
break;
default:
SYSLOG("Wrong reply packet");
exit(2);
}
/* done */
SYSLOG("Done");
396
VERSANT Database Fundamentals Manual
return 0;
}
Authentication program for database machine
Following is a sample user authentication program to run on the machine containing a database.
It is a stripped down example that simply reads a plain-text password and then verifies it.
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <omapi.h>
/*
* File descriptor 0 is opened for reading from Versant kernel.
* File descriptor 1 is opened for writing to Versant kernel.
*/
#define IN 0
#define OUT 1
#define PASSWORD "simple-but-unsafe"
#define SYSLOG(msg) syslog(LOG_ERR, "(BE): %s", (msg))
int main(
int argc,
char *argv[]
)
{
o_auth_packet packet;
char buff[30];
SYSLOG("Start");
/* write an O_AUTH_READ packet to kernel and wait for an reply */
packet.opcode = O_AUTH_READ;
packet.len = 0;
write(OUT, &packet, sizeof(o_auth_packet));
/* read in the password */
read(IN, &packet, sizeof(o_auth_packet));
read(IN, buff, packet.len);
/* verify the reply packet */
if (packet.len == strlen(PASSWORD) &&
!strncmp(buff, PASSWORD, strlen(PASSWORD)))
{
SYSLOG("Access granted");
packet.opcode = O_AUTH_GRANTED;
}
else
{
SYSLOG("Access denied");
packet.opcode = O_AUTH_DENIED;
}
/* write the authentication result to kernel */
packet.len = 0;
397
VERSANT Database Fundamentals Manual
Following is a sample user authentication program to run on the machine containing a database.
It uses Kerberos V5 as the principal authentication mechanism. It performs the by parsing a KRB_AP_REQ
packet sent from authentication program running on the application machine.
Many of the resource deallocation codes are ignored on purpose in order to make the sample program
simpler.
#include "krb5.h"
#include "com_err.h"
#include <stdio.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <omapi.h>
#include "auth_krb5.h"
#define SYSLOG(msg) syslog(LOG_ERR, "(BE): %s", (msg))
#define KEYTAB "/tmp/v5srvtab"
void main(argc, argv)
int argc;
char *argv[];
{
o_auth_packet packet;
char* msgp;
krb5_context context;
krb5_auth_context* auth_context = NULL;
krb5_ccache ccdef;
krb5_data recv_data;
krb5_principal principal;
krb5_keytab keytab;
krb5_error_code retval;
SYSLOG("Start");
/* initialize Kerberos context */
krb5_init_context(& context);
krb5_init_ets(context);
/* initialize authentication context */
if (retval = krb5_auth_con_init(context, &auth_context))
{
398
VERSANT Database Fundamentals Manual
SYSLOG(error_message(retval));
exit(1);
}
/* identify our credentials cache */
if (retval = krb5_cc_default(context, &ccdef))
{
SYSLOG(error_message(retval));
exit(2);
}
/* write an O_AUTH_READ packet to kernel and wait for an reply */
packet.opcode = O_AUTH_READ;
packet.len = 0;
write(OUT, &packet, sizeof(o_auth_packet));
read(IN, &packet, sizeof(o_auth_packet));
if ((msgp = (char *) malloc(packet.len)) == NULL)
{
SYSLOG("Out of memory");
exit(3);
}
/* read in the KRB_AP_REQ message */
read(IN, msgp, packet.len);
/* reconstruct the KRB_AP_REQ message */
memcpy(&recv_data, msgp, sizeof(recv_data));
recv_data.data = msgp + sizeof(recv_data);
if (retval = krb5_kt_resolve(context, KEYTAB, &keytab))
{
SYSLOG(error_message(retval));
exit(4);
}
/* parse the KRB_AP_REQ message */
if (retval = krb5_sname_to_principal(
context, OBE_HOST, OBE_SERVICE,
KRB5_NT_SRV_HST, &principal))
{
SYSLOG(error_message(retval));
exit(5);
}
if (retval = krb5_rd_req(context, &auth_context,
&recv_data, principal,
keytab, NULL, NULL))
{
SYSLOG(error_message(retval));
SYSLOG("Authentication failed");
packet.opcode = O_AUTH_DENIED;
}
else
{
SYSLOG("Authentication succeeded");
packet.opcode = O_AUTH_GRANTED;
}
399
VERSANT Database Fundamentals Manual
Following is a sample user authentication program to run on the machine containing an application.
This program utilizes Kerberos V5 as the principal authentication mechanism. It achieves authentication by
sending a KRB_AP_REQ packet to authentication program on a machine containing a database.
Many of the resource deallocation codes are ignored in order to make the sample program simpler.
#include "krb5.h"
#include "com_err.h"
#include <stdio.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <omapi.h>
#include "auth_krb5.h"
#define SYSLOG(msg) syslog(LOG_ERR, "(FE): %s", (msg))
void
main(argc, argv)
int argc;
char *argv[];
{
o_auth_packet packet;
krb5_context context;
krb5_auth_context* auth_context = NULL;
krb5_ccache ccdef;
krb5_data recv_data;
krb5_error_code retval;
SYSLOG("Start");
/* initialize Kerberos context */
krb5_init_context(& context);
krb5_init_ets(context);
/* initialize authentication context */
if (retval = krb5_auth_con_init(context, &auth_context))
{
SYSLOG(error_message(retval));
exit(1);
400
VERSANT Database Fundamentals Manual
}
/* identify our credentials cache */
if (retval = krb5_cc_default(context, &ccdef))
{
SYSLOG(error_message(retval));
exit(2);
}
/* format a KRB_AP_REQ message */
if (retval = krb5_mk_req(context, &auth_context,
AP_OPTS_USE_SESSION_KEY,
OBE_SERVICE, OBE_HOST, NULL, ccdef, &recv_data))
{
SYSLOG(error_message(retval));
exit(3);
}
/* send the KRB_AP_REQ message to kernel via an O_AUTH_WRITE
packet */
packet.opcode = O_AUTH_WRITE;
packet.len = sizeof(recv_data) + recv_data.length;
if ((packet.data = (o_ptr) malloc(packet.len)) == NULL)
{
SYSLOG("Out of memory");
exit(4);
}
memcpy(packet.data, &recv_data, sizeof(recv_data));
memcpy(((char *)packet.data)+sizeof(recv_data),
recv_data.data, recv_data.length);
write(OUT, &packet, sizeof(o_auth_packet));
write(OUT, packet.data, packet.len);
/* write an O_AUTH_READ packet to kernel and wait for an reply */
packet.opcode = O_AUTH_READ;
packet.len = 0;
write(OUT, &packet, sizeof(o_auth_packet));
/* wait */
read(IN, &packet, sizeof(o_auth_packet));
/* verify the reply packet */
switch (packet.opcode)
{
case O_AUTH_GRANTED:
/* forward the packet to kernel */
SYSLOG("Authentication succeeded");
write(OUT, &packet, sizeof(o_auth_packet));
break;
case O_AUTH_DENIED:
/* forward the packet to kernel */
SYSLOG("Authentication failed");
write(OUT, &packet, sizeof(o_auth_packet));
break;
default:
SYSLOG("Wrong reply packet received");
401
VERSANT Database Fundamentals Manual
exit(2);
}
/* done */
SYSLOG("Done");
exit(0);
}
User Authentication Sample Programs, Windows NT
Following are sample programs for customized user authentication suitable for Windows NT.
On Unix, the application communicates with the authentication program through a Unix pipe which is
duplicated onto the standard input and standard output handles of the authentication program. The program
simply used standard i/o routines to read and write from the pipe.
On NT we use an NT anonymous pipe to communicate between the authentication program and the
application. The read handle and the write handle of the pipe are passed in to the authentication program as
command-line arguments. These arguments are integers in decimal notation, and need to be converted to
handle numbers using atoi(). The authentication program is expected to use WIN32 ReadFile() and
WriteFile() calls to read from and write to these handles in order to communicate with the spawning
program.
The server authentication program follows the same protocol to communicate with VERSANT's server
process. It takes input and output handle numbers as command-line arguments, and uses ReadFile() and
WriteFile() for i/o on these handles.
Sample client and server authentication programs for NT are presented below.
/*
* A sample client user authentication program.
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <omapi.h>
FILE *outputFile = NULL;
#define SYSLOG(msg) { \
outputFile = fopen("authfe.out", "a"); \
fprintf(outputFile, "(FE): %s\n", (msg)); \
fclose(outputFile); \
}
#define PASSWORD "simple-but-unsafe"
/*
* This is a bare-bone front-end authentication program whose sole
* purpose is to demonstrate the usage of Versant user authentication
* facility. It simply passes a plain-text password to the server
* authentication program.
*/
int main (int argc, char **argv)
402
VERSANT Database Fundamentals Manual
{
o_auth_packet packet;
FILE *pfile;
DWORD BytesWritten, BytesRead;
HANDLE hIn, hOut;
char message[256];
if (argc < 3)
SYSLOG("Bad arguments");
hIn = (HANDLE)atoi (argv[1]);
hOut = (HANDLE)atoi (argv[2]);
sprintf (message, "hIn = %d, hOut = %d", hIn, hOut);
SYSLOG (message);
SYSLOG("Start");
/* write an O_AUTH_WRITE packet that contains the password
* to kernel
*/
packet.opcode = O_AUTH_WRITE;
packet.len = strlen(PASSWORD);
if (WriteFile ( hOut, &packet, sizeof(o_auth_packet),
&BytesWritten, NULL) == 0)
{
sprintf (message, "WriteFile failed : %d", GetLastError ());
SYSLOG (message);
return (1);
}
if (BytesWritten != sizeof(o_auth_packet))
{
sprintf ( message, "WriteFile wrote %d bytes instead of %d",
BytesWritten, sizeof(o_auth_packet));
SYSLOG (message);
return (1);
}
if (WriteFile (hOut, PASSWORD, packet.len, &BytesWritten,
NULL) == 0)
{
sprintf (message, "WriteFile failed : %d", GetLastError ());
SYSLOG (message);
return (1);
}
if (BytesWritten != packet.len)
{
sprintf (message, "WriteFile wrote %d bytes instead of %d",
BytesWritten, packet.len);
SYSLOG (message);
return (1);
}
/* write an O_AUTH_READ packet to kernel and wait for an reply */
packet.opcode = O_AUTH_READ;
packet.len = 0;
if (WriteFile (hOut, &packet, sizeof(o_auth_packet),
403
VERSANT Database Fundamentals Manual
&BytesWritten, NULL) == 0)
{
sprintf (message, "WriteFile failed : %d", GetLastError ());
SYSLOG (message);
return (1);
}
if (BytesWritten != sizeof(o_auth_packet))
{
sprintf (message, "WriteFile wrote %d bytes instead of %d",
BytesWritten, sizeof(o_auth_packet));
SYSLOG (message);
return (1);
}
if (ReadFile (hIn, &packet, sizeof (o_auth_packet),
&BytesRead, NULL) == 0)
{
sprintf (message, "ReadFile failed : %d", GetLastError ());
SYSLOG (message);
return (1);
}
if (BytesRead != sizeof (o_auth_packet))
{
sprintf (message, "ReadFile read %d bytes instead of %d",
BytesRead, sizeof (o_auth_packet));
SYSLOG (message);
return (1);
}
/* verify the reply packet */
switch (packet.opcode)
{
case O_AUTH_GRANTED:
/* forward the packet to kernel */
SYSLOG("Access granted");
if (WriteFile (hOut, &packet, sizeof (o_auth_packet),
&BytesWritten, NULL) == 0)
{
sprintf (message, "WriteFile failed : %d",
GetLastError ());
SYSLOG (message);
return (1);
}
if (BytesWritten != sizeof (o_auth_packet))
{
sprintf (message, "WriteFile wrote %d bytes instead of
%d", BytesWritten, sizeof(o_auth_packet));
SYSLOG (message);
return (1);
}
break;
case O_AUTH_DENIED:
404
VERSANT Database Fundamentals Manual
405
VERSANT Database Fundamentals Manual
406
VERSANT Database Fundamentals Manual
BytesRead, packet.len);
SYSLOG (message);
return (1);
}
/* verify the reply packet */
if (packet.len == strlen(PASSWORD) &&
!strncmp(buff, PASSWORD, strlen(PASSWORD)))
{
SYSLOG("Access granted");
packet.opcode = O_AUTH_GRANTED;
}
else
{
SYSLOG("Access denied");
packet.opcode = O_AUTH_DENIED;
}
/* write the authentication result to kernel */
packet.len = 0;
if (WriteFile (hOut, &packet, sizeof(o_auth_packet),
&BytesWritten, NULL) == 0)
{
sprintf (message, "WriteFile failed : %d", GetLastError ());
SYSLOG (message);
return (1);
}
if (BytesWritten != sizeof(o_auth_packet))
{
sprintf (message, "WriteFile wrote %d bytes instead of %d",
BytesWritten, sizeof(o_auth_packet));
SYSLOG (message);
return (1);
}
/* done */
SYSLOG("Done");
return 0;
}
407
VERSANT Database Fundamentals Manual
User Privileges
A privilege of a user is a right to access a database or objects in a database, or execute database utilities.
Please see User Management Utility (dbuser) for details on how to grant access privileges to a user to access
a database.
PUBLIC access
When the PUBLIC access is granted for a database, anyone can access the database. No user authentication is
done.
When this is true, the database is completely accessible to anyone. None of the above requirements need to
be satisfied in order to gain access entry into that database system. However it should be noted that setting
the database to public access, does not guarantee entry into the database system while running in single-
process mode. As stated earlier, only the DBA can access the database in single-process mode.
Access Modes
• Read-Only
• Read Write
These access privileges can be granted to a database user by the DBA while adding a user to the system. The
DBA always has read-write access privilege.
Following are VERSANT system utility access privileges for each database role. These privileges are the same
whether a system utility is executed from a command line or from the VERSANT Utility Tool. Also shown are
the utilities, which can be executed remotely.
dbsa dba dbuser Remote Execution
addvol no yes no yes
comparedb no yes no yes
compardb (pc) no yes no yes
convertdb no yes no no
cnvrtdb (pc) no yes no no
createdb no yes no yes
createreplica no yes no yes
creatrep (pc) no yes no yes
db2tty no yes yes yes
408
VERSANT Database Fundamentals Manual
409
VERSANT Database Fundamentals Manual
410
VERSANT Database Fundamentals Manual
411
VERSANT Database Fundamentals Manual
Overview
Current global economy requires global computing solutions. These global computing solutions should be in
harmony with users' cultural and linguistic rules. Users expect the software to run in their native language.
This overview will cover terms related to internationalization. The rest of the chapter will explain Versant
internationalization framework.
The VERSANT ODBMS product consists of the database management system as well as utilities and
administration tools. In the 6.0.0 Release, Versant has focused on allowing the Versant database to be used in
“internationalized” applications. Over subsequent releases, Versant will expand the scope to include other
tools and products.
Versant acknowledges the use of the IBM ICU library to implement the locale dependent string comparisons.
Globalization
The process of enabling a software product for global market is called globalization, which consists of two
distinct steps: internationalization and localization.
Internationalization
Internationalization is the process of designing software that can be adapted to various languages and regions
without changing executables. The term internationalization is also abbreviated as I18N, because there are 18
letters between the first "I" and the last "N".
Localization
Localization is the process of adapting an internationalized product to meet the requirements of one
particular language or region. Steps are provided to help you in localizing versant error messages.
412
VERSANT Database Fundamentals Manual
The first step in globalizing a product is to internationalize it. In this release Versant has internationalized
the product so that you can develop Internationalized application. It will also help in localizing it.
413
VERSANT Database Fundamentals Manual
The essence of how you develop Versant applications remains largely unchanged. For developing
"internationalized" applications, Versant release 6.0.5 offers the following additional features:
• Pass-through guarantee (that ensures "what you store, is what you get back")
The core features mentioned above are described in detail in the sub-sections that follow. The localization of
the error messages is described in the section Versant Localization.
/national
Versant can use users native language collating sequence when executing queries on string valued attributes.
It is achieved by using special virtual attribute for I18N, /national. (For details on virtual attributes please refer
414
VERSANT Database Fundamentals Manual
to the end of the chapter.) User should modify query attributes as follows
`/national LOCALE ENCODING attrname`
Valid examples :
`/national de_DE UTF8 first_name == "Andre" ` // valid
`/national en_US LATIN_1 last_name == "try" ` // valid
`/national BRIGHT LATIN_1 last_name == "lll" `// will be done on
en_US locale.
Invalid examples: //
`/national UTF8 de_DE first_name == "Paul"`
`/national de_DE bogus_encode last_name == "Yulin"`
If the locale specified by you does not exist then a default, en_US locale will be used. If the encoding
specified by you does not exist then an error will be returned.
You will have to specify a virtual attribute template to indicate encoding and collating sequence specifier.
415
VERSANT Database Fundamentals Manual
for “$locale_name”, “$encoding” will be used from “locale” profile.be parameter. “$attr” should
be used for specifying nested virtual attributes.
virtual_locale "/national de_DE utf8 $attr"
$attr - specifies that you can specify a single string attribute to an index.
Primary: a letter difference. For example, 'a' and 'b' are considered as different, but not ‘a’, ‘A’ and ‘à’.
Secondary: an accent difference. For example, 'a' and 'à' are considered as different, but not ‘a’ and ‘A’.
Tertiary: a case difference. For example, 'a' and 'A' are considered different.
(The difference between tertiary and identical is that some strings can be spelled with more than one different
sequence of numeric Unicode values. Tertiary strength will compare two variant representations of the same
string as equal, but identical strength will compare them as different).
From release 6.0.5, applications can specify the collation strengths per predicate term. Note that, this
collation strength will impact not only the behavior of operators O_MATCHES (like, in VQL) and
O_NOT_MATCHES (not_like, in VQL), but also on the equality operators O_EQ, O_NOT_EQ. For example in
a comparison at primary strength with operator O_EQ, strings with less equality as compared to the secondary
strength and O_EQ will be returned.
Examples
VQL
In VQL queries can be written to use I18N feature as under:
Suppose a class ‘Book’ has a attribute ‘Title’. There are instances of German and English books. A VQL query
on books with titles beginning with letter "U" might be:
int run_query()
{
d_VQL_query query("select OID from Book where title >= "U" and
title <= "V");
416
VERSANT Database Fundamentals Manual
d_oql_excute(query,structVstr);
...
}
But, the given query will not retrieve the titles beginning with "Ü". In order to achieve the required result, you
will have to modify the query as follows:
int run_query()
{
}
From 6.0.5 onwards the following locale specific pattern matching queries are allowed:
select * from BasicEmployee where `/national fr_FR utf8 Tertiary
BasicEmployee::emp_name` like ‘Frèd’
C++
In C++, PPredicate needs to be changed as follows:
PClassObject<Book>::Object().select(NULL,FALSE,
PPredicate(PAttribute("/national de_DE utf8 Book::title") > "U"
" &&
PAttribute("/national de_DE utf8 Book::title") ==
" "V" "));
RESULT: will include books beginning with "U" or "Ü" upto "V"
Application Support
Java
You can use Java application to insert data into the database. Ensure that you are using the same encoding as
specified in the profile.be to enter the data. Versant will ensure that data entered is not lost.
C++
You can use C++ application to insert data into the database. Ensure that you are using the same encoding as
specified in the profile.be to enter the data. Versant will ensure that data entered is not lost.
417
VERSANT Database Fundamentals Manual
Database Utilities
All the regular VERSANT utilities like vstream, vbackup etc. will work with internationalization.
Application Impact
Developer impact
To internationalize application a developer must be aware of
• virtual attribute - /national
• profile.be parameter locale
• Review queries and indices on string valued attributes.
• Display issues if any
Database Administrator (DBA) impact
Must know locale, encodings application needs and set the parameters accordingly. Once the data
has been loaded you cannot modify the locale.
Errors returned
Error Message
V_BAD_STRING_EXPANSION
String cannot be converted to Unicode due to incomplete data string.
V_BAD_LOCALE
Locale cannot be opened.
V_BAD_ENCODING
You have specified an invalid Encoding.
V_CANNOT_LOAD_COMPARE_ROUTINE
Could not load user defined compare routine or could not load libnational .so.
lib/error.msg file still exists for backward compatibility, if the error message file named by the
418
VERSANT Database Fundamentals Manual
In db directory:
• db/dbname/profile.be file contains database's national code set information.
Deployment Issues
As an application developer you need to ensure that your application (at client side) displays the data
correctly. VERSANT will not do any code conversion.
Appropriate VERSANT_LOCALE value needs to be set to get an error message in a particular locale. For more
details refer to the section on Versant Localization.
419
VERSANT Database Fundamentals Manual
Versant Localization
VERSANT_LOCALE
The environment variable will indicate if you want to choose a locale specific error messages or not. Default
error messages will be in Latin-1. For example:
If a you set VERSANT_LOCALE to "ja_JP" then all Versant error messages will be displayed in Japanese
language.
VERSANT interfaces and tools use a common mechanism called Standard Localization Facility to generate all
natural language messages, both error messages and non-error messages.
For example, progress messages, prompts, and names seen in graphical interfaces are all generated by the
Standard Localization Facility.
This text file is the source of all natural language messages for all interfaces and tools that use the
standard facility.
error.msi
This binary file is an index of the locations of the error messages in the error.msg file. The last letter of
that file has been changed to "i" to mean "index".
When this file exists and is accurate, message retrieval is accelerated. If it does not exist or is inaccurate,
420
VERSANT Database Fundamentals Manual
messages will still be retrieved, but slowly. Thus any time a change is made to error.msg, the
error.msi file should be rebuilt by running the verrindx command.
error.txt
The lines in the error.msg file are a subset of the lines in this file. The additional lines in this file
provide elaboration on the error messages.
421
VERSANT Database Fundamentals Manual
422
VERSANT Database Fundamentals Manual
1. Edit error.txt.
Edit the error.txt file, translating the English messages into the local language.
• The "%" substitutions must also be retained, in exactly the order they appear here.
For example, the following error message must be translated so that the attribute name is still substituted
first, and the class name is still substituted second, even if that is inconvenient in the local language:
8544, SCAP_NEWATTRAT: Cannot add new attribute %s to class %d
2. Build error.msg.
Build the error.msg file by copying the new error.txt file, but retaining only the lines that begin
with a digit.
In the above, for .../lib substitute the path to the VERSANT lib directory.
3. Run verrindx.
Run the VERSANT verrindx command. This should be done any time the error.msg file is altered.
423
VERSANT Database Fundamentals Manual
There are two types of lines in the error.txt file: primary lines and secondary lines.
Elements:
nnnn
Primary lines begin with a digit, which is the error number. The error number can be one or more digits,
0-9.
,
A comma delimiter.
space
A space delimiter.
error_name
The error name, which can be one or more characters. You can use any character except a colon.
:
A colon delimiter.
space
A space delimiter
message
Substitute a string
424
VERSANT Database Fundamentals Manual
%d
Secondary lines — Zero or more secondary lines follow each primary line and elaborate on the error in the
primary line.
Elements:
tab
The message can contain any characters except for escape characters.
425
VERSANT Database Fundamentals Manual
\n
These lines are not used consistently, and could be left out rather than translated.
error.msg
The syntax of the error.msg file is the same as the error.txt file, except that secondary lines are not allowed.
Following is a fragment from the error.txt file with the components described above.
Each primary line must be one long line in the file. For example, the lines beginning with 8052 and 8054 are
shown here as two lines, but in the file they must each be a single line.
The lines that do not start with a digit must begin with a tab character.
426
VERSANT Database Fundamentals Manual
In the error.msg file, all of the secondary lines in the above fragment would be left out and only the
following would remain (again, each element must be a single long line in the file):
427
VERSANT Database Fundamentals Manual
Debugging messages are not localized at the present time. Instead, they are compiled out of production
releases. In the future, debugging messages will be localized.
Shell Scripts
Some shell scripts do not use the Standard Localization Facility. Their messages are in the shell scripts,
and can be easily edited. Other shell scripts do use the Standard Facility. These scripts can be identified
because they call the undocumented command perrfilt.
German
Country Language
(German=de)
de_AT
Austria
de_DE
Germany
de_LU
Luxembourg
de_CH
Switzerland
French
Country Language
(French=fr)
fr_BE
Belgium
428
VERSANT Database Fundamentals Manual
fr_CA
Canada
fr_FR
France
fr_LU
Luxembourg
fr_CH
Switzerland
Chinese
Country Language
(Chinese=zh)
zh_CN
China
zh_HK
Hong Kong
zh_TW
Taiwan
Spanish
Country Language
(Spanish=es)
es_AR
Argentina
es_BO
Bolivia
es_CL
Chile
es_CO
Colombia
es_CR
Costa Rica
es_DO
Dominican Republic
es_EC
Ecuador
es_SV
El Salvador
es_GT
Guatemala
es_HN
Honduras
429
VERSANT Database Fundamentals Manual
es_MX
Mexico
es_NI
Nicaragua
es_PA
Panama
es_PY
Paraguay
es_PE
Peru
es_PR
Puerto Rico
es_ES
Spain
es_UY
Uruguay
es_VE
Venezuela
English
Country Language
(English=en)
en_AU
Australia
en_BE
Belgium
en_CA
Canada
en_IE
Ireland
en_NZ
New Zealand
en_ZA
South Africa
en_GB
United Kingdom
en_US
United States
430
VERSANT Database Fundamentals Manual
Versant will support locales and encodings supported by ICU 1.3.1. For more details go to
www.alphaworks.ibm.com.
If a wrong LOCALE is specified, a default locale, en_US will be used. For example: if you misspell a locale and
the corresponding locale does not exist , then en_US will be used
Restrictions
Versant internationalization has the following restrictions:
Class names and attribute names are not certified to be Unicode/multi-byte compliant.
Client-side environment variable VERSANT_LOCALE and Server-side variable, virtual_locale for Locale
does not indicate any code conversion between Client-side and Server-side.
431
VERSANT Database Fundamentals Manual
• vexport, vimport and vstream is pass-through certified for UTF-8, Latin-1 only.
• FTS, Event Notification in C++, Java are not be tested in lab-to-lab release
OS paths, file name sizes will not be increased. The length of these variables will remain the same. Some
of the Operating Systems do not allow non-ASCII file names.
For detailed listing of limits refer to "Names rules" section in VERSANT/C Manual.
Internationalization parameters like 'locale' that can be specified in the database server profile file (
profile.be) should not be modified, after the data has been loaded in the database. If you change the
profile parameters, the locale specific string comparisons may yield incorrect results as the data may be in
a different encoding.
Vstr<char> will be treated as Vstr<o_ulb>, that is [] operator will return nth byte rather than a
character.
Versant will support encodings, which do not use NULL as part of any character. For example: UTF-8,
Latin-1.
Java strings will be returned as ByteArray. It will not be converted to encoding as done currently.
A pattern matching query with accent character before the wildcard may not work correctly under the
432
VERSANT Database Fundamentals Manual
following conditions:
• Accent character before the wildcard in the pattern string
• There is a corresponding index
• Primary strength level
For example, the following query doesn’t return the object that contains “Frederic”
select selfoid from BasicEmployee where `/national fr_FR utf8 PRIMARY
BasicEmployee::emp_name` like 'fr?*'
The workaround for this problem is to avoid using the accent character in the pattern string:
select selfoid from BasicEmployee where `/national fr_FR utf8 PRIMARY
BasicEmployee::emp_name` like 'fre*'
Following is the Virtual Attribute grammar that uses an informal BNF notation.
SLASH “/”
BRACE-START “{“
BRACE-END “}”
433
VERSANT Database Fundamentals Manual
434
VERSANT Database Fundamentals Manual
Most applications use Versant ODBMS as an embedded database. The term "embedded" implies the database is
not visible to the user of the application directly. Furthermore, it also implies the typical administration of the
database can be done by the application transparently, without much involvement by DBAs. For the traditional
"telco" application, Versant ODBMS has been embedded in switching and network management systems. In more
recent applications, the trend is to use Versant ODBMS as a metadata or enterprise data cache for Java based e-biz
applications. The latter set of applications also tend to use Java based application servers.
The Versant Developer Suite 6.0 has been enhanced to provide support to embed the utilities within user
applications. This has been achieved in the following ways
435
VERSANT Database Fundamentals Manual
APIs
The following APIs (in C, C++ and Java) are available for use directly through application programs:
These behave with the same functionality of their utility counterparts. Please refer to the C / C++ / Java Versant
Manuals for a description of each of these interfaces.
Passing of parameters to these functions is to be achieved through the use of the "o_nvlist" structure (class
NameValueList in C++). Functions to manipulate the variables of these types are listed in the table below.
436
VERSANT Database Fundamentals Manual
o_deletenvlist() NameValueList::release()
o_firstnamefromnvlist() NameValueList::get_first_name()
o_freevaluefromnvlist() NameValueList::free()
o_getnameatcursorfromnvlist() NameValueList::get_current_name()
o_getnamecountfromnvlist() NameValueList::count()
o_getvalueatcursorfromnvlist() NameValueList::get_current_value()
o_getvaluefromnvlist() NameValueList::get_value()
o_nextnamefromnvlist() NameValueList::get_next_name()
o_nvlistlength() NameValueList::length()
o_removeatcursorfromnvlist() NameValueList::remove_current()
o_removefromnvlist() NameValueList::remove()
o_replacevalueatcursorinnvlist() NameValueList::replace_current()
o_replacevaluebyindexinnvlist() NameValueList::replace()
o_replacevaluebyvalueinnvlist() NameValueList::replace()
The "o_nvlist" structure hold numerous name-value pairs that is common when invoking command-line utilities.
makedb
The utility "makedb" accepts an option "-owner" which also requires a value. To invoke this utility from the
command-line, the following command is to be specified
makedb -owner vsntuser versantdb
To achieve the same through an application, the following piece of C code can be written:
o_nvlist makedbList;
o_createnvlist(&makedbList);
o_addtonvlist(makedbList, "-owner", "vsntuser");
o_makedb("versantdb", makedbList);
o_deletenvlist(makedbList);
The same functionality in C++ would be as follows:
NameValueList makedbList;
makedbList.add("-owner", "vsntuser");
::dom = new PDOM();
::dom->makedb("versantdb", makedbList);
o_writeprofile()
This API can be used to modify the contents of the back-end profile for the database. Normally, this is achieved by
editing the back-end profile file "profile.be".
437
VERSANT Database Fundamentals Manual
As an example, we want to add the following parameters to the back-end profile for the database "versantdb".
extent_size
8
heap_size
150
logging
on
polling_optimize
off
commit_flush
on
plogvol
4M physical.log 100
virtual_locale
"abc 123"
event_daemon
/opt/versant/nvlist/list "abc"
To achieve this through the application program, the following code snippet can be used:
o_nvlist prflList;
o_createnvlist(&prflbList);
o_addtonvlist(prflList, "extent_size", "8");
o_addtonvlist(prflList, "heap_size", "150");
o_addtonvlist(prflList, "logging", "on");
o_addtonvlist(prflList, "polling_optimize", "off");
o_addtonvlist(prflList, "commit_flush", "on");
o_addtonvlist(prflList, "plogvol", "4M physical.log 100");
o_addtonvlist(prflList, "virtual_locale", "\"abc 123\"");
o_addtonvlist(prflList, "event_daemon", "/opt/versant/nvlist/list \"abc\"");
o_writeprofile(O_BE_PROFILE, "versantdb", prflList);
createdb
The next task would be to invoke "createdb" invoked from the command line as:
createdb versantdb
This could be invoked from a C program as
o_createdb("versantdb", NULL);
Equivalent C++ program invocation:
::dom->createdb("versantdb");
vmovedb
vmovedb –threads 5 –C node employee person src_db dest_db
438
VERSANT Database Fundamentals Manual
sch2db
A schema file "schema.sch" is to be loaded into the database just created. The command line utility is invoked as:
sch2db –D versantdb –y schema.sch
To implement the same through the C program:
o_nvlist schdbList;
o_createnvlist(&schdbList);
o_addtonvlist(schdbList, "-D", "versantdb");
o_addtonvlist(schdbList, "y", NULL);
o_sch2db("schema.sch", schdbList);
o_deletenvlist(schdbList);
The C++ program would have the following statements:
NameValueList schdbList;
schdbList.add("-D", "versantdb");
schdbList.add("-y", NULL);
::dom->sch2db("schema.sch", schdbList);
439
VERSANT Database Fundamentals Manual
SS daemon enhancements
The Versant SS daemon is the Versant system services daemon that is invoked by "inetd" on Unix machines.
This daemon handles all initial requests from the client application. The "inetd" is configured to invoke this
daemon during Versant ODBMS installation. Therefore root priviledge is required to install Versant ODBMS
completely. This proves to be a hurdle to embed Versant in an application.
In the Versant Developer Suite 6.0 new functionality has been added to the SS daemon. The daemon can
now be executed from the command line (and hence does not require root priviledges). Please refer to the
"SS daemon" chapter for details.
440
VERSANT Database Fundamentals Manual
441
VERSANT Database Fundamentals Manual
442
VERSANT Database Fundamentals Manual
Restrictions.............................................................................................................................. 523
443
VERSANT Database Fundamentals Manual
Search Queries
Concepts
Query definition — A "query" examines a group of objects and returns those that satisfy a search condition.
Query types — The general term "query" conceptually refers both to routines that evaluate object values
("find") and routines that evaluate object identities using links ("get").
find Functions and methods that use search conditions to find objects and return links
are called "search" or "find" routines. The term "query" is often used to refer to just
"find" routines.
get Functions and methods that use links to bring objects in memory are called
"dereference," "retrieval," or "get" routines.
VERSANT evaluates "find" queries on the server machine containing the database to be searched and then
sends links to the found objects to the application.
VERSANT evaluates "get" queries on the application machine and then retrieves the desired objects from any
connected database.
Because of the client/server architecture for search queries, objects to be found by a "find" query must be
located in a single database. However, you can send the same search query to numerous databases, store the
object references (links) to objects in multiple databases in a collection, and then use a dereference method to
retrieve them as desired.
444
VERSANT Database Fundamentals Manual
Mechanisms
Find Object — The following functions find objects by using a search condition.
o_defineevent() define event to find objects
o_fetchcursor() find objects with cursor
o_findarchivedobj() find objects in an archive
o_pathpcheckout() find objects and check it out
o_pathselect() find objects of a class or vstr
o_pathselectcursor() find objects with cursor
o_pcheckout() find objects and checkout
o_select() find objects of a class
Logical database — If you want to perform a query in parallel over a number of databases, you can use the
following functions to create and manage a logical database:
o_addtologicaldb() Add a database to a logical database.
o_deletelogicaldb() Delete a logical database.
o_newlogicaldb() Create a logical database.
o_removefromlogicaldb() Remove a database from a logical database.
Get Object — The following functions use handles, links, or pointers to retrieve objects. In a sense, they are
not query functions, but rather they are "get" functions.
o_gcheckout() Get and check out a group of objects
o_getclosure() Get a group of objects
o_greadobjs() Get a group of objects
o_locateclass() Get a class object
o_locateobj() Get an object
o_locatevsn() Get an object version
o_refreshobj() Get a fresh object
o_refreshobjs() Get fresh objects
The remainder of this chapter is concerned only with functions that perform a search.
445
VERSANT Database Fundamentals Manual
Structure
Parameters — The functions that search for objects require specifications of various parameters. For example,
the syntax of o_select() is:
o_vstr o_select(
o_clsname classname,
o_dbname dbname,
o_bool iflag,
o_lockmode lockmode,
o_vstr predicate );
Return value — A query returns links to objects that satisfy the query: the objects themselves are not passed
from the server database to the client application. To actually use a returned object, you must use a link to
bring it into memory with a "get" function, such as o_locateobj() or o_greadobj().
Starting point objects — A query evaluates a group of objects known as the "starting point objects." The
starting point objects may be a class of objects, a class and its subclasses, or a specific group of objects. After
evaluating the starting point objects, a query can expand outwards to examine attribute values in embedded
objects and linked objects.
In the above, the starting point objects are specified by the following parameters.
classname Name of a class.
Other functions, such as o_pathselect(), allow you to query over a
specific group of objects.
dbname Name of a database.
VERSANT evaluates "find" queries on the server machine containing
the database to be searched, so the starting point objects must be
located in a single database. However, you can send the same search
query to numerous databases, store the returned links in a vstr, and
then use a "get" function to retrieve them as desired. ("Get" functions
are evaluated on a client machine and can fetch objects from any
connected database.)
iflag Inheritance specifier: specify TRUE to evaluate and return instances of
both the class and its subclasses or specify FALSE to evaluate and
return only instances of the class.
Lock mode — By definition, a select operation returns a vstr of links to the objects that satisfy the query
conditions. In the above, the lock that you specify in the lockmode parameter will be placed on the class
object for the returned objects.
In a query, you will usually want to set an intention read lock on a class, which prevents the definition of the
class from changing while still allowing other users to use instances of the class in a normal manner. For
446
VERSANT Database Fundamentals Manual
information on locks and how they are applied to class objects, see the chapter "Locks".
Predicate — A predicate is a vstr of pointers to search conditions called "predicate terms". To express how
predicate terms are to be collectively applied, they are concatenated into a predicate using logical, boolean
operators. Allowed boolean operators are logical AND, OR, and NOT.
The real action of a find function happens in the predicate term. The remainder of this section discusses
predicates and predicate terms.
Predicate Term
In C/VERSANT, predicate terms are structures of type o_predterm. The syntax of the o_predterm data type
is:
typedef struct o_predterm {
o_attrname attribute;
o_opertype oper;
o_bufdesc key;
o_typetype keytype;
o_u4b flags;
} o_predterm;
Query evaluation attribute (the attribute parameter) — The "evaluation attribute" is the attribute whose
value is to be examined. Evaluation attributes must be specified in a precise, non-ambiguous way that is
understandable to a database.
Query comparison operator (the oper parameter) — A "comparison operator" determines how an attribute
value is compared to a key value. A comparison operator can be a relational operator for comparing scalar
values, a pattern matching operator for comparing strings, an "is-a" operator for determining class
membership, or a set operator.
Query key value (the key and keytype parameters) — A "key value" is the value to which a specified
attribute is compared. A key value can be a link, links, or a value for any type of attribute allowed in a query.
Query logical path statement (the flags parameter) — A "logical path statement" determines how to handle
cases where leaf attributes (the attribute being evaluated) are null or where there are multiple leaf attributes
for a particular starting point object. By default, a starting point object is returned only if a valid leaf attribute is
found and the value found in the leaf attribute satisfies the specified search condition.
447
VERSANT Database Fundamentals Manual
Predicates — Predicate terms can be used alone as a predicate or concatenated to form a multi-part predicate.
448
VERSANT Database Fundamentals Manual
The following discusses predicate term attributes, comparison operators, and path statements in detail.
The following explains the attribute types allowed and how to specify an evaluation attribute.
449
VERSANT Database Fundamentals Manual
Many fixed arrays of one byte elemental types. fixed array of char
For example: char firstname[20] fixed array of o_1b
fixed array of o_u1b
vstr of o_object
Vstrs containing links.
Do not use enum You should not use enum, as it is not portable: some machines
implement enum as o_2b, while others implement enum as o_4b.
Do not use other types of Do not use fixed arrays of types other than char, o_1b, o_u1b,
fixed arrays and links. For example, you cannot use o_4b pixel[5].
Do not use other types of Do not use vstrs of types other than char, o_1b, o_u1b, and
vstrs. o_object.
Do not use an embedded Do not use an embedded class or struct with a fixed length that
class or struct with a fixed embeds another fixed array, class or struct.
length
Do not use vstrs of class or Do not use vstrs of class or struct types. For example, you cannot
struct use Vstr<Employee>.
450
VERSANT Database Fundamentals Manual
Precise attribute names are needed, because a derived class could conceivably inherit attributes with the
same name from numerous superclasses along one or more inheritance paths. Even if there is no initial
ambiguity, classes may later be created which do cause ambiguity.
Following are attribute name rules required by VERSANT for each type of attribute that may be used to query
or index.
451
VERSANT Database Fundamentals Manual
452
VERSANT Database Fundamentals Manual
struct Base {
int id;
int code;
};
Then the database attribute names are:
Date::day
Date::b.Base::id
Date::b.Base::code
In ODMG, this is called a "path expression." By definition, a "patch expression" specifies a path through
several interrelated objects to access a destination object.
The general syntax for a path expression is a listing of the attribute names on the path to the attribute to be
examined, with the names on the path delimited with \t.
For example, suppose that a class Person has a links attribute garages whose domain is class Garage.
Suppose also that class Garage also has a links attribute cars whose domain is class Car, and that class Car
has an attribute colors.
Now suppose that we want to search over the Person class for people with cars of a certain color. Then, to
specify the attribute colors in a predicate, we can specify it with the path from Person to colors using \t:
Person::garages\tGarage::cars\tCar::colors
The last name in the attribute path, in this case colors, is called the "leaf name." The intermediate names, in
this case garages and cars, are called "non-leaf" names.
453
VERSANT Database Fundamentals Manual
• The attribute path can include any number of steps to the final attribute.
• The starting point objects can be either the objects in a class or the objects in a link vstr or link array.
• Only starting point objects can be returned from a query using paths. Objects encountered along the
path cannot be returned.
• Once the starting point objects have been returned, you can also return objects linked to the starting
point objects to a specified level of closure. The following of links to a particular level of closure is
unrelated to the following of a path to a leaf attribute.
Comparison Operators
A "comparison operator" determines how an attribute value is compared to a key value. A comparison
operator can be a relational operator for comparing scalar values, a pattern matching operator for comparing
strings, an "is-a" operator for determining class membership, or a set operator for comparing sets.
Relational operators
Compare scalar values —
Evaluation attribute a scalar value
Key attribute a scalar value
String operators
Compare strings —
Evaluation attribute a string
Key attribute a string, possibly containing wildcards
454
VERSANT Database Fundamentals Manual
Wildcards Match
* Any sequence, including an empty sequence.
? Any single character
[chars] Any character in set.
[x-y] Any character between x and y, inclusive.
\x Single character x (avoid wildcard interpretation of * ? [ ] in a
pattern)
Class operators
Compare class memberships —
These operators test whether an object is an instance of a given class, but they do not test
whether the object is an instance of a subclass.
Evaluation attribute an instance object
Key attribute class name of a class object
455
VERSANT Database Fundamentals Manual
Set operators
Compare sets of objects —
Usage Notes
• There is no restriction on the number of links in the key attribute, except available heap space
on both the client and server machines at the time the query is executed.
456
VERSANT Database Fundamentals Manual
• To improve the performance of a query involving links, you can create a btree index on an
attribute containing links.
A query with path expressions uses a Depth-First-Fetch approach to evaluate each path
expression. Its performance can becomes an issue for a large starting class with complex
relationship among classes along the navigation path as it requires an exhaustive traversal
defined on the links from the given path expressions.
Example
For the following example, assume that we have the following three classes and attributes:
Classes Attributes
Broker dealers(linkvstr)
Dealer vehicles(linkvstr)
Vehicle color, make
Each broker works for some dealers. Each dealer has certain inventory of vehicles. Each vehicle
has attributes of color and make.
Assume the following objects in these classes in a database:
Suppose that we want to select those brokers who sell a "red Honda".
At first, it seems as if this request can be described in the VERSANT Query Language as:
SELECT selfoid
FROM Broker
WHERE dealers->vehicles->color = "red" AND
dealers->vehicles->make = "Honda"
But the above query returns a broker who does not sell a red Honda:
s1 This broker does not sell a red Honda, but is returned because she sells a red
Ford and a blue Honda ("a red car" and a "Honda").
s2 This broker does sell a red Honda.
s4 This broker does sell a red Honda.
To correctly get only brokers who sell red Hondas can become quite intricate if no set operators
are used, but we can easily construct a sequence of queries that will return the expected
457
VERSANT Database Fundamentals Manual
Here is one way to get only brokers who sell red Hondas, using reverse traversal and reverse joins:
3. Finally, get the brokers who represent dealers with red Hondas.
SELECT selfoid
FROM Broker
WHERE dealers INTERSECT Dealers_red_Honda
458
VERSANT Database Fundamentals Manual
Logical Paths
When you specify an attribute path, the number of object paths searched using intermediate arrays or vstrs of
links can expand to any number. This means that a search with numerous link arrays as intermediate steps can
involve a large number of paths and a large number of objects.
A particular path is said to be "complete" when all of the following are true:
• The path can be followed all the way from a starting point object to an evaluation attribute.
• The data type of the evaluation attribute is such that it can be compared with the key value
specified in the predicate.
• The comparison operator specified is appropriate for comparing the data types of the evaluation
attribute and the key value.
There are many reasons a particular path can be "incomplete." For example, the following things can go wrong
when a query attempts to follow a particular actual object path.
• A link in a non-leaf attribute may be NULL.
• A link may point to an object in another database.
• A link may point to an object that has been deleted.
• A leaf or non-leaf object might not have the attribute named.
• The operator may not be valid for the leaf attribute.
• The attribute may not be the same type as the key value.
Logical path statements are a way of being more precise about search conditions. When you consider paths, a
query can represent both a value test and a path test.
A query's value test occurs in its predicate. A predicate is said to be "true" if an attribute has a value that meets
the search condition expressed by the comparison operator and the key value. For example, suppose that
Henry has a red Porsche. If you ask for all persons with a red Porsche ("Porsche = red"), then for Henry the
predicate evaluates true.
A query's path test occurs in its logical path statement. Most of the time you can ignore the path test, but
when you are phrasing some kinds of queries, you may need to consider paths. For example, if you have no
Porsches, are all of your Porsches red? In this case, the answer depends upon your logical path statement.
459
VERSANT Database Fundamentals Manual
VERSANT allows you to specify what happens when paths are complete or incomplete. The following
principles form the basis of the logical evaluation of paths:
• Incomplete paths are ignored.
• If complete paths = 0, then you get the starting point object only if you also ask for "empty succeeds."
• If complete paths = 1, then you get the starting point object only if your predicate is true.
• If complete paths > 1, then, by default, you get the starting point object if at least one predicate is true.
• If complete paths > 1 and you also ask that "all paths" be evaluated, then you get the starting point object
only if all predicates are evaluated as true.
• If recursion has been turned off, then the first attribute in the attribute path must exist on the
class which is selected on, or else the query returns an error code regardless of option selected.
If the first attribute in the path exists, then the remainder of the query is performed per the
460
VERSANT Database Fundamentals Manual
option selected.
• If there is only one attribute name in the attribute path, then in all cases the comparison operator
must apply to the data type found there, or else the query returns an error code.
Example
For example, consider the example of persons who have garages which have cars which have colors. In this
example, suppose that a person can be associated with more than one garage, and that a garage can be
associated with more than one car.
461
VERSANT Database Fundamentals Manual
If you evaluate for not red (!= "red"), the results are:
462
VERSANT Database Fundamentals Manual
Test — There is a least one complete path, and all complete paths are not red.
Path condition = O_ALL_PATHS
Results:
Sue Not returned All complete paths are red.
Rob Not returned One path to red.
Jan Returned Two complete paths, and all complete paths are not red.
Ned Not returned There is no complete path.
Test — There is at least one path to not red OR all paths are not complete.
Path condition = O_EMPTY_TRUE
Results:
Sue Not returned Two complete paths, but no path to not red.
Rob Returned One complete path to not red.
Jan Returned One complete path to not red.
Ned Returned All paths are not complete.
Test — All complete paths are not red OR there are no complete paths.
Path condition = O_ALL_PATHS | O_EMPTY_TRUE
Results:
Sue Not returned One complete path is red.
Rob Not returned One complete path is red.
Jan Returned All complete paths are not red.
Ned Returned There are no complete paths.
463
VERSANT Database Fundamentals Manual
Concepts
Index purpose
Indexes allow routines that query, create, and update objects to have quick access to the values of a
particular attribute of a class.
Query routines can use indexes to filter objects so that only objects of interest within a specified range
are fetched from disk. This can improve query performance in many situations.
Create and update routines can use indexes to enforce uniqueness constraints on attribute values.
Index structure
An index is set on a single attribute of a class and affects all instances of the class. It is a list of pairs
consisting of key values and associated object identifiers.
There are two kinds of index structures: b-tree and hash table. Both types of structures maintain a
separate storage area containing attribute values, only their organization is different. The type of
structure that you want to use depends upon the types of queries you will be making.
Index constraints
There are two kinds of index constraints: "normal" and "unique." A "normal" index places no constraints
on attribute values. A "unique" index requires that each instance of a class has a value in the indexed
attribute that is unique with respect to the values in that attribute in all other instances of the class.
If you want to create a unique index, you can use either a b-tree or hash table structure. The only
difference is that when you create the index, you specify that you want unique values.
General Rules
Indexes do not have names.
Once created, indexes are maintained automatically, which means that they may somewhat slow the
creation, updating, and deletion of instances.
464
VERSANT Database Fundamentals Manual
The actual creation of an index does not occur until the current short transaction is committed.
An attribute can have up to two indexes, a b-tree index (normal or unique) and a hash table index (normal
or unique).
When you migrate an object to a database in which its class is not defined, the class definition, index
definition, and index behavior are also migrated.
When you migrate an object to a database in which its class is already defined, the migration will fail if the
target database does not have the same class definition, index definition, and index behavior as the
source database.
When you create an index, that index is created on an attribute of a class and not on an attribute in any
other class. In other words, indexes are not inherited. To index subclass attributes, you need to
specifically set indexes on each subclass.
This is important to remember if you are using multiple inheritance and/or are querying over members of
a class and its subclasses. The general caution is that if you use multiple inheritance but treat inheriting
classes separately, then you may get anomalies.
465
VERSANT Database Fundamentals Manual
In the above, class Person has an id attribute that is inherited by several subclasses. If you set an index
on id in Person and then perform a query on Employee, then the index on Person is not used.
Similarly, if you set a unique index on id in Employee, then when you create a new Developer, a
uniqueness check on id is not made.
Because indexes are set only on one attribute in one class, you should set corresponding indexes on all
classes that have an inheritance relationship. For example, in the above, if you set an index on Person,
then set the same index on Customer, Employee, Developer, and Intern.
Cursor queries use indexes in the same way that other queries do.
Attribute Rules
Attributes that Can be Indexed
You can index on the following types of attributes:
• Elementary types with a repeat count of 1: char, o_u1b, o_1b, o_u2b, o_2b, o_u4b, o_4b,
o_u8b, o_8b, o_float, o_double, enum, o_object
Note — A unique BTree index cannot be created on o_object data types. You can set a unique
BTree index on the C++ link and link vstr types so that arrays of links can be used for query
operations on sets of objects.
466
VERSANT Database Fundamentals Manual
• The following elementary types with a repeat count: char, o_u1b, o_1b, and o_object.
For example: char firstname[20]
• Vstrs of the following elemental types: o_1b, o_u1b, char, and o_object.
Mechanisms
Index Functions
o_createindex() Create a b-tree or hash index, either normal or unique.
o_deleteindex() Delete a b-tree or hash index, either normal or unique.
Index Utility
467
VERSANT Database Fundamentals Manual
Index Options
In routines that create and delete indexes, following are options for the type of index.
O_IT_BTREE b-tree
O_IT_UNIQUE_BTREE unique b-tree
O_IT_HASH hash
O_IT_UNIQUE_HASH unique hash
Query Costs
One of the costs of a query is fetching data pages from disk so that the server process can evaluate objects.
Once objects are in memory, applying the predicate happens quickly.
If you use no indexes, a query will fetch all data pages for a class and evaluate all objects in a single pass
through the data pages. This approach optimizes the disk fetch and is appropriate if you want to return a
relatively large proportion of the instances of a class.
If you have a large number of objects and want to return, say, a quarter or less of all instances, then an index
can improve query performance. The tradeoff is improved query speed versus index overhead, as indexes are
automatically updated when objects are added, changed, or dropped.
An index might logically be set on attributes related to domains, subclasses, and links for queries that evaluate
substructures. Alternately, they may be set on a first-level attribute used frequently as a search condition or
on logical object identifiers.
A b-tree index is useful for value-range comparisons, and a hash index is useful for direct comparisons of
values.
A b-tree index will return objects in ascending order; to access objects in descending order, access the
returned vstr or array from its end rather than beginning. However, if a query evaluates a class and its
subclasses, objects are returned in ascending order in each subclass, which means that the set of all objects
are not necessarily in ascending order.
If you will only be making equality comparisons, generally a hash table is better; otherwise, use a b-tree index.
468
VERSANT Database Fundamentals Manual
The following types of comparison operators can use the following types of indexes.
O_EQ Scalar equal to Can use either a hash or b-tree index.
If there are both kinds of indexes on
the evaluation operator, the hash
index will be used.
O_NE Scalar not equal to Does not use an index.
O_GT, O_GE, O_LT, Scalar relative to Can use a b-tree index.
O_LE
O_MATCHES String equal to Can use a b-tree index, if the key
string to be compared does not start
with "*" or "?".
O_NOT_MATCHES String not equal to Does not use an index.
O_INTERSECT Set intersection Can use a b-tree index.
O_SUPERSET_OF Set superset Can use a b-tree index.
O_EQUIVALENT_SET Set equal to Can use a b-tree index.
O_NOT_INTERSECT Set not intersection Does not use an index.
O_SUBSET_OF Set subset Does not use an index.
O_NOT_SUPERSET_OF Set not superset Does not use an index.
O_NOT_SUBSET_OF Set not subset Does not use an index.
O_NOT_EQUIVALENT_SET Set not equal to Does not use an index.
O_ISA_EXACT Class is same as Not relevant.
O_NOT_ISA_EXACT Class is not same as Not relevant.
469
VERSANT Database Fundamentals Manual
A query with an exact match predicate will traverse a b-tree index on an attribute starting from the root
page down through interior node pages, if any, to a leaf page with an entry for the key value.
If the key value cannot be located, then no object satisfies the predicate, and the query is finished.
If an entry for the key value is found, then the object is retrieved into server memory from the
corresponding data page whose location is stored inside the entry for the key value. If there are other
predicate terms, the retrieved object is further evaluated and returned to the application if it satisfies all
predicate terms.
If the index is a unique index, the query is finished. If the index is not unique, then all objects with the key
value are retrieved, evaluated, and returned if they satisfy all predicate terms.
Range predicate
By definition, a range predicate has the form (attribute >= lower_bound and
attribute <= upper_bound). An exact match predicate is a special form of a range predicate where
the lower_bound is the same as the upper_bound.
Like the exact match case, a query with a range predicate traverses a b-tree index searching for an entry
on a leaf page whose key value is greater than or equal to the lower bound. As in the exact match case, as
each entry that matches the range predicate is found, its corresponding object is retrieved, further
evaluated, and returned if it satisfies all predicate terms. If the last entry of a leaf page is processed and
the upper bound has not been reached, the query follows the next pointer of the doubly linked entries to
the right-hand neighbor and resumes sequential processing with the first entry. This continues until
either the upper boundary, u, is reached, or the last leaf page has been processed.
470
VERSANT Database Fundamentals Manual
If the operators O_INTERSECT, O_SUPERSET, or O_EQUIVALENT_SETS are used, a B-tree index can be
created on the attribute. The B-tree index can then be used to drive the predicate evaluation. Instead of
scanning the entire class, the targets to be examined can be reduced by searching the B-tree index from
key_links, reducing the time and resource costs because the number of links in key_links is generally
much smaller than the number of links in the class.
When a query traverses a B-tree index, frequently referenced pages, such as the root page and its directly
descendent interior node pages, are likely to remain in the buffer cache. As a result, the cost of b-tree
overhead for a query includes a couple of node pages down the traversed path, and a list of leaf pages
covered by the key values.
Suppose that your predicate consists of a single predicate term, and that it is indexable.
In this case, index pages are brought into memory and read sequentially on its leaf pages. If an index
value satisfies the predicate term, then the data page for the corresponding object is fetched.
In this case, the query terms will be read from left to right.
If none of the terms are indexable, then all objects will be fetched and evaluated in a single pass through
471
VERSANT Database Fundamentals Manual
If there is only one indexable term, then the index pages are brought into memory and read sequentially
from its leaf pages. If an index value satisfies the indexable term, then the data page for the
corresponding object is fetched, and the object is evaluated further using the rest of the predicate terms.
If there are multiple indexable terms, then the terms will be evaluated to determine which one to use. If
any of the indexable terms uses the equals operator, then the first term using the equals operator,
reading from left to right, is used. If none of the indexable terms use the equals operator, then the first
term found, reading from left to right, will be used. (The reasoning for preferring the equals operator is
that it will likely produce fewer objects for evaluation.)
Once an indexable term is chosen, index pages for that term will be brought into memory and read
sequentially. If an index value satisfies the predicate term, then the data page for the corresponding
object is fetched, and the object is evaluated further.
Knowing that access paths are chosen with left-to-right, equality-preference logic, you can phrase your
query to use the indexable term that you want. For example, suppose that you have an indexable term
with an equality operator, such as (sex=='M'). To avoid having it used as the access path, you can
rewrite this term as a range predicate, such as (sex >= 'M' and sex <= 'M'), and then move it to the
right end of your predicate.
If your query uses terms concatenated with the OR operator, then what happens is more complex. First
your query is converted to disjunctive normal form, which means that mathematical conversions are done
so that the query is expressed in a left-to-right format such as:
( A and B and C ) or ( D and E ) or ( F ) ...
where A, B, C... are "terms" and groups like (A and B and C) are called "orterms".
If you write your query in this form to begin with, its components will be evaluted left-to-right per the
following discussion. If your query has to be converted to disjunctive normal form, DeMorgan's rules and
the distributive rule of boolean algebras will be applied, but the basic left-to-right ordering of elements in
your query is preserved as much as possible.
If any orterm lacks an indexable term, then the entire class extent will be walked, one object at a time,
evaluating the query on each object (with shortcut optimization when an orterm is true or a term inside an
orterm is false). In this case, all objects are traversed, because if all objects need to be evaluated, it is
faster to do it in one pass.
If each orterm has at least one indexable term, then the entire query is indexable, and an index is used to
traverse each orterm in succession. This means that objects in each orterm are returned in ascending
order with respect to the index attribute, and that all objects in the result set are not necessarily sorted in
472
VERSANT Database Fundamentals Manual
a particular order.
Within each orterm, there may be multiple indexable terms concatenated with AND. If so, the terms are
evaluated to determine which one to use. This evaluation occurs as described above for a query with
terms concatenated with AND. In each index traversal of each orterm, data pages for objects whose
indexes satisfy the query are fetched for further evaluation.
Although the automatic index choices are made in a way that optimizes most queries, you can control the
choice by phrasing your query so that your first choice of index appears in the left-most term of each
"orterm." If you have a range expressed in two terms, such as x>=42 and x<=54, put these two terms on
the left.
Knowing these behaviors, you can customize your indexes and queries to your needs. To evaluate the impact
of various query strategies, you can monitor disk activity with performance monitoring statistics. For further
information on statistics, see the chapter "Statistics Collection."
A b-tree index will be used if the predicate term contains operators >, >=, <, <=, ==, or O_MATCHES. A b-tree
index will not be used if the predicate term contains != or O_NOT_MATCHES or if the predicate term contains a
set operator with the prefix O_NOT_.
If there are multiple predicate terms concatenated with AND, the leftmost predicate term with a b-tree index
determines the ordering, except when an index is applied to an equality operator. (See the section "Query
Usage of Indexes" for a complete discussion.)
Because indexes are set and evaluated on a class basis, if a query traverses subclasses, then each subclass will
be a separately sorted sub-set.
When you change the definition of a class, instances are not updated to the new definition as they are
accessed. This prevents paralyzing a database whenever a schema change is made. If a class definition has
been changed but all instances have not yet been evolved to the new definition, then a query will return the
evolved and non-evolved sets as separate sorted subsets. Once all instances have been evolved, queries will
return a single set of sorted objects. (One way to evolve instances is to mark them dirty and then perform a
commit.)
The following examples illustrate how b-tree indexes sort query terms. In the following, the notation (btree)
indicates that a b-tree index has been set on the attribute used in the query term.
predterm_A(btree)
473
VERSANT Database Fundamentals Manual
Results will be sorted into 2 sets, one on predterm_A and one on predterm_C.
predterm_A(btree) O_AND predterm_B O_OR predterm_C O_AND predterm_D(btree)
Results will be sorted into 2 sets, one on predterm_A and one on predterm_D.
predterm_A(btree) O_AND predterm_B O_OR
predterm_C(btree) O_AND predterm_D(btree)
Results will be sorted into 2 sets, one on predterm_A and one on predterm_C.
predterm_A(btree) O_AND predterm_B(btree) O_OR predterm_C O_AND predterm_D
Results will not be sorted, because the non-indexed second OR term requires a full pass of the class
instances, therefore the entire predicate can be efficiently resolved in this single pass.
predterm_A(btree) O_AND predterm_B O_OR predterm_A(btree) O_AND predterm_D
Results will not be sorted, because using the index on both OR terms might result in duplicates in the
result set. A workaround is to perform two queries and then eliminate duplicates.
474
VERSANT Database Fundamentals Manual
Following are usage notes related to unique indexes. For these notes, suppose that you have the following
schema.
If a class has duplicate values in an attribute and you try to create a unique index on that attribute, you will
get the error SM_E_DUPLICATEKEY and the index will not be created.
Once an attribute has an index of a particular kind (either B-tree or hash), you cannot change its
uniqueness. If you try to do so, you will get the error SCH_INDEX_CONFLICT. Instead, you must delete
the existing index and then create a new one. For example, if an attribute has a b-tree index and you try
to create a unique b-tree index, you will get an error.
If you place both a hash index and a btree index on an attribute, you should make sure both are unique or
non-unique (a mixture would make no sense, anyway.)
475
VERSANT Database Fundamentals Manual
When using unique indexes, it is especially important to remember that indexes are not inherited.
In the above, class Person has an id attribute that is inherited by several subclasses. If you set a unique
index on id in Employee, then when you create a new Developer, a uniqueness check on id is not
made.
If a unique index has been set, the uniqueness of an attribute will be checked by the database server
process as soon as a new value is sent to it. Values will be sent when you perform a commit, checkpoint
commit, query, or group write. They will also be sent if VERSANT needs to swap an object, and the
object is not pinned. If a duplicate value is found, you will get the error SM_E_SAMEKEYFOUND.
If you decide to delay the cost of checking for uniqueness until a commit, you will get better
performance, but you may have problems with exeception handling since you will not know which object
caused the exception.
If you explicitly write each new and changed object at the time they are created or changed, your
performance will be slower but exceptions will be easier to handle.
If you want to test whether a given value exists for an attribute without waiting until a commit, you can
query the class using a predicate that compares the attribute with the test value. If you have set a unique
index, this query will be very fast, and an empty result will probably mean that your test value will be a
unique value. However, this test is not infallible, because another user could conceivably add a duplicate
value during the time between your test query and your commit. Therefore, if you are using a unique
index, you should always have an exception handler for a duplicate value error.
For example, in the above, before creating a new instance of Developer, perform a query on the id
attribute of Person that specifies a search of its subclasses. This query will return a value only if a
conflicting attribute value is found.
If a large number of classes and/or instances are involved, you might want to create a separate class
whose only purpose is to keep track of unique values in a particular attribute.
When you create or update objects with unique indexes, remember that a group write, query, or object
swap will return an object to its database while still leaving it subject to a roll back. This can cause a
problem if you rollback your transaction and another user has created or updated an object with a value
that would conflict with the roll back value.
476
VERSANT Database Fundamentals Manual
For example, if you want to check for uniqueness with a group value, you may want to either reserve your
rollback value or set a lock on the entire class.
For example, suppose a class has an attribute "a", and a unique index has been defined on it. The
following sequence of events will cause a problem.
In the above case, an exception was raised, because a second user had a window of time in which to
create an object with an attribute value that conflicted with the roll back value of an object written by user
1.
A similar problem might arise if Application 1 had unpinned obj1 (which allowed it to be flushed to its
database) or had performed a query on its class (which flushes new and dirty objects before operating.)
To avoid rollback problems when using objects with unique attributes, you can do any of the following
things:
• Do not unpin, query, or group write objects in high concurrency situations. However, as mentioned
above, this will make your exception handling harder, because you will learn of uniqueness conflicts
only at commit time.
• Set a read lock on the class object, which will prevent other users from making any changes at all.
477
VERSANT Database Fundamentals Manual
However, this may not be feasible, as it would cause serious concurrency problems in a multiple user
environment.
The following repeats the above example, except in this case Application 1 protects a rollback value with
a dummy object.
You should not create a unique index on a time stamp attribute used for optimistic locking, since it is
likely that there will be duplicate values for that attribute. In other words, it is not a good idea to overload
an attribute to play dual roles for both unique constraint and optimistic locking. However, VERSANT does
not restrict such a practice.
478
VERSANT Database Fundamentals Manual
Because of the ability to create indexes, there are many advantages of using the strategy of reverse traversal
and reverse joins to execute a path query. Each stage works like regular query. Then indexes, if any, can be
used in each stage. In consequence, the selectivity in general is lower than the normal path query. Thus, one
can expect a better query performance. However, you should also consider a test of network traffic from each
stage as the result needs to be sent back to the client.
For example, consider again the example of brokers who represent dealers who have vehicles that was
presented in the section "Set Queries". In this example, we have the following three classes and attributes:
Classes Attributes
Broker dealers(linkvstr)
Dealer vehicles(linkvstr)
Vehicle color, make
Each broker works for some dealers. Each dealer has certain inventory of vehicles. Each vehicle has attributes
of color and make. We assumed the following objects in these classes in a database:
In this example, one of the predicates was "vehicles INTERSECT Vehicles_red_Honda". Since vehicle is
a vstr of links, without index support, this would be an expensive operation as the predicate needs to compare
each dealer's vehicles with the given linkvstr of "red Honda". Because it compares two variable length of
links, even one comparison may be costly, not to mention repeating it for each dealer.
479
VERSANT Database Fundamentals Manual
However, if we create an index on the link vstr attribute, the cost can be much lower. For example, an index
on vehicles on Dealer will have this content among others:
This is a normal B-tree index with a new meaning, because it is constructed with a special method. For
example, a dealer such as d1:{v1, v4} produces two entries to the B-tree index:
In other words, the index stores the reverse links from vehicles to dealers given a dealer class. From this
index, we can easily find all the dealers who have a specific vehicle in their inventory. For example, given
Vehicles_red_Honda={v5, v6}, we can quickly locate two entries, and return {d3, d2} as the answer.
Note that this requires two iterations: the first iteration is for vehicle v5, and the next iteration is for v6. Each
iteration will work the same way as the normal speedy searching capability from a B-tree index. By contrast, if
there is no B-tree index defined on links_attr and a predicate has an O_INTERSECT operator, then each
instance under the class is visited once and each link under links_attr has to compare with those under
links_value until a match or exhaustion. In most cases, this would be very expensive.
480
VERSANT Database Fundamentals Manual
Cursor Queries
Concepts
Cursors allow you to perform a query in stages
When you perform a query, you send a message from your application to a database, the database finds the
objects satisfying your search conditions, and then the database returns links to the found objects. You can
then use the returned links to bring objects into memory one at a time or in a group.
The default behavior for queries is fine for most situations, but if your query involves an extremely large
number of objects, the following kinds of problems can occur:
• Your application may pause for an unacceptably long time while you wait for all objects to be returned;
• Your chances of encountering a locked object increases, which might time-out the entire query;
• Any read or write lock you place directly or indirectly on the objects returned might hinder access by
other users;
• You cannot sample just a few objects to confirm that the objects being returned are those that you
actually want;
• Your interface language is oriented to handling one object at a time rather than sets of objects.
A cursor allows you to perform a query in stages. A cursor query is still performed by the database server
process, but now you can control how many objects are found and sent to your application at a time.
In some situations, there are numerous advantages to a cursor query. If you choose a small batch size, the
time to get your first objects will be short. If your class contains, say, a million or more objects and you only
need the first few, then you can close the cursor after finding and fetching only the objects you need. And,
while you are looking at a batch of objects, the objects not in your batch can remain unlocked and available for
other users.
481
VERSANT Database Fundamentals Manual
Mechanisms
The following functions create cursors, fetch objects from cursors, and release cursors.
o_pathselectcursor() Create cursor.
o_fetchcursor() Get object with cursor.
o_releasecursor() Release cursor.
Also: all open cursors are closed when a transaction
ends with a commit, checkpoint commit, or rollback.
Elements
The following are key elements of a query performed with a cursor.
Cursor handle
Each cursor has an identifier, called a "cursor handle," so that you can separately control numerous
cursors at the same time.
The "cursor result set" contains all objects that satisfy the query. The difference between the cursor result
set and the links returned by a normal query is that the cursor result set is fetched in batches rather than
all at once.
A "cursor fetch request" is a request to the query to fetch another batch of objects.
The "cursor fetch count" is the number of objects that you ask to be fetched in the next batch of objects.
Cursor batch
A "cursor batch" is a set of objects returned in a cursor fetch request. The number of objects in a cursor
batch may be less than the cursor fetch count if there are no more objects satisfying the query predicate
or if the request encountered a lock conflict. In this release, you can move sequentially foward through
the batches that comprise the cursor result set.
Cursor position
The "cursor position" is a pointer to the current cursor batch. Once you have a cursor batch, you can read,
482
VERSANT Database Fundamentals Manual
update, or delete any object in the batch. Once the cursor moves forward, the objects previously
touched by the cursor will not be selected again.
Cursor creation
A cursor is created by calling a cursor allocation method, which can also optionally fetch the first cursor
batch and set the cursor position. A cursor remains active until it is released.
Cursor release
You can close a cursor and release its resources either explicitly with a function call or implicitly by ending
a session or disconnecting from the database associated with the cursor query. Cursors are also released
implicitly at the end of a transaction.
The following are some key concepts and terms related to the consistency of objects returned with a cursor
query.
Dirty read
In the context of a query performed with a cursor, a "dirty read" means fetching an object that is being
modified by another open transaction. For example, suppose another user is modifying an object, has
written it to a database, but has not committed their transaction. While this is happening, suppose that
you fetch the object without a lock (before the other user commits their transaction.) If the other user
then rolls back their transaction, the object you have can be considered to have never existed.
Cursor stability
Placing a lock on the objects in a cursor batch is called "protecting the cursor batch" in order to provide
"cursor stability." If locks are placed only on the cursor batch and then released when the next batch is
fetched, you can still get "non-repeatable reads" and "phantom updates," but you are guaranteed that the
objects in the cursor batch are stable while you are looking at them.
Non-repeatable
Non-repeatable read
A "non-repeatable" read is when you read the same object twice in the same transaction and get different
object values each time. This can happen if you use a dirty read to fetch an object that is later modified by
another transaction, or the lock placed on the object is not held until the end of the transaction.
483
VERSANT Database Fundamentals Manual
Phantom updates
A "phantom update" is when you perform the same query twice in the same transaction and get different
objects each time. This can happen after a read if another user creates or deletes an object that satisfies
your search conditions, or if another user changes an object so that it newly falls within your search
conditions.
Serializable execution
"Serializable execution" means that separate, concurrent transactions have the same effect as performing
the same transactions one after another. This is a relevant concept when you are using cursor queries to
step through batches of objects, because it means that each concurrent transaction is seeing a consistent
set of objects and changing them in ways that does not compromise the work of all other transactions. As
a result, no inconsistencies will be seen from a serializable execution of transactions.
The consistency of the cursor result set depends upon the lock options you specify when you create the
cursor. The key parameters are:
The short lock to be placed on the class object for the class on which the cursor query will operate.
The short lock to be placed on the current set of cursor batch objects.
Whether to release read locks (but not other locks) on a batch when the next batch of objects is fetched.
To release read locks (while holding update and write locks,) specify the O_CURSOR_RELEASE_RLOCK
option.
Your choices for these three parameters determine the "isolation level" of the cursor operation. The "isolation
level" of a transaction is a measure of how operations on a cursor result set can affect and be affected by
operations in other concurrent transactions.
484
VERSANT Database Fundamentals Manual
The following are the isolation levels that result from various choices of lock parameters.
Cursor batch objects are unstable, and non-repeatable reads and phantom updates may occur.
Cursor batch objects are stable, but non-repeatable reads and phantom updates may occur.
Cursor batch objects are stable and reads are repeatable, but phantom updates may occur.
Cursor batch objects are stable, reads are repeatable, and phantom updates will not occur.
485
VERSANT Database Fundamentals Manual
Note that as the isolation level increases, objects and reads become more stable, but concurrency is reduced.
For example, at isolation level 3, the read lock on the class object has the effect of placing a read lock on all
objects of the class, which prevents all other users from updating, deleting, or creating an object of that class.
Also, for each isolation level, a stronger class or instance lock mode than the minimum lock mode shown
above will further reduce concurrency with other transactions.
Whatever your isolation level, you can still acquire write locks on cursor batch objects in order to update or
delete them, and your transaction will always commit or roll back as an atomic unit.
Anomalies
Isolation level 0 — As objects are fetched without a lock, your application needs to handle the possibility
of error 5006, OB_NO_SUCH_OBJECT, which can occur if an object is deleted by another transaction after
the "fetch" but before the "get."
Isolation level 0 and 1 — In this release, an object is physically moved to the end of the storage space for
its class if it outgrows its current page(s) or if it is deleted and then restored by a transaction rollback.
When an object is in a cursor result set and then gets moved, it can be returned more than once. For
example, suppose a cursor fetches an object with a read lock, but then the read lock is released when the
cursor position is moved. In this case, if another transaction causes the object to move physically, then
the object will be seen again by the cursor.
Example
Following is a C/VERSANT program that illustrates the creation, use, and release of a cursor. It assumes a
database named engineering.
#include <stdio.h>
#include <string.h>
#include "osc.h"
#include "oscerr.h"
#include "omapi.h"
#define NULL_OBJECT ((o_object)NULL)
o_clsname emp_class = "Employee";
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* define_emp() function that creates Employee class object */
/* */
static void define_emp()
{
o_vstr new_attrs;
o_object new_Empclass;
486
VERSANT Database Fundamentals Manual
487
VERSANT Database Fundamentals Manual
/* */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* define_dep() function that creates Department class object */
/* */
static void define_dep()
{
o_vstr new_attrs;
o_object new_Depclass;
o_clsname new_clsname = "Department";
/* static description of every Department attribute. in an array */
static o_attrdesc dep_attrs[]=
{
{"dep_name", {"",""}, "char", O_DYNA_VECTOR, NULL, 0, NULL},
{"dep_manager", {"",""}, "Employee", O_SINGLE, NULL, 0, NULL},
{"dep_employees",{"",""},"Employee",O_DYNA_VECTOR,NULL,0,NULL},
};
/* static array of POINTERS TO Department attributes */
static o_attrdesc *dep_attrs_ptr[]=
{
&dep_attrs[0],
&dep_attrs[1],
&dep_attrs[2],
};
/* create vstr of Department attributes */
if (o_newvstr( &new_attrs, sizeof(dep_attrs_ptr),
(o_u1b *)dep_attrs_ptr) == NULL_VSTR)
{
printf ("ERROR: cannot create new dep vstrs (err %d)\n",
o_errno);
o_exit(1);
}
/* create Department class object */
new_Depclass = o_defineclass(new_clsname, NULL, NULL_VSTR,
new_attrs, NULL_VSTR);
if (new_Depclass != NULL)
printf ("Created Department class\n");
else if (o_errno == SCH_CLASS_DEFINED)
printf ("ERROR: Department class already exists\n");
else
{
printf ("ERROR: could not define Department class
(err %d)\n",o_errno);
o_exit(1);
}
o_deletevstr(&new_attrs);
} /* define_dep */
488
VERSANT Database Fundamentals Manual
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* get_emp_name() function to get Employee name value */
/* caller must delete returned vstr */
static o_vstr get_emp_name(emp_object)
o_object emp_object;
{
o_vstr ename;
o_bufdesc attr_buf;
attr_buf.data = (o_u1b *)&ename;
attr_buf.length = sizeof(o_vstr);
if (o_getattr(emp_object,"emp_name",&attr_buf) != O_OK)
{
printf ("ERROR: getting employee name (err %d)\n", o_errno);
o_exit(1);
}
return ename;
} /* get_employee_name() */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* get_dep_employees() function to get Department employees value */
/* caller must delete returned vstr */
static o_vstr get_dep_employees(dep_object)
o_object dep_object;
{
o_vstr emps_linkvstr;
o_bufdesc attr_buf;
attr_buf.data = (o_u1b *)&emps_linkvstr;
attr_buf.length = sizeof(o_vstr);
if (o_getattr(dep_object,"dep_employees",&attr_buf) != O_OK)
{
printf ("ERROR: getting department employees (err %d)\n",
o_errno);
o_exit(1);
}
return emps_linkvstr;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* check_dep_employee() test if Employee link is in */
/* dep_employees vstr */
/* */
static o_u1b check_dep_employee(dep_object,emp_object)
o_object dep_object;
o_object emp_object;
{
o_u1b checkresult;
o_vstr depempvstr;
o_vstr empvstr;
depempvstr = get_dep_employees(dep_object);
if (o_newvstr( &empvstr, sizeof(o_object),
(o_u1b *)&emp_object) == NULL_VSTR)
{
489
VERSANT Database Fundamentals Manual
490
VERSANT Database Fundamentals Manual
/* */
static o_object make_emp(aname,adep, dept, asal,anum,ajob)
char* aname;
char* adep;
o_object dept;
o_u4b anum;
o_float asal;
char * ajob;
{
o_object newEmployee;
o_bufdesc attr_buf;
struct employeeRec
{
o_vstr emp_name;
o_vstr emp_department;
o_float emp_salary;
o_u4b emp_number;
o_vstr emp_job_description;
};
struct employeeRec empstruct;
if (o_newvstr( &empstruct.emp_name, strlen(aname) + 1,
(o_u1b *)aname) == NULL_VSTR)
{
printf("ERROR: cannot create new emp name vstr
(err %d)\n",o_errno);
o_exit(1);
}
if (o_newvstr( &empstruct.emp_department, strlen(adep) + 1,
(o_u1b *)adep) == NULL_VSTR)
{
printf("ERROR: cannot create new emp dept vstr
(err %d)\n",o_errno);
o_exit(1);
}
empstruct.emp_salary = asal;
empstruct.emp_number = anum;
if (o_newvstr( &empstruct.emp_job_description, strlen(ajob) + 1,
(o_u1b *)ajob) == NULL_VSTR)
{
printf("ERROR: cannot create new emp job vstr
(err %d)\n",o_errno);
o_exit(1);
}
attr_buf.length = sizeof(empstruct);
attr_buf.data = (o_u1b *)&empstruct;
newEmployee = o_createobj( emp_class, &attr_buf, FALSE);
if (newEmployee == NULL_OBJECT)
{
printf("ERROR: cannot create new employee instance
(err %d)\n",o_errno);
491
VERSANT Database Fundamentals Manual
o_exit(1);
}
if ( dept != NULL_OBJECT )
add_dep_employee(dept, newEmployee);
return newEmployee;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* make_dep() create an instance of Department class */
/* */
static o_object make_dep(deptname,mgr_object)
char* deptname;
o_object mgr_object;
{
o_object newDepartment;
o_clsname depclass = "Department";
o_bufdesc attr_buf;
struct departmentRec
{
o_vstr dep_name;
o_object dep_manager;
o_vstr dep_employees;
};
struct departmentRec depstruct;
if (o_newvstr( &depstruct.dep_name, strlen(deptname) + 1,
(o_u1b *)deptname) == NULL_VSTR)
{
printf("ERROR: cannot create new dep name vstr
(err %d)\n",o_errno);
o_exit(1);
}
depstruct.dep_manager = mgr_object;
depstruct.dep_employees = NULL_VSTR;
attr_buf.length = sizeof(depstruct);
attr_buf.data = (o_u1b *)&depstruct;
newDepartment = o_createobj(depclass, &attr_buf, FALSE);
if (newDepartment == NULL_OBJECT)
{
printf("ERROR: cannot create new department instance
(err %d)\n",o_errno);
o_exit(1);
}
return newDepartment;
}
492
VERSANT Database Fundamentals Manual
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* print_emp() function to print one Employee instance */
/* */
static void print_emp(emp_vstr)
o_vstr emp_vstr;
{
o_4b i, num_objs;
o_object *objp;
o_vstr ename, edep, ejob;
o_u4b enumber;
o_float esal;
o_bufdesc attr_buf;
num_objs = o_sizeofvstr(&emp_vstr)/sizeof(o_object);
for (i = 0, objp = (o_object*)emp_vstr; i < num_objs; objp++, i++)
{
ename = get_emp_name(*objp);
attr_buf.data = (o_u1b *)&edep;
attr_buf.length = sizeof(o_vstr);
if (o_getattr(*objp,"emp_department",&attr_buf) != O_OK)
{
printf ("ERROR: getting department name (err %d)\n",
o_errno);
o_exit(1);
}
attr_buf.data = (o_u1b *)&esal;
attr_buf.length = sizeof(o_float);
if (o_getattr(*objp,"emp_salary",&attr_buf) != O_OK)
{
printf ("ERROR: getting employee salary (err %d)\n",
o_errno);
o_exit(1);
}
attr_buf.data = (o_u1b *)&enumber;
attr_buf.length = sizeof(o_u4b);
if (o_getattr(*objp,"emp_number",&attr_buf) != O_OK)
{
printf ("ERROR: getting employee number (err %d)\n",
o_errno);
o_exit(1);
}
attr_buf.data = (o_u1b *)&ejob;
attr_buf.length = sizeof(o_vstr);
if (o_getattr(*objp,"emp_job_description",&attr_buf) != O_OK)
{
printf ("ERROR: getting job description (err %d)\n",
o_errno);
o_exit(1);
}
printf ("%s %s $%.2f #%d %s \n",
(char*)ename,(char*)edep,esal,enumber,
493
VERSANT Database Fundamentals Manual
(char*)ejob);
o_deletevstr(&ename);
o_deletevstr(&edep);
o_deletevstr(&ejob);
}
}
main()
{
o_4b i, j;
o_object dept1, dept2, dept3, dept4;
o_object mgr1, mgr2, mgr3, mgr4;
o_object emp1, emp2, emp3, emp4,emp5,emp6,emp7,emp8,emp9,emp10;
o_err err;
o_cursor emp_cursor;
o_vstr emp_objs;
char* dbname = "engineering";
char* depart = "Research & Development";
o_vstr mypredvstr;
o_predterm mypred;
o_predterm* mypredptr = &mypred;
o_predblock mypredblk;
o_u4b numobjects;
o_u4b retry_count = 0;
o_u4b options = O_CURSOR_RELEASE_RLOCK;
if (o_beginsession("",dbname,"",0) != O_OK)
{
printf("ERROR: begin session (err %d)\n", o_errno);
return 1;
}
{ /* begin database session */
/* Create employee class object */
o_dropclass("Employee", dbname);
define_emp();
if (o_xact(O_COMMIT, NULL) != O_OK)
{
printf("ERROR: commit (err %d)\n",o_errno);
o_exit(1);
}
/* Create department class object */
o_dropclass("Department", dbname);
define_dep();
if (o_xact(O_COMMIT, NULL) != O_OK)
{
printf("ERROR: commit (err %d)\n",o_errno);
o_exit(1);
}
printf("=>Create new Employee managers...\n");
mgr1 = make_emp( "Bob Black","Production", NULL_OBJECT,
(o_float)35000,(o_u4b)100022,"Manager");
mgr2 = make_emp( "Bill Green","Research & Development",
494
VERSANT Database Fundamentals Manual
NULL_OBJECT, (o_float)35000,
(o_u4b)100022,"Manager");
mgr3 = make_emp( "Tom White","Quality Assurance",
NULL_OBJECT, (o_float)35000,
(o_u4b)100022,"Manager");
mgr4 = make_emp( "Mary Jones","Administration",
NULL_OBJECT, (o_float)35000,
(o_u4b)100022,"Manager");
printf("=>Create new Departments...\n");
dept1 = make_dep("Production",mgr1);
dept2 = make_dep("Research & Development", mgr2);
dept3 = make_dep("Quality Assurance",mgr3);
dept4 = make_dep("Administration",mgr4);
printf("=>Create new Employees...\n");
emp1 = make_emp("Larry Link","Production", dept1,
(o_float)35000,(o_u4b)100022,"Engineer");
emp2 = make_emp("Alex Loids","Production", dept1,
(o_float)35000,(o_u4b)100022,"Engineer");
emp3 = make_emp("Oliver Object","Research & Development",
dept2, (o_float)35000,
(o_u4b)100022,"Engineer");
emp4 = make_emp("Ethel Murtz","Production", dept1,
(o_float)35000,(o_u4b)100022,"Engineer");
emp5 = make_emp("Lance Lee","Production", dept1,
(o_float)35000,(o_u4b)100022,"Engineer");
emp6 = make_emp("Kate Krammer","Research & Development",
dept2, (o_float)35000,
(o_u4b)100022,"Engineer");
emp7 = make_emp("Liz Lucky","Administration", dept4,
(o_float)35000,(o_u4b)100022,"Office admin");
emp8 = make_emp("Sam Skywalker","Administration", dept4,
(o_float)35000,(o_u4b)100022,"Payroll admin");
emp9 = make_emp("Jill Jennings","Administration", dept4,
(o_float)35000,(o_u4b)100022,"HR admin");
emp10 = make_emp("Tammy Tanner","Administration", dept4,
(o_float)35000,(o_u4b)100022,"Receptionist");
if (o_xact(O_COMMIT, NULL) != O_OK)
{
printf("ERROR: commit (err %d)\n", o_errno);
o_exit(1);
}
/* Set up predicate for the search condition */
mypredptr->attribute = "emp_department";
mypredptr->oper = O_EQ;
mypredptr->key.length = strlen(depart) + 1;
mypredptr->key.data = (o_u1b *)depart;
if (o_newvstr(&mypredvstr, sizeof(mypred), (o_u1b *)&mypred)
== NULL_VSTR)
{
printf("ERROR: creating vstr mypredvstr
495
VERSANT Database Fundamentals Manual
(err %d)\n",o_errno);
o_exit (1);
}
/* Set up a pred block for o_pathselectcursor() */
mypredblk.conj = O_AND;
mypredblk.flags = 0;
mypredblk.more_predblocks = NULL_VSTR;
mypredblk.leaf_predterms = mypredvstr;
/*
* Create cursor on employee class to find employees in the
* Technical Support department.
*/
emp_objs = o_pathselectcursor(emp_class, dbname,
options, IRLOCK, RLOCK, 1, mypredblk, &emp_cursor, 0, 0);
o_deletevstr(&mypredvstr);
if (emp_objs == NULL_VSTR)
{
if (o_errno == O_OK)
printf("No objects qualified for the cursor.\n");
else
{
printf("ERROR:failed to create cursor, (err%d)\n",
o_errno);
o_exit(o_errno);
}
}
/* Fetch 2 employees at a time and print */
while (emp_objs != NULL_VSTR)
{
printf("\nfetch %d employee\n",
o_sizeofvstr(&emp_objs)/sizeof(o_object*));
print_emp(emp_objs);
o_deletevstr(&emp_objs);
emp_objs = o_fetchcursor(&emp_cursor, 2,0,0);
numobjects = o_sizeofvstr(&emp_objs)/sizeof(o_object *);
if (numobjects != 2)
{
err = o_errno;
if (numobjects > 0)
{
if (err == O_OK) /* reach the end of cursor */
{
printf("\nfetch %d employees\n", numobjects);
print_emp(emp_objs);
o_deletevstr(&emp_objs);
break;
}
else
{ /* case: SM_LOCK_TIMEDOUT and
SM_LOCK_WOULDBLOCK */
496
VERSANT Database Fundamentals Manual
497
VERSANT Database Fundamentals Manual
VQL Queries
Overview
The VERSANT Query Language (VQL) is a "light-weight" language which enables you to find and manipulate
objects in VERSANT databases using statements expressed as a string. You can then parse and execute the
string using C/VERSANT or C++/VERSANT routines.
In some ways, VQL looks like SQL with some object extensions. For example, in VQL you can perform a path
query and query on a vstr.
VQL Procedures
Parsing and executing a VQL statement are separate operations.
1. Write a VQL query or update statement and place it in a string of data type char*.
2. Parse the VQL statement using functions provided in the C/VQL interface.
Mechanisms
OQL Statements
COMMIT Commit transaction.
DELETE Delete objects.
INSERT Insert objects.
QUIT Terminate application.
ROLLBACK Roll back transaction.
SELECT Find objects.
UPDATE Update objects.
VQL Functions
498
VERSANT Database Fundamentals Manual
Following are the relationships of data structures for the result of o_parsequery().
499
VERSANT Database Fundamentals Manual
500
VERSANT Database Fundamentals Manual
Case Sensitivity
VQL statements (such as SELECT) and key words (such as SELFOID) are not case sensitive. However, class
names, attribute names, and data values are case sensitive.
Statement Strings
You can express one or more operations in a VQL statement string by concatenating the individual operations
with a semicolon. The general syntax is:
stmts ::= stmt [; stmt ... ]
For example:
select * from Employee; select * from Department
Specifying Attributes
The SELECT, INSERT, and UPDATE statements require specification of a list of attributes. Following are notes
related to specifying an attribute in an attribute list.
General syntax
501
VERSANT Database Fundamentals Manual
If the attribute name contains no reserved words or special characters, such as spaces, then you can simply
supply the name.
If the attribute name does contain a reserved word or special character, then use backquotes (ASCII 96 `)
rather than single or double quotes (ASCII 39 ' or ASCII 34 ") around it. Single and double quotes are
reserved for specifying strings.
Abbreviation examples
An attribute name can be either the full name of an attribute, which is defined in the database schema, or an
abbreviation of the full name with the class name and type names being stripped.
For example, the full name of an attribute employees of class Department that has been defined in C++ and
stored in a database is:
Department::employees
Please see your C++/VERSANT Reference Manual for complete information on how to express an attribute
name in database format.
If there is multiple inheritance, the attribute name abbreviation in a query may lead to ambiguity in terms of
telling which attribute the query is to reference.
For example, suppose that both Class A and Class B have an attribute named number and suppose that Class
C inherits from both A and B. This means that there are two attributes with the same name in C: A::number
and B::number. For a query such as "select * from C where number > 8", there is no way to tell whether
query references B::number or A::number. In this case, the parser will pick the first attribute it finds in the
VERSANT database schema that matches the shortname number, which may or may not lead to the intended
502
VERSANT Database Fundamentals Manual
result.
In general, if you query over a class that uses multiple inheritance, we recommend that you use the full,
unambiguous attribute name in the query.
Literal values of data type float must be in either decimal form or exponential form.
The valid form of type float is only a subset of ANSI C decimal form and exponential form. For example,
8.754e+78F is not valid in VQL because no character (in this case F) is allowed to be appended at the end of
exponential form.
For example, the following are acceptable values for a value of type float: -134.789, 7.8976e-308,
8.9654E-876, -9.876e89, 9.654E70, 8.754e+204, 9.8765E+70.
VQL can only refer to the "raw" attribute which is stored in the database schema. Some of them may be
different from the ones that are presented in the applications.
For example, the attribute start_date of class Employee might be of type VDATE in a C++ application.
However, the actual attribute which contains data about start_date in the database schema is:
Employee::start_date.VDate::absolute_day_number".
VQL can only refer to this attribute either using it's full name (give above) or using the following abbreviation:
start_data.absolute_day_number
Keyword SELFOID
A logical object identifier or loid is the unique, never reused identifier number associated with each persistent
object. This number is guaranteed to be unique among all objects in a system of databases.
In a SELECT statement, you can get the loid for an object returned by a query by including the keyword
SELFOID in the list of attribute names. However, if you use the wildcard character * as your attribute list for a
query, the object loid will not be returned.
503
VERSANT Database Fundamentals Manual
nn.nn.nn
See your VERSANT interface manual for routines that get and use loids.
The parser will convert constant values of integer types in a predicate into the corresponding database type.
If the value return overflows or underflows the database type, an error will be returned. For example, if you
perform a SELECT query where attr_u2b = -30, the conversion from integer -30 to type o_u2b will fail due
to the underflow of type o_u2b.
The parser will convert a constant value of type float to either type o_float or o_double, depending on
the attribute type. Conversion from float to any other type is invalid and cause an error. If a float value
overflows or underflows the corresponding type of the database attribute, the parser will raise error.
504
VERSANT Database Fundamentals Manual
Creating a Predicate
Predicates are used in SELECT, UPDATE, and DELETE statements to specify selection criteria for objects.
Individual query expressions can be concatenated with the following boolean operators:
AND query_expr AND query_expr
Boolean AND.
OR query_expr OR query_expr
Boolean OR.
NOT NOT query_expr
Boolean NOT.
505
VERSANT Database Fundamentals Manual
See the "Specifying Attributes" section for information on how to specify an attribute.
relop
A relational operator, one of the following:
= Equal to
< Less than
<= Less than or equal to
<> Not equal to
> Greater than
>= Greater than or equal to
LIKE String pattern matches
NOT_LIKE String pattern does not match
const
A literal value, one of the following:
float A number of type float
int A number of type int
string A string (use backquotes if special characters)
oidval An object loid expressed with syntax nn.nn.nn
constaggr A list of constant values with comma delimiters
When comparing strings, you can use the following wildcards:
* Match any sequence of characters
? Match any single character
[chars] Match any character in given set.
[x-y] Match any character in given range.
\x Match single character x.
isa_relop
A class membership operator, one of the following:
ISA Is member of class.
NOT_ISA Is not member of class.
These operators test whether an object is an instance of a given class, but they do not test
whether the object is an instance of a subclass.
class
Class name, specified with the following syntax:
name | `name`
param
A substitution parameter, specified in the following format:
$n
where n represents a non-negative integer.
506
VERSANT Database Fundamentals Manual
During the VQL parsing phase, a substitution parameter is recognized as a place holder which is
to be filled after the parsing and before the query execution. In this way, the same query can be
executed with different parameters without the need of parsing the query again and again.
See also the VQL sections in the C/VERSANT and C++/VERSANT reference manuals for more
information on using substitutions paramters in C and C++ applications.
type
Following are valid values for type when casting const and param values:
char
o_1b
o_u1b
o_2b
o_u2b
o_4b
o_u4b
o_8b
o_u8b
o_float
o_double
o_object
Handling of types
During parsing, the parser will try to find the exact type information based on the pathattr. However, if the
parser cannot determine the attribute type (for example, if there is a C++ LinkAny type in the path
expression), it will do the conversion for the constant as follows.
507
VERSANT Database Fundamentals Manual
For example:
Class Author: public PObject
{ ...
public:
LinkAny lastBook;
...
}
Class Book : public PObject
{ ...
public:
Vstr(char) title;
o_u4b numberOfPages;
...
}
If there is a C++ LinkAny Type in a path expression, the fullname of the attribute (such as
Book::numberOfPages) must be specified instead of a short name: otherwise, the database won't be able to
recognize the name of attribute at runtime.
If you select over a vstr, the parser will not verify an attribute in the select list during the parsing, because if
the select vstr is a vstr of C++ type VstrLinkAny , it is not possible to verify the attribute types.
When the parser sees a query over a vstr, the parser places all attribute names into the parser result, and the
attribute is resolved at run time by the application. If you use the wildcard "*" in select with vstr query and the
vstr is of type VstrLinkAny, the parser will create an o_projection structure with a vstr of 0 elements for
attributes.
For example:
Class Publisher: public PObject
{ public:
...
VIList<Book> topSellerList;
...
}
select * from (4.0.7777)->topSellerList.elements
The above should return objects. However, no attribute will be displayed since the parser can not resolve the
508
VERSANT Database Fundamentals Manual
The following is a better way is to rewrite the above query and will allow you to dereference returned objects
in your application:
select selfOid from (4.0.7777)->topSellerList.elements
509
VERSANT Database Fundamentals Manual
predicate_term ::=
path_attribute relation_operator constant |
path_attribute relation_operator (type) constant |
path_attribute isa_operator identifer |
path_attribute relation_operator parameter |
path_attribute relation_operator (type) parameter |
path_attribute isa_operator parameter
select_attr ::= SELFOID | * | attribute
path_attribute ::= attribute { arrow attribute }
update_attr ::= attribute update_operator constant
class ::= identifier
attribute ::= identifier
constant ::= float_literal | integer_literal |
string | loid | constant_collection
constant_collection ::= open_brace constant_list close_brace
parameter ::= dolar_sign integer_literal
loid ::= integer_literal.integer_literal.interger_literal
identifier ::= string_literal | `string_literal`
string ::= "string_literal" | 'string_literal'
update_operator ::= += | =
relation_operator ::= < | > | <> | <= | >= | = | LIKE | NOT_LIKE
isa_operator ::= ISA | NOT_ISA
open_brace ::= {
close_brace ::= }
dolar_sign ::= $
arrow ::= ->
type ::= string_literal
Example
The following program illustrates how to use C/VERSANT to parse a VQL query statement and execute the
query based on the result generated by the parser.
For this code and other examples, see your installation directory $VERSANT_ROOT/demo/c/ivql.
This program takes several VQL statements, parses them, and executes the query. The VQL statements
involved include SELECT, UPDATE, INSERT, DELETE, COMMIT, ROLLBACK, and QUIT. This program also shows
how to perform parameter substitution.
#include <stdio.h>
#include <ctype.h>
#include <oscerr.h>
#include <omapi.h>
#include <omsch.h>
510
VERSANT Database Fundamentals Manual
511
VERSANT Database Fundamentals Manual
options[0]=0;
if (o_beginsession("VQL_TRAN", dbname, "VQL_SESSION", options)
!= O_OK)
{
printf("Failed to open database %s\n", dbname);
exit(-1);
}
/* Parse VQL Statements */
if (o_parsequery(querystring, parse_result, dbname) != O_OK)
{
printf("%s\n", querystring);
printf("%s\n", 1+parse_result->prs_err.prer_tokenoffset, "^");
printf("%s\n", parse_result->prs_err.prer_errmsg);
exit(-1);
}
num_statements = o_sizeofvstr(&parse_result->prs_pblkvstr) /
sizeof(o_parseblock *);
/* Processing each statement */
for (i = 0; i < num_statements; i++)
{
/* Get the handle of parse block. */
pblkp = ((o_parseblock **) parse_result->prs_pblkvstr)[i];
/* Substitute paramters if there is any */
if (pblkp->prb_paramvstr)
{
number = (o_4b *) malloc(sizeof(o_4b));
num_params = o_sizeofvstr(pblkp->prb_paramvstr) /
sizeof(o_param);
/* For this sample, there is only one parameter to
** substitute.*/
if (num_params != 1)
{
printf("Unexpected number of parameters. \n");
exit(-1);
}
param_pointer = ((o_param *) (*pblkp->prb_paramvstr));
/* Set the new value buffer */
*number = 100023;
new_buffer.data = (o_u1b *) number;
new_buffer.length = sizeof(o_4b);
memcpy(¶m_pointer->prm_predterm->key,
(o_bufdesc *) &new_buffer, sizeof(o_bufdesc));
}
512
VERSANT Database Fundamentals Manual
case O_QRY_UPDATE:
case O_QRY_DELETE:
/* Apply query */
objectvstr =
o_pathselect(pblkp->prb_classname,
dbname, 0, NULL,
pblkp->prb_options, IRLOCK,
IRLOCK, pblkp->prb_predicate,
pblkp->prb_vstrobj,
pblkp->prb_vstrattr);
if (objectvstr == (o_vstr) NULL)
{
if (o_errno != O_OK)
{
printf ("Query execution error %d\n",o_errno);
exit(-1);
}
}
break;
/* Get the single object */
case O_QRY_SELOBJ:
case O_QRY_UPDOBJ:
case O_QRY_DELOBJ:
/* Place the single object into objectvstr */
if (!o_newvstr(&objectvstr, sizeof(o_object),
(o_u1b *) &pblkp->prb_vstrobj))
{
printf("Fail to create vstr (err %d)\n",
o_errno);
o_exit(-1);
}
}
/* Apply operation to the objects that have been retrieved */
switch (pblkp->prb_command)
{
case O_QRY_SELECT:
case O_QRY_SELOBJ:
/* Get the number of attributes */
num_attr = o_sizeofvstr(&pblkp->prb_prjvstr) /
sizeof(o_projection *);
/*
** For SELECT query of this operation, there is
** only two attributes: name, number; therefore
** we only expect two attributes
*/
if (num_attr != 2)
{
printf("Unexpected number of attributes. \n");
exit(-1);
}
513
VERSANT Database Fundamentals Manual
514
VERSANT Database Fundamentals Manual
515
VERSANT Database Fundamentals Manual
{
printf("Error: o_setattr \
failed (err %d)\n", o_errno);
}
/* Prepare the new value buffer */
new_buffer.data =
(o_u1b *) new_bufdata;
new_buffer.length = sizeof(o_vstr);
/* Add new elements to collection.*/
vstr_pointer =
(o_vstr *) new_buffer.data;
(*vstr_pointer) =
o_concatvstr((o_vstr *)
(buffer.data),
(o_vstr *)
(projection->prj_buf.data));
/* Set new value to attribute */
if (o_setattr(object,
projection->prj_attrname,
&new_buffer) != O_OK)
{
printf("Error: o_setattr \
failed (err %d)\n",
o_errno);
}
/* clean up */
o_deletevstr((o_vstr *)(buffer.data));
o_deletevstr((o_vstr *)(new_buffer.data));
break;
}
}
}
break;
case O_QRY_DELETE:
case O_QRY_DELOBJ:
missing = (o_vstr) 0;
/* delete objects */
if (o_gdeleteobjs(objectvstr, NULL, &missing) != O_OK)
{
printf("Delete fails. \n");
exit(-1);
}
break;
case O_QRY_COMMIT:
/* Commit transations */
o_xact(O_COMMIT, "VQL_TRAN");
break;
case O_QRY_ROLLBACK:
/* Rollback transcation */
o_xact(O_ROLLBACK, "VQL_TRAN");
516
VERSANT Database Fundamentals Manual
break;
case O_QRY_QUIT:
quit = TRUE;
break;
default:
printf("Unrecognized command.");
exit(-1);
}
if (quit)
break;
}
if (number)
free(number);
o_freequery(parse_result);
o_endsession("VQL_SESSION", options);
}
517
VERSANT Database Fundamentals Manual
Advanced Queries
This section explains in detail the concept of query enhancing capabilities of Versant ODBMS. To improve the
query capability, a new concept of Virtual Attributes has been introduced. The enhanced query capability in
Versant Release 6.0.0, will now allow you to query using Virtual Attributes and create index on Virtual
Attributes.
Virtual Attribute
Virtual Attribute is a derived attribute, built on one or more normal attributes of a class. As the Virtual
Attributes are computed at runtime, they are not part of the schema of a class, but can be indexed. The
indexes on Virtual Attributes are maintained during normal operations like insert, delete and update and
used during query processing. This release will support only b-tree index on Virtual Attribute of a Class.
The implementation of Virtual Attribute Templates (the "class" of Virtual Attributes) are plugins provided as
shared objects that gets loaded into the Versant server process’ address space.
Following is the Virtual Attribute grammar that uses an informal BNF notation.
SLASH “/”
BRACE-START “{“
BRACE-END “}”
518
VERSANT Database Fundamentals Manual
For the supported locale and encoding schemes, refer the Chapter 21: Internationalization for details.
The syntax of profile.be entry to specify the VAT plug-ins is as given below:
custom_be_plugins <plugin-name>$<arg>[,....]
For example, to use the Virtual Attribute built on VAT national, the custom_be_plugins parameter in
profile.be should be configured as follows:
custom_be_plugins /versant/lib/libnational.so$/versant
where /versant is VERSANT_RUN_ROOT and the argument to the plugin specifies the location of the data
used by the plugin.
The evaluation attribute specified in predicate terms can be a Virtual Attribute. The key value for predicate
terms which has Virtual Attribute can be specified in a string buffer quoted with curly braces. If a Virtual
Attribute is built on multiple attributes, a composite key value can be specified by separating the key values by
space.
519
VERSANT Database Fundamentals Manual
The attribute in o_predterm is the evaluation attribute, for which the value is to be examined. This could be
a Virtual Attribute. This allows the following C/Versant APIs to be used with Virtual Attributes.
o_vstr o_select (
o_clsname classname,
o_dbname dbname,
o_bool iflag,
o_lockmode lockmode,
o_vstr predicate );
o_vstr o_pathselect (
o_clsname classname,
o_dbname dbname,
o_4b levels,
o_vstr linktypes,
o_u4b options,
o_lockmode this_cls_lockmode,
o_lockmode instance_lockmode,
o_predblock *predicate,
o_object vstr_object,
o_attrname vstr_attribute );
o_vstr o_pathselectcursor
o_clsname classname,
o_dbname dbname,
o_u4b options,
o_lockmode classlock,
o_lockmode instlock,
o_u4b fetchcount,
o_predblock* predicate,
o_cursor* cursor,
o_u4b flags1,
o_u4b flags2 );
520
VERSANT Database Fundamentals Manual
o_err o_deleteindex (
o_dbname dbname,
o_classname classname,
o_attrname attrname,
o_indextype indextype );
The database utility, dbtool can be used to create or delete index defined on Virtual Attributes. The Virtual
Attribute should be specified within quotes.
dbtool -icb class { attribute | virtual-attribute }
Creates a b-tree index for the specified attribute or a Virtual Attribute of a class.
For example: to select the objects from a class Employee whose LastName ignoring the case is "samuel"
521
VERSANT Database Fundamentals Manual
522
VERSANT Database Fundamentals Manual
Examples
Examples of Virtual Attributes used in index creation/deletion and query operations.
• create unique B-TREE index on virtual attribute "/nocase ascii Title" of class MyClass (case-
independent index on attribute Title of class MyClass)
o_createindex ("MyDB", "MyClass", "/nocase ascii Title", O_IT_UNIQUE_BTREE);
• to query objects from a class MyClass whose case-insensitive Title is "the diamond age".
select * from MyClass where `/nocase ascii Title` = "the diamond age";
• create unique B-TREE index on Virtual Attributes "/tuple {/nocase ascii LastName} FirstName"
of class Employee (composite index on Virtual Attribute "/nocase ascii LastName" and attribute
FirstName of class Employee)
o_createindex ("EmpDB", "Employee", "/tuple { /nocase ascii LastName }
FirstName", O_IT_BTREE);
• to select the objects from a class Employee whose LastName ignoring the case is "samuel" and FirstName
is "Mohan M".
select * from Employee where `/tuple { /nocase ascii LastName } Firstname` =
"samuel {Mohan M}";
• drop an index on Virtual Attributes "/nocase ascii Title" of class MyClass
o_deleteindex ("MyDB", "MyClass", "/nocase ascii Title", O_IT_UNIQUE_BTREE);
Restrictions
• The size of Virtual Attributes is limited to 512 bytes.
• Virtual Attributes can only be used in a predicate term of a query and not in projection, that is Virtual
Attributes cannot be used in front end operations like o_getattr().
• Virtual Attributes cannot be specified on a path attribute. For example, the following is not supported :
523
VERSANT Database Fundamentals Manual
• Virtual Attributes can not be specified in a query which uses O_SELECT_WITH_VSTR option. The starting
point objects should be objects of a class or a class and its subclasses if O_SELECT_DEEP option is
specified.
• Renaming an attribute requires indexes defined on Virtual Attributes for that specific attribute to be
dropped.
• Dropping all instances in a class will also require indexes defined on Virtual Attributes of that class to be
dropped. .
• Usage of online index creation on Virtual Attributes with FTS is not supported.
• Indexes defined on Virtual Attributes needs to be created explicitly on the target database after vcopydb.
• Creating index on Virtual Attributes can be performed only on an non-empty class. After dropinst of all
instances of a class, the indexes on Virtual Attributes needs to be created explicitly only after creating
atleast an instance.
• Virtual Attributes can not be part of predicates which are used in event registration.
• Querying with different encoding scheme than the scheme used to store the data would cause
unexpected results.
• If an index is defined on a Class for a Virtual Attribute, queries that do not use the Virtual Attributes may
not use the index defined on that Class. For example: if an index is defined on a Virtual Attribute
"/nocase ascii Title", querying on the attribute Title will not make use of the index defined on the
Virtual Attribute.
• Restrictions on the indexability of normal attributes is also applicable on Virtual Attributes. Apart from
this, different Virtual Attribute implementation may impose restriction on the data type of the underlying
attribute on which it is built. For e.g, /nocase or /national will work only with string data types.
524
VERSANT Database Fundamentals Manual
Index
attribute rules, 467, 25
/
attribute specification by path, 453, 11
/national, 415, 3
attribute types allowed, 450, 8
asynchronous database replication actions, 362, 30 cached object descriptor table, 199, 5
525
VERSANT Database Fundamentals Manual
changing an attribute's data type, 103, 9 checkouts, locks, and versions, 114, 6
check in objects by ending a long transaction, 118, 10 class names and attribute names, 432, 14
checkin and schema evolution, 120, 12 class, change class schema, 440, 5
checkin non-versioned objects, 119, 11 class, load class into database, 440, 5
checkout and long transaction, 117, 9 cluster objects near a parent, 278, 16
checkout general notes, 117, 9 collect statistics and store them in a file, 299, 9
checkout non-versioned objects, 114, 6 collect statistics and write to file, 300, 10
checkout non-versioned objects with read and write locks, collect statistics in memory and view in real time, 301, 11
116, 8
collect statistics on function entry, 300, 10
checkout specified objects, 113, 5
collect statistics on function exit, 300, 10
checkout versioned objects, 115, 7
compare class memberships, 456, 14
checkout versioned objects and non-versioned objects
with NULL locks, 116, 8 compare scalar values, 455, 13
concepts, 328, 374, 445, 465, 482, 2, 3, 23, 40 customized user authentication, 391, 7
create database directories and files, 438, 3 database object model, 26, 19
create statistics expressions, 295, 6 database user management utility (dbuser), 387, 3
create user defined collection points, 298, 8 database users (dbuser), 387, 3
error messages that are incomplete, 108, 14 find and check out objects of a class, 114, 6
528
VERSANT Database Fundamentals Manual
get event notification status, 338, 8 initialize event notification for a database, 337, 7
get object information, get volume information, 302, 12 intention read lock, 80, 14
get statistics with a direct connection, 293, 4 internationalization (i18n) support, 10, 3
localizing usage notes, 429, 12 mixing C and C++ code and objects, 159, 2
long transaction event notification, 347, 16 multiple threads in a single session, 228, 2
long transaction name, 232, 6 multiple threads in the same session, 241, 14
530
VERSANT Database Fundamentals Manual
multi-threaded database server, 207, 13 no conversion between different locales for client-side
and server-side, 432, 14
531
VERSANT Database Fundamentals Manual
one or more threads in multiple sessions, 228, 2 persistent lock description, 85, 19
operator [] returns nth byte instead of character, 433, 15 persistent lock event notification description, 89, 23
optimistic locking general notes, 184, 16 persistent lock object ready, 89, 23
overview, 101, 122, 132, 196, 332, 413, 499, 7, 2, 12, persistent lock reserve, 87, 22
2, 56
persistent lock wait, 87, 21
pass through certification in beta version, 432, 14 persistent read lock, 86, 20
pattern matching query with accent character, 433, 15 persistent write lock, 85, 19
predicate, 448, 6
532
VERSANT Database Fundamentals Manual
profile.be should not be modified, 433, 15 remove event message queue for a database, 337, 7
resolving, 134, 14
Q
restore defaults for read operations with synchronous
query, 9, 2 database replication, 384, 12
R S
raise event on an object, 339, 8 salient features of VERSANT developer suite 6.0:, 9, 2
schema evolution, 101, 7 set database for vstr read operations, 362, 30
search query indexes, 465, 23 set preferred database for read operations with
synchronous database replication, 383, 11
searching and sorting of string-data are allowed in non-
english languages, 415, 3 set replica reporting on for current session, 383, 11
security, 10, 3 set statistics collection off when using file, 300, 10
select statistics, turn statistics on, 293, 4 set statistics collection off when using memory, 301, 11
server page cache, 203, 9 set statistics collection on and send to file, 299, 9
server process, 207, 13 set statistics collection on and send to memory, 301, 11
534
VERSANT Database Fundamentals Manual
short read lock, 70, 5 statistics collection procedures for applications, 307, 17
start work on event message queue, 339, 9 syntax of virtual attribute, 519, 76
statistics collection overview, 291, 2 thread and session concepts and terms, 229, 3
535
VERSANT Database Fundamentals Manual
usage notes for C and C++, 339, 9 view statistics for a database, 293, 4
user authentication process, 393, 9 view statistics from a profile file, 297, 7
user authentication sample programs, UNIX, 396, 12 view statistics in memory, 301, 11
user authentication sample programs, Windows NT, 403, virtual atrributes in VQL query, 522, 79
18
virtual attribute, 519, 76
user privileges, 409, 24
virtual attributes in index operation, 522, 78
users, 386, 2
virtual attributes in predicate term of search queries and
utility access privileges, 409, 24 cursors, 521, 77
536
VERSANT Database Fundamentals Manual
537
VERSANT Corporation
Worldwide Headquarters
6539 Dumbarton Circle
Fremont, CA 94555 USA
Main: +1 510 789 1500
Fax: +1 510 789 1515
VERSANT Ltd.
European Headquarters
Unit 4.2 Intec Business Park
About Versant
Wade Road, Basingstoke
Hampshire, RG24 8NE UK
Main: +44 (0) 1256 366500 Versant Overview
Fax: +44 (0) 1256 366555
As a leading provider of object-oriented middleware infrastructure, Versant offers
VERSANT GmbH the Versant Developer Suite and Versant enJin. Versant has demonstrated
Arabellastrasse 4 leadership in offering object-oriented solutions for some of the most demanding
D-81925 Müenchen, Germany and complex applications in the world. Today, Versant solutions support more
Main: +49 89 920078 0 than 700 customers including AT&T, Alcatel, BNP/Paribas, British Airways,
Fax: +49 89 920078 44 Chicago Stock Exchange, Department of Defense, Ericsson, ING Barings, Neustar,
Versant India Pvt Ltd. SIAC, Siemens, TRW, Telstra, among others. The Versant Developer Suite, an
1240-A, Subhadra Bhavan,
object database, helps large-scale enterprise-level customers build and manage
Apte Road, Shivajinagar highly distributed and complex C++ or Java applications. Its newest e-business
Pune 411 004, India product suite, Versant enJin, works with the leading application servers to
Main: +91 20 553 9909 accelerate Internet transactions.
Fax: +91 20 553 9908
VERSANT DISTRIBUTORS: Versant History, Innovating for Excellence
VERSANT France S.A.
104, rue Castagnary In 1988, Versant’s visionaries began building solutions based on a highly scalable
F-75015 Paris, France
Main: +33 1 44 19 10 02
and distributed object-oriented architecture and a patented caching algorithm that
proved to be prescient. Versant’s initial flagship product, the Versant Object
VERSANT Italy S.r.l.
Via C. Colombo, 163 Database Management System (ODBMS), was viewed by the industry as the one
00147 Roma, Italy true enterprise-scalable object database. Leading telecommunications, financial
Main: +39 06 5185 0800 services, defense and transportation companies have all depended on Versant to
VERSANT Israel Ltd. solve some of the most complex data management applications in the world.
Haouman St., 9, POB 52210 Applications such as fraud detection, risk analysis, yield management and real-time
91521 Jerusalem, Israel
Main: +972 2 679 38 30
data collection and analysis have benefited from Versant’s unique object-oriented
Cast-Info
architecture.
Tuset Nb. 23, 1r
08006 Barcelona, Spain
Main: +34 93 445 68 00
Nexgen Technology Co.
2F, Yaekwang Bldg. 875-5
Bangbae-Dong, Seocho-Gu, 1-800-VERSANT
Seoul, South of Korea 137-060
Main: +82 2 533 4377
www.versant.com
TechMatrix Corporation
Shuwa-Yanagibashi Building
19, 2-Chome, Yanagibashi
Part Number: 1038-0603
Taito-Ku, Tokyo
Main: +81 3 3864 7608
© 2002 Versant Corporation.
ZLnet, Inc.
123# Weidi Road, 29-30th floor All products are trademarks or registered trademarks of their respective companies in the United States and
Tianjin Finance Bldg., Hexi District other countries.
Tianjin, 300074, China The information contained in this document is a summary only.
Main: +86 22 28401558
For more information about Versant Corporation and its products and services, please contact Versant
Worldwide or European Headquarters.