/* r4relate.c   (c)Copyright Sequiter Software Inc., 1992-1994.  All rights reserved. */

#include "d4all.h"
#ifndef S4UNIX
#ifdef __TURBOC__
#pragma hdrstop
#endif
#endif

#include <time.h>

int S4FUNCTION f4flag_is_set_flip( F4FLAG *flag_ptr, unsigned long r )
{
  if ( flag_ptr->flags == 0 )
    return 1 ;

  if ( flag_ptr->is_flip )
    return ! f4flag_is_set( flag_ptr, r ) ;
  else
    return f4flag_is_set( flag_ptr, r ) ;
}

/* returns the position of the next flipped flag in the flag set - start at r */
unsigned long S4FUNCTION f4flag_get_next_flip( F4FLAG *f4, unsigned long r, char direction )
{
  unsigned char c_flag ;
  unsigned long low_val, on_val ;
  char i ;
  unsigned long high_val ;

  c_flag = 0 ;
  on_val = r ;
  if ( f4->flags == 0 || r > f4->num_flags )
    return 0 ;

  low_val = (unsigned long)( r & 0x7 ) ;
  high_val = (unsigned long)( r >> 3 ) ;

  if ( (int)direction == -1 )
  {
    c_flag = (unsigned char)( f4->flags[high_val] ) ;
    if ( f4->is_flip )
      c_flag = (unsigned char) ~c_flag ;

    c_flag = (unsigned char)( (unsigned char)( c_flag << ( 7 - low_val ) ) >> ( 7 - low_val )) ;

    on_val += ( 7 - low_val ) ;

    if ( c_flag == 0 )
      for( ; c_flag == 0 ; on_val -= 8 )
      {
        if ( high_val-- <= 1 )  /* if was zero, or is now zero */
        {
          if ( f4->flags[0] == 0 )
            return r ;
          c_flag = f4->flags[0] ;
          if ( f4->is_flip )
            c_flag = (unsigned char) ~c_flag ;
          on_val -= 8 ;  /* for sure if high_val == 0, else?? */
          break ;
        }
        c_flag = f4->flags[high_val] ;
        if ( f4->is_flip )
          c_flag = (unsigned char) ~c_flag ;
      }

    for( i = 7 ; (int)i >= 0 ; i--, on_val-- )
      if ( c_flag & ( 0x01 << i ) )
        break ;

    return (r - on_val) ;
  }
  else
  {
    c_flag = (unsigned char)f4->flags[high_val] ;
    if ( f4->is_flip )
      c_flag = (unsigned char) ~c_flag ;
    c_flag = (unsigned char) (c_flag >> low_val) ;
    if ( c_flag == 0 )
    {
      on_val -= low_val ;
      for( ; c_flag == 0 ; on_val += 8 )
      {
        if ( on_val >= f4->num_flags )
          return (f4->num_flags - r + 1) ;
        c_flag = f4->flags[++high_val] ;
        if ( f4->is_flip )
          c_flag = (unsigned char) ~c_flag ;
      }
    }

    for( i = 0 ; i <= 7 ; i++, on_val++ )
      if ( c_flag & ( 0x01 << i ) )
        break ;

    return (on_val - r) ;
  }
}

int S4FUNCTION r4data_list_add( LIST4 *l4, DATA4 *data, RELATE4 *relate )
{
  R4DATA_LIST *r4 ;

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  r4 = (R4DATA_LIST *)u4alloc_free( data->code_base, sizeof( R4DATA_LIST ) ) ;
  if ( r4 == 0 )
    return -1 ;
  r4->data = data ;
  r4->relate = relate ;
  l4add( l4, r4 ) ;
  return 0 ;
}

int S4FUNCTION r4data_list_find( LIST4 *l4, RELATE4 *r4 )
{
  R4DATA_LIST *link ;

  for ( link = 0 ;; )
  {
    link = (R4DATA_LIST *)l4next( l4, link ) ;
    if ( link == 0 )
      return 0 ;
    if ( link->relate == r4 )
      return 1 ;
  }
}

void S4FUNCTION r4data_list_free( LIST4 *l4 )
{
  R4DATA_LIST *r4data, *r4data2 ;

  for ( r4data = (R4DATA_LIST *)l4first( l4 ) ; r4data ; )
  {
    r4data->relate->sort_type = 0 ;
    r4data2 = (R4DATA_LIST *)l4next( l4, r4data ) ;
    l4remove( l4, r4data ) ;
    u4free( r4data ) ;
    r4data = r4data2 ;
  }
}

/* this function takes a completed sort list, and adds data members in the
   following case:

   If (r)elate must be added, r's siblings must also be added

   Or, interpreted differently, if r is a relation, and any of it's children
   must be added, then all of its children must be added.
   */
static int r4data_list_massage( LIST4 *l4 )
{
  RELATE4 *relate_child ;
  R4DATA_LIST *r4data ;
  int add_children, relate_added ;

  if ( l4->n_link == 0 )  /* no work required */
    return 0 ;

  r4data = 0 ;

  for( ;; )
  {
    r4data = (R4DATA_LIST *)l4next( l4, r4data ) ;
    if ( r4data == 0 )
      break ;

    relate_child = 0 ;
    add_children = 0 ;
    for( ;; )
    {
      relate_child = (RELATE4 *)l4next( &r4data->relate->slaves, relate_child ) ;
      if ( relate_child == 0 )
        break ;
      if ( r4data_list_find( l4, relate_child ) )
      {
        add_children = 1 ;
        break ;
      }
    }

    if ( add_children == 1 )
    {
      relate_added = 0 ;
      relate_child = 0 ;
      for( ;; )
      {
        relate_child = (RELATE4 *)l4next( &r4data->relate->slaves, relate_child ) ;
        if ( relate_child == 0 )
          break ;
        if ( r4data_list_find( l4, relate_child ) == 0 )
        {
          r4data_list_add( l4, relate_child->data, relate_child ) ;
          relate_child->sort_type = relate4exact ;
          relate_added = 1 ;
        }
      }
      if ( relate_added == 1 )
        r4data = 0 ;   /* start at list top again to be sure none missed */
    }
  }

  return 0 ;
}

/* 1 - database added, 0 - database not added, -1 - error */
/* check_type gives the caller's status in terms of whether we should be included */
int S4FUNCTION r4data_list_build( LIST4 *l4, RELATE4 *relate, EXPR4 *expr, int check_type )
{
  int i ;
  char must_add ;
  E4INFO *info ;
  RELATE4 *slave_on ;

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  must_add = 0 ;

  /* 1st check if we must belong */
  for( i = 0 ; i < expr->info_n ; i++ )
  {
    info = expr->info + i ;
    if ( info->field_ptr )
    {
      if ( info->field_ptr->data == relate->data )
      {
        must_add = 1 ;
        break ;
      }
    }
  }

  relate->sort_type = relate4exact ;

  if ( must_add )
    check_type = relate4exact ;
  else
  {
    if ( relate->relation_type == relate4scan )
      check_type = relate4scan ;
    else
      if ( check_type != relate4scan )   /* non-scan parent must be added, so we add ourselves too, in order to save work later */
        must_add = 1 ;
  }

  /* if a child must be added, we must be too: */
  for ( slave_on = 0 ;; )
  {
    slave_on = (RELATE4 *)l4next( &relate->slaves, slave_on ) ;
    if ( slave_on == 0 )
      break ;
    if ( r4data_list_build( l4, slave_on, expr, check_type ) == 1 )
      must_add = 1 ;
  }

  if ( must_add )
    r4data_list_add( l4, relate->data, relate ) ;
  else
    if ( relate->relation_type == relate4scan )
      relate->sort_type = relate4sort_skip ;

  return must_add ;
}

int S4FUNCTION relate4blank_set( RELATE4 *relate, char direction )
{
  RELATE4 *slave ;
  int rc ;

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  relate->is_read = 1 ;
  if ( direction == 1 )
  {
    if ( d4go_eof( relate->data ) < 0 )
      return -1 ;
  }
  else
  {
    rc = d4top( relate->data ) ;
    if ( rc )
      return rc ;
    rc = d4skip( relate->data, -1L ) ;
    relate->data->rec_num = -1 ;
    d4blank( relate->data ) ;
    relate->data->record_changed = 0 ;
    if ( relate->code_base->error_code < 0 )
      return -1 ;
#ifndef S4SINGLE
    if ( rc == r4locked || rc < 0 )
      return rc ;
#endif
  }

  for( slave = 0 ;; )
  {
    slave = (RELATE4 *)l4next( &relate->slaves, slave ) ;
    if ( slave == 0 )
      return 0 ;
    rc = relate4blank_set( slave, direction ) ;
    if ( rc < 0 )
      return rc ;
  }
}

int S4FUNCTION relate4bottom( RELATE4 *relate )
{
  RELATION4 *relation ;
  int rc, rc2 ;
  long rec ;

#ifdef S4VBASIC
  if ( c4parm_check( relate->code_base, 1, E4_R4BOTTOM ) )
    return -1 ;
#endif

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4BOTTOM ) ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  relation = relate->relation ;
  relate = &relation->relate ;

  if ( relation->skip_backwards == 0 )
  {
    relate4sort_free( relation, 0 ) ;
    relate4skip_enable( relate, 1 ) ;
  }

  rc = relate4top( relate ) ;
  if ( rc )
    return rc ;

  relate4set_not_read( relate ) ;

  if ( relation->in_sort == relate4sort_done )
  {
    if ( relate4sort_get_record( relation, relation->sort_rec_count ) == r4eof )
      return r4eof ;
    relation->sort_rec_on = relation->sort_rec_count ;
  }
  else
  {
    if ( d4bottom( relate->data ) < 0 )
      return -1 ;
    if ( relation->expr_source )
    {
      rec = d4recno( relate->data ) ;
#ifndef S4INDEX_OFF
      if ( relate->data_tag )
        while ( f4flag_is_set_flip( &relate->set, rec ) == 0 )
        {
#ifdef S4HAS_DESCENDING
          rc = (int)t4dskip( relate->data_tag, -1L ) ;
#else
          rc = (int)t4skip( relate->data_tag, -1L ) ;
#endif
          if ( rc != -1 )
          {
            if ( rc == 0 )
              return r4bof ;
            return rc ;
          }
          rec = t4recno( relate->data_tag ) ;
        }
      else
      {
#endif

        if ( f4flag_is_set_flip( &relate->set, rec ) == 0 )
        {
          rec = d4recno( relate->data ) - f4flag_get_next_flip( &relate->set, d4recno( relate->data), -1 ) ;
          if ( rec == 0 )
            return r4bof ;
        }
#ifndef S4INDEX_OFF
      }
#endif
      d4go( relate->data, rec ) ;
    }
    relate4set_not_read( relate ) ;
  }

  rc = relate4read_rest( relate, -1 ) ;
  if ( rc == relate4filter_record )
    rc = relate4skip( relate, -1L ) ;

  if ( rc < 0 || rc == r4terminate )
    return rc ;

  if ( relation->expr_source )
  {
    rc2 = log4true( &relation->log ) ;
    if ( rc2 == r4terminate )
      return r4terminate ;
    if ( rc2 == 0 )
    {
      if ( relation->in_sort == relate4sort_skip )  /* must temporarily disable in order to get a matching scan if available */
      {
        relation->in_sort = 0 ;
        rc = relate4skip( relate, -1L ) ;
        relation->in_sort = relate4sort_skip ;
      }
      else
        rc = relate4skip( relate, -1L ) ;
    }
  }

  return rc ;
}

int S4FUNCTION relate4build_scan_list( RELATE4 *relate, RELATION4 *relation )
{
  RELATE4 *relate_on ;
  RELATE4LIST *ptr ;

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  for( relate_on = 0 ;; )
  {
    relate_on = (RELATE4 *)l4next(&relate->slaves,relate_on) ;
    if ( relate_on  == 0 )
      break ;
    if ( relate4build_scan_list( relate_on, relation ) < 0 )
      return -1 ;
  }

  if ( relate->relation_type == relate4scan || relate == &relation->relate )
  {
    ptr = (RELATE4LIST *)mem4create_alloc( relate->code_base, &relation->relate_list_memory, 5, sizeof(RELATE4LIST), 5, 0 ) ;
    if ( ptr == 0 )
      return -1 ;
    ptr->ptr = relate ;
    l4add( &relation->relate_list, ptr ) ;
  }
  return 0 ;
}

void S4FUNCTION relate4changed( RELATE4 *relate )
{
  RELATION4 *relation ;
  int j ;
  void *ptr ;

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4CHANGED ) ;
#endif

  if ( relate->code_base->error_code < 0 )
    return ;

  u4free( relate->scan_value ) ;
  relate->scan_value = 0 ;
  relation = relate->relation ;
  relation->is_initialized = 0 ;
  relate4sort_free( relation, 0 ) ;

  for( ptr = 0 ;; )
  {
    ptr = l4last( &relation->relate_list) ;
    if ( ptr == 0 )
      break ;
    l4remove( &relation->relate_list, ptr ) ;
    mem4free( relation->relate_list_memory, ptr ) ;
  }

  u4free( relation->relate.set.flags ) ;
  memset( (void *)&relation->relate.set, 0, sizeof( F4FLAG ) ) ;

  if ( relation->log.expr != 0 )
  {
    for( j = relation->log.expr->info_n; --j >= 0; )
    {
      E4INFO_REPORT *info_ptr = relation->log.info_report + j ;
      if ( info_ptr->data_list != 0 )
      {
        u4free( (void *)info_ptr->data_list->pointers ) ;
#ifdef S4DEBUG
        info_ptr->data_list->pointers = 0 ;
#endif
        mem4free( relation->data_list_memory, info_ptr->data_list ) ;
#ifdef S4DEBUG
        info_ptr->data_list = 0 ;
#endif
      }
    }

    expr4free( relation->log.expr ) ;
    relation->log.expr = 0 ;
    u4free( relation->log.info_report ) ;
    relation->log.info_report = 0 ;
  }
  relation->in_sort = 0 ;
}

RELATE4 *S4FUNCTION relate4create_slave( RELATE4 *master, DATA4 *slave_data, char *master_expr, TAG4 *slave_tag )
{
  RELATION4 *relation ;
  RELATE4 *slave ;
  char t1, t2 ;
  int failed ;

  if ( master == 0 )
    return 0 ;

#ifdef S4VBASIC
  if ( c4parm_check( master->code_base, 1, E4_R4CREATE_SLAVE ) )
    return (RELATE4 *) 0 ;
#endif

  if ( master->code_base->error_code < 0 )
    return 0 ;

  relation = master->relation ;

#ifdef S4DEBUG
  if ( slave_data == 0 || master_expr == 0 )
    e4severe( e4parm, E4_R4CREATE_SLAVE ) ;

  /* check that the d4 doesn't belong to any existing relation */
  if ( relate4lookup_relate( &relation->relate, slave_data ) != 0 )
  {
    e4( master->code_base, e4parm, E4_PARM_REL ) ;
    return 0 ;
  }
#endif

  relate4changed( master ) ;

  slave = (RELATE4 *)mem4create_alloc( master->code_base, &relation->relate_memory, 5, sizeof(RELATE4), 5, 0 ) ;
  if ( slave == 0 )
    return 0 ;

  relate4init_relate( slave, master->relation, slave_data, master->code_base ) ;

  slave->master_expr = expr4parse( master->data, master_expr ) ;
  if ( slave->master_expr == 0 )
  {
    mem4free( relation->relate_memory, slave ) ;
    return 0 ;
  }

#ifndef S4INDEX_OFF
  if ( slave_tag != 0 )
  {
    t1 = t4type( slave_tag ) ;
    t2 = expr4type( slave->master_expr ) ;
    if ( t1 != t2 )   /* check if types are compatible... */
    {
      failed = 1 ;

      if ( t1 == r4date && t2 == r4date_doub )
        failed = 0 ;
      else
      {
        if ( t1 == r4date_doub && t2 == r4date )
          failed = 0 ;
        else
        {
          if ( t1 == r4num && t2 == r4num_doub )
            failed = 0 ;
          else
            if ( t1 == r4num_doub && t2 == r4num )
              failed = 0 ;
        }
      }
      if ( failed == 1 )
      {
        if ( master->code_base->relate_error )
          e4( master->code_base, e4relate, E4_RELATE_RCS ) ;
        mem4free( relation->relate_memory, slave ) ;
        return 0 ;
      }
    }
  }
#endif

  slave->data_tag = slave_tag ;
  slave->master = master ;

  l4add( &master->slaves, slave ) ;
  relate4match_len( slave, -1 ) ; /* Set to maximum */

  return slave ;
}

/* checks if the given dbf belongs to one of the relations in relation */
int S4FUNCTION relate4dbf_in_relation( RELATE4 *relate, DATA4 *dbf )
{
  RELATE4 *relate_on ;

  relate_on = &relate->relation->relate ;
  while( relate_on->master )
    relate_on = relate_on->master ;

  do
  {
    if ( relate_on->data == dbf )
      return 1 ;
  } while( relate4next( &relate_on ) != 2 ) ;

  return 0 ;
}

int S4FUNCTION relate4do( RELATE4 *relate )
{
  int rc ;

#ifdef S4VBASIC
  if ( c4parm_check( relate->code_base, 1, E4_R4DO ) )
    return -1 ;
#endif

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4DO ) ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  relate4set_not_read( relate ) ;
  rc = relate4read_rest( relate, 0 ) ;
  if ( rc == relate4filter_record )  /* no match is an error */
  {
    if ( relate->code_base->relate_error )
      return e4( relate->code_base, e4lookup_err, relate->data->alias ) ;
    return r4terminate ;
  }

  return rc ;
}

int S4FUNCTION relate4do_one( RELATE4 *relate )
{
  int rc ;

#ifdef S4VBASIC
  if ( c4parm_check( relate->code_base, 1, E4_R4DO_ONE ) )
    return -1 ;
#endif

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4DO_ONE ) ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  if ( relate->master == 0 )   /* no master, so we must be read */
    return 0 ;
  relate4set_not_read( relate ) ;
  rc = relate4lookup( relate, 0 ) ;
  relate->is_read = relate->master->is_read ;  /* we are read if master is read */
  if ( rc == relate4filter_record )  /* no match is an error */
  {
    if ( relate->code_base->relate_error )
      return e4( relate->code_base, e4lookup_err, relate->data->alias ) ;
    return r4terminate ;
  }
  return rc ;
}

int S4FUNCTION relate4eof( RELATE4 *relate )
{
#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4EOF ) ;

  if ( relate->relation->is_initialized == 0 )
  {
    e4( relate->code_base, e4info, E4_INFO_REL ) ;
    return -1 ;
  }
#endif

  if ( relate->relation->in_sort == relate4sort_done )
    return relate->relation->sort_eof_flag ;
  else
    return d4eof( relate->relation->relate.data ) ;
}

int S4FUNCTION relate4error_action( RELATE4 *relate, int code )
{
  int rc ;

  if ( relate == 0 )
    return -1 ;

#ifdef S4DEBUG
  if ( code != relate4blank && code != relate4skip_rec && code != relate4terminate )
    e4severe( e4parm, E4_INFO_IVE ) ;
#endif
  rc = relate->error_action ;
  relate->error_action = code ;
  return rc ;
}

int S4FUNCTION relate4free( RELATE4 *relate, int close_files )
{
  int rc = 0 ;
  RELATION4 *relation ;
  RELATE4 *relate_on ;

  if ( relate == 0 )
    return -1 ;

#ifdef S4VBASIC
  if ( c4parm_check( relate->code_base, 1, E4_R4FREE ) )
    return -1 ;
#endif

#ifndef S4SINGLE
  relate4unlock( relate ) ;
#endif

  relate4changed( relate ) ;
  relation = relate->relation ;
  relate = &relation->relate ;

  if( close_files )
    if( d4close( relate->data ) < 0 )
      rc = -1 ;

  for( relate_on = 0 ;; )
  {
    relate_on = (RELATE4 *)l4last( &relate->slaves ) ;
    if ( relate_on == 0 )
      break ;
    if( relate4free_relate( relate_on, close_files ) < 0 )
      rc = -1 ;
  }

  mem4release( relation->relate_memory ) ;
  mem4release( relation->relate_list_memory ) ;
  mem4release( relation->data_list_memory ) ;

  relate4sort_free( relation, 1 ) ;
  u4free( relation->expr_source ) ;
  u4free( relation ) ;

  return rc ;
}

int S4FUNCTION relate4free_relate( RELATE4 *relate, int close_files )
{
  int rc = 0 ;
  RELATE4 *relate_on ;
  if ( relate->master == 0 )
    return relate4free( relate, close_files ) ;

  relate4changed( relate ) ;

  if( close_files )
    if( d4close( relate->data ) < 0 )
      rc = -1 ;

  for( ;; )
  {
    relate_on = (RELATE4 *)l4last( &relate->slaves) ;
    if ( relate_on == 0 )
      break ;
    if( relate4free_relate( relate_on, close_files ) < 0 )
      rc = -1 ;
  }

  expr4free( relate->master_expr ) ;
  u4free( relate->scan_value ) ;
  relate->scan_value = 0 ;
  u4free( relate->set.flags ) ;
  relate->set.flags = 0 ;

  l4remove( &relate->master->slaves, relate ) ;
  mem4free( relate->relation->relate_memory, relate ) ;
  relate = 0 ;

  return rc ;
}

RELATE4 *S4FUNCTION relate4init( DATA4 *master )
{
  RELATION4 *relation ;
  CODE4 *code_base ;

#ifdef S4VBASIC
  if ( c4parm_check( master->code_base, 1, E4_R4INIT ) )
    return (RELATE4 *) 0 ;
#endif

#ifdef S4DEBUG
  if ( master == 0 )
    e4severe( e4parm, E4_R4INIT ) ;
#endif

  code_base = master->code_base ;
  if ( code_base->error_code < 0 )
    return 0 ;

  relation = (RELATION4 *)u4alloc_er( code_base, sizeof( RELATION4 ) ) ;
  if ( relation == 0 )
    return 0 ;

  relate4init_relate( &relation->relate, relation, master, code_base ) ;
  relation->log.relation = relation ;
  relation->log.code_base = code_base ;
  relation->sort.file.hand = -1 ;
  relation->sorted_file.hand = -1 ;

  return &relation->relate ;
}

void S4FUNCTION relate4init_relate( RELATE4 *relate, RELATION4 *relation, DATA4 *data, CODE4 *code_ptr )
{
  relate->code_base = code_ptr ;
  relate->relation_type = relate4exact ;
  relate->data = data ;
  relate->error_action  = relate4blank ;
  relate->relation = relation ;
  relate->data->count = d4reccount( relate->data ) ;
}

int S4FUNCTION relate4lock( RELATE4 *relate )
{
#ifdef S4SINGLE
  return 0 ;
#else
  CODE4 *code_base ;
  int rc, old_attempts, count ;
  DATA4 *data_on ;

  if ( relate == 0 )
    return -1 ;

#ifdef S4VBASIC
  if ( c4parm_check( relate->code_base, 1, E4_R4LOCK ) )
    return -1 ;
#endif

  code_base = relate->code_base ;
  if ( code_base->error_code < 0 )
    return -1 ;

  relate->relation->locked = 1 ;

  count = old_attempts = code_base->lock_attempts ;  /* take care of wait here */
  code_base->lock_attempts = 1 ;

  for(;;)
  {
    rc = 0 ;
    for ( data_on = (DATA4 *)l4first( &code_base->data_list) ; data_on ; data_on = (DATA4 *)l4next( &code_base->data_list, data_on ) )
      if ( relate4dbf_in_relation( relate, data_on ) )
      {
        rc = d4lock_all( data_on ) ;
        if ( rc != 0 )
          break ;
      }

    if ( rc != r4locked )
      break ;

    relate4unlock( relate ) ;
    if ( count == 0 )
      break ;

    if ( count > 0 )
      count-- ;

#ifdef S4TEMP
    if ( d4display_quit( &display ) )
      e4severe( e4result, E4_RESULT_EXI ) ;
#endif

    u4delay_sec() ;   /* wait a second */
  }

  code_base->lock_attempts = old_attempts ;
  if ( code_base->error_code < 0 )
    return -1 ;

  return rc ;
#endif
}

/* direction : -1 = look backwards, 0 = lookup only, 1 = look forwards */
int S4FUNCTION relate4lookup( RELATE4 *relate, char direction )
{
  int rc, len ;
  long recno ;
  char *ptr ;

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4LOOKUP ) ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  if ( direction != 0 && relate->relation->is_initialized == 0 )
  {
#ifdef S4DEBUG
    e4( relate->code_base, e4info, E4_INFO_REL ) ;
#endif
    return -1 ;
  }

  relate->is_read = 1 ;
  if ( relate->master == 0 )
    return 0 ;

  d4tag_select( relate->data, relate->data_tag ) ;

#ifndef S4INDEX_OFF
  if ( relate->data_tag == 0 )
  {
#endif
    recno = (long)expr4double( relate->master_expr ) ;
    if ( relate->code_base->error_code < 0 )
      return -1 ;

    if ( direction != 0 )
      if ( f4flag_is_set_flip( &relate->set, recno ) == 0 )
        return relate4filter_record ;

    rc = d4go( relate->data, recno ) ;
    if ( rc < 0 )
      return -1 ;
    if ( rc != r4entry )  /* if not error, then return */
      return 0 ;
    if ( relate->relation_type == relate4approx )
    {
      d4go_eof( relate->data ) ;
      return 0 ;
    }
#ifndef S4INDEX_OFF
  }
  else
  {
    len = expr4key( relate->master_expr, &ptr ) ;
    if ( len < 0 )
      return -1 ;

    len = (len < relate->match_len) ? len : relate->match_len ;   /* min of len and match len */

    if ( relate->relation_type == relate4scan )
    {
#ifdef S4DEBUG
      if ( relate->master == 0 )
        e4severe( e4info, E4_RELATE_MEN ) ;
#endif
      if ( relate->master->scan_value == 0 )
      {
        relate->master->scan_value_len = len ;
        relate->master->scan_value = (char *)u4alloc_er( relate->code_base, len ) ;
        if ( relate->master->scan_value == 0 )
          return -1 ;
      }
      memcpy( relate->master->scan_value, ptr, len ) ;
    }

    rc = t4seek( relate->data_tag, ptr, len ) ;
    if ( rc < 0 )
      return -1 ;
    if ( relate->relation_type == relate4approx || rc == 0 )
    {
      if ( t4eof( relate->data_tag) )
      {
        d4go_eof( relate->data ) ;
        return 0 ;
      }

      if ( (int)direction < 0 && rc == 0 && relate->relation_type == relate4scan )  /* look for last one */
        for( ;; )
        {
#ifdef S4HAS_DESCENDING
          if ( !t4dskip( relate->data_tag, 1L ) )
#else
            if ( !t4skip( relate->data_tag, 1L ) )
#endif
              break ;
          if ( u4memcmp( t4key_data( relate->data_tag )->value, ptr, len ) )
          {
#ifdef S4HAS_DESCENDING
            t4dskip( relate->data_tag, -1L ) ;
#else
            t4skip( relate->data_tag, -1L ) ;
#endif
            break ;
          }
        }

      recno = t4key_data( relate->data_tag )->num ;
      if ( direction != 0 )
        if ( f4flag_is_set_flip( &relate->set, recno ) == 0 )
          return relate4filter_record ;
      if ( d4go( relate->data, recno ) < 0 )
        return -1 ;
      return 0 ;
    }
    else
      recno = t4key_data( relate->data_tag )->num ;
  }
#endif

#ifdef S4CHANGE_BACK
  if ( relate->relation_type == relate4scan )  /* no matching records, so filter */
    return relate4filter_record ;
#endif

  switch( relate->error_action )  /* if got here, must be error condition */
  {
  case relate4blank:
    if ( d4go_eof( relate->data ) < 0 )
      return -1 ;
    if ( direction != 0 )
      if ( f4flag_is_set_flip( &relate->set, recno ) == 0 )
        return relate4filter_record ;
    return 0 ;
  case relate4skip_rec:
    return relate4filter_record ;
  case relate4terminate:
    if ( relate->code_base->relate_error )
      return e4( relate->code_base, e4lookup_err, relate->data->alias ) ;
    return r4terminate ;
  default:
#ifdef S4DEBUG
    /* should never get this far */
    e4severe( e4info, E4_RELATE_EAI ) ;
#endif
    return -1 ;
  }
}

RELATE4 *S4FUNCTION relate4lookup_relate( RELATE4 *relate, DATA4 *d4 )
{
  RELATE4 *relate_return, *relate_on ;

  if ( relate->data == d4 )
    return relate ;
  for( relate_on = 0 ;; )
  {
    relate_on = (RELATE4 *)l4next( &relate->slaves, relate_on) ;
    if ( relate_on == 0 )
      return 0 ;
    relate_return = relate4lookup_relate( relate_on, d4 ) ;
    if ( relate_return )
      return relate_return ;
  }
}


int S4FUNCTION relate4match_len( RELATE4 *relate, int match_len )
{
  int len ;

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4MATCH_LEN ) ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  len = expr4key_len( relate->master_expr ) ;

#ifdef S4CLIPPER
  if ( match_len <= 0 )
    match_len = len ;
#else
  if ( match_len <= 0 )
  {
    relate->match_len = len ;
    return match_len ;
  }
#endif

#ifndef S4INDEX_OFF
#ifndef S4CLIPPER
  if ( relate->data_tag )
    if( expr4type(  relate->data_tag->expr ) != r4str)  /* make sure r4str only */
    {
#ifdef S4DEBUG
      e4( relate->code_base, e4relate, E4_RELATE_REL ) ;
#endif
      return -1 ;
    }
#endif
#endif

  if ( match_len >= len )
    match_len = len ;

#ifndef S4INDEX_OFF
  if ( relate->data_tag )
  {
    len = expr4key_len( relate->data_tag->expr ) ;
    if ( match_len >= len )
      match_len = len ;
  }
#endif

  relate->match_len = match_len ;
  relate4changed( relate ) ;
  return match_len ;
}

int S4FUNCTION relate4next( RELATE4 **ptr_ptr )
{
  RELATE4 *cur ;
  void *next_link ;
  int rc ;

#ifdef S4DEBUG
  if ( ptr_ptr == 0 )
    e4severe( e4parm, E4_R4NEXT ) ;
  if ( *ptr_ptr == 0 )
    e4severe( e4parm, E4_R4NEXT ) ;
#endif

  cur = *ptr_ptr ;
  rc = 1 ;

  if ( cur->slaves.n_link > 0 )
  {
    *ptr_ptr = (RELATE4 *)l4first( &cur->slaves ) ;
    return 1 ;
  }

  for(;;)
  {
    rc -- ;
    if ( cur->master == 0 )
    {
      *ptr_ptr = 0 ;
      return 2 ;
    }

    next_link = l4next( &cur->master->slaves, cur ) ;
    if ( next_link )
    {
      *ptr_ptr = (RELATE4 *)next_link ;
      return rc ;
    }

    cur = cur->master ;
  }
}

int S4FUNCTION relate4next_record_in_scan( RELATE4 *relate )
{
  long next_rec ;
  int  rc, save_code, len ;
  B4KEY_DATA *key ;
  char *ptr ;

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4NEXT_RIS ) ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  if ( relate->relation->is_initialized == 0 )
  {
#ifdef S4DEBUG
    e4( relate->code_base, e4info, E4_INFO_REL ) ;
#endif
    return -1 ;
  }

  if ( relate->relation->in_sort == relate4sort_skip && relate->sort_type == relate4sort_skip )
    return r4eof ;

#ifndef S4INDEX_OFF
  if ( relate->data_tag == 0 )
  {
#endif
    if ( d4bof( relate->data ) )
      next_rec = 1 ;
    else
      next_rec = d4recno( relate->data ) + 1 ;
    next_rec += f4flag_get_next_flip( &relate->set, next_rec, 1 ) ;
    if ( next_rec > relate->data->count )
    {
      relate->data->count = d4reccount( relate->data ) ;
      if ( next_rec > relate->data->count )
        return r4eof ;
    }
#ifndef S4INDEX_OFF
  }
  else
    for(;;)
    {
      if ( d4bof( relate->data ) )
      {
        if ( relate->data->num_recs == 0 )
          return r4eof ;
        len = expr4key( relate->master_expr, &ptr ) ;
        if ( len < 0 )
          return -1 ;

        len = (len < relate->match_len) ? len : relate->match_len ;   /* min of len and match len */

        rc = (int)t4seek( relate->data_tag, relate->master->scan_value, len ) ;
        if ( rc < 0 )
          return -1 ;
        if ( rc == 0 )
          rc = 1 ;
        else
          rc = 0 ;
      }
      else
#ifdef S4HAS_DESCENDING
        rc = (int)t4dskip( relate->data_tag, 1 ) ;
#else
      rc = (int)t4skip( relate->data_tag, 1 ) ;
#endif
      if ( rc < 0 )
        return -1 ;
      if ( rc != 1 )
        return r4eof ;

      key = t4key_data( relate->data_tag) ;
      next_rec = key->num ;

      if ( relate->master )
        if ( u4memcmp( key->value, relate->master->scan_value, relate->master->scan_value_len ) != 0 )
          return r4eof ;

      if ( f4flag_is_set_flip( &relate->set, next_rec ) )
        break ;
    }
#endif

  save_code = relate->code_base->go_error ;
  relate->code_base->go_error = 0 ;
  rc = d4go( relate->data, next_rec ) ;
  relate->code_base->go_error = save_code ;
  if ( rc < 0 )
    return -1 ;
  if ( rc == r4entry )
    return r4eof ;
  relate->is_read = 1 ;   /* we have updated this one */
  return relate4skipped ;
}

int S4FUNCTION relate4next_scan_record( RELATION4 *relation )
{
  RELATE4 *relate ;
  int rc, rc2 ;

  if ( relation->relate.code_base->error_code < 0 )
    return -1 ;

  relation->relate_list.selected = (void *)l4first( &relation->relate_list ) ;
  for(;;)
  {
    relate = ((RELATE4LIST *)relation->relate_list.selected)->ptr ;
    relate4set_not_read( relate ) ;  /* This data file & its slaves */
    if ( relation->in_sort == relate4sort_done )
      if ( r4data_list_find( &relation->sort_data_list, relate ) )
        return relate4sort_next_record( relation ) ;

    rc = relate4next_record_in_scan( relate ) ;
    if ( rc == relate4skipped )
      return 0 ;
    if ( rc < 0 )
      return -1 ;
    rc2 = relate4blank_set( relate, (char)1 ) ;
    if ( rc2 == r4locked || rc2 < 0 )  /* error or locked */
      return rc2 ;
    if ( relate->master == 0 )
      if ( d4eof( relate->data ) )
        return r4eof ;
    relation->relate_list.selected = l4next( &relation->relate_list, relation->relate_list.selected);
  }
}

int S4FUNCTION relate4prev_record_in_scan( RELATE4 *relate )
{
  long next_rec ;
  int  rc, save_code ;
  B4KEY_DATA *key ;

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4PREV_RIS ) ;
#endif

  if ( relate->relation->is_initialized == 0 )
  {
#ifdef S4DEBUG
    e4( relate->code_base, e4info, E4_INFO_REL ) ;
#endif
    return -1 ;
  }

#ifndef S4INDEX_OFF
  if ( relate->data_tag == 0 )
  {
#endif
    next_rec = d4recno( relate->data ) - 1 ;
    next_rec -= f4flag_get_next_flip( &relate->set, next_rec, -1 ) ;
    if ( next_rec <= 0 )
      return r4bof ;
    if ( next_rec > relate->data->count )
    {
      relate->data->count = d4reccount( relate->data ) ;
      if ( next_rec > relate->data->count )
        return r4eof ;
    }
#ifndef S4INDEX_OFF
  }
  else
    for(;;)
    {
      if ( relate4eof( relate ) )   /* if eof in relate, just leave on last tag entry */
        rc = t4eof( relate->data_tag ) ? 0 : -1 ;
      else
      {
        if ( d4eof( relate->data ) == 1 )
        {
          if ( relate->data->num_recs == 0 )
            return r4bof ;
          rc = (int)t4bottom( relate->data_tag ) ;
          if ( rc < 0 )
            return -1 ;
          if ( rc == 0 )
            rc = -1 ;
          else
            rc = 0 ;
        }
        else
#ifdef S4HAS_DESCENDING
          rc = (int)t4dskip( relate->data_tag, -1L ) ;
#else
        rc = (int)t4skip( relate->data_tag, -1L ) ;
#endif
      }
      if ( rc > 0 )
        return -1 ;
      if ( rc != -1L )
        return r4bof ;

      key = t4key_data( relate->data_tag) ;
      next_rec = key->num ;

      if ( relate->master )
        if ( u4memcmp( key->value, relate->master->scan_value, relate->master->scan_value_len ) != 0 )
          return r4bof ;

      if ( f4flag_is_set_flip( &relate->set, next_rec ) )
        break ;
    }
#endif

  save_code = relate->code_base->go_error ;
  relate->code_base->go_error = 0 ;
  rc = d4go( relate->data, next_rec ) ;
  relate->code_base->go_error = save_code ;
  if ( rc < 0 )
    return -1 ;
  if ( rc == r4entry )
    return r4eof ;
  relate->is_read = 1 ;   /* we have updated this one */
  return relate4skipped ;
}

int S4FUNCTION relate4prev_scan_record( RELATION4 *relation )
{
  RELATE4 *relate ;
  int rc, rc2 ;

  if ( relation->relate.code_base->error_code < 0 )
    return -1 ;

  relate = ((RELATE4LIST *)l4last( &relation->relate_list ))->ptr ;
  if ( relate4eof( relate ) )  /* at eof means we must read this record */
    if ( relation->in_sort != relate4sort_done )
    {
      rc2 = relate->code_base->read_lock ;  /* suspend the potential lock call... */
      relate->code_base->read_lock = 0 ;
      rc = relate4bottom( relate ) ;
      relate->code_base->read_lock = rc2 ;
      if ( rc == r4eof )   /* no records, so can't skip back */
        return r4bof ;
else
  return rc ;
    }

  relation->relate_list.selected = (void *)l4first( &relation->relate_list ) ;
  for(;;)
  {
    relate = ((RELATE4LIST *)relation->relate_list.selected)->ptr ;
    relate4set_not_read( relate ) ;  /* This data file & its slaves */
    if ( relation->in_sort == relate4sort_done )
      if ( r4data_list_find( &relation->sort_data_list, relate ) )
        return relate4sort_prev_record( relation ) ;

    rc = relate4prev_record_in_scan(relate) ;
    if ( rc == relate4skipped )
    {
      if ( relate4eof( relate ) )
      {
        if ( relate->relation->in_sort == relate4sort_done && relate->relation->sort_eof_flag == 1 )
        {
          relation->sort_rec_on-- ;  /* move off eof on sort part */
          relate->relation->sort_eof_flag = 0 ;
        }
        else
          d4go( relate->relation->relate.data, d4reccount( relate->relation->relate.data ) ) ;
      }
      return 0 ;
    }
    if ( rc < 0 )
      return -1 ;
    rc2 = relate4blank_set( relate, (char)-1 ) ;
    if ( rc2 == r4locked || rc2 < 0  )  /* error or locked */
      return rc2 ;
    if ( relate->master == 0 )
    {
      if ( d4bof(relate->data) )
        return r4bof ;
      if ( d4eof(relate->data) )
        return r4eof ;
    }
    relation->relate_list.selected = l4next( &relation->relate_list, relation->relate_list.selected);
  }
}

int S4FUNCTION relate4query_set( RELATE4 *relate, char *expr )
{
  int len ;

  if ( relate == 0 )
    return -1 ;

#ifdef S4VBASIC
  if ( c4parm_check( relate->code_base, 1, E4_R4QUERY_SET ) )
    return -1 ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  relate4changed( relate ) ;
  u4free( relate->relation->expr_source ) ;
  relate->relation->expr_source = 0 ;
  if ( expr == 0 )
    return 0 ;
  if ( expr[0] == 0 )
    return 0 ;
  len = strlen( expr ) + 1 ;
  relate->relation->expr_source = (char *)u4alloc_er( relate->code_base, len ) ;
  if ( relate->relation->expr_source == 0 )
    return -1 ;
  memcpy( relate->relation->expr_source, expr, len ) ;
  return 0 ;
}

int S4FUNCTION relate4read_in( RELATE4 *relate )
{
  int rc ;
  if ( relate->code_base->error_code < 0 )
    return -1 ;
  if ( relate->is_read )
    return 0 ;
  if ( relate->master )
    if ( relate->master->is_read == 0 )
    {
      rc = relate4read_in( relate->master ) ;
      if ( rc == relate4filter_record || rc == r4terminate )
        return rc ;
    }

  return relate4lookup( relate, 1 ) ;
}

int S4FUNCTION relate4read_rest( RELATE4 *relate, char direction )
{
  RELATE4 *slave ;
  int rc ;
  int scan_done ;

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4READ_REST ) ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1;

  if ( relate->is_read == 0 )
  {
    rc = relate4lookup( relate, direction );
    if ( rc < 0 || rc == relate4filter_record || rc == r4terminate )
      return rc ;
  }

  scan_done = 0 ;
  for( slave = 0 ;; )
  {
    slave = (RELATE4 *)l4next( &relate->slaves, slave ) ;
    if ( slave == 0 )
      return 0 ;
    if ( slave->relation_type == relate4scan && scan_done == 1 )
    {
      relate4blank_set( slave, (char)(1 - direction) ) ;
      slave->is_read = 1 ;
      rc = relate4read_rest( slave, direction ) ;
    }
    else
    {
      rc = relate4read_rest( slave, direction ) ;
      if ( slave->relation_type == relate4scan && rc == 0 )
        if ( !d4eof( slave->data ) )
          scan_done = 1 ;
    }
    if ( rc < 0 || rc == relate4filter_record || rc == r4terminate )
      return rc ;
  }
}

void S4FUNCTION relate4set_not_read( RELATE4 *relate )
{
  RELATE4 *slave_on ;
  if ( relate->is_read )
  {
    relate->is_read = 0 ;
    for( slave_on = 0 ;; )
    {
      slave_on = (RELATE4 *)l4next(&relate->slaves,slave_on) ;
      if ( slave_on == 0 )
        return ;
      relate4set_not_read( slave_on ) ;
    }
  }
}

int S4FUNCTION relate4skip( RELATE4 *relate, long num_skip )
{
  int rc, sort_status, rc2 ;
#ifdef S4UNIX
  int sign ;
#else
  signed char sign ;
#endif
  RELATION4 *relation ;
#ifdef S4REPORT
#ifdef S4WINDOWS
  char countstring[22];
  static long int scanreccount = 0, selectreccount = 0, sortreccount = 0;
  HWND statwin;
#endif
#endif

#ifdef S4VBASIC
  if ( c4parm_check( relate->code_base, 1, E4_R4SKIP ) )
    return -1 ;
#endif

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4SKIP ) ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  if ( relate->relation->is_initialized == 0 )
  {
#ifdef S4DEBUG
    e4( relate->code_base, e4info, E4_INFO_REL ) ;
#endif
    return -1 ;
  }

  relation = relate->relation ;
  relate = &relation->relate ;

  if ( num_skip < 0 )
  {
    if ( relation->skip_backwards == 0 )
    {
#ifdef S4DEBUG
      e4describe( relate->code_base, e4info, E4_R4SKIP, E4_INFO_BAC, (char *)0 ) ;
#endif
      return -1 ;
    }
    sign = -1 ;
  }
  else
    sign = 1 ;

  sort_status = 0 ;
  rc = 0 ;
  for( ; num_skip ; )
  {

#ifdef S4REPORT
#ifdef S4WINDOWS
    if(GetWindowWord(relate->code_base->hWnd,8) == 666)
      statwin = relate->code_base->hWnd;

    if( statwin )
    {
      if(GetWindowWord(statwin,6)==0)
      {
        SetWindowWord(statwin,6,1);
        scanreccount = sortreccount = selectreccount = 0;
      }

      scanreccount++;
      if( scanreccount < 20 || (scanreccount % 20) == 0 )
      {
        c4ltoa45(scanreccount,countstring,sizeof(countstring)-1);
        countstring[21] = 0;
        SendMessage((HWND)GetWindowWord(statwin,0),WM_SETTEXT,0,(LPARAM)((LPSTR)countstring));
      }
    }
#endif
#endif

    if ( sign > 0 )
    {
      rc = relate4next_scan_record( relation ) ;
      if ( rc == r4eof )
        break ;
    }
    else
    {
      rc = relate4prev_scan_record( relation ) ;
      if ( rc == r4bof )
        break ;
    }

#ifdef S4SINGLE
    if ( rc < 0 )
#else
      if ( rc < 0 || rc == r4locked )
#endif
        break ;

    rc = relate4read_rest( relate, sign ) ;
    if ( rc == relate4filter_record )
      continue ;

    if ( rc < 0 || rc == r4terminate )
      break ;

    if ( relation->expr_source )
    {
      rc2 = log4true(&relation->log ) ;
      if ( rc2 == r4terminate )
      {
        rc = r4terminate ;
        break ;
      }
      if ( rc2 == 0 )
      {
        if ( relation->in_sort == relate4sort_skip )  /* must temporarily disable in order to get a matching scan if available */
        {
          sort_status = 1 ;
          relation->in_sort = 0 ;
        }
        continue ;
      }
    }

    num_skip -= sign ;
  }

#ifdef S4WINDOWS
#ifdef S4REPORT
  if(GetWindowWord(relate->code_base->hWnd,8) == 666)
    statwin = relate->code_base->hWnd;

  if( statwin )
  {
    selectreccount++;
    if(selectreccount < 20 || (selectreccount % 20) == 0)
    {
      c4ltoa45(selectreccount,countstring,sizeof(countstring)-1);
      countstring[21] = 0;
      SendMessage((HWND)GetWindowWord(statwin,2),WM_SETTEXT,0,(LPARAM)((LPSTR)countstring));
    }
    if(relate->relation->in_sort)
    {
      sortreccount++;
      if( sortreccount < 20 || (sortreccount % 20) == 0)
      {
        c4ltoa45(sortreccount,countstring,sizeof(countstring)-1);
        countstring[21] = 0;
        SendMessage((HWND)GetWindowWord(statwin,4),WM_SETTEXT,0,(LPARAM)((LPSTR)countstring));
      }
    }
  }
#endif
#endif

  if ( sort_status == 1 )
    relation->in_sort = relate4sort_skip ;
  return rc ;
}

int S4FUNCTION relate4skip_enable( RELATE4 *relate, int do_enable )
{
  if ( relate == 0 )
    return -1 ;

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  if ( relate->relation->skip_backwards != (char) do_enable )
  {
    relate->relation->skip_backwards = (char) do_enable ;
    relate4changed( relate ) ;
  }
  return 0 ;
}

int S4FUNCTION relate4sort( RELATE4 *relate )
{
  EXPR4 *sort_expr ;
  int rc, i, len ;
  long j, zero = 0L ;
  char n_dbf, *sort_key ;
  R4DATA_LIST *r4data ;
  RELATION4 *relation ;
  CODE4 *code_base ;

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4SORT ) ;
#endif

  code_base = relate->code_base ;
  if ( code_base->error_code < 0 )
    return -1 ;

  relation = relate->relation ;
  relate = &relation->relate ;
  rc = 0 ;
  sort_expr = expr4parse( relate->data, relation->sort_source ) ;

  relation->in_sort = relate4sort_skip ;
  relation->sort_done_flag = 0 ;

  rc = relate4top( relate ) ;
  if ( rc )   /* no records satisfy the relate, or error */
  {
    expr4free( sort_expr ) ;
    return rc ;
  }

  len = expr4key( sort_expr, &sort_key ) ;
  if ( len <= 0 )
  {
    expr4free( sort_expr ) ;
    return -1 ;
  }

#ifdef S4DEBUG
  if ( relation->sort_data_list.n_link != 0 )
    e4severe( e4parm, E4_PARM_NFD ) ;
#endif

  if ( r4data_list_build( &relation->sort_data_list, relate, sort_expr, relate4exact ) < 0 )
  {
    expr4free( sort_expr ) ;
    return -1 ;
  }

  if ( r4data_list_massage( &relation->sort_data_list ) < 0 )
  {
    expr4free( sort_expr ) ;
    return -1 ;
  }

  n_dbf = (char) relation->sort_data_list.n_link ;

  relation->sort_other_len = (unsigned)n_dbf * sizeof( long ) ;
  relation->other_data = (char *)u4alloc( relation->sort_other_len ) ;
  if ( relation->other_data == 0 )
    return -1 ;

  rc = sort4init_free( &relation->sort, code_base, len, relation->sort_other_len, relate ) ;
  if ( rc )
  {
    expr4free( sort_expr ) ;
    return rc ;
  }

#ifndef S4FOX
#ifndef S4CLIPPER
  switch( expr4type( sort_expr ) )
  {
#ifdef S4NDX
  case r4num:
  case r4num_doub:
  case r4date:
  case r4date_doub:
    relation->sort.cmp = t4cmp_doub ;
    break ;
#endif
#ifdef S4MDX
  case r4num:
    /*               if ( is_desc )                 */
    /*                  relation->sort.cmp = t4desc_bcd_cmp ;  */
    /*               else                           */
    relation->sort.cmp = c4bcd_cmp ;
    break ;
  case r4date:
    /*               if ( is_desc )                 */
    /*                  relation->sort.cmp = t4desc_cmp_doub ; */
    /*               else                           */
    relation->sort.cmp = t4cmp_doub ;
    break ;
    /*            case r4str:                       */
    /*               if ( is_desc )                 */
    /*                  relation->sort.cmp = t4desc_memcmp ;   */
    /*               else                           */
    /*                  relation->sort.cmp = u4memcmp ;        */

#endif
  default:
    break ;
  }
#endif
#endif

  /* call relate4top() again in case free-ups occurred */
  rc = relate4top( relate ) ;
  if ( rc )   /* no records satisfy the relate, or error */
  {
    expr4free( sort_expr ) ;
    return rc ;
  }

  for ( j = 0L, rc = 0 ; !rc ; j++, rc = relate4skip( relate, 1L ) )
  {
    for ( i = 0, r4data = 0 ;; i++ )
    {
      r4data = (R4DATA_LIST *)l4next( &relation->sort_data_list, r4data ) ;
      if ( r4data == 0 )
        break ;
      if ( d4eof( r4data->data ) || d4bof( r4data->data ) )   /* relate4blank case */
        memcpy( relation->other_data + i * sizeof(long), (void *)&zero, sizeof( long ) ) ;
      else
        memcpy( relation->other_data + i * sizeof(long), (void *)&r4data->data->rec_num, sizeof( long ) ) ;
    }
    if ( expr4key( sort_expr, &sort_key ) < 0 )
    {
      expr4free( sort_expr ) ;
      u4free( relation->other_data ) ;
      relation->other_data = 0 ;
      return -1 ;
    }
    if ( sort4put( &relation->sort, j, sort_key, relation->other_data ) < 0 )
    {
      expr4free( sort_expr ) ;
      u4free( relation->other_data ) ;
      relation->other_data = 0 ;
      return -1 ;
    }
  }

  expr4free( sort_expr ) ;

  if ( rc < 0 || rc == r4terminate )
  {
    u4free( relation->other_data ) ;
    relation->other_data = 0 ;
    return rc ;
  }

  relation->sort_rec_count = j ;
  relation->in_sort = relate4sort_done ;

  if ( relation->skip_backwards )
    if ( file4temp( &relation->sorted_file, code_base, relation->sorted_file_name, 1 ) < 0 )
    {
      u4free( relation->other_data ) ;
      relation->other_data = 0 ;
      return -1 ;
    }

  if ( sort4get_init_free( &relation->sort, relate ) < 0 )
    return -1 ;

  relation->sort_rec_on = relation->sort_file_pos = relation->sort_rec_to = 0L ;

  return 0 ;
}

void S4FUNCTION relate4sort_free( RELATION4 *relation, int delete_sort )
{
  if ( relation == 0 )
    return ;

  sort4free( &relation->sort ) ;
  u4free( relation->other_data ) ;
  relation->other_data = 0 ;
  if ( relation->sorted_file.hand >= 0 )
    file4close( &relation->sorted_file ) ;
  r4data_list_free( &relation->sort_data_list ) ;
  relation->in_sort = 0 ;
  if ( delete_sort )
  {
    u4free( relation->sort_source ) ;
    relation->sort_source = 0 ;
  }
}

int S4FUNCTION relate4sort_get_record( RELATION4 *relation, long num )
{
  int len, i, rc ;
  char *other, *key ;
  R4DATA_LIST *link_on ;
  long j, num_left ;
#ifdef S4UNIX
  long long_ptr ;
#endif

  if ( relation->relate.code_base->error_code < 0 )
    return -1 ;

  if ( num <= 0 )
    return r4bof ;

  relation->sort_eof_flag = 0 ;
  num_left = num - relation->sort_rec_to ;

  if ( num_left <= 0 )  /* already read, so just return from file */
  {
    if ( relation->skip_backwards == 0 )
      return -1 ;
    len = file4read( &relation->sorted_file, ( num - 1 ) * relation->sort_other_len, relation->other_data, relation->sort_other_len ) ;
    if ( len != relation->sort_other_len )  /* free up and exit */
      return -1 ;
    other = relation->other_data ;
  }
  else
    while ( num_left-- )
    {
      if ( relation->sort_done_flag == 1 )  /* sort is finished, therefore must be eof */
        return r4eof ;

      rc = sort4get( &relation->sort, &j, (void **)&key, (void **)&other ) ;
      if ( rc )  /* no more items, or error */
      {
        sort4free( &relation->sort ) ;
        if ( rc == 1 )
        {
          relation->sort_eof_flag = 1 ;
          relation->sort_done_flag = 1 ;
          return r4eof ;
        }
        else
          return rc ;
      }
      relation->sort_rec_to++ ;
      if ( relation->skip_backwards )
      {
        file4write( &relation->sorted_file, relation->sort_file_pos, other, relation->sort_other_len ) ;
        relation->sort_file_pos += relation->sort_other_len ;
      }
    }

  /* now read the database records in */
  for ( i = 0, link_on = 0 ;; i++ )
  {
    link_on = (R4DATA_LIST *)l4next( &relation->sort_data_list, link_on ) ;
    if ( link_on == 0 )
      return 0 ;
#ifdef S4UNIX
    memcpy( &long_ptr, (long *)other + i, sizeof(long_ptr) ) ;
    if ( long_ptr == 0 )  /* relate4blank case */
      d4go_eof( link_on->data ) ;
    else
      d4go( link_on->data, long_ptr ) ;
#else
    if ( *((long *)other + i ) == 0 )  /* relate4blank case */
      d4go_eof( link_on->data ) ;
    else
      d4go( link_on->data, *((long *)other + i ) ) ;
#endif
    link_on->relate->is_read = 1 ;
  }
}

int S4FUNCTION relate4sort_next_record( RELATION4 *relation )
{
  int rc ;

  if ( relation->relate.code_base->error_code < 0 )
    return -1 ;

  rc = relate4sort_get_record( relation, relation->sort_rec_on + 1 ) ;
  if ( rc == 0 )
    relation->sort_rec_on++ ;
  if ( rc == r4eof )
    relation->sort_rec_on = relation->sort_rec_count + 1 ;
  return rc ;
}

int S4FUNCTION relate4sort_prev_record( RELATION4 *relation )
{
  int rc ;

  if ( relation->relate.code_base->error_code < 0 )
    return -1 ;

  rc = relate4sort_get_record( relation, relation->sort_rec_on - 1 ) ;
  if ( rc == 0 )
    relation->sort_rec_on-- ;
  return rc ;
}

int S4FUNCTION relate4sort_set( RELATE4 *relate, char *expr )
{
  RELATION4 *relation ;
  int len ;

  if ( relate == 0 )
    return -1 ;

#ifdef S4VBASIC
  if ( c4parm_check( relate->code_base, 1, E4_R4SORT_SET ) )
    return -1 ;
#endif

  if ( relate->code_base->error_code < 0 )
    return -1 ;

  relation = relate->relation ;
  relate = &relation->relate ;
  relate4changed( relate ) ;
  u4free( relate->relation->sort_source ) ;
  relate->relation->sort_source = 0 ;
  if ( expr )
    if ( expr[0] )
    {
      len = strlen( expr ) ;
      relation->sort_source = (char *)u4alloc_er( relate->code_base, len + 1 ) ;
      if ( relation->sort_source == 0 )
        return -1 ;
      memcpy( relation->sort_source, expr, len ) ;
    }

  return 0 ;
}

int S4FUNCTION relate4top( RELATE4 *relate )
{
  RELATION4 *relation ;
  int rc, rc2 ;
  long rec ;
  CODE4 *code_base ;
#ifndef S4OPTIMIZE_OFF
  char has_opt ;
#endif

#ifdef S4VBASIC
  if ( c4parm_check( relate->code_base, 1, E4_R4TOP ) )
    return -1 ;
#endif

#ifdef S4DEBUG
  if( relate == 0 )
    e4severe( e4parm, E4_R4TOP ) ;
#else
  if( relate == 0 )
    return -1 ;
#endif

  code_base = relate->code_base ;
  if ( code_base->error_code < 0 )
    return -1 ;

  relation = relate->relation ;
  relate = &relation->relate ;

  rc = 0 ;

  if ( relation->in_sort == relate4sort_done )
    if ( relation->skip_backwards == 0 )
      relate4changed( relate ) ;

  relate->data_tag = d4tag_selected( relate->data ) ;
  if ( relation->is_initialized == 0 )
  {

#ifndef S4SINGLE
    if ( code_base->read_lock )
      rc = relate4lock( relate ) ;
#endif
#ifndef S4OPTIMIZE_OFF
    has_opt = (char)code_base->has_opt ;
#endif
    if ( rc < 0 )
      return rc ;
    relate->relation->bitmaps_freed = 0 ;
    if ( relation->expr_source )
    {
      relation->log.expr = expr4parse( relate->data, relation->expr_source ) ;
      if ( relation->log.expr == 0 )
        return -1 ;

      if ( log4bitmap_do( &relation->log ) < 0 )
        relate->relation->bitmaps_freed = 1 ;
      log4determine_evaluation_order( &relation->log ) ;
    }

    if ( relate4build_scan_list( relate, relation ) < 0 )
      return -1 ;
    relation->relate_list.selected = (void *)l4first( &relation->relate_list) ;

    relation->is_initialized = 1 ;

    if ( relation->sort_source )
    {
      rc = relate4sort( relate ) ;
      if ( rc < 0 || rc == r4terminate )
        return rc ;
    }

#ifndef S4OPTIMIZE_OFF
    if ( has_opt )
      d4opt_restart( code_base ) ;
#endif
  }

  relate4set_not_read( relate ) ;

  if ( relation->in_sort == relate4sort_done )
  {
    relation->sort_rec_on = 0 ;
    rc = relate4sort_next_record( relation ) ;
  }
  else
    rc = d4top( relate->data ) ;

  if ( rc )    /* eof or error */
    return rc ;

  if ( relation->expr_source )
  {
    rec = d4recno( relate->data ) ;
    if ( f4flag_is_set_flip( &relate->set, rec ) == 0 )
    {
#ifndef S4INDEX_OFF
      if ( relate->data_tag )
        while ( f4flag_is_set_flip( &relate->set, rec ) == 0 )
        {
#ifdef S4HAS_DESCENDING
          rc = (int)t4dskip( relate->data_tag, 1L ) ;
#else
          rc = (int)t4skip( relate->data_tag, 1L ) ;
#endif
          if ( rc != 1 )
          {
            if ( rc == 0 )
            {
              d4go_eof( relate->data ) ;
              return r4eof ;
            }
            return rc ;
          }
          rec = t4recno( relate->data_tag ) ;
        }
      else
      {
#endif
        rec = f4flag_get_next_flip( &relate->set, 1L, 1 ) + 1L ;
        if ( rec > relate->data->count )
        {
          relate->data->count = d4reccount( relate->data ) ;
          if ( rec > relate->data->count )
          {
            d4go_eof( relate->data ) ;
            return r4eof ;
          }
        }
#ifndef S4INDEX_OFF
      }
#endif
    }
    d4go( relate->data, rec ) ;
  }

  rc = relate4read_rest( relate, 1 ) ;
  if ( rc == relate4filter_record )
    return relate4skip( relate, 1L ) ;

  if ( rc < 0 || rc == r4terminate )
    return rc ;

  if ( relation->expr_source )
  {
    rc2 = log4true( &relation->log ) ;
    if ( rc2 == r4terminate )
      return r4terminate ;
    if ( rc2 == 0 )
    {
      if ( relation->in_sort == relate4sort_skip )  /* must temporarily disable in order to get a matching scan if available */
      {
        relation->in_sort = 0 ;
        rc = relate4skip( relate, 1L ) ;
        relation->in_sort = relate4sort_skip ;
      }
      else
        rc = relate4skip( relate, 1L ) ;
    }
  }

  return rc ;
}

int S4FUNCTION relate4type( RELATE4 *relate, int relate_type )
{
  int rc ;

  if ( relate == 0 )
    return -1 ;

#ifdef S4DEBUG
  if ( relate_type != relate4exact && relate_type != relate4scan && relate_type != relate4approx )
    e4severe( e4parm, E4_INFO_IVT ) ;
#endif
  rc = relate->relation_type ;
  if ( rc != relate_type )
  {
    relate->relation_type = relate_type ;
    relate4changed( relate ) ;
  }
  return rc ;
}

int S4FUNCTION relate4unlock( RELATE4 *relate )
{
#ifndef S4SINGLE
  DATA4 *data_on ;

#ifdef S4DEBUG
  if ( relate == 0 )
    e4severe( e4parm, E4_R4UNLOCK ) ;
#endif

  if ( !relate->relation->locked )
    return 0 ;

  for ( data_on = (DATA4 *)l4first( &relate->code_base->data_list) ; data_on ; data_on = (DATA4 *)l4next( &relate->code_base->data_list, data_on ) )
    if ( relate4dbf_in_relation( relate, data_on ) )
      d4unlock( data_on ) ;

  relate->relation->locked = 0 ;
#endif

  return 0 ;
}

#ifdef S4VB_DOS

RELATE4 *S4FUNCTION relate4create_slave_v( RELATE4 *master, DATA4 *slave, char *master_expr, TAG4 *slave_tag)
{
  return relate4create_slave(master, slave, c4str(master_expr), slave_tag) ;
}

char * relate4master_expr_v( RELATE4 *r4 )
{
#ifdef S4VBASIC
  if ( c4parm_check( r4->code_base, 1, "relate4masterExpr():" ) ) return 0 ;
#endif

  return v4str(r4->master_expr->source) ;
}

int S4FUNCTION relate4query_set_v ( RELATE4 *relate, char *expr )
{
  return relate4query_set( relate, c4str(expr) ) ;
}

int S4FUNCTION relate4sort_set_v ( RELATE4 *relate, char *expr )
{
  return relate4sort_set( relate, c4str(expr) ) ;
}

#endif
