Copyright ©1995 by NeXT Computer, Inc.  All Rights Reserved.

DBRecordStream



Inherits From: Object
Declared In: dbkit/DBRecordStream.h



Class Description

The DBRecordStream class defines an object that allows sequential access to the records stored in a database server. Normally, you create instances of DBRecordStream's subclass, DBRecordList.  A DBRecordList object can fetch and store many records at a time, and provides random access into its "record table."



Setting Up a DBRecordStream

You create a new DBRecordStream object in the usual way, by sending alloc and init messages.  Before you can use a DBRecordStream to access records in a database, you must specify the source of the data (say, the "authors" table of an SQL database) and the properties (for example, name, address, and telephone number) that are to be fetched from that source.  The setProperties:ofSource: method lets you do both.

id database, authors, recordStream, propertyList;

database = [DBDatabase findDatabaseNamed:"pubs" connect:YES];
authors = [database entityNamed:"authors"];
recordStream = [[DBRecordList alloc] init];
propertyList = [[List alloc] init];

[authors getProperties:propertyList];
[recordStream setProperties:propertyList ofSource:authors];

To allow modification of records in the database, a DBRecordStream must know the key property (or properties) for the source. A key property uniquely identifies individual records within the source.  For example, within a table of employee data, the employee's identification number uniquely identifies the records.  Typically, the model created by DBModeler identifies the key properties of the data sources, but you can set them directly using setKeyProperties:.

Optionally, you can specify that the records be returned in sorted order.  Sending an addRetrieveOrder:for: message to the DBRecordStream associates a sorting order with a property.  These messages are additive; for example:

id lastName, firstName;

firstName = [authors propertyNamed:"au_fname"];
lastName = [authors propertyNamed:"au_lname"];
[recordStream addRetrieveOrder:DB_AscendingOrder for:lastName];
[recordStream addRetrieveOrder:DB_AscendingOrder for:firstName];

The records will be retrieved in alphabetical order according to the authors' last names.  For authors having identical last names, the retrieval order will be determined by first names.



Fetching Data

A DBRecordStream accesses data in the database when it is sent a fetchUsingQualifier: message.

[recordStream fetchUsingQualifier:nil];

If the qualifier argument is nil, all records within the source will be made available through the DBRecordStream.  If you supply a qualifier, only the set of records meeting its restrictions (for example, "au_lname = `Smith'") will be made available.



Accessing Data in the DBRecordStream

After receiving a fetchUsingQualifier: message, the DBRecordStream can be queried for record data.  The first record returned by the fetch operation is available immediately; the second and subsequent records can be accessed by sending the DBRecordStream setNext messages.

You access the data within a record indirectly, through DBValue objects.  The getValue:forProperty: method causes the DBRecordStream to set a DBValue object's value equal to a specified property in the current record:

id authors, state, recordStream, value;

state = [authors propertyNamed:"state"];
value = [[DBValue alloc] init];

[recordStream getValue:value forProperty:state];
printf("state: %s\n", [value stringValue]);


Modifying Records

The data in the DBRecordStream's current record can be modified using the setValue:forProperty: method.  The current record can be deleted by invoking deleteRecord.

To add a new record to the DBRecordStream, you first create an empty record by sending a newRecord message.  The DBRecordStream responds by using its current set of properties (as returned by getProperties:) to create an empty record. Once the empty record has been created, you can set the values for its properties as you would any record.

These modifications, deletions, and additions only affect the current record in the DBRecordStream.  To reflect these changes in the database itself, you must send the DBRecordStream a saveModifications message. If the database being accessed supports transactions, they should always be enabled before saving modifications.  In general, it's both safer for the integrity of the data involved and much more efficient to do this.

Emptying, initing, or fetching records into the DBRecordStream (or DBRecordList) resets  it to an "unmodified" state.  After that, modifications are tracked until the DBRecordList is refilled or it receives a saveModifications message.



Responding to Notification that a Modification Will Fail

A DBRecordStream (or its subclass DBRecordList) notifies its delegate of the impending failure of an operation that would modify, delete, or add records to the database.  The delegate receives a recordStream:willFailForReason: message. It can then take action to review the condition that caused the failure. In some circumstances, it can refuse to accept the failure.

Saving a record (or a set of records) happens in two stages.  First the records are verified. Then they are written out to the database.  If a failure occurs during the verification stage, the application can choose to abort the transaction.  Having the delegate return YES to the notification recordStream:willFailForReason: means that the delegate assents to the failure, and permits the entire save to fail.  (This failure doesn't, of itself, abort the transaction of which the save is part.)  Alternatively, the application can pretend that the verification succeeded and let the save proceed.

If a failure occurs during the writing stage, here again the delegate can either return YES (thereby assenting to the failure and aborting the operation), or it can return NO (thereby skipping the particular record for which writing failed but going ahead with writing the others).  If you choose to have the delegate return NO, you may be left with a situation in which the record's "modified" flag is set and so is the "modified" flag for its DBRecordStream or DBRecordList, but the offending record is nevertheless unsaved, and the transaction will nevertheless continue, commit, and return success.

Warning: Before having the delegate return NO to recordStream:willFailForReason:, you should be very sure this is what you want it to do.  Returning NO permits what looks like successful completion of a save, despite the fact that some of the application's data still differs from the data in the database.
For failures denoted by the failure codes DB_NoRecordKey or DB_RecordStreamNotReady, there isn't anything you can do to keep going. In those situations, the method fails regardless of what the delegate returns.



Instance Variables

id delegate;

id source;

id properties;

id database;


delegate The object that responds to notification messages
source The database entity from which records are to be retrieved
properties The list of properties of records to be retrieved
database The DBDatabase object that owns the record stream



Method Types

Initializing and freeing init
free
Setting up a DBRecordStream addRetrieveOrder:for:
setProperties:ofSource:
getProperties:
setKeyProperties:
getKeyProperties:
Fetching data fetchUsingQualifier:
cancelFetch
currentRetrieveStatus
Accessing data getValue:forProperty:
getRecordKeyValue:
setNext
Modifying data setValue:forProperty:
newRecord
isNewRecord
deleteRecord
isModified
isReadOnly
Saving modifications saveModifications
Resetting a DBRecordStream clear
Assigning Delegates delegate
setDelegate:
binderDelegate
setBinderDelegate:



Instance Methods

addRetrieveOrder:for:
addRetrieveOrder:(DBRetrieveOrder)anOrder for:(id <DBProperties>)aProperty

Associates a retrieval order with the property aProperty.  The permissible values of anOrder are:

Constant Meaning
DB_NoOrder Remove ordering associated with aProperty
DB_AscendingOrder Sort records in ascending order of the values in aProperty
DB_DescendingOrder Sort records in descending order of values in aProperty

You can specify sort orders for multiple properties by sending multiple addRetrieveOrder:for: messages; the sorts will be nested.  For example, assume you specify an ascending order for a property associated with employee names and a descending order for a property associated with employee salaries.  Records will be retrieved in alphabetical order based on the employee's last name and, for employees having the same last name, will be ordered in descending numerical order based on salaries.

If an addRetrieveOrder:for: message hasn't been sent to a DBRecordStream object, it retrieves records in ascending order of the first property in its property list.

Returns a nil if an error occurs; otherwise, returns self.

See also:  getProperties:



binderDelegate
binderDelegate

Returns the delegate used by the DBRecordStream's DBBinder objects.

See also:  setBinderDelegate:



cancelFetch
cancelFetch

Terminates the current fetch operation; this is generally only of use if the DBRecordStream is fetching in the background. Returns self.

See also:  fetchUsingQualifier:



clear
clear

Resets the DBRecordStream.  The DBRecordStream's record data, list of properties, and list of key properties are emptied.  Its database instance variable is set to nil, but its delegate remains unchanged.  Its status is set to DB_NotReady.  Returns self.

See also:  currentRetrieveStatus, free



currentRetrieveStatus
(DBRecordRetrieveStatus)currentRetrieveStatus

Returns the DBRecordStream's status, which can be:

Constant Meaning
DB_NotReady Not ready to fetch or insert data
DB_Ready Ready to fetch or insert data
DB_FetchInProgress Fetch in progress; more records are available
DB_FetchCompleted Fetch finished; no more records remain



delegate
delegate

Returns the DBRecordStream's delegate or nil if no delegate has be set.

See also:  setDelegate:, recordStream:willFailForReason: (delegate method)



deleteRecord
deleteRecord

Deletes the current record in the DBRecordStream and causes the DBRecordStream to access the next record in sequence, if any.

Returns nil if the deletion can't be accomplished; otherwise, returns self.  If the deletion fails, the DBRecordStream will attempt to notify its delegate of the reason, and the cursor remains unchanged (pointing to the record that should have been deleted but wasn't).

See also:  recordStream:willFailForReason: (delegate method)



fetchUsingQualifier:
fetchUsingQualifier:(DBQualifier *)aQualifier

Selects data from the database and makes it available to the DBRecordStream.  The scope of records retrieved from the database is controlled by aQualifier.  For example, assuming the data source is an SQL database, aQualifier could be an object that represents the expression "where name = `Holbein'". If aQualifier is nil, all records in aSource are selected. The argument aQualifier and the current property list must refer to the same entity; otherwise an error occurs.

In case of error, this method makes the DBRecordStream's list of properties empty, and returns nil.  Otherwise, returns self.

See also:  cancelFetch, setProperties:ofSource:



free
free

Releases the storage for the DBRecordStream.



getKeyProperties:
(List *)getKeyProperties:(List *)keyList

Fills keyList with objects that represent the key properties of the DBRecordStream.   Each of these objects conforms to the DBProperties protocol.  Returns the newly filled List object.

See also:  setKeyProperties:



getProperties:
(List *)getProperties:(List *)propertyList

Places the DBRecordStream's property list in propertyList and returns propertyList.

See also:  setProperties:ofSource:



getRecordKeyValue:
getRecordKeyValue:(DBValue *)aValue

Places the value of the current record's key property (or properties) in aValue.

This method is especially useful when data must be exchanged between DBRecordStreams.  For example, suppose one DBRecordStream supplies employee information and another supplies department information to the user interface of an application.  A user can change an employee's department by selecting from a list of department names.  After a department name is selected, you can use getRecordKeyValue: to determine the corresponding record's key value so that you can set the department identification in the employee's record.

Returns nil if the DBRecordStream has status DB_NotReady; otherwise, returns aValue.



getValue:forProperty:
getValue:(DBValue *)aValue forProperty:aProperty

Places the value for aProperty into aValue.  This method is the only means of retrieving record data stored in the DBRecordStream.

When aProperty is a relationship, the method sets aValue so that it includes the key value of the relationship's source property and the entity that is the relationship's target.  (In that case, sending aValue the DBValues message isEntity  would get the response YES.)  The fact that the value object identifies the target entity is exploited by the method setProperties:ofSource:.

If the status of the DBRecordStream is DB_NotReady, this method return nil.  Otherwise, it returns the DBValue object.

See also:  setValueFor:from:, propertyNamed: (DBDatabase), isEntity (DBValues protocol), setProperties:ofSource:



init
init

Initializes and returns a newly allocated DBRecordStream.  The DBRecordStream's delegate instance variable is set to nil and its retrieve status is set to DB_NotReady.

This method is the designated initializer for DBRecordStream.



isModified
(BOOL)isModified

Returns YES if the current record has been modified since it was added to the DBRecordStream or fetched from the database; NO otherwise.

See also:  isNewRecord



isNewRecord
(BOOL)isNewRecord

Returns YES if the current record is new; that is, it the result of the DBRecordStream receiving a newRecord message.

See also:  newRecord, isModified



isReadOnly
(BOOL)isReadOnly

Returns YES if the records in the DBRecordStream can only be read, not modified.  If a DBRecordStream's key properties haven't been set, isReadOnly will return YES.

See also:  setKeyProperties:, getKeyProperties:



newRecord
newRecord

Creates a new, empty record.  Before this operation can take place, the DBRecordStream attempts to save modifications of the current record to the database.  If these changes can't be saved, newRecord returns nil, no new record is created, and the cursor is not advanced.  Otherwise, newRecord returns self, and the cursor is advanced to make the new record the current record.

See also:  saveModifications



saveModifications
(unsigned int)saveModifications

Saves the new or modified record to the database.  If the database supports transactions and there's no transaction in progress, this save operation is nested within a new transaction.

If there is no transaction in progress, a new transaction is created for this operation.  If the modifications can be made to the database, this transaction is committed.  An error during this commit process raises a DB_TRANSACTION_ERROR exception.

Returns these values:

Value Reason
1 The save operation was successful.
0 There were no modifications to save.
DB_NoIndex Either the DBRecordStream isn't ready (its status is DB_NotReady or DB_NoRecordKey), or the record in the database has changed since it was fetched and the delegate hasn't forced the modification to be saved.  (See recordStream:willFailForReason:)

If the attempt to save modifications fails, the delegate is notified by sending it a recordStream:willFailForReason: message, and the DBRecordStream's internal cursor is not advanced to the next record.

See also:  areTransactionsEnabled (DBDatabase), beginTransaction (DBDatabase)



setBinderDelegate:
setBinderDelegate:newDelegate

Sets the delegate for the DBRecordStream's DBBinder objects.  This delegate can intercede in operations that would add or modify the database.  See the DBBinder class specification for more information.

See also:  binderDelegate



setDelegate:
setDelegate:anObject

Sets the DBRecordStream's delegate.  Returns self.

See also:  delegate, recordStream:willFailForReason: (delegate method)



setKeyProperties:
(List *)setKeyProperties:(List *)propertyList

Sets the DBRecordStream's list of key properties to propertyList.  Each of the objects in propertyList must conform to the DBProperties protocol.  Typically, key properties are identified in the database model using DBModeler, so you rarely invoke this method.

Returns nil if any property in propertyList is not a property of the DBRecordStream's source; otherwise, returns the property list.

See also:  getKeyProperties:



setNext
setNext

Advances the DBRecordStream's internal cursor by 1, so that it points to the next record in the group of records made available by a fetch operation.

Returns self if successful and nil if not.  A nil return can mean that there are no further records to return or that the DBRecordStream was unable to save modifications to the current record.

See also:  saveModifications



setProperties:ofSource:
(List *)setProperties:(List *)propertyList ofSource:aSource

Sets the properties that will be fetched or stored by a DBRecordStream, or its subclass, a DBRecordList.  The properties transferred will be those contained in propertyList. The argument aSource specifies the entity that contains the properties; this is typically a DBEntities object that's the "root" of all the properties in the property list.  If aSource is nil, the entity for the first property in propertyList is used.

The argument aSource can also be a DBValue object that's gotten by asking for the value for a relationship from a data-storage object that has already fetched data.  The DBValue object encodes the relationship's attribute equivalence such that when the receiving DBRecordStream fetches, it qualifies the fetch to select the "detail" records for the "master" record from whence the DBValue was plucked.

The application should send a setProperties:ofSource: message before doing anything with a DBRecordStream or DBRecordList.  Once the list of properties has been set, the application can send fetchUsingQualifier: messages, based on the list of properties that has been set.  To a DBRecordList, the application can also send fetchUsingQualifier:empty:, or can make multiple inserts or multiple deletes.   (After once calling setProperties:ofSource:, you shouldn't call it again until you really need to establish a new property list, since each use discards any prior data without saving.)

Returns nil if the properties in propertyList don't share the same entity or if some other error occurs; otherwise, returns self.

See also:  getProperties:, getValue:forProperty:, isEntity (DBValues protocol)



setValue:forProperty:
setValue:(DBValue *)aValue forProperty:aProperty

Sets the value for aProperty in the current record to that contained in aValue.  Returns a nonzero value if successful; otherwise, returns nil.

See also:  getValue:forProperty:



Methods Implemented by the Delegate

recordStream:willFailForReason:
(BOOL)recordStream:sender willFailForReason:(DBFailureCode) aCode

Responds to a message informing the delegate that a modification couldn't be saved to the database.   In general, returning YES to this message acknowledges the failure and permits the operation to be aborted, thereby aborting the local transaction of which it is part.

Note:  If the local transaction is nested within another transaction, it is the application's responsibility to either rollback or commit the outer transaction.

Returning NO skips the specific record involved but permits the operation to continues the processing of others records (if any).

The aCode argument identifies the reason for the failure and can have the following values:

Constant Meaning
DB_RecordHasChanged The record in the database has changed since it was fetched by the DBRecordStream. Saving the modification would overwrite someone else's changes.  Returning YES to this message acknowledges the failure and permits the operation to be aborted.  Returning NO skips the record and continues with the others.
DB_RecordKeyNotUnique More than one record in the database corresponds to the record in the DBRecordStream that is being updated or deleted.    Returning YES to this message acknowledges the failure and permits the operation to be aborted.  Returning NO permits a delete to proceed with the other records, but can't help an update, since update is never permitted with an ambiguous key.
DB_RecordStreamNotReady The DBRecordStream isn't ready for this operation (its status is DB_NotReady).  The boolean return value of the message is ignored.
DB_NoRecordKey The modification couldn't be saved because no property (or combination of properties) within the record was identified as the record key.  The boolean return value of the message is ignored.
DB_AdaptorError The modification couldn't be saved because of some sort of error reported by the adaptor. Returning YES to this message acknowledges the failure and permits the operation to be aborted.   Returning NO skips the record and continues with the others.

See also:  saveModifications, setDelegate:, delegate



recordStreamPrepareCurrentRecordForModification:
(BOOL)recordStreamPrepareCurrentRecordForModification:aRecordStream

Notifies the delegate of a proposed modification to the current record, verifies that the record is unique, and permits modification to proceed only if the return is YES.

If implemented, this delegate method provides an alternative to the standard check that a DBRecordStream performs before deleting or modifying a record. (The DBRecordStream or its subclass normally verifies that a record still exists, and that it is unique.  It invokes a "confirming select" on the DBDatabase using the key value, and then compares all properties to see that none has changed.  The select is usually a locking select.)  This delegate method replaces that mechanism, making the delegate responsible for verification and locking.  If the method returns YES, the record is considered to be verified, and modification proceeds.  If the method returns NO, the record is not modified, which may cause the entire sequence containing saveModifications: to fail, depending on the transaction model being used.

This method should not call any of the methods implemented by DBRecordStream or DBRecordList other than getValue:forProperty: