/*********************************************************************** * *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 {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 #include #include #include #ifdef unix # include #elif defined (WIN32) # include #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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * table_name == (const CHAR *) The name of the base table * to create. * RETURN == (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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * table_name == (const CHAR *) The name of the table to * make multiversion. * RETURN == (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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * table_name == (const CHAR *) The name of the edit table * to create. * RETURN == (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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * version_name == (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 == (SE_VERSIONINFO *) The resulting version. * RETURN == (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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * parent_state == (SE_STATEINFO) The state to be the parent * of the new state. * new_state == (SE_STATEINFO *) The newly created child * state of the parent state. * RETURN == (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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * state_id == (LONG) The id for the state in which the * edits are to occur. * table_name == (const CHAR *) The name of the table which * to edit. * delete_count == (LONG) The number of rows to delete. * delete_list == (const LONG *) The list of row ids for rows * to be deleted. * update_count == (LONG) The number of rows to update. * update_list == (const LONG *) The list of row ids for rows * to be updated. * insert_list == (LONG) The number of rows to insert. * RETURN == (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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * table_name == (const CHAR *) The name of the table for * User 1 to edit. * version_name == (const CHAR *) The version to hold all * of User 1's edits. * RETURN == (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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * table_name == (const CHAR *) The name of the table for * User 2 to edit. * version_name == (const CHAR *) The version to hold all * of User 2's edits. * RETURN == (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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * table_name == (const CHAR *) The name of the table to * apply changes to. * source_id == (LONG) The Id of the state to get * ids from. * difference_id == (LONG) The Id of the state to compare * against. * copy_from_id == (LONG) The Id of the state to copy rows * from. * target_id == (LONG) The Id of the state to copy into * or delete from. * difference_type == (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 == (const CHAR *) The where clause to use * when selecting rows to operate on; * NULL if no selection is required. * action_type == (RE_ACTION) The action to perform with * the rows detected: * RE_DELETE_ROWS_FOUND * RE_COPY_ROWS_FOUND * RETURN == (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 == (SE_CONNECTION) ArcSDE server * connection handle; used to request * information and actions from the * server. * table_name == (const CHAR *) The name of the table * for User 2 to reconcile. * user_1_version_name == (const CHAR *) The version containing * User 1's edits. * user_2_version_name == (const CHAR *) The version containing * User 2's edits. * RETURN == (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 == (SE_CONNECTION) ArcSDE server connection * handle; used to request information and * actions from the server. * table_name == (const CHAR *) The name of the table to * be listed. * version_name == (const CHAR *) The version at which the * the table is to be listed. * RETURN == (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
" " {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); }