mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-25 00:03:03 +01:00
2774 lines
71 KiB
C++
2774 lines
71 KiB
C++
/*
|
|
* PROGRAM: JRD Access Method
|
|
* MODULE: cvt.cpp
|
|
* DESCRIPTION: Data mover and converter and comparator, etc.
|
|
*
|
|
* 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): ______________________________________.
|
|
*
|
|
* 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete ports:
|
|
* - DELTA and IMP
|
|
*
|
|
* 2001.6.16 Claudio Valderrama: Wiped out the leading space in
|
|
* cast(float_expr as char(n)) in dialect 1, reported in SF.
|
|
* 2001.11.19 Claudio Valderrama: integer_to_text() should use the
|
|
* source descriptor "from" to call conversion_error.
|
|
*
|
|
* 2002.10.28 Sean Leyne - Completed removal of obsolete "DGUX" port
|
|
*
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "../jrd/common.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include "../jrd/jrd_time.h"
|
|
#ifndef REQUESTER
|
|
#include "../jrd/jrd.h"
|
|
#else
|
|
#include "../jrd/common.h"
|
|
#endif
|
|
#include "../jrd/req.h"
|
|
#include "../jrd/val.h"
|
|
#include "../jrd/quad.h"
|
|
#include "gen/iberror.h"
|
|
#include "../jrd/intl.h"
|
|
#include "../jrd/gdsassert.h"
|
|
#include "../jrd/cvt_proto.h"
|
|
#include "../jrd/dsc_proto.h"
|
|
#include "../jrd/err_proto.h"
|
|
#include "../jrd/gds_proto.h"
|
|
#include "../jrd/intl_proto.h"
|
|
#include "../jrd/thd_proto.h"
|
|
|
|
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
|
|
#if TIME_WITH_SYS_TIME
|
|
# include <sys/time.h>
|
|
# include <time.h>
|
|
#else
|
|
# if HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
# else
|
|
# include <time.h>
|
|
# endif
|
|
#endif
|
|
|
|
#if !(defined REQUESTER && defined SUPERCLIENT)
|
|
#include "../jrd/intl_classes.h"
|
|
#endif
|
|
|
|
#ifdef sun
|
|
#define DBL_MAX_10_EXP 308
|
|
#endif
|
|
|
|
#ifdef SCO_UNIX
|
|
#define DBL_MAX_10_EXP 308
|
|
#endif
|
|
|
|
#ifndef DBL_MAX_10_EXP
|
|
#include <float.h>
|
|
#endif
|
|
|
|
#ifdef VMS
|
|
double MTH$CVT_D_G(), MTH$CVT_G_D();
|
|
#endif
|
|
|
|
/* ERR_post is used to flag whether we were called from mov.cpp or
|
|
anywhere else CVT is used from (by comparing with param err) */
|
|
|
|
/* normally the following two definitions are part of limits.h
|
|
but due to a compiler bug on Apollo casting LONG_MIN to be a
|
|
double, these have to be defined as double numbers...
|
|
|
|
Also, since SunOS4.0 doesn't want to play with the rest of
|
|
the ANSI world, these definitions have to be included explicitly.
|
|
So, instead of some including <limits.h> and others using these
|
|
definitions, just always use these definitions (huh?) */
|
|
|
|
#define LONG_MIN_real -2147483648. /* min decimal value of an "SLONG" */
|
|
#define LONG_MAX_real 2147483647. /* max decimal value of an "SLONG" */
|
|
#define LONG_MIN_int -2147483648 /* min integer value of an "SLONG" */
|
|
#define LONG_MAX_int 2147483647 /* max integer value of an "SLONG" */
|
|
|
|
/* It turns out to be tricky to write the INT64 versions of those constant in
|
|
a way that will do the right thing on all platforms. Here we go. */
|
|
|
|
#define LONG_MAX_int64 ((SINT64)2147483647) /* max int64 value of an SLONG */
|
|
#define LONG_MIN_int64 (-LONG_MAX_int64-1) /* min int64 value of an SLONG */
|
|
|
|
#define QUAD_MIN_real -9223372036854775808. /* min decimal value of quad */
|
|
#define QUAD_MAX_real 9223372036854775807. /* max decimal value of quad */
|
|
|
|
#define QUAD_MIN_int quad_min_int /* min integer value of quad */
|
|
#define QUAD_MAX_int quad_max_int /* max integer value of quad */
|
|
|
|
#define FLOAT_MAX 3.4e38 /* max float (32 bit) value */
|
|
|
|
#define LETTER7(c) ((c) >= 'A' && (c) <= 'Z')
|
|
#define DIGIT(c) ((c) >= '0' && (c) <= '9')
|
|
#define ABSOLUT(x) ((x) < 0 ? -(x) : (x))
|
|
|
|
/* The expressions for SHORT_LIMIT, LONG_LIMIT, INT64_LIMIT and
|
|
* QUAD_LIMIT return the largest value that permit you to multiply by
|
|
* 10 without getting an overflow. The right operand of the << is two
|
|
* less than the number of bits in the type: one bit is for the sign,
|
|
* and the other is because we divide by 5, rather than 10. */
|
|
|
|
#define SHORT_LIMIT ((1 << 14) / 5)
|
|
#define LONG_LIMIT ((1L << 30) / 5)
|
|
|
|
#define COMMA ','
|
|
|
|
/* NOTE: The syntax for the below line may need modification to ensure
|
|
* the result of 1 << 62 is a quad
|
|
*/
|
|
#define QUAD_LIMIT ((((SINT64) 1) << 62) / 5)
|
|
#define INT64_LIMIT ((((SINT64) 1) << 62) / 5)
|
|
#define NUMERIC_LIMIT (INT64_LIMIT)
|
|
|
|
#define TODAY "TODAY"
|
|
#define NOW "NOW"
|
|
#define TOMORROW "TOMORROW"
|
|
#define YESTERDAY "YESTERDAY"
|
|
|
|
#define CVT_FAILURE_SPACE 128
|
|
|
|
#define CVT_COPY_BUFF(from,to,len) \
|
|
{if (len) {MOVE_FAST(from,to,len); from +=len; to+=len; len=0;} }
|
|
|
|
typedef enum {
|
|
expect_timestamp,
|
|
expect_sql_date,
|
|
expect_sql_time
|
|
} EXPECT_DATETIME;
|
|
|
|
#if (defined REQUESTER || defined SUPERCLIENT)
|
|
static TEXT cvt_failures[CVT_FAILURE_SPACE];
|
|
static TEXT* cvt_failures_ptr = NULL;
|
|
static const TEXT* error_string(const char*, SSHORT);
|
|
#endif
|
|
|
|
static void conversion_error(const dsc*, FPTR_ERROR);
|
|
static void datetime_to_text(const dsc*, dsc*, FPTR_ERROR);
|
|
static SSHORT decompose(const char*, USHORT, SSHORT, SLONG*, FPTR_ERROR);
|
|
static void float_to_text(const dsc*, dsc*, FPTR_ERROR);
|
|
static void integer_to_text(const dsc*, dsc*, FPTR_ERROR);
|
|
static void string_to_datetime(const dsc*, GDS_TIMESTAMP*, EXPECT_DATETIME,
|
|
FPTR_ERROR);
|
|
static double power_of_ten(const int);
|
|
|
|
#ifndef NATIVE_QUAD
|
|
#ifndef WORDS_BIGENDIAN
|
|
static const SQUAD quad_min_int = { 0, LONG_MIN };
|
|
static const SQUAD quad_max_int = { -1, LONG_MAX };
|
|
#else
|
|
static const SQUAD quad_min_int = { LONG_MIN, 0 };
|
|
static const SQUAD quad_max_int = { LONG_MAX, -1 };
|
|
#endif
|
|
#endif
|
|
|
|
|
|
#if !defined (NATIVE_QUAD)
|
|
#include "../jrd/quad.cpp"
|
|
#endif
|
|
|
|
using namespace Jrd;
|
|
|
|
double CVT_date_to_double(const dsc* desc, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ d a t e _ t o _ d o u b l e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert a date to double precision for
|
|
* date arithmetic routines.
|
|
*
|
|
**************************************/
|
|
SLONG temp[2], *date;
|
|
|
|
/* If the input descriptor is not in date form, convert it. */
|
|
|
|
if (desc->dsc_dtype == dtype_timestamp)
|
|
date = (SLONG*) desc->dsc_address;
|
|
else if (desc->dsc_dtype == dtype_sql_time) {
|
|
/* Temporarily convert the time to a timestamp for conversion */
|
|
date = temp;
|
|
date[0] = 0;
|
|
date[1] = *(SLONG*) desc->dsc_address;
|
|
}
|
|
else if (desc->dsc_dtype == dtype_sql_date) {
|
|
/* Temporarily convert the date to a timestamp for conversion */
|
|
date = temp;
|
|
date[0] = *(SLONG*) desc->dsc_address;
|
|
date[1] = 0;
|
|
}
|
|
else {
|
|
/* Unknown type - most likely a string. Try to convert it to a
|
|
timestamp -- or die trying (reporting an error).
|
|
Don't worry about users putting a TIME or DATE here - this
|
|
conversion is occuring in really flexible spots - we don't
|
|
want to overdo it. */
|
|
|
|
dsc temp_desc;
|
|
MOVE_CLEAR(&temp_desc, sizeof(temp_desc));
|
|
|
|
temp_desc.dsc_dtype = dtype_timestamp;
|
|
temp_desc.dsc_length = sizeof(temp);
|
|
date = temp;
|
|
temp_desc.dsc_address = (UCHAR*) date;
|
|
CVT_move(desc, &temp_desc, err);
|
|
}
|
|
|
|
/* Instead of returning the calculated double value in the return
|
|
statement, am assigning the value to a local volatile double
|
|
variable and returning that. This is to prevent a specific kind of
|
|
precision error caused on Intel platforms (SCO and Linux) due
|
|
to FPU register being 80 bits long and double being 64 bits long */
|
|
volatile double retval;
|
|
retval =
|
|
date[0] +
|
|
(double) date[1] / (24. * 60. * 60. * ISC_TIME_SECONDS_PRECISION);
|
|
return retval;
|
|
}
|
|
|
|
|
|
void CVT_double_to_date(double real, SLONG fixed[2], FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ d o u b l e _ t o _ d a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert a double precision representation of a date
|
|
* to a fixed point representation. Double is used for
|
|
* date arithmetic.
|
|
*
|
|
**************************************/
|
|
|
|
fixed[0] = (SLONG) real;
|
|
fixed[1] =
|
|
(SLONG) ((real - fixed[0]) * 24. * 60. * 60. *
|
|
ISC_TIME_SECONDS_PRECISION);
|
|
}
|
|
|
|
|
|
double CVT_get_double(const dsc* desc, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ g e t _ d o u b l e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert something arbitrary to a double precision number
|
|
*
|
|
**************************************/
|
|
double value;
|
|
SSHORT scale;
|
|
|
|
switch (desc->dsc_dtype) {
|
|
case dtype_short:
|
|
value = *((SSHORT *) desc->dsc_address);
|
|
break;
|
|
|
|
case dtype_long:
|
|
value = *((SLONG *) desc->dsc_address);
|
|
break;
|
|
|
|
case dtype_quad:
|
|
#ifdef NATIVE_QUAD
|
|
value = *((SQUAD *) desc->dsc_address);
|
|
#else
|
|
value = ((SLONG *) desc->dsc_address)[HIGH_WORD];
|
|
value *= -((double) LONG_MIN_real);
|
|
if (value < 0)
|
|
value -= ((ULONG *) desc->dsc_address)[LOW_WORD];
|
|
else
|
|
value += ((ULONG *) desc->dsc_address)[LOW_WORD];
|
|
#endif
|
|
break;
|
|
|
|
case dtype_int64:
|
|
value = (double) *((SINT64 *) desc->dsc_address);
|
|
break;
|
|
|
|
case dtype_real:
|
|
return *((float*) desc->dsc_address);
|
|
|
|
case DEFAULT_DOUBLE:
|
|
/* a MOVE_FAST is done in case dsc_address is on a platform dependant
|
|
invalid alignment address for doubles */
|
|
MOVE_FAST(desc->dsc_address, &value, sizeof(double));
|
|
return value;
|
|
|
|
#ifdef VMS
|
|
case SPECIAL_DOUBLE:
|
|
return CNVT_TO_DFLT((double*) desc->dsc_address);
|
|
#endif
|
|
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
{
|
|
TEXT buffer[50]; /* must hold ascii of largest double */
|
|
const char* p;
|
|
|
|
const SSHORT length =
|
|
CVT_make_string(desc, ttype_ascii,
|
|
&p,
|
|
(vary*) buffer, sizeof(buffer), err);
|
|
value = 0.0;
|
|
scale = 0;
|
|
SSHORT sign = 0;
|
|
bool digit_seen = false, past_sign = false, fraction = false;
|
|
const char* const end = p + length;
|
|
for (; p < end; p++) {
|
|
if (*p == COMMA)
|
|
continue;
|
|
else if (DIGIT(*p)) {
|
|
digit_seen = true;
|
|
past_sign = true;
|
|
if (fraction)
|
|
scale++;
|
|
value = value * 10. + (*p - '0');
|
|
}
|
|
else if (*p == '.') {
|
|
past_sign = true;
|
|
if (fraction)
|
|
conversion_error(desc, err);
|
|
else
|
|
fraction = true;
|
|
}
|
|
else if (!past_sign && *p == '-') {
|
|
sign = -1;
|
|
past_sign = true;
|
|
}
|
|
else if (!past_sign && *p == '+') {
|
|
sign = 1;
|
|
past_sign = true;
|
|
}
|
|
else if (*p == 'e' || *p == 'E')
|
|
break;
|
|
else if (*p != ' ')
|
|
conversion_error(desc, err);
|
|
}
|
|
|
|
/* If we didn't see a digit then must be a funny string like " ". */
|
|
if (!digit_seen)
|
|
conversion_error(desc, err);
|
|
|
|
if (sign == -1)
|
|
value = -value;
|
|
|
|
/* If there's still something left, there must be an explicit
|
|
exponent */
|
|
|
|
if (p < end) {
|
|
digit_seen = false;
|
|
sign = 0;
|
|
SSHORT exp = 0;
|
|
for (p++; p < end; p++) {
|
|
if (DIGIT(*p)) {
|
|
digit_seen = true;
|
|
exp = exp * 10 + *p - '0';
|
|
|
|
/* The following is a 'safe' test to prevent overflow of
|
|
exp here and of scale below. A more precise test occurs
|
|
later in this routine. */
|
|
|
|
if (exp >= SHORT_LIMIT)
|
|
(*err) (isc_arith_except, 0);
|
|
}
|
|
else if (*p == '-' && !digit_seen && !sign)
|
|
sign = -1;
|
|
else if (*p == '+' && !digit_seen && !sign)
|
|
sign = 1;
|
|
else if (*p != ' ')
|
|
conversion_error(desc, err);
|
|
}
|
|
if (!digit_seen)
|
|
conversion_error(desc, err);
|
|
|
|
if (sign == -1)
|
|
scale += exp;
|
|
else
|
|
scale -= exp;
|
|
}
|
|
|
|
/* if the scale is greater than the power of 10 representable
|
|
in a double number, then something has gone wrong... let
|
|
the user know... */
|
|
|
|
if (ABSOLUT(scale) > DBL_MAX_10_EXP)
|
|
(*err)(isc_arith_except, 0);
|
|
|
|
/*
|
|
Repeated division is a good way to mung the least significant bits
|
|
of your value, so we have replaced this iterative multiplication/division
|
|
by a single multiplication or division, depending on sign(scale).
|
|
if (scale > 0)
|
|
do value /= 10.; while (--scale);
|
|
else if (scale)
|
|
do value *= 10.; while (++scale);
|
|
*/
|
|
if (scale > 0)
|
|
value /= power_of_ten(scale);
|
|
else if (scale < 0)
|
|
value *= power_of_ten(-scale);
|
|
}
|
|
return value;
|
|
|
|
case dtype_timestamp:
|
|
case dtype_sql_date:
|
|
case dtype_sql_time:
|
|
case dtype_blob:
|
|
case dtype_array:
|
|
conversion_error(desc, err);
|
|
break;
|
|
|
|
default:
|
|
(*err) (isc_badblk, 0); /* internal error */
|
|
break;
|
|
}
|
|
|
|
/* Last, but not least, adjust for scale */
|
|
|
|
if ((scale = desc->dsc_scale) == 0)
|
|
return value;
|
|
|
|
/* if the scale is greater than the power of 10 representable
|
|
in a double number, then something has gone wrong... let
|
|
the user know... */
|
|
|
|
if (ABSOLUT(scale) > DBL_MAX_10_EXP)
|
|
(*err) (isc_arith_except, 0);
|
|
|
|
if (scale > 0)
|
|
value *= power_of_ten(scale);
|
|
else if (scale < 0)
|
|
value /= power_of_ten(-scale);
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
SLONG CVT_get_long(const dsc* desc, SSHORT scale, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ g e t _ l o n g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert something arbitrary to a long (32 bit) integer of given
|
|
* scale.
|
|
*
|
|
**************************************/
|
|
SLONG value, high, fraction;
|
|
|
|
double d;
|
|
SINT64 val64;
|
|
USHORT length;
|
|
TEXT buffer[50]; /* long enough to represent largest long in ASCII */
|
|
|
|
/* adjust exact numeric values to same scaling */
|
|
|
|
if (DTYPE_IS_EXACT(desc->dsc_dtype))
|
|
scale -= desc->dsc_scale;
|
|
|
|
const char* p = reinterpret_cast<char*>(desc->dsc_address);
|
|
|
|
switch (desc->dsc_dtype) {
|
|
case dtype_short:
|
|
value = *((SSHORT *) p);
|
|
break;
|
|
|
|
case dtype_long:
|
|
value = *((SLONG *) p);
|
|
break;
|
|
|
|
case dtype_int64:
|
|
val64 = *((SINT64 *) p);
|
|
|
|
/* adjust for scale first, *before* range-checking the value. */
|
|
if (scale > 0) {
|
|
fraction = 0;
|
|
do {
|
|
if (scale == 1)
|
|
fraction = (SLONG) (val64 % 10);
|
|
val64 /= 10;
|
|
} while (--scale);
|
|
if (fraction > 4)
|
|
val64++;
|
|
/*
|
|
* The following 2 lines are correct for platforms where
|
|
* ((-85 / 10 == -8) && (-85 % 10 == -5)). If we port to
|
|
* a platform where ((-85 / 10 == -9) && (-85 % 10 == 5)),
|
|
* we'll have to change this depending on the platform.
|
|
*/
|
|
else if (fraction < -4)
|
|
val64--;
|
|
}
|
|
else if (scale < 0)
|
|
do {
|
|
if ((val64 > INT64_LIMIT) || (val64 < -INT64_LIMIT))
|
|
(*err) (isc_arith_except, 0);
|
|
val64 *= 10;
|
|
} while (++scale);
|
|
|
|
if ((val64 > LONG_MAX_int64) || (val64 < LONG_MIN_int64))
|
|
(*err) (isc_arith_except, 0);
|
|
return (SLONG) val64;
|
|
|
|
case dtype_quad:
|
|
value = ((SLONG *) p)[LOW_WORD];
|
|
high = ((SLONG *) p)[HIGH_WORD];
|
|
if ((value >= 0 && !high) || (value < 0 && high == -1))
|
|
break;
|
|
(*err) (isc_arith_except, 0);
|
|
break;
|
|
|
|
case dtype_real:
|
|
case dtype_double:
|
|
#ifdef VMS
|
|
case dtype_d_float:
|
|
#endif
|
|
if (desc->dsc_dtype == dtype_real)
|
|
d = *((float*) p);
|
|
else if (desc->dsc_dtype == DEFAULT_DOUBLE)
|
|
d = *((double*) p);
|
|
#ifdef VMS
|
|
else
|
|
d = CNVT_TO_DFLT((double*) p);
|
|
#endif
|
|
if (scale > 0)
|
|
do {
|
|
d /= 10.;
|
|
} while (--scale);
|
|
else if (scale < 0)
|
|
do {
|
|
d *= 10.;
|
|
} while (++scale);
|
|
if (d > 0)
|
|
d += 0.5;
|
|
else
|
|
d -= 0.5;
|
|
|
|
/* make sure the cast will succeed - different machines
|
|
do different things if the value is larger than a long
|
|
can hold */
|
|
/* If rounding would yield a legitimate value, permit it */
|
|
|
|
if (d < (double) LONG_MIN_real) {
|
|
if (d > (double) LONG_MIN_real - 1.)
|
|
return LONG_MIN;
|
|
(*err) (isc_arith_except, 0);
|
|
}
|
|
if (d > (double) LONG_MAX_real) {
|
|
if (d < (double) LONG_MAX_real + 1.)
|
|
return LONG_MAX_int;
|
|
(*err) (isc_arith_except, 0);
|
|
}
|
|
return (SLONG) d;
|
|
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
length =
|
|
CVT_make_string(desc, ttype_ascii, &p, (vary*) buffer,
|
|
sizeof(buffer), err);
|
|
scale -= decompose(p, length, dtype_long, &value, err);
|
|
break;
|
|
|
|
case dtype_blob:
|
|
case dtype_sql_date:
|
|
case dtype_sql_time:
|
|
case dtype_timestamp:
|
|
case dtype_array:
|
|
conversion_error(desc, err);
|
|
break;
|
|
|
|
default:
|
|
(*err)(isc_badblk, 0); /* internal error */
|
|
break;
|
|
}
|
|
|
|
/* Last, but not least, adjust for scale */
|
|
|
|
if (scale > 0) {
|
|
if (DTYPE_IS_EXACT(desc->dsc_dtype)) {
|
|
fraction = 0;
|
|
do {
|
|
if (scale == 1)
|
|
fraction = value % 10;
|
|
value /= 10;
|
|
} while (--scale);
|
|
if (fraction > 4)
|
|
value++;
|
|
/*
|
|
* The following 2 lines are correct for platforms where
|
|
* ((-85 / 10 == -8) && (-85 % 10 == -5)). If we port to
|
|
* a platform where ((-85 / 10 == -9) && (-85 % 10 == 5)),
|
|
* we'll have to change this depending on the platform.
|
|
*/
|
|
else if (fraction < -4)
|
|
value--;
|
|
}
|
|
else {
|
|
do {
|
|
value /= 10;
|
|
} while (--scale);
|
|
}
|
|
}
|
|
else if (scale < 0) {
|
|
do {
|
|
if (value > LONG_LIMIT || value < -LONG_LIMIT)
|
|
(*err) (isc_arith_except, 0);
|
|
value *= 10;
|
|
} while (++scale);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
UCHAR CVT_get_numeric(const UCHAR* string,
|
|
const USHORT length,
|
|
SSHORT* scale, double* ptr, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ g e t _ n u m e r i c
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert a numeric literal (string) to its binary value.
|
|
*
|
|
* If the literal contains an exponent or is too large to fit
|
|
* in an int64, return a double, else if the literal is too
|
|
* large to fit in a long, return an int64, else return a long.
|
|
*
|
|
* The return value from the function is set to dtype_double,
|
|
* dtype_int64, or dtype_long depending on the conversion performed.
|
|
* The binary value (long, int64, or double) is stored at the
|
|
* address given by ptr.
|
|
*
|
|
*
|
|
**************************************/
|
|
dsc desc;
|
|
|
|
MOVE_CLEAR(&desc, sizeof(desc));
|
|
desc.dsc_dtype = dtype_text;
|
|
desc.dsc_ttype = ttype_ascii;
|
|
desc.dsc_length = length;
|
|
desc.dsc_address = const_cast<UCHAR*>(string);
|
|
// The above line allows the assignment, but "string" is treated as const
|
|
// for all the purposes here.
|
|
|
|
SINT64 value = 0;
|
|
SSHORT local_scale = 0, sign = 0;
|
|
bool digit_seen = false, fraction = false;
|
|
|
|
const UCHAR* p = string;
|
|
const UCHAR* const end = p + length;
|
|
for (; p < end; p++) {
|
|
if (DIGIT(*p)) {
|
|
digit_seen = true;
|
|
|
|
/* Before computing the next value, make sure there will be
|
|
no overflow. Trying to detect overflow after the fact is
|
|
tricky: the value doesn't always become negative after an
|
|
overflow! */
|
|
|
|
if (value >= NUMERIC_LIMIT) {
|
|
/* possibility of an overflow */
|
|
if (value > NUMERIC_LIMIT)
|
|
break;
|
|
else if (((*p > '8') && (sign == -1))
|
|
|| ((*p > '7') && (sign != -1)))
|
|
break;
|
|
}
|
|
|
|
/* Force the subtraction to be performed before the addition,
|
|
thus preventing a possible signed arithmetic overflow. */
|
|
value = value * 10 + (*p - '0');
|
|
if (fraction)
|
|
--local_scale;
|
|
}
|
|
else if (*p == '.') {
|
|
if (fraction)
|
|
conversion_error(&desc, err);
|
|
else
|
|
fraction = true;
|
|
}
|
|
else if (*p == '-' && !digit_seen && !sign && !fraction)
|
|
sign = -1;
|
|
else if (*p == '+' && !digit_seen && !sign && !fraction)
|
|
sign = 1;
|
|
else if (*p == 'e' || *p == 'E')
|
|
break;
|
|
else if (*p != ' ')
|
|
conversion_error(&desc, err);
|
|
}
|
|
|
|
if (!digit_seen)
|
|
conversion_error(&desc, err);
|
|
|
|
if ((p < end) || /* there is an exponent */
|
|
((value < 0) && (sign != -1))) /* MAX_SINT64+1 wrapped around */
|
|
{
|
|
/* convert to double */
|
|
*ptr = CVT_get_double(&desc, err);
|
|
return dtype_double;
|
|
}
|
|
|
|
*scale = local_scale;
|
|
|
|
/* The literal has already been converted to a 64-bit integer: return
|
|
a long if the value fits into a long, else return an int64. */
|
|
|
|
if ((value <= LONG_MAX_int64) && (value >= 0)) {
|
|
*(SLONG *) ptr = (SLONG) ((sign == -1) ? -value : value);
|
|
return dtype_long;
|
|
}
|
|
else if ((sign == -1) && (-value == LONG_MIN_int64)) {
|
|
*(SLONG *) ptr = LONG_MIN;
|
|
return dtype_long;
|
|
}
|
|
else {
|
|
/* Either MAX_SLONG < value <= MAX_SINT64, or
|
|
((value == MIN_SINT64) && (sign == -1)).
|
|
In the first case, the number can be negated, while in the second
|
|
negating the value will not change it on a 2s-complement system. */
|
|
|
|
*(SINT64 *) ptr = ((sign == -1) ? -value : value);
|
|
return dtype_int64;
|
|
}
|
|
}
|
|
|
|
|
|
SQUAD CVT_get_quad(const dsc* desc, SSHORT scale, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ g e t _ q u a d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert something arbitrary to a quad (64 bit) integer of given
|
|
* scale.
|
|
*
|
|
**************************************/
|
|
SQUAD value;
|
|
#ifdef NATIVE_QUAD
|
|
SLONG fraction;
|
|
#endif
|
|
double d;
|
|
USHORT length;
|
|
TEXT buffer[50]; /* long enough to represent largest quad in ASCII */
|
|
|
|
/* adjust exact numeric values to same scaling */
|
|
|
|
if (DTYPE_IS_EXACT(desc->dsc_dtype))
|
|
scale -= desc->dsc_scale;
|
|
|
|
const char* p = reinterpret_cast<char*>(desc->dsc_address);
|
|
|
|
switch (desc->dsc_dtype) {
|
|
case dtype_short:
|
|
((SLONG *) & value)[LOW_WORD] = *((SSHORT *) p);
|
|
((SLONG *) & value)[HIGH_WORD] = (*((SSHORT *) p) < 0) ? -1 : 0;
|
|
break;
|
|
|
|
case dtype_long:
|
|
((SLONG *) & value)[LOW_WORD] = *((SLONG *) p);
|
|
((SLONG *) & value)[HIGH_WORD] = (*((SLONG *) p) < 0) ? -1 : 0;
|
|
break;
|
|
|
|
case dtype_quad:
|
|
value = *((SQUAD *) p);
|
|
break;
|
|
|
|
case dtype_int64:
|
|
((SLONG *) & value)[LOW_WORD] = (SLONG) (*((SINT64 *) p) & 0xffffffff);
|
|
((SLONG *) & value)[HIGH_WORD] = (SLONG) (*((SINT64 *) p) >> 32);
|
|
break;
|
|
|
|
case dtype_real:
|
|
case dtype_double:
|
|
#ifdef VMS
|
|
case dtype_d_float:
|
|
#endif
|
|
if (desc->dsc_dtype == dtype_real)
|
|
d = *((float*) p);
|
|
else if (desc->dsc_dtype == DEFAULT_DOUBLE)
|
|
d = *((double*) p);
|
|
#ifdef VMS
|
|
else
|
|
d = CNVT_TO_DFLT((double*) p);
|
|
#endif
|
|
if (scale > 0)
|
|
do {
|
|
d /= 10.;
|
|
} while (--scale);
|
|
else if (scale < 0)
|
|
do {
|
|
d *= 10.;
|
|
} while (++scale);
|
|
if (d > 0)
|
|
d += 0.5;
|
|
else
|
|
d -= 0.5;
|
|
|
|
/* make sure the cast will succeed - different machines
|
|
do different things if the value is larger than a quad
|
|
can hold */
|
|
|
|
if (d < (double) QUAD_MIN_real || (double) QUAD_MAX_real < d) {
|
|
/* If rounding would yield a legitimate value, permit it */
|
|
|
|
if (d > (double) QUAD_MIN_real - 1.)
|
|
return QUAD_MIN_int;
|
|
else if (d < (double) QUAD_MAX_real + 1.)
|
|
return QUAD_MAX_int;
|
|
(*err)(isc_arith_except, 0);
|
|
}
|
|
return QUAD_FROM_DOUBLE(d, err);
|
|
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
length =
|
|
CVT_make_string(desc, ttype_ascii, &p, (vary*) buffer,
|
|
sizeof(buffer), err);
|
|
scale -= decompose(p, length, dtype_quad, &value.high, err);
|
|
break;
|
|
|
|
case dtype_blob:
|
|
case dtype_sql_date:
|
|
case dtype_sql_time:
|
|
case dtype_timestamp:
|
|
case dtype_array:
|
|
conversion_error(desc, err);
|
|
break;
|
|
|
|
default:
|
|
(*err)(isc_badblk, 0); /* internal error */
|
|
break;
|
|
}
|
|
|
|
/* Last, but not least, adjust for scale */
|
|
|
|
if (scale == 0)
|
|
return value;
|
|
|
|
#ifndef NATIVE_QUAD
|
|
(*err)(isc_badblk, 0); /* internal error */
|
|
#else
|
|
if (scale > 0) {
|
|
if (desc->dsc_dtype == dtype_short ||
|
|
desc->dsc_dtype == dtype_long || desc->dsc_dtype == dtype_quad)
|
|
{
|
|
fraction = 0;
|
|
do {
|
|
if (scale == 1)
|
|
fraction = value % 10;
|
|
value /= 10;
|
|
} while (--scale);
|
|
if (fraction > 4)
|
|
value++;
|
|
/*
|
|
* The following 2 lines are correct for platforms where
|
|
* ((-85 / 10 == -8) && (-85 % 10 == -5)). If we port to
|
|
* a platform where ((-85 / 10 == -9) && (-85 % 10 == 5)),
|
|
* we'll have to change this depending on the platform.
|
|
*/
|
|
else if (fraction < -4)
|
|
value--;
|
|
}
|
|
else {
|
|
do {
|
|
value /= 10;
|
|
} while (--scale);
|
|
}
|
|
}
|
|
else {
|
|
do {
|
|
if (value > QUAD_LIMIT || value < -QUAD_LIMIT)
|
|
(*err) (isc_arith_except, 0);
|
|
value *= 10;
|
|
} while (++scale);
|
|
}
|
|
#endif
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
SINT64 CVT_get_int64(const dsc* desc, SSHORT scale, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ g e t _ i n t 6 4
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert something arbitrary to an SINT64 (64 bit integer)
|
|
* of given scale.
|
|
*
|
|
**************************************/
|
|
SINT64 value;
|
|
SLONG fraction;
|
|
double d;
|
|
USHORT length;
|
|
TEXT buffer[50]; /* long enough to represent largest SINT64 in ASCII */
|
|
|
|
/* adjust exact numeric values to same scaling */
|
|
|
|
if (DTYPE_IS_EXACT(desc->dsc_dtype))
|
|
scale -= desc->dsc_scale;
|
|
|
|
const char* p = reinterpret_cast<char*>(desc->dsc_address);
|
|
|
|
switch (desc->dsc_dtype) {
|
|
case dtype_short:
|
|
value = *((SSHORT *) p);
|
|
break;
|
|
|
|
case dtype_long:
|
|
value = *((SLONG *) p);
|
|
break;
|
|
|
|
case dtype_int64:
|
|
value = *((SINT64 *) p);
|
|
break;
|
|
|
|
case dtype_quad:
|
|
value = (((SINT64) ((SLONG *) p)[HIGH_WORD]) << 32) +
|
|
(((ULONG *) p)[LOW_WORD]);
|
|
break;
|
|
|
|
case dtype_real:
|
|
case dtype_double:
|
|
#ifdef VMS
|
|
case dtype_d_float:
|
|
#endif
|
|
if (desc->dsc_dtype == dtype_real)
|
|
d = *((float*) p);
|
|
else if (desc->dsc_dtype == DEFAULT_DOUBLE)
|
|
d = *((double*) p);
|
|
#ifdef VMS
|
|
else
|
|
d = CNVT_TO_DFLT((double*) p);
|
|
#endif
|
|
if (scale > 0)
|
|
do {
|
|
d /= 10.;
|
|
} while (--scale);
|
|
else if (scale < 0)
|
|
do {
|
|
d *= 10.;
|
|
} while (++scale);
|
|
if (d > 0)
|
|
d += 0.5;
|
|
else
|
|
d -= 0.5;
|
|
|
|
/* make sure the cast will succeed - different machines
|
|
do different things if the value is larger than a quad
|
|
can hold.
|
|
|
|
Note that adding or subtracting 0.5, as we do in CVT_get_long,
|
|
will never allow the rounded value to fit into an int64,
|
|
because when the double value is too large in magnitude
|
|
to fit, 0.5 is less than half of the least significant bit
|
|
of the significant (sometimes miscalled "mantissa") of the
|
|
double, and thus will have no effect on the sum. */
|
|
|
|
if (d < (double) QUAD_MIN_real || (double) QUAD_MAX_real < d)
|
|
(*err) (isc_arith_except, 0);
|
|
|
|
return (SINT64) d;
|
|
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
length =
|
|
CVT_make_string(desc, ttype_ascii, &p, (vary*) buffer,
|
|
sizeof(buffer), err);
|
|
scale -= decompose(p, length, dtype_int64, (SLONG *) & value, err);
|
|
break;
|
|
|
|
case dtype_blob:
|
|
case dtype_sql_date:
|
|
case dtype_sql_time:
|
|
case dtype_timestamp:
|
|
case dtype_array:
|
|
conversion_error(desc, err);
|
|
break;
|
|
|
|
default:
|
|
(*err) (isc_badblk, 0); /* internal error */
|
|
break;
|
|
}
|
|
|
|
/* Last, but not least, adjust for scale */
|
|
|
|
if (scale > 0) {
|
|
if (desc->dsc_dtype == dtype_short ||
|
|
desc->dsc_dtype == dtype_long || desc->dsc_dtype == dtype_int64)
|
|
{
|
|
fraction = 0;
|
|
do {
|
|
if (scale == 1)
|
|
fraction = (SLONG) (value % 10);
|
|
value /= 10;
|
|
} while (--scale);
|
|
if (fraction > 4)
|
|
value++;
|
|
/*
|
|
* The following 2 lines are correct for platforms where
|
|
* ((-85 / 10 == -8) && (-85 % 10 == -5)). If we port to
|
|
* a platform where ((-85 / 10 == -9) && (-85 % 10 == 5)),
|
|
* we'll have to change this depending on the platform.
|
|
*/
|
|
else if (fraction < -4)
|
|
value--;
|
|
}
|
|
else {
|
|
do {
|
|
value /= 10;
|
|
} while (--scale);
|
|
}
|
|
}
|
|
else if (scale < 0) {
|
|
do {
|
|
if (value > INT64_LIMIT || value < -INT64_LIMIT)
|
|
(*err) (isc_arith_except, 0);
|
|
value *= 10;
|
|
} while (++scale);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
USHORT CVT_get_string_ptr(const dsc* desc,
|
|
USHORT* ttype,
|
|
UCHAR** address,
|
|
vary* temp, USHORT length, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ g e t _ s t r i n g _ p t r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get address and length of string, converting the value to
|
|
* string, if necessary. The caller must provide a sufficiently
|
|
* large temporary. The address of the resultant string is returned
|
|
* by reference. Get_string returns the length of the string (in bytes).
|
|
*
|
|
* The text-type of the string is returned in ttype.
|
|
*
|
|
* Note: If the descriptor is known to be a string type, the fourth
|
|
* argument (temp buffer) may be NULL.
|
|
*
|
|
* If input (desc) is already a string, the output pointers
|
|
* point to the data of the same text_type. If (desc) is not
|
|
* already a string, output pointers point to ttype_ascii.
|
|
*
|
|
**************************************/
|
|
fb_assert(desc != NULL);
|
|
fb_assert(ttype != NULL);
|
|
fb_assert(address != NULL);
|
|
fb_assert(err != NULL);
|
|
fb_assert((((temp != NULL) && (length > 0))
|
|
|| (desc->dsc_dtype == dtype_text)
|
|
|| (desc->dsc_dtype == dtype_cstring)
|
|
|| (desc->dsc_dtype == dtype_varying)));
|
|
|
|
/* If the value is already a string (fixed or varying), just return
|
|
the address and length. */
|
|
|
|
if (desc->dsc_dtype <= dtype_any_text) {
|
|
*address = desc->dsc_address;
|
|
*ttype = INTL_TTYPE(desc);
|
|
if (desc->dsc_dtype == dtype_text)
|
|
return desc->dsc_length;
|
|
if (desc->dsc_dtype == dtype_cstring)
|
|
return MIN((USHORT) strlen((char *) desc->dsc_address),
|
|
desc->dsc_length - 1);
|
|
if (desc->dsc_dtype == dtype_varying) {
|
|
vary* varying = (vary*) desc->dsc_address;
|
|
*address = reinterpret_cast<UCHAR*>(varying->vary_string);
|
|
return MIN(varying->vary_length,
|
|
(USHORT) (desc->dsc_length - sizeof(USHORT)));
|
|
}
|
|
}
|
|
|
|
/* No luck -- convert value to varying string. */
|
|
|
|
dsc temp_desc;
|
|
MOVE_CLEAR(&temp_desc, sizeof(temp_desc));
|
|
temp_desc.dsc_length = length;
|
|
temp_desc.dsc_address = (UCHAR *) temp;
|
|
INTL_ASSIGN_TTYPE(&temp_desc, ttype_ascii);
|
|
temp_desc.dsc_dtype = dtype_varying;
|
|
CVT_move(desc, &temp_desc, err);
|
|
*address = reinterpret_cast<UCHAR*>(temp->vary_string);
|
|
*ttype = INTL_TTYPE(&temp_desc);
|
|
|
|
return temp->vary_length;
|
|
}
|
|
|
|
|
|
GDS_DATE CVT_get_sql_date(const dsc* desc, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ g e t _ s q l _ d a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert something arbitrary to a SQL date value
|
|
*
|
|
**************************************/
|
|
if (desc->dsc_dtype == dtype_sql_date)
|
|
return *((GDS_DATE *) desc->dsc_address);
|
|
|
|
DSC temp_desc;
|
|
GDS_DATE value;
|
|
memset(&temp_desc, 0, sizeof(temp_desc));
|
|
temp_desc.dsc_dtype = dtype_sql_date;
|
|
temp_desc.dsc_address = (UCHAR *) &value;
|
|
CVT_move(desc, &temp_desc, err);
|
|
return value;
|
|
}
|
|
|
|
|
|
GDS_TIME CVT_get_sql_time(const dsc* desc, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ g e t _ s q l _ t i m e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert something arbitrary to a SQL time value
|
|
*
|
|
**************************************/
|
|
if (desc->dsc_dtype == dtype_sql_time)
|
|
return *((GDS_TIME *) desc->dsc_address);
|
|
|
|
DSC temp_desc;
|
|
GDS_TIME value;
|
|
memset(&temp_desc, 0, sizeof(temp_desc));
|
|
temp_desc.dsc_dtype = dtype_sql_time;
|
|
temp_desc.dsc_address = (UCHAR *) &value;
|
|
CVT_move(desc, &temp_desc, err);
|
|
return value;
|
|
}
|
|
|
|
|
|
GDS_TIMESTAMP CVT_get_timestamp(const dsc* desc, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ g e t _ t i m e s t a m p
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert something arbitrary to a SQL timestamp
|
|
*
|
|
**************************************/
|
|
if (desc->dsc_dtype == dtype_timestamp)
|
|
return *((GDS_TIMESTAMP *) desc->dsc_address);
|
|
|
|
DSC temp_desc;
|
|
GDS_TIMESTAMP value;
|
|
memset(&temp_desc, 0, sizeof(temp_desc));
|
|
temp_desc.dsc_dtype = dtype_timestamp;
|
|
temp_desc.dsc_address = (UCHAR *) &value;
|
|
CVT_move(desc, &temp_desc, err);
|
|
return value;
|
|
}
|
|
|
|
|
|
USHORT CVT_make_string(const dsc* desc,
|
|
USHORT to_interp,
|
|
const char** address,
|
|
vary* temp,
|
|
USHORT length,
|
|
FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ m a k e _ s t r i n g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert the data from the desc to a string in the specified interp.
|
|
* The pointer to this string is returned in address.
|
|
*
|
|
**************************************/
|
|
fb_assert(desc != NULL);
|
|
fb_assert(address != NULL);
|
|
fb_assert(err != NULL);
|
|
fb_assert((((temp != NULL) && (length > 0))
|
|
|| ((INTL_TTYPE(desc) <= dtype_any_text)
|
|
&& (INTL_TTYPE(desc) == to_interp))));
|
|
|
|
if (desc->dsc_dtype <= dtype_any_text && INTL_TTYPE(desc) == to_interp) {
|
|
*address = reinterpret_cast<char*>(desc->dsc_address);
|
|
const USHORT from_len = desc->dsc_length;
|
|
if (desc->dsc_dtype == dtype_text)
|
|
return from_len;
|
|
if (desc->dsc_dtype == dtype_cstring)
|
|
return MIN((USHORT) strlen((char *) desc->dsc_address),
|
|
from_len - 1);
|
|
if (desc->dsc_dtype == dtype_varying) {
|
|
vary* varying = (vary*) desc->dsc_address;
|
|
*address = varying->vary_string;
|
|
return MIN(varying->vary_length, (USHORT) (from_len - sizeof(USHORT)));
|
|
}
|
|
}
|
|
|
|
/* Not string data, then -- convert value to varying string. */
|
|
|
|
dsc temp_desc;
|
|
MOVE_CLEAR(&temp_desc, sizeof(temp_desc));
|
|
temp_desc.dsc_length = length;
|
|
temp_desc.dsc_address = (UCHAR *) temp;
|
|
INTL_ASSIGN_TTYPE(&temp_desc, to_interp);
|
|
temp_desc.dsc_dtype = dtype_varying;
|
|
CVT_move(desc, &temp_desc, err);
|
|
*address = temp->vary_string;
|
|
|
|
return temp->vary_length;
|
|
}
|
|
|
|
|
|
void CVT_move(const dsc* from, dsc* to, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C V T _ m o v e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Move (and possible convert) something to something else.
|
|
*
|
|
**************************************/
|
|
SSHORT fill;
|
|
SLONG l;
|
|
UCHAR *ptr;
|
|
USHORT strtype;
|
|
#if !defined(REQUESTER) && !defined(SUPERCLIENT)
|
|
CHARSET_ID charset1, charset2;
|
|
#endif
|
|
UCHAR fill_char;
|
|
|
|
SLONG length = from->dsc_length;
|
|
UCHAR* p = to->dsc_address;
|
|
const UCHAR* q = from->dsc_address;
|
|
|
|
/* If the datatypes and lengths are identical, just move the
|
|
stuff byte by byte. Although this may seem slower than
|
|
optimal, it would cost more to find the fast move than the
|
|
fast move would gain. */
|
|
|
|
if (DSC_EQUIV(from, to)) {
|
|
if (length) {
|
|
MOVE_FAST(q, p, length);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Do data type by data type conversions. Not all are supported,
|
|
and some will drop out for additional handling. */
|
|
|
|
switch (to->dsc_dtype) {
|
|
case dtype_timestamp:
|
|
switch (from->dsc_dtype) {
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
{
|
|
GDS_TIMESTAMP date;
|
|
tm times;
|
|
|
|
string_to_datetime(from, &date, expect_timestamp, err);
|
|
|
|
isc_decode_timestamp(&date, ×);
|
|
if ((times.tm_year + 1900) < MIN_YEAR
|
|
|| (times.tm_year) + 1900 > MAX_YEAR)
|
|
{
|
|
(*err) (isc_date_range_exceeded, 0);
|
|
}
|
|
|
|
((GDS_TIMESTAMP *) to->dsc_address)->timestamp_date = date.timestamp_date;
|
|
((GDS_TIMESTAMP *) to->dsc_address)->timestamp_time = date.timestamp_time;
|
|
}
|
|
return;
|
|
|
|
case dtype_sql_date:
|
|
((GDS_TIMESTAMP *) (to->dsc_address))->timestamp_date =
|
|
*(GDS_DATE *) (from->dsc_address);
|
|
((GDS_TIMESTAMP *) (to->dsc_address))->timestamp_time = 0;
|
|
return;
|
|
|
|
case dtype_sql_time:
|
|
((GDS_TIMESTAMP*) (to->dsc_address))->timestamp_date = 0;
|
|
((GDS_TIMESTAMP*) (to->dsc_address))->timestamp_time =
|
|
*(GDS_TIME*) (from->dsc_address);
|
|
|
|
/* Per SQL Specs, we need to set the DATE
|
|
portion to the current date */
|
|
{
|
|
time_t clock;
|
|
/** Cannot call GET_THREAD_DATA because that macro calls
|
|
BUGCHECK i.e. ERR_bugcheck() which is not part of
|
|
client library **/
|
|
thread_db* tdbb = PLATFORM_GET_THREAD_DATA;
|
|
|
|
/* If we're in the engine, then the THDD type must
|
|
be a THDD_TYPE_TDBB. So, if we're in the engine
|
|
and have a request, pull the effective date out
|
|
of the request timestamp.
|
|
Otherwise, take the CURRENT date to populate the
|
|
date portion of the timestamp */
|
|
|
|
if ((tdbb) &&
|
|
(((THDD) tdbb)->thdd_type == THDD_TYPE_TDBB) &&
|
|
tdbb->tdbb_request)
|
|
{
|
|
if (tdbb->tdbb_request->req_timestamp)
|
|
{
|
|
clock = tdbb->tdbb_request->req_timestamp;
|
|
}
|
|
else
|
|
{
|
|
// all requests should have a timestamp
|
|
fb_assert(false);
|
|
clock = time(NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
clock = time(NULL);
|
|
}
|
|
const tm* times = localtime(&clock);
|
|
if (!times)
|
|
{
|
|
(*err)(isc_date_range_exceeded, 0);
|
|
}
|
|
GDS_TIMESTAMP enc_times;
|
|
isc_encode_timestamp(times, &enc_times);
|
|
((GDS_TIMESTAMP*) (to->dsc_address))->timestamp_date =
|
|
enc_times.timestamp_date;
|
|
}
|
|
return;
|
|
|
|
default:
|
|
fb_assert(FALSE); /* Fall into ... */
|
|
case dtype_short:
|
|
case dtype_long:
|
|
case dtype_int64:
|
|
case dtype_quad:
|
|
case dtype_real:
|
|
case dtype_double:
|
|
#ifdef VMS
|
|
case dtype_d_float:
|
|
#endif
|
|
conversion_error(from, err);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case dtype_sql_date:
|
|
switch (from->dsc_dtype) {
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
{
|
|
GDS_TIMESTAMP date;
|
|
tm times;
|
|
|
|
string_to_datetime(from, &date, expect_sql_date, err);
|
|
isc_decode_timestamp(&date, ×);
|
|
if ((times.tm_year + 1900) < MIN_YEAR
|
|
|| (times.tm_year) + 1900 > MAX_YEAR)
|
|
{
|
|
(*err) (isc_date_range_exceeded, 0);
|
|
}
|
|
|
|
*((GDS_DATE *) to->dsc_address) = date.timestamp_date;
|
|
}
|
|
return;
|
|
|
|
case dtype_timestamp:
|
|
{
|
|
GDS_TIMESTAMP new_date;
|
|
tm times;
|
|
|
|
new_date.timestamp_date =
|
|
((GDS_TIMESTAMP *) from->dsc_address)->timestamp_date;
|
|
new_date.timestamp_time = 0;
|
|
|
|
isc_decode_timestamp(&new_date, ×);
|
|
if ((times.tm_year + 1900) < MIN_YEAR
|
|
|| (times.tm_year) + 1900 > MAX_YEAR)
|
|
{
|
|
(*err) (isc_date_range_exceeded, 0);
|
|
}
|
|
|
|
*((GDS_DATE *) to->dsc_address) =
|
|
((GDS_TIMESTAMP *) from->dsc_address)->timestamp_date;
|
|
}
|
|
return;
|
|
|
|
default:
|
|
fb_assert(FALSE); /* Fall into ... */
|
|
case dtype_sql_time:
|
|
case dtype_short:
|
|
case dtype_long:
|
|
case dtype_int64:
|
|
case dtype_quad:
|
|
case dtype_real:
|
|
case dtype_double:
|
|
#ifdef VMS
|
|
case dtype_d_float:
|
|
#endif
|
|
conversion_error(from, err);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case dtype_sql_time:
|
|
switch (from->dsc_dtype) {
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
{
|
|
GDS_TIMESTAMP date;
|
|
string_to_datetime(from, &date, expect_sql_time, err);
|
|
*(GDS_TIME *) to->dsc_address = date.timestamp_time;
|
|
}
|
|
return;
|
|
|
|
case dtype_timestamp:
|
|
*((GDS_TIME *) to->dsc_address) =
|
|
((GDS_TIMESTAMP *) from->dsc_address)->timestamp_time;
|
|
return;
|
|
|
|
default:
|
|
fb_assert(FALSE); /* Fall into ... */
|
|
case dtype_sql_date:
|
|
case dtype_short:
|
|
case dtype_long:
|
|
case dtype_int64:
|
|
case dtype_quad:
|
|
case dtype_real:
|
|
case dtype_double:
|
|
#ifdef VMS
|
|
case dtype_d_float:
|
|
#endif
|
|
conversion_error(from, err);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case dtype_text:
|
|
case dtype_cstring:
|
|
case dtype_varying:
|
|
switch (from->dsc_dtype) {
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
/* If we are within the engine, INTL_convert_string
|
|
* will convert the string between character sets
|
|
* (or die trying).
|
|
* This module, however, can be called from outside
|
|
* the engine (for instance, moving values around for
|
|
* DSQL).
|
|
* In that event, we'll move the values if we think
|
|
* they are compatible text types, otherwise fail.
|
|
* eg: Simple cases can be handled here (no
|
|
* character set conversion).
|
|
*
|
|
* a charset type binary is compatible with all other types.
|
|
* if a charset involved is ttype_dynamic, we must look up
|
|
* the charset of the attachment (only if we are in the
|
|
* engine). If we are outside the engine, the
|
|
* assume that the engine has converted the values
|
|
* previously in the request.
|
|
*
|
|
* Even within the engine, not calling INTL_convert_string
|
|
* unless really required is a good optimization.
|
|
*/
|
|
|
|
#ifndef REQUESTER
|
|
#ifndef SUPERCLIENT
|
|
if ((INTL_TTYPE(from) == ttype_dynamic) &&
|
|
(err == ERR_post))
|
|
charset1 = INTL_charset(NULL, INTL_TTYPE(from), err);
|
|
else
|
|
charset1 = INTL_TTYPE(from);
|
|
|
|
if ((INTL_TTYPE(to) == ttype_dynamic) &&
|
|
(err == ERR_post))
|
|
charset2 = INTL_charset(NULL, INTL_TTYPE(to), err);
|
|
else
|
|
charset2 = INTL_TTYPE(to);
|
|
|
|
/* The charset[12] can be ttype_dynamic only if we are
|
|
outside the engine. Within the engine INTL_charset
|
|
would have set it to the ttype of the attachment */
|
|
|
|
if ((charset1 != charset2) &&
|
|
(charset2 != ttype_none) &&
|
|
(charset1 != ttype_binary) &&
|
|
(charset2 != ttype_binary) &&
|
|
(charset1 != ttype_dynamic) && (charset2 != ttype_dynamic)) {
|
|
if (err == ERR_post) {
|
|
INTL_convert_string(to, from, err);
|
|
return;
|
|
}
|
|
else
|
|
#endif
|
|
#endif
|
|
(*err) (isc_arith_except, 0);
|
|
#ifndef REQUESTER
|
|
#ifndef SUPERCLIENT
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
length = l =
|
|
CVT_get_string_ptr(from, &strtype, &ptr, NULL, 0, err);
|
|
q = ptr;
|
|
switch (to->dsc_dtype) {
|
|
case dtype_text:
|
|
length = MIN(length, to->dsc_length);
|
|
l -= length;
|
|
/* TMN: Here we should really have the following fb_assert */
|
|
/* fb_assert((to->dsc_length - length) <= MAX_SSHORT); */
|
|
fill = (SSHORT) (to->dsc_length - length);
|
|
|
|
CVT_COPY_BUFF(q, p, length);
|
|
if (fill > 0) {
|
|
#ifndef REQUESTER
|
|
#ifndef SUPERCLIENT
|
|
if (charset2 == ttype_binary)
|
|
fill_char = 0x00;
|
|
else
|
|
#endif
|
|
#endif
|
|
fill_char = ASCII_SPACE;
|
|
do {
|
|
*p++ = fill_char;
|
|
} while (--fill);
|
|
/* Note: above is correct only for narrow
|
|
and multi-byte character sets which
|
|
use ASCII for the SPACE character. */
|
|
}
|
|
break;
|
|
|
|
case dtype_cstring:
|
|
/* Note: Following is only correct for narrow and
|
|
multibyte character sets which use a zero
|
|
byte to represent end-of-string */
|
|
|
|
length = MIN(length, to->dsc_length - 1);
|
|
l -= length;
|
|
CVT_COPY_BUFF(q, p, length);
|
|
*p = 0;
|
|
break;
|
|
|
|
case dtype_varying:
|
|
length =
|
|
MIN(length, (SLONG) (to->dsc_length - sizeof(USHORT)));
|
|
l -= length;
|
|
/* TMN: Here we should really have the following fb_assert */
|
|
/* fb_assert(length <= MAX_USHORT); */
|
|
((vary*) p)->vary_length = (USHORT) length;
|
|
p = reinterpret_cast<UCHAR*>(((vary*) p)->vary_string);
|
|
CVT_COPY_BUFF(q, p, length);
|
|
break;
|
|
}
|
|
|
|
if (l) {
|
|
/* scan the truncated string to ensure only spaces lost */
|
|
/* Note: it is correct only for narrow and multi-byte
|
|
character sets which use ASCII for the SPACE
|
|
character. */
|
|
|
|
do {
|
|
if (*q++ != ASCII_SPACE)
|
|
(*err) (isc_arith_except, 0);
|
|
} while (--l);
|
|
}
|
|
return;
|
|
|
|
case dtype_short:
|
|
case dtype_long:
|
|
case dtype_int64:
|
|
case dtype_quad:
|
|
integer_to_text(from, to, err);
|
|
return;
|
|
|
|
case dtype_real:
|
|
case dtype_double:
|
|
#ifdef VMS
|
|
case dtype_d_float:
|
|
#endif
|
|
float_to_text(from, to, err);
|
|
return;
|
|
|
|
case dtype_sql_date:
|
|
case dtype_sql_time:
|
|
case dtype_timestamp:
|
|
datetime_to_text(from, to, err);
|
|
return;
|
|
|
|
default:
|
|
fb_assert(FALSE); /* Fall into ... */
|
|
case dtype_blob:
|
|
conversion_error(from, err);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case dtype_blob:
|
|
case dtype_array:
|
|
if (from->dsc_dtype == dtype_quad) {
|
|
((SLONG *) p)[0] = ((SLONG *) q)[0];
|
|
((SLONG *) p)[1] = ((SLONG *) q)[1];
|
|
return;
|
|
}
|
|
|
|
if (to->dsc_dtype != from->dsc_dtype)
|
|
(*err) (isc_wish_list, isc_arg_gds, isc_blobnotsup,
|
|
isc_arg_string, "move", 0);
|
|
|
|
/* Note: DSC_EQUIV failed above as the blob sub_types were different,
|
|
* or their character sets were different. In V4 we aren't trying
|
|
* to provide blob type integrity, so we just assign the blob id
|
|
*/
|
|
|
|
/* Move blob_id byte-by-byte as that's the way it was done before */
|
|
CVT_COPY_BUFF(q, p, length);
|
|
return;
|
|
|
|
case dtype_short:
|
|
l = CVT_get_long(from, (SSHORT) to->dsc_scale, err);
|
|
/* TMN: Here we should really have the following fb_assert */
|
|
/* fb_assert(l <= MAX_SSHORT); */
|
|
*(SSHORT *) p = (SSHORT) l;
|
|
if (*(SSHORT *) p != l)
|
|
(*err) (isc_arith_except, 0);
|
|
return;
|
|
|
|
case dtype_long:
|
|
*(SLONG *) p = CVT_get_long(from, (SSHORT) to->dsc_scale, err);
|
|
return;
|
|
|
|
case dtype_int64:
|
|
*(SINT64 *) p = CVT_get_int64(from, (SSHORT) to->dsc_scale, err);
|
|
return;
|
|
|
|
case dtype_quad:
|
|
if (from->dsc_dtype == dtype_blob || from->dsc_dtype == dtype_array) {
|
|
((SLONG *) p)[0] = ((SLONG *) q)[0];
|
|
((SLONG *) p)[1] = ((SLONG *) q)[1];
|
|
return;
|
|
}
|
|
*(SQUAD *) p = CVT_get_quad(from, (SSHORT) to->dsc_scale, err);
|
|
return;
|
|
|
|
case dtype_real:
|
|
{
|
|
double d_value;
|
|
d_value = CVT_get_double(from, err);
|
|
if (ABSOLUT(d_value) > FLOAT_MAX)
|
|
(*err) (isc_arith_except, 0);
|
|
*(float*) p = (float) d_value;
|
|
}
|
|
return;
|
|
|
|
case DEFAULT_DOUBLE:
|
|
*(double*) p = CVT_get_double(from, err);
|
|
return;
|
|
|
|
#ifdef VMS
|
|
case SPECIAL_DOUBLE:
|
|
*(double*) p = CVT_get_double(from, err);
|
|
*(double*) p = CNVT_FROM_DFLT((double*) p);
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
if (from->dsc_dtype == dtype_array || from->dsc_dtype == dtype_blob)
|
|
{
|
|
(*err) (isc_wish_list, isc_arg_gds, isc_blobnotsup,
|
|
isc_arg_string, "move", 0);
|
|
}
|
|
|
|
(*err) (isc_badblk, 0); /* internal error */
|
|
}
|
|
|
|
|
|
static void conversion_error(const dsc* desc, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c o n v e r s i o n _ e r r o r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* A data conversion error occurred. Complain.
|
|
*
|
|
**************************************/
|
|
const char* p;
|
|
TEXT s[40];
|
|
|
|
if (desc->dsc_dtype == dtype_blob)
|
|
p = "BLOB";
|
|
else if (desc->dsc_dtype == dtype_array)
|
|
p = "ARRAY";
|
|
else {
|
|
const char* string;
|
|
const USHORT length =
|
|
CVT_make_string(desc, ttype_ascii, &string,
|
|
(vary*) s, sizeof(s), err);
|
|
#if (defined REQUESTER || defined SUPERCLIENT)
|
|
p = error_string(string, length);
|
|
#else
|
|
p = ERR_string(string, length);
|
|
#endif
|
|
}
|
|
|
|
(*err)(isc_convert_error, isc_arg_string, p, 0);
|
|
}
|
|
|
|
|
|
static void datetime_to_text(const dsc* from, dsc* to, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d a t e t i m e _ t o _ t e x t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert a timestamp, date or time value to text.
|
|
*
|
|
**************************************/
|
|
thread_db* tdbb = NULL;
|
|
bool version4 = true;
|
|
|
|
fb_assert(DTYPE_IS_TEXT(to->dsc_dtype));
|
|
|
|
/* Convert a date or time value into a timestamp for manipulation */
|
|
|
|
GDS_TIMESTAMP date;
|
|
|
|
switch (from->dsc_dtype) {
|
|
case dtype_sql_time:
|
|
date.timestamp_date = 0;
|
|
date.timestamp_time = *(GDS_TIME *) from->dsc_address;
|
|
break;
|
|
case dtype_sql_date:
|
|
date.timestamp_date = *(GDS_DATE *) from->dsc_address;
|
|
date.timestamp_time = 0;
|
|
break;
|
|
case dtype_timestamp:
|
|
/** Cannot call GET_THREAD_DATA because that macro calls
|
|
BUGCHECK i.e. ERR_bugcheck() which is not part of
|
|
client library **/
|
|
tdbb = PLATFORM_GET_THREAD_DATA;
|
|
if ((tdbb) &&
|
|
(((THDD) tdbb)->thdd_type == THDD_TYPE_TDBB) &&
|
|
tdbb->tdbb_request)
|
|
{
|
|
version4 = (tdbb->tdbb_request->req_flags & req_blr_version4) ?
|
|
true : false;
|
|
}
|
|
date = *(GDS_TIMESTAMP *) from->dsc_address;
|
|
break;
|
|
default:
|
|
fb_assert(FALSE);
|
|
(*err) (isc_badblk, 0); /* internal error */
|
|
break;
|
|
}
|
|
|
|
/* Decode the timestamp into human readable terms */
|
|
|
|
tm times;
|
|
isc_decode_timestamp(&date, ×);
|
|
|
|
TEXT temp[30]; /* yyyy-mm-dd hh:mm:ss.tttt OR
|
|
dd-MMM-yyyy hh:mm:ss.tttt */
|
|
TEXT* p = temp;
|
|
|
|
/* Make a textual date for data types that include it */
|
|
|
|
if (from->dsc_dtype != dtype_sql_time) {
|
|
|
|
if (from->dsc_dtype == dtype_sql_date || !version4) {
|
|
sprintf(p, "%4.4d-%2.2d-%2.2d",
|
|
times.tm_year + 1900, times.tm_mon + 1, times.tm_mday);
|
|
}
|
|
else {
|
|
/* Prior to BLR version 5 - timestamps where converted to
|
|
text in the dd-Mon-yyyy format */
|
|
sprintf(p, "%d-%.3s-%d",
|
|
times.tm_mday,
|
|
FB_LONG_MONTHS_UPPER[times.tm_mon], times.tm_year + 1900);
|
|
}
|
|
while (*p)
|
|
p++;
|
|
};
|
|
|
|
/* Put in a space to separate date & time components */
|
|
|
|
if ((from->dsc_dtype == dtype_timestamp) && (!version4))
|
|
*p++ = ' ';
|
|
|
|
/* Add the time part for data types that include it */
|
|
|
|
if (from->dsc_dtype != dtype_sql_date) {
|
|
if (from->dsc_dtype == dtype_sql_time || !version4) {
|
|
sprintf(p, "%2.2d:%2.2d:%2.2d.%4.4d",
|
|
times.tm_hour, times.tm_min, times.tm_sec,
|
|
(USHORT) (date.timestamp_time %
|
|
ISC_TIME_SECONDS_PRECISION));
|
|
}
|
|
else if (times.tm_hour || times.tm_min || times.tm_sec
|
|
|| date.timestamp_time)
|
|
{
|
|
/* Timestamp formating prior to BLR Version 5 is slightly
|
|
different */
|
|
sprintf(p, " %d:%.2d:%.2d.%.4d",
|
|
times.tm_hour, times.tm_min, times.tm_sec,
|
|
(USHORT) (date.timestamp_time %
|
|
ISC_TIME_SECONDS_PRECISION));
|
|
}
|
|
while (*p)
|
|
p++;
|
|
};
|
|
|
|
/* Move the text version of the date/time value into the destination */
|
|
|
|
dsc desc;
|
|
MOVE_CLEAR(&desc, sizeof(desc));
|
|
desc.dsc_address = (UCHAR *) temp;
|
|
desc.dsc_dtype = dtype_text;
|
|
desc.dsc_ttype = ttype_ascii;
|
|
desc.dsc_length = (p - temp);
|
|
if (from->dsc_dtype == dtype_timestamp && version4) {
|
|
/* Prior to BLR Version5, when a timestamp is converted to a string it
|
|
is silently truncated if the destination string is not large enough */
|
|
|
|
fb_assert(to->dsc_dtype <= dtype_any_text);
|
|
|
|
const USHORT l = (to->dsc_dtype == dtype_cstring) ? 1 :
|
|
(to->dsc_dtype == dtype_varying) ? sizeof(USHORT) : 0;
|
|
desc.dsc_length = MIN(desc.dsc_length, (to->dsc_length - l));
|
|
}
|
|
|
|
CVT_move(&desc, to, err);
|
|
}
|
|
|
|
|
|
static SSHORT decompose(const char* string,
|
|
USHORT length,
|
|
SSHORT dtype,
|
|
SLONG* return_value,
|
|
FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d e c o m p o s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Decompose a numeric string in mantissa and exponent.
|
|
*
|
|
**************************************/
|
|
#ifndef NATIVE_QUAD
|
|
/* For now, this routine does not handle quadwords unless this is
|
|
supported by the platform as a native datatype. */
|
|
|
|
if (dtype == dtype_quad)
|
|
(*err) (isc_badblk, 0); /* internal error */
|
|
#endif
|
|
|
|
dsc errd;
|
|
MOVE_CLEAR(&errd, sizeof(errd));
|
|
errd.dsc_dtype = dtype_text;
|
|
errd.dsc_ttype = ttype_ascii;
|
|
errd.dsc_length = length;
|
|
errd.dsc_address = reinterpret_cast<UCHAR*>(const_cast<char*>(string));
|
|
|
|
SINT64 value = 0;
|
|
SSHORT scale = 0, sign = 0;
|
|
bool digit_seen = false, fraction = false;
|
|
const SINT64 lower_limit = (dtype == dtype_long) ? MIN_SLONG : MIN_SINT64;
|
|
const SINT64 upper_limit = (dtype == dtype_long) ? MAX_SLONG : MAX_SINT64;
|
|
|
|
const SINT64 limit_by_10 = upper_limit / 10; /* used to check for overflow */
|
|
|
|
const char* p = string;
|
|
const char* const end = p + length;
|
|
for (; p < end; p++)
|
|
{
|
|
if (*p == ',')
|
|
continue;
|
|
else if (DIGIT(*p)) {
|
|
digit_seen = true;
|
|
|
|
/* Before computing the next value, make sure there will be
|
|
no overflow. Trying to detect overflow after the fact is
|
|
tricky: the value doesn't always become negative after an
|
|
overflow! */
|
|
|
|
if (value >= limit_by_10) {
|
|
/* possibility of an overflow */
|
|
if (value > limit_by_10)
|
|
(*err) (isc_arith_except, 0);
|
|
else if (((*p > '8') && (sign == -1))
|
|
|| ((*p > '7') && (sign != -1)))
|
|
{
|
|
(*err) (isc_arith_except, 0);
|
|
}
|
|
}
|
|
|
|
value = value * 10 + *p - '0';
|
|
if (fraction)
|
|
--scale;
|
|
}
|
|
else if (*p == '.') {
|
|
if (fraction)
|
|
conversion_error(&errd, err);
|
|
else
|
|
fraction = true;
|
|
}
|
|
else if (*p == '-' && !digit_seen && !sign && !fraction)
|
|
sign = -1;
|
|
else if (*p == '+' && !digit_seen && !sign && !fraction)
|
|
sign = 1;
|
|
else if (*p == 'e' || *p == 'E')
|
|
break;
|
|
else if (*p != ' ')
|
|
conversion_error(&errd, err);
|
|
}
|
|
|
|
|
|
if (!digit_seen)
|
|
conversion_error(&errd, err);
|
|
|
|
if ((sign == -1) && value != lower_limit)
|
|
value = -value;
|
|
|
|
/* If there's still something left, there must be an explicit exponent */
|
|
if (p < end) {
|
|
sign = 0;
|
|
SSHORT exp = 0;
|
|
digit_seen = false;
|
|
for (p++; p < end; p++) {
|
|
if (DIGIT(*p)) {
|
|
digit_seen = true;
|
|
exp = exp * 10 + *p - '0';
|
|
|
|
/* The following is a 'safe' test to prevent overflow of
|
|
exp here and of scale below. A more precise test will
|
|
occur in the calling routine when the scale/exp is
|
|
applied to the value. */
|
|
|
|
if (exp >= SHORT_LIMIT)
|
|
(*err) (isc_arith_except, 0);
|
|
}
|
|
else if (*p == '-' && !digit_seen && !sign)
|
|
sign = -1;
|
|
else if (*p == '+' && !digit_seen && !sign)
|
|
sign = 1;
|
|
else if (*p != ' ')
|
|
conversion_error(&errd, err);
|
|
}
|
|
if (sign == -1)
|
|
scale -= exp;
|
|
else
|
|
scale += exp;
|
|
|
|
if (!digit_seen)
|
|
conversion_error(&errd, err);
|
|
|
|
}
|
|
|
|
if (dtype == dtype_long)
|
|
*return_value = (SLONG) value;
|
|
else
|
|
*((SINT64 *) return_value) = value;
|
|
|
|
return scale;
|
|
}
|
|
|
|
|
|
#if (defined REQUESTER || defined SUPERCLIENT)
|
|
static const TEXT* error_string(const char* in_string, SSHORT length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e r r o r _ s t r i n g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Copy a stack local string into an area that is
|
|
* guaranteed to exist for a reasonable length of time.
|
|
* A circular buffer is used to hold these strings. It
|
|
* is independent of the JRD allocator mechanism.
|
|
*
|
|
**************************************/
|
|
if (!cvt_failures_ptr)
|
|
cvt_failures_ptr = cvt_failures;
|
|
|
|
/* If there isn't any more room in the buffer, start at the beginning again */
|
|
|
|
if (cvt_failures_ptr + length + 1 > cvt_failures + CVT_FAILURE_SPACE)
|
|
cvt_failures_ptr = cvt_failures;
|
|
|
|
TEXT* new_string = cvt_failures_ptr;
|
|
|
|
while (length--
|
|
&& (cvt_failures_ptr < cvt_failures + CVT_FAILURE_SPACE - 1))
|
|
{
|
|
*cvt_failures_ptr++ = *in_string++;
|
|
}
|
|
*cvt_failures_ptr++ = 0;
|
|
|
|
return new_string;
|
|
}
|
|
#endif
|
|
|
|
|
|
static void float_to_text(const dsc* from, dsc* to, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f l o a t _ t o _ t e x t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert an arbitrary floating type number to text.
|
|
* To avoid messiness, convert first into a temp (fixed) then
|
|
* move to the destination.
|
|
* Never print more digits than the source type can actually
|
|
* provide: instead pad the output with blanks on the right if
|
|
* needed.
|
|
*
|
|
**************************************/
|
|
double d;
|
|
char temp[] = "-1.234567890123456E-300";
|
|
|
|
const int to_len = DSC_string_length(to); // length of destination
|
|
const int width = MIN(to_len, (int) sizeof(temp) - 1); // minimum width to print
|
|
|
|
int precision;
|
|
if (dtype_double == from->dsc_dtype) {
|
|
precision = 16; /* minimum significant digits in a double */
|
|
d = *(double*) from->dsc_address;
|
|
}
|
|
else {
|
|
fb_assert(dtype_real == from->dsc_dtype);
|
|
precision = 8; /* minimum significant digits in a float */
|
|
d = (double) *(float*) from->dsc_address;
|
|
}
|
|
|
|
/* If this is a double with non-zero scale, then it is an old-style
|
|
NUMERIC(15, -scale): print it in fixed format with -scale digits
|
|
to the right of the ".". */
|
|
|
|
/* CVC: Here sprintf was given an extra space in the two formatting
|
|
masks used below, "%- #*.*f" and "%- #*.*g" but certainly with positive
|
|
quantities and CAST it yields an annoying leading space.
|
|
However, by getting rid of the space you get in dialect 1:
|
|
cast(17/13 as char(5)) => 1.308
|
|
cast(-17/13 as char(5)) => -1.31
|
|
Since this is inconsistent with dialect 3, see workaround at the tail
|
|
of this function. */
|
|
|
|
int chars_printed; /* number of characters printed */
|
|
if ((dtype_double == from->dsc_dtype) && (from->dsc_scale < 0))
|
|
chars_printed = sprintf(temp, "%- #*.*f", width, -from->dsc_scale, d);
|
|
else
|
|
chars_printed = LONG_MAX_int; /* sure to be greater than to_len */
|
|
|
|
/* If it's not an old-style numeric, or the f-format was too long for the
|
|
destination, try g-format with the maximum precision which makes sense
|
|
for the input type: if it fits, we're done. */
|
|
|
|
if (chars_printed > width) {
|
|
char num_format[] = "%- #*.*g";
|
|
chars_printed = sprintf(temp, num_format, width, precision, d);
|
|
|
|
/* If the full-precision result is too wide for the destination,
|
|
reduce the precision and try again. */
|
|
|
|
if (chars_printed > width) {
|
|
precision -= (chars_printed - width);
|
|
|
|
/* If we cannot print at least two digits, one on each side of the
|
|
".", report an overflow exception. */
|
|
if (precision < 2)
|
|
(*err) (isc_arith_except, 0);
|
|
|
|
chars_printed = sprintf(temp, num_format, width, precision, d);
|
|
|
|
/* It's possible that reducing the precision caused sprintf to switch
|
|
from f-format to e-format, and that the output is still too long
|
|
for the destination. If so, reduce the precision once more.
|
|
This is certain to give a short-enough result. */
|
|
|
|
if (chars_printed > width) {
|
|
precision -= (chars_printed - width);
|
|
if (precision < 2)
|
|
(*err) (isc_arith_except, 0);
|
|
chars_printed = sprintf(temp, num_format, width, precision, d);
|
|
}
|
|
}
|
|
}
|
|
fb_assert(chars_printed <= width);
|
|
|
|
/* Now move the result to the destination array. */
|
|
|
|
dsc intermediate;
|
|
intermediate.dsc_dtype = dtype_text;
|
|
intermediate.dsc_ttype = ttype_ascii;
|
|
/* CVC: If you think this is dangerous, replace the "else" with a call to
|
|
MEMMOVE(temp, temp + 1, chars_printed) or something cleverer.
|
|
Paranoid assumption:
|
|
UCHAR is unsigned char as seen on jrd\common.h => same size. */
|
|
if (d < 0)
|
|
{
|
|
intermediate.dsc_address = reinterpret_cast<UCHAR*>(temp);
|
|
intermediate.dsc_length = chars_printed;
|
|
}
|
|
else
|
|
{
|
|
if (!temp[0])
|
|
temp[1] = 0;
|
|
intermediate.dsc_address = reinterpret_cast<UCHAR*>(temp) + 1;
|
|
intermediate.dsc_length = chars_printed - 1;
|
|
}
|
|
|
|
CVT_move(&intermediate, to, err);
|
|
}
|
|
|
|
|
|
static void integer_to_text(const dsc* from, dsc* to, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n t e g e r _ t o _ t e x t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert your basic binary number to
|
|
* nice, formatted text.
|
|
*
|
|
**************************************/
|
|
#ifndef NATIVE_QUAD
|
|
/* For now, this routine does not handle quadwords unless this is
|
|
supported by the platform as a native datatype. */
|
|
|
|
if (from->dsc_dtype == dtype_quad)
|
|
(*err) (isc_badblk, 0); /* internal error */
|
|
#endif
|
|
|
|
SSHORT pad = 0, decimal = 0, neg = 0;
|
|
|
|
/* Save (or compute) scale of source. Then convert source to ordinary
|
|
longword or int64. */
|
|
|
|
SCHAR scale = from->dsc_scale;
|
|
|
|
if (scale > 0)
|
|
pad = scale;
|
|
else if (scale < 0)
|
|
decimal = 1;
|
|
|
|
SINT64 n;
|
|
dsc intermediate;
|
|
MOVE_CLEAR(&intermediate, sizeof(intermediate));
|
|
intermediate.dsc_dtype = dtype_int64;
|
|
intermediate.dsc_length = sizeof(n);
|
|
intermediate.dsc_scale = scale;
|
|
intermediate.dsc_address = (UCHAR*) &n;
|
|
|
|
CVT_move(from, &intermediate, err);
|
|
|
|
/* Check for negation, then convert the number to a string of digits */
|
|
|
|
UINT64 u;
|
|
if (n >= 0)
|
|
u = n;
|
|
else {
|
|
neg = 1;
|
|
u = -n;
|
|
}
|
|
|
|
UCHAR temp[32];
|
|
UCHAR* p = temp;
|
|
|
|
do {
|
|
*p++ = (UCHAR) (u % 10) + '0';
|
|
u /= 10;
|
|
} while (u);
|
|
|
|
SSHORT l = p - temp;
|
|
|
|
/* if scale < 0, we need at least abs(scale)+1 digits, so add
|
|
any leading zeroes required. */
|
|
while (l + scale <= 0) {
|
|
*p++ = '0';
|
|
l++;
|
|
}
|
|
/* postassertion: l+scale > 0 */
|
|
fb_assert(l + scale > 0);
|
|
|
|
// CVC: also, we'll check for buffer overflow directly.
|
|
fb_assert(temp + sizeof(temp) >= p);
|
|
|
|
/* Compute the total length of the field formatted. Make sure it
|
|
fits. Keep in mind that routine handles both string and varying
|
|
string fields. */
|
|
|
|
const SSHORT length = l + neg + decimal + pad;
|
|
|
|
if ((to->dsc_dtype == dtype_text && length > to->dsc_length) ||
|
|
(to->dsc_dtype == dtype_cstring && length >= to->dsc_length) ||
|
|
(to->dsc_dtype == dtype_varying
|
|
&& length > (SSHORT) (to->dsc_length - sizeof(USHORT))))
|
|
{
|
|
conversion_error(from, err);
|
|
}
|
|
|
|
UCHAR* q = (to->dsc_dtype == dtype_varying) ?
|
|
to->dsc_address + sizeof(USHORT) : to->dsc_address;
|
|
|
|
/* If negative, put in minus sign */
|
|
|
|
if (neg)
|
|
*q++ = '-';
|
|
|
|
/* If a decimal point is required, do the formatting. Otherwise just
|
|
copy number */
|
|
|
|
if (scale >= 0)
|
|
do {
|
|
*q++ = *--p;
|
|
} while (--l);
|
|
else {
|
|
l += scale; /* l > 0 (see postassertion: l+scale > 0 above) */
|
|
do {
|
|
*q++ = *--p;
|
|
} while (--l);
|
|
*q++ = '.';
|
|
do {
|
|
*q++ = *--p;
|
|
} while (++scale);
|
|
}
|
|
|
|
/* If padding is required, do it now. */
|
|
|
|
if (pad)
|
|
do {
|
|
*q++ = '0';
|
|
} while (--pad);
|
|
|
|
/* Finish up by padding (if fixed) or computing the actual length
|
|
(varying string) */
|
|
|
|
if (to->dsc_dtype == dtype_text) {
|
|
if ((l = to->dsc_length - length) > 0) {
|
|
do {
|
|
*q++ = ' ';
|
|
} while (--l);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (to->dsc_dtype == dtype_cstring) {
|
|
*q = 0;
|
|
return;
|
|
}
|
|
|
|
*(SSHORT *) (to->dsc_address) = q - to->dsc_address - sizeof(SSHORT);
|
|
}
|
|
|
|
|
|
static void string_to_datetime(
|
|
const dsc* desc,
|
|
GDS_TIMESTAMP* date,
|
|
EXPECT_DATETIME expect_type, FPTR_ERROR err)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s t r i n g _ t o _ d a t e t i m e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Convert an arbitrary string to a date and/or time.
|
|
*
|
|
* String must be formed using ASCII characters only.
|
|
* Conversion routine can handle the following input formats
|
|
* "now" current date & time
|
|
* "today" Today's date 0:0:0.0 time
|
|
* "tomorrow" Tomorrow's date 0:0:0.0 time
|
|
* "yesterday" Yesterday's date 0:0:0.0 time
|
|
* YYYY-MM-DD [HH:[Min:[SS.[Thou]]]]]
|
|
* MM:DD[:YY [HH:[Min:[SS.[Thou]]]]]
|
|
* DD.MM[:YY [HH:[Min:[SS.[Thou]]]]]
|
|
* Where:
|
|
* DD = 1 .. 31 (Day of month)
|
|
* YY = 00 .. 99 2-digit years are converted to the nearest year
|
|
* in a 50 year range. Eg: if this is 1996
|
|
* 96 ==> 1996
|
|
* 97 ==> 1997
|
|
* ...
|
|
* 00 ==> 2000
|
|
* 01 ==> 2001
|
|
* ...
|
|
* 44 ==> 2044
|
|
* 45 ==> 2045
|
|
* 46 ==> 1946
|
|
* 47 ==> 1947
|
|
* ...
|
|
* 95 ==> 1995
|
|
* If the current year is 1997, then 46 is converted
|
|
* to 2046 (etc).
|
|
* = 100.. 5200 (Year)
|
|
* MM = 1 .. 12 (Month of year)
|
|
* = "JANUARY"... (etc)
|
|
* HH = 0 .. 23 (Hour of day)
|
|
* Min = 0 .. 59 (Minute of hour)
|
|
* SS = 0 .. 59 (Second of minute - LEAP second not supported)
|
|
* Thou = 0 .. 9999 (Fraction of second)
|
|
* HH, Min, SS, Thou default to 0 if missing.
|
|
* YY defaults to current year if missing.
|
|
* Note: ANY punctuation can be used instead of : (eg: / - etc)
|
|
* Using . (period) in either of the first two separation
|
|
* points will cause the date to be parsed in European DMY
|
|
* format.
|
|
* Arbitrary whitespace (space or TAB) can occur between
|
|
* components.
|
|
*
|
|
**************************************/
|
|
|
|
/* Values inside of description
|
|
> 0 is number of digits
|
|
0 means missing
|
|
ENGLISH_MONTH for the presence of an English month name
|
|
SPECIAL for a special date verb */
|
|
#define ENGLISH_MONTH -1
|
|
#define SPECIAL -2
|
|
USHORT position_year = 0;
|
|
USHORT position_month = 1;
|
|
USHORT position_day = 2;
|
|
bool have_english_month = false;
|
|
bool dot_separator_seen = false;
|
|
tm times, times2, *ptimes;
|
|
TEXT buffer[100]; /* arbitrarily large */
|
|
|
|
const char* string;
|
|
const USHORT length =
|
|
CVT_make_string(desc, ttype_ascii, &string,
|
|
(vary*) buffer, sizeof(buffer), err);
|
|
|
|
const char* p = string;
|
|
const char* const end = p + length;
|
|
|
|
USHORT n, components[7];
|
|
SSHORT description[7];
|
|
memset(components, 0, sizeof(components));
|
|
memset(description, 0, sizeof(description));
|
|
|
|
/* Parse components */
|
|
/* The 7 components are Year, Month, Day, Hours, Minutes, Seconds, Thou */
|
|
/* The first 3 can be in any order */
|
|
|
|
const int start_component = (expect_type == expect_sql_time) ? 3 : 0;
|
|
int i;
|
|
for (i = start_component; i < 7; i++) {
|
|
|
|
/* Skip leading blanks. If we run out of characters, we're done
|
|
with parse. */
|
|
|
|
while (p < end && (*p == ' ' || *p == '\t'))
|
|
p++;
|
|
if (p == end)
|
|
break;
|
|
|
|
/* Handle digit or character strings */
|
|
|
|
TEXT c = UPPER7(*p);
|
|
if (DIGIT(c)) {
|
|
USHORT precision = 0;
|
|
n = 0;
|
|
while (p < end && DIGIT(*p)) {
|
|
n = n * 10 + *p++ - '0';
|
|
precision++;
|
|
}
|
|
description[i] = precision;
|
|
}
|
|
else if (LETTER7(c) && !have_english_month) {
|
|
TEXT temp[sizeof(YESTERDAY) + 1];
|
|
|
|
TEXT* t = temp;
|
|
while ((p < end) && (t < &temp[sizeof(temp) - 1])) {
|
|
c = UPPER7(*p);
|
|
if (!LETTER7(c))
|
|
break;
|
|
*t++ = c;
|
|
p++;
|
|
}
|
|
*t = 0;
|
|
|
|
/* Insist on at least 3 characters for month names */
|
|
if (t - temp < 3) {
|
|
conversion_error(desc, err);
|
|
return;
|
|
}
|
|
|
|
const TEXT* const* month_ptr = FB_LONG_MONTHS_UPPER;
|
|
while (true) {
|
|
/* Month names are only allowed in first 2 positions */
|
|
if (*month_ptr && i < 2) {
|
|
t = temp;
|
|
const TEXT* m = *month_ptr++;
|
|
while (*t && *t == *m) {
|
|
++t;
|
|
++m;
|
|
}
|
|
if (!*t)
|
|
break;
|
|
}
|
|
else {
|
|
/* it's not a month name, so it's either a magic word or
|
|
a non-date string. If there are more characters, it's bad */
|
|
|
|
description[i] = SPECIAL;
|
|
|
|
while (++p < end)
|
|
if (*p != ' ' && *p != '\t' && *p != 0)
|
|
conversion_error(desc, err);
|
|
|
|
/* fetch the current time */
|
|
|
|
const time_t clock = time(NULL);
|
|
ptimes = localtime(&clock);
|
|
if (!ptimes)
|
|
{
|
|
(err)(isc_date_range_exceeded, 0);
|
|
}
|
|
times2 = *ptimes;
|
|
|
|
if (strcmp(temp, NOW) == 0) {
|
|
isc_encode_timestamp(×2, date);
|
|
return;
|
|
}
|
|
if (expect_type == expect_sql_time) {
|
|
conversion_error(desc, err);
|
|
return;
|
|
}
|
|
times2.tm_hour = times2.tm_min = times2.tm_sec = 0;
|
|
isc_encode_timestamp(×2, date);
|
|
if (strcmp(temp, TODAY) == 0)
|
|
return;
|
|
if (strcmp(temp, TOMORROW) == 0) {
|
|
date->timestamp_date++;
|
|
return;
|
|
}
|
|
if (strcmp(temp, YESTERDAY) == 0) {
|
|
date->timestamp_date--;
|
|
return;
|
|
}
|
|
conversion_error(desc, err);
|
|
return;
|
|
}
|
|
}
|
|
n = month_ptr - FB_LONG_MONTHS_UPPER;
|
|
position_month = i;
|
|
description[i] = ENGLISH_MONTH;
|
|
have_english_month = true;
|
|
}
|
|
else { /* Not a digit and not a letter - must be punctuation */
|
|
|
|
conversion_error(desc, err);
|
|
return;
|
|
}
|
|
|
|
components[i] = n;
|
|
|
|
/* Grab whitespace following the number */
|
|
while (p < end && (*p == ' ' || *p == '\t'))
|
|
p++;
|
|
|
|
if (p == end)
|
|
break;
|
|
|
|
/* Grab a separator character */
|
|
if (*p == '/' || *p == '-' || *p == ',' || *p == ':') {
|
|
p++;
|
|
continue;
|
|
}
|
|
if (*p == '.') {
|
|
if (i <= 1)
|
|
dot_separator_seen = true;
|
|
p++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* User must provide at least 2 components */
|
|
if (i - start_component < 1) {
|
|
conversion_error(desc, err);
|
|
return;
|
|
}
|
|
|
|
/* Dates cannot have a Time portion */
|
|
if (expect_type == expect_sql_date && i > 2) {
|
|
conversion_error(desc, err);
|
|
return;
|
|
}
|
|
|
|
memset(×, 0, sizeof(times));
|
|
|
|
if (expect_type != expect_sql_time) {
|
|
/* Figure out what format the user typed the date in */
|
|
|
|
/* A 4 digit number to start implies YYYY-MM-DD */
|
|
if (description[0] >= 3) {
|
|
position_year = 0;
|
|
position_month = 1;
|
|
position_day = 2;
|
|
}
|
|
|
|
/* An English month to start implies MM-DD-YYYY */
|
|
else if (description[0] == ENGLISH_MONTH) {
|
|
position_year = 2;
|
|
position_month = 0;
|
|
position_day = 1;
|
|
}
|
|
|
|
/* An English month in the middle implies DD-MM-YYYY */
|
|
else if (description[1] == ENGLISH_MONTH) {
|
|
position_year = 2;
|
|
position_month = 1;
|
|
position_day = 0;
|
|
}
|
|
|
|
/* A period as a separator implies DD.MM.YYYY */
|
|
else if (dot_separator_seen) {
|
|
position_year = 2;
|
|
position_month = 1;
|
|
position_day = 0;
|
|
}
|
|
|
|
/* Otherwise assume MM-DD-YYYY */
|
|
else {
|
|
position_year = 2;
|
|
position_month = 0;
|
|
position_day = 1;
|
|
}
|
|
|
|
/* Forbid years with more than 4 digits */
|
|
/* Forbid months or days with more than 2 digits */
|
|
/* Forbid months or days being missing */
|
|
if (description[position_year] > 4 ||
|
|
description[position_month] > 2
|
|
|| description[position_month] == 0
|
|
|| description[position_day] > 2
|
|
|| description[position_day] <= 0)
|
|
{
|
|
conversion_error(desc, err);
|
|
return;
|
|
}
|
|
|
|
/* Slide things into day, month, year form */
|
|
|
|
times.tm_year = components[position_year];
|
|
times.tm_mon = components[position_month];
|
|
times.tm_mday = components[position_day];
|
|
|
|
const time_t clock = time(NULL);
|
|
ptimes = localtime(&clock);
|
|
if (!ptimes)
|
|
{
|
|
(err)(isc_date_range_exceeded, 0);
|
|
}
|
|
times2 = *ptimes;
|
|
|
|
/* Handle defaulting of year */
|
|
|
|
if (description[position_year] == 0) {
|
|
times.tm_year = times2.tm_year + 1900;
|
|
}
|
|
|
|
/* Handle conversion of 2-digit years */
|
|
|
|
else if (description[position_year] <= 2) {
|
|
if (times.tm_year < (times2.tm_year - 50) % 100)
|
|
times.tm_year += 2000;
|
|
else
|
|
times.tm_year += 1900;
|
|
}
|
|
|
|
times.tm_year -= 1900;
|
|
times.tm_mon -= 1;
|
|
}
|
|
else {
|
|
/* The date portion isn't needed for time - but to
|
|
keep the conversion in/out of isc_time clean lets
|
|
intialize it properly anyway */
|
|
times.tm_year = 0;
|
|
times.tm_mon = 0;
|
|
times.tm_mday = 1;
|
|
}
|
|
|
|
/* Handle time values out of range - note possibility of 60 for
|
|
* seconds value due to LEAP second (Not supported in V4.0).
|
|
*/
|
|
if (((times.tm_hour = components[3]) > 23) ||
|
|
((times.tm_min = components[4]) > 59) ||
|
|
((times.tm_sec = components[5]) > 59) ||
|
|
(description[6] > -ISC_TIME_SECONDS_PRECISION_SCALE))
|
|
{
|
|
conversion_error(desc, err);
|
|
}
|
|
|
|
/* convert day/month/year to Julian and validate result
|
|
This catches things like 29-Feb-1995 (not a leap year) */
|
|
|
|
isc_encode_timestamp(×, date);
|
|
if (expect_type != expect_sql_time) {
|
|
isc_decode_timestamp(date, ×2);
|
|
|
|
if ((times.tm_year + 1900) < MIN_YEAR
|
|
|| (times.tm_year) + 1900 > MAX_YEAR)
|
|
{
|
|
(*err) (isc_date_range_exceeded, 0);
|
|
}
|
|
|
|
if (times.tm_year != times2.tm_year ||
|
|
times.tm_mon != times2.tm_mon ||
|
|
times.tm_mday != times2.tm_mday ||
|
|
times.tm_hour != times2.tm_hour ||
|
|
times.tm_min != times2.tm_min || times.tm_sec != times2.tm_sec)
|
|
{
|
|
conversion_error(desc, err);
|
|
}
|
|
};
|
|
|
|
/* Convert fraction of seconds */
|
|
while (description[6]++ < -ISC_TIME_SECONDS_PRECISION_SCALE)
|
|
components[6] *= 10;
|
|
|
|
date->timestamp_time += components[6];
|
|
}
|
|
|
|
|
|
double power_of_ten(const int scale)
|
|
{
|
|
/*************************************
|
|
*
|
|
* p o w e r _ o f _ t e n
|
|
*
|
|
*************************************
|
|
*
|
|
* Functional description
|
|
* return 10.0 raised to the scale power for 0 <= scale < 320.
|
|
*
|
|
*************************************/
|
|
|
|
/* Note that we could speed things up slightly by making the auxiliary
|
|
* arrays global to this source module and replacing this function with
|
|
* a macro, but the old code did up to 308 multiplies to our 1, and
|
|
* that seems enough of a speed-up for now.
|
|
*/
|
|
|
|
static const double upper_part[] =
|
|
{ 1.e000, 1.e032, 1.e064, 1.e096, 1.e128,
|
|
1.e160, 1.e192, 1.e224, 1.e256, 1.e288
|
|
};
|
|
|
|
static const double lower_part[] =
|
|
{ 1.e00, 1.e01, 1.e02, 1.e03, 1.e04, 1.e05,
|
|
1.e06, 1.e07, 1.e08, 1.e09, 1.e10, 1.e11,
|
|
1.e12, 1.e13, 1.e14, 1.e15, 1.e16, 1.e17,
|
|
1.e18, 1.e19, 1.e20, 1.e21, 1.e22, 1.e23,
|
|
1.e24, 1.e25, 1.e26, 1.e27, 1.e28, 1.e29,
|
|
1.e30, 1.e31
|
|
};
|
|
|
|
/* The sole caller of this function checks for scale <= 308 before calling,
|
|
* but we just fb_assert the weakest precondition which lets the code work.
|
|
* If the size of the exponent field, and thus the scaling, of doubles
|
|
* gets bigger, increase the size of the upper_part array.
|
|
*/
|
|
fb_assert((scale >= 0) && (scale < 320));
|
|
|
|
/* Note that "scale >> 5" is another way of writing "scale / 32",
|
|
* while "scale & 0x1f" is another way of writing "scale % 32".
|
|
* We split the scale into the lower 5 bits and everything else,
|
|
* then use the "everything else" to index into the upper_part array,
|
|
* whose contents increase in steps of 1e32.
|
|
*/
|
|
return upper_part[scale >> 5] * lower_part[scale & 0x1f];
|
|
}
|
|
|