Hi, Alexander,

Looks good, thanks. Great commit comment too.

Just a few questions and test suggestions - see below.

Regards,
Sergei
Chief Architect, MariaDB Server
and secur...@mariadb.org

On Aug 23, Alexander Barkov wrote:
> revision-id: c46e7c5cb64 (mariadb-11.4.1-5-gc46e7c5cb64)
> parent(s): ae6684d79f4
> author: Alexander Barkov
> committer: Alexander Barkov
> timestamp: 2024-02-20 13:49:54 +0400
> message:
> 
> MDEV-12252 ROW data type for stored function return values
 
> diff --git a/mysql-test/main/sp-anchor-row-type-table.test 
> b/mysql-test/main/sp-anchor-row-type-table.test
> index 7123f9160db..2fbcfbffc1b 100644
> --- a/mysql-test/main/sp-anchor-row-type-table.test
> +++ b/mysql-test/main/sp-anchor-row-type-table.test
> @@ -939,3 +939,70 @@ DROP TABLE t1;
>  --echo #
>  --echo # End of 10.4 tests
>  --echo #
> +
> +--echo #
> +--echo # Start of 11.3 tests
> +--echo #
> +
> +--echo #
> +--echo # MDEV-12252 ROW data type for stored function return values
> +--echo #
> +
> +--error ER_PARSE_ERROR
> +CREATE FUNCTION f1() RETURNS ROW TYPE OF step1.step2.step3 RETURN ROW(1,2);
> +
> +CREATE FUNCTION f1() RETURNS ROW TYPE OF t1 RETURN ROW(1,2);

you don't resolve t1 at CREATE FUNCTION time. why?
is it how TYPE OF works in DECLARE too?

> +SHOW CREATE FUNCTION f1;
> +--error ER_NO_SUCH_TABLE
> +SELECT f1();
> +DROP FUNCTION f1;
> +
> +CREATE FUNCTION f1() RETURNS ROW TYPE OF test.t1 RETURN ROW(1,2);
> +SHOW CREATE FUNCTION f1;
> +--error ER_NO_SUCH_TABLE
> +SELECT f1();
> +DROP FUNCTION f1;
> +
> +
> +CREATE TABLE t1 (a INT, b VARCHAR(32));
> +CREATE FUNCTION f1() RETURNS ROW TYPE OF test.t1 RETURN (SELECT * FROM t1);
> +SHOW CREATE FUNCTION f1;
> +DELIMITER $$;
> +CREATE PROCEDURE p1()
> +BEGIN
> +  DECLARE r ROW TYPE OF t1 DEFAULT f1();
> +  SELECT r.a, r.b;
> +END;
> +$$
> +DELIMITER ;$$
> +
> +# Testing with no rows
> +CALL p1();
> +--error ER_OPERAND_COLUMNS
> +SELECT f1();
> +SELECT f1()=ROW(1,'b1') AS c;
> +SELECT f1()=ROW(NULL,NULL) AS c;
> +
> +# Testing with one row
> +INSERT INTO t1 VALUES (1,'b1');
> +CALL p1();
> +--error ER_OPERAND_COLUMNS
> +SELECT f1();
> +SELECT f1()=ROW(1,'b1') AS c;
> +SELECT f1()=ROW(1,'') AS c;
> +SELECT f1()=ROW(2,'b1') AS c;
> +SELECT f1()=ROW(1,NULL) AS c;
> +SELECT f1()=ROW(NULL,'b1') AS c;

do one more SHOW CREATE FUNCTION here

> +
> +# Testing with two rows
> +INSERT INTO t1 VALUES (2,'b2');
> +--error ER_SUBQUERY_NO_1_ROW
> +CALL p1();
> +--error ER_OPERAND_COLUMNS
> +SELECT f1();
> +--error ER_SUBQUERY_NO_1_ROW
> +SELECT f1()=ROW(1,'b1') AS c;
> +
> +DROP PROCEDURE p1;
> +DROP FUNCTION f1;
> +DROP TABLE t1;
> diff --git a/mysql-test/main/sp-anchor-type.test 
> b/mysql-test/main/sp-anchor-type.test
> index 0e24ef900d8..fed53fd011a 100644
> --- a/mysql-test/main/sp-anchor-type.test
> +++ b/mysql-test/main/sp-anchor-type.test
> @@ -767,3 +767,78 @@ BEGIN NOT ATOMIC
>  END;
>  $$
>  DELIMITER ;$$
> +
> +
> +--echo #
> +--echo # Start of 11.3 tests
> +--echo #
> +
> +--echo #
> +--echo # MDEV-12252 ROW data type for stored function return values
> +--echo #
> +
> +--error ER_PARSE_ERROR
> +CREATE FUNCTION f1() RETURNS TYPE OF a RETURN 1;
> +
> +CREATE FUNCTION f1() RETURNS TYPE OF t1.a RETURN (SELECT min(a) FROM t1);
> +SHOW CREATE FUNCTION f1;
> +--error ER_NO_SUCH_TABLE
> +SELECT f1();
> +CREATE TABLE t1 (b INT);
> +--error ER_BAD_FIELD_ERROR
> +SELECT f1();
> +DROP TABLE t1;
> +DROP FUNCTION f1;
> +
> +CREATE DATABASE db1;
> +DELIMITER $$;
> +CREATE FUNCTION db1.f1() RETURNS TYPE OF db1.t1.a
> +BEGIN
> +  RETURN (SELECT min(a) FROM t1);
> +END;
> +$$
> +DELIMITER ;$$
> +SHOW CREATE FUNCTION db1.f1;
> +--error ER_NO_SUCH_TABLE
> +SELECT db1.f1();
> +CREATE TABLE db1.t1 (b TIME);
> +--error ER_BAD_FIELD_ERROR
> +SELECT db1.f1();
> +DROP TABLE db1.t1;
> +CREATE TABLE db1.t1 (a TIME);
> +SELECT db1.f1();
> +INSERT INTO db1.t1 VALUES ('10:20:30');
> +SELECT db1.f1();
> +DROP TABLE db1.t1;
> +DROP FUNCTION db1.f1;
> +DROP DATABASE db1;
> +
> +CREATE TABLE t1 (a TIME);
> +CREATE FUNCTION f1() RETURNS TYPE OF test.t1.a RETURN (SELECT min(a) FROM 
> t1);
> +SHOW CREATE FUNCTION f1;
> +SELECT f1();
> +DROP FUNCTION f1;
> +DROP TABLE t1;
> +
> +CREATE TABLE t1 (a TIME);
> +DELIMITER $$;
> +CREATE FUNCTION f1() RETURNS TYPE OF t1.a
> +BEGIN
> +  RETURN (SELECT min(a) FROM t1);
> +END;
> +$$
> +DELIMITER ;$$
> +SHOW CREATE FUNCTION f1;
> +SELECT f1();
> +CREATE TABLE t2 AS SELECT f1();
> +SHOW CREATE TABLE t2;
> +SELECT * FROM t2;
> +DROP TABLE t2;
> +INSERT INTO t1 VALUES ('10:20:30');
> +SELECT f1();
> +CREATE TABLE t2 AS SELECT f1();
> +SHOW CREATE TABLE t2;
> +SELECT * FROM t2;
> +DROP TABLE t2;

please, also try here

 DROP TABLE t1;
 CREATE TABLE t1 (a INT);
 INSERT INTO t1 VALUES (10);
 CREATE TABLE t2 AS SELECT f1();

the point is to change TYPE OF t1.a
after the function has been successfully called,
and thus is already in the sp cache.

> +DROP FUNCTION f1;
> +DROP TABLE t1;
> diff --git a/mysql-test/suite/compat/oracle/t/sp-package.test 
> b/mysql-test/suite/compat/oracle/t/sp-package.test
> index aefede41c8b..7c231d736c5 100644
> --- a/mysql-test/suite/compat/oracle/t/sp-package.test
> +++ b/mysql-test/suite/compat/oracle/t/sp-package.test
> @@ -3109,3 +3109,79 @@ DROP TABLE t1;
>  DROP FUNCTION f1_deterministic;
>  DROP FUNCTION f2_not_deterministic;
>  DROP PACKAGE pkg1;
> +
> +--echo #
> +--echo # MDEV-12252 ROW data type for stored function return values
> +--echo #
> +
> +--echo #
> +--echo # Testing fixed ROW type with package routines
> +--echo #
> +
> +DELIMITER $$;
> +CREATE PACKAGE pkg
> +AS
> +  FUNCTION f1() RETURN ROW(a INT, b VARCHAR(32));
> +  PROCEDURE p1(r ROW(a INT, b VARCHAR(32)));
> +  PROCEDURE p2();
> +END;
> +$$
> +CREATE PACKAGE BODY pkg
> +AS
> +  FUNCTION f1() RETURN ROW(a INT, b VARCHAR(32)) AS
> +  BEGIN
> +    RETURN ROW(1,'b1');
> +  END;
> +  PROCEDURE p1(r ROW(a INT, b VARCHAR(32))) AS
> +  BEGIN
> +    SELECT r.a, r.b;
> +  END;
> +  PROCEDURE p2() AS
> +  BEGIN
> +    CALL p1(f1());
> +  END; 
> +END;
> +$$
> +DELIMITER ;$$
> +CALL pkg.p1(pkg.f1());
> +CALL pkg.p2;
> +DROP PACKAGE pkg;
> +
> +
> +--echo #
> +--echo # Testing table%ROWTYPE with package routines
> +--echo #

