~drizzle-trunk/drizzle/development

1 by brian
clean slate
1
/* Copyright (C) 2001 MySQL AB
2
3
   This program is free software; you can redistribute it and/or modify
4
   it under the terms of the GNU General Public License as published by
5
   the Free Software Foundation; version 2 of the License.
6
7
   This program is distributed in the hope that it will be useful,
8
   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
   GNU General Public License for more details.
11
12
   You should have received a copy of the GNU General Public License
13
   along with this program; if not, write to the Free Software
14
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
15
16
/*
17
  Function to handle quick removal of duplicates
18
  This code is used when doing multi-table deletes to find the rows in
19
  reference tables that needs to be deleted.
20
21
  The basic idea is as follows:
22
23
  Store first all strings in a binary tree, ignoring duplicates.
24
  When the tree uses more memory than 'max_heap_table_size',
25
  write the tree (in sorted order) out to disk and start with a new tree.
26
  When all data has been generated, merge the trees (removing any found
27
  duplicates).
28
29
  The unique entries will be returned in sort order, to ensure that we do the
30
  deletes in disk order.
31
*/
32
33
#include "mysql_priv.h"
34
#include "sql_sort.h"
35
36
37
int unique_write_to_file(uchar* key, element_count count, Unique *unique)
38
{
39
  /*
40
    Use unique->size (size of element stored in the tree) and not
41
    unique->tree.size_of_element. The latter is different from unique->size
42
    when tree implementation chooses to store pointer to key in TREE_ELEMENT
43
    (instead of storing the element itself there)
44
  */
45
  return my_b_write(&unique->file, key, unique->size) ? 1 : 0;
46
}
47
48
int unique_write_to_ptrs(uchar* key, element_count count, Unique *unique)
49
{
50
  memcpy(unique->record_pointers, key, unique->size);
51
  unique->record_pointers+=unique->size;
52
  return 0;
53
}
54
55
Unique::Unique(qsort_cmp2 comp_func, void * comp_func_fixed_arg,
56
	       uint size_arg, ulonglong max_in_memory_size_arg)
57
  :max_in_memory_size(max_in_memory_size_arg), size(size_arg), elements(0)
58
{
59
  my_b_clear(&file);
60
  init_tree(&tree, (ulong) (max_in_memory_size / 16), 0, size, comp_func, 0,
61
            NULL, comp_func_fixed_arg);
62
  /* If the following fail's the next add will also fail */
63
  my_init_dynamic_array(&file_ptrs, sizeof(BUFFPEK), 16, 16);
64
  /*
65
    If you change the following, change it in get_max_elements function, too.
66
  */
67
  max_elements= (ulong) (max_in_memory_size /
68
                         ALIGN_SIZE(sizeof(TREE_ELEMENT)+size));
69
  VOID(open_cached_file(&file, mysql_tmpdir,TEMP_PREFIX, DISK_BUFFER_SIZE,
70
		   MYF(MY_WME)));
71
}
72
73
74
/*
75
  Calculate log2(n!)
76
77
  NOTES
78
    Stirling's approximate formula is used:
79
80
      n! ~= sqrt(2*M_PI*n) * (n/M_E)^n
81
82
    Derivation of formula used for calculations is as follows:
83
84
    log2(n!) = log(n!)/log(2) = log(sqrt(2*M_PI*n)*(n/M_E)^n) / log(2) =
85
86
      = (log(2*M_PI*n)/2 + n*log(n/M_E)) / log(2).
87
*/
88
89
inline double log2_n_fact(double x)
90
{
91
  return (log(2*M_PI*x)/2 + x*log(x/M_E)) / M_LN2;
92
}
93
94
95
/*
96
  Calculate cost of merge_buffers function call for given sequence of
97
  input stream lengths and store the number of rows in result stream in *last.
98
99
  SYNOPSIS
100
    get_merge_buffers_cost()
101
      buff_elems  Array of #s of elements in buffers
102
      elem_size   Size of element stored in buffer
103
      first       Pointer to first merged element size
104
      last        Pointer to last merged element size
105
106
  RETURN
107
    Cost of merge_buffers operation in disk seeks.
108
109
  NOTES
110
    It is assumed that no rows are eliminated during merge.
111
    The cost is calculated as
112
113
      cost(read_and_write) + cost(merge_comparisons).
114
115
    All bytes in the sequences is read and written back during merge so cost
116
    of disk io is 2*elem_size*total_buf_elems/IO_SIZE (2 is for read + write)
117
118
    For comparisons cost calculations we assume that all merged sequences have
119
    the same length, so each of total_buf_size elements will be added to a sort
120
    heap with (n_buffers-1) elements. This gives the comparison cost:
121
122
      total_buf_elems* log2(n_buffers) / TIME_FOR_COMPARE_ROWID;
123
*/
124
125
static double get_merge_buffers_cost(uint *buff_elems, uint elem_size,
126
                                     uint *first, uint *last)
