mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-23 10:43:03 +01:00
354 lines
8.6 KiB
C++
354 lines
8.6 KiB
C++
/*
|
|
* PROGRAM: JRD access method
|
|
* MODULE: Coercion.cpp
|
|
* DESCRIPTION: Automatically coercing user datatypes
|
|
*
|
|
* The contents of this file are subject to the Initial
|
|
* Developer's 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.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
|
|
*
|
|
* Software distributed under the License is distributed AS IS,
|
|
* 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 Alex Peshkov
|
|
* for the Firebird Open Source RDBMS project.
|
|
*
|
|
* Copyright (c) 2019 Alex Peshkov <peshkoff@mail.ru>
|
|
* and all contributors signed below.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
*
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "../jrd/Coercion.h"
|
|
#include "../jrd/cvt_proto.h"
|
|
|
|
#include "../dsql/dsql.h"
|
|
#include "../dsql/make_proto.h"
|
|
#include "../jrd/align.h"
|
|
#include "../jrd/DataTypeUtil.h"
|
|
|
|
using namespace Jrd;
|
|
using namespace Firebird;
|
|
|
|
static const USHORT FROM_MASK = FLD_has_len | FLD_has_chset | FLD_has_scale |
|
|
FLD_has_sub | FLD_has_prec;
|
|
static const USHORT TO_MASK = FLD_has_len | FLD_has_chset | FLD_has_scale |
|
|
FLD_legacy | FLD_native | FLD_has_sub | FLD_has_prec | FLD_extended;
|
|
|
|
bool CoercionArray::coerce(thread_db* tdbb, dsc* d, unsigned startItem) const
|
|
{
|
|
// move down through array to ensure correct order: newer rule overrides older one
|
|
for (unsigned n = getCount(); n-- > startItem; )
|
|
{
|
|
if (getElement(n).coerce(tdbb, d))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CoercionArray::setRule(const TypeClause* from, const TypeClause *to)
|
|
{
|
|
CoercionRule newRule;
|
|
newRule.setRule(from, to);
|
|
|
|
for (unsigned n = 0; n < getCount(); ++n)
|
|
{
|
|
if (getElement(n) == newRule)
|
|
{
|
|
remove(n);
|
|
break;
|
|
}
|
|
}
|
|
|
|
add(newRule);
|
|
}
|
|
|
|
void CoercionRule::raiseError()
|
|
{
|
|
// Do not use ERR_post here - old error should be overwritten
|
|
(Arg::Gds(isc_bind_convert) << fromDsc.typeToText() << toDsc.typeToText()).raise();
|
|
}
|
|
|
|
void CoercionRule::setRule(const TypeClause* from, const TypeClause *to)
|
|
{
|
|
fromMask = from->flags & FROM_MASK;
|
|
DsqlDescMaker::fromField(&fromDsc, from);
|
|
|
|
toMask = to->flags & TO_MASK;
|
|
DsqlDescMaker::fromField(&toDsc, to);
|
|
|
|
// Check for datatype compatibility
|
|
|
|
// No checks for special case
|
|
if (toMask & (FLD_native | FLD_legacy))
|
|
return;
|
|
|
|
// Extending timezone info
|
|
if (toMask & FLD_extended)
|
|
{
|
|
if (fromDsc.isDateTimeTz())
|
|
return;
|
|
raiseError();
|
|
}
|
|
|
|
// Exceptions - enable blob2blob & blob2string casts
|
|
if ((toDsc.dsc_dtype == dtype_blob && fromDsc.isText()) ||
|
|
(fromDsc.dsc_dtype == dtype_blob && toDsc.isText()) ||
|
|
(toDsc.isBlob() && fromDsc.isBlob()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Disable the rest of casts with blobs
|
|
if (toDsc.isBlob() || fromDsc.isBlob())
|
|
raiseError();
|
|
|
|
// Generic check
|
|
const unsigned DATASIZE = 256;
|
|
UCHAR buf[DATASIZE * 2 + FB_ALIGNMENT];
|
|
memset(buf, 0, sizeof buf);
|
|
toDsc.dsc_address = FB_ALIGN(buf, FB_ALIGNMENT);
|
|
if (! (toMask & FLD_has_len))
|
|
{
|
|
toDsc.dsc_length = DATASIZE - 2;
|
|
}
|
|
fromDsc.dsc_address = toDsc.dsc_address + DATASIZE;
|
|
|
|
try
|
|
{
|
|
CVT_move(&fromDsc, &toDsc, 0);
|
|
}
|
|
catch(const Exception&)
|
|
{
|
|
raiseError();
|
|
}
|
|
}
|
|
|
|
dsc* CoercionRule::makeLegacy(USHORT mask)
|
|
{
|
|
toMask = FLD_legacy;
|
|
fromMask = mask;
|
|
return &fromDsc;
|
|
}
|
|
|
|
bool CoercionRule::operator==(const CoercionRule& rule) const
|
|
{
|
|
if (fromMask != rule.fromMask)
|
|
return false;
|
|
return match(&rule.fromDsc);
|
|
}
|
|
|
|
bool CoercionRule::match(const dsc* d) const
|
|
{
|
|
// check for exact match (taking flags into an account)
|
|
if ((d->dsc_dtype == fromDsc.dsc_dtype) &&
|
|
((d->dsc_length == fromDsc.dsc_length) || (!(fromMask & FLD_has_len))) &&
|
|
((d->getCharSet() == fromDsc.getCharSet()) || (!(fromMask & FLD_has_chset))) &&
|
|
((d->getSubType() == fromDsc.getSubType()) || (!(fromMask & FLD_has_sub))) &&
|
|
((d->dsc_scale == fromDsc.dsc_scale) || (!(fromMask & FLD_has_scale))))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// check for inexact datatype match when FLD_has_prec is not set
|
|
if (!(fromMask & FLD_has_prec))
|
|
{
|
|
switch(fromDsc.dsc_dtype)
|
|
{
|
|
case dtype_dec64:
|
|
case dtype_dec128:
|
|
if (DTYPE_IS_DECFLOAT(d->dsc_dtype))
|
|
return true;
|
|
break;
|
|
|
|
case dtype_short:
|
|
case dtype_long:
|
|
case dtype_int64:
|
|
case dtype_int128:
|
|
if (d->isExact() && (fromMask & FLD_has_sub) && (d->dsc_sub_type == fromDsc.dsc_sub_type))
|
|
return true;
|
|
break;
|
|
|
|
case dtype_timestamp_tz:
|
|
case dtype_sql_time_tz:
|
|
if (d->isDateTimeTz())
|
|
return true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static const USHORT COMPATIBLE_TEXT = 1;
|
|
static const USHORT COMPATIBLE_INT = 2;
|
|
|
|
static const USHORT subTypeCompatibility[DTYPE_TYPE_MAX] =
|
|
{
|
|
0, // dtype_unknown
|
|
COMPATIBLE_TEXT, // dtype_text
|
|
0, // dtype_cstring
|
|
COMPATIBLE_TEXT, // dtype_varying
|
|
0,
|
|
0,
|
|
0, // dtype_packed
|
|
0, // dtype_byte
|
|
COMPATIBLE_INT, // dtype_short -32768
|
|
COMPATIBLE_INT, // dtype_long -2147483648
|
|
0, // dtype_quad -9223372036854775808
|
|
0, // dtype_real -1.23456789e+12
|
|
0, // dtype_double -1.2345678901234567e+123
|
|
0, // dtype_d_float (believed to have this range) -1.2345678901234567e+123
|
|
0, // dtype_sql_date YYYY-MM-DD
|
|
0, // dtype_sql_time HH:MM:SS.MMMM
|
|
0, // dtype_timestamp YYYY-MM-DD HH:MM:SS.MMMM
|
|
0, // dtype_blob FFFF:FFFF
|
|
0, // dtype_array FFFF:FFFF
|
|
COMPATIBLE_INT, // dtype_int64 -9223372036854775808
|
|
0, // dtype_dbkey
|
|
0, // dtype_boolean
|
|
0, // dtype_dec64 1 + 1 + 1 + 1 + 16(34) + 3(4)
|
|
0, // dtype_dec128 +- . e +- coeff + exp
|
|
COMPATIBLE_INT, // dtype_int128
|
|
0, // dtype_sql_time_tz HH:MM:SS.MMMM +NN:NN
|
|
0, // dtype_timestamp_tz YYYY-MM-DD HH:MM:SS.MMMM +NN:NN
|
|
0, // dtype_ex_time_tz HH:MM:SS.MMMM +NN:NN
|
|
0, // dtype_ex_timestamp_tz YYYY-MM-DD HH:MM:SS.MMMM +NN:NN
|
|
};
|
|
|
|
|
|
bool CoercionRule::coerce(thread_db* tdbb, dsc* d) const
|
|
{
|
|
// check does descriptor match FROM clause
|
|
if (! match(d))
|
|
return false;
|
|
|
|
// native binding - do not touch descriptor at all
|
|
if (toMask & FLD_native)
|
|
return true;
|
|
|
|
// process legacy case
|
|
if (toMask & FLD_legacy)
|
|
{
|
|
bool found = true;
|
|
|
|
switch(d->dsc_dtype)
|
|
{
|
|
case dtype_dec64:
|
|
case dtype_dec128:
|
|
d->dsc_dtype = dtype_double;
|
|
d->dsc_length = sizeof(double);
|
|
break;
|
|
case dtype_sql_time_tz:
|
|
d->dsc_dtype = dtype_sql_time;
|
|
d->dsc_length = sizeof(ISC_TIME);
|
|
break;
|
|
case dtype_timestamp_tz:
|
|
d->dsc_dtype = dtype_timestamp;
|
|
d->dsc_length = sizeof(ISC_TIMESTAMP);
|
|
break;
|
|
case dtype_int128:
|
|
d->dsc_dtype = dtype_int64;
|
|
d->dsc_length = sizeof(SINT64);
|
|
break;
|
|
case dtype_boolean:
|
|
d->dsc_dtype = dtype_text;
|
|
d->dsc_length = 5;
|
|
break;
|
|
default:
|
|
found = false;
|
|
break;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
// extending time zone
|
|
if (toMask & FLD_extended)
|
|
{
|
|
bool found = true;
|
|
|
|
switch(d->dsc_dtype)
|
|
{
|
|
case dtype_timestamp_tz:
|
|
d->dsc_dtype = dtype_ex_timestamp_tz;
|
|
d->dsc_length = sizeof(ISC_TIMESTAMP_TZ_EX);
|
|
break;
|
|
case dtype_sql_time_tz:
|
|
d->dsc_dtype = dtype_ex_time_tz;
|
|
d->dsc_length = sizeof(ISC_TIME_TZ_EX);
|
|
break;
|
|
default:
|
|
found = false;
|
|
break;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
// final pass - order is important
|
|
|
|
const auto srcCharSet = d->getCharSet();
|
|
|
|
// scale
|
|
if (toMask & FLD_has_scale)
|
|
d->dsc_scale = toDsc.dsc_scale;
|
|
else if (!(DTYPE_IS_EXACT(d->dsc_dtype) && DTYPE_IS_EXACT(toDsc.dsc_dtype)))
|
|
d->dsc_scale = 0;
|
|
|
|
// subtype
|
|
if (toMask & FLD_has_sub ||
|
|
d->dsc_dtype >= DTYPE_TYPE_MAX || toDsc.dsc_dtype >= DTYPE_TYPE_MAX ||
|
|
subTypeCompatibility[d->dsc_dtype] == 0 ||
|
|
subTypeCompatibility[d->dsc_dtype] != subTypeCompatibility[toDsc.dsc_dtype])
|
|
{
|
|
d->dsc_sub_type = toDsc.dsc_sub_type;
|
|
}
|
|
|
|
// length
|
|
if (toMask & FLD_has_len)
|
|
d->dsc_length = toDsc.dsc_length;
|
|
|
|
// type
|
|
if (toMask & FLD_has_prec ||
|
|
subTypeCompatibility[d->dsc_dtype] != COMPATIBLE_INT ||
|
|
subTypeCompatibility[toDsc.dsc_dtype] != COMPATIBLE_INT)
|
|
{
|
|
if (!type_lengths[toDsc.dsc_dtype])
|
|
{
|
|
fb_assert(toDsc.isText());
|
|
d->dsc_length = d->getStringLength();
|
|
}
|
|
else
|
|
d->dsc_length = type_lengths[toDsc.dsc_dtype];
|
|
|
|
d->dsc_dtype = toDsc.dsc_dtype;
|
|
}
|
|
|
|
// charset
|
|
if (toMask & FLD_has_chset)
|
|
d->setTextType(toDsc.getTextType());
|
|
|
|
if (d->isText())
|
|
d->dsc_length = DataTypeUtil(tdbb).convertLength(d->dsc_length, srcCharSet, toDsc.getCharSet());
|
|
|
|
// varchar length
|
|
if (d->dsc_dtype == dtype_varying)
|
|
d->dsc_length += sizeof(USHORT);
|
|
|
|
// subtype - special processing for BLOBs
|
|
if (toMask & FLD_has_sub)
|
|
d->setBlobSubType(toDsc.getBlobSubType());
|
|
|
|
return true;
|
|
}
|
|
|