mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-25 00:03:03 +01:00
3056 lines
75 KiB
C++
3056 lines
75 KiB
C++
/*
|
|
* PROGRAM: JRD Sort
|
|
* MODULE: sort.c
|
|
* DESCRIPTION: Top level sort module
|
|
*
|
|
* The contents of this file are subject to the Interbase Public
|
|
* License Version 1.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.Inprise.com/IPL.html
|
|
*
|
|
* Software distributed under the License is distributed on an
|
|
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
|
|
* or implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code was created by Inprise Corporation
|
|
* and its predecessors. Portions created by Inprise Corporation are
|
|
* Copyright (C) Inprise Corporation.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
* $Id: sort.cpp,v 1.30 2003-02-17 08:42:00 eku Exp $
|
|
*
|
|
* 2001-09-24 SJL - Temporary fix for large sort file bug
|
|
*
|
|
* 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
|
|
*
|
|
* 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define
|
|
*
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include "../jrd/common.h"
|
|
#include "../jrd/jrd.h"
|
|
#include "../jrd/sort.h"
|
|
#include "../jrd/sort_mem.h"
|
|
#include "gen/codes.h"
|
|
#include "../jrd/intl.h"
|
|
#include "../jrd/gdsassert.h"
|
|
#include "../jrd/rse.h"
|
|
#include "../jrd/val.h"
|
|
#include "../jrd/err_proto.h"
|
|
#include "../jrd/dls_proto.h"
|
|
#include "../jrd/gds_proto.h"
|
|
#include "../jrd/sort_proto.h"
|
|
#include "../jrd/all_proto.h"
|
|
#include "../jrd/sch_proto.h"
|
|
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_UIO_H
|
|
#include <sys/uio.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_STDIO_H
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
/* RITTER - declare seek and off_t - :ATTENTION: for 64bit I/O we might need ib_stdio.h ! */
|
|
|
|
|
|
#ifdef SOLARIS
|
|
#include "../jrd/ib_stdio.h"
|
|
#endif
|
|
|
|
#ifdef WIN_NT
|
|
/* for SEEK_SET */
|
|
#include <stdio.h>
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#endif
|
|
|
|
#define IO_RETRY 20
|
|
#define RUN_GROUP 8
|
|
#define MAX_MERGE_LEVEL 2
|
|
|
|
#ifdef VMS
|
|
extern double MTH$CVT_D_G(), MTH$CVT_G_D();
|
|
#endif
|
|
|
|
/* The sort buffer size should be just under a multiple of the
|
|
hardware memory page size to account for memory allocator
|
|
overhead. On most platorms, this saves 4KB to 8KB per sort
|
|
buffer from being allocated but not used. */
|
|
|
|
#define SORT_BUFFER_CHUNK_SIZE 4096
|
|
#define MIN_SORT_BUFFER_SIZE (SORT_BUFFER_CHUNK_SIZE * 4)
|
|
#define MAX_SORT_BUFFER_SIZE (SORT_BUFFER_CHUNK_SIZE * 32)
|
|
|
|
#define MAX_TEMPFILE_SIZE 1073741824 // 1GB
|
|
|
|
#define DIFF_LONGS(a,b) ((a) - (b))
|
|
#define SWAP_LONGS(a,b,t) {t=a; a=b; b=t;}
|
|
|
|
/* compare p and q both SORTP pointers for l 32-bit longwords
|
|
l != 0 if p and q are not equal for l bytes */
|
|
#define DO_32_COMPARE(p, q, l) do if (*p++ != *q++) break; while (--l);
|
|
|
|
#define MOVE_32(len,from,to) memcpy(to, from, len*4)
|
|
|
|
#ifndef EINTR
|
|
#define EINTR 0
|
|
#endif
|
|
|
|
/* these values are not defined as const as they are passed to
|
|
the diddle_key routines which mangles them.
|
|
As the diddle_key routines differ on VAX (little endian) and non VAX
|
|
(big endian) patforms, making the following const caused a core on the
|
|
Intel Platforms, while Solaris was working fine. */
|
|
static ULONG low_key[] = { 0, 0, 0, 0, 0, 0 };
|
|
|
|
static ULONG high_key[] = {
|
|
ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX,
|
|
ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX,
|
|
ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX,
|
|
ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX,
|
|
ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX,
|
|
ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX,
|
|
ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX,
|
|
ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX};
|
|
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
static SORT_RECORD *get_merge(MRG, SCB, RSE_GET_MODE);
|
|
#else
|
|
static void diddle_key(UCHAR *, SCB, USHORT);
|
|
static SORT_RECORD *get_merge(MRG, SCB);
|
|
#endif
|
|
|
|
static UCHAR *sort_alloc(SCB, ULONG);
|
|
static void error_memory(SCB);
|
|
static ULONG find_file_space(SCB, ULONG, SFB *);
|
|
static void free_file_space(SCB, SFB, ULONG, ULONG);
|
|
static void init(SCB);
|
|
static BOOLEAN local_fini(SCB, ATT);
|
|
static void merge_runs(SCB, USHORT);
|
|
static void quick(SLONG, SORTP **, USHORT);
|
|
static ULONG order(SCB);
|
|
static void put_run(SCB);
|
|
static void release_merge(MRG);
|
|
static void sort(SCB);
|
|
#ifdef DEBUG
|
|
static void validate(SCB);
|
|
#endif
|
|
|
|
#ifdef DEBUG_SORT_TRACE
|
|
static void write_trace(UCHAR *, SFB, ULONG, BLOB_PTR *, ULONG);
|
|
#include "../jrd/ib_stdio.h"
|
|
|
|
IB_FILE *trace_file = NULL;
|
|
#endif
|
|
|
|
#ifdef SMALL_FILE_NAMES
|
|
#define SCRATCH "fb_s"
|
|
#endif
|
|
|
|
#ifndef SCRATCH
|
|
#define SCRATCH "fb_sort_"
|
|
#endif
|
|
|
|
#ifdef WIN_NT
|
|
#define SYS_ERR gds_arg_win32
|
|
#endif
|
|
|
|
#ifndef SYS_ERR
|
|
#define SYS_ERR gds_arg_unix
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
#ifdef WORDS_BIGENDIAN
|
|
void SORT_diddle_key(UCHAR * record, SCB scb, USHORT direction)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ d i d d l e _ k e y ( n o n - V A X )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform transformation between the natural form of a record
|
|
* and a form that can be sorted in unsigned comparison order.
|
|
*
|
|
* direction - TRUE for SORT_put() and FALSE for SORT_get()
|
|
*
|
|
**************************************/
|
|
UCHAR *p, *fill_pos, fill_char;
|
|
USHORT complement, n, l, fill, flag;
|
|
SKD *key, *end;
|
|
|
|
for (key = scb->scb_description, end = key + scb->scb_keys; key < end;
|
|
key++) {
|
|
p = record + key->skd_offset;
|
|
n = key->skd_length;
|
|
complement = key->skd_flags & SKD_descending;
|
|
switch (key->skd_dtype) {
|
|
case SKD_ulong:
|
|
case SKD_ushort:
|
|
case SKD_bytes:
|
|
break;
|
|
|
|
/* Stash embedded control info for non-fixed data types in the sort
|
|
record and zap it so that it doesn't interfere with collation. */
|
|
|
|
case SKD_varying:
|
|
if (direction) {
|
|
if (!(scb->scb_flags & scb_sorted)) {
|
|
*((USHORT *) (record + key->skd_vary_offset)) =
|
|
((VARY *) p)->vary_length;
|
|
fill_char =
|
|
(key->skd_flags & SKD_binary) ? 0 : ASCII_SPACE;
|
|
fill_pos = p + sizeof(USHORT) + ((VARY *) p)->vary_length;
|
|
fill = n - sizeof(USHORT) - ((VARY *) p)->vary_length;
|
|
if (fill)
|
|
memset(fill_pos, fill_char, fill);
|
|
}
|
|
((VARY *) p)->vary_length = 0;
|
|
}
|
|
break;
|
|
|
|
case SKD_cstring:
|
|
if (direction) {
|
|
fill_char = (key->skd_flags & SKD_binary) ? 0 : ASCII_SPACE;
|
|
if (!(scb->scb_flags & scb_sorted)) {
|
|
*((USHORT *) (record + key->skd_vary_offset)) = l =
|
|
strlen(p);
|
|
fill_pos = p + l;
|
|
fill = n - l;
|
|
if (fill)
|
|
memset(fill_pos, fill_char, fill);
|
|
}
|
|
else {
|
|
l = *((USHORT *) (record + key->skd_vary_offset));
|
|
*(p + l) = fill_char;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SKD_text:
|
|
break;
|
|
|
|
case SKD_float:
|
|
case SKD_double:
|
|
flag = (direction || !complement) ? direction : TRUE;
|
|
if (flag ^ (*p >> 7))
|
|
*p ^= 1 << 7;
|
|
else
|
|
complement = !complement;
|
|
break;
|
|
|
|
case SKD_long:
|
|
case SKD_short:
|
|
case SKD_quad:
|
|
case SKD_timestamp1:
|
|
case SKD_timestamp2:
|
|
case SKD_sql_time:
|
|
case SKD_sql_date:
|
|
case SKD_int64:
|
|
*p ^= 1 << 7;
|
|
break;
|
|
|
|
default:
|
|
assert(FALSE);
|
|
break;
|
|
}
|
|
if (complement && n)
|
|
do
|
|
*p++ ^= -1;
|
|
while (--n);
|
|
|
|
/* Flatter but don't complement control info for non-fixed
|
|
data types when restoring the data. */
|
|
|
|
if (key->skd_dtype == SKD_varying && !direction) {
|
|
p = record + key->skd_offset;
|
|
((VARY *) p)->vary_length = *((USHORT *) (record + key->skd_vary_offset));
|
|
}
|
|
|
|
if (key->skd_dtype == SKD_cstring && !direction) {
|
|
p = record + key->skd_offset;
|
|
l = *((USHORT *) (record + key->skd_vary_offset));
|
|
*(p + l) = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#else
|
|
void SORT_diddle_key(UCHAR * record, SCB scb, USHORT direction)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ d i d d l e _ k e y ( V A X )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform transformation between the natural form of a record
|
|
* and a form that can be sorted in unsigned comparison order.
|
|
*
|
|
* direction - TRUE for SORT_put() and FALSE for SORT_get()
|
|
*
|
|
**************************************/
|
|
BLOB_PTR *p;
|
|
UCHAR c1, c2, fill_char, *fill_pos;
|
|
USHORT complement, n, w, l, fill;
|
|
USHORT HUGE_PTR *wp;
|
|
SSHORT longs, flag;
|
|
SORTP *lwp;
|
|
ULONG lw;
|
|
SKD *key, *end;
|
|
#ifdef VMS
|
|
double *dp;
|
|
#endif
|
|
|
|
for (key = scb->scb_description, end = key + scb->scb_keys; key < end;
|
|
key++) {
|
|
p = (BLOB_PTR *) record + key->skd_offset;
|
|
wp = (USHORT HUGE_PTR *) p;
|
|
lwp = (SORTP *) p;
|
|
complement = key->skd_flags & SKD_descending;
|
|
n = ROUNDUP(key->skd_length, sizeof(SLONG));
|
|
switch (key->skd_dtype) {
|
|
case SKD_timestamp1:
|
|
case SKD_timestamp2:
|
|
case SKD_sql_date:
|
|
case SKD_sql_time:
|
|
p[3] ^= 1 << 7;
|
|
break;
|
|
|
|
case SKD_ulong:
|
|
case SKD_ushort:
|
|
break;
|
|
|
|
case SKD_text:
|
|
case SKD_bytes:
|
|
case SKD_cstring:
|
|
case SKD_varying:
|
|
|
|
/* Stash embedded control info for non-fixed data types in the sort
|
|
record and zap it so that it doesn't interfere with collation. */
|
|
|
|
if (key->skd_dtype == SKD_varying && direction) {
|
|
if (!(scb->scb_flags & scb_sorted)) {
|
|
*((USHORT *) (record + key->skd_vary_offset)) = l =
|
|
((VARY *) p)->vary_length;
|
|
fill_char =
|
|
(key->skd_flags & SKD_binary) ? 0 : ASCII_SPACE;
|
|
fill_pos = p + sizeof(USHORT) + l;
|
|
fill = n - sizeof(USHORT) - l;
|
|
if (fill)
|
|
memset(fill_pos, fill_char, fill);
|
|
}
|
|
((VARY *) p)->vary_length = 0;
|
|
}
|
|
|
|
if (key->skd_dtype == SKD_cstring && direction) {
|
|
fill_char = (key->skd_flags & SKD_binary) ? 0 : ASCII_SPACE;
|
|
if (!(scb->scb_flags & scb_sorted)) {
|
|
*((USHORT *) (record + key->skd_vary_offset)) = l =
|
|
strlen(p);
|
|
fill_pos = p + l;
|
|
fill = n - l;
|
|
if (fill)
|
|
memset(fill_pos, fill_char, fill);
|
|
}
|
|
else {
|
|
l = *((USHORT *) (record + key->skd_vary_offset));
|
|
*(p + l) = fill_char;
|
|
}
|
|
}
|
|
|
|
longs = n >> SHIFTLONG;
|
|
while (--longs >= 0) {
|
|
c1 = p[3];
|
|
p[3] = *p;
|
|
*p++ = c1;
|
|
c1 = p[1];
|
|
p[1] = *p;
|
|
*p = c1;
|
|
p += 3;
|
|
}
|
|
p = (BLOB_PTR *) wp;
|
|
break;
|
|
|
|
case SKD_short:
|
|
p[1] ^= 1 << 7;
|
|
break;
|
|
|
|
case SKD_long:
|
|
p[3] ^= 1 << 7;
|
|
break;
|
|
|
|
case SKD_quad:
|
|
p[7] ^= 1 << 7;
|
|
break;
|
|
|
|
case SKD_int64:
|
|
/* INT64's fit in TWO LONGS, and hence the SWAP has to happen
|
|
here for the right order comparison using DO_32_COMPARE */
|
|
if (!direction)
|
|
SWAP_LONGS(lwp[0], lwp[1], lw);
|
|
|
|
p[7] ^= 1 << 7;
|
|
|
|
if (direction)
|
|
SWAP_LONGS(lwp[0], lwp[1], lw);
|
|
break;
|
|
|
|
#ifdef IEEE
|
|
case SKD_double:
|
|
if (!direction) {
|
|
lw = lwp[0];
|
|
lwp[0] = lwp[1];
|
|
lwp[1] = lw;
|
|
}
|
|
flag = (direction || !complement) ? direction : TRUE;
|
|
if (flag ^ (p[7] >> 7))
|
|
p[7] ^= 1 << 7;
|
|
else
|
|
complement = !complement;
|
|
if (direction) {
|
|
lw = lwp[0];
|
|
lwp[0] = lwp[1];
|
|
lwp[1] = lw;
|
|
}
|
|
break;
|
|
|
|
case SKD_float:
|
|
flag = (direction || !complement) ? direction : TRUE;
|
|
if (flag ^ (p[3] >> 7))
|
|
p[3] ^= 1 << 7;
|
|
else
|
|
complement = !complement;
|
|
break;
|
|
|
|
#else /* IEEE */
|
|
#ifdef VMS
|
|
case SKD_d_float:
|
|
dp = (double *) p;
|
|
if (direction)
|
|
*dp = MTH$CVT_D_G(dp);
|
|
|
|
#endif
|
|
case SKD_double:
|
|
w = wp[2];
|
|
wp[2] = wp[3];
|
|
wp[3] = w;
|
|
|
|
#ifndef VMS
|
|
case SKD_d_float:
|
|
#endif
|
|
case SKD_float:
|
|
if (!direction)
|
|
if (complement) {
|
|
if (p[3] & 1 << 7)
|
|
complement = !complement;
|
|
else
|
|
p[3] ^= 1 << 7;
|
|
}
|
|
else {
|
|
if (p[3] & 1 << 7)
|
|
p[3] ^= 1 << 7;
|
|
else
|
|
complement = !complement;
|
|
}
|
|
w = wp[0];
|
|
wp[0] = wp[1];
|
|
wp[1] = w;
|
|
if (direction)
|
|
if (p[3] & 1 << 7)
|
|
complement = !complement;
|
|
else
|
|
p[3] ^= 1 << 7;
|
|
#ifdef VMS
|
|
else if (key->skd_dtype == SKD_d_float)
|
|
*dp = MTH$CVT_G_D(dp);
|
|
#endif
|
|
break;
|
|
#endif /* IEEE */
|
|
|
|
default:
|
|
assert(FALSE);
|
|
break;
|
|
}
|
|
if (complement && n)
|
|
do
|
|
*p++ ^= -1;
|
|
while (--n);
|
|
|
|
/* Flatter but don't complement control info for non-fixed
|
|
data types when restoring the data. */
|
|
|
|
if (key->skd_dtype == SKD_varying && !direction) {
|
|
p = (BLOB_PTR *) record + key->skd_offset;
|
|
((VARY *) p)->vary_length = *((USHORT *) (record + key->skd_vary_offset));
|
|
}
|
|
|
|
if (key->skd_dtype == SKD_cstring && !direction) {
|
|
p = (BLOB_PTR *) record + key->skd_offset;
|
|
l = *((USHORT *) (record + key->skd_vary_offset));
|
|
*(p + l) = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
void SORT_error(
|
|
STATUS * status_vector,
|
|
SFB sfb, TEXT * string, STATUS operation, int errcode)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ e r r o r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Report fatal error.
|
|
*
|
|
**************************************/
|
|
|
|
assert(status_vector != NULL);
|
|
|
|
*status_vector++ = gds_arg_gds;
|
|
*status_vector++ = isc_io_error;
|
|
*status_vector++ = gds_arg_string;
|
|
*status_vector++ = (STATUS) string;
|
|
*status_vector++ = gds_arg_string;
|
|
*status_vector++ = (STATUS) ERR_cstring(sfb->sfb_file_name);
|
|
*status_vector++ = isc_arg_gds;
|
|
*status_vector++ = operation;
|
|
if (errcode) {
|
|
*status_vector++ = SYS_ERR;
|
|
*status_vector++ = errcode;
|
|
}
|
|
*status_vector++ = gds_arg_gds;
|
|
*status_vector++ = gds_sort_err; /* Msg355: sort error */
|
|
*status_vector = gds_arg_end;
|
|
|
|
ERR_punt();
|
|
}
|
|
|
|
|
|
void SORT_fini(SCB scb, ATT att)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ f i n i
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Finish sort, and release all resources.
|
|
*
|
|
**************************************/
|
|
BOOLEAN rval; /* Return value from local_fini */
|
|
|
|
rval = local_fini(scb, att);
|
|
|
|
/* And the sort context block itself */
|
|
/* *IF* local_fini didn't have a problem with the SCB. */
|
|
/* -- Morgan Schweers (mrs) */
|
|
|
|
if (rval == TRUE)
|
|
gds__free(scb);
|
|
}
|
|
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
void SORT_get(
|
|
STATUS * status_vector,
|
|
SCB scb, ULONG ** record_address, RSE_GET_MODE mode)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ g e t ( I B _ V 4 _ 1 )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get a record from sort (in order, of course).
|
|
* The address of the record is returned in <record_address>
|
|
* If the stream is exhausted, SORT_get ib_puts NULL in <record_address>.
|
|
*
|
|
**************************************/
|
|
SORT_RECORD *record;
|
|
|
|
scb->scb_status_vector = status_vector;
|
|
|
|
/* If there were runs, get the records from the merge
|
|
tree--otherwise everything fit in memory */
|
|
|
|
if (scb->scb_merge)
|
|
record = get_merge(scb->scb_merge, scb, mode);
|
|
else
|
|
switch (mode) {
|
|
case RSE_get_forward:
|
|
if (scb->scb_flags & scb_initialized)
|
|
scb->scb_flags &= ~scb_initialized;
|
|
|
|
while (TRUE) {
|
|
if (scb->scb_next_pointer > scb->scb_last_pointer) {
|
|
record = NULL;
|
|
break;
|
|
}
|
|
if (record = *scb->scb_next_pointer++)
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case RSE_get_backward:
|
|
if (scb->scb_flags & scb_initialized) {
|
|
scb->scb_flags &= ~scb_initialized;
|
|
scb->scb_next_pointer = scb->scb_last_pointer + 1;
|
|
}
|
|
else
|
|
/* by definition, the next pointer is on the next record,
|
|
so we have to go back one to get to the last fetched record--
|
|
this is easier than changing the sense of the next pointer */
|
|
|
|
{
|
|
scb->scb_next_pointer--;
|
|
if (scb->scb_next_pointer <= scb->scb_first_pointer + 1) {
|
|
record = NULL;
|
|
scb->scb_next_pointer++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (TRUE) {
|
|
scb->scb_next_pointer--;
|
|
if (scb->scb_next_pointer <= scb->scb_first_pointer) {
|
|
record = NULL;
|
|
scb->scb_next_pointer++;
|
|
break;
|
|
}
|
|
if (record = *scb->scb_next_pointer)
|
|
break;
|
|
}
|
|
|
|
/* reset next pointer to one greater than the last fetched */
|
|
|
|
scb->scb_next_pointer++;
|
|
break;
|
|
|
|
#ifdef PC_ENGINE
|
|
case RSE_get_current:
|
|
if (scb->scb_next_pointer <= scb->scb_first_pointer ||
|
|
scb->scb_next_pointer > scb->scb_last_pointer)
|
|
record = NULL;
|
|
record = *scb->scb_next_pointer;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
assert(FALSE);
|
|
break;
|
|
}
|
|
|
|
if (record)
|
|
SORT_diddle_key((UCHAR *) record->sort_record_key, scb, FALSE);
|
|
|
|
*record_address = (ULONG *) record;
|
|
}
|
|
#else
|
|
void SORT_get(STATUS * status_vector, SCB scb, ULONG ** record_address)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ g e t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get a record from sort (in order, of course).
|
|
* The address of the record is returned in <record_address>
|
|
* If the stream is exhausted, SORT_get ib_puts NULL in <record_address>.
|
|
*
|
|
**************************************/
|
|
SORT_RECORD *record;
|
|
|
|
scb->scb_status_vector = status_vector;
|
|
|
|
/* If there weren't any runs, everything fit in memory. Just return stuff. */
|
|
|
|
if (!scb->scb_merge)
|
|
while (TRUE) {
|
|
if (scb->scb_records == 0) {
|
|
record = NULL;
|
|
break;
|
|
}
|
|
scb->scb_records--;
|
|
if ( (record = *scb->scb_next_pointer++) )
|
|
break;
|
|
}
|
|
else
|
|
record = get_merge(scb->scb_merge, scb);
|
|
|
|
*record_address = (ULONG *) record;
|
|
|
|
if (record) {
|
|
diddle_key((UCHAR *) record->sort_record_key, scb, FALSE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
SCB SORT_init(STATUS * status_vector,
|
|
USHORT record_length,
|
|
USHORT keys,
|
|
SKD * key_description,
|
|
BOOLEAN(*call_back) (),
|
|
void *user_arg,
|
|
ATT att,
|
|
ULONG max_records)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ i n i t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Initialize for a sort. All we really need is a description
|
|
* of the sort keys. Return the address of a sort context block.
|
|
* If duplicate control is required, the user may specify a call
|
|
* back routine. If supplied, the call back routine is called
|
|
* with three argument: the two records and the user supplied
|
|
* argument. If the call back routine returns TRUE, the second
|
|
* duplicate record is eliminated.
|
|
*
|
|
**************************************/
|
|
SCB scb;
|
|
SKD *p, *q;
|
|
|
|
/* Allocate and setup a sort context block, including copying the
|
|
key description vector. Round the record length up to the next
|
|
longword, and add a longword to a pointer back to the pointer
|
|
slot. */
|
|
|
|
try {
|
|
scb = (SCB) gds__alloc((SLONG) SCB_LEN(keys));
|
|
} catch(const std::exception&) {
|
|
/* FREE: scb is freed by SORT_fini(), called by higher level cleanup */
|
|
/* FREE: or later in this module in error cases */
|
|
*status_vector++ = gds_arg_gds;
|
|
*status_vector++ = gds_sort_mem_err;
|
|
*status_vector = gds_arg_end;
|
|
return NULL;
|
|
}
|
|
memset((UCHAR *) scb, 0, SCB_LEN(keys));
|
|
|
|
scb->scb_status_vector = status_vector;
|
|
scb->scb_length = record_length;
|
|
scb->scb_longs =
|
|
ROUNDUP(record_length + sizeof(SLONG *),
|
|
sizeof(SLONG *)) >> SHIFTLONG;
|
|
scb->scb_dup_callback = call_back;
|
|
scb->scb_dup_callback_arg = user_arg;
|
|
scb->scb_keys = keys;
|
|
|
|
scb->scb_max_records = max_records;
|
|
|
|
p = scb->scb_description;
|
|
q = key_description;
|
|
do
|
|
*p++ = *q++;
|
|
while (--keys);
|
|
|
|
--p;
|
|
scb->scb_key_length =
|
|
ROUNDUP(p->skd_offset + p->skd_length, sizeof(SLONG)) >> SHIFTLONG;
|
|
|
|
/* Next, try to allocate a "big block". How big? Big enough! */
|
|
try {
|
|
#ifdef DEBUG_MERGE
|
|
/* To debug the merge algorithm, force the in-memory pool to be VERY small */
|
|
scb->scb_size_memory = 2000;
|
|
scb->scb_memory =
|
|
(SORTP *) gds__alloc((SLONG) scb->scb_size_memory);
|
|
/* FREE: scb_memory is freed by local_fini() */
|
|
#else
|
|
/* Try to get a big chunk of memory, if we can't try smaller and
|
|
smaller chunks until we can get the memory. If we get down to
|
|
too small a chunk - punt and report not enough memory. */
|
|
|
|
for (scb->scb_size_memory = MAX_SORT_BUFFER_SIZE;;
|
|
scb->scb_size_memory -= SORT_BUFFER_CHUNK_SIZE)
|
|
if (scb->scb_size_memory < MIN_SORT_BUFFER_SIZE)
|
|
break;
|
|
else if ( (scb->scb_memory =
|
|
(SORTP *) gds__alloc((SLONG) scb->scb_size_memory)) )
|
|
/* FREE: scb_memory is freed by local_fini() */
|
|
break;
|
|
#endif /* DEBUG_MERGE */
|
|
} catch(const std::exception&) {
|
|
*status_vector++ = gds_arg_gds;
|
|
*status_vector++ = gds_sort_mem_err;
|
|
/* Msg356: sort error: not enough memory */
|
|
*status_vector = gds_arg_end;
|
|
gds__free(scb);
|
|
return NULL;
|
|
}
|
|
|
|
scb->scb_end_memory =
|
|
(SORTP *) ((BLOB_PTR *) scb->scb_memory + scb->scb_size_memory);
|
|
scb->scb_first_pointer = (SORT_RECORD **) scb->scb_memory;
|
|
|
|
/* Set up to receive the first record. */
|
|
|
|
init(scb);
|
|
|
|
/* If a linked list pointer was given, link in new sort block */
|
|
|
|
if (att) {
|
|
scb->scb_next = att->att_active_sorts;
|
|
att->att_active_sorts = scb;
|
|
scb->scb_attachment = att;
|
|
}
|
|
|
|
return scb;
|
|
}
|
|
|
|
|
|
int SORT_put(STATUS * status_vector, SCB scb, ULONG ** record_address)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ p u t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allocate space for a record for sort. The caller is responsible
|
|
* for moving in the record.
|
|
*
|
|
* Records are added from the top (higher addresses) of sort memory going down. Record
|
|
* pointers are added at the bottom (lower addresses) of sort memory going up. When
|
|
* they overlap, the records in memory are sorted and written to a "run"
|
|
* in the scratch files. The runs are eventually merged.
|
|
*
|
|
**************************************/
|
|
RUN run;
|
|
USHORT count, depth;
|
|
SR *record;
|
|
|
|
scb->scb_status_vector = status_vector;
|
|
|
|
/* Find the last record passed in, and zap the keys something comparable
|
|
by unsigned longword compares. */
|
|
|
|
record = scb->scb_last_record;
|
|
|
|
if (record != (SR *) scb->scb_end_memory)
|
|
#ifdef SCROLLABLE_CURSORS
|
|
SORT_diddle_key((UCHAR *) (record->sr_sort_record.sort_record_key),
|
|
scb, TRUE);
|
|
#else
|
|
diddle_key((UCHAR *) (record->sr_sort_record.sort_record_key), scb,
|
|
TRUE);
|
|
#endif
|
|
|
|
/* If there isn't room for the record, sort and write the run.
|
|
Check that we are not at the beginning of the buffer in addition
|
|
to checking for space for the record. This avoids the pointer
|
|
record from underflowing in the second condition. */
|
|
|
|
if ((BLOB_PTR *) record < (BLOB_PTR *) (scb->scb_memory + scb->scb_longs)
|
|
|| (BLOB_PTR *) NEXT_RECORD(record) <=
|
|
(BLOB_PTR *) (scb->scb_next_pointer + 1)) {
|
|
put_run(scb);
|
|
for (;;) {
|
|
run = scb->scb_runs;
|
|
if ((depth = run->run_depth) == MAX_MERGE_LEVEL)
|
|
break;
|
|
count = 1;
|
|
while ((run = run->run_next) && run->run_depth == depth)
|
|
count++;
|
|
if (count < RUN_GROUP)
|
|
break;
|
|
merge_runs(scb, count);
|
|
}
|
|
init(scb);
|
|
record = scb->scb_last_record;
|
|
}
|
|
|
|
record = NEXT_RECORD(record);
|
|
|
|
/* Make sure the first longword of the record points to the pointer */
|
|
scb->scb_last_record = record;
|
|
record->sr_bckptr = scb->scb_next_pointer;
|
|
|
|
/* Move key_id into *scb->scb_next_pointer and then
|
|
increment scb->scb_next_pointer */
|
|
*scb->scb_next_pointer++ =
|
|
reinterpret_cast <
|
|
sort_record * >(record->sr_sort_record.sort_record_key);
|
|
#ifndef SCROLLABLE_CURSORS
|
|
scb->scb_records++;
|
|
#endif
|
|
*record_address = (ULONG *) record->sr_sort_record.sort_record_key;
|
|
|
|
return FB_SUCCESS;
|
|
}
|
|
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
void SORT_read_block(
|
|
#else
|
|
ULONG SORT_read_block(
|
|
#endif
|
|
STATUS * status_vector,
|
|
SFB sfb,
|
|
ULONG seek, BLOB_PTR * address, ULONG length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ r e a d _ b l o c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Read a block of stuff from a scratch file.
|
|
*
|
|
**************************************/
|
|
ULONG len, read_len, i;
|
|
|
|
#ifdef DEBUG_SORT_TRACE
|
|
UCHAR *org_address;
|
|
ULONG org_length, org_seek;
|
|
|
|
org_address = address;
|
|
org_length = length;
|
|
org_seek = seek;
|
|
#endif
|
|
|
|
/* Checkout of engine on sort I/O */
|
|
|
|
THREAD_EXIT;
|
|
|
|
/* The following is a crock induced by a VMS C bug */
|
|
|
|
while (length) {
|
|
len = length;
|
|
for (i = 0; i < IO_RETRY; i++) {
|
|
if (lseek(sfb->sfb_file, LSEEK_OFFSET_CAST seek, SEEK_SET) == -1) {
|
|
THREAD_ENTER;
|
|
SORT_error(status_vector, sfb, "lseek", isc_io_read_err,
|
|
errno);
|
|
}
|
|
if ((read_len = read(sfb->sfb_file, address, len)) == len)
|
|
break;
|
|
else if ((SSHORT) read_len == -1 && !SYSCALL_INTERRUPTED(errno)) {
|
|
THREAD_ENTER;
|
|
SORT_error(status_vector, sfb, "read", isc_io_read_err,
|
|
errno);
|
|
}
|
|
}
|
|
|
|
if (i == IO_RETRY) {
|
|
THREAD_ENTER;
|
|
SORT_error(status_vector, sfb, "read", isc_io_read_err, errno);
|
|
}
|
|
length -= read_len;
|
|
address += read_len;
|
|
seek += read_len;
|
|
}
|
|
|
|
THREAD_ENTER;
|
|
|
|
#ifdef DEBUG_SORT_TRACE
|
|
write_trace("Read", sfb, org_seek, org_address, org_length);
|
|
#endif
|
|
#ifndef SCROLLABLE_CURSORS
|
|
return seek;
|
|
#endif
|
|
}
|
|
|
|
|
|
void SORT_shutdown(ATT att)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ s h u t d o w n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Clean up any pending sorts.
|
|
*
|
|
**************************************/
|
|
|
|
/* We ignore the result from local_fini,
|
|
since the expectation is that from the
|
|
way we are passing in the structures
|
|
that every SCB *IS* part of the ptr
|
|
chain. Also, we're not freeing the
|
|
structure here, so if something goes
|
|
wrong, it's not *CRITICAL*. -- mrs */
|
|
|
|
while (att->att_active_sorts)
|
|
local_fini(att->att_active_sorts, att);
|
|
}
|
|
|
|
|
|
int SORT_sort(STATUS * status_vector, SCB scb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ s o r t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform any intermediate computing before giving records
|
|
* back. If there weren't any runs, run sort the buffer.
|
|
* If there were runs, sort and write out the last run and
|
|
* build a merge tree.
|
|
*
|
|
**************************************/
|
|
ULONG count, run_count, size, temp;
|
|
RUN run;
|
|
RMH *m1, *m2, *streams, streams_local[200];
|
|
MRG merge;
|
|
MRG merge_pool;
|
|
SORTP *buffer;
|
|
|
|
scb->scb_status_vector = status_vector;
|
|
|
|
if (scb->scb_last_record != (SR *) scb->scb_end_memory)
|
|
#ifdef SCROLLABLE_CURSORS
|
|
SORT_diddle_key((UCHAR *) KEYOF(scb->scb_last_record), scb, TRUE);
|
|
#else
|
|
diddle_key((UCHAR *) KEYOF(scb->scb_last_record), scb, TRUE);
|
|
#endif
|
|
|
|
/* If there aren't any runs, things fit nicely in memory. Just sort the mess
|
|
and we're ready for output */
|
|
|
|
if (!scb->scb_runs) {
|
|
sort(scb);
|
|
#ifdef SCROLLABLE_CURSORS
|
|
scb->scb_last_pointer = scb->scb_next_pointer - 1;
|
|
#endif
|
|
scb->scb_next_pointer = scb->scb_first_pointer + 1;
|
|
#ifdef SCROLLABLE_CURSORS
|
|
scb->scb_flags |= scb_initialized;
|
|
#endif
|
|
scb->scb_flags |= scb_sorted;
|
|
return FB_SUCCESS;
|
|
}
|
|
|
|
/* Write the last records as a run */
|
|
|
|
put_run(scb);
|
|
|
|
/* Build a merge tree for the run blocks. Start by laying them all out
|
|
in a vector; this is done to allow us to build a merge tree from the
|
|
bottom up, ensuring that a balanced tree is built. */
|
|
|
|
for (run_count = 0, run = scb->scb_runs; run; run = run->run_next) {
|
|
if (run->run_buff_alloc) {
|
|
gds__free(run->run_buffer);
|
|
run->run_buff_alloc = 0;
|
|
}
|
|
++run_count;
|
|
}
|
|
|
|
try {
|
|
if ((run_count * sizeof(RMH)) > sizeof(streams_local))
|
|
streams =
|
|
(RMH *) gds__alloc((SLONG) run_count * sizeof(RMH));
|
|
/* FREE: streams is freed later in this routine */
|
|
else
|
|
streams = streams_local;
|
|
} catch(const std::exception&) {
|
|
*status_vector++ = gds_arg_gds;
|
|
*status_vector++ = gds_sort_mem_err;
|
|
*status_vector = gds_arg_end;
|
|
return gds_sort_mem_err;
|
|
}
|
|
|
|
m1 = streams;
|
|
for (run = scb->scb_runs; run; run = run->run_next)
|
|
*m1++ = (RMH) run;
|
|
count = run_count;
|
|
|
|
/* We're building a b-tree of the sort merge blocks, we have (count)
|
|
* leaves already, so we *know* we need (count-1) merge blocks.
|
|
*/
|
|
if (count > 1) {
|
|
assert(!scb->scb_merge_pool); /* shouldn't have a pool */
|
|
try {
|
|
scb->scb_merge_pool =
|
|
(MRG) gds__alloc((SLONG) (count - 1)*sizeof(struct mrg));
|
|
/* FREE: smb_merge_pool freed in local_fini() when the scb is released */
|
|
|
|
merge_pool = scb->scb_merge_pool;
|
|
} catch(const std::exception&) {
|
|
gds__free(streams);
|
|
*status_vector++ = gds_arg_gds;
|
|
*status_vector++ = gds_sort_mem_err;
|
|
*status_vector = gds_arg_end;
|
|
return gds_sort_mem_err;
|
|
}
|
|
memset(merge_pool, 0, (count - 1) * sizeof(struct mrg));
|
|
}
|
|
else {
|
|
/* "Merge" of 1 or 0 runs doesn't make sense. */
|
|
assert(FALSE); /* We really shouldn't get here */
|
|
merge = (MRG) * streams; /* But if we do... */
|
|
}
|
|
|
|
/* each pass through the vector builds a level of the merge tree
|
|
by condensing two runs into one
|
|
We will continue to make passes until there is a single item */
|
|
|
|
/* See also kissing cousin of this loop in merge_runs() */
|
|
|
|
while (count > 1) {
|
|
m1 = m2 = streams;
|
|
|
|
/* "m1" is used to sequence through the runs being merged,
|
|
while "m2" points at the new merged run */
|
|
|
|
while (count >= 2) {
|
|
merge = merge_pool++;
|
|
merge->mrg_header.rmh_type = TYPE_MRG;
|
|
|
|
assert(((*m1)->rmh_type == TYPE_MRG) || /* garbage watch */
|
|
((*m1)->rmh_type == TYPE_RUN));
|
|
|
|
(*m1)->rmh_parent = merge;
|
|
merge->mrg_stream_a = *m1++;
|
|
|
|
assert(((*m1)->rmh_type == TYPE_MRG) || /* garbage watch */
|
|
((*m1)->rmh_type == TYPE_RUN));
|
|
|
|
(*m1)->rmh_parent = merge;
|
|
merge->mrg_stream_b = *m1++;
|
|
|
|
merge->mrg_record_a = (SORT_RECORD *) NULL;
|
|
merge->mrg_record_b = (SORT_RECORD *) NULL;
|
|
|
|
*m2++ = (RMH) merge;
|
|
count -= 2;
|
|
}
|
|
|
|
if (count)
|
|
*m2++ = *m1++;
|
|
count = m2 - streams;
|
|
}
|
|
|
|
if (streams != streams_local)
|
|
gds__free(streams);
|
|
buffer = (SORTP *) scb->scb_first_pointer;
|
|
merge->mrg_header.rmh_parent = NULL;
|
|
scb->scb_merge = merge;
|
|
scb->scb_longs -= SIZEOF_SR_BCKPTR_IN_LONGS;
|
|
|
|
/* Divvy up the sort space among buffers for runs. Although something slightly
|
|
better could be arranged, for now give them all the same size hunk. */
|
|
|
|
temp = DIFF_LONGS(scb->scb_end_memory, buffer);
|
|
count = temp / (scb->scb_longs * run_count);
|
|
if (count) {
|
|
size = count * (SSHORT) scb->scb_longs;
|
|
count = run_count;
|
|
}
|
|
else {
|
|
size = (SSHORT) scb->scb_longs;
|
|
count = temp / scb->scb_longs;
|
|
}
|
|
|
|
/* allocate buffer space for either all the runs, if they fit, or for
|
|
as many as allow */
|
|
|
|
for (run = scb->scb_runs; run && count; count--, run = run->run_next) {
|
|
run->run_buffer = buffer;
|
|
buffer += size;
|
|
run->run_record =
|
|
reinterpret_cast < sort_record * >(run->run_end_buffer = buffer);
|
|
}
|
|
|
|
/* if there was not enough buffer space, get some more for the remaining runs
|
|
allocating enough for the merge space plus a link */
|
|
|
|
for (; run; run = run->run_next) {
|
|
try {
|
|
run->run_buffer =
|
|
(ULONG *) gds__alloc((SLONG) (size * sizeof(ULONG)));
|
|
/* FREE: smb_merge_space freed in local_fini() when the scb is released */
|
|
} catch(const std::exception&) {
|
|
*status_vector++ = gds_arg_gds;
|
|
*status_vector++ = gds_sort_mem_err;
|
|
*status_vector = gds_arg_end;
|
|
return gds_sort_mem_err;
|
|
}
|
|
/* Link the new buffer into the chain of buffers */
|
|
run->run_buff_alloc = 1;
|
|
run->run_record =
|
|
reinterpret_cast < sort_record * >(run->run_end_buffer =
|
|
run->run_buffer + size);
|
|
}
|
|
|
|
scb->scb_flags |= scb_sorted;
|
|
return FB_SUCCESS;
|
|
}
|
|
|
|
|
|
ULONG SORT_write_block(STATUS * status_vector,
|
|
SFB sfb, ULONG seek, BLOB_PTR * address, ULONG length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* S O R T _ w r i t e _ b l o c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Write a block of stuff to the scratch file.
|
|
*
|
|
**************************************/
|
|
ULONG len, write_len, i;
|
|
|
|
#ifdef DEBUG_SORT_TRACE
|
|
write_trace("Write", sfb, seek, address, length);
|
|
#endif
|
|
|
|
/* Check out of engine on sort I/O */
|
|
|
|
THREAD_EXIT;
|
|
|
|
/* The following is a crock induced by a VMS C bug */
|
|
|
|
while (length) {
|
|
len = length;
|
|
for (i = 0; i < IO_RETRY; i++) {
|
|
if (lseek(sfb->sfb_file, LSEEK_OFFSET_CAST seek, SEEK_SET) == -1) {
|
|
THREAD_ENTER;
|
|
SORT_error(status_vector, sfb, "lseek", isc_io_write_err,
|
|
errno);
|
|
}
|
|
if ((write_len = write(sfb->sfb_file, address, len)) == len)
|
|
break;
|
|
else {
|
|
if (write_len >= 0)
|
|
/* If write returns value that is not equal len, then
|
|
most likely there is not enough space, try to write
|
|
one more time to get meaningful errno */
|
|
write_len = write(sfb->sfb_file, address + write_len,
|
|
len - write_len);
|
|
if ((SSHORT) write_len == -1 && !SYSCALL_INTERRUPTED(errno)) {
|
|
THREAD_ENTER;
|
|
SORT_error(status_vector, sfb, "write", isc_io_write_err,
|
|
errno);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i == IO_RETRY) {
|
|
THREAD_ENTER;
|
|
SORT_error(status_vector, sfb, "write", isc_io_write_err, errno);
|
|
}
|
|
length -= write_len;
|
|
address += write_len;
|
|
seek += write_len;
|
|
}
|
|
|
|
THREAD_ENTER;
|
|
|
|
return seek;
|
|
}
|
|
|
|
|
|
static UCHAR *sort_alloc(SCB scb, ULONG size)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a l l o c
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allocate and zero a block of memory.
|
|
*
|
|
* Notes about memory management in this module.
|
|
* - Apparently this historical reason (from Deej) that this
|
|
* module uses ALL_malloc directly, instead of the JRD allocator
|
|
* is so a large sort will not push up the high-water mark of
|
|
* memory allocated to a request or attachment (recall this memory
|
|
* isn't released until the request/attachment finished)
|
|
* - As a result, the memory blocks allocated here don't have
|
|
* the blk_header structure (we'ld have to add it if we ever
|
|
* change this)
|
|
* - Most things allocated have pointers placed in the scb.
|
|
* (sort control block)
|
|
* - There is an error handler set up by our caller, which will
|
|
* call back to SORT_fini(), which frees all the memory
|
|
* chains that hang off the scb.
|
|
* - There are some short-term allocations done (for instance,
|
|
* when sorting a run before writing it to disk). There appears
|
|
* to be no need to have an error handler to free them as
|
|
* no errors can be posted during the process.
|
|
*
|
|
* 1994-October-11 David Schnepper
|
|
*
|
|
**************************************/
|
|
UCHAR* block = 0;
|
|
|
|
try {
|
|
block =
|
|
reinterpret_cast<UCHAR*>(gds__alloc(size));
|
|
/* FREE: caller responsible for freeing */
|
|
} catch(const std::exception&) {
|
|
if (!block)
|
|
{
|
|
error_memory(scb);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
memset(block, 0, size);
|
|
return block;
|
|
}
|
|
|
|
#ifndef SCROLLABLE_CURSORS
|
|
#ifdef WORDS_BIGENDIAN
|
|
static void diddle_key(UCHAR * record, SCB scb, USHORT direction)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d i d d l e _ k e y ( n o n - V A X )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform transformation between the natural form of a record
|
|
* and a form that can be sorted in unsigned comparison order.
|
|
*
|
|
* direction - TRUE for SORT_put() and FALSE for SORT_get()
|
|
*
|
|
**************************************/
|
|
UCHAR *p, *fill_pos, fill_char;
|
|
USHORT complement, n, l, fill, flag;
|
|
SKD *key, *end;
|
|
|
|
for (key = scb->scb_description, end = key + scb->scb_keys; key < end;
|
|
key++) {
|
|
p = record + key->skd_offset;
|
|
n = key->skd_length;
|
|
complement = key->skd_flags & SKD_descending;
|
|
switch (key->skd_dtype) {
|
|
case SKD_ulong:
|
|
case SKD_ushort:
|
|
case SKD_bytes:
|
|
case SKD_sql_time:
|
|
break;
|
|
|
|
/* Stash embedded control info for non-fixed data types in the sort
|
|
record and zap it so that it doesn't interfere with collation. */
|
|
|
|
case SKD_varying:
|
|
if (direction) {
|
|
if (!(scb->scb_flags & scb_sorted)) {
|
|
*((USHORT *) (record + key->skd_vary_offset)) =
|
|
((VARY *) p)->vary_length;
|
|
fill_char =
|
|
(key->skd_flags & SKD_binary) ? 0 : ASCII_SPACE;
|
|
fill_pos = p + sizeof(USHORT) + ((VARY *) p)->vary_length;
|
|
fill = n - sizeof(USHORT) - ((VARY *) p)->vary_length;
|
|
if (fill)
|
|
memset(fill_pos, fill_char, fill);
|
|
}
|
|
((VARY *) p)->vary_length = 0;
|
|
}
|
|
break;
|
|
|
|
case SKD_cstring:
|
|
if (direction) {
|
|
fill_char = (key->skd_flags & SKD_binary) ? 0 : ASCII_SPACE;
|
|
if (!(scb->scb_flags & scb_sorted)) {
|
|
*((USHORT *) (record + key->skd_vary_offset)) = l =
|
|
strlen((char*)p);
|
|
fill_pos = p + l;
|
|
fill = n - l;
|
|
if (fill)
|
|
memset(fill_pos, fill_char, fill);
|
|
}
|
|
else {
|
|
l = *((USHORT *) (record + key->skd_vary_offset));
|
|
*(p + l) = fill_char;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SKD_text:
|
|
break;
|
|
|
|
#ifndef VMS
|
|
case SKD_d_float:
|
|
#else
|
|
Deliberate_compile_error++;
|
|
Fix for any VMS port.
|
|
#endif
|
|
case SKD_float:case SKD_double:flag = (direction || !complement)
|
|
? direction : TRUE;
|
|
if (flag ^ (*p >> 7))
|
|
*p ^= 1 << 7;
|
|
else
|
|
complement = !complement;
|
|
break;
|
|
|
|
case SKD_long:
|
|
case SKD_short:
|
|
case SKD_quad:
|
|
case SKD_timestamp1:
|
|
case SKD_timestamp2:
|
|
case SKD_sql_date:
|
|
case SKD_int64:
|
|
*p ^= 1 << 7;
|
|
break;
|
|
|
|
default:
|
|
assert(FALSE);
|
|
break;
|
|
}
|
|
if (complement && n)
|
|
do
|
|
*p++ ^= -1;
|
|
while (--n);
|
|
|
|
/* Flatter but don't complement control info for non-fixed
|
|
data types when restoring the data. */
|
|
|
|
if (key->skd_dtype == SKD_varying && !direction) {
|
|
p = record + key->skd_offset;
|
|
((VARY *) p)->vary_length = *((USHORT *) (record + key->skd_vary_offset));
|
|
}
|
|
|
|
if (key->skd_dtype == SKD_cstring && !direction) {
|
|
p = record + key->skd_offset;
|
|
l = *((USHORT *) (record + key->skd_vary_offset));
|
|
*(p + l) = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#else
|
|
static void diddle_key(UCHAR * record, SCB scb, USHORT direction)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d i d d l e _ k e y ( V A X )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform transformation between the natural form of a record
|
|
* and a form that can be sorted in unsigned comparison order.
|
|
*
|
|
* direction - TRUE for SORT_put() and FALSE for SORT_get()
|
|
*
|
|
**************************************/
|
|
BLOB_PTR *p;
|
|
UCHAR c1, fill_char, *fill_pos;
|
|
USHORT complement, n, l, fill;
|
|
USHORT HUGE_PTR *wp;
|
|
SSHORT longs, flag;
|
|
SORTP *lwp;
|
|
ULONG lw;
|
|
SKD *key, *end;
|
|
#ifdef VMS
|
|
double *dp;
|
|
#endif
|
|
#ifndef IEEE
|
|
USHORT w;
|
|
#endif
|
|
|
|
for (key = scb->scb_description, end = key + scb->scb_keys; key < end;
|
|
key++) {
|
|
p = (BLOB_PTR *) record + key->skd_offset;
|
|
wp = (USHORT HUGE_PTR *) p;
|
|
lwp = (SORTP *) p;
|
|
complement = key->skd_flags & SKD_descending;
|
|
n = ROUNDUP(key->skd_length, sizeof(SLONG));
|
|
switch (key->skd_dtype) {
|
|
case SKD_timestamp1:
|
|
case SKD_timestamp2:
|
|
case SKD_sql_time:
|
|
case SKD_sql_date:
|
|
p[3] ^= 1 << 7;
|
|
break;
|
|
|
|
case SKD_ulong:
|
|
case SKD_ushort:
|
|
break;
|
|
|
|
case SKD_text:
|
|
case SKD_bytes:
|
|
case SKD_cstring:
|
|
case SKD_varying:
|
|
|
|
/* Stash embedded control info for non-fixed data types in the sort
|
|
record and zap it so that it doesn't interfere with collation. */
|
|
|
|
if (key->skd_dtype == SKD_varying && direction) {
|
|
if (!(scb->scb_flags & scb_sorted)) {
|
|
*((USHORT *) (record + key->skd_vary_offset)) = l =
|
|
((VARY *) p)->vary_length;
|
|
fill_char =
|
|
(key->skd_flags & SKD_binary) ? 0 : ASCII_SPACE;
|
|
fill_pos = p + sizeof(USHORT) + l;
|
|
fill = n - sizeof(USHORT) - l;
|
|
if (fill)
|
|
memset(fill_pos, fill_char, fill);
|
|
}
|
|
((VARY *) p)->vary_length = 0;
|
|
}
|
|
|
|
if (key->skd_dtype == SKD_cstring && direction) {
|
|
fill_char = (key->skd_flags & SKD_binary) ? 0 : ASCII_SPACE;
|
|
if (!(scb->scb_flags & scb_sorted)) {
|
|
*((USHORT *) (record + key->skd_vary_offset)) = l =
|
|
strlen(reinterpret_cast < const char *>(p));
|
|
fill_pos = p + l;
|
|
fill = n - l;
|
|
if (fill)
|
|
memset(fill_pos, fill_char, fill);
|
|
}
|
|
else {
|
|
l = *((USHORT *) (record + key->skd_vary_offset));
|
|
*(p + l) = fill_char;
|
|
}
|
|
}
|
|
|
|
longs = n >> SHIFTLONG;
|
|
while (--longs >= 0) {
|
|
c1 = p[3];
|
|
p[3] = *p;
|
|
*p++ = c1;
|
|
c1 = p[1];
|
|
p[1] = *p;
|
|
*p = c1;
|
|
p += 3;
|
|
}
|
|
p = (BLOB_PTR *) wp;
|
|
break;
|
|
|
|
case SKD_short:
|
|
p[1] ^= 1 << 7;
|
|
break;
|
|
|
|
case SKD_long:
|
|
p[3] ^= 1 << 7;
|
|
break;
|
|
|
|
case SKD_quad:
|
|
p[7] ^= 1 << 7;
|
|
break;
|
|
|
|
case SKD_int64:
|
|
/* INT64's fit in TWO LONGS, and hence the SWAP has to happen
|
|
here for the right order comparison using DO_32_COMPARE */
|
|
if (!direction)
|
|
SWAP_LONGS(lwp[0], lwp[1], lw);
|
|
|
|
p[7] ^= 1 << 7;
|
|
|
|
if (direction)
|
|
SWAP_LONGS(lwp[0], lwp[1], lw);
|
|
break;
|
|
|
|
#ifdef IEEE
|
|
case SKD_double:
|
|
if (!direction) {
|
|
lw = lwp[0];
|
|
lwp[0] = lwp[1];
|
|
lwp[1] = lw;
|
|
}
|
|
flag = (direction || !complement) ? direction : TRUE;
|
|
if (flag ^ (p[7] >> 7))
|
|
p[7] ^= 1 << 7;
|
|
else
|
|
complement = !complement;
|
|
if (direction) {
|
|
lw = lwp[0];
|
|
lwp[0] = lwp[1];
|
|
lwp[1] = lw;
|
|
}
|
|
break;
|
|
|
|
case SKD_float:
|
|
flag = (direction || !complement) ? direction : TRUE;
|
|
if (flag ^ (p[3] >> 7))
|
|
p[3] ^= 1 << 7;
|
|
else
|
|
complement = !complement;
|
|
break;
|
|
|
|
#else /* IEEE */
|
|
#ifdef VMS
|
|
case SKD_d_float:
|
|
dp = (double *) p;
|
|
if (direction)
|
|
*dp = MTH$CVT_D_G(dp);
|
|
|
|
#endif
|
|
case SKD_double:
|
|
w = wp[2];
|
|
wp[2] = wp[3];
|
|
wp[3] = w;
|
|
|
|
#ifndef VMS
|
|
case SKD_d_float:
|
|
#endif
|
|
case SKD_float:
|
|
if (!direction)
|
|
if (complement) {
|
|
if (p[3] & 1 << 7)
|
|
complement = !complement;
|
|
else
|
|
p[3] ^= 1 << 7;
|
|
}
|
|
else {
|
|
if (p[3] & 1 << 7)
|
|
p[3] ^= 1 << 7;
|
|
else
|
|
complement = !complement;
|
|
}
|
|
w = wp[0];
|
|
wp[0] = wp[1];
|
|
wp[1] = w;
|
|
if (direction)
|
|
if (p[3] & 1 << 7)
|
|
complement = !complement;
|
|
else
|
|
p[3] ^= 1 << 7;
|
|
#ifdef VMS
|
|
else if (key->skd_dtype == SKD_d_float)
|
|
*dp = MTH$CVT_G_D(dp);
|
|
#endif
|
|
break;
|
|
#endif /* IEEE */
|
|
|
|
default:
|
|
|
|
/* Don't want the debug version to
|
|
stop because uf skd_type = 0
|
|
FSG 22.Dez.2000
|
|
|
|
assert (FALSE);
|
|
*/
|
|
|
|
break;
|
|
}
|
|
if (complement && n)
|
|
do
|
|
*p++ ^= -1;
|
|
while (--n);
|
|
|
|
/* Flatter but don't complement control info for non-fixed
|
|
data types when restoring the data. */
|
|
|
|
if (key->skd_dtype == SKD_varying && !direction) {
|
|
p = (BLOB_PTR *) record + key->skd_offset;
|
|
((VARY *) p)->vary_length = *((USHORT *) (record + key->skd_vary_offset));
|
|
}
|
|
|
|
if (key->skd_dtype == SKD_cstring && !direction) {
|
|
p = (BLOB_PTR *) record + key->skd_offset;
|
|
l = *((USHORT *) (record + key->skd_vary_offset));
|
|
*(p + l) = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
static void error_memory(SCB scb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e r r o r _ m e m o r y
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Report fatal out of memory error.
|
|
*
|
|
**************************************/
|
|
STATUS *status_vector;
|
|
|
|
status_vector = scb->scb_status_vector;
|
|
|
|
assert(status_vector != NULL);
|
|
|
|
*status_vector++ = gds_arg_gds;
|
|
*status_vector++ = gds_sort_mem_err;
|
|
*status_vector = gds_arg_end;
|
|
ERR_punt();
|
|
}
|
|
|
|
|
|
static ULONG find_file_space(SCB scb, ULONG size, SFB * ret_sfb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f i n d _ f i l e _ s p a c e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Find space of input size in one of the
|
|
* open sort files. If a free block is not
|
|
* available, allocate space at the end.
|
|
*
|
|
**************************************/
|
|
WFS space, *ptr, *best;
|
|
SFB sfb, *sfb_ptr, best_sfb, last_sfb;
|
|
TEXT file_name[128];
|
|
|
|
/* Find the best available space. This is defined as the smallest free space
|
|
that is big enough. This preserves large blocks. */
|
|
|
|
best = NULL;
|
|
last_sfb = NULL;
|
|
file_name[0] = '\0';
|
|
|
|
/* Search through the available space in the work file list. */
|
|
|
|
for (sfb_ptr = &scb->scb_sfb; (sfb = *sfb_ptr); sfb_ptr = &sfb->sfb_next) {
|
|
for (ptr = &sfb->sfb_file_space; (space = *ptr);
|
|
ptr = &(*ptr)->wfs_next) {
|
|
/* if this is smaller than our previous best, use it */
|
|
|
|
if (space->wfs_size >= size &&
|
|
(!best || (space->wfs_size < (*best)->wfs_size))) {
|
|
best = ptr;
|
|
best_sfb = sfb;
|
|
}
|
|
}
|
|
|
|
/* Save the previous sfb pointer because when we get out of this
|
|
for loop, sfb would be a NULL pointer. */
|
|
|
|
last_sfb = sfb;
|
|
}
|
|
sfb = last_sfb;
|
|
|
|
/* If we didn't find any space, allocate it at the end of the file. */
|
|
|
|
if (!best) {
|
|
/* if there is no file allocated yet or the size requested is bigger
|
|
than available space in the current directory, create a new file
|
|
and return. */
|
|
|
|
if (!sfb || !DLS_get_temp_space(size, sfb) ||
|
|
(sfb->sfb_file_size + size >= MAX_TEMPFILE_SIZE)) {
|
|
|
|
sfb = (SFB) sort_alloc(scb, (ULONG) sizeof(struct sfb));
|
|
/* FREE: scb_sfb chain is freed in local_fini() */
|
|
|
|
// Is the last DLS at it's size limit? If so, add a new DLS dir M.E.G
|
|
if (last_sfb && (last_sfb->sfb_dls->dls_inuse + size >= MAX_TEMPFILE_SIZE))
|
|
if (!DLS_add_dir(MAX_TEMPFILE_SIZE, last_sfb->sfb_dls->dls_directory))
|
|
error_memory(scb);
|
|
|
|
if (last_sfb)
|
|
last_sfb->sfb_next = sfb;
|
|
else
|
|
scb->scb_sfb = sfb;
|
|
|
|
/* Find a free space */
|
|
|
|
sfb->sfb_dls = NULL;
|
|
if (!DLS_get_temp_space(size, sfb))
|
|
/* There is not enough space */
|
|
error_memory(scb);
|
|
|
|
/* Create a scratch file */
|
|
|
|
sfb->sfb_file =
|
|
(int) gds__tmp_file2(FALSE, SCRATCH, file_name,
|
|
sfb->sfb_dls->dls_directory);
|
|
|
|
/* allocate the file name even if the file is not open,
|
|
because the error routine depends on it
|
|
This is released during local_fini(). */
|
|
|
|
sfb->sfb_file_name =
|
|
(TEXT *) sort_alloc(scb, (ULONG) (strlen(file_name) + 1));
|
|
/* FREE: sfb_file_name is freed in local_fini() */
|
|
|
|
strcpy(sfb->sfb_file_name, file_name);
|
|
|
|
if (sfb->sfb_file == -1)
|
|
SORT_error(scb->scb_status_vector, sfb, "open",
|
|
isc_io_open_err, errno);
|
|
|
|
sfb->sfb_mem = FB_NEW (*getDefaultMemoryPool()) SortMem(sfb, size);
|
|
}
|
|
|
|
*ret_sfb = sfb;
|
|
sfb->sfb_file_size += size;
|
|
return sfb->sfb_file_size - size;
|
|
}
|
|
|
|
/* set up the return parameters */
|
|
|
|
*ret_sfb = best_sfb;
|
|
space = *best;
|
|
|
|
/* If the hunk was an exact fit, remove the work file space block from the
|
|
list and splice it into the free list */
|
|
|
|
if (space->wfs_size == size) {
|
|
*best = space->wfs_next;
|
|
space->wfs_next = best_sfb->sfb_free_wfs;
|
|
best_sfb->sfb_free_wfs = space;
|
|
return space->wfs_position;
|
|
}
|
|
|
|
/* The best block is too big -- chop the needed space off the end. */
|
|
|
|
space->wfs_size -= size;
|
|
|
|
return space->wfs_position + space->wfs_size;
|
|
}
|
|
|
|
|
|
static void free_file_space(SCB scb, SFB sfb, ULONG position, ULONG size)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f r e e _ f i l e _ s p a c e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Release a segment of work file.
|
|
*
|
|
**************************************/
|
|
WFS space, *ptr, next;
|
|
ULONG end;
|
|
|
|
assert(size > 0);
|
|
assert(position < sfb->sfb_file_size); /* Block starts in file */
|
|
end = position + size;
|
|
assert(end <= sfb->sfb_file_size); /* Block ends in file */
|
|
|
|
/* Search through work file space blocks looking for an adjacent block. */
|
|
|
|
for (ptr = &sfb->sfb_file_space; (space = *ptr); ptr = &space->wfs_next) {
|
|
if (end >= space->wfs_position)
|
|
break;
|
|
}
|
|
|
|
if (space) {
|
|
/* may have found an adjacent block - try to join them together */
|
|
|
|
if (end == space->wfs_position) {
|
|
/* newly freed block starts just before previously freed */
|
|
space->wfs_position -= size;
|
|
space->wfs_size += size;
|
|
return;
|
|
}
|
|
|
|
if (position == space->wfs_position + space->wfs_size) {
|
|
/* newly freed block starts just after previously freed */
|
|
space->wfs_size += size;
|
|
|
|
if ((next = space->wfs_next) && end == next->wfs_position) {
|
|
/* The NEXT freed block is adjacent, join it too */
|
|
space->wfs_size += next->wfs_size;
|
|
space->wfs_next = next->wfs_next;
|
|
next->wfs_next = sfb->sfb_free_wfs;
|
|
sfb->sfb_free_wfs = next;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Blocks weren't adjacent - just nearby */
|
|
|
|
/* Check that block to free doesn't overlap existing free block */
|
|
assert(position >= space->wfs_position + space->wfs_size);
|
|
}
|
|
|
|
/* Block didn't seem to append nicely to an existing block */
|
|
|
|
if ( (space = sfb->sfb_free_wfs) )
|
|
sfb->sfb_free_wfs = space->wfs_next;
|
|
else
|
|
space = (WFS) sort_alloc(scb, (ULONG) sizeof(struct wfs));
|
|
/* FREE: wfs_next chain is freed in local_fini() */
|
|
|
|
space->wfs_next = *ptr;
|
|
*ptr = space;
|
|
space->wfs_size = size;
|
|
space->wfs_position = position;
|
|
}
|
|
|
|
|
|
static SORT_RECORD *get_merge(MRG merge, SCB scb
|
|
#ifdef SCROLLABLE_CURSORS
|
|
, RSE_GET_MODE mode
|
|
#endif
|
|
)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ m e r g e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get next record from a merge tree and/or run.
|
|
*
|
|
**************************************/
|
|
SORT_RECORD *record; /* no more than 1 SORTP* to a line */
|
|
SORTP *p; /* no more than 1 SORTP* to a line */
|
|
SORTP *q; /* no more than 1 SORTP* to a line */
|
|
ULONG l;
|
|
#ifdef SCROLLABLE_CURSORS
|
|
ULONG space_available, data_remaining;
|
|
#else
|
|
ULONG n;
|
|
#endif
|
|
USHORT eof;
|
|
RUN run;
|
|
|
|
record = NULL;
|
|
eof = FALSE;
|
|
|
|
while (merge) {
|
|
/* If node is a run, get the next record (or not) and back to parent. */
|
|
|
|
if (merge->mrg_header.rmh_type == TYPE_RUN) {
|
|
run = (RUN) merge;
|
|
merge = run->run_header.rmh_parent;
|
|
|
|
/* check for end-of-file condition in either direction */
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
if (
|
|
(mode == RSE_get_backward
|
|
&& run->run_records >= run->run_max_records - 1)
|
|
|| (mode == RSE_get_forward && run->run_records == 0))
|
|
#else
|
|
if (run->run_records == 0)
|
|
#endif
|
|
{
|
|
record = (SORT_RECORD *) - 1;
|
|
eof = TRUE;
|
|
continue;
|
|
}
|
|
|
|
eof = FALSE;
|
|
|
|
/* Find the appropriate record in the buffer to return. */
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
if (mode == RSE_get_forward) {
|
|
run->run_record = NEXT_RUN_RECORD(run->run_record);
|
|
#endif
|
|
if ((record = (SORT_RECORD *) run->run_record) <
|
|
(SORT_RECORD *) run->run_end_buffer) {
|
|
#ifndef SCROLLABLE_CURSORS
|
|
run->run_record =
|
|
reinterpret_cast <
|
|
sort_record * >(NEXT_RUN_RECORD(run->run_record));
|
|
#endif
|
|
--run->run_records;
|
|
continue;
|
|
}
|
|
#ifndef SCROLLABLE_CURSORS
|
|
/* There are records remaining, but the buffer is full. Read a buffer
|
|
full. */
|
|
|
|
l =
|
|
(ULONG) ((BLOB_PTR *) run->run_end_buffer -
|
|
(BLOB_PTR *) run->run_buffer);
|
|
n = run->run_records * scb->scb_longs * sizeof(ULONG);
|
|
l = MIN(l, n);
|
|
run->run_seek =
|
|
run->run_sfb->sfb_mem->read(scb->scb_status_vector,
|
|
run->run_seek,
|
|
reinterpret_cast<char*>(run->run_buffer),
|
|
l);
|
|
#else
|
|
}
|
|
else {
|
|
run->run_record = PREV_RUN_RECORD(run->run_record);
|
|
if ((record = (SORT_RECORD *) run->run_record) >=
|
|
run->run_buffer) {
|
|
++run->run_records;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* There are records remaining, but we have stepped over the
|
|
edge of the cache. Read the next buffer full of records. */
|
|
|
|
assert((BLOB_PTR *) run->run_end_buffer >
|
|
(BLOB_PTR *) run->run_buffer);
|
|
|
|
space_available =
|
|
(ULONG) ((BLOB_PTR *) run->run_end_buffer -
|
|
(BLOB_PTR *) run->run_buffer);
|
|
if (mode == RSE_get_forward)
|
|
data_remaining =
|
|
run->run_records * scb->scb_longs * sizeof(ULONG);
|
|
else
|
|
data_remaining =
|
|
(run->run_max_records -
|
|
run->run_records) * scb->scb_longs * sizeof(ULONG);
|
|
l = MIN(space_available, data_remaining);
|
|
|
|
if (mode == RSE_get_forward)
|
|
run->run_seek += run->run_cached;
|
|
else
|
|
run->run_seek -= l;
|
|
|
|
run->run_sfb->sfb_mem->read(run->run_seek, run->run_buffer, l);
|
|
run->run_cached = l;
|
|
|
|
if (mode == RSE_get_forward) {
|
|
#endif
|
|
record = reinterpret_cast < sort_record * >(run->run_buffer);
|
|
#ifndef SCROLLABLE_CURSORS
|
|
run->run_record =
|
|
reinterpret_cast <
|
|
sort_record * >(NEXT_RUN_RECORD(record));
|
|
#endif
|
|
--run->run_records;
|
|
#ifdef SCROLLABLE_CURSORS
|
|
}
|
|
else {
|
|
record = PREV_RUN_RECORD(run->run_end_buffer);
|
|
++run->run_records;
|
|
}
|
|
|
|
run->run_record = (SORT_RECORD *) record;
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
/* If've we got a record, somebody asked for it. Find out who. */
|
|
|
|
if (record)
|
|
if (merge->mrg_stream_a && !merge->mrg_record_a)
|
|
if (eof)
|
|
merge->mrg_stream_a = NULL;
|
|
else
|
|
merge->mrg_record_a = record;
|
|
else if (eof)
|
|
merge->mrg_stream_b = NULL;
|
|
else
|
|
merge->mrg_record_b = record;
|
|
|
|
|
|
/* If either streams need a record and is still active, loop back to pick
|
|
up the record. If either stream is dry, return the record of the other.
|
|
If both are dry, indicate eof for this stream. */
|
|
|
|
record = NULL;
|
|
eof = FALSE;
|
|
|
|
if (!merge->mrg_record_a && merge->mrg_stream_a) {
|
|
merge = (MRG) merge->mrg_stream_a;
|
|
continue;
|
|
}
|
|
|
|
if (!merge->mrg_record_b)
|
|
if (merge->mrg_stream_b) {
|
|
merge = (MRG) merge->mrg_stream_b;
|
|
continue;
|
|
}
|
|
else if ( (record = merge->mrg_record_a) ) {
|
|
merge->mrg_record_a = (SORT_RECORD *) NULL;
|
|
merge = merge->mrg_header.rmh_parent;
|
|
continue;
|
|
}
|
|
else {
|
|
eof = TRUE;
|
|
record = (SORT_RECORD *) - 1;
|
|
merge = merge->mrg_header.rmh_parent;
|
|
continue;
|
|
}
|
|
|
|
if (!merge->mrg_record_a) {
|
|
record = merge->mrg_record_b;
|
|
merge->mrg_record_b = NULL;
|
|
merge = merge->mrg_header.rmh_parent;
|
|
continue;
|
|
}
|
|
|
|
/* We have prospective records from each of the sub-streams. Compare them.
|
|
If equal, offer each to user routine for possible sacrifice. */
|
|
|
|
p = merge->mrg_record_a->sort_record_key;
|
|
q = merge->mrg_record_b->sort_record_key;
|
|
l = scb->scb_key_length;
|
|
|
|
DO_32_COMPARE(p, q, l);
|
|
|
|
if (l == 0 && scb->scb_dup_callback) {
|
|
#ifdef SCROLLABLE_CURSORS
|
|
SORT_diddle_key((UCHAR *) merge->mrg_record_a, scb, FALSE);
|
|
SORT_diddle_key((UCHAR *) merge->mrg_record_b, scb, FALSE);
|
|
#else
|
|
diddle_key((UCHAR *) merge->mrg_record_a, scb, FALSE);
|
|
diddle_key((UCHAR *) merge->mrg_record_b, scb, FALSE);
|
|
#endif
|
|
if (reinterpret_cast < UCHAR(*)(...) >
|
|
(*scb->scb_dup_callback) (merge->mrg_record_a,
|
|
merge->mrg_record_b,
|
|
scb->scb_dup_callback_arg)) {
|
|
merge->mrg_record_a = (SORT_RECORD *) NULL;
|
|
#ifdef SCROLLABLE_CURSORS
|
|
SORT_diddle_key((UCHAR *) merge->mrg_record_b, scb, TRUE);
|
|
#else
|
|
diddle_key((UCHAR *) merge->mrg_record_b, scb, TRUE);
|
|
#endif
|
|
continue;
|
|
}
|
|
#ifdef SCROLLABLE_CURSORS
|
|
SORT_diddle_key((UCHAR *) merge->mrg_record_a, scb, TRUE);
|
|
SORT_diddle_key((UCHAR *) merge->mrg_record_b, scb, TRUE);
|
|
#else
|
|
diddle_key((UCHAR *) merge->mrg_record_a, scb, TRUE);
|
|
diddle_key((UCHAR *) merge->mrg_record_b, scb, TRUE);
|
|
#endif
|
|
}
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
if (mode == RSE_get_forward && p[-1] < q[-1])
|
|
#else
|
|
if (p[-1] < q[-1])
|
|
#endif
|
|
{
|
|
record = merge->mrg_record_a;
|
|
merge->mrg_record_a = (SORT_RECORD *) NULL;
|
|
}
|
|
else {
|
|
record = merge->mrg_record_b;
|
|
merge->mrg_record_b = (SORT_RECORD *) NULL;
|
|
}
|
|
|
|
merge = merge->mrg_header.rmh_parent;
|
|
}
|
|
|
|
/* Merge pointer is null; we're done. Return either the most
|
|
recent record, or end of file, as appropriate. */
|
|
|
|
return (eof) ? NULL : record;
|
|
}
|
|
|
|
|
|
static void init(SCB scb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n i t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Initialize the sort control block for a quick sort.
|
|
*
|
|
**************************************/
|
|
|
|
scb->scb_next_pointer = scb->scb_first_pointer;
|
|
scb->scb_last_record = (SR *) scb->scb_end_memory;
|
|
#ifndef SCROLLABLE_CURSORS
|
|
scb->scb_run_start = scb->scb_records;
|
|
#endif
|
|
|
|
*scb->scb_next_pointer++ = reinterpret_cast < sort_record * >(low_key);
|
|
}
|
|
|
|
|
|
static BOOLEAN local_fini(SCB scb, ATT att)
|
|
{
|
|
/**************************************
|
|
*
|
|
* l o c a l _ f i n i
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Finish sort, and release all resources.
|
|
*
|
|
**************************************/
|
|
WFS space;
|
|
RUN run;
|
|
SFB sfb;
|
|
ULONG **merge_buf;
|
|
BOOLEAN found_it = TRUE;
|
|
SCB *ptr;
|
|
|
|
if (att) {
|
|
|
|
/* cover case where a posted error caused reuse by another thread */
|
|
|
|
if (scb->scb_attachment != att)
|
|
att = scb->scb_attachment;
|
|
found_it = FALSE;
|
|
}
|
|
|
|
/* Start by unlinking from que, if present */
|
|
|
|
if (att)
|
|
for (ptr = &att->att_active_sorts; *ptr; ptr = &(*ptr)->scb_next)
|
|
if (*ptr == scb) {
|
|
*ptr = scb->scb_next;
|
|
found_it = TRUE;
|
|
break;
|
|
}
|
|
|
|
/* *NO*. I won't free it if it's not in
|
|
the pointer list that has been passed
|
|
to me. THIS MEANS MEMORY LEAK. --mrs*/
|
|
|
|
if (found_it != TRUE)
|
|
return (FALSE);
|
|
|
|
/* Loop through the sfb list and close work files. */
|
|
|
|
while ( (sfb = scb->scb_sfb) ) {
|
|
scb->scb_sfb = sfb->sfb_next;
|
|
DLS_put_temp_space(sfb);
|
|
|
|
delete sfb->sfb_mem;
|
|
|
|
close(sfb->sfb_file);
|
|
|
|
if (sfb->sfb_file_name) {
|
|
unlink(sfb->sfb_file_name);
|
|
gds__free(sfb->sfb_file_name);
|
|
sfb->sfb_file_name = NULL;
|
|
}
|
|
|
|
while ( (space = sfb->sfb_free_wfs) ) {
|
|
sfb->sfb_free_wfs = space->wfs_next;
|
|
gds__free(space);
|
|
}
|
|
|
|
while ( (space = sfb->sfb_file_space) ) {
|
|
sfb->sfb_file_space = space->wfs_next;
|
|
gds__free(space);
|
|
}
|
|
|
|
gds__free(sfb);
|
|
}
|
|
|
|
/* get rid of extra merge space */
|
|
|
|
while ( (merge_buf = (ULONG **) scb->scb_merge_space) ) {
|
|
scb->scb_merge_space = *merge_buf;
|
|
gds__free(merge_buf);
|
|
}
|
|
|
|
/* If runs are allocated and not in the big block, release them. Then release
|
|
the big block. */
|
|
|
|
if (scb->scb_memory) {
|
|
gds__free(scb->scb_memory);
|
|
scb->scb_memory = NULL;
|
|
}
|
|
|
|
/* Clean up the runs that were used */
|
|
while ( (run = scb->scb_runs) ) {
|
|
scb->scb_runs = run->run_next;
|
|
if (run->run_buff_alloc)
|
|
gds__free(run->run_buffer);
|
|
gds__free(run);
|
|
}
|
|
|
|
/* Clean up the free runs also */
|
|
while ( (run = scb->scb_free_runs) ) {
|
|
scb->scb_free_runs = run->run_next;
|
|
if (run->run_buff_alloc)
|
|
gds__free(run->run_buffer);
|
|
gds__free(run);
|
|
}
|
|
|
|
if (scb->scb_merge_pool) {
|
|
gds__free(scb->scb_merge_pool);
|
|
scb->scb_merge_pool = NULL;
|
|
}
|
|
|
|
scb->scb_merge = NULL;
|
|
return (TRUE);
|
|
}
|
|
|
|
|
|
static void merge_runs(SCB scb, USHORT n)
|
|
{
|
|
/**************************************
|
|
*
|
|
* m e r g e _ r u n s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Merge the first n runs hanging off the sort control block, pushing
|
|
* the resulting run back onto the sort control block.
|
|
*
|
|
**************************************/
|
|
USHORT count, rec_size, buffers;
|
|
SORT_RECORD *p;
|
|
SORT_RECORD *q;
|
|
ULONG size, seek;
|
|
RUN run;
|
|
struct mrg blks[32];
|
|
struct run temp_run;
|
|
MRG merge;
|
|
RMH *m1, *m2, streams[32];
|
|
BLOB_PTR *buffer;
|
|
|
|
assert((n - 1) <= FB_NELEM(blks)); /* stack var big enuf? */
|
|
|
|
scb->scb_longs -= SIZEOF_SR_BCKPTR_IN_LONGS;
|
|
|
|
/* Make a pass thru the runs allocating buffer space, computing work file
|
|
space requirements, and filling in a vector of streams with run pointers. */
|
|
|
|
rec_size = scb->scb_longs << SHIFTLONG;
|
|
buffers = scb->scb_size_memory / rec_size;
|
|
size = rec_size * (buffers / (USHORT) (2 * n));
|
|
buffer = (BLOB_PTR *) scb->scb_first_pointer;
|
|
temp_run.run_end_buffer =
|
|
(SORTP *) (buffer + (scb->scb_size_memory / rec_size) * rec_size);
|
|
m1 = streams;
|
|
temp_run.run_size = 0;
|
|
temp_run.run_buff_alloc = 0;
|
|
|
|
for (run = scb->scb_runs, count = 0; count < n;
|
|
run = run->run_next, count++) {
|
|
*m1++ = (RMH) run;
|
|
|
|
/* size = 0 indicates the record is too big to divvy up the
|
|
big sort buffer, so separate buffers must be allocated */
|
|
|
|
if (!size) {
|
|
if (!run->run_buff_alloc) {
|
|
try {
|
|
run->run_buffer =
|
|
(ULONG *) gds__alloc((SLONG) rec_size * 2);
|
|
} catch (const std::exception&) {
|
|
/* FREE: smb_merge_space freed in local_fini() when scb released */
|
|
if (!run->run_buffer)
|
|
error_memory(scb);
|
|
}
|
|
run->run_buff_alloc = 1;
|
|
}
|
|
run->run_end_buffer =
|
|
reinterpret_cast <
|
|
ULONG * >((BLOB_PTR *) run->run_buffer + (rec_size * 2));
|
|
run->run_record =
|
|
reinterpret_cast < sort_record * >(run->run_end_buffer);
|
|
}
|
|
else {
|
|
run->run_buffer = (ULONG *) buffer;
|
|
buffer += size;
|
|
run->run_record =
|
|
reinterpret_cast < sort_record * >(run->run_end_buffer =
|
|
(ULONG *) buffer);
|
|
}
|
|
temp_run.run_size += run->run_size;
|
|
}
|
|
temp_run.run_record = reinterpret_cast < sort_record * >(buffer);
|
|
temp_run.run_buffer = reinterpret_cast < ULONG * >(temp_run.run_record);
|
|
|
|
/* Build merge tree bottom up */
|
|
/* See also kissing cousin of this loop in SORT_sort() */
|
|
|
|
for (count = n, merge = blks; count > 1;) {
|
|
m1 = m2 = streams;
|
|
while (count >= 2) {
|
|
merge->mrg_header.rmh_type = TYPE_MRG;
|
|
|
|
assert(((*m1)->rmh_type == TYPE_MRG) || /* garbage watch */
|
|
((*m1)->rmh_type == TYPE_RUN));
|
|
|
|
(*m1)->rmh_parent = merge;
|
|
merge->mrg_stream_a = *m1++;
|
|
|
|
assert(((*m1)->rmh_type == TYPE_MRG) || /* garbage watch */
|
|
((*m1)->rmh_type == TYPE_RUN));
|
|
|
|
(*m1)->rmh_parent = merge;
|
|
merge->mrg_stream_b = *m1++;
|
|
|
|
merge->mrg_record_a = (SORT_RECORD *) NULL;
|
|
merge->mrg_record_b = (SORT_RECORD *) NULL;
|
|
*m2++ = (RMH) merge;
|
|
merge++;
|
|
count -= 2;
|
|
}
|
|
if (count)
|
|
*m2++ = *m1++;
|
|
count = m2 - streams;
|
|
}
|
|
|
|
--merge;
|
|
merge->mrg_header.rmh_parent = NULL;
|
|
|
|
/* Merge records into run */
|
|
|
|
q = reinterpret_cast < sort_record * >(temp_run.run_buffer);
|
|
seek = temp_run.run_seek =
|
|
find_file_space(scb, temp_run.run_size, &temp_run.run_sfb);
|
|
temp_run.run_records = 0;
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
while (p = get_merge(merge, scb, RSE_get_forward))
|
|
#else
|
|
while ( (p = get_merge(merge, scb)) )
|
|
#endif
|
|
{
|
|
if (q >= (SORT_RECORD *) temp_run.run_end_buffer) {
|
|
size = (BLOB_PTR *) q - (BLOB_PTR *) temp_run.run_buffer;
|
|
seek = temp_run.run_sfb->sfb_mem->write(scb->scb_status_vector, seek,
|
|
reinterpret_cast<char*>(temp_run.run_buffer),
|
|
size);
|
|
q = reinterpret_cast < sort_record * >(temp_run.run_buffer);
|
|
}
|
|
count = scb->scb_longs;
|
|
do
|
|
*q++ = *p++;
|
|
while (--count);
|
|
++temp_run.run_records;
|
|
}
|
|
#ifdef SCROLLABLE_CURSORS
|
|
temp_run.run_max_records = temp_run.run_records;
|
|
#endif
|
|
|
|
/* Write the tail of the new run and return any unused space. */
|
|
|
|
if ( (size = (BLOB_PTR *) q - (BLOB_PTR *) temp_run.run_buffer) )
|
|
seek = temp_run.run_sfb->sfb_mem->write(scb->scb_status_vector, seek,
|
|
reinterpret_cast<char*>(temp_run.run_buffer),
|
|
size);
|
|
|
|
/* if the records did not fill the allocated run (such as when duplicates are
|
|
rejected), then free the remainder and diminish the size of the run accordingly */
|
|
|
|
if (seek - temp_run.run_seek < temp_run.run_size) {
|
|
free_file_space(scb, temp_run.run_sfb, seek,
|
|
temp_run.run_seek + temp_run.run_size - seek);
|
|
temp_run.run_size = seek - temp_run.run_seek;
|
|
}
|
|
|
|
/* Make a final pass thru the runs releasing space, blocks, etc. */
|
|
|
|
for (count = 0; count < n; count++) {
|
|
/* Remove run from list of in-use run blocks */
|
|
run = scb->scb_runs;
|
|
scb->scb_runs = run->run_next;
|
|
#ifdef SCROLLABLE_CURSORS
|
|
seek = run->run_seek + run->run_cached - run->run_size;
|
|
#else
|
|
seek = run->run_seek - run->run_size;
|
|
#endif
|
|
/* Free the sort file space associated with the run */
|
|
|
|
free_file_space(scb, run->run_sfb, seek, run->run_size);
|
|
|
|
/* Add run descriptor to list of unused run descriptor blocks */
|
|
|
|
run->run_next = scb->scb_free_runs;
|
|
scb->scb_free_runs = run;
|
|
}
|
|
|
|
scb->scb_free_runs = run->run_next;
|
|
if (run->run_buff_alloc) {
|
|
gds__free(run->run_buffer);
|
|
run->run_buff_alloc = 0;
|
|
}
|
|
temp_run.run_header.rmh_type = TYPE_RUN;
|
|
temp_run.run_depth = run->run_depth;
|
|
*run = temp_run;
|
|
run->run_next = scb->scb_runs;
|
|
++run->run_depth;
|
|
scb->scb_runs = run;
|
|
|
|
scb->scb_longs += SIZEOF_SR_BCKPTR_IN_LONGS;
|
|
}
|
|
|
|
|
|
static void quick(SLONG size, SORTP ** pointers, USHORT length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* q u i c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Sort an array of record pointers. The routine assumes the
|
|
* following:
|
|
*
|
|
* a. Each element in the array points to the key of a record.
|
|
*
|
|
* b. Keys can be compared by auto-incrementing unsigned longword
|
|
* compares.
|
|
*
|
|
* c. Relative array positions "-1" and "size" point to guard records
|
|
* containing the least and the greatest possible sort keys.
|
|
*
|
|
* ***************************************************************
|
|
* * Boy, did the assumption below turn out to be pretty stupid! *
|
|
* ***************************************************************
|
|
*
|
|
* Note: For the time being, the key length field is ignored on the
|
|
* assumption that something will eventually stop the comparison.
|
|
*
|
|
* WARNING: THIS ROUTINE DOES NOT MAKE A FINAL PASS TO UNSCRAMBLE
|
|
* PARITIONS OF SIZE TWO. THE POINTER ARRAY REQUIRES ADDITIONAL
|
|
* PROCESSING BEFORE IT MAY BE USED!
|
|
*
|
|
**************************************/
|
|
SORTP **stack_lower[50];
|
|
SORTP **stack_upper[50];
|
|
SORTP ***sl;
|
|
SORTP ***su;
|
|
SORTP *temp;
|
|
SORTP **r;
|
|
SORTP **i;
|
|
SORTP **j;
|
|
ULONG key;
|
|
SORTP *p;
|
|
SORTP *q;
|
|
SLONG interval;
|
|
USHORT tl;
|
|
|
|
#define exchange(x, y) {temp = x; x = y; y = temp;}
|
|
|
|
sl = stack_lower;
|
|
su = stack_upper;
|
|
*sl++ = pointers;
|
|
*su++ = pointers + size - 1;
|
|
|
|
while (sl > stack_lower) {
|
|
|
|
/* Pick up the next interval off the respective stacks */
|
|
|
|
r = *--sl;
|
|
j = *--su;
|
|
|
|
/* Compute the interval. If two or less, defer the sort to a final
|
|
pass. */
|
|
|
|
interval = j - r;
|
|
if (interval < 2)
|
|
continue;
|
|
|
|
/* Go guard against pre-ordered data, swap the first record with the
|
|
middle record. This isn't perfect, but it is cheap. */
|
|
|
|
i = r + interval / 2;
|
|
((SORTP ***) (*r))[-1] = i;
|
|
((SORTP ***) (*i))[-1] = r;
|
|
exchange(*r, *i);
|
|
|
|
/* Prepare to do the partition. Pick up the first longword of the
|
|
key to speed up comparisons. */
|
|
|
|
i = r + 1;
|
|
key = **r;
|
|
|
|
/* From each end of the interval converge to the middle swapping out of
|
|
parition records as we go. Stop when we converge. */
|
|
|
|
while (TRUE) {
|
|
while (**i < key)
|
|
i++;
|
|
if (**i == key)
|
|
while (TRUE) {
|
|
p = *i;
|
|
q = *r;
|
|
tl = length - 1;
|
|
while (tl && *p == *q) {
|
|
p++;
|
|
q++;
|
|
tl--;
|
|
}
|
|
if (tl && *p > *q)
|
|
break;
|
|
i++;
|
|
}
|
|
|
|
while (**j > key)
|
|
j--;
|
|
if (**j == key)
|
|
while (j != r) {
|
|
p = *j;
|
|
q = *r;
|
|
tl = length - 1;
|
|
while (tl && *p == *q) {
|
|
p++;
|
|
q++;
|
|
tl--;
|
|
}
|
|
if (tl && *p < *q)
|
|
break;
|
|
j--;
|
|
}
|
|
if (i >= j)
|
|
break;
|
|
((SORTP ***) (*i))[-1] = j;
|
|
((SORTP ***) (*j))[-1] = i;
|
|
exchange(*i, *j);
|
|
i++;
|
|
j--;
|
|
}
|
|
|
|
/* We have formed two partitions, separated by a slot for the
|
|
initial record "r". Exchange the record currently in the
|
|
slot with "r". */
|
|
|
|
((SORTP ***) (*r))[-1] = j;
|
|
((SORTP ***) (*j))[-1] = r;
|
|
exchange(*r, *j);
|
|
|
|
/* Finally, stack the two intervals, longest first. */
|
|
|
|
i = *su;
|
|
if ((j - r) > (i - j + 1)) {
|
|
*sl++ = r;
|
|
*su++ = j - 1;
|
|
*sl++ = j + 1;
|
|
*su++ = i;
|
|
}
|
|
else {
|
|
*sl++ = j + 1;
|
|
*su++ = i;
|
|
*sl++ = r;
|
|
*su++ = j - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static ULONG order(SCB scb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* o r d e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* The memoryfull of record pointers have been sorted, but more
|
|
* records remain, so the run will have to be written to disk. To
|
|
* speed this up, re-arrange the records in physical order so they
|
|
* can be written with a single disk write.
|
|
*
|
|
**************************************/
|
|
SR *record;
|
|
SORT_RECORD *output;
|
|
SORT_PTR *lower_limit;
|
|
SORT_RECORD **ptr;
|
|
SORTP* buffer = 0;
|
|
SSHORT length;
|
|
ULONG temp[1024];
|
|
|
|
ptr = scb->scb_first_pointer + 1; /* 1st ptr is low key */
|
|
|
|
/* last inserted record, also the top of the memory where SORT_RECORDS can
|
|
be written */
|
|
lower_limit = reinterpret_cast < SORT_PTR * >(output =
|
|
reinterpret_cast <
|
|
sort_record *
|
|
>(scb->scb_last_record));
|
|
|
|
try {
|
|
if ((scb->scb_longs * sizeof(ULONG)) > sizeof(temp))
|
|
buffer =
|
|
(ULONG *) gds__alloc((SLONG) (scb->scb_longs*sizeof(ULONG)));
|
|
/* FREE: buffer is freed later in this routine */
|
|
else
|
|
buffer = temp;
|
|
} catch(const std::exception&) {
|
|
if (!buffer)
|
|
error_memory(scb);
|
|
}
|
|
|
|
/* Check out the engine */
|
|
|
|
THREAD_EXIT;
|
|
|
|
/* length of the key part of the record */
|
|
length = scb->scb_longs - SIZEOF_SR_BCKPTR_IN_LONGS;
|
|
|
|
/* scb_next_pointer points to the end of pointer memory or the beginning of
|
|
records */
|
|
while (ptr < scb->scb_next_pointer) {
|
|
/* If the next pointer is null, it's record has been eliminated as a
|
|
duplicate. This is the only easy case. */
|
|
|
|
if (!(record = reinterpret_cast < SR * >(*ptr++)))
|
|
continue;
|
|
|
|
/* make record point back to the starting of SR struct.
|
|
as all scb_*_pointer point to the key_id locations! */
|
|
record =
|
|
reinterpret_cast <
|
|
SR * >(((SORTP *) record) - SIZEOF_SR_BCKPTR_IN_LONGS);
|
|
|
|
/* If the lower limit of live records points to a deleted or used record,
|
|
advance the lower limit. */
|
|
|
|
while (!*(lower_limit)
|
|
&& (lower_limit < (SORT_PTR *) scb->scb_end_memory))
|
|
lower_limit =
|
|
reinterpret_cast <
|
|
SORT_PTR * >(((SORTP *) lower_limit) + scb->scb_longs);
|
|
|
|
/* If the record we want to move won't interfere with lower active
|
|
record, just move the record into position. */
|
|
|
|
if (record->sr_sort_record.sort_record_key == (ULONG *) lower_limit) {
|
|
MOVE_32(length, record->sr_sort_record.sort_record_key, output);
|
|
output =
|
|
reinterpret_cast < sort_record * >((SORTP *) output + length);
|
|
continue;
|
|
}
|
|
|
|
if (((SORTP *) output) + scb->scb_longs - 1 <= (SORTP *) lower_limit) {
|
|
/* null the bckptr for this record */
|
|
record->sr_bckptr =
|
|
reinterpret_cast < sort_record ** >((SR **) NULL);
|
|
MOVE_32(length, record->sr_sort_record.sort_record_key, output);
|
|
output =
|
|
reinterpret_cast < sort_record * >((SORTP *) output + length);
|
|
continue;
|
|
}
|
|
|
|
/* There's another record sitting where we want to put our record. Move
|
|
the next logical record to a temp, move the lower limit record to the
|
|
next record's old position (adjusting pointers as we go), then move
|
|
the current record to output. */
|
|
|
|
MOVE_32(length, (SORTP *) record->sr_sort_record.sort_record_key,
|
|
buffer);
|
|
|
|
**((SORT_PTR ***) lower_limit) =
|
|
reinterpret_cast <
|
|
SORT_PTR * >(record->sr_sort_record.sort_record_key);
|
|
MOVE_32(scb->scb_longs, lower_limit, record);
|
|
lower_limit = (SORT_PTR *) ((SORTP *) lower_limit + scb->scb_longs);
|
|
|
|
MOVE_32(length, buffer, output);
|
|
output =
|
|
reinterpret_cast <
|
|
sort_record * >((SORT_PTR *) ((SORTP *) output + length));
|
|
}
|
|
|
|
/* Check back into the engine */
|
|
|
|
THREAD_ENTER;
|
|
|
|
/* It's OK to free this after checking back into the engine, there's
|
|
* only fatal failures possible there
|
|
*/
|
|
|
|
if (buffer != temp)
|
|
if (buffer != NULL)
|
|
gds__free(buffer);
|
|
|
|
return (((SORTP *) output) -
|
|
((SORTP *) scb->scb_last_record)) / (scb->scb_longs -
|
|
SIZEOF_SR_BCKPTR_IN_LONGS);
|
|
}
|
|
|
|
|
|
static void put_run(SCB scb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p u t _ r u n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Memory has been exhausted. Do a sort on what we have and write
|
|
* it to the scratch file. Keep in mind that since duplicate records
|
|
* may disappear, the number of records in the run may be less than
|
|
* were sorted.
|
|
*
|
|
**************************************/
|
|
RUN run;
|
|
//ULONG n, records;
|
|
|
|
if ( (run = scb->scb_free_runs) )
|
|
scb->scb_free_runs = run->run_next;
|
|
else {
|
|
run = (RUN) sort_alloc(scb, (ULONG) sizeof(struct run));
|
|
/* FREE: run will be either on the scb_runs or scb_free_runs list,
|
|
* which are freed in local_fini() */
|
|
}
|
|
|
|
run->run_next = scb->scb_runs;
|
|
scb->scb_runs = run;
|
|
run->run_header.rmh_type = TYPE_RUN;
|
|
run->run_depth = 0;
|
|
|
|
/* Do the in-core sort. The first phase a duplicate handling we be performed
|
|
in "sort". */
|
|
|
|
sort(scb);
|
|
|
|
/* Re-arrange records in physical order so they can be dumped in a single write
|
|
operation. */
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
run->run_records = run->run_max_records = order(scb);
|
|
run->run_cached = 0;
|
|
#else
|
|
run->run_records = order(scb);
|
|
#endif
|
|
|
|
/* Write records to scratch file. Keep track of the number of bytes
|
|
written, etc. */
|
|
|
|
run->run_size =
|
|
run->run_records * (scb->scb_longs -
|
|
SIZEOF_SR_BCKPTR_IN_LONGS) * sizeof(ULONG);
|
|
run->run_seek = find_file_space(scb, run->run_size, &run->run_sfb);
|
|
run->run_sfb->sfb_mem->write(scb->scb_status_vector, run->run_seek,
|
|
reinterpret_cast<char*>(scb->scb_last_record),
|
|
run->run_size);
|
|
}
|
|
|
|
|
|
static void sort(SCB scb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s o r t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set up for and call quick sort. Quicksort, by design, doesn't
|
|
* order partitions of length 2, so make a pass thru the data to
|
|
* straighten out pairs. While we at it, if duplicate handling has
|
|
* been requested, detect and handle them.
|
|
*
|
|
**************************************/
|
|
SORTP **i;
|
|
SORTP **j;
|
|
SORTP *p;
|
|
SORTP *q;
|
|
SORTP *temp;
|
|
ULONG n;
|
|
USHORT tl;
|
|
|
|
/* Check out the engine */
|
|
|
|
THREAD_EXIT;
|
|
|
|
/* First, insert a pointer to the high key. */
|
|
|
|
*scb->scb_next_pointer = reinterpret_cast < sort_record * >(high_key);
|
|
|
|
/* Next, call QuickSort. Keep in mind that the first pointer is the
|
|
low key and not a record. */
|
|
|
|
j = (SORTP **) (scb->scb_first_pointer) + 1;
|
|
n = (SORTP **) (scb->scb_next_pointer) - j; /* calculate # of records */
|
|
|
|
quick(n, j, scb->scb_longs);
|
|
|
|
/* Scream through and correct any out of order pairs. */
|
|
|
|
while (j < (SORTP **) scb->scb_next_pointer) {
|
|
i = j;
|
|
j++;
|
|
if (**i >= **j) {
|
|
p = *i;
|
|
q = *j;
|
|
tl = scb->scb_longs - 1;
|
|
while (tl && *p == *q) {
|
|
p++;
|
|
q++;
|
|
tl--;
|
|
}
|
|
if (tl && *p > *q) {
|
|
((SORTP ***) (*i))[-1] = j;
|
|
((SORTP ***) (*j))[-1] = i;
|
|
temp = *i;
|
|
*i = *j;
|
|
*j = temp;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If duplicate handling hasn't been requested, we're done. */
|
|
|
|
if (!scb->scb_dup_callback) {
|
|
/* Check back into the engine */
|
|
|
|
THREAD_ENTER;
|
|
return;
|
|
}
|
|
|
|
/* Make another pass and eliminate duplicates. It's possible to do this
|
|
is the same pass the final ordering, but the logic is complicated enough
|
|
to screw up register optimizations. Better two fast passes than one
|
|
slow pass, I suppose. Prove me wrong and win a trip for two to
|
|
Cleveland, Ohio. */
|
|
|
|
j = reinterpret_cast < SORTP ** >(scb->scb_first_pointer + 1);
|
|
|
|
while (j < (SORTP **) scb->scb_next_pointer) {
|
|
i = j;
|
|
j++;
|
|
if (**i != **j)
|
|
continue;
|
|
p = *i;
|
|
q = *j;
|
|
tl = scb->scb_longs - 1;
|
|
while (tl && *p == *q) {
|
|
p++;
|
|
q++;
|
|
tl--;
|
|
}
|
|
if ( ((p - *i) >= scb->scb_key_length) ) {
|
|
#ifdef SCROLLABLE_CURSORS
|
|
SORT_diddle_key((UCHAR *) * i, scb, FALSE);
|
|
SORT_diddle_key((UCHAR *) * j, scb, FALSE);
|
|
#else
|
|
diddle_key((UCHAR *) * i, scb, FALSE);
|
|
diddle_key((UCHAR *) * j, scb, FALSE);
|
|
#endif
|
|
if (reinterpret_cast < UCHAR(*)(...) >
|
|
(*scb->scb_dup_callback) (*i, *j, scb->scb_dup_callback_arg)) {
|
|
((SORTP ***) (*i))[-1] = NULL;
|
|
*i = NULL;
|
|
}
|
|
else
|
|
#ifdef SCROLLABLE_CURSORS
|
|
SORT_diddle_key((UCHAR *) * i, scb, TRUE);
|
|
SORT_diddle_key((UCHAR *) * j, scb, TRUE);
|
|
#else
|
|
diddle_key((UCHAR *) * i, scb, TRUE);
|
|
diddle_key((UCHAR *) * j, scb, TRUE);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Check back into the engine */
|
|
|
|
THREAD_ENTER;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
static void validate(SCB scb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* v a l i d a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Validate data structures.
|
|
*
|
|
**************************************/
|
|
SORTP **ptr;
|
|
SORTP *record;
|
|
STATUS *status_vector;
|
|
|
|
for (ptr = (SORTP **) (scb->scb_first_pointer + 1);
|
|
ptr < (SORTP **) scb->scb_next_pointer; ptr++) {
|
|
record = *ptr;
|
|
if (record[-1] != (SORTP) ptr) {
|
|
status_vector = scb->scb_status_vector;
|
|
*status_vector++ = gds_arg_gds;
|
|
*status_vector++ = gds_crrp_data_err;
|
|
/* Msg360: sort error: corruption in data structure */
|
|
*status_vector = gds_arg_end;
|
|
ERR_punt();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef DEBUG_SORT_TRACE
|
|
static void write_trace(
|
|
UCHAR * operation,
|
|
SFB sfb, ULONG seek, BLOB_PTR * address, ULONG length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* w r i t e _ t r a c e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Write a trace record.
|
|
*
|
|
**************************************/
|
|
UCHAR file_name[32], data[41], *p;
|
|
#if defined FREEBSD || defined NETBSD
|
|
int fd;
|
|
#endif
|
|
|
|
if (!trace_file) {
|
|
#if (defined WIN_NT)
|
|
strcpy(file_name, "/interbas/stXXXXXX");
|
|
#else
|
|
strcpy(file_name, "/interbase/DEBUG_SORT_TRACE_XXXXXX");
|
|
#endif
|
|
#if defined FREEBSD || defined NETBSD
|
|
fd = mkstemp(file_name);
|
|
trace_file = fdopen(fd, "w");
|
|
#else
|
|
mktemp(file_name);
|
|
trace_file = ib_fopen(file_name, "w");
|
|
#endif
|
|
}
|
|
|
|
if (!trace_file)
|
|
return;
|
|
|
|
for (p = data; p < data + sizeof(data) - 1; address++)
|
|
*p++ = (*address) ? *address : '.';
|
|
|
|
*p = 0;
|
|
|
|
ib_fprintf(trace_file, "Fid: %d, %.5s %.7ld - %.7ld\t/%s/\n",
|
|
sfb->sfb_file, operation, seek, seek + length, data);
|
|
}
|
|
#endif
|