home *** CD-ROM | disk | FTP | other *** search
- /*
- ** 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
-