home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
NeXTSTEP 3.0
/
NeXTSTEP3.0.iso
/
NextDeveloper
/
Headers
/
dbkit
/
DBBinder.h
< prev
next >
Wrap
Text File
|
1992-05-05
|
16KB
|
408 lines
/*
** DBBinder.h
** Database Kit, Release 3.0
** Copyright (c) 1992, NeXT Computer, Inc. All rights reserved.
*/
#import <objc/Object.h>
#import <dbkit/protocols.h>
#import <dbkit/enums.h>
#import <objc/List.h>
#import <streams/streams.h>
#import <mach/cthreads.h>
@class DBQualifier;
@class DBDatabase;
/*
** The DBBinder is a class which "connects" objects and variables within
** a NextStep program to external data. The connect can be bidirectional,
** that is, values from NextStep can be either propagated to or slaved to
** the external database. There are three verbs for modifying external
** data: "insert", "update", and "delete". Data can be pulled from the
** database with "select" and "fetch". Lastly, "evaluate" can be used to
** perform either function.
**
** DBBinder is composed of two internal pieces --
** (1) mappings, which link DBDataPaths (which can be thought of as
** pointers to external data) to any number of DBDataWraps (which
** correspond to internal NextStep "pointers") When selecting data from
** the external database into the app, the dataPaths help locate
** the data, and then the dataWraps are used to load the data into the
** app's objects. When loading data from the app to the database, the
** dataWraps provide data, which is put into the locations referred to
** by the dataPaths.$ 7(2) qualifiers, which are a list of query expressions which are combined
** by the adaptor into a single expression. The qualifiers can be built
** from any combination of objects that recognize the stringValue message,
** although its common to use DBString, DBExpressionList, DBDataWrap,
** and DBDataGuide objects as the basic building blocks. The qualifiers
** reside in the DBDataSet for the binder.
*/
@interface DBBinder : Object <DBCursorPositioning>
{
@public
id database; // remote source of info
id recordPrototype; // the template object (an instance)
id container; // the repository for recordPrototype copies
id delegate; // receive channel notification, if used
@private
id _qualifier; // the DBQualifier (optional)
id _properties; // a list of DBDataPaths or DBAttributes
id _mappingsByProperty; // HashTable of BinderMappings by dataPath
id _private;
mutex_t _protoLock; // lock for recordPrototype access
condition_t _dataAvailable;// condition for async fetch
cthread_t _fetchThread; // thread that implements async fetch
void *_fetchMsg; // mach message used in async fetch
unsigned _currentPosition; // for cursoring
unsigned _fetchLimit; // to control number of rows fetched
struct {
BOOL abortFlag:1; // used by cancel
BOOL flushEnabled:1; // whether container is emptied on success
BOOL freeOnFlush:1; // whether to free objects in container on flush
BOOL limitHit:1; // fetchLimit has been exceeded
BOOL fetchDone:1; // when caching, only one fetch done per eval
BOOL ignoreDuplicates:1; // select distinct, versus select all
BOOL sharesContext:1; // shared transaction context for selects
BOOL ownsRecordPrototype:1; // recordPrototype was created by binder
int _RESERVED:8;
} _flags;
NXZone *_tempZone; // recursive structures built here for easy free
NXZone *_protoZone; // zone for dynamically created objects
}
- init;
- initForDatabase:aDb
withProperties:(List*)aList andQualifier:(DBQualifier*)aQualifier;
- free;
/*
** Setting the database allows you to use the database for transaction
** control and as a source of data. It also has a default data dictionary,
** used by $ 8mically created protoClasses.
*/
- setDatabase:(DBDatabase*)aDatabase;
- (DBDatabase*)database;
/*
** getProperties fills a List based on the binder's mappings;
** the order is the same order as the "target list" in the query.
** Both the list and the properties that are contained in it
** should not be freed, since they are not copied.
**
** IMPORTANT! setProperties: causes a reset of the entire binder!
**
** setProperties: returns the new recordPrototype object
**
** addProperty is used to describe a class to be built on the fly.
** The class is then used to create a recordPrototype instance that is used as
** the binder's recordPrototype. This is an alternative to using
** setRecordPrototype. createRecordPrototype will automatically be called
** by data-producing methods.
**
** Calling addProperty or removeProperty should eventually be followed
** by a call to createRecordPrototype. Because of this, you normally want to
** call reset before the first addProperty.
**
** addProperty: either creates a new mapping or returns the pre-existing
** mapping for the argument. createRecordPrototype returns new
** recordPrototype or nil.
*/
- (List*)getProperties:(List*)aList;
- (List*)setProperties:(List*)aList;
- addProperty:anObject;
- removePropertyAt:(unsigned)index;
- createRecordPrototype;
/*
** These are the main feature...and are pretty self explanatory. Evaluate
** is in fact the routine that does all the work usually -- the other verbs
** take the binder and format it into the expected query language.
**
** DBBinder is basically a cursor into NextStep -- fetch will load the objects
** that are contained in the DBDataWraps in the mappings with the next "row"
** of data.
**
** Note that a select will normally do a fetch, but that an evaluate leaves
** it to the programmer to do a fetch if necessary. Also note that it is
** good practice to call cancelFetch: when finished, since many databases
** dedicate expensive runtime structures to a binder, which can be reclaimed
** upon cancelFetch.
*/
- insert;
- select;
- update;
- delete;
- (BOOL)evaluateString:(const unsigned char*)aString;
- fetch;
- cancelFetch;
/*
** Having used the DBCursorPositioning methods for positioning, here is the
** method to set/retrieve values from the recordPrototype. The DBValue
** pointer that is r$ 9ned is owned by the binder, and will change
** frequently. It is not safe to keep references to one of these DBValues
** lying around; you should retrieve it immediately before use.
**
** Note that not all properties in the binder will have a value,
** since some properties can be embedded in the qualifier tree,
** rather than having a binding associated with them. In this case, the call
** will return nil.
**
** Also note that there is a maxiumum of one value for a given
** property. This means that complex qualifiers MUST use the qualifier
** mechanism -- the mechanism by which a binding can be used as a qualifier
** is really meant for qualified updates based on a previous select.
*/
- (DBValue*)valueForProperty:(id<DBProperties>)aProperty;
/*
** Adaptors that support sorted results can utilize these methods. Multiple
** property sorts are handled in the order in which they are added.
*/
- addRetrieveOrder:(DBRetrieveOrder)anOrder for:(id<DBProperties>)aProperty;
- removeRetrieveOrderFor:(id<DBProperties>)aProperty;
- (DBRetrieveOrder)retrieveOrderFor:(id<DBProperties>)aProperty;
- (unsigned)positionInOrderingsFor:(id<DBProperties>)aProperty;
/*
** The binder delegate can act as a control, denying or approving
** operations. Any of the methods that return BOOL will either confirm or
** deny the operation in the head binder. For example, if binderWillInsert
** returns NO from the delegate, the insert will not be executed.
*/
- delegate;
- setDelegate:anObject;
- read:(NXTypedStream*)s;
- write:(NXTypedStream*)s;
@end
@interface Object (BinderDelegate)
- (BOOL)binderWillInsert:aBinder;
- binderDidInsert:aBinder;
- (BOOL)binderWillSelect:aBinder;
- binderDidSelect:aBinder;
- (BOOL)binderWillUpdate:aBinder;
- binderDidUpdate:aBinder;
- (BOOL)binderWillDelete:aBinder;
- binderDidDelete:aBinder;
- (BOOL)binder:aBinder willEvaluateString:(const unsigned char*)aString;
- binder:aBinder didEvaluateString:(const unsigned char*)aString;
- (BOOL)binderWillFetch:aBinder;
- binderDidFetch:aBinder;
@end
@interface DBBinder (Advanced)
/*
** Data is moved from an adaptor into a binder through the use of objective-C
** objects. The binder's recordPrototype object acts as a shuttle between the
** external database and the binder.
**
** If there is no recordPrototype object, but the proto class $ @been set, an
** object of that class will be created; a subclass will be created if there
** are more results than the recordPrototype can handle.
**
** If no recordPrototype or protoclass has been specified, a class which will
** suffice is created dynamically. This class defaults to being a subclass
** of Object, although the superclass, can be specified using
** setDynamicRecordSuperclassName.
**
** To turn off the effect of these methods, set their arguments to NULL.
** These should not be used without a good understanding of the dynamic
** class creation protocol.
**
** Note that these apply globally to all binders using dynamic class creation!
*/
+ setDynamicRecordSuperclassName:(const char*)aName;
+ setDynamicRecordClassName:(const char*)aName;
/*
** Reset clears the binder, and frees any internal structures that it had
** created. If you passed your own ids, its up to you to free them, including
** the recordPrototype, the properties, and the set. (Internal
** DBDataPaths and DBDataWraps are freed, but this does not free the
** objects that are wrapped inside!)
**
** scratchZone returns an NXZone pointer that can be used to allocate objects
** that will then be freed en masse whenever a reset is done to the binder.
** This can be a very efficient way of allocating numerous support objects
** that exist for a single query. Note that the zone returned will vary
** from reset to reset!
**
** Flush is called by objects that return data (typically adaptors). Use
** setFlushEnabled and setFreeObjectsOnFlush to affect its behavior.
*/
- reset;
- (BOOL)flush;
- (NXZone*)scratchZone;
/*
** setQualifier returns the old qualifier -- it does not cause a reset.
*/
- setQualifier:(DBQualifier*)aQualifier;
- (DBQualifier*)qualifier;
/*
** setRecordPrototype is useful for filling existing classes with data from a
** database. Pass a prototypical object in, do the query, and then use
** the container full o' newly minted objects. setRecordPrototype: is usually
** followed by one or more calls to associateXXX:
**
** The recordPrototype is used as a conduit for data from the adaptor to
** the binder. When the newly stuffed recordPrototype arrives, the
** appropriate parts are distributed to any "external mappings" that exist.
** Because of this, external mappings must always have a val$ Aeference
** to the recordPrototype. (This is managed automatically by the binder.)
*/
- setRecordPrototype:anObject;
- recordPrototype;
- (BOOL)ownsRecordPrototype;
/*
** These establish which selectors or instance variables in a custom
** recordPrototype will be mapped onto the database.
*/
- associateRecordIvar:(const char*)ivar
withProperty:(id<DBProperties>)aProperty;
- associateRecordSelectors:(SEL)set :(SEL)get
withProperty:(id<DBProperties>)aProperty;
/*
** The container holds the objects to be submitted to the database, or the
** objects that result from a query. setContainer: returns the old container
** so that it can be freed.
**
** In order to use asynchronous fetching of data safely, the container should
** either be threadsafe, or all access to the container should be through
** the binder. (setTo, etc.)
**
** Providing a container turns on "caching" of data -- setting it to nil
** turns it off.
**
** Containers provide random access positioning -- these routines return
** an id, which corresponds to the "current object".
**
** All of these methods, except setNext:, will raise an exception if there
** is no container.
**
** If there is an asyncFetch going on, these calls will BLOCK until the
** requested "row" is available. These are actually the preferred interface
** for getting at the results of an asynchronous fetch while the fetch
** is in progress.
**
** All of the positioning methods except for setNext: (which works in all
** cases) will raise an exception if there is no container.
*/
- setContainer:(id<DBContainers>)anObject;
- (id<DBContainers>)container;
/*
** This regulates whether the container is emptied on every data generating
** message, and if it is emptied, then whether the objects in the container
** are freed.
*/
- setFlushEnabled:(BOOL)yn;
- (BOOL)isFlushEnabled;
- setFreeObjectsOnFlush:(BOOL)yn;
- (BOOL)areObjectsFreedOnFlush;
/*
** fetchAsync forks a thread and returns.
**
** It then continues to stuff results into the container until fetchData:
** returns nil. Because of this, its possible to have an adaptor that could
** be streaming results back, while accepting changes through evaluate: or
** other calls... This will be particularly handy with news feeds, etc.
**
** It is, however, fatal to change the structure of the binder wil$ Be
** fetch thread is running -- this means that only the non-structural calls
** are safe, such as cancelFetch:
**
** fetchInThread will fetch data into the container in a separate thread.
** There must be a container for this routine to work. If this is used
** from a non-NeXTstep program, then checkThreadedFetchCompletion: can be
** used to sync with the thread.
*/
- selectWithoutFetching;
- fetchInThread;
- checkThreadedFetchCompletion:(double)timeout;
/*
** This can be called by an adaptor -- the default behavior is to defer to
** the database, who in turn defers to its delegate. This method is
** called immediately before an expression is evaluated. If NO is returned,
** the expression is cancelled. Normally, this method is called for every
** query expression evaluated.
*/
- (BOOL)adaptorWillEvaluateString:(const unsigned char*)aString;
/*
** If you'd only like only unique rows for the qualifier (and the adaptor
** supports this) then setIgnoreDuplicateResults:YES
*/
- setIgnoresDuplicateResults:(BOOL)yn;
- (BOOL)ignoresDuplicateResults;
/*
** Databases that support "protected" access through the notion of
** transaction processing have a "cursor" that can be shared among a number
** of binders. The default is for this "cursor" to be used by any data
** modifying operations (like insert, delete, or update). Select, evaluate,
** and fetch, however, get their own "cursors" by default. To change this
** behavior for a binder, set this flag to YES. The binder will then use
** the shared "cursor" even for these operations.
**
** NOTE HOWEVER: when using a single shared resource, all operations must fully
** complete before the next is invoked. (selects and updates could not be
** interleaved, for instance...) The default behavior permits a looser
** ordering than this, but is also potentially more expensive in terms of
** resources.
**
** ALSO NOTE: [setSharesContext:YES] will turn OFF flushing for a binder!
**
** By setting the context to be shared, "select for update" can be implemented,
** in which the selected items are locked until updated.
**
** [someBinder setSharesContext:YES];
** [someDatabase beginTransaction];
** [someBinder select:self];
** ...processing here...
** [someBinder update:self];
** [someDatabase endTransaction];
*/
- setSharesContext:(BOOL)$ C- (BOOL)sharesContext;
/*
** A sanity check for retrieving data -- note that multiple fetches can
** be performed to "continue" an operation that was stopped because of the
** limit. This limit only applies when a container is in place and a
** synchronous fetch is used.
*/
- (unsigned)maximumRecordsPerFetch;
- setMaximumRecordsPerFetch:(unsigned)aRecordCount;
- (BOOL)recordLimitReached;
@end
@interface List (DBContainers)
- addObject:anObject forBinder:(DBBinder*)aBinder;
- (unsigned int)prepareForBinder:(DBBinder*)aBinder;
- objectAt:(unsigned int)index forBinder:(DBBinder*)aBinder;
@end