This is DBBinder.h in view mode; [Download] [Up]
/* ** 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 <ansi/stdarg.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. ** (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 { #if __BIG_ENDIAN__ 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; #else int _RESERVED:8; BOOL ownsRecordPrototype:1; /* recordPrototype was created by binder*/ BOOL sharesContext:1; /* shared transaction context for selects*/ BOOL ignoreDuplicates:1; /* select distinct, versus select all*/ BOOL fetchDone:1; /* when caching, only one fetch done per eval*/ BOOL limitHit:1; /* fetchLimit has been exceeded*/ BOOL freeOnFlush:1; /* whether to free objects in container on flush*/ BOOL flushEnabled:1; /* whether container is emptied on success*/ BOOL abortFlag:1; /* used by cancel*/ #endif } _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 dynamically 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 returned 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 has 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 valid reference ** 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 will the ** 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)yn; - (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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.