                          WELCOME TO BORLAND SQL LINK
                              CONNECTING TO ORACLE
                          ---------------------------

This file contains important, late-breaking information about Borland
SQL Link, including revisions to the documentation.  Information in this
file supersedes information in the SQL Link documentation.


TABLE OF CONTENTS
-----------------

1. Installation Information
2. Tips for using SQL Link
3. SQL Link documentation notes
4. ObjectPAL tips for SQL Link applications



1. INSTALLATION INFORMATION
---------------------------

During installation, SQL Link overwrites any version of ORA6WIN.DLL with 
the version supplied on the SQL Link Installation Disk.  However, the ver-
sion supplied with SQL Link is NOT the latest version of this .DLL file.

If you are currently using the latest version of this .DLL, make a backup 
copy of ORA6WIN.DLL before you install SQL Link.  Once SQL Link installa-
tion is complete, replace the older version of ORA6WIN.DLL installed by SQL
Link with your later version.

If you need the latest version of ORA6WIN.DLL, please contact Oracle.



2.  TIPS FOR USING SQL LINK
---------------------------

NUMBER CONVERSION WHEN ORA7WIN.DLL IS USED.  Due to a known problem with
ORA7WIN.DLL, you may experience problems with number conversion or cor-
ruption when using ORACLE with SQL Link.  (For example, you may be unable
to delete a record while in a form from an ORACLE table with a numeric
field.)  Using a version of ORA6WIN.DLL fixes this problem.

If you experience problems with number conversion or corruption, contact
your ORACLE sales representative.


USING ALIASES WITHOUT SPECIFYING A LANGUAGE DRIVER.  If no language
driver is specified, the default language driver is ASCII.  Language-
specific processes like character translation and table name validation 
will default to U.S. rules.  For further information, see the section
on Borland language drivers in your Connecting To... manual.



WORKING WITH LARGE TABLES.  If you create data entry forms on large tables 
you may find some performance degradation -- particularly if the user is 
allowed to view unrestricted record sets.  The following suggestions may 
help cut down the load on your server and help optimize server performance:

-  With large data sets, it is good programming practice to write applica-
   tions which access relatively few records at a time.  One way to do this
   within a form-based application is to create forms which order the data
   by index and set RANGE criteria to limit the record set.  This can be 
   done interactively using the ORDER/RANGE dialog, but can also be con-
   trolled by the application developer.  (For example, instead of creating
   a form which displays all orders for all customers, write your applica-
   tion to limit the user's working selection by criteria such as customer
   name, state, and area code.)

-  In ORACLE, forms which are created for the purpose of entering or
   modifying data do not need to be indexed.  However, if you view your 
   data in INDEX order, navigation and update speed will improve.  (Use the
   form's ORDER/RANGE dialog box to set this up.)

-  Where possible, create applications which order on fields which were
   created with the REQUIRED (NOT NULL) option.  Since this field
   property must be specified at the time of table creation; be sure to 
   plan for this as part of your application design.



3. SQL LINK DOCUMENTATION NOTES
-------------------------------

THIS RELEASE SUPPORTS ORACLE SQL*NET SPX FOR WINDOWS 1.1.1.8 OR LATER.


SPECIFYING THE ORACLE "VENDOR INIT" PARAMETER.  The ORACLE Init parameter 
"VENDOR INIT" specifies the name of the vendor .DLL to be used in a mixed-
mode environment.  Unless you specify a particular .DLL this parameter
will be blank, and SQL Link will search for an appropriate .DLL to use. It
will first search for ORA6WIN.DLL, then ORA7WIN.DLL.



SQL LINK USER'S GUIDE, PAGE 13.  Two additional points for the subsection
on "Using Table windows" are: (1) The .TVS file is not automatically dele-
ted when the SQL table is deleted, and (2) If you change your private dir-
ectory the table will no longer be displayed with the properties you set.


SQL LINK USER'S GUIDE, PAGE 14.  Add the following note before the para-
graph that begins with "For detailed information..."

     NOTE:  The above tables are only generated if the query is processed
            within the QBE environment.  For further information, see
            "QBE query processing," in Chapter 3.


SQL LINK USER'S GUIDE CHAPTER 4.  Chapter 4, "SQL-enabled ObjectPAL," does
not document the transactionsActive() database method or the 
update(fname,fvalue [,fname,fvalue]) tcursor method.  For information
about these two methods, see NEWPAL.DB. 


SQL LINK USER'S GUIDE, PAGE 29.  The description of the syntax for 
beginTransaction includes the parameter ([const isoLevel String]).  This
parameter is not supported in this release of SQL Link.


SQL LINK USER'S GUIDE, PAGES 29 - 31.  Tables 4.5 and 4.6 are not sorted
by object type.


SQL LINK USER'S GUIDE, PAGE 30.  Table 4.5, "Standard ObjectPAL methods
that support SQL Link," includes the sort table method.  The sort method
belongs in Table 4.6, "Standard ObjectPAL methods that do not support
SQL Link." 


SQL LINK USER'S GUIDE, PAGE 37.  peOptRecLockFail should be
peOptRecLockFailed. Error constant is incorrect.


SQL LINK USER'S GUIDE, PAGE 40.  The steps for rebinding forms discussed
on this page of the User's Guide should be corrected as follows:

1.  Copy Paradox tables to the SQL server.

2.  Index the server tables to duplicate the linking and ordering indexes
    you had on your original tables.

3.  Before you rebind the form, copy it to a different subdirectory so that
    Paradox for Windows will not be able to find the original tables when
    you open the form.

4.  Change your working directory to the new subdirectory and Open the form
    in Design Mode.

5.  When the original Paradox tables cannot be found, you will be allowed 
    to replace those tables in the data model with the equivalent SQL 
    tables.

6.  In some cases, you may need to fix up the links between tables in the
    Data Model dialog.  To do this, simply unlink all tables and re-link
    on the appropriate indexes.

7.  Recreate all calculated fields.

8.  Update all hard-coded table names in ObjectPAL code.

9.  Save the form.


3. OBJECTPAL TIPS FOR SQL LINK APPLICATIONS
-------------------------------------------

This section supplies some tips for creating ObjectPAL applications for 
use in an SQL environment.  For more information on how to create SQL 
applications, see TechFax Bulletin #1677, "Developing Applications Using 
SQL Link."


USING FORMS TO EDIT SQL TABLES.  By default, SQL tables are marked read-
only when added to a form's data model.  This prevents novice users from
inadvertently editing these tables.  To edit an SQL table interactively, 
use a form and make sure the SQL table's Read-Only property is un-checked
in the form's data model.

To change a table's Read-Only property, open the form in a Design window,
click the Data Model SpeedBar button (or choose Form|Data Model) to dis-
play the Data Model dialog box, and then right-click on the table in the 
data model diagram to inspect it.


CUSTOMIZING BEHAVIOR FOR SQL TABLES.  While Paradox's interactive editing 
features are effective for most situations, there are times when you'll 
want to enhance or override Paradox's built-in behavior.  To do this ef-
fectively, use the action method and trap the action you're interested in.
The following code sample shows most of the actions you'll be concerned 
with:

   method action(var eventInfo ActionEvent)
   var
      actionId smallInt
   endVar

      actionId = eventInfo.id()
      Switch
         Case actionId = DataInsertRecord : CustomInsertCode()
         Case actionId = DataDeleteRecord : CustomDeleteCode()
         Case actionId = DataLockRecord   : CustomLockCode()
         Case actionId = DataUnlockRecord : CustomUnlockCode()
         Case actionId = DataPostRecord   : CustomPostCode()
         Case actionId = DataCancelRecord : CustomCancelCode()
      endSwitch
   endmethod

In this example, custom methods are called when one of the desired actions
takes place.  To use this code in an application, create custom methods 
with the names shown above (and then place your code in the custom 
methods) or replace the custom method names with the code you want
to use.  These actions are useful for any table type (whether SQL, 
Paradox, or dBASE) and let you control almost every record-oriented
action a user can generate.


WHERE TO PLACE CODE.  The code sample shown above works best 
on the record object of a table frame or multi-record object (MRO).  
You can also place it on a table frame or MRO object directly, but this 
is not recommended as the actions are record-level actions.

If you do not want to use a table frame or MRO, then place code in the 
Prefilter clause of the form's action method.  For example:

   method action(var eventInfo ActionEvent)
   var
      actionId smallInt
   endVar

   if eventInfo.isPreFilter()
      then
     ; This code executes for each object on the form.

      actionId = eventInfo.id() 
      Switch
         Case actionId = DataInsertRecord : CustomInsertCode()
         Case actionId = DataDeleteRecord : CustomDeleteCode()
         Case actionId = DataLockRecord   : CustomLockCode()
         Case actionId = DataUnlockRecord : CustomUnlockCode()
         Case actionId = DataPostRecord   : CustomPostCode()
         Case actionId = DataCancelRecord : CustomCancelCode()
      endSwitch

   else
     ; This code executes only for the form.
     
   endIf
   endmethod

For better performance, use table frames or multi-record objects whenever
possible.  (To display one record at a time, use a single record MRO.)


HOW ACTIONS ARE GENERATED.  When customizing Paradox's built-in behavior,
remember that actions can be generated in a variety of ways.  For example,
the DataDeleteRecord action can be caused by pressing Ctrl+Delete, or 
choosing Record|Delete.  The following paragraphs show how some actions 
are generated:

     DATAINSERTRECORD is generated when you press Insert, choose 
     Record|Insert or start typing into the empty record at the end of 
     the table when the Auto-Append property is true.

     DATADELETERECORD is generated when you press Ctrl+Delete, choose 
     Record|Delete.

     DATALOCKRECORD is generated when you press F5, choose Record|Lock,
     or when you enter values into an unlocked record.  When working with
     SQL tables, remember that SQL uses optimistic locking, which means 
     that tables are not locked until a transaction is attempted.  

     DATAUNLOCKRECORD is generated when you press Shift+F5, choose 
     Record|Unlock, or move off a locked record or a new record being 
     inserted.  If the record can't be posted (because it generates a key 
     violation, for example), this action fails.  This action can also be 
     generated by moving to an object bound to a different table or by
     exiting Edit mode (using F9 or Form|End Edit).

     DATAPOSTRECORD is generated when you press Ctrl+F5, choose
     Record|Post/Keep Locked, or move from a locked master record to an
     unlocked detail record in a table frame.  This action means "save the
     record, but keep it locked" and is an effective way to work with
     record flyaway.  (For more information about flyaway, see the Object-
     PAL User's Guide.)

     DATACANCELRECORD is generated when you press Alt+Backspace, choose 
     Edit|Undo, choose Record|Cancel Changes, or leave a locked record 
     that has not been changed.  This action cancels unsaved changes and 
     unlocks a record.


DETERMINING EDITING STATES WITH PROPERTIES.  For best results, your code
should be aware of various editing states and react accordingly.  Use an 
object's properties to determine the editing state and how to react.  The
following properties are useful:

     READONLY indicates whether or not the object can be edited.

     EDITING (on a form, tableFrame, MRO or record) indicates whether or 
     not Paradox is in Edit mode.

     LOCKED indicates whether an object has been locked.  Because SQL Link
     files use optimistic locking, this property indicates that a record
     has been internally locked by Paradox; the record is not locked on 
     the SQL server until a transaction is attempted.

     TOUCHED (on a form, tableFrame, MRO or record) indicates whether a
     user has edited any value in a record or field.  For a field object,
     TOUCHED means the field itself is in the process of being changed.
     The record doesn't get touched until you move off that field.

     INSERTING indicates whether or not the current record is being
     inserted into a table.  This record does not yet exist in the table.
     It will be inserted into the table when the DataUnlockRecord or
     DataPostRecord action is generated.

Examples of how to use these properties effectively can be found in
"Enhancing Default Behavior," later in this file.


DEFAULT ACTION BEHAVIOR.  When Paradox receives one of the actions des-
cribed in the above code sample, it performs some default behavior.  The
behavior is executed when you issue the DoDefault command or when the
action method completes execution.

Following is a description of the default behavior of some actions:

     DATAINSERTRECORD checks for errors for the current record (if locked) 
     and makes sure it's appropriate to leave that record.  If the record 
     can be departed, then this action inserts a temporary, unsaved record
     into the form you're viewing.  If you depart this record before mak-
     ing changes, it is deleted.  If you enter values into the new rec-
     ord's fields, the record is saved when you depart it.

     DATADELETERECORD removes the current record from the underlying table.
     If you're working with Paradox or SQL tables, the data in the table 
     is permanently removed from the table.  (dBASE tables support "soft-
     deletes," which let you undelete a record at a later time.  For more 
     information, see the Paradox User's Guide.)

     DATALOCKRECORD locks the record in the underlying table.  If you're 
     working with Paradox or dBASE tables, this prevents other users from 
     changing or deleting the record. However, SQL tables use "optimistic" 
     locking. which means a record isn't locked until a transaction is 
     attempted.  To work with this, Paradox indicates that an SQL record 
     has been locked internally, but the actual record is not locked in 
     the table until you commit changes or start a transaction.

     DATAUNLOCKRECORD commits (saves) changes made to a record and removes
     the lock.  If you change a key value that causes the record to appear 
     in a different location in the table, the record "flies away" to the
     new location.  If this happens and you don't like the resulting visu-
     al behavior, use DataPostRecord to save the record and keep it locked.
     If the record cannot be saved, this action fails and the lock is re-
     tained.

     DATAPOSTRECORD commits changes to a record and retains the lock.  If
     you change a record's key values, the record still "flies" to the new 
     location in the table, but the form is synchronized to that new loca-
     tion.  This lets you save a record while making sure the saved record
     is still selected and displayed.

     DATACANCELRECORD abandons any changes to a record and removes the 
     lock.  Visually, it restores the record to its last saved values, re-
     moves the  "Locked" message in the status bar, and displays a mes-
     sage.  If you execute the default behavior, this action cannot fail.

     Additionally, actions generate internal actions to refresh (repaint) 
     the screen and perform related internal functions.


DISABLING DEFAULT ACTION BEHAVIOR.  Because actions also perform internal
processing (such as repainting the screen), do not use the disableDefault
command to prevent (block) the default behavior from executing.  Instead,
use the eventInfo.setErrorCode() method.  This prevents the data-oriented 
behavior from taking place and lets Paradox perform any required internal
processing.

The setErrorCode() method requires an error constant.  If you use an
error constant related to blocking the action, a warning message appears
in the status line.  To prevent an action without displaying an error 
message, use eventInfo.setErrorCode(UserError).  The following table 
shows error constants that display messages appropriate for specific 
actions and the messages that appear:

     peCannotDelete         Unable to delete.
     peCannotEdit           You cannot modify this field.
     peCannotEditField      This field cannot be edited.
     peCannotInsertRecord   Cannot insert record here.
     peCannotLock           Cannot lock record.
     peCannotPerformAction  Unable to perform action.
     peDataLoss             Character(s) not supported by table language.
     peDeadlock             A deadlock was detected.
     peFieldIsBlank         Field is blank.
     peNoActiveTransaction  No active transaction to commit or rollback.
     peNotLocked            Object not locked.
     peOptRecLockFailed     Record lock failed.
     peOptRecLockRecDel     Record lock failed; record deleted.
     peUnlockFailed         Unlock failed.

Paradox supports a variety of other error constants that may be useful.
Use enumRTLConstants to generate a list of these constants.


ENHANCING DEFAULT BEHAVIOR.  For the most part, your applications will
enhance the default behavior of an action or set of actions.  For example,
you might cascade record edits (or deletions) from one table to a set of
related tables.  This section contains tips, techniques, and ideas for 
enhancing specific actions.

In general, you'll want to place enhancement code after an action's de- 
fault behavior executes (e.g. after a doDefault statement.)  For example,
the following code sample shows one way to set the default value of a 
field when a record is inserted:

   method action(var eventInfo ActionEvent)
      
      if eventInfo.id() = DataInsertRecord then
         doDefault
         If eventInfo.errorCode() = 0 then
            Orders.Sale_Date.Value = Today()
         endIf
      endIf

   endMethod

In this example, Orders is an SQL table containing a field called Sale 
Date, which holds the date a sale is made.  The above code waits for the 
user to insert a new record and then defaults the Sale Date field to the 
current date, provided a record was inserted properly.  (For Paradox 
tables, it's better to use the Default validity check.  For more informa-
tion, see the Paradox User's Guide.)

There are times when you'll want to execute enhancement code before the
default behavior of an action executes.  The following example shows one
way to save a record that will be deleted, so it can be undeleted later.

   method action(var eventInfo ActionEvent)
      if eventInfo.id() = DataDeleteRecord then
         self.copyToArray(savedRec)
         doDefault
      endIf
   endMethod

In this example, the code has been placed on the record object of a table
frame or a multi-record object.  SavedRec is a resizable dynamic array
(dynArray) of AnyType that's been defined in a Var window of an 
appropriate object.  To incorporate this into an application, you'll need
to include a mechanism to "undelete" the record.  (One such mechanism uses
the pushButton method of a button object to insert a blank record and the
copyFromArray method to restore the original record.)

The following table provides tips and techniques for most of the actions
you'll work with regularly:

     DATAINSERTRECORD enhancements should be placed after a doDefault
     statement, unless you want to prevent this action in some cases.
     If you want to initialize fields, be sure to do this after the
     default behavior executes, as shown in an earlier code sample.
     Also, use eventInfo.errorCode() to make sure a record was 
     inserted.  If the table is not in Edit mode, the table is read-
     only, or the user has insufficient access rights to the table or a 
     field in the table, errorCode() contains an appropriate error code 
     and a message appears in the status line.  While Paradox checks 
     these conditions automatically, you can use errorCode() to display 
     messages to the user or to react to the error condition.

     DATADELETERECORD enhancements can take many forms and are 
     generally placed before the default behavior executes.  You can 
     display a confirmation dialog, save the record so it can be 
     undeleted later, or cascade deletions to other tables in a single 
     transaction.  If your code deletes the record in question, be sure 
     to block the built-in behavior; otherwise, two records will be de-
     leted (the one you wanted to delete and the next one in the table).

     DATALOCKRECORD enhancements should be placed after a doDefault state-
     ment.  Use the following code to make sure the lock was placed cor-
     rectly:

       if eventInfo.id() = dataLockRecord then
           doDefault
          if eventInfo.errorCode() = 0 then
             CustomLockCode()
          else
             CustomLockFailedCode()
          endIf
       endIf

     To perform related error checking before the default behavior 
     executes, use something like this:

        if eventInfo.id() = dataLockRecord then
           if not self.Editing or self.readOnly then
              eventInfo.setErrorCode(UserError)
          Else
             CustomLockCode()
           endIf
        Endif

     This approach may require you to block the default behavior of this 
     action.  Use care.

     DATAUNLOCKRECORD and DATAPOSTRECORD enhancements are similar and can
     be made before or after the default behavior, depending on what you
     want to do.  For example, you might perform some field-level valida-
     tion checks before unlocking a record or you might recalculate a
     summary field after saving a record.

     These actions can be generated for records that are not locked, for
     records that were locked and not modified, and for records that were
     appended to the end of a table.  To work with these conditions, use
     an approach like this:

        if actionID = dataUnlockRecord or actionID = dataPostRecord then
           if self.Touched then
              if self.Inserting then
                 CustomNewRecordCode()
              else
                CustomSaveRecordCode()
              endIf
           endIf
       endIf

     This example lets Paradox handle the cases where these actions occur
     on unlocked or unmodified records and lets you handle new records 
     differently from changed records.  If the record wasn't locked, a 
     silent error is generated.  If the record wasn't modified, Paradox 
     releases the lock.

     If you use TCursors, pass-through SQL, or another non-UIObject 
     oriented approach to post changes to a table, set the Touched prope-
     rty of the record to False before executing the default behavior.  
     This releases the lock quietly and prevents Paradox from trying to 
     post changes later.  An example of this approach is:

        If eventInfo.id() = dataUnlockRecord then
           tc.attach(self)
          ; other code
          tc.postRecord()
          self.Touched = False
         doDefault
       endIf

     If you do not reset the Touched property and use doDefault, you may
     generate endless loops (as Paradox tries to post your changes) or 
     other errors (as Paradox tries to remove the lock from an internal
     action).

     DATACANCELRECORD enhancements are generally limited to preventing
     users from canceling changes.  This is best accomplished by using
     eventInfo.setErrorCode(UserError), but there are other approaches.
     If you use an alternate approach, use DoDefault to remove the lock;
     otherwise, you may receive unexpected results.  (Unlike 
     DataUnlockRecord or DataPostRecord, the Touched property is not 
     helpful with this action.)


To find out more about these actions, turn on Trace Execution and Trace
Builtins for the action() method (as described in the ObjectPAL User's 
Guide).  This tracer enables you to see the general flow of events through
the system, which can help you better understand what specific actions 
your code needs to address.


DEADLOCK RECOVERY.  Deadlock errors can occur for any number of reasons,
depending on what kind of locking mechanism your SQL server supports.  
Since the server might lock an individual record, a group of records, 
or even a page of records, deadlock errors can effectively prevent record 
updates.

Three new error codes have been implemented to signal a SQL server update 
failure:

     peOptRecLockFailed -- another user has modified a record but not 
     changed the key field in the record

     peOptRecLockRecDel -- another user has deleted or modified the key
     field in a record

     pcDeadlock -- the server detects a deadlock situation


When you want to change a value in a record on a server table, a record 
lock is acquired, the record is updated, and the record is unlocked. 
If the record is modified between the time the lock was acquired and the
time the lock was released, the unlock will fail and the record will
remained locked.  To recover from the failure you must cancel the changes, 
re-read the server record and try the update again.

In interactive mode, updates are canceled using Alt+Bksp (cancel changes).
Once the change is canceled, the latest server records are read 
automatically.

In ObjectPAL, updates are canceled using the canceledit() method.  Once
the change is canceled, the latest server records must be explicitly
read by executing the tcursor class method ForceRefresh().
The following example shows how a typical ObjectPAL custom method could be 
written to modify a SQL server record.

;
; CustomUpdate
;
; Global vars declared on the form page
;    customerTc tcursor   
; User Defined Data type
;    dynarraytype = dynarray[] anytype
;
method customUpdate(localarray dynarraytype )

   var
      serverarray     dynarraytype
   endvar

; Determine what the user wants to do:
;   Update the server record
;   Cancel update
;   View the server record vs proposed updates

; View the server and local records
 customerTc.copytoArray(serverarray)
 serverarray.view("Server Record")
 localarray.view("Local Record")

   ans = msgYesNoCancel("Question", "Update current record?")

; Update the record
   if ans = "Yes" then
      customerTc.edit()
      customerTc.lockRecord()
      ; User2 updates the record and commits a change

      ; Modify record using a global array
      if not customerTc.copyFromArray(localArray) then
              errorshow()
      endif
             
      ; Unlock Record - updating the server record.
      if not customerTc.unlockRecord() then
         msginfo("Update Failure", "Recovery in progress")

         ; Optimistic Record Lock Lost error trap
         if errorHasErrorCode(peOptRecLockFailed) then
            customerTc.copytoArray(serverarray)
            customerTc.cancelEdit()
            customerTc.ForceRefresh()
            customerTc.currRecord()
            customUpdate(serverarray) ; try update again
         else
                  ; trap some other error 
         endif
       else
              msginfo(" ","Record Updated")
              customerTc.endedit()
       endif
    else
       message("Edit Canceled")
    endif

endmethod


Other helpful deadlock tips:

1. ForceRefresh will refresh all views and ObjectPAL cursors accessing
   the table that is refreshed.

2. RollbackTransaction() will refresh all tables involved in the 
   transaction.

3. When a record is locked (interactively or with an explicit record
   lock in ObjectPAL), the locked record will not reflect the server's
   current record when refreshing until the lock is released. 

