ftp.nice.ch/pub/next/developer/languages/c/gcc.2.7.2.2.N.b.tar.gz#/lib/gcc-lib/m68k-next-nextstep3/2.7.2.2.f.2/include/dbkit/DBBinder.h

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.