now you can also add package tests outside of oracle mode

> +
> +CREATE TABLE t1 (a INT, b VARCHAR(32));
> +INSERT INTO t1 VALUES (1,'b1');
> +DELIMITER /;
> +CREATE PACKAGE pkg
> +AS
> +  FUNCTION f1 RETURN t1%ROWTYPE;
> +  PROCEDURE p1(r t1%ROWTYPE);
> +  PROCEDURE p2;
> +END;
> +/
> +CREATE PACKAGE BODY pkg
> +AS
> +  FUNCTION f1 RETURN t1%ROWTYPE AS
> +    r t1%ROWTYPE;
> +  BEGIN
> +    SELECT * INTO r FROM t1;
> +    RETURN r;
> +  END;
> +  PROCEDURE p1(r t1%ROWTYPE) AS
> +  BEGIN
> +    SELECT r.a || ' ' || r.b;
> +  END;
> +  PROCEDURE p2 AS
> +  BEGIN
> +    p1(f1());
> +  END; 
> +END;
> +/
> +DELIMITER ;/
> +CALL pkg.p1(pkg.f1());
> +CALL pkg.p2;
> +DROP PACKAGE pkg;
> +DROP TABLE t1;
> diff --git a/sql/field.h b/sql/field.h
> index 7f1c243a180..7af069a9735 100644
> --- a/sql/field.h
> +++ b/sql/field.h
> @@ -1373,6 +1374,20 @@ class Field: public Value_source
>      in str and restore it with set() if needed
>    */
>    virtual void sql_type(String &str) const =0;
> +  void sql_type_for_sp_returns(String &str) const

This is oddly specific. Strange that we don't need to print the field
type with charset/collation anywhere else, but for sp returns we do.

> +  {
> +    sql_type(str);
> +    if (has_charset())
> +    {
> +      str.append(STRING_WITH_LEN(" CHARSET "));
> +      str.append(charset()->cs_name);
> +      if (Charset(charset()).can_have_collate_clause())
> +      {
> +        str.append(STRING_WITH_LEN(" COLLATE "));
> +        str.append(charset()->coll_name);
> +      }
> +    }
> +  }
>    virtual void sql_rpl_type(String *str) const { sql_type(*str); }
>    virtual uint size_of() const =0;           // For new field
>    inline bool is_null(my_ptrdiff_t row_offset= 0) const
> diff --git a/sql/field.cc b/sql/field.cc
> index 35be045620d..23b050de221 100644
> --- a/sql/field.cc
> +++ b/sql/field.cc
> @@ -2768,6 +2768,32 @@ bool Field_row::sp_prepare_and_store_item(THD *thd, 
> Item **value)
>  }
>  
>  
> +uint Field_row::cols() const
> +{
> +  // The table with ROW members must be already instantiated
> +  DBUG_ASSERT(m_table);
> +  return m_table->s->fields;
> +}
> +
> +
> +void Field_row::sql_type(String &res) const

would be more logical to call this Field_row::sql_type_for_sp_returns()

> +{
> +  res.set_ascii(STRING_WITH_LEN("row("));
> +  for (uint i= 0; i < m_table->s->fields; i++)
> +  {
> +    if (i > 0)
> +      res.append(',');
> +    Field *field= m_table->field[i];
> +    res.append(field->field_name);
> +    res.append(' ');
> +    StringBuffer<64> col;
> +    field->sql_type_for_sp_returns(col);
> +    res.append(col.to_lex_cstring());
> +  }
> +  res.append(')');
> +}
> +
> +
>  /****************************************************************************
>    Functions for the Field_decimal class
>    This is an number stored as a pre-space (or pre-zero) string
> diff --git a/sql/item.cc b/sql/item.cc
> index 54ad511cb6b..0a125752979 100644
> --- a/sql/item.cc
> +++ b/sql/item.cc
> @@ -3001,10 +3003,31 @@ Item_sp::init_result_field(THD *thd, uint max_length, 
> uint maybe_null,
>    dummy_table->s->table_name= empty_clex_str;
>    dummy_table->maybe_null= maybe_null;
>  
> +  if (m_sp->m_return_field_def.is_column_type_ref() &&
> +      m_sp->m_return_field_def.column_type_ref()->
> +        resolve_type_ref(thd, &m_sp->m_return_field_def))
> +    DBUG_RETURN(TRUE);

Here you overwrite old m_return_field_def value which was a column_type_ref
with its actual type, don't you?

What will happen if you change the column's type
after the sp was already executed once?

> +
>    if (!(sp_result_field= m_sp->create_result_field(max_length, name,
>                                                     dummy_table)))
>     DBUG_RETURN(TRUE);
>  
> +  /*
> +    In case of a ROW return type we need to create Item_field_row
> +    on top of Field_row, and remember it in sp_result_item_field_row.
> +    ROW members are later accessed using sp_result_item_field_row->addr(i),
> +    e.g. when copying the function return value to a local variable.
> +    For scalar return types no Item_field is needed around sp_result_field,
> +    as the value is fetched directly from sp_result_field,
> +    inside Item_func_sp::val_xxx() methods.

Sorry, I don't understand that. Why cannot you use Field_row here?

> +  */
> +  if (Field_row *field_row= dynamic_cast<Field_row*>(sp_result_field))
> +  {
> +    if (!(sp_result_item_field_row=
> +        m_sp->m_return_field_def.make_item_field_row(thd, field_row)))
> +      DBUG_RETURN(true);
> +  }
> +
>    if (sp_result_field->pack_length() > sizeof(result_buf))
>    {
>      void *tmp;
> diff --git a/sql/sp.cc b/sql/sp.cc
> index ddaeeb8cb7a..7eab8304c31 100644
> --- a/sql/sp.cc
> +++ b/sql/sp.cc
> @@ -1091,18 +1136,19 @@ sp_returns_type(THD *thd, String &result, const 
> sp_head *sp)
>    bzero((char*) &share, sizeof(share));
>    table.in_use= thd;
>    table.s = &share;
> -  field= sp->create_result_field(0, 0, &table);
> -  field->sql_type(result);
> +  field= create_result_field(0, 0, &table);
>  
> -  if (field->has_charset())
> +  if (m_return_field_def.is_row())
>    {
> -    result.append(STRING_WITH_LEN(" CHARSET "));
> -    result.append(field->charset()->cs_name);
> -    if (Charset(field->charset()).can_have_collate_clause())
> -    {
> -      result.append(STRING_WITH_LEN(" COLLATE "));
> -      result.append(field->charset()->coll_name);
> -    }
> +    Field_row *field_row= dynamic_cast<Field_row*>(field);
> +    if (!field_row->row_create_fields(
> +           thd, m_return_field_def.row_field_definitions()))
> +      field->sql_type(result);

when is this used? you're creating a TABLE and Field's -
only to print types and charsets. This might be tolerable, but only if it's
not needed often

> +  }
> +  else
> +  {
> +    DBUG_ASSERT(m_return_field_def.type_handler()->is_scalar_type());
> +    field->sql_type_for_sp_returns(result);
>    }
>  
>    delete field;
> diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
> index 5977e309ae4..f11e4f89466 100644
> --- a/sql/sql_yacc.yy
> +++ b/sql/sql_yacc.yy
> @@ -18347,7 +18338,50 @@ sp_parameters:
>          ;
>  
>  sf_returned_type_clause:
> -          RETURNS_SYM sf_return_type
> +          RETURNS_SYM
> +          {
> +            LEX *lex= Lex;

you still do this lex=Lex? :)
it became obsolete, like, 15 years ago. If not more.

> +            lex->init_last_field(&lex->sphead->m_return_field_def,
> +                                 &empty_clex_str);
> +          }
> +          sf_return_type
> +        ;
> +
> +sf_return_type:
> +          field_type
> +          {
> +            if (unlikely(Lex->sf_return_fill_definition($1)))
> +              MYSQL_YYABORT;
> +          }
> +        | ROW_SYM row_type_body
> +          {
> +            if (Lex->sf_return_fill_definition_row($2))
> +              MYSQL_YYABORT;
> +          }
> +        | ROW_SYM TYPE_SYM OF_SYM ident
> +          {
> +            if (Lex->sf_return_fill_definition_rowtype_of(
> +                       Qualified_column_ident(&$4)))
> +              MYSQL_YYABORT;
> +          }
> +        | ROW_SYM TYPE_SYM OF_SYM ident '.' ident
> +          {
> +            if (Lex->sf_return_fill_definition_rowtype_of(
> +                       Qualified_column_ident(&$4, &$6)))
> +              MYSQL_YYABORT;
> +          }
> +        | TYPE_SYM OF_SYM ident '.' ident
> +          {
> +            if (Lex->sf_return_fill_definition_type_of(
> +                       Qualified_column_ident(&$3, &$5)))
> +              MYSQL_YYABORT;
> +          }
> +        | TYPE_SYM OF_SYM ident '.' ident '.' ident
> +          {
> +            if (Lex->sf_return_fill_definition_type_of(
> +                       Qualified_column_ident(thd, &$3, &$5, &$7)))
> +              MYSQL_YYABORT;
> +          }
>          ;
>  
>  package_implementation_item_declaration:
_______________________________________________
developers mailing list -- developers@lists.mariadb.org
To unsubscribe send an email to developers-le...@lists.mariadb.org

Reply via email to