Commit cccfa9dc authored by Alexander Barkov's avatar Alexander Barkov

MDEV-19908 Add class Type_collection

parent 5de9dd7b
...@@ -356,22 +356,16 @@ SET SESSION debug_dbug="-d,Item_func_in"; ...@@ -356,22 +356,16 @@ SET SESSION debug_dbug="-d,Item_func_in";
# MDEV-12238 Add Type_handler::Item_func_{plus|minus|mul|div|mod}_fix_length_and_dec() # MDEV-12238 Add Type_handler::Item_func_{plus|minus|mul|div|mod}_fix_length_and_dec()
# #
SET debug_dbug='+d,num_op'; SET debug_dbug='+d,num_op';
CREATE TABLE t1 AS SELECT SELECT POINT(0,0)+POINT(0,0);
POINT(0,0)+POINT(0,0), ERROR HY000: Illegal parameter data types geometry and geometry for operation '+'
POINT(0,0)-POINT(0,0), SELECT POINT(0,0)-POINT(0,0);
POINT(0,0)*POINT(0,0), ERROR HY000: Illegal parameter data types geometry and geometry for operation '-'
POINT(0,0)/POINT(0,0), SELECT POINT(0,0)*POINT(0,0);
POINT(0,0) MOD POINT(0,0) LIMIT 0; ERROR HY000: Illegal parameter data types geometry and geometry for operation '*'
SHOW CREATE TABLE t1; SELECT POINT(0,0)/POINT(0,0);
Table Create Table ERROR HY000: Illegal parameter data types geometry and geometry for operation '/'
t1 CREATE TABLE `t1` ( SELECT POINT(0,0) MOD POINT(0,0);
`POINT(0,0)+POINT(0,0)` geometry DEFAULT NULL, ERROR HY000: Illegal parameter data types geometry and geometry for operation 'MOD'
`POINT(0,0)-POINT(0,0)` geometry DEFAULT NULL,
`POINT(0,0)*POINT(0,0)` geometry DEFAULT NULL,
`POINT(0,0)/POINT(0,0)` geometry DEFAULT NULL,
`POINT(0,0) MOD POINT(0,0)` geometry DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
CREATE TABLE t1 AS SELECT CREATE TABLE t1 AS SELECT
POINT(0,0)+'0', POINT(0,0)+'0',
POINT(0,0)-'0', POINT(0,0)-'0',
......
...@@ -73,15 +73,21 @@ SET SESSION debug_dbug="-d,Item_func_in"; ...@@ -73,15 +73,21 @@ SET SESSION debug_dbug="-d,Item_func_in";
SET debug_dbug='+d,num_op'; SET debug_dbug='+d,num_op';
# (GEOMETRY,GEOMETRY) gives GEOMETRY for all operators # (GEOMETRY,GEOMETRY) goes through
CREATE TABLE t1 AS SELECT # Type_collection_geometry::aggregate_for_num_op() which fails.
POINT(0,0)+POINT(0,0), # Type pairs from Type_handler_data::m_type_aggregator_xxx are not even tested,
POINT(0,0)-POINT(0,0), # as both sides are from the same type collection.
POINT(0,0)*POINT(0,0),
POINT(0,0)/POINT(0,0), --error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
POINT(0,0) MOD POINT(0,0) LIMIT 0; SELECT POINT(0,0)+POINT(0,0);
SHOW CREATE TABLE t1; --error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
DROP TABLE t1; SELECT POINT(0,0)-POINT(0,0);
--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
SELECT POINT(0,0)*POINT(0,0);
--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
SELECT POINT(0,0)/POINT(0,0);
--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
SELECT POINT(0,0) MOD POINT(0,0);
# (GEOMETRY,VARCHAR) gives GEOMETRY for all operators # (GEOMETRY,VARCHAR) gives GEOMETRY for all operators
CREATE TABLE t1 AS SELECT CREATE TABLE t1 AS SELECT
......
...@@ -70,8 +70,83 @@ Type_handler_blob_compressed type_handler_blob_compressed; ...@@ -70,8 +70,83 @@ Type_handler_blob_compressed type_handler_blob_compressed;
Type_handler_interval_DDhhmmssff type_handler_interval_DDhhmmssff; Type_handler_interval_DDhhmmssff type_handler_interval_DDhhmmssff;
class Type_collection_std: public Type_collection
{
public:
const Type_handler *aggregate_for_result(const Type_handler *a,
const Type_handler *b)
const override
{
return Type_handler::aggregate_for_result_traditional(a, b);
}
const Type_handler *aggregate_for_comparison(const Type_handler *a,
const Type_handler *b)
const override;
const Type_handler *aggregate_for_min_max(const Type_handler *a,
const Type_handler *b)
const override;
const Type_handler *aggregate_for_num_op(const Type_handler *a,
const Type_handler *b)
const override;
};
static Type_collection_std type_collection_std;
const Type_collection *Type_handler::type_collection() const
{
return &type_collection_std;
}
#ifdef HAVE_SPATIAL #ifdef HAVE_SPATIAL
Type_handler_geometry type_handler_geometry; Type_handler_geometry type_handler_geometry;
class Type_collection_geometry: public Type_collection
{
const Type_handler *aggregate_common(const Type_handler *a,
const Type_handler *b) const
{
DBUG_ASSERT(a == &type_handler_geometry);
DBUG_ASSERT(b == &type_handler_geometry);
return &type_handler_geometry;
}
public:
const Type_handler *aggregate_for_result(const Type_handler *a,
const Type_handler *b)
const override
{
return aggregate_common(a, b);
}
const Type_handler *aggregate_for_comparison(const Type_handler *a,
const Type_handler *b)
const override
{
return aggregate_common(a, b);
}
const Type_handler *aggregate_for_min_max(const Type_handler *a,
const Type_handler *b)
const override
{
return aggregate_common(a, b);
}
const Type_handler *aggregate_for_num_op(const Type_handler *a,
const Type_handler *b)
const override
{
return NULL;
}
};
static Type_collection_geometry type_collection_geometry;
const Type_collection *Type_handler_geometry::type_collection() const
{
return &type_collection_geometry;
}
#endif #endif
...@@ -80,6 +155,14 @@ bool Type_handler_data::init() ...@@ -80,6 +155,14 @@ bool Type_handler_data::init()
#ifdef HAVE_SPATIAL #ifdef HAVE_SPATIAL
#ifndef DBUG_OFF #ifndef DBUG_OFF
/*
The rule (geometry,geometry)->geometry is needed here to make sure
(in gis-debug.test) that is does not affect anything, and this pair
returns an error in an expression like (POINT(0,0)+POINT(0,0)).
Both sides are from the same type collection here,
so aggregation goes only through Type_collection_xxx::aggregate_yyy()
and never reaches Type_aggregator::find_handler().
*/
if (m_type_aggregator_non_commutative_test.add(&type_handler_geometry, if (m_type_aggregator_non_commutative_test.add(&type_handler_geometry,
&type_handler_geometry, &type_handler_geometry,
&type_handler_geometry) || &type_handler_geometry) ||
...@@ -93,9 +176,6 @@ bool Type_handler_data::init() ...@@ -93,9 +176,6 @@ bool Type_handler_data::init()
m_type_aggregator_for_result.add(&type_handler_geometry, m_type_aggregator_for_result.add(&type_handler_geometry,
&type_handler_null, &type_handler_null,
&type_handler_geometry) || &type_handler_geometry) ||
m_type_aggregator_for_result.add(&type_handler_geometry,
&type_handler_geometry,
&type_handler_geometry) ||
m_type_aggregator_for_result.add(&type_handler_geometry, m_type_aggregator_for_result.add(&type_handler_geometry,
&type_handler_hex_hybrid, &type_handler_hex_hybrid,
&type_handler_long_blob) || &type_handler_long_blob) ||
...@@ -117,9 +197,6 @@ bool Type_handler_data::init() ...@@ -117,9 +197,6 @@ bool Type_handler_data::init()
m_type_aggregator_for_result.add(&type_handler_geometry, m_type_aggregator_for_result.add(&type_handler_geometry,
&type_handler_string, &type_handler_string,
&type_handler_long_blob) || &type_handler_long_blob) ||
m_type_aggregator_for_comparison.add(&type_handler_geometry,
&type_handler_geometry,
&type_handler_geometry) ||
m_type_aggregator_for_comparison.add(&type_handler_geometry, m_type_aggregator_for_comparison.add(&type_handler_geometry,
&type_handler_null, &type_handler_null,
&type_handler_geometry) || &type_handler_geometry) ||
...@@ -1446,14 +1523,12 @@ const Type_handler *Type_handler_typelib::cast_to_int_type_handler() const ...@@ -1446,14 +1523,12 @@ const Type_handler *Type_handler_typelib::cast_to_int_type_handler() const
bool bool
Type_handler_hybrid_field_type::aggregate_for_result(const Type_handler *other) Type_handler_hybrid_field_type::aggregate_for_result(const Type_handler *other)
{ {
if (m_type_handler->is_traditional_type() && other->is_traditional_type()) const Type_collection *collection0= m_type_handler->type_collection();
{ if (collection0 == other->type_collection())
m_type_handler= other= collection0->aggregate_for_result(m_type_handler, other);
Type_handler::aggregate_for_result_traditional(m_type_handler, other); else
return false; other= type_handler_data->
} m_type_aggregator_for_result.find_handler(m_type_handler, other);
other= type_handler_data->
m_type_aggregator_for_result.find_handler(m_type_handler, other);
if (!other) if (!other)
return true; return true;
m_type_handler= other; m_type_handler= other;
...@@ -1572,37 +1647,42 @@ Type_handler_hybrid_field_type::aggregate_for_comparison(const Type_handler *h) ...@@ -1572,37 +1647,42 @@ Type_handler_hybrid_field_type::aggregate_for_comparison(const Type_handler *h)
{ {
DBUG_ASSERT(m_type_handler == m_type_handler->type_handler_for_comparison()); DBUG_ASSERT(m_type_handler == m_type_handler->type_handler_for_comparison());
DBUG_ASSERT(h == h->type_handler_for_comparison()); DBUG_ASSERT(h == h->type_handler_for_comparison());
const Type_collection *collection0= m_type_handler->type_collection();
if (!m_type_handler->is_traditional_type() || if (collection0 == h->type_collection())
!h->is_traditional_type()) h= collection0->aggregate_for_comparison(m_type_handler, h);
{ else
h= type_handler_data-> h= type_handler_data->
m_type_aggregator_for_comparison.find_handler(m_type_handler, h); m_type_aggregator_for_comparison.find_handler(m_type_handler, h);
if (!h) if (!h)
return true; return true;
m_type_handler= h; m_type_handler= h;
DBUG_ASSERT(m_type_handler == m_type_handler->type_handler_for_comparison()); DBUG_ASSERT(m_type_handler == m_type_handler->type_handler_for_comparison());
return false; return false;
} }
Item_result a= cmp_type(); const Type_handler *
Item_result b= h->cmp_type(); Type_collection_std::aggregate_for_comparison(const Type_handler *ha,
const Type_handler *hb) const
{
Item_result a= ha->cmp_type();
Item_result b= hb->cmp_type();
if (a == STRING_RESULT && b == STRING_RESULT) if (a == STRING_RESULT && b == STRING_RESULT)
m_type_handler= &type_handler_long_blob; return &type_handler_long_blob;
else if (a == INT_RESULT && b == INT_RESULT) if (a == INT_RESULT && b == INT_RESULT)
m_type_handler= &type_handler_longlong; return &type_handler_longlong;
else if (a == ROW_RESULT || b == ROW_RESULT) if (a == ROW_RESULT || b == ROW_RESULT)
m_type_handler= &type_handler_row; return &type_handler_row;
else if (a == TIME_RESULT || b == TIME_RESULT) if (a == TIME_RESULT || b == TIME_RESULT)
{ {
if ((a == TIME_RESULT) + (b == TIME_RESULT) == 1) if ((a == TIME_RESULT) + (b == TIME_RESULT) == 1)
{ {
/* /*
We're here if there's only one temporal data type: We're here if there's only one temporal data type:
either m_type_handler or h. either m_type_handler or h.
Temporal types bit non-temporal types.
*/ */
if (b == TIME_RESULT) const Type_handler *res= b == TIME_RESULT ? hb : ha;
m_type_handler= h; // Temporal types bit non-temporal types
/* /*
Compare TIMESTAMP to a non-temporal type as DATETIME. Compare TIMESTAMP to a non-temporal type as DATETIME.
This is needed to make queries with fuzzy dates work: This is needed to make queries with fuzzy dates work:
...@@ -1610,9 +1690,9 @@ Type_handler_hybrid_field_type::aggregate_for_comparison(const Type_handler *h) ...@@ -1610,9 +1690,9 @@ Type_handler_hybrid_field_type::aggregate_for_comparison(const Type_handler *h)
WHERE WHERE
ts BETWEEN '0000-00-00' AND '2010-00-01 00:00:00'; ts BETWEEN '0000-00-00' AND '2010-00-01 00:00:00';
*/ */
if (m_type_handler->type_handler_for_native_format() == if (res->type_handler_for_native_format() == &type_handler_timestamp2)
&type_handler_timestamp2) return &type_handler_datetime;
m_type_handler= &type_handler_datetime; return res;
} }
else else
{ {
...@@ -1624,19 +1704,15 @@ Type_handler_hybrid_field_type::aggregate_for_comparison(const Type_handler *h) ...@@ -1624,19 +1704,15 @@ Type_handler_hybrid_field_type::aggregate_for_comparison(const Type_handler *h)
to print DATE constants using proper format: to print DATE constants using proper format:
'YYYY-MM-DD' rather than 'YYYY-MM-DD 00:00:00'. 'YYYY-MM-DD' rather than 'YYYY-MM-DD 00:00:00'.
*/ */
if (m_type_handler->field_type() != h->field_type()) if (ha->field_type() != hb->field_type())
m_type_handler= &type_handler_datetime; return &type_handler_datetime;
return ha;
} }
} }
else if ((a == INT_RESULT || a == DECIMAL_RESULT) && if ((a == INT_RESULT || a == DECIMAL_RESULT) &&
(b == INT_RESULT || b == DECIMAL_RESULT)) (b == INT_RESULT || b == DECIMAL_RESULT))
{ return &type_handler_newdecimal;
m_type_handler= &type_handler_newdecimal; return &type_handler_double;
}
else
m_type_handler= &type_handler_double;
DBUG_ASSERT(m_type_handler == m_type_handler->type_handler_for_comparison());
return false;
} }
...@@ -1652,12 +1728,12 @@ Type_handler_hybrid_field_type::aggregate_for_comparison(const Type_handler *h) ...@@ -1652,12 +1728,12 @@ Type_handler_hybrid_field_type::aggregate_for_comparison(const Type_handler *h)
bool bool
Type_handler_hybrid_field_type::aggregate_for_min_max(const Type_handler *h) Type_handler_hybrid_field_type::aggregate_for_min_max(const Type_handler *h)
{ {
if (!m_type_handler->is_traditional_type() || const Type_collection *collection0= m_type_handler->type_collection();
!h->is_traditional_type()) if (collection0 == h->type_collection())
h= collection0->aggregate_for_min_max(m_type_handler, h);
else
{ {
/* /*
If at least one data type is non-traditional,
do aggregation for result immediately.
For now we suppose that these two expressions: For now we suppose that these two expressions:
- LEAST(type1, type2) - LEAST(type1, type2)
- COALESCE(type1, type2) - COALESCE(type1, type2)
...@@ -1666,78 +1742,73 @@ Type_handler_hybrid_field_type::aggregate_for_min_max(const Type_handler *h) ...@@ -1666,78 +1742,73 @@ Type_handler_hybrid_field_type::aggregate_for_min_max(const Type_handler *h)
This may change in the future. This may change in the future.
*/ */
h= type_handler_data-> h= type_handler_data->
m_type_aggregator_for_result.find_handler(m_type_handler, h); m_type_aggregator_for_result.find_handler(m_type_handler, h);
if (!h)
return true;
m_type_handler= h;
return false;
} }
if (!h)
return true;
m_type_handler= h;
return false;
}
Item_result a= cmp_type(); const Type_handler *
Item_result b= h->cmp_type(); Type_collection_std::aggregate_for_min_max(const Type_handler *ha,
const Type_handler *hb) const
{
Item_result a= ha->cmp_type();
Item_result b= hb->cmp_type();
DBUG_ASSERT(a != ROW_RESULT); // Disallowed by check_cols() in fix_fields() DBUG_ASSERT(a != ROW_RESULT); // Disallowed by check_cols() in fix_fields()
DBUG_ASSERT(b != ROW_RESULT); // Disallowed by check_cols() in fix_fields() DBUG_ASSERT(b != ROW_RESULT); // Disallowed by check_cols() in fix_fields()
if (a == STRING_RESULT && b == STRING_RESULT) if (a == STRING_RESULT && b == STRING_RESULT)
m_type_handler= return Type_collection_std::aggregate_for_result(ha, hb);
Type_handler::aggregate_for_result_traditional(m_type_handler, h); if (a == INT_RESULT && b == INT_RESULT)
else if (a == INT_RESULT && b == INT_RESULT)
{ {
// BIT aggregates with non-BIT as BIGINT // BIT aggregates with non-BIT as BIGINT
if (m_type_handler != h) if (ha != hb)
{ {
if (m_type_handler == &type_handler_bit) if (ha == &type_handler_bit)
m_type_handler= &type_handler_longlong; ha= &type_handler_longlong;
else if (h == &type_handler_bit) else if (hb == &type_handler_bit)
h= &type_handler_longlong; hb= &type_handler_longlong;
} }
m_type_handler= return Type_collection_std::aggregate_for_result(ha, hb);
Type_handler::aggregate_for_result_traditional(m_type_handler, h);
} }
else if (a == TIME_RESULT || b == TIME_RESULT) if (a == TIME_RESULT || b == TIME_RESULT)
{ {
if ((m_type_handler->type_handler_for_native_format() == if ((ha->type_handler_for_native_format() == &type_handler_timestamp2) +
&type_handler_timestamp2) + (hb->type_handler_for_native_format() == &type_handler_timestamp2) == 1)
(h->type_handler_for_native_format() ==
&type_handler_timestamp2) == 1)
{ {
/* /*
Handle LEAST(TIMESTAMP, non-TIMESTAMP) as DATETIME, Handle LEAST(TIMESTAMP, non-TIMESTAMP) as DATETIME,
to make sure fuzzy dates work in this context: to make sure fuzzy dates work in this context:
LEAST('2001-00-00', timestamp_field) LEAST('2001-00-00', timestamp_field)
*/ */
m_type_handler= &type_handler_datetime2; return &type_handler_datetime2;
} }
else if ((a == TIME_RESULT) + (b == TIME_RESULT) == 1) if ((a == TIME_RESULT) + (b == TIME_RESULT) == 1)
{ {
/* /*
We're here if there's only one temporal data type: We're here if there's only one temporal data type:
either m_type_handler or h. either m_type_handler or h.
Temporal types bit non-temporal types.
*/ */
if (b == TIME_RESULT) return (b == TIME_RESULT) ? hb : ha;
m_type_handler= h; // Temporal types bit non-temporal types
} }
else /*
{ We're here if both m_type_handler and h are temporal data types.
/* */
We're here if both m_type_handler and h are temporal data types. return Type_collection_std::aggregate_for_result(ha, hb);
*/
m_type_handler=
Type_handler::aggregate_for_result_traditional(m_type_handler, h);
}
}
else if ((a == INT_RESULT || a == DECIMAL_RESULT) &&
(b == INT_RESULT || b == DECIMAL_RESULT))
{
m_type_handler= &type_handler_newdecimal;
} }
else if ((a == INT_RESULT || a == DECIMAL_RESULT) &&
(b == INT_RESULT || b == DECIMAL_RESULT))
{ {
// Preserve FLOAT if two FLOATs, set to DOUBLE otherwise. return &type_handler_newdecimal;
if (m_type_handler != &type_handler_float || h != &type_handler_float)
m_type_handler= &type_handler_double;
} }
return false; // Preserve FLOAT if two FLOATs, set to DOUBLE otherwise.
if (ha == &type_handler_float && hb == &type_handler_float)
return &type_handler_float;
return &type_handler_double;
} }
...@@ -1772,8 +1843,8 @@ Type_handler_hybrid_field_type::aggregate_for_min_max(const char *funcname, ...@@ -1772,8 +1843,8 @@ Type_handler_hybrid_field_type::aggregate_for_min_max(const char *funcname,
const Type_handler * const Type_handler *
Type_handler::aggregate_for_num_op_traditional(const Type_handler *h0, Type_collection_std::aggregate_for_num_op(const Type_handler *h0,
const Type_handler *h1) const Type_handler *h1) const
{ {
Item_result r0= h0->cmp_type(); Item_result r0= h0->cmp_type();
Item_result r1= h1->cmp_type(); Item_result r1= h1->cmp_type();
...@@ -1814,17 +1885,15 @@ Type_handler_hybrid_field_type::aggregate_for_num_op(const Type_aggregator *agg, ...@@ -1814,17 +1885,15 @@ Type_handler_hybrid_field_type::aggregate_for_num_op(const Type_aggregator *agg,
const Type_handler *h1) const Type_handler *h1)
{ {
const Type_handler *hres; const Type_handler *hres;
if (h0->is_traditional_type() && h1->is_traditional_type()) const Type_collection *collection0= h0->type_collection();
{ if (collection0 == h1->type_collection())
set_handler(Type_handler::aggregate_for_num_op_traditional(h0, h1)); hres= collection0->aggregate_for_num_op(h0, h1);
return false; else
} hres= agg->find_handler(h0, h1);
if ((hres= agg->find_handler(h0, h1))) if (!hres)
{ return true;
set_handler(hres); m_type_handler= hres;
return false; return false;
}
return true;
} }
......
...@@ -87,6 +87,7 @@ class Vers_history_point; ...@@ -87,6 +87,7 @@ class Vers_history_point;
class Virtual_column_info; class Virtual_column_info;
class Conv_source; class Conv_source;
class ST_FIELD_INFO; class ST_FIELD_INFO;
class Type_collection;
#define my_charset_numeric my_charset_latin1 #define my_charset_numeric my_charset_latin1
...@@ -3281,12 +3282,10 @@ class Type_handler ...@@ -3281,12 +3282,10 @@ class Type_handler
DBUG_ASSERT(type != TIME_RESULT); DBUG_ASSERT(type != TIME_RESULT);
return get_handler_by_cmp_type(type); return get_handler_by_cmp_type(type);
} }
virtual const Type_collection *type_collection() const;
static const static const
Type_handler *aggregate_for_result_traditional(const Type_handler *h1, Type_handler *aggregate_for_result_traditional(const Type_handler *h1,
const Type_handler *h2); const Type_handler *h2);
static const
Type_handler *aggregate_for_num_op_traditional(const Type_handler *h1,
const Type_handler *h2);
virtual const Name name() const= 0; virtual const Name name() const= 0;
virtual const Name version() const { return m_version_default; } virtual const Name version() const { return m_version_default; }
...@@ -6372,6 +6371,7 @@ class Type_handler_geometry: public Type_handler_string_result ...@@ -6372,6 +6371,7 @@ class Type_handler_geometry: public Type_handler_string_result
bool is_param_long_data_type() const { return true; } bool is_param_long_data_type() const { return true; }
uint32 max_display_length_for_field(const Conv_source &src) const; uint32 max_display_length_for_field(const Conv_source &src) const;
uint32 calc_pack_length(uint32 length) const; uint32 calc_pack_length(uint32 length) const;
const Type_collection *type_collection() const override;
const Type_handler *type_handler_for_comparison() const; const Type_handler *type_handler_for_comparison() const;
bool type_can_have_key_part() const bool type_can_have_key_part() const
{ {
...@@ -6570,6 +6570,24 @@ class Type_handler_interval_DDhhmmssff: public Type_handler_long_blob ...@@ -6570,6 +6570,24 @@ class Type_handler_interval_DDhhmmssff: public Type_handler_long_blob
}; };
class Type_collection
{
public:
virtual ~Type_collection() {}
virtual const Type_handler *aggregate_for_result(const Type_handler *h1,
const Type_handler *h2)
const= 0;
virtual const Type_handler *aggregate_for_comparison(const Type_handler *h1,
const Type_handler *h2)
const= 0;
virtual const Type_handler *aggregate_for_min_max(const Type_handler *h1,
const Type_handler *h2)
const= 0;
virtual const Type_handler *aggregate_for_num_op(const Type_handler *h1,
const Type_handler *h2)
const= 0;
};
/** /**
A handler for hybrid type functions, e.g. A handler for hybrid type functions, e.g.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment