/***********************************************************************
*
*N  {versioning_sample.c}  --  Examples of programming vs a versioned instance
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This program provides examples of programming against a versioned
*   instance.  Two versions are created, seperately edited, then
*   reconciled.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*X  Script:
*
* 1. High-Level Overview
*
*     Each of the following steps will be broken down further below.
*
*  o  Create a base table;
*  o  User 1 creates a version and edits the base table in several states;
*  o  User 2 creates a version and edits the base table in several states;
*  o  User 1 reconciles his and User 2's versions.
*
*
* 2. Create a Base Table
*
*      A user-named table with 60 rows and the following schema will
* be created:
*
*      DATA            SE_STRING_TYPE(64)
*      CATEGORY        INTEGER
*
* The DATA column will contain strings of the form: "Original Row 1",
* "Original Row 2", etc.  The CATEGORY column will evenly divided
* between 1's and 2's.  This table will be given an ArcSDE-maintained row
* id column (ROW_ID) and made a layer (with a spatial column named
* SHAPE).  Simple squares will be generated for shapes.  Once it is
* built, it will also be made multiversion.
*
*
* 3.  User 1's Edit Session
*
*      User 1 will start by creating a state that is the child of the
* state of the DEFAULT version, and a version named "User 1" which is a
* child of the DEFAULT version.
*
*      User 1 will perform two updates, a delete and an insert, then
* close the current state, and create a child state.  This will be
* repeated four times to simulate an edit session with save points.
* Updates will have DATA fields with values of the form: "Updated Row 1,
* at State 3".  Inserted rows will have DATA fields of the form: "Row
* Inserted at state 3".
*
*      To simulate a rollback/undo operation, the last state will be
* deleted.
*
*      The version 'User 1' will be move to the last state in the chain.
*
*      The edit session will be compressed using trim to reduce it from
* three states to one.
*
*
* 4.  User 2's Edit Session
*
*      User 2 will start by creating a state that is the child of the
* state of the DEFAULT version, and a version named "User 2" which is a
* child of the DEFAULT version.
*
*      User 2 will perform two updates, a delete and an insert, then
* close the current state, and create a child state.  This will be
* repeated four times to simulate an edit session with save points.
* About 1/2 of the rows will be rows also modified by User 1.  Updates
* will have DATA fields with values of the form: "Updated Row 1, at
* State 3".  Inserted rows will have DATA fields of the form: "New Row
* 34, at state 3".
*
*      The version 'User 2' will be move to the last state in the chain.
*
*      The edit session will be compressed using trim to reduce it from
* four states to one.
*
*
* 5.  User 1 Reconciles with User 2.
*
*      User 1 will create a new state to receive the results of the
* reconciliation.  The following reconcilation queries will be made:
*
* NoChange/Delete from User 1 to User 2.  Any unmodified rows in User 1
* that were deleted in User 2 and have a CATEGORY of 1 will be deleted.
*
* Nochange/Update from User 1 to User 2.  Any rows updated in User 2 and
* not in User 1 and having a CATEGORY of 1 will be copied over.
*
* Insert from User 2 to User 1.  Any rows inserted into User 2 will be
* copied to User 1.
*
* Update/Delete from User 2 to User 1.  Any rows deleted in User 1 but
* updated in User 2 will be copied from User 2 to User 1.
*
*      Finally, the version "User 1" will move moved to the new,
* reconciled state, the state closed, and trim performed to remove the
* old state.
*
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
* How to compile:: 
*	 Please refer section "Build C Samples" in the SDE sample
*
*E
*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Usage:
*     vrsn_editing <table>   <user>   <password>   {server} {instance} {database}
*
*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*X  Legalese:
*
* Copyright � 2007 ESRI
*
* All rights reserved under the copyright laws of the United States and 
* applicable international laws, treaties, and conventions.
*
* You may freely redistribute and use this sample code, with or without 
* modification, provided you include the original copyright notice and use 
* restrictions.  
*
* Disclaimer:  THE SAMPLE CODE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 
* WARRANTIES, INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ESRI OR 
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
* OR BUSINESS INTERRUPTION) SUSTAINED BY YOU OR A THIRD PARTY, HOWEVER CAUSED 
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
* TORT ARISING IN ANY WAY OUT OF THE USE OF THIS SAMPLE CODE, EVEN IF ADVISED 
* OF THE POSSIBILITY OF SUCH DAMAGE.
* 
* For additional information, contact:
* Environmental Systems Research Institute, Inc.
* Attn: Contracts and Legal Services Department
* 380 New York Street
* Redlands, California, 92373
* USA
* 
* email: contracts@esri.com
*
*E
****************************************************************************/
#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <time.h>  
#ifdef unix
#  include <unistd.h>  
#elif defined (WIN32)
#  include <winsock.h>  
#endif
 
#include "sdetype.h"
#include "sdeerno.h"
 
/* Constants. */
 
#define NUMBER_OF_ROWS  60
#define MAX_EDIT_DEPTH  6
#define USER_1_VERSION  "User 1"
#define USER_2_VERSION  "User 2"
 
/* Function macros */
 
#define print_error(c,s) \
   S_print_error (c,s,__LINE__)
 
#define print_conn_error(h,c,s) \
   S_print_conn_error (h,c,s,__LINE__)
 
#define print_stream_error(st,c,s) \
   S_print_stream_error (st,c,s,__LINE__)
 
/* Type definitions. */
 
typedef enum {
  RE_DELETE_ROWS_FOUND,
  RE_COPY_ROWS_FOUND
} RE_ACTION;
 
/***********************************************************************
*
*N  {S_print_error}  --  Print an ArcSDE error.
*
***********************************************************************/
static void S_print_error (LONG        code,
                           const CHAR  *str,
                           LONG        line_number)
{
  LONG  result;
  CHAR  errstr[SE_MAX_MESSAGE_LENGTH];
 
  result = SE_error_get_string (code,errstr);
  if (SE_SUCCESS != result) strcpy (errstr,"Unknown error code");
  printf ("Error: %s (%d).\n",errstr,code);
  printf ("Error: %s\n",str);
  printf ("Near line number: %d\n",line_number);
  return;
}
 
/***********************************************************************
*
*N  {S_print_conn_error}  --  Print an ArcSDE connection error.
*
***********************************************************************/
static void S_print_conn_error (SE_CONNECTION  handle,
                                LONG           code,
                                const CHAR     *errstr,
                                LONG           line_number)
{
  SE_ERROR  error;
 
  S_print_error (code,errstr,line_number);
  if (code == SE_DB_IO_ERROR) {
    if ((SE_connection_get_ext_error (handle,&error)) == SE_SUCCESS) {
      if (error.err_msg1[0] != NULL) {
        printf ("%s\n",error.err_msg1);
        if (error.err_msg2[0] != NULL)
          printf ("%s\n",error.err_msg2);
      }
      else
        printf ("DBMS error (%d)\n",error.ext_error);
    }
  }
  return;
}
 
/***********************************************************************
*
*N  {S_print_stream_error}  --  Print an ArcSDE stream error.
*
***********************************************************************/
static void S_print_stream_error (SE_STREAM  stream,
                                  LONG       code,
                                  CHAR       *errstr,
                                  LONG       line_number)
{
  SE_ERROR  error;
 
  S_print_error (code,errstr,line_number);
  if (code == SE_DB_IO_ERROR) {
    if ((SE_stream_get_ext_error (stream,&error)) == SE_SUCCESS) {
      if (error.err_msg1[0] != NULL) {
        printf ("%s\n",error.err_msg1);
        if (error.err_msg2[0] != NULL)
          printf ("%s\n",error.err_msg2);
      }
      else
        printf ("DBMS error (%d)\n",error.ext_error);
    }
  }
  return;
}
 
