← Back to team overview

maria-developers team mailing list archive

Re: Fwd: mixing of user-defined data types with other data types

 

Hi Alexander,

I've reviewed your second patch. Comments inline.

>> +}
> >>
> >>
> >>  /*
> >> diff --git a/sql/field.h b/sql/field.h
> >> index 541da5a..fa84e7d 100644
> >> --- a/sql/field.h
> >> +++ b/sql/field.h
> >> @@ -835,7 +835,6 @@ class Field: public Value_source
> >>    virtual Item_result cmp_type () const { return result_type(); }
> >>    static bool type_can_have_key_part(enum_field_types);
> >>
> > The field_type_merge function breaks all the other naming patterns. We
> > have result_type, cmp_type real_type and now field_type_merge. Wouldn't
> it be
> > better to name it field_merge_type, to be consistent? Or since this
> > is a lookup kind of operation, perhaps name it
> (get|lookup)_merge_field_type?
> > I know it's not _exactly_ like result_type and compare_type, but it
> generally
> > is used in a simillar context.
>
> Don't we make our life harder in respect of merging when renaming?
>
> Usually I try not to rename existing functions:
> - There is a chance that we'll merge something from earlier versions,
>   and renaming can cause conflicts.
> - Also, developers are used to this name and can do
>   "grep field_type_merge" or similar when searching the code.
>

I see your point. I am not 100% in favor of the idea of protecting
ourselves (making life easier) in future merges, as it tends to keep us
stuck with the same version of (often) difficult to read code. For this
case, I guess we can live with it, but I'll keep on pointing such things in
future reviews. :)


>
> >>
> >>    static enum_field_types field_type_merge(enum_field_types,
> enum_field_types);
> >> -  static Item_result result_merge_type(enum_field_types);
> >>    virtual bool eq(Field *field)
> >>    {
> >>      return (ptr == field->ptr && null_ptr == field->null_ptr &&
> >> diff --git a/sql/item.cc b/sql/item.cc
> >> index c97f41f..c9b6155 100644
> >> --- a/sql/item.cc
> >> +++ b/sql/item.cc
> >> @@ -9657,20 +9657,8 @@ Item_type_holder::Item_type_holder(THD *thd,
> Item *item)
> >>    maybe_null= item->maybe_null;
> >>    collation.set(item->collation);
> >>    get_full_info(item);
> >> -  /**
> >> -    Field::result_merge_type(real_field_type()) should be equal to
> >> -    result_type(), with one exception when "this" is a Item_field for
> >> -    a BIT field:
> >> -    - Field_bit::result_type() returns INT_RESULT, so does its
> Item_field.
> >> -    - Field::result_merge_type(MYSQL_TYPE_BIT) returns STRING_RESULT.
> >> -    Perhaps we need a new method in Type_handler to cover these type
> >> -    merging rules for UNION.
> >> -  */
> >> -  DBUG_ASSERT(real_field_type() == MYSQL_TYPE_BIT ||
> >> -              Item_type_holder::result_type()  ==
> >> -
> Field::result_merge_type(Item_type_holder::real_field_type()));
> >>    /* fix variable decimals which always is NOT_FIXED_DEC */
> >> -  if (Field::result_merge_type(real_field_type()) == INT_RESULT)
> >>
> > Alright so this seems to be fixed here, looking at Type_handler_bit,
> > inheriting from Type_handler_int_result. Do we test this somewhere
> though?
> > I couldn't find it in the test case, perhaps you can point it out to me.
>
>
> In theory, decimals is always 0 if result_type() is INT_RESULT.
> But I'm not fully sure that in reality non of the Items return
> non-zero decimals in combination with INT_RESULT.
> There's so many hacks in the code, so we combination can
> be used somewhere.
>
> I just tried to comment out these two lines:
> > -  if (Item_type_holder::result_type() == INT_RESULT)
> > -    decimals= 0;
> > +//  if (Item_type_holder::result_type() == INT_RESULT)
> > +//    decimals= 0;
> both in the constructor and in the method join_types()
> and run test. Nothing failed.
>
> So perhaps these two lines can be just replaced to:
>
> DBUG_ASSERT(decimals == 0 ||
>             Item_type_holder::result_type() != INT_RESULT);
>
> push, and see.
>
> Any suggestions?
>

This is indeed ugly. I've tried to look into the calling places, then to
backtrack from there but I gave up after about 30 minutes of seeing a never
ending branching possibility of items. Please add the assert.


>
> > Let's discuss about cleaning this up later. To me it feels like this
> Item does
> > not really belong in the Item class and should be factored out. Probably
> > a whole project on its own :)
>
> I made attempts to move Item_type_holder out of the Item hierarchy in
> the past, but failed. It caused too much refactoring, because
> Item_type_holder is used with List<Item> all around the UNION
> and table creation code.
>
> Perhaps we should make another attempt eventually.
> I suggest to postpone this at least after the main Type_handler related
> changes are done.
>
>
I agree.


> >>
> >>        item_decimals= 0;
> >>      decimals= MY_MAX(decimals, item_decimals);
> >>    }
> >> diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc
> >> index 98b179b..e5e366e 100644
> >> --- a/sql/item_cmpfunc.cc
> >> +++ b/sql/item_cmpfunc.cc
> >> @@ -180,32 +179,40 @@ static int agg_cmp_type(Item_result *type, Item
> **items, uint nitems)
> >>    @return aggregated field type.
> >>  */
> >>
> >> -enum_field_types agg_field_type(Item **items, uint nitems,
> >> -                                bool treat_bit_as_number)
> >>
> > Why is this function in item_cmpfunc.cc and not in sql_type.cc?
>
> Moving this to sql_type.cc can cause additional merge conflicts.
>
> But perhaps it's Ok to move it, as it gets changed significantly anyway
> (not logically, but textually).
>
>
I vote for moving it. I hate it when the implementation is all over the
place.


>
> >> +bool
> >> +Type_handler_hybrid_field_type::aggregate_for_result(const char
> *funcname,
> >> +                                                     Item **items,
> uint nitems,
> >> +                                                     bool
> treat_bit_as_number)
> >>  {
> > I would move uint i to be a local for variable. This is a C-style loop.
> > Is there a compiler that doesn't support this in one of our builders?
>
> Done.
>
> > Also, how about size_t instead of uint? (Probably not necessary but
> > wlad made a point of prefering to use that for iterators and such).
>
> size_t is fine. But this should be done together with changing
> Item_args::arg_count, which is passed to this method.
> I suggest not to do this change under terms of this patch.
>
>
I agree.


> <cut>
>
> >> diff --git a/sql/sql_type.cc b/sql/sql_type.cc
> >> index 8746595..397b5cf 100644
> >> --- a/sql/sql_type.cc
> >> +++ b/sql/sql_type.cc
> >> @@ -54,6 +51,41 @@ static Type_handler_set         type_handler_set;
> >>  Type_handler_null        type_handler_null;
> >>  Type_handler_row         type_handler_row;
> >>  Type_handler_varchar     type_handler_varchar;
> >> +Type_handler_newdecimal  type_handler_newdecimal;
> >> +Type_handler_longlong    type_handler_longlong;
> >> +Type_handler_bit         type_handler_bit;
> >> +
> >> +
> > I'm sure there's a better way to write this so that it gets initialized
> at
> > compile time instead of at runtime (before main).
> > Perhaps we can define the Type_aggregator differently. I need to look
> this
> > Up. For now it will work.
> > Standard offers no guarantees regarding the order, but it shouldn't
> matter
> > for us as the address shouldn't change for global objects during
> > initialization.
>
> The part from Static_data_initializer should eventually be gone.
> We need to extend the Type_handler API first, so a data type handler
> (plugin) can provide an array of its aggregation rules.
> So in the future the server will do the calls like
> type_aggregator_for_result.add() when loading a new data
> type plugin, either on startup, or during INSTALL PLUGIN.
>
> For now, type handlers reside statically in the server anyway,
> so this should work fine, and I think it's 100% safe.
> As you noted, the addresses should not change even if
> the get initialized in some non-reliable order.
>
>
> I chose this approach because I didn't want to expose this code to
> mysqld.cc now. Exposing it would be too early at this point.
>
>
I agree.


> I don't like this class too much. One can easily break it by either
> changing
> > LEX_CSTRING::str or LEX_CSTRING::length without changing the other one.
> > I suggest we make the inheritance private so that the only way to access
> the
> > members is through the methods available.
>
> Done: I changed it to derive privately.
>
> > A suggestion I have is to create a generic "String" class that has this
> same
> > behaviour, without calling it Name. Afterwards typedefing it to Name.
>
> Earlier I proposed to add similar classes to struct.h,
> something like this:
>
> struct Lex_cstring_st: public LEX_CSTRING; // without initialization
>
> class Lex_cstring: protected Lex_cstring_st; // with initialization
>
> and to move all global functions operating
> on LEX_CSTRING as methods into these new struct and class.
>
>
> But Monty disliked it. Monty thinks that having more globally visible
> classes makes the code harder to read.
> I think it makes the code easier to read, to use, and to reuse.
> We never could agree :)
>
> So if we're adding Lex_cstring_st at this point we should be ready:
> - either to convince Monty that this is good
> - or to revert our changes in struct.h and move the class locally
>   to sql_type.h again.
>
> Let's go with Lex_cstring_st in struct.h ?
>
> :)
>

I say we keep it as is for the moment. The patch changes enough as is. It
can happen in a follow up, once we're done.


>> +  static const
> >> +  Type_handler *aggregate_for_result_traditional(const Type_handler
> *h1,
> >> +                                                 const Type_handler
> *h2);
> >> +
> > This function can return a const reference from a const static object
> > within the class's namespace. Why create an object every time this is
> called?
> > Compiler might optimize it, but why risk it?
> > This goes for all the implementations.
>
> For my opinion it's 100% safe. There is no any risk here.
> It should be the same safe with doing "return 10" from a "int" function.
>
> It just reserves 16 bytes for a LEX_CSTRING on the stack and populates
> it, and then the caller uses this populated LEX_CSTRING to access
> its members though the methods ptr() or length().
> Some compilers should probably be able even to use registers instead
> of stack for this. But my intent was not to rely on using registers.
> I just found this style as the shortest possible and the most readable.


> As for performance, it requires the same amount of resources with for
> example passing LEX_CSTRING by value to some function, or just
> to create a local LEX_CSTRING/LEX_STRING variable.
>
> Here are some examples in the existing code:
>
> sp_sql->append(C_STRING_WITH_LEN("CREATE "));
> sp_sql->append(C_STRING_WITH_LEN("PROCEDURE "));
> LEX_STRING pw= { C_STRING_WITH_LEN("password") };
>
> Notice, we don't create static LEX_STRING or LEX_CSTRING for all
> possible strings we need in the server.
> The proposed code should be exactly the same cheap with these examples.
>
> Another approach would be to:
> - have a static variable for every Type_handler name
> - return this variable from the method name().
>
> This could give slight benefits when we need ptr() without length(),
> or the other way around. And the caller in item.cc actually uses
> name().ptr() without name().length().
>
> But as name() will be used for errors and for DBUG_PRINT mostly,
> so I thought it would be more useful to save the number of lines.
> And it's easier to read this way.
> You can see the name right inside the class definition,
> you don't have to go to sql_class.cc to see it.
>


The solution I propose is this:
// in sql_type.h
Type_handler {
    const Name& name() const = 0;
}

Type_handler_xxx {
     static const Name xxx_name;   // xxx
     const Name& name() const;
}

// In sql_type.cc
Type_handler_xxx::xxx_name = Name(C_STRING_WITH_LEN("xxx"));
const Name& Type_handler_xxx::name() const
{
   return xxx_name;
}

Performance wise this is superior. Have a look at assembly for the
following function calls:

// these are defined in say sql_type.cc;
Type_handler *give_random_type_handler()
{
  return new Type_handler_date();
}

bool use_random_name(const Name& name)
{
  printf("%s\n", name.ptr());
  return true;
}

Using them in sql_<something_else>.cc

////// Assembly generated by proposed patch: (GCC 6.2.0)
  Type_handler *hnd = give_random_type_handler();
   1cc5c:       e8 00 00 00 00          call   1cc61
  const Name& n = hnd->name();
   1cc61:       48 8b 10                mov    rdx,QWORD PTR [rax]
   1cc64:       48 89 c7                mov    rdi,rax
   1cc67:       ff 12                   call   QWORD PTR [rdx]
   1cc69:       48 8d bd 00 cf ff ff    lea    rdi,[rbp-0x3100]
   1cc70:       48 89 85 00 cf ff ff    mov    QWORD PTR [rbp-0x3100],rax
   1cc77:       48 89 95 08 cf ff ff    mov    QWORD PTR [rbp-0x30f8],rdx

  use_random_name(n);
   1cc7e:       e8 00 00 00 00          call   1cc83

///// Assembly generated by my suggestion: (GCC 6.2.0)
  Type_handler *hnd = give_random_type_handler();
   1cc5c:       e8 00 00 00 00          call   1cc61
  const Name& n = hnd->name();
   1cc61:       48 8b 10                mov    rdx,QWORD PTR [rax]
   1cc64:       48 89 c7                mov    rdi,rax
   1cc67:       ff 12                   call   QWORD PTR [rdx]
  use_random_name(n);
   1cc69:       48 89 c7                mov    rdi,rax
   1cc6c:       e8 00 00 00 00          call   1cc71

The difference is that the optimized version only uses registers, while the
first version has to write to memory (or cache I guess).

To me, readability seems the same for both implementations. I'm not going
to insist on this too much, but I believe it's best practice to have this
sort of code that passes a const reference, instead of always creating a
new item on the stack. We can discuss more in detail on this if you'd like.


So to sum up, please add the assert you proposed and remove setting
decimals to 0 as a "safety measure". I would like to make use of a static
variable instead of how we now construct "Name" objects for every name()
call.

Ok to push otherwise.

Regards,
Vicențiu

References