127
{
128
  uint total_buf_elems= 0;
129
  for (uint *pbuf= first; pbuf <= last; pbuf++)
130
    total_buf_elems+= *pbuf;
131
  *last= total_buf_elems;
132
133
  int n_buffers= last - first + 1;
134
135
  /* Using log2(n)=log(n)/log(2) formula */
136
  return 2*((double)total_buf_elems*elem_size) / IO_SIZE +
137
     total_buf_elems*log((double) n_buffers) / (TIME_FOR_COMPARE_ROWID * M_LN2);
138
}
139
140
141
/*
142
  Calculate cost of merging buffers into one in Unique::get, i.e. calculate
143
  how long (in terms of disk seeks) the two calls
144
    merge_many_buffs(...);
145
    merge_buffers(...);
146
  will take.
147
148
  SYNOPSIS
149
    get_merge_many_buffs_cost()
150
      buffer        buffer space for temporary data, at least
151
                    Unique::get_cost_calc_buff_size bytes
152
      maxbuffer     # of full buffers
153
      max_n_elems   # of elements in first maxbuffer buffers
154
      last_n_elems  # of elements in last buffer
155
      elem_size     size of buffer element
156
157
  NOTES
158
    maxbuffer+1 buffers are merged, where first maxbuffer buffers contain
159
    max_n_elems elements each and last buffer contains last_n_elems elements.
160
161
    The current implementation does a dumb simulation of merge_many_buffs
162
    function actions.
163
164
  RETURN
165
    Cost of merge in disk seeks.
166
*/
167
168
static double get_merge_many_buffs_cost(uint *buffer,
169
                                        uint maxbuffer, uint max_n_elems,
170
                                        uint last_n_elems, int elem_size)
171
{
172
  register int i;
173
  double total_cost= 0.0;
174
  uint *buff_elems= buffer; /* #s of elements in each of merged sequences */
175
176
  /*
177
    Set initial state: first maxbuffer sequences contain max_n_elems elements
178
    each, last sequence contains last_n_elems elements.
179
  */
180
  for (i = 0; i < (int)maxbuffer; i++)
181
    buff_elems[i]= max_n_elems;
182
  buff_elems[maxbuffer]= last_n_elems;
183
184
  /*
185
    Do it exactly as merge_many_buff function does, calling
186
    get_merge_buffers_cost to get cost of merge_buffers.
187
  */
188
  if (maxbuffer >= MERGEBUFF2)
189
  {
190
    while (maxbuffer >= MERGEBUFF2)
191
    {
192
      uint lastbuff= 0;
193
      for (i = 0; i <= (int) maxbuffer - MERGEBUFF*3/2; i += MERGEBUFF)
194
      {
195
        total_cost+=get_merge_buffers_cost(buff_elems, elem_size,
196
                                           buff_elems + i,
197
                                           buff_elems + i + MERGEBUFF-1);
198
	lastbuff++;
199
      }
200
      total_cost+=get_merge_buffers_cost(buff_elems, elem_size,
201
                                         buff_elems + i,
202
                                         buff_elems + maxbuffer);
203
      maxbuffer= lastbuff;
204
    }
205
  }
206
207
  /* Simulate final merge_buff call. */
208
  total_cost += get_merge_buffers_cost(buff_elems, elem_size,
209
                                       buff_elems, buff_elems + maxbuffer);
210
  return total_cost;
211
}
212
213
214
/*
215
  Calculate cost of using Unique for processing nkeys elements of size
216
  key_size using max_in_memory_size memory.
217
218
  SYNOPSIS
219
    Unique::get_use_cost()
220
      buffer    space for temporary data, use Unique::get_cost_calc_buff_size
221
                to get # bytes needed.
222
      nkeys     #of elements in Unique
223
      key_size  size of each elements in bytes
224
      max_in_memory_size amount of memory Unique will be allowed to use
225
226
  RETURN
227
    Cost in disk seeks.
228
229
  NOTES
230
    cost(using_unqiue) =
231
      cost(create_trees) +  (see #1)
232
      cost(merge) +         (see #2)
233
      cost(read_result)     (see #3)
234
235
    1. Cost of trees creation
236
      For each Unique::put operation there will be 2*log2(n+1) elements
237
      comparisons, where n runs from 1 tree_size (we assume that all added
238
      elements are different). Together this gives:
239
240
      n_compares = 2*(log2(2) + log2(3) + ... + log2(N+1)) = 2*log2((N+1)!)
241
242
      then cost(tree_creation) = n_compares*ROWID_COMPARE_COST;
243
244
      Total cost of creating trees:
245
      (n_trees - 1)*max_size_tree_cost + non_max_size_tree_cost.
246
247
      Approximate value of log2(N!) is calculated by log2_n_fact function.
248
249
    2. Cost of merging.
250
      If only one tree is created by Unique no merging will be necessary.
251
      Otherwise, we model execution of merge_many_buff function and count
252
      #of merges. (The reason behind this is that number of buffers is small,
253
      while size of buffers is big and we don't want to loose precision with
254
      O(x)-style formula)
255
256
    3. If only one tree is created by Unique no disk io will happen.
257
      Otherwise, ceil(key_len*n_keys) disk seeks are necessary. We assume
258
      these will be random seeks.
259
*/
260
261
double Unique::get_use_cost(uint *buffer, uint nkeys, uint key_size,
262
                            ulonglong max_in_memory_size)
263
{
264
  ulong max_elements_in_tree;
265
  ulong last_tree_elems;
266
  int   n_full_trees; /* number of trees in unique - 1 */
267
  double result;
268
269
  max_elements_in_tree= ((ulong) max_in_memory_size /
270
                         ALIGN_SIZE(sizeof(TREE_ELEMENT)+key_size));
271
272
  n_full_trees=    nkeys / max_elements_in_tree;
273
  last_tree_elems= nkeys % max_elements_in_tree;
274
275
  /* Calculate cost of creating trees */
276
  result= 2*log2_n_fact(last_tree_elems + 1.0);
277
  if (n_full_trees)
278
    result+= n_full_trees * log2_n_fact(max_elements_in_tree + 1.0);
279
  result /= TIME_FOR_COMPARE_ROWID;
280
281
282
  if (!n_full_trees)
283
    return result;
284
285
  /*
286
    There is more then one tree and merging is necessary.
287
    First, add cost of writing all trees to disk, assuming that all disk
288
    writes are sequential.
289
  */
290
  result += DISK_SEEK_BASE_COST * n_full_trees *
291
              ceil(((double) key_size)*max_elements_in_tree / IO_SIZE);
292
  result += DISK_SEEK_BASE_COST * ceil(((double) key_size)*last_tree_elems / IO_SIZE);
293
294
  /* Cost of merge */
295
  double merge_cost= get_merge_many_buffs_cost(buffer, n_full_trees,
296
                                               max_elements_in_tree,
297
                                               last_tree_elems, key_size);
298
  if (merge_cost < 0.0)
299
    return merge_cost;
300
301
  result += merge_cost;
302
  /*
303
    Add cost of reading the resulting sequence, assuming there were no
304
    duplicate elements.
305
  */
306
  result += ceil((double)key_size*nkeys/IO_SIZE);
307
308
  return result;
309
}
310
311
Unique::~Unique()
312
{
313
  close_cached_file(&file);
314
  delete_tree(&tree);
315
  delete_dynamic(&file_ptrs);
316
}
317
318
319
    /* Write tree to disk; clear tree */
320
bool Unique::flush()
321
{
322
  BUFFPEK file_ptr;
323
  elements+= tree.elements_in_tree;
324
  file_ptr.count=tree.elements_in_tree;
325
  file_ptr.file_pos=my_b_tell(&file);
326
327
  if (tree_walk(&tree, (tree_walk_action) unique_write_to_file,
328
		(void*) this, left_root_right) ||
329
      insert_dynamic(&file_ptrs, (uchar*) &file_ptr))
330
    return 1;
331
  delete_tree(&tree);
332
  return 0;
333
}
334
335
336
/*
337
  Clear the tree and the file.
338
  You must call reset() if you want to reuse Unique after walk().
339
*/
340
341
void
342
Unique::reset()
343
{
344
  reset_tree(&tree);
345
  /*
346
    If elements != 0, some trees were stored in the file (see how
347
    flush() works). Note, that we can not count on my_b_tell(&file) == 0
348
    here, because it can return 0 right after walk(), and walk() does not
349
    reset any Unique member.
350
  */
351
  if (elements)
352
  {
353
    reset_dynamic(&file_ptrs);
354
    reinit_io_cache(&file, WRITE_CACHE, 0L, 0, 1);
355
  }
356
  elements= 0;
357
}
358
359
/*
360
  The comparison function, passed to queue_init() in merge_walk() and in
361
  merge_buffers() when the latter is called from Uniques::get() must
362
  use comparison function of Uniques::tree, but compare members of struct
363
  BUFFPEK.
364
*/
365
366
C_MODE_START
367
368
static int buffpek_compare(void *arg, uchar *key_ptr1, uchar *key_ptr2)
369
{
370
  BUFFPEK_COMPARE_CONTEXT *ctx= (BUFFPEK_COMPARE_CONTEXT *) arg;
371
  return ctx->key_compare(ctx->key_compare_arg,
372
                          *((uchar **) key_ptr1), *((uchar **)key_ptr2));
373
}
374
375
C_MODE_END
376
377
378
/*
379
  DESCRIPTION
380
381
    Function is very similar to merge_buffers, but instead of writing sorted
382
    unique keys to the output file, it invokes walk_action for each key.
383
    This saves I/O if you need to pass through all unique keys only once.
384
385
  SYNOPSIS
386
    merge_walk()
387
  All params are 'IN' (but see comment for begin, end):
388
    merge_buffer       buffer to perform cached piece-by-piece loading
389
                       of trees; initially the buffer is empty
390
    merge_buffer_size  size of merge_buffer. Must be aligned with
391
                       key_length
392
    key_length         size of tree element; key_length * (end - begin)
393
                       must be less or equal than merge_buffer_size.
394
    begin              pointer to BUFFPEK struct for the first tree.
395
    end                pointer to BUFFPEK struct for the last tree;
396
                       end > begin and [begin, end) form a consecutive
397
                       range. BUFFPEKs structs in that range are used and
398
                       overwritten in merge_walk().
399
    walk_action        element visitor. Action is called for each unique
400
                       key.
401
    walk_action_arg    argument to walk action. Passed to it on each call.
402
    compare            elements comparison function
403
    compare_arg        comparison function argument
404
    file               file with all trees dumped. Trees in the file
405
                       must contain sorted unique values. Cache must be
406
                       initialized in read mode.
407
  RETURN VALUE
408
    0     ok
409
    <> 0  error
410
*/
411
412
static bool merge_walk(uchar *merge_buffer, ulong merge_buffer_size,
413
                       uint key_length, BUFFPEK *begin, BUFFPEK *end,
414
                       tree_walk_action walk_action, void *walk_action_arg,
415
                       qsort_cmp2 compare, void *compare_arg,
416
                       IO_CACHE *file)
417
{
418
  BUFFPEK_COMPARE_CONTEXT compare_context = { compare, compare_arg };
419
  QUEUE queue;
420
  if (end <= begin ||
421
      merge_buffer_size < (ulong) (key_length * (end - begin + 1)) ||
422
      init_queue(&queue, (uint) (end - begin), offsetof(BUFFPEK, key), 0,
423
                 buffpek_compare, &compare_context))
424
    return 1;
425
  /* we need space for one key when a piece of merge buffer is re-read */
426
  merge_buffer_size-= key_length;
427
  uchar *save_key_buff= merge_buffer + merge_buffer_size;
428
  uint max_key_count_per_piece= (uint) (merge_buffer_size/(end-begin) /
429
                                        key_length);
430
  /* if piece_size is aligned reuse_freed_buffer will always hit */
431
  uint piece_size= max_key_count_per_piece * key_length;
432
  uint bytes_read;               /* to hold return value of read_to_buffer */
433
  BUFFPEK *top;
434
  int res= 1;
435
  /*
436
    Invariant: queue must contain top element from each tree, until a tree
437
    is not completely walked through.
438
    Here we're forcing the invariant, inserting one element from each tree
439
    to the queue.
440
  */
441
  for (top= begin; top != end; ++top)
442
  {
443
    top->base= merge_buffer + (top - begin) * piece_size;
444
    top->max_keys= max_key_count_per_piece;
445
    bytes_read= read_to_buffer(file, top, key_length);
446
    if (bytes_read == (uint) (-1))
447
      goto end;
448
    DBUG_ASSERT(bytes_read);
449
    queue_insert(&queue, (uchar *) top);
450
  }
451
  top= (BUFFPEK *) queue_top(&queue);
452
  while (queue.elements > 1)
453
  {
454
    /*
455
      Every iteration one element is removed from the queue, and one is
456
      inserted by the rules of the invariant. If two adjacent elements on
457
      the top of the queue are not equal, biggest one is unique, because all
458
      elements in each tree are unique. Action is applied only to unique
459
      elements.
460
    */
461
    void *old_key= top->key;
462
    /*
463
      read next key from the cache or from the file and push it to the
464
      queue; this gives new top.
465
    */
466
    top->key+= key_length;
467
    if (--top->mem_count)
468
      queue_replaced(&queue);
469
    else /* next piece should be read */
470
    {
471
      /* save old_key not to overwrite it in read_to_buffer */
472
      memcpy(save_key_buff, old_key, key_length);
473
      old_key= save_key_buff;
474
      bytes_read= read_to_buffer(file, top, key_length);
475
      if (bytes_read == (uint) (-1))
476
        goto end;
477
      else if (bytes_read > 0)      /* top->key, top->mem_count are reset */
478
        queue_replaced(&queue);     /* in read_to_buffer */
479
      else
480
      {
481
        /*
482
          Tree for old 'top' element is empty: remove it from the queue and
483
          give all its memory to the nearest tree.
484
        */
485
        queue_remove(&queue, 0);
486
        reuse_freed_buff(&queue, top, key_length);
487
      }
488
    }
489
    top= (BUFFPEK *) queue_top(&queue);
490
    /* new top has been obtained; if old top is unique, apply the action */
491
    if (compare(compare_arg, old_key, top->key))
492
    {
493
      if (walk_action(old_key, 1, walk_action_arg))
494
        goto end;
495
    }
496
  }
497
  /*
498
    Applying walk_action to the tail of the last tree: this is safe because
499
    either we had only one tree in the beginning, either we work with the
500
    last tree in the queue.
501
  */
502
  do
503
  {
504
    do
505
    {
506
      if (walk_action(top->key, 1, walk_action_arg))
507
        goto end;
508
      top->key+= key_length;
509
    }
510
    while (--top->mem_count);
511
    bytes_read= read_to_buffer(file, top, key_length);
512
    if (bytes_read == (uint) (-1))
513
      goto end;
514
  }
515
  while (bytes_read);
516
  res= 0;
517
end:
518
  delete_queue(&queue);
519
  return res;
520
}
521
522
523
/*
524
  DESCRIPTION
525
    Walks consecutively through all unique elements:
526
    if all elements are in memory, then it simply invokes 'tree_walk', else
527
    all flushed trees are loaded to memory piece-by-piece, pieces are
528
    sorted, and action is called for each unique value.
529
    Note: so as merging resets file_ptrs state, this method can change
530
    internal Unique state to undefined: if you want to reuse Unique after
531
    walk() you must call reset() first!
532
  SYNOPSIS
533
    Unique:walk()
534
  All params are 'IN':
535
    action  function-visitor, typed in include/my_tree.h
536
            function is called for each unique element
537
    arg     argument for visitor, which is passed to it on each call
538
  RETURN VALUE
539
    0    OK
540
    <> 0 error
541
 */
542
543
bool Unique::walk(tree_walk_action action, void *walk_action_arg)
544
{
545
  int res;
546
  uchar *merge_buffer;
547
548
  if (elements == 0)                       /* the whole tree is in memory */
549
    return tree_walk(&tree, action, walk_action_arg, left_root_right);
550
551
  /* flush current tree to the file to have some memory for merge buffer */
552
  if (flush())
553
    return 1;
554
  if (flush_io_cache(&file) || reinit_io_cache(&file, READ_CACHE, 0L, 0, 0))
555
    return 1;
556
  if (!(merge_buffer= (uchar *) my_malloc((ulong) max_in_memory_size, MYF(0))))
557
    return 1;
558
  res= merge_walk(merge_buffer, (ulong) max_in_memory_size, size,
559
                  (BUFFPEK *) file_ptrs.buffer,
560
                  (BUFFPEK *) file_ptrs.buffer + file_ptrs.elements,
561
                  action, walk_action_arg,
562
                  tree.compare, tree.custom_arg, &file);
563
  my_free((char*) merge_buffer, MYF(0));
564
  return res;
565
}
566
567
/*
568
  Modify the TABLE element so that when one calls init_records()
569
  the rows will be read in priority order.
570
*/
571
572
bool Unique::get(TABLE *table)
573
{
574
  SORTPARAM sort_param;
575
  table->sort.found_records=elements+tree.elements_in_tree;
576
577
  if (my_b_tell(&file) == 0)
578
  {
579
    /* Whole tree is in memory;  Don't use disk if you don't need to */
580
    if ((record_pointers=table->sort.record_pointers= (uchar*)
581
	 my_malloc(size * tree.elements_in_tree, MYF(0))))
582
    {
583
      (void) tree_walk(&tree, (tree_walk_action) unique_write_to_ptrs,
584
		       this, left_root_right);
585
      return 0;
586
    }
587
  }
588
  /* Not enough memory; Save the result to file && free memory used by tree */
589
  if (flush())
590
    return 1;
591
592
  IO_CACHE *outfile=table->sort.io_cache;
593
  BUFFPEK *file_ptr= (BUFFPEK*) file_ptrs.buffer;
594
  uint maxbuffer= file_ptrs.elements - 1;
595
  uchar *sort_buffer;
596
  my_off_t save_pos;
597
  bool error=1;
598
599
      /* Open cached file if it isn't open */
600
  outfile=table->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE),
601
                                MYF(MY_ZEROFILL));
602
603
  if (!outfile || (! my_b_inited(outfile) && open_cached_file(outfile,mysql_tmpdir,TEMP_PREFIX,READ_RECORD_BUFFER, MYF(MY_WME))))
604
    return 1;
605
  reinit_io_cache(outfile,WRITE_CACHE,0L,0,0);
606
607
  bzero((char*) &sort_param,sizeof(sort_param));
608
  sort_param.max_rows= elements;
609
  sort_param.sort_form=table;
610
  sort_param.rec_length= sort_param.sort_length= sort_param.ref_length=
611
    size;
612
  sort_param.keys= (uint) (max_in_memory_size / sort_param.sort_length);
613
  sort_param.not_killable=1;
614
615
  if (!(sort_buffer=(uchar*) my_malloc((sort_param.keys+1) *
616
				       sort_param.sort_length,
617
				       MYF(0))))
618
    return 1;
619
  sort_param.unique_buff= sort_buffer+(sort_param.keys*
620
				       sort_param.sort_length);
621
622
  sort_param.compare= (qsort2_cmp) buffpek_compare;
623
  sort_param.cmp_context.key_compare= tree.compare;
624
  sort_param.cmp_context.key_compare_arg= tree.custom_arg;
625
626
  /* Merge the buffers to one file, removing duplicates */
627
  if (merge_many_buff(&sort_param,sort_buffer,file_ptr,&maxbuffer,&file))
628
    goto err;
629
  if (flush_io_cache(&file) ||
630
      reinit_io_cache(&file,READ_CACHE,0L,0,0))
631
    goto err;
632
  if (merge_buffers(&sort_param, &file, outfile, sort_buffer, file_ptr,
633
		    file_ptr, file_ptr+maxbuffer,0))
634
    goto err;
635
  error=0;
636
err:
637
  x_free(sort_buffer);
638
  if (flush_io_cache(outfile))
639
    error=1;
640
641
  /* Setup io_cache for reading */
642
  save_pos=outfile->pos_in_file;
643
  if (reinit_io_cache(outfile,READ_CACHE,0L,0,0))
644
    error=1;
645
  outfile->end_of_file=save_pos;
646
  return error;
647
}