/***********************************************************************
*
*N  {S_compare_ids}  --  Compare a pair of row ids for sorting.
*
***********************************************************************/
static int S_compare_ids (const void  *id_ptr_1,
                          const void  *id_ptr_2)
{
  LONG  id_1 = *(LONG *)id_ptr_1;
  LONG  id_2 = *(LONG *)id_ptr_2;
 
  if (id_1 >   id_2) return (1);
  if (id_1 < id_2) return (-1);
  return (0);
}
 
/***********************************************************************
*
*N  {S_create_base_table}  --  Create the base table and contents for test
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This function creates a non-multiversion table with three columns
*   (DATA, CATEGORY and SHAPE), and places NUMBER_OF_ROWS rows in it.
*   Every row receives a unique DATA string, a CATEGORY of 1 or 2, and
*   a unique square polygon.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection  <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                               handle; used to request information and
*                               actions from the server.
*     table_name  <Input>    ==  (const CHAR *) The name of the base table
*                               to create.
*     RETURN     <Output>    ==  (LONG) Error return: SE_SUCCESS on success,
*                               an appropriate ArcSDE error code on
*                               failure.
*E
***********************************************************************/
static LONG S_create_base_table (SE_CONNECTION  connection,
                                 const CHAR     *table_name)
{
  LONG           result,row,category;
  SE_STREAM      stream;
  CHAR           data[64],*columns[3];
  SE_LAYERINFO   layer;
  SE_COORDREF    coordref;
  SE_SHAPE       shape;
  SE_ENVELOPE    rectangle;
 
  SHORT          data_ind = SE_IS_NOT_NULL_VALUE;
  SHORT          category_ind = SE_IS_NOT_NULL_VALUE;
  SHORT          shape_ind = SE_IS_NOT_NULL_VALUE;
  SE_COLUMN_DEF  column_def[] = {{"data",SE_STRING_TYPE,64,0,FALSE},
                                 {"category",SE_INTEGER_TYPE,0,0,TRUE}};
  
  /* Create the table. */
 
  result = SE_table_create (connection,table_name,(SHORT)2,column_def,
                            "DEFAULTS");
  if (SE_SUCCESS != result) {
    CHAR  error_msg[SE_MAX_MESSAGE_LENGTH];
 
    sprintf (error_msg,"Unable to create table.",table_name);
    print_conn_error (connection,result,"Unable to create table");
    return (result);
  }
 
  /* Give the table a layer (column name SHAPE). */
 
  result = SE_coordref_create (&coordref);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create coordref object");
    return (result);
  }
  result = SE_coordref_set_xy (coordref,0.0,0.0,10000.0);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_coordref_set_xy");
    return (result);
  }
 
  result = SE_layerinfo_create (coordref,&layer);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create layerinfo object");
    return (result);
  }
  result = SE_layerinfo_set_grid_sizes (layer,1000.0,0.0,0.0);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_layerinfo_set_grid_sizes");
    return (result);
  }
  result = SE_layerinfo_set_shape_types (layer,
                                         SE_AREA_TYPE_MASK|SE_NIL_TYPE_MASK);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_layerinfo_set_shape_types");
    return (result);
  }
  result = SE_layerinfo_set_spatial_column (layer,table_name,"shape");
  if (SE_SUCCESS != result) {
    print_error (result,"SE_layerinfo_set_spatial_column");
    return (result);
  }
 
  result = SE_layer_create (connection,layer,0,0);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to create layer.");
    return (result);
  }
 
  SE_layerinfo_free (layer);
 
  /* Set up to insert NUMBER_OF_ROWS rows. */
 
  result = SE_shape_create (coordref,&shape);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create shape object");
    return (result);
  }
  SE_coordref_free (coordref);
 
  result = SE_stream_create (connection,&stream);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to create stream.");
    return (result);
  }
 
  result = SE_connection_start_transaction (connection);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"SE_connection_start_transaction");
    return (result);
  }
 
  columns[0] = column_def[0].column_name;
  columns[1] = column_def[1].column_name;
  columns[2] = "shape";
  result = SE_stream_insert_table (stream,table_name,(SHORT)3,
                                   (const CHAR **)columns);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_insert_table");
    return (result);
  }
 
  result = SE_stream_set_write_mode (stream,TRUE /* Buffered. */);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_set_write_mode");
    return (result);
  }
 
  result = SE_stream_bind_input_column (stream,1,data,&data_ind);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_bind_input_column (data)");
    return (result);
  }
 
  result = SE_stream_bind_input_column (stream,2,&category,&category_ind);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_bind_input_column (category)");
    return (result);
  }
 
  result = SE_stream_bind_input_column (stream,3,shape,&shape_ind);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_bind_input_column (shape)");
    return (result);
  }
 
  /* Perform the actual inserts. */
 
  for (row = 0;row < NUMBER_OF_ROWS;row++) {
 
    /* Generate data. */
 
    sprintf (data,"Base row %d",row + 1);
    category = row % 2 + 1;
    rectangle.minx = 1000.0 + row * 100.0;
    rectangle.miny = 1000.0;
    rectangle.maxx = 1100.0 + row * 100.0;
    rectangle.maxy = 1100.0;
    result = SE_shape_generate_rectangle (&rectangle,shape);
    if (SE_SUCCESS != result) {
      print_error (result,"Unable to generate rectangle into shape object");
      break;
    }
 
    /* Insert it. */
 
    result = SE_stream_execute (stream);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_execute (insert)");
      return (result);
    }
  }
 
  /* Done, clean up. */
 
  result = SE_stream_free (stream);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_free");
    return (result);
  }
 
  result = SE_connection_commit_transaction (connection);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Error in commit.");
    return (result);
  }
 
  SE_shape_free (shape);
 
  /* Done. */
 
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {S_make_table_multiversion}  --  Make our test table multiversioned.
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This function takes the table created by S_create_base_table(),
*   and makes it multiversion and gives it a ArcSDE-maintained unique,
*   integer row id column (ROW_ID).
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection  <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                               handle; used to request information and
*                               actions from the server.
*     table_name  <Input>    ==  (const CHAR *) The name of the table to
*                               make multiversion.
*     RETURN     <Output>    ==  (LONG) Error return: SE_SUCCESS on success,
*                               an appropriate ArcSDE error code on
*                               failure.
*E
***********************************************************************/
static LONG S_make_table_multiversion (SE_CONNECTION  connection,
                                       const CHAR     *table_name)
{
  LONG        result;
  SE_REGINFO  registration;
 
  /* Fetch the existing registration for the table (the table will always
     have an existing registration entry because all ArcSDE-created tables
     are automatically registered).  */
 
  result = SE_reginfo_create (registration);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create REGINFO object.");
    return (result);
  }
   
  result = SE_registration_get_info (connection,table_name,registration);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to fetch registration");
    return (result);
  }
 
  /* Update the registration entry to give it an ArcSDE-maintained row id
     column (named ROW_ID), to make it multiversion, and to give it a
     meaningful description.  */
 
  result = SE_reginfo_set_description (registration,
                                       "Version operations demo table.");
  if (SE_SUCCESS != result) {
    print_error (result,"SE_reginfo_set_description");
    return (result);
  }
  result = SE_reginfo_set_rowid_column (registration,"row_id",
                                        SE_REGISTRATION_ROW_ID_COLUMN_TYPE_SDE);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_reginfo_set_rowid_column");
    return (result);
  }
  result = SE_reginfo_set_multiversion (registration,TRUE);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_reginfo_set_multiversion");
    return (result);
  }
 
  /* Make the actual changes to the registration. */
 
  result = SE_registration_alter (connection,registration);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to modify registration.");
    return (result);
  }
 
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {S_create_edit_table}  --  Create a table to edit
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This function creates our example edit table, which will have
*   60 rows in the base state, and be multiversioned.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection  <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                               handle; used to request information and
*                               actions from the server.
*     table_name  <Input>    ==  (const CHAR *) The name of the edit table
*                               to create.
*     RETURN     <Output>    ==  (LONG) Error return: SE_SUCCESS on success,
*                               an appropriate ArcSDE error code on
*                               failure.
*E
***********************************************************************/
static LONG S_create_edit_table (SE_CONNECTION  connection,
                                 const CHAR     *table_name)
{
  LONG  result;
 
  /* Delete the table in case it is pre-existing. */
 
  (void) SE_table_delete (connection,table_name);
 
  /* Create an unversioned table with data. */
 
  result = S_create_base_table (connection,table_name);
  if (SE_SUCCESS != result) return (result);
 
  /* Make it multiversioned. */
 
  result = S_make_table_multiversion (connection,table_name);
 
  return (result);
}
 
/***********************************************************************
*
*N  {S_create_version}  --  Create the version used for the edit run
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This function creates a uniquely named version to be used for the
*   edit session.  It starts pointing to the base state of the instance.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection    <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                                 handle; used to request information and
*                                 actions from the server.
*     version_name  <Input>    ==  (const CHAR *) The name for the version
*                                 to create.  (This may not be the actual
*                                 name of the version created, to insure
*                                 uniqueness, ArcSDE may append a suffix
*                                 to the version name.)
*     version      <Output>    ==  (SE_VERSIONINFO *) The resulting version.
*     RETURN       <Output>    ==  (LONG) Error return: SE_SUCCESS on success,
*                                 an appropriate ArcSDE error code on
*                                 failure.
*E
***********************************************************************/
static LONG S_create_version (SE_CONNECTION   connection,
                              const CHAR      *version_name,
                              SE_VERSIONINFO  *version)
{
  LONG  result,result2;
 
  /* Create the empty version object. */
 
  result = SE_versioninfo_create (version);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create VERSIONINFO object.");
    return (result);
  }
 
  /* Fetch the default version: we need it's state id. */
 
  result = SE_version_get_info (connection,SE_QUALIFIED_DEFAULT_VERSION_NAME,
                                *version);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to fetch default version");
    exit (EXIT_FAILURE);
  }
 
  /* Change our copy of the default version into the version we want to
     create. */
 
  result = SE_versioninfo_set_description (*version,
                                           "Editing demonstration version.");
  if (SE_SUCCESS != result) {
    print_error (result,"SE_versioninfo_set_description");
    return (result);
  }
  result = SE_versioninfo_set_name (*version,version_name);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_versioninfo_set_name");
    return (result);
  }
  result = SE_versioninfo_set_parent_name (*version,
                                           SE_QUALIFIED_DEFAULT_VERSION_NAME);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_versioninfo_set_parent_name");
    return (result);
  }
 
  /* Create the version in the database (if it already exists, we will delete
     it; in a user-driven application we might instead call SE_version_create()
     with the unique flag set to TRUE, and ArcSDE would add a suffix to our
     supplied version name in order to make it unique).  */
 
  (void) SE_version_delete (connection,version_name);
 
  result = SE_version_create (connection,*version,FALSE,*version);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to create version.");
    return (result);
  }
 
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {S_create_child_state}  --  Create a child state of the supplied state
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     Given an existing state, this function creates a child of that
*   state, and returns its description.  If the parent state is open, it
*   is closed first.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection    <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                                 handle; used to request information and
*                                 actions from the server.
*     parent_state  <Input>    ==  (SE_STATEINFO) The state to be the parent
*                                 of the new state.
*     new_state    <Output>    ==  (SE_STATEINFO *) The newly created child
*                                 state of the parent state.
*     RETURN       <Output>    ==  (LONG) Error return: SE_SUCCESS on success,
*                                 an appropriate ArcSDE error code on
*                                 failure.
*E
***********************************************************************/
static LONG S_create_child_state (SE_CONNECTION  connection,
                                  SE_STATEINFO   parent_state,
                                  SE_STATEINFO   *new_state)
{
  LONG  result,parent_state_id;
 
  /* Set up. */
 
  result = SE_stateinfo_get_id (parent_state,&parent_state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  /* If the parent state is open, close it.   Only open states can be written
     to, and only closed states can have child states. */
 
  if (SE_stateinfo_is_open (parent_state)) {
    result = SE_state_close (connection,parent_state_id);
    if (SE_SUCCESS != result) {
      CHAR  error_msg[SE_MAX_MESSAGE_LENGTH];
 
      sprintf (error_msg,"Unable to close state %d.",parent_state_id);
      print_conn_error (connection,result,"Unable to fetch registration");
      return (result);
    }
  }
 
  /* Create the child state and its local object. */
 
  result = SE_stateinfo_create (new_state);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create STATEINFO object.");
    return (result);
  }
 
  result = SE_state_create (connection,*new_state,parent_state_id,*new_state);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to create state.");
    SE_stateinfo_free (*new_state);
    *new_state = (SE_STATEINFO)NULL;
    return (result);
  }
 
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {S_edit_state}  -- "Edit" the specified table in the specified state
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This function simulates a set of edits on a table in a state.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection    <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                                 handle; used to request information and
*                                 actions from the server.
*     state_id      <Input>    ==  (LONG) The id for the state in which the
*                                 edits are to occur.
*     table_name    <Input>    ==  (const CHAR *) The name of the table which
*                                 to edit.
*     delete_count  <Input>    ==  (LONG) The number of rows to delete.
*     delete_list   <Input>    ==  (const LONG *) The list of row ids for rows
*                                 to be deleted.
*     update_count  <Input>    ==  (LONG) The number of rows to update.
*     update_list   <Input>    ==  (const LONG *) The list of row ids for rows
*                                 to be updated.
*     insert_list   <Input>    ==  (LONG) The number of rows to insert.
*     RETURN       <Output>    ==  (LONG) Error return: SE_SUCCESS on success,
*                                 an appropriate ArcSDE error code on
*                                 failure.
*E
***********************************************************************/
static LONG S_edit_state (SE_CONNECTION  connection,
                          LONG           state_id,
                          const CHAR     *table_name,
                          LONG           delete_count,
                          const LONG     *delete_list,
                          LONG           update_count,
                          const LONG     *update_list,
                          LONG           insert_count)
{
  LONG           result,row_id,*sorted_update_list,row,category;
  SE_STREAM      stream;
  CHAR           data[64],*columns[3];
  SE_LAYERINFO   layer;
  SE_COORDREF    coordref;
  SE_SHAPE       shape;
  SE_ENVELOPE    rectangle;
 
  SHORT          data_ind = SE_IS_NOT_NULL_VALUE;
  SHORT          category_ind = SE_IS_NOT_NULL_VALUE;
  SHORT          shape_ind = SE_IS_NOT_NULL_VALUE;
  int			 random_factor = 0;
 
  /* Set up our stream and transaction environment. */
 
  result = SE_connection_start_transaction (connection);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"SE_connection_start_transaction");
    return (result);
  }
  result = SE_stream_create (connection,&stream);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to create stream.");
    return (result);
  }
 
  result = SE_stream_set_state (stream,
                                state_id,
                                SE_NULL_STATE_ID,
                                SE_STATE_DIFF_NOCHECK);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_set_state");
    return (result);
  }
 
  /* Perform the deletes. */
 
  if (delete_count >   0) {
    result = SE_stream_delete_by_id_list (stream,
                                          table_name,
                                          (LONG *)delete_list,
                                          delete_count);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_delete_by_id_list");
      return (result);
    }
 
    result = SE_stream_close (stream,FALSE /* Don't reset. */);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_close");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
  }
 
  /* Perform the update.   We are going to use SE_stream_update_ordered(),
     which is the most efficient update function to use for versioned data*;
     but requires that rows be updated in row_id order (ascending).  If this
     is not practical, SE_stream_update_row() should be used instead (or if
     updating by other than row_id, SE_stream_update_table()). 
 
     (*) For unversioned data, SE_stream_update_row() is as efficient.  */
 
  /* Create a sorted version of the update list. */
 
  if (update_count >   0) {
    sorted_update_list = (LONG *) malloc (update_count * sizeof(LONG));
    if ((LONG *)NULL == sorted_update_list) {
      CHAR  msg[80];
 
      sprintf (msg,"Unable to allocate %d bytes for sorted update list.",
               update_count * sizeof(LONG));
      print_error (SE_OUT_OF_CLMEM,msg);
      (void) SE_connection_rollback_transaction (connection);
      return (SE_OUT_OF_CLMEM);
    }
 
    memcpy (sorted_update_list,update_list,update_count * sizeof(LONG));
    qsort (sorted_update_list,update_count,sizeof(LONG),S_compare_ids);
 
    /* Perform the actual update. */
 
    columns[0] = "data";
 
    result = SE_stream_update_ordered (stream,
                                       table_name,
                                       &row_id,
                                       update_list /* Unsorted list OK here. */,
                                       update_count,
                                       (SHORT)1,
                                       (const CHAR **)columns);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_update_row");
      (void) SE_connection_rollback_transaction (connection);
      free (sorted_update_list);
      return (result);
    }
 
    result = SE_stream_set_write_mode (stream,TRUE /* Buffered. */);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_set_write_mode");
      return (result);
    }
 
    result = SE_stream_bind_input_column (stream,1,data,&data_ind);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_bind_input_column (data)");
      return (result);
    }
 
    for (row = 0;row < update_count;row++) {
      sprintf (data,"Updated row %d at state %d",sorted_update_list[row],
               state_id);
      row_id = sorted_update_list[row]; /* Select row to update. */
      result = SE_stream_execute (stream);
      if (SE_SUCCESS != result) {
        print_stream_error (stream,result,"SE_stream_execute (insert)");
        (void) SE_connection_rollback_transaction (connection);
        free (sorted_update_list);
        return (result);
      }
    }
    free (sorted_update_list);
 
    result = SE_stream_close (stream,FALSE /* Don't reset. */);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_close");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
  }
 
  /* Perform the inserts. */
 
  if (insert_count >   0) {
 
    /* Since the table has a layer, we need to fetch that layer in order
       to obtain the correct coordref for our shapes. */
 
    result = SE_layerinfo_create (NULL,&layer);
    if (SE_SUCCESS != result) {
      print_error (result,"Unable to create layerinfo object");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
 
    result = SE_coordref_create (&coordref);
    if (SE_SUCCESS != result) {
      print_error (result,"Unable to create coordref object");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
 
    result = SE_layer_get_info (connection,table_name,"shape",layer);
    if (SE_SUCCESS != result) {
      print_conn_error (connection,result,"Unable to fetch layer definition");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
 
    result = SE_layerinfo_get_coordref (layer,coordref);
    if (SE_SUCCESS != result) {
      print_error (result,"Unable to get coordref object from layerinfo "
                   "object");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
 
    result = SE_shape_create (coordref,&shape);
    if (SE_SUCCESS != result) {
      print_error (result,"Unable to create shape object");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
 
    SE_coordref_free (coordref);
    SE_layerinfo_free (layer);
 
    /* Set up for actual insert. */
 
    columns[1] = "category";
    columns[2] = "shape";
    result = SE_stream_insert_table (stream,table_name,(SHORT)3,
                                     (const CHAR **)columns);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_insert_table");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
 
    if (0 == update_count) { /* If we haven't done this yet. */
      result = SE_stream_set_write_mode (stream,TRUE /* Buffered. */);
      if (SE_SUCCESS != result) {
        print_stream_error (stream,result,"SE_stream_set_write_mode");
        return (result);
      }
    }
 
    result = SE_stream_bind_input_column (stream,1,data,&data_ind);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_bind_input_column (data)");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
 
    result = SE_stream_bind_input_column (stream,2,&category,&category_ind);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,
                          "SE_stream_bind_input_column (category)");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
 
    result = SE_stream_bind_input_column (stream,3,shape,&shape_ind);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_bind_input_column (shape)");
      (void) SE_connection_rollback_transaction (connection);
      return (result);
    }
 
    /* Perform the actual inserts. */
 
	random_factor = state_id%50;
    for (row = 0;row < insert_count;row++) {
 
      /* Generate data. */
 
      sprintf (data,"Row Inserted at state %d",state_id);
      category = row % 2 + 1;
      rectangle.minx = 1000.0 + (row + random_factor) * 100.0;
      rectangle.miny = 1000.0;
      rectangle.maxx = 1100.0 + (row + random_factor) * 100.0;
      rectangle.maxy = 1100.0;
      result = SE_shape_generate_rectangle (&rectangle,shape);
      if (SE_SUCCESS != result) {
        print_error (result,"Unable to generate rectangle into shape object");
        break;
      }
 
      /* Insert it. */
 
      result = SE_stream_execute (stream);
      if (SE_SUCCESS != result) {
        print_stream_error (stream,result,"SE_stream_execute (insert)");
        (void) SE_connection_rollback_transaction (connection);
        return (result);
      }
    }
 
    SE_shape_free (shape);
  }
 
  /* Done, clean up and commit. */
 
  result = SE_stream_free (stream);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_free");
    (void) SE_connection_rollback_transaction (connection);
    return (result);
  }
 
  result = SE_connection_commit_transaction (connection);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Error in commit.");
    return (result);
  }
 
  /* Done. */
 
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {S_user_1_edit_session}  --  User 1's edit session.
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     Execute the first "user"'s simulated edit session.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection    <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                                 handle; used to request information and
*                                 actions from the server.
*     table_name    <Input>    ==  (const CHAR *) The name of the table for
*                                 User 1 to edit.
*     version_name  <Input>    ==  (const CHAR *) The version to hold all
*                                 of User 1's edits.
*     RETURN       <Output>    ==  (LONG) Error return: SE_SUCCESS on success,
*                                 an appropriate ArcSDE error code on
*                                 failure.
*E
***********************************************************************/
static LONG S_user_1_edit_session (SE_CONNECTION  connection,
                                   const CHAR     *table_name,
                                   const CHAR     *version_name)
{
  LONG            result,state_id,edit_depth,low_state_id;
  LONG            delete_count,update_count,insert_count;
  LONG            delete_list[1],update_list[2];
  SE_VERSIONINFO  version;
  SE_STATEINFO    state_list[MAX_EDIT_DEPTH];
 
  /* Create our version. */
 
  result = S_create_version (connection,version_name,&version);
  if (SE_SUCCESS != result) return (result);
 
  /* Create our first working state. */
 
  for (edit_depth = 0;edit_depth < MAX_EDIT_DEPTH;edit_depth++)
    state_list[edit_depth] = NULL;
 
  edit_depth = 0;
 
  result = SE_stateinfo_create (&state_list[edit_depth]);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create STATEINFO object.");
    return (result);
  }
 
  result = SE_versioninfo_get_state_id (version,&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_versioninfo_get_state_id");
    return (result);
  }
 
  result = SE_state_get_info (connection,state_id,state_list[edit_depth]);
  if (SE_SUCCESS != result) {
    CHAR  error_msg[SE_MAX_MESSAGE_LENGTH];
 
    sprintf (error_msg,"Unable to fetch state %d.",state_id);
    print_conn_error (connection,result,error_msg);
    return (result);
  }
 
  result = S_create_child_state (connection,
                                 state_list[edit_depth],
                                 &state_list[edit_depth + 1]);
  if (SE_SUCCESS != result) return (result);
  edit_depth++;
 
  /* Perform a set of edits. */
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  delete_count = 1;
  delete_list[0] = 1;
  update_count = 2;
  update_list[0] = 2;
  update_list[1] = 3;
  insert_count = 1;
 
  result = S_edit_state (connection,
                         state_id,
                         table_name,
                         delete_count,
                         delete_list,
                         update_count,
                         update_list,
                         insert_count);
  if (SE_SUCCESS != result) return (result);
 
  /* Create our second state, and edit it. */
 
  result = S_create_child_state (connection,
                                 state_list[edit_depth],
                                 &state_list[edit_depth + 1]);
  if (SE_SUCCESS != result) return (result);
  edit_depth++;
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  delete_count = 1;
  delete_list[0] = 11;
  update_count = 2;
  update_list[0] = 12;
  update_list[1] = 13;
  insert_count = 1;
 
  result = S_edit_state (connection,
                         state_id,
                         table_name,
                         delete_count,
                         delete_list,
                         update_count,
                         update_list,
                         insert_count);
  if (SE_SUCCESS != result) return (result);
 
  /* Create our third state, and edit it. */
 
  result = S_create_child_state (connection,
                                 state_list[edit_depth],
                                 &state_list[edit_depth + 1]);
  if (SE_SUCCESS != result) return (result);
  edit_depth++;
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  delete_count = 1;
  delete_list[0] = 21;
  update_count = 2;
  update_list[0] = 22;
  update_list[1] = 23;
  insert_count = 1;
 
  result = S_edit_state (connection,
                         state_id,
                         table_name,
                         delete_count,
                         delete_list,
                         update_count,
                         update_list,
                         insert_count);
  if (SE_SUCCESS != result) return (result);
 
  /* Create our fourth state, and edit it. */
 
  result = S_create_child_state (connection,
                                 state_list[edit_depth],
                                 &state_list[edit_depth + 1]);
  if (SE_SUCCESS != result) return (result);
  edit_depth++;
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  delete_count = 1;
  delete_list[0] = 31;
  update_count = 2;
  update_list[0] = 32;
  update_list[1] = 33;
  insert_count = 1;
 
  result = S_edit_state (connection,
                         state_id,
                         table_name,
                         delete_count,
                         delete_list,
                         update_count,
                         update_list,
                         insert_count);
  if (SE_SUCCESS != result) return (result);
 
  /* We decide that our last set of changes were wrong, so we delete the
     last state we created. */
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  result = SE_state_delete (connection,state_id);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"SE_state_delete");
    return (result);
  }
 
  edit_depth--;
 
  /* Move our version to point at the last state we edited. */
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  result = SE_version_change_state (connection,version,state_id);
  if (SE_SUCCESS != result) {
    CHAR  error_msg[SE_MAX_MESSAGE_LENGTH];
 
    sprintf (error_msg,"Unable to move version %s to new state %d",
             version_name,state_id);
    print_conn_error (connection,result,error_msg);
    return (result);
  }
 
  /* Trim our branch of the state tree to its minimum length -- one state. */
 
  result = SE_stateinfo_get_id (state_list[1],&low_state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  result = SE_state_trim_tree (connection,low_state_id,state_id);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to trim edit tree.");
    return (result);
  }
 
  /* Done, clean up. */
 
  for (edit_depth = 0;edit_depth < MAX_EDIT_DEPTH;edit_depth++)
    if (state_list[edit_depth]) SE_stateinfo_free (state_list[edit_depth]);
 
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {S_user_2_edit_session}  --  User 2's edit session.
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     Execute the second "user"'s simulated edit session.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection    <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                                 handle; used to request information and
*                                 actions from the server.
*     table_name    <Input>    ==  (const CHAR *) The name of the table for
*                                 User 2 to edit.
*     version_name  <Input>    ==  (const CHAR *) The version to hold all
*                                 of User 2's edits.
*     RETURN       <Output>    ==  (LONG) Error return: SE_SUCCESS on success,
*                                 an appropriate ArcSDE error code on
*                                 failure.
*E
***********************************************************************/
static LONG S_user_2_edit_session (SE_CONNECTION  connection,
                                   const CHAR     *table_name,
                                   const CHAR     *version_name)
{
  LONG            result,state_id,edit_depth,low_state_id;
  LONG            delete_count,update_count,insert_count;
  LONG            delete_list[1],update_list[2];
  SE_VERSIONINFO  version;
  SE_STATEINFO    state_list[MAX_EDIT_DEPTH];
 
  /* Create our version. */
 
  result = S_create_version (connection,version_name,&version);
  if (SE_SUCCESS != result) return (result);
 
  /* Create our first working state. */
 
  for (edit_depth = 0;edit_depth < MAX_EDIT_DEPTH;edit_depth++)
    state_list[edit_depth] = NULL;
 
  edit_depth = 0;
 
  result = SE_stateinfo_create (&state_list[edit_depth]);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create STATEINFO object.");
    return (result);
  }
 
  result = SE_versioninfo_get_state_id (version,&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_versioninfo_get_state_id");
    return (result);
  }
 
  result = SE_state_get_info (connection,state_id,state_list[edit_depth]);
  if (SE_SUCCESS != result) {
    CHAR  error_msg[SE_MAX_MESSAGE_LENGTH];
 
    sprintf (error_msg,"Unable to fetch state %d.",state_id);
    print_conn_error (connection,result,error_msg);
    return (result);
  }
 
  result = S_create_child_state (connection,
                                 state_list[edit_depth],
                                 &state_list[edit_depth + 1]);
  if (SE_SUCCESS != result) return (result);
  edit_depth++;
 
  /* Perform a set of edits. */
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  delete_count = 1;
  delete_list[0] = 1;
  update_count = 2;
  update_list[0] = 2;
  update_list[1] = 4;
  insert_count = 1;
 
  result = S_edit_state (connection,
                         state_id,
                         table_name,
                         delete_count,
                         delete_list,
                         update_count,
                         update_list,
                         insert_count);
  if (SE_SUCCESS != result) return (result);
 
  /* Create our second state, and edit it. */
 
  result = S_create_child_state (connection,
                                 state_list[edit_depth],
                                 &state_list[edit_depth + 1]);
  if (SE_SUCCESS != result) return (result);
  edit_depth++;
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  delete_count = 1;
  delete_list[0] = 14;
  update_count = 2;
  update_list[0] = 11;
  update_list[1] = 15;
  insert_count = 1;
 
  result = S_edit_state (connection,
                         state_id,
                         table_name,
                         delete_count,
                         delete_list,
                         update_count,
                         update_list,
                         insert_count);
  if (SE_SUCCESS != result) return (result);
 
  /* Create our third state, and edit it. */
 
  result = S_create_child_state (connection,
                                 state_list[edit_depth],
                                 &state_list[edit_depth + 1]);
  if (SE_SUCCESS != result) return (result);
  edit_depth++;
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  delete_count = 1;
  delete_list[0] = 22;
  update_count = 2;
  update_list[0] = 25;
  update_list[1] = 26;
  insert_count = 1;
 
  result = S_edit_state (connection,
                         state_id,
                         table_name,
                         delete_count,
                         delete_list,
                         update_count,
                         update_list,
                         insert_count);
  if (SE_SUCCESS != result) return (result);
 
  /* Create our fourth state, and edit it. */
 
  result = S_create_child_state (connection,
                                 state_list[edit_depth],
                                 &state_list[edit_depth + 1]);
  if (SE_SUCCESS != result) return (result);
  edit_depth++;
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  delete_count = 1;
  delete_list[0] = 31;
  update_count = 2;
  update_list[0] = 32;
  update_list[1] = 33;
  insert_count = 1;
 
  result = S_edit_state (connection,
                         state_id,
                         table_name,
                         delete_count,
                         delete_list,
                         update_count,
                         update_list,
                         insert_count);
  if (SE_SUCCESS != result) return (result);
 
  /* Move our version to point at the last state we edited. */
 
  result = SE_stateinfo_get_id (state_list[edit_depth],&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  result = SE_version_change_state (connection,version,state_id);
  if (SE_SUCCESS != result) {
    CHAR  error_msg[SE_MAX_MESSAGE_LENGTH];
 
    sprintf (error_msg,"Unable to move version %s to new state %d",
             version_name,state_id);
    print_conn_error (connection,result,error_msg);
    return (result);
  }
 
  /* Trim our branch of the state tree to its minimum length -- one state. */
 
  result = SE_stateinfo_get_id (state_list[1],&low_state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  result = SE_state_trim_tree (connection,low_state_id,state_id);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to trim edit tree.");
    return (result);
  }
 
  /* Done, clean up. */
 
  for (edit_depth = 0;edit_depth < MAX_EDIT_DEPTH;edit_depth++)
    if (state_list[edit_depth]) SE_stateinfo_free (state_list[edit_depth]);
 
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {S_reconcile_operation}  --  Perform a reconciliation operation
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This function performs a single reconciliation operation on a
*   table, such as deleting all rows in one state that were deleted in
*   another, or copying over all inserts in one state that matched a
*   where clause into another state.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection       <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                                    handle; used to request information and
*                                    actions from the server.
*     table_name       <Input>    ==  (const CHAR *) The name of the table to
*                                    apply changes to.
*     source_id        <Input>    ==  (LONG) The Id of the state to get
*                                    ids from.
*     difference_id    <Input>    ==  (LONG) The Id of the state to compare
*                                    against.
*     copy_from_id     <Input>    ==  (LONG) The Id of the state to copy rows
*                                    from.
*     target_id        <Input>    ==  (LONG) The Id of the state to copy into
*                                    or delete from.
*     difference_type  <Input>    ==  (LONG) The difference type filter to
*                                    detect rows with:
*                                     SE_STATE_DIFF_NOCHANGE_UPDATE
*                                     SE_STATE_DIFF_NOCHANGE_DELETE
*                                     SE_STATE_DIFF_UPDATE_NOCHANGE
*                                     SE_STATE_DIFF_UPDATE_UPDATE
*                                     SE_STATE_DIFF_UPDATE_DELETE
*                                     SE_STATE_DIFF_INSERT
*     where_clause     <Input>    ==  (const CHAR *) The where clause to use
*                                    when selecting rows to operate on;
*                                    NULL if no selection is required.
*     action_type      <Input>    ==  (RE_ACTION) The action to perform with
*                                    the rows detected:
*                                     RE_DELETE_ROWS_FOUND
*                                     RE_COPY_ROWS_FOUND
*     RETURN          <Output>    ==  (LONG) Error return: SE_SUCCESS on
*                                    success, an appropriate ArcSDE error
*                                    code on failure.
*E
***********************************************************************/
static LONG S_reconcile_operation (SE_CONNECTION  connection,
                                   const CHAR     *table_name,
                                   LONG           source_id,
                                   LONG           difference_id,
                                   LONG           copy_from_id,
                                   LONG           target_id,
                                   LONG           difference_type,
                                   const CHAR     *where_clause,
                                   RE_ACTION      action_type)
{
  LONG              result,row_id,*row_id_list,row_id_count;
  CHAR              *table_list[1];
  const CHAR        *columns[1];
  SHORT             row_id_ind;
  SE_STREAM         stream;
  SE_SQL_CONSTRUCT  sql_construct;
 
  /* Set up the stream. */
 
  result = SE_stream_create (connection,&stream);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to create stream.");
    exit (EXIT_FAILURE);
  }
 
  result = SE_stream_set_state (stream,source_id,difference_id,difference_type);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"Unable to set states for stream.");
    exit (EXIT_FAILURE);
  }
 
  /* Initialize query. */
 
  columns[0] = "ROW_ID";
 
  table_list[0] = (CHAR *)table_name;
  sql_construct.num_tables = 1;
  sql_construct.tables = (CHAR **)table_list;
  sql_construct.where = (CHAR *)where_clause;
 
  result = SE_stream_query (stream,(SHORT)1,columns,&sql_construct);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to initialize query.");
    return (result);
  }
 
  result = SE_stream_bind_output_column (stream,1,&row_id,&row_id_ind);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"Unable bind 'row_id' column.");
    return (result);
  }
 
  /* Collect the ids of all of the rows that we are interested in. */
 
  result = SE_stream_execute (stream);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"Error in executing query.");
    return (result);
  }
 
  row_id_list = (LONG *)NULL;
  row_id_count = 0;
 
  while ((result = SE_stream_fetch (stream)) == SE_SUCCESS) {
    if (SE_IS_NULL_VALUE == row_id_ind) continue; /* Can't really happen. */
    row_id_count++;
    row_id_list = realloc (row_id_list,row_id_count * sizeof(LONG));
    if ((LONG *)NULL == row_id_list) {
      print_error (SE_OUT_OF_CLMEM,"Unable to allocate enought memory for "
                   "id list.");
      return (SE_OUT_OF_CLMEM);
    }
    row_id_list[row_id_count - 1] = row_id;
  }
  if (SE_FINISHED != result) {
    print_stream_error (stream,result,"Error in fetching row.");
    return (result);
  }
 
  result = SE_stream_close (stream,TRUE /* Reset. */);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_close");
    return (result);
  }
 
  /* Peform the indicated operation with the rows we collected the ids of. */
 
  if (0 == row_id_count) { /* Nothing to do, so return. */
    result = SE_stream_free (stream);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_free");
      return (result);
    }
    return (SE_SUCCESS);
  }
 
  result = SE_connection_start_transaction (connection);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"SE_connection_start_transaction");
    return (result);
  }
 
  switch (action_type) {
 
  case RE_DELETE_ROWS_FOUND:
 
    /* Delete from the target state all the rows for which we collected ids. */
 
    result = SE_stream_set_state (stream,
                                  target_id,
                                  SE_NULL_STATE_ID,
                                  SE_STATE_DIFF_NOCHECK);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_set_state");
      return (result);
    }
   
    result = SE_stream_delete_by_id_list (stream,
                                          table_name,
                                          row_id_list,
                                          row_id_count);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"Error in deleting rows.");
      return (result);
    }
                                         
    break;
 
  case RE_COPY_ROWS_FOUND:
 
    /* Copy from the from state to the target states all of the rows for which
       we have an id. */
 
    result = SE_stream_set_state (stream,
                                  target_id,
                                  copy_from_id,
                                  SE_STATE_DIFF_INSERT);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"SE_stream_set_state");
      return (result);
    }
   
    result = SE_stream_copy_state_rows (stream,
                                        table_name,
                                        row_id_list,
                                        row_id_count);
    if (SE_SUCCESS != result) {
      print_stream_error (stream,result,"Error in copying rows.");
      return (result);
    }
  }
 
  result = SE_stream_free (stream);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_free");
    return (result);
  }
 
  result = SE_connection_commit_transaction (connection);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Error in commit.");
    return (result);
  }
 
  /* Done. */
 
  free (row_id_list);
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {S_reconcile_user_1}  --  Reconcile user 1's version with user 2's
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This function reconciles user 1's version with user 2's, resulting
*   in a new state for user 1's version containing data as appropriate
*   from both versions.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection           <Input>    ==  (SE_CONNECTION) ArcSDE server
*                                        connection handle; used to request
*                                        information and actions from the
*                                        server.
*     table_name           <Input>    ==  (const CHAR *) The name of the table
*                                        for User 2 to reconcile.
*     user_1_version_name  <Input>    ==  (const CHAR *) The version containing
*                                        User 1's edits.
*     user_2_version_name  <Input>    ==  (const CHAR *) The version containing
*                                        User 2's edits.
*     RETURN              <Output>    ==  (LONG) Error return: SE_SUCCESS on
*                                        success, an appropriate ArcSDE error
*                                        code on failure.
*E
***********************************************************************/
static LONG S_reconcile_user_1 (SE_CONNECTION  connection,
                                const CHAR     *table_name,
                                const CHAR     *user_1_version_name,
                                const CHAR     *user_2_version_name)
{
  LONG            result,user_1_state_id,user_2_state_id,old_state_id;
  SE_VERSIONINFO  version;
  SE_STATEINFO    state;
 
  /* Get both versions, and determine their state ids. (We fetch user 1's
     version 2nd as we need to keep it around so when we are done, we can
     use it in the call to SE_version_change_state() when we point user 1's
     version at the result of the reconciliation.)  */
 
  result = SE_versioninfo_create (&version);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create VERSIONINFO object.");
    return (result);
  }
 
  result = SE_version_get_info (connection,user_2_version_name,version);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to fetch user 2's version");
    exit (EXIT_FAILURE);
  }
 
  result = SE_versioninfo_get_state_id (version,&user_2_state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_versioninfo_get_state_id");
    return (result);
  }
 
  result = SE_version_get_info (connection,user_1_version_name,version);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to fetch user 1's version");
    exit (EXIT_FAILURE);
  }
 
  result = SE_versioninfo_get_state_id (version,&user_1_state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_versioninfo_get_state_id");
    return (result);
  }
 
  /* Create a new state as a child of version 1's state to receive the results
     of the reconciliation. */
 
  result = SE_stateinfo_create (&state);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create STATEINFO object.");
    return (result);
  }
 
  result = SE_state_create (connection,state,user_1_state_id,state);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to create state.");
    SE_stateinfo_free (state);
    return (result);
  }
 
  old_state_id = user_1_state_id;
  result = SE_stateinfo_get_id (state,&user_1_state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_stateinfo_get_id");
    return (result);
  }
 
  SE_stateinfo_free (state);
 
  /* Perform the required reconcilation queries and operations. */
 
  /* NoChange/Delete from User 1 to User 2.  Any unmodified rows in User 1
     that were deleted in User 2 and have a CATEGORY of 1 will be deleted. */
 
  result = S_reconcile_operation (connection,
                                  table_name,
                                  user_1_state_id /* Source state id */,
                                  user_2_state_id /* Difference state id */,
                                  SE_NULL_STATE_ID,
                                  user_1_state_id /* Target state id */,
                                  SE_STATE_DIFF_NOCHANGE_DELETE,
                                  "CATEGORY = 1",
                                  RE_DELETE_ROWS_FOUND);
  if (SE_SUCCESS != result) return (result);
 
  /* Nochange/Update from User 1 to User 2.  Any rows updated in User 2 and
     not in User 1 and having a CATEGORY of 1 will be copied over.  */
 
  result = S_reconcile_operation (connection,
                                  table_name,
                                  user_1_state_id /* Source state id */,
                                  user_2_state_id /* Difference state id */,
                                  user_2_state_id /* Copy from state id */,
                                  user_1_state_id /* Target state id */,
                                  SE_STATE_DIFF_NOCHANGE_UPDATE,
                                  "CATEGORY = 1",
                                  RE_COPY_ROWS_FOUND);
  if (SE_SUCCESS != result) return (result);
 
  /* Insert from User 2 to User 1.  Any rows inserted into User 2 will be
     copied to User 1.  */
 
  result = S_reconcile_operation (connection,
                                  table_name,
                                  user_2_state_id /* Source state id */,
                                  user_1_state_id /* Difference state id */,
                                  user_2_state_id /* Copy from state id */,
                                  user_1_state_id /* Target state id */,
                                  SE_STATE_DIFF_INSERT,
                                  NULL,
                                  RE_COPY_ROWS_FOUND);
  if (SE_SUCCESS != result) return (result);
 
  /* Update/Delete from User 2 to User 1.  Any rows deleted in User 1 but
     updated in User 2 will be copied from User 2 to User 1.   */
 
  result = S_reconcile_operation (connection,
                                  table_name,
                                  user_2_state_id /* Source state id */,
                                  user_1_state_id /* Difference state id */,
                                  user_2_state_id /* Copy from state id */,
                                  user_1_state_id /* Target state id */,
                                  SE_STATE_DIFF_UPDATE_DELETE,
                                  NULL,
                                  RE_COPY_ROWS_FOUND);
  if (SE_SUCCESS != result) return (result);
 
  /* We're done with the reconcilition, close the state to prevent any more
     writes, and make our version point to it. */
 
  result = SE_state_close (connection,user_1_state_id);
  if (SE_SUCCESS != result) {
    CHAR  error_msg[SE_MAX_MESSAGE_LENGTH];
 
    sprintf (error_msg,"Unable to close state %d.",user_1_state_id);
    print_conn_error (connection,result,"Unable to fetch registration");
    return (result);
  }
 
  result = SE_version_change_state (connection,version,user_1_state_id);
  if (SE_SUCCESS != result) {
    CHAR  error_msg[SE_MAX_MESSAGE_LENGTH];
 
    sprintf (error_msg,"Unable to move version %s to new state %d",
             user_1_version_name,user_1_state_id);
    print_conn_error (connection,result,error_msg);
    return (result);
  }
 
  SE_versioninfo_free (version);
 
  /* Trim the state tree to remove the state immeadiately above the one
     containing the reconcilation results -- it isn't needed any more.  This
     will leave us just one step below our original parent state, keeping the
     tree small. */
 
  result = SE_state_trim_tree (connection,old_state_id,user_1_state_id);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to trim edit tree.");
    return (result);
  }
 
  /* Done! */
 
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {S_list_table_version}  --  List a table's row at a specified version
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This function lists a specified table's rows as at a specified
*   version to stdout.  This function is hard-coded for our test case
*   schema.
*E
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*A  Parameters:
*     connection    <Input>    ==  (SE_CONNECTION) ArcSDE server connection
*                                 handle; used to request information and
*                                 actions from the server.
*     table_name    <Input>    ==  (const CHAR *) The name of the table to
*                                 be listed.
*     version_name  <Input>    ==  (const CHAR *) The version at which the
*                                 the table is to be listed.
*     RETURN       <Output>    ==  (LONG) Error return: SE_SUCCESS on success,
*                                 an appropriate ArcSDE error code on
*                                 failure.
*E
***********************************************************************/
static LONG S_list_table_version (SE_CONNECTION  connection,
                                  const CHAR     *table_name,
                                  const CHAR     *version_name)
{
  LONG              result,state_id,row_id,category;
  CHAR              data[64];
  CHAR              *table_list[1];
  const CHAR        *columns[3];
  SHORT             data_ind,category_ind,row_id_ind;
  SE_STREAM         stream;
  SE_SQL_CONSTRUCT  sql_construct;
  SE_VERSIONINFO    version;
 
  /* Get the specified version, and extract its state id. */
 
  result = SE_versioninfo_create (&version);
  if (SE_SUCCESS != result) {
    print_error (result,"Unable to create VERSIONINFO object.");
    return (result);
  }
 
  result = SE_version_get_info (connection,version_name,version);
  if (SE_SUCCESS != result) {
    CHAR  error_msg[SE_MAX_MESSAGE_LENGTH];
 
    sprintf (error_msg,"Unable to fetch version %s",version_name);
    print_conn_error (connection,result,error_msg);
    exit (EXIT_FAILURE);
  }
 
  result = SE_versioninfo_get_state_id (version,&state_id);
  if (SE_SUCCESS != result) {
    print_error (result,"SE_versioninfo_get_state_id");
    return (result);
  }
 
  SE_versioninfo_free (version);
 
  /* Set up the stream. */
 
  result = SE_stream_create (connection,&stream);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to create stream.");
    exit (EXIT_FAILURE);
  }
 
  result = SE_stream_set_state (stream,
                                state_id,
                                SE_NULL_STATE_ID,
                                SE_STATE_DIFF_NOCHECK);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_set_state");
    return (result);
  }
 
  /* Initialize query. */
 
  columns[0] = "ROW_ID";
  columns[1] = "CATEGORY";
  columns[2] = "DATA";
 
  table_list[0] = (CHAR *)table_name;
  sql_construct.num_tables = 1;
  sql_construct.tables = (CHAR **)table_list;
  sql_construct.where = (CHAR *)NULL;
 
  result = SE_stream_query (stream,(SHORT)3,columns,&sql_construct);
  if (SE_SUCCESS != result) {
    print_conn_error (connection,result,"Unable to initialize query.");
    return (result);
  }
 
  result = SE_stream_bind_output_column (stream,1,&row_id,&row_id_ind);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"Unable bind 'row_id' column.");
    return (result);
  }
 
  result = SE_stream_bind_output_column (stream,2,&category,&category_ind);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"Unable bind 'category' column.");
    return (result);
  }
 
  result = SE_stream_bind_output_column (stream,3,data,&data_ind);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"Unable bind 'data' column.");
    return (result);
  }
 
  /* Loop through the rows, printing them. */
 
  printf ("Listing version %s of table %s:\n\n",version_name,table_name);
  printf ("ROW_ID\t\tCATEGORY\t\tDATA\n======\t\t========\t\t====\n");
 
  result = SE_stream_execute (stream);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"Error in executing query.");
    return (result);
  }
 
  while ((result = SE_stream_fetch (stream)) == SE_SUCCESS) {
    if (SE_IS_NULL_VALUE == row_id_ind) row_id = -9999;
    if (SE_IS_NULL_VALUE == category_ind) category = 0;
    if (SE_IS_NULL_VALUE == data_ind) strcpy (data,"(null)");
    printf ("%6d\t\t%6d\t\t\t%s\n",row_id,category,data);
  }
  if (SE_FINISHED != result) {
    print_stream_error (stream,result,"Error in fetching row.");
    return (result);
  }
  puts (" ");
 
  /* Done, clean up. */
 
  result = SE_stream_free (stream);
  if (SE_SUCCESS != result) {
    print_stream_error (stream,result,"SE_stream_free");
    return (result);
  }
 
  return (SE_SUCCESS);
}
 
/***********************************************************************
*
*N  {main}  --  Versioned demo main function, executes demo script
*
*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
*P  Purpose:
*     This main function executes the top level steps of the versioned
*   demo script described in the main header of this file.
*E
***********************************************************************/
int main (int  argc,
          char *argv[])
{
  SE_CONNECTION   connection;
  LONG            result;
  CHAR            *table_name,*user,*password,*server,*instance,*database;
  CHAR            error_msg[SE_MAX_MESSAGE_LENGTH];
  SE_ERROR        error;
 
  static const CHAR  *_usage = "Usage: versioned_1 <table>   <user>   <password>   "
                                             " {server} {service} {database}\n";
 
  /* Check arguments. */
 
  if (argc < 4 || argc >   7) {
    fputs (_usage,stderr);
    exit (EXIT_FAILURE);
  }
 
  /* Assign arguments to local variables -- optional arguments may be
     represented by a # */
 
  table_name = argv[1];
  user     = argv[2];
  password = argv[3];
  server = "localhost";
  instance = (CHAR *)NULL;
  database = (CHAR *)NULL;
  if (argc >   4) {
    server = argv[4];
    if (strcmp (server,"#") == 0) server = "localhost";
    if (argc >   5) {
      instance = argv[5];
      if (strcmp (instance,"#") == 0) instance = (CHAR *)NULL;
      if (argc >   6) {
        database = argv[6];
        if (strcmp (database,"#") == 0) database = (CHAR *)NULL;
      }
    }
  }
   
  /* Connect to the ArcSDE server. */
 
  result = SE_connection_create (server,instance,database,user,password,
                                 &error,&connection);
  if (result != SE_SUCCESS) {
    sprintf (error_msg,"Could not create a connection for user %s.\n",user);
    print_error (result,error_msg);
    if (SE_DB_IO_ERROR == result) {
      printf ("Extended DBMS error code: %d\n",error.ext_error);
      printf ("%s\n",error.err_msg1);
      printf ("%s\n",error.err_msg2);
    }
    return (EXIT_FAILURE);
  }
  printf ("Connected to ArcSDE server as user %s . . .\n",user);
 
  /* Create the table for editing. */
 
  result = S_create_edit_table (connection,table_name);
  if (SE_SUCCESS != result) return (EXIT_FAILURE);
  printf ("Created edit table %s . . .\n",table_name);
 
  /* List the edit table. */
 
  result = S_list_table_version (connection,table_name,
                                 SE_QUALIFIED_DEFAULT_VERSION_NAME);
  if (SE_SUCCESS != result) return (EXIT_FAILURE);
 
  /* Run user 1's edit session. */
 
  result = S_user_1_edit_session (connection,table_name,USER_1_VERSION);
  if (SE_SUCCESS != result) return (EXIT_FAILURE);
  printf ("User 1's edit session complete . . . \n");
 
  /* List the results of user 1's edit session. */
 
  result = S_list_table_version (connection,table_name,USER_1_VERSION);
  if (SE_SUCCESS != result) return (EXIT_FAILURE);
  printf ("User 2's edit session complete . . . \n");
 
  /* Run user 2's edit session. */
 
  result = S_user_2_edit_session (connection,table_name,USER_2_VERSION);
  if (SE_SUCCESS != result) return (EXIT_FAILURE);
 
  /* List the results of user 2's edit session. */
 
  result = S_list_table_version (connection,table_name,USER_2_VERSION);
  if (SE_SUCCESS != result) return (EXIT_FAILURE);
 
  /* Reconcile user 1's version and user 2's version in order to create
     an updated user 1 version. */
 
  result = S_reconcile_user_1 (connection,
                               table_name,
                               USER_1_VERSION,
                               USER_2_VERSION);
  if (SE_SUCCESS != result) return (EXIT_FAILURE);
  printf ("User 1 reconciled with User 2 . . .\n");
 
  /* List the new user 1 version. */
 
  result = S_list_table_version (connection,table_name,USER_1_VERSION);
  if (SE_SUCCESS != result) return (EXIT_FAILURE);
 
  /* Done. */
 
  SE_connection_free (connection);
  return (EXIT_SUCCESS);
}