← Back to team overview

maria-developers team mailing list archive

Please review MDEV-17832 Protocol: extensions for Pluggable types and JSON, GEOMETRY

 

Hi, Sergei, Georg,

Please review a fixed version of the patch for MDEV-17832.

There are two files attached:
- mdev-17821.v18.diff     (server changes)
- mdev-17821-cli.v06.diff (libmariadb changes)


Comparing to the previous version, this version:


1. Adds a new structure MA_FIELD_EXTENSION

2. Moves extended data type information from MYSQL_FIELD
   to MYSQL_FIELD::extension in the client-server implementation.

   Note, in case of embedded server, the extended metadata
   is stored directly to MYSQL_FIELD.

3. Adds a new API function mariadb_field_metadata_attr(),
   to extract metadata from MYSQL_FIELD.


4. Changes the way how the metadata is packed on the wire
   from "easily human readable" to "easily parse-able", which:
   - makes the things faster
   - allows to transfer arbitrary binary data in the future, if needed.

Every metadata chunk is now encoded as:

a. chunk type (1 byte)
b. chunk data length (1 byte)
c. chunk data (according to #b)

For now, two chunk types are implemented:
- data type name (used for GEOMETRY sub-types, and for INET6)
- format name (for JSON)


Thanks!
commit 833aefd138d78c036aa4dafc52f62186c8531361
Author: Alexander Barkov <bar@xxxxxxxxxxx>
Date:   Tue Feb 25 16:09:42 2020 +0400

    MDEV-17832 Protocol: extensions for Pluggable types and JSON, GEOMETRY

diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index dfa805fc7e8..0f63f58e5da 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -44,7 +44,9 @@ IF(UNIX)
   SET_TARGET_PROPERTIES(mysql PROPERTIES ENABLE_EXPORTS TRUE)
 ENDIF(UNIX)
 
-MYSQL_ADD_EXECUTABLE(mysqltest mysqltest.cc COMPONENT Test)
+MYSQL_ADD_EXECUTABLE(mysqltest mysqltest.cc
+                     ${CMAKE_SOURCE_DIR}/sql/sql_string.cc
+                     COMPONENT Test)
 SET_SOURCE_FILES_PROPERTIES(mysqltest.cc PROPERTIES COMPILE_FLAGS "-DTHREADS ${PCRE2_DEBIAN_HACK}")
 TARGET_LINK_LIBRARIES(mysqltest  ${CLIENT_LIB} pcre2-posix pcre2-8)
 SET_TARGET_PROPERTIES(mysqltest PROPERTIES ENABLE_EXPORTS TRUE)
diff --git a/client/client_metadata.h b/client/client_metadata.h
new file mode 100644
index 00000000000..ee0185e5049
--- /dev/null
+++ b/client/client_metadata.h
@@ -0,0 +1,58 @@
+#ifndef SQL_METADATA_INCLUDED
+#define SQL_METADATA_INCLUDED
+/*
+   Copyright (c) 2020, MariaDB Corporation.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; version 2 of the License.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */
+
+/* This file is originally from the mysql distribution. Coded by monty */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface			/* gcc class implementation */
+#endif
+
+#include "sql_string.h"
+
+
+class Client_field_metadata
+{
+  MARIADB_FIELD_METADATA_STRING m_type;
+  MARIADB_FIELD_METADATA_STRING m_format;
+public:
+  Client_field_metadata(MYSQL_FIELD *field)
+   :m_type(mariadb_field_metadata_attr(field,
+                                       MARIADB_FIELD_METADATA_DATA_TYPE_NAME)),
+    m_format(mariadb_field_metadata_attr(field,
+                                         MARIADB_FIELD_METADATA_FORMAT_NAME))
+  { }
+  void append_to(Binary_string *to) const
+  {
+    uint orig_to_length= to->length();
+    if (m_type.length)
+    {
+      to->append(C_STRING_WITH_LEN("type="));
+      to->append(m_type.str, m_type.length);
+    }
+    if (m_format.length)
+    {
+      if (to->length() != orig_to_length)
+        to->append(" ", 1);
+      to->append(C_STRING_WITH_LEN("format="));
+      to->append(m_format.str, m_format.length);
+    }
+  }
+};
+
+
+#endif // SQL_METADATA_INCLUDED
diff --git a/client/mysql.cc b/client/mysql.cc
index 2c7f0a500b9..e7ac0979e4f 100644
--- a/client/mysql.cc
+++ b/client/mysql.cc
@@ -58,6 +58,7 @@ static char *server_version= NULL;
 #define MAX_SERVER_ARGS               64
 
 #include "sql_string.h"
+#include "client_metadata.h"
 
 extern "C" {
 #if defined(HAVE_CURSES_H) && defined(HAVE_TERM_H)
@@ -3513,12 +3514,15 @@ print_field_types(MYSQL_RES *result)
 
   while ((field = mysql_fetch_field(result)))
   {
+    Client_field_metadata metadata(field);
+    BinaryStringBuffer<128> metadata_str;
+    metadata.append_to(&metadata_str);
     tee_fprintf(PAGER, "Field %3u:  `%s`\n"
                        "Catalog:    `%s`\n"
                        "Database:   `%s`\n"
                        "Table:      `%s`\n"
                        "Org_table:  `%s`\n"
-                       "Type:       %s\n"
+                       "Type:       %s%s%.*s%s\n"
                        "Collation:  %s (%u)\n"
                        "Length:     %lu\n"
                        "Max_length: %lu\n"
@@ -3527,6 +3531,9 @@ print_field_types(MYSQL_RES *result)
                 ++i,
                 field->name, field->catalog, field->db, field->table,
                 field->org_table, fieldtype2str(field->type),
+                metadata_str.length() ? " (" : "",
+                metadata_str.length(), metadata_str.ptr(),
+                metadata_str.length() ? ")" : "",
                 get_charset_name(field->charsetnr), field->charsetnr,
                 field->length, field->max_length, field->decimals,
                 fieldflags2str(field->flags));
diff --git a/client/mysqltest.cc b/client/mysqltest.cc
index 148a492a648..a43ad8a3cc9 100644
--- a/client/mysqltest.cc
+++ b/client/mysqltest.cc
@@ -40,6 +40,7 @@
 #include <mysqld_error.h>
 #include <sql_common.h>
 #include <m_ctype.h>
+#include "client_metadata.h"
 #include <my_dir.h>
 #include <hash.h>
 #include <stdarg.h>
@@ -7709,6 +7710,16 @@ void append_metadata(DYNAMIC_STRING *ds,
     dynstr_append_mem(ds, field->name, field->name_length);
     dynstr_append_mem(ds, "\t", 1);
     replace_dynstr_append_uint(ds, field->type);
+
+    Client_field_metadata metadata(field);
+    BinaryStringBuffer<128> metadata_str;
+    metadata.append_to(&metadata_str);
+    if (metadata_str.length())
+    {
+      dynstr_append_mem(ds, " (", 2);
+      dynstr_append_mem(ds, metadata_str.ptr(), metadata_str.length());
+      dynstr_append_mem(ds, ")", 1);
+    }
     dynstr_append_mem(ds, "\t", 1);
     replace_dynstr_append_uint(ds, field->length);
     dynstr_append_mem(ds, "\t", 1);
diff --git a/include/mysql.h b/include/mysql.h
index ec49ca0482a..7ba7503ca4e 100644
--- a/include/mysql.h
+++ b/include/mysql.h
@@ -119,6 +119,8 @@ typedef struct st_mysql_field {
   unsigned int charsetnr;     /* Character set */
   enum enum_field_types type; /* Type of field. See mysql_com.h for types */
   void *extension;
+  char *type_info;                /* Added in MariaDB-10.5 */
+  unsigned int type_info_length;  /* Added in MariaDB-10.5 */
 } MYSQL_FIELD;
 
 typedef char **MYSQL_ROW;		/* return data as array of strings */
@@ -411,6 +413,19 @@ MYSQL_FIELD * STDCALL mysql_fetch_fields(MYSQL_RES *res);
 MYSQL_ROW_OFFSET STDCALL mysql_row_tell(MYSQL_RES *res);
 MYSQL_FIELD_OFFSET STDCALL mysql_field_tell(MYSQL_RES *res);
 
+
+typedef struct st_mariadb_metadata_string
+{
+  const char *str;
+  size_t length;
+} MARIADB_FIELD_METADATA_STRING;
+
+
+MARIADB_FIELD_METADATA_STRING
+  mariadb_field_metadata_attr(const MYSQL_FIELD *field,
+                              enum mariadb_field_metadata_attr_t type);
+
+
 unsigned int STDCALL mysql_field_count(MYSQL *mysql);
 my_ulonglong STDCALL mysql_affected_rows(MYSQL *mysql);
 my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql);
diff --git a/include/mysql.h.pp b/include/mysql.h.pp
index 4fcfab4aea4..4dbca301c03 100644
--- a/include/mysql.h.pp
+++ b/include/mysql.h.pp
@@ -28,6 +28,11 @@ enum enum_indicator_type
   STMT_INDICATOR_DEFAULT,
   STMT_INDICATOR_IGNORE
 };
+enum mariadb_field_metadata_attr_t
+{
+  MARIADB_FIELD_METADATA_DATA_TYPE_NAME= 1,
+  MARIADB_FIELD_METADATA_FORMAT_NAME= 2
+};
 struct st_vio;
 typedef struct st_vio Vio;
 typedef struct st_net {
@@ -229,6 +234,8 @@ typedef struct st_mysql_field {
   unsigned int charsetnr;
   enum enum_field_types type;
   void *extension;
+  char *type_info;
+  unsigned int type_info_length;
 } MYSQL_FIELD;
 typedef char **MYSQL_ROW;
 typedef unsigned int MYSQL_FIELD_OFFSET;
@@ -436,6 +443,14 @@ MYSQL_FIELD * mysql_fetch_field_direct(MYSQL_RES *res,
 MYSQL_FIELD * mysql_fetch_fields(MYSQL_RES *res);
 MYSQL_ROW_OFFSET mysql_row_tell(MYSQL_RES *res);
 MYSQL_FIELD_OFFSET mysql_field_tell(MYSQL_RES *res);
+typedef struct st_mariadb_metadata_string
+{
+  const char *str;
+  size_t length;
+} MARIADB_FIELD_METADATA_STRING;
+MARIADB_FIELD_METADATA_STRING
+  mariadb_field_metadata_attr(const MYSQL_FIELD *field,
+                              enum mariadb_field_metadata_attr_t type);
 unsigned int mysql_field_count(MYSQL *mysql);
 my_ulonglong mysql_affected_rows(MYSQL *mysql);
 my_ulonglong mysql_insert_id(MYSQL *mysql);
diff --git a/include/mysql_com.h b/include/mysql_com.h
index 4eafe148743..bf520a20228 100644
--- a/include/mysql_com.h
+++ b/include/mysql_com.h
@@ -296,6 +296,8 @@ enum enum_indicator_type
 #define MARIADB_CLIENT_COM_MULTI (1ULL << 33)
 /* support of array binding */
 #define MARIADB_CLIENT_STMT_BULK_OPERATIONS (1ULL << 34)
+/* support of extended data type/format information */
+#define MARIADB_CLIENT_EXTENDED_TYPE_INFO (1ULL << 35)
 
 #ifdef HAVE_COMPRESS
 #define CAN_CLIENT_COMPRESS CLIENT_COMPRESS
@@ -337,6 +339,7 @@ enum enum_indicator_type
                            CLIENT_CONNECT_ATTRS |\
                            MARIADB_CLIENT_COM_MULTI |\
                            MARIADB_CLIENT_STMT_BULK_OPERATIONS |\
+                           MARIADB_CLIENT_EXTENDED_TYPE_INFO|\
                            CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS)
 
 /*
@@ -348,6 +351,13 @@ enum enum_indicator_type
                                                & ~CLIENT_COMPRESS) \
                                                & ~CLIENT_SSL_VERIFY_SERVER_CERT)
 
+enum mariadb_field_metadata_attr_t
+{
+  MARIADB_FIELD_METADATA_DATA_TYPE_NAME= 1,
+  MARIADB_FIELD_METADATA_FORMAT_NAME= 2
+};
+
+
 /**
   Is raised when a multi-statement transaction
   has been started, either explicitly, by means
diff --git a/libmysqld/emb_qcache.cc b/libmysqld/emb_qcache.cc
index 603542e820e..3e33a138cb0 100644
--- a/libmysqld/emb_qcache.cc
+++ b/libmysqld/emb_qcache.cc
@@ -445,6 +445,8 @@ int emb_load_querycache_result(THD *thd, Querycache_stream *src)
         !(field->catalog= src->load_str(f_alloc, &field->catalog_length))    ||
         src->load_safe_str(f_alloc, &field->def, &field->def_length))
       goto err;
+    field->type_info= NULL;
+    field->type_info_length= 0;
   }
   
   data->rows= rows;
diff --git a/libmysqld/lib_sql.cc b/libmysqld/lib_sql.cc
index 8b25515405c..eeb15808f8e 100644
--- a/libmysqld/lib_sql.cc
+++ b/libmysqld/lib_sql.cc
@@ -1080,6 +1080,19 @@ bool Protocol_text::store_field_metadata(const THD * thd,
   client_field->catalog= dup_str_aux(field_alloc, "def", 3, cs, thd_cs);
   client_field->catalog_length= 3;
 
+  LEX_CSTRING type_info= server_field.extended_type_info.lex_cstring();
+  if (type_info.length)
+  {
+    client_field->type_info= dup_str_aux(field_alloc, type_info, cs, thd_cs);
+    client_field->type_info_length= client_field->type_info ?
+                                    (uint) type_info.length : (uint) 0;
+  }
+  else
+  {
+    client_field->type_info= NULL;
+    client_field->type_info_length= 0;
+  }
+
   if (IS_NUM(client_field->type))
     client_field->flags|= NUM_FLAG;
 
diff --git a/libmysqld/libmysql.c b/libmysqld/libmysql.c
index 2be94882303..8e3647f3c24 100644
--- a/libmysqld/libmysql.c
+++ b/libmysqld/libmysql.c
@@ -732,6 +732,35 @@ mysql_fetch_field(MYSQL_RES *result)
 }
 
 
+/**************************************************************************
+** Return mysql field metadata
+**************************************************************************/
+MARIADB_FIELD_METADATA_STRING
+mariadb_field_metadata_attr(const MYSQL_FIELD *field,
+                            enum mariadb_field_metadata_attr_t type)
+{
+  static const MARIADB_FIELD_METADATA_STRING null_str= {NULL, 0};
+  const char *ptr= field->type_info;
+  const char *end= field->type_info + field->type_info_length;
+  for ( ; ptr < end ; )
+  {
+    MARIADB_FIELD_METADATA_STRING res;
+    size_t itype;
+    if ((uchar) *ptr > 127)
+      return null_str;
+    itype= (size_t) (*ptr++);
+    res.length= (size_t) (*ptr++);
+    res.str= ptr;
+    if (res.str + res.length > end)
+      return null_str;
+    if (itype == (size_t) type)
+      return res;
+    ptr+= res.length;
+  }
+  return null_str;
+}
+
+
 /**************************************************************************
   Move to a specific row and column
 **************************************************************************/
@@ -1741,6 +1770,8 @@ static void alloc_stmt_fields(MYSQL_STMT *stmt)
     }
     field->extension= 0; /* Avoid dangling links. */
     field->max_length= 0; /* max_length is set in mysql_stmt_store_result() */
+    field->type_info= NULL;
+    field->type_info_length= 0;
   }
 }
 
diff --git a/mysql-test/main/gis.result b/mysql-test/main/gis.result
index 936924ffe87..ebdb4ddfd24 100644
--- a/mysql-test/main/gis.result
+++ b/mysql-test/main/gis.result
@@ -5095,5 +5095,51 @@ ERROR HY000: Operator does not exists: 'CAST(expr AS multilinestring)'
 SELECT CONVERT(1, MULTIPOLYGON);
 ERROR HY000: Operator does not exists: 'CAST(expr AS multipolygon)'
 #
+# MDEV-17832 Protocol: extensions for Pluggable types and JSON, GEOMETRY
+#
+SET NAMES utf8;
+CREATE TABLE t1 (
+p    POINT,
+ls   LINESTRING,
+pl   POLYGON,
+mp   MULTIPOINT,
+mls  MULTILINESTRING,
+mpl  MULTIPOLYGON,
+gc   GEOMETRYCOLLECTION,
+g    GEOMETRY
+) CHARACTER SET utf8;
+SELECT * FROM t1;
+Catalog	Database	Table	Table_alias	Column	Column_alias	Type	Length	Max length	Is_null	Flags	Decimals	Charsetnr
+def	test	t1	t1	p	p	255 (type=point)	4294967295	0	Y	144	0	63
+def	test	t1	t1	ls	ls	255 (type=linestring)	4294967295	0	Y	144	0	63
+def	test	t1	t1	pl	pl	255 (type=polygon)	4294967295	0	Y	144	0	63
+def	test	t1	t1	mp	mp	255 (type=multipoint)	4294967295	0	Y	144	0	63
+def	test	t1	t1	mls	mls	255 (type=multilinestring)	4294967295	0	Y	144	0	63
+def	test	t1	t1	mpl	mpl	255 (type=multipolygon)	4294967295	0	Y	144	0	63
+def	test	t1	t1	gc	gc	255 (type=geometrycollection)	4294967295	0	Y	144	0	63
+def	test	t1	t1	g	g	255	4294967295	0	Y	144	0	63
+p	ls	pl	mp	mls	mpl	gc	g
+SELECT
+COALESCE(p) AS p,
+COALESCE(ls) AS ls,
+COALESCE(pl) AS pl,
+COALESCE(mp) AS mp,
+COALESCE(mls) AS mls,
+COALESCE(mpl) AS mpl,
+COALESCE(gc) AS gc,
+COALESCE(g) AS g
+FROM t1;
+Catalog	Database	Table	Table_alias	Column	Column_alias	Type	Length	Max length	Is_null	Flags	Decimals	Charsetnr
+def					p	255 (type=point)	4294967295	0	Y	128	0	63
+def					ls	255 (type=linestring)	4294967295	0	Y	128	0	63
+def					pl	255 (type=polygon)	4294967295	0	Y	128	0	63
+def					mp	255 (type=multipoint)	4294967295	0	Y	128	0	63
+def					mls	255 (type=multilinestring)	4294967295	0	Y	128	0	63
+def					mpl	255 (type=multipolygon)	4294967295	0	Y	128	0	63
+def					gc	255 (type=geometrycollection)	4294967295	0	Y	128	0	63
+def					g	255	4294967295	0	Y	128	0	63
+p	ls	pl	mp	mls	mpl	gc	g
+DROP TABLE t1;
+#
 # End of 10.5 tests
 #
diff --git a/mysql-test/main/gis.test b/mysql-test/main/gis.test
index 48f2803b27d..22bd1553bc2 100644
--- a/mysql-test/main/gis.test
+++ b/mysql-test/main/gis.test
@@ -3180,6 +3180,38 @@ SELECT CONVERT(1, MULTILINESTRING);
 SELECT CONVERT(1, MULTIPOLYGON);
 
 
+--echo #
+--echo # MDEV-17832 Protocol: extensions for Pluggable types and JSON, GEOMETRY
+--echo #
+
+SET NAMES utf8;
+CREATE TABLE t1 (
+  p    POINT,
+  ls   LINESTRING,
+  pl   POLYGON,
+  mp   MULTIPOINT,
+  mls  MULTILINESTRING,
+  mpl  MULTIPOLYGON,
+  gc   GEOMETRYCOLLECTION,
+  g    GEOMETRY
+) CHARACTER SET utf8;
+--disable_ps_protocol
+--enable_metadata
+SELECT * FROM t1;
+SELECT
+  COALESCE(p) AS p,
+  COALESCE(ls) AS ls,
+  COALESCE(pl) AS pl,
+  COALESCE(mp) AS mp,
+  COALESCE(mls) AS mls,
+  COALESCE(mpl) AS mpl,
+  COALESCE(gc) AS gc,
+  COALESCE(g) AS g
+FROM t1;
+--disable_metadata
+--enable_ps_protocol
+DROP TABLE t1;
+
 --echo #
 --echo # End of 10.5 tests
 --echo #
diff --git a/mysql-test/main/mysql-metadata.result b/mysql-test/main/mysql-metadata.result
new file mode 100644
index 00000000000..ce6c77ed7fa
--- /dev/null
+++ b/mysql-test/main/mysql-metadata.result
@@ -0,0 +1,87 @@
+#
+# MDEV-17832 Protocol: extensions for Pluggable types and JSON, GEOMETRY
+#
+SET NAMES utf8;
+CREATE TABLE t1 (
+js0 JSON,
+js1 TEXT CHECK (JSON_VALID(js1)),
+js2 TEXT CHECK (LENGTH(js2) > 0 AND JSON_VALID(js2)),
+js3 TEXT CHECK (LENGTH(js2) > 0 OR JSON_VALID(js2))
+) CHARACTER SET utf8;
+--------------
+SELECT * FROM t1
+--------------
+
+Field   1:  `js0`
+Catalog:    `def`
+Database:   `test`
+Table:      `t1`
+Org_table:  `t1`
+Type:       BLOB (format=json)
+Collation:  latin1_swedish_ci (8)
+Length:     4294967295
+Max_length: 0
+Decimals:   0
+Flags:      BLOB BINARY 
+
+Field   2:  `js1`
+Catalog:    `def`
+Database:   `test`
+Table:      `t1`
+Org_table:  `t1`
+Type:       BLOB (format=json)
+Collation:  latin1_swedish_ci (8)
+Length:     65535
+Max_length: 0
+Decimals:   0
+Flags:      BLOB 
+
+Field   3:  `js2`
+Catalog:    `def`
+Database:   `test`
+Table:      `t1`
+Org_table:  `t1`
+Type:       BLOB (format=json)
+Collation:  latin1_swedish_ci (8)
+Length:     65535
+Max_length: 0
+Decimals:   0
+Flags:      BLOB 
+
+Field   4:  `js3`
+Catalog:    `def`
+Database:   `test`
+Table:      `t1`
+Org_table:  `t1`
+Type:       BLOB
+Collation:  latin1_swedish_ci (8)
+Length:     65535
+Max_length: 0
+Decimals:   0
+Flags:      BLOB 
+
+
+0 rows in set (TIME)
+
+Bye
+--------------
+SELECT JSON_COMPACT(js0) FROM t1
+--------------
+
+Field   1:  `JSON_COMPACT(js0)`
+Catalog:    `def`
+Database:   ``
+Table:      ``
+Org_table:  ``
+Type:       LONG_BLOB (format=json)
+Collation:  binary (63)
+Length:     4294967295
+Max_length: 0
+Decimals:   0
+Flags:      BINARY 
+
+
+0 rows in set (TIME)
+
+Bye
+DROP TABLE t1;
diff --git a/mysql-test/main/mysql-metadata.test b/mysql-test/main/mysql-metadata.test
new file mode 100644
index 00000000000..bab44496f78
--- /dev/null
+++ b/mysql-test/main/mysql-metadata.test
@@ -0,0 +1,22 @@
+-- source include/have_working_dns.inc
+-- source include/not_embedded.inc
+
+--echo #
+--echo # MDEV-17832 Protocol: extensions for Pluggable types and JSON, GEOMETRY
+--echo #
+
+SET NAMES utf8;
+CREATE TABLE t1 (
+  js0 JSON,
+  js1 TEXT CHECK (JSON_VALID(js1)),
+  js2 TEXT CHECK (LENGTH(js2) > 0 AND JSON_VALID(js2)),
+  js3 TEXT CHECK (LENGTH(js2) > 0 OR JSON_VALID(js2))
+) CHARACTER SET utf8;
+
+--replace_regex /0 rows in set [(].*[)]/0 rows in set (TIME)/
+--exec $MYSQL -vvv --column-type-info --database=test -e "SELECT * FROM t1;"
+
+--replace_regex /0 rows in set [(].*[)]/0 rows in set (TIME)/
+--exec $MYSQL -vvv --column-type-info --database=test -e "SELECT JSON_COMPACT(js0) FROM t1;"
+
+DROP TABLE t1;
diff --git a/mysql-test/main/type_json.result b/mysql-test/main/type_json.result
index 96e96cca404..2c4f7b7d42b 100644
--- a/mysql-test/main/type_json.result
+++ b/mysql-test/main/type_json.result
@@ -98,3 +98,33 @@ select cast('{a:1}' as text);
 ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'text)' at line 1
 select cast('{a:1}' as json);
 ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'json)' at line 1
+#
+# Start of 10.5 tests
+#
+#
+# MDEV-17832 Protocol: extensions for Pluggable types and JSON, GEOMETRY
+#
+SET NAMES utf8;
+CREATE TABLE t1 (
+js0 JSON,
+js1 TEXT CHECK (JSON_VALID(js1)),
+js2 TEXT CHECK (LENGTH(js2) > 0 AND JSON_VALID(js2)),
+js3 TEXT CHECK (LENGTH(js2) > 0 OR JSON_VALID(js2))
+) CHARACTER SET utf8;
+SELECT * FROM t1;
+Catalog	Database	Table	Table_alias	Column	Column_alias	Type	Length	Max length	Is_null	Flags	Decimals	Charsetnr
+def	test	t1	t1	js0	js0	252 (format=json)	4294967295	0	Y	144	0	33
+def	test	t1	t1	js1	js1	252 (format=json)	196605	0	Y	16	0	33
+def	test	t1	t1	js2	js2	252 (format=json)	196605	0	Y	16	0	33
+def	test	t1	t1	js3	js3	252	196605	0	Y	16	0	33
+js0	js1	js2	js3
+SELECT js0, JSON_COMPACT(js0), JSON_COMPACT('{}') FROM t1;
+Catalog	Database	Table	Table_alias	Column	Column_alias	Type	Length	Max length	Is_null	Flags	Decimals	Charsetnr
+def	test	t1	t1	js0	js0	252 (format=json)	4294967295	0	Y	144	0	33
+def					JSON_COMPACT(js0)	251 (format=json)	4294967295	0	Y	128	0	63
+def					JSON_COMPACT('{}')	253 (format=json)	6	0	Y	128	0	63
+js0	JSON_COMPACT(js0)	JSON_COMPACT('{}')
+DROP TABLE t1;
+#
+# End of 10.5 tests
+#
diff --git a/mysql-test/main/type_json.test b/mysql-test/main/type_json.test
index bd13dc1fcf4..7ab0af20e03 100644
--- a/mysql-test/main/type_json.test
+++ b/mysql-test/main/type_json.test
@@ -64,3 +64,30 @@ drop table t1;
 select cast('{a:1}' as text);
 --error ER_PARSE_ERROR
 select cast('{a:1}' as json);
+
+--echo #
+--echo # Start of 10.5 tests
+--echo #
+
+--echo #
+--echo # MDEV-17832 Protocol: extensions for Pluggable types and JSON, GEOMETRY
+--echo #
+
+SET NAMES utf8;
+CREATE TABLE t1 (
+  js0 JSON,
+  js1 TEXT CHECK (JSON_VALID(js1)),
+  js2 TEXT CHECK (LENGTH(js2) > 0 AND JSON_VALID(js2)),
+  js3 TEXT CHECK (LENGTH(js2) > 0 OR JSON_VALID(js2))
+) CHARACTER SET utf8;
+--disable_ps_protocol
+--enable_metadata
+SELECT * FROM t1;
+SELECT js0, JSON_COMPACT(js0), JSON_COMPACT('{}') FROM t1;
+--disable_metadata
+--enable_ps_protocol
+DROP TABLE t1;
+
+--echo #
+--echo # End of 10.5 tests
+--echo #
diff --git a/plugin/type_inet/mysql-test/type_inet/type_inet6.result b/plugin/type_inet/mysql-test/type_inet/type_inet6.result
index 3dbbbccb100..15c8dbf8ca9 100644
--- a/plugin/type_inet/mysql-test/type_inet/type_inet6.result
+++ b/plugin/type_inet/mysql-test/type_inet/type_inet6.result
@@ -40,12 +40,12 @@ CREATE TABLE t1 (a INET6);
 INSERT INTO t1 VALUES ('::1');
 SELECT * FROM t1;
 Catalog	Database	Table	Table_alias	Column	Column_alias	Type	Length	Max length	Is_null	Flags	Decimals	Charsetnr
-def	test	t1	t1	a	a	254	39	3	Y	160	0	8
+def	test	t1	t1	a	a	254 (type=inet6)	39	3	Y	160	0	8
 a
 ::1
 SELECT CAST('::' AS INET6) AS a;
 Catalog	Database	Table	Table_alias	Column	Column_alias	Type	Length	Max length	Is_null	Flags	Decimals	Charsetnr
-def					a	254	39	2	N	33	0	8
+def					a	254 (type=inet6)	39	2	N	33	0	8
 a
 ::
 DROP TABLE t1;
diff --git a/plugin/type_inet/mysql-test/type_inet/type_inet6_mysql.result b/plugin/type_inet/mysql-test/type_inet/type_inet6_mysql.result
index 468e9fea41e..868b9902f5c 100644
--- a/plugin/type_inet/mysql-test/type_inet/type_inet6_mysql.result
+++ b/plugin/type_inet/mysql-test/type_inet/type_inet6_mysql.result
@@ -4,12 +4,36 @@ Catalog:    `def`
 Database:   `test`
 Table:      `t1`
 Org_table:  `t1`
-Type:       STRING
+Type:       STRING (type=inet6)
 Collation:  latin1_swedish_ci (8)
 Length:     39
 Max_length: 0
 Decimals:   0
 Flags:      UNSIGNED BINARY 
 
+Field   2:  `b`
+Catalog:    `def`
+Database:   ``
+Table:      ``
+Org_table:  ``
+Type:       STRING (type=inet6)
+Collation:  latin1_swedish_ci (8)
+Length:     39
+Max_length: 0
+Decimals:   0
+Flags:      NOT_NULL UNSIGNED 
+
+Field   3:  `c`
+Catalog:    `def`
+Database:   ``
+Table:      ``
+Org_table:  ``
+Type:       STRING (type=inet6)
+Collation:  latin1_swedish_ci (8)
+Length:     39
+Max_length: 0
+Decimals:   0
+Flags:      UNSIGNED 
+
 
 DROP TABLE t1;
diff --git a/plugin/type_inet/mysql-test/type_inet/type_inet6_mysql.test b/plugin/type_inet/mysql-test/type_inet/type_inet6_mysql.test
index 5e6ac6f3804..dfb300816d9 100644
--- a/plugin/type_inet/mysql-test/type_inet/type_inet6_mysql.test
+++ b/plugin/type_inet/mysql-test/type_inet/type_inet6_mysql.test
@@ -2,5 +2,5 @@
 -- source include/not_embedded.inc
 
 CREATE TABLE t1 (a INET6);
---exec $MYSQL -t test --column-type-info -e "SELECT * FROM t1" 2>&1 
+--exec $MYSQL -t test --column-type-info -e "SELECT a, CAST('::' AS INET6) AS b, COALESCE(a) AS c FROM t1" 2>&1 
 DROP TABLE t1;
diff --git a/plugin/type_inet/sql_type_inet.cc b/plugin/type_inet/sql_type_inet.cc
index 6803bdba434..79f85cd1f19 100644
--- a/plugin/type_inet/sql_type_inet.cc
+++ b/plugin/type_inet/sql_type_inet.cc
@@ -705,6 +705,11 @@ class Field_inet6: public Field
     str.set_ascii(name.ptr(), name.length());
   }
 
+  bool extended_type_info_append_to(Extended_type_info *to) const override
+  {
+    return type_handler_inet6.extended_type_info_append_to(to);
+  }
+
   bool validate_value_in_record(THD *thd, const uchar *record) const override
   {
     return false;
diff --git a/plugin/type_inet/sql_type_inet.h b/plugin/type_inet/sql_type_inet.h
index b483ff94e8d..7066e50a54d 100644
--- a/plugin/type_inet/sql_type_inet.h
+++ b/plugin/type_inet/sql_type_inet.h
@@ -328,6 +328,15 @@ class Type_handler_inet6: public Type_handler
   {
     return PROTOCOL_SEND_STRING;
   }
+  bool extended_type_info_append_to(Extended_type_info *to) const
+  {
+    return to->append_type(name().lex_cstring());
+  }
+  bool Item_extended_type_info_append_to(Extended_type_info *to,
+                                         const Item *item) const override
+  {
+    return extended_type_info_append_to(to);
+  }
 
   enum_field_types field_type() const override
   {
diff --git a/sql/field.cc b/sql/field.cc
index 1ce49b0bdfa..8bab37a567c 100644
--- a/sql/field.cc
+++ b/sql/field.cc
@@ -2039,6 +2039,19 @@ void Field::make_send_field(Send_field *field)
   field->set_handler(type_handler());
   field->flags=table->maybe_null ? (flags & ~NOT_NULL_FLAG) : flags;
   field->decimals= 0;
+  extended_type_info_append_to(&field->extended_type_info);
+  if (check_constraint)
+  {
+    /*
+      Append the format that is implicitly implied by the CHECK CONSTRAINT.
+      For example:
+        CREATE TABLE t1 (js longtext DEFAULT NULL CHECK (json_valid(a)));
+        SELECT j FROM t1;
+      will add "cf=json" to the extended type info metadata for t1.js.
+    */
+    check_constraint->expr->
+      format_by_check_constraint_append_to(&field->extended_type_info);
+  }
 }
 
 
diff --git a/sql/field.h b/sql/field.h
index 4a8eec35b05..c9b6d4749c1 100644
--- a/sql/field.h
+++ b/sql/field.h
@@ -1299,6 +1299,10 @@ class Field: public Value_source
   */
   virtual void sql_type(String &str) const =0;
   virtual void sql_rpl_type(String *str) const { sql_type(*str); }
+  virtual bool extended_type_info_append_to(Extended_type_info *to) const
+  {
+    return false;
+  }
   virtual uint size_of() const =0;		// For new field
   inline bool is_null(my_ptrdiff_t row_offset= 0) const
   {
@@ -5467,6 +5471,7 @@ class Send_field :public Sql_alloc,
   LEX_CSTRING db_name;
   LEX_CSTRING table_name, org_table_name;
   LEX_CSTRING col_name, org_col_name;
+  Extended_type_info extended_type_info;
   ulong length;
   uint flags, decimals;
   Send_field(Field *field)
diff --git a/sql/item.cc b/sql/item.cc
index 6b585325439..15758228a41 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -6197,6 +6197,8 @@ void Item::init_make_send_field(Send_field *tmp_field,
   tmp_field->decimals=decimals;
   if (unsigned_flag)
     tmp_field->flags |= UNSIGNED_FLAG;
+  tmp_field->extended_type_info.init();
+  h->Item_extended_type_info_append_to(&tmp_field->extended_type_info, this);
 }
 
 void Item::make_send_field(THD *thd, Send_field *tmp_field)
diff --git a/sql/item.h b/sql/item.h
index 6a9d401b101..d0ff49275b4 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -1814,6 +1814,14 @@ class Item: public Value_source,
   /* This is to handle printing of default values */
   virtual bool need_parentheses_in_default() { return false; }
   virtual void save_in_result_field(bool no_conversions) {}
+  /*
+    Data type format implied by the CHECK CONSTRAINT,
+    to be sent to the client in the result set metadata.
+  */
+  virtual bool format_by_check_constraint_append_to(Extended_type_info *) const
+  {
+    return false;
+  }
   /*
     set value of aggregate function in case of no rows for grouping were found
   */
diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc
index 6df2b5dbd3a..949e389698a 100644
--- a/sql/item_cmpfunc.cc
+++ b/sql/item_cmpfunc.cc
@@ -6481,6 +6481,20 @@ Item *Item_cond_and::neg_transformer(THD *thd)	/* NOT(a AND b AND ...)  -> */
 }
 
 
+bool
+Item_cond_and::format_by_check_constraint_append_to(Extended_type_info *to) const
+{
+  List_iterator_fast<Item> li(const_cast<List<Item>&>(list));
+  Item *item;
+  while ((item= li++))
+  {
+    if (item->format_by_check_constraint_append_to(to))
+      return true;
+  }
+  return false;
+}
+
+
 Item *Item_cond_or::neg_transformer(THD *thd)	/* NOT(a OR b OR ...)  -> */
 					/* NOT a AND NOT b AND ... */
 {
diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h
index 9b20fa50214..cc089a815b7 100644
--- a/sql/item_cmpfunc.h
+++ b/sql/item_cmpfunc.h
@@ -3349,6 +3349,7 @@ class Item_cond_and :public Item_cond
   COND *build_equal_items(THD *thd, COND_EQUAL *inherited,
                           bool link_item_fields,
                           COND_EQUAL **cond_equal_ref);
+  bool format_by_check_constraint_append_to(Extended_type_info *to) const;
   void add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level,
                       table_map usable_tables, SARGABLE_PARAM **sargables);
   SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr);
diff --git a/sql/item_jsonfunc.h b/sql/item_jsonfunc.h
index e61d0875056..ac8903b4e65 100644
--- a/sql/item_jsonfunc.h
+++ b/sql/item_jsonfunc.h
@@ -84,6 +84,11 @@ class Item_func_json_valid: public Item_bool_func
     maybe_null= 1;
     return FALSE;
   }
+  bool format_by_check_constraint_append_to(Extended_type_info *to) const
+  {
+    static const Lex_cstring fmt(STRING_WITH_LEN("json"));
+    return to->append_format(fmt);
+  }
   Item *get_copy(THD *thd)
   { return get_item_copy<Item_func_json_valid>(thd, this); }
 };
@@ -118,6 +123,12 @@ class Item_json_func: public Item_str_func
   Item_json_func(THD *thd, List<Item> &list)
    :Item_str_func(thd, list) { }
   bool is_json_type() { return true; }
+  void make_send_field(THD *thd, Send_field *tmp_field)
+  {
+    Item_str_func::make_send_field(thd, tmp_field);
+    static const Lex_cstring fmt(STRING_WITH_LEN("json"));
+    tmp_field->extended_type_info.append_format(fmt);
+  }
 };
 
 
diff --git a/sql/protocol.cc b/sql/protocol.cc
index 30e4cc6c214..e1074295e8e 100644
--- a/sql/protocol.cc
+++ b/sql/protocol.cc
@@ -816,8 +816,14 @@ bool Protocol_text::store_field_metadata(const THD * thd,
         store_str(field.table_name, cs, thd_charset) ||
         store_str(field.org_table_name, cs, thd_charset) ||
         store_str(field.col_name, cs, thd_charset) ||
-        store_str(field.org_col_name, cs, thd_charset) ||
-        packet->realloc(packet->length() + 12))
+        store_str(field.org_col_name, cs, thd_charset))
+      return true;
+    if (thd->client_capabilities & MARIADB_CLIENT_EXTENDED_TYPE_INFO)
+    {
+      if (store_str(field.extended_type_info.lex_cstring(), cs, thd_charset))
+        return true;
+    }
+    if (packet->realloc(packet->length() + 12))
       return true;
     /* Store fixed length fields */
     pos= (char*) packet->end();
diff --git a/sql/sql_type.h b/sql/sql_type.h
index ce87c8e9d93..899f189adeb 100644
--- a/sql/sql_type.h
+++ b/sql/sql_type.h
@@ -23,6 +23,7 @@
 
 
 #include "mysqld.h"
+#include "lex_string.h"
 #include "sql_array.h"
 #include "sql_const.h"
 #include "sql_time.h"
@@ -137,6 +138,40 @@ enum column_definition_type_t
 };
 
 
+class Extended_type_info: public Binary_string
+{
+public:
+  void init() { length(0); }
+  bool append_chunk(mariadb_field_metadata_attr_t type,
+                    const LEX_CSTRING &value)
+  {
+    /*
+      If we eventually support many metadata chunk types and long metadata
+      values, we'll need to encode type and length using net_store_length()
+      and do corresponding changes to the unpacking code in libmariadb.
+      For now let's just assert that type and length fit into one byte.
+    */
+    DBUG_ASSERT(net_length_size((ulonglong) type) == 1);
+    DBUG_ASSERT(net_length_size((ulonglong) value.length) == 1);
+    size_t nbytes= 1/*type*/ + 1/*length*/ + value.length;
+    if (reserve(nbytes))
+      return true;
+    qs_append((char) (uchar) type);
+    qs_append((char) (uchar) value.length);
+    qs_append(&value);
+    return false;
+  }
+  bool append_format(const LEX_CSTRING &str)
+  {
+    return append_chunk(MARIADB_FIELD_METADATA_FORMAT_NAME, str);
+  }
+  bool append_type(const LEX_CSTRING &str)
+  {
+    return append_chunk(MARIADB_FIELD_METADATA_DATA_TYPE_NAME, str);
+  }
+};
+
+
 class Data_type_statistics
 {
 public:
@@ -3426,6 +3461,11 @@ class Type_handler
     return field_type();
   }
   virtual protocol_send_type_t protocol_send_type() const= 0;
+  virtual bool Item_extended_type_info_append_to(Extended_type_info *to,
+                                                 const Item *item) const
+  {
+    return false;
+  }
   virtual Item_result result_type() const= 0;
   virtual Item_result cmp_type() const= 0;
   virtual enum_dynamic_column_type
diff --git a/sql/sql_type_geom.cc b/sql/sql_type_geom.cc
index 3ca0eaabc3f..41c5c6741d3 100644
--- a/sql/sql_type_geom.cc
+++ b/sql/sql_type_geom.cc
@@ -34,6 +34,16 @@ Named_type_handler<Type_handler_geometrycollection> type_handler_geometrycollect
 
 Type_collection_geometry        type_collection_geometry;
 
+
+bool
+Type_handler_geometry::extended_type_info_append_to(Extended_type_info *to)
+                                                    const
+{
+  return geometry_type() == GEOM_GEOMETRY ? false :
+         to->append_type(name().lex_cstring());
+}
+
+
 const Type_handler_geometry *
 Type_handler_geometry::type_handler_geom_by_type(uint type)
 {
diff --git a/sql/sql_type_geom.h b/sql/sql_type_geom.h
index a2baa5ae299..114b42485d6 100644
--- a/sql/sql_type_geom.h
+++ b/sql/sql_type_geom.h
@@ -39,9 +39,15 @@ class Type_handler_geometry: public Type_handler_string_result
                                          Item * const *args,
                                          uint start, uint end);
   static const Type_handler_geometry *type_handler_geom_by_type(uint type);
+  bool extended_type_info_append_to(Extended_type_info *to) const;
 public:
   virtual ~Type_handler_geometry() {}
   enum_field_types field_type() const override { return MYSQL_TYPE_GEOMETRY; }
+  bool Item_extended_type_info_append_to(Extended_type_info *to,
+                                         const Item *item) const override
+  {
+    return extended_type_info_append_to(to);
+  }
   bool is_param_long_data_type() const override { return true; }
   uint32 max_display_length_for_field(const Conv_source &src) const override;
   uint32 calc_pack_length(uint32 length) const override;
@@ -366,6 +372,10 @@ class Field_geom :public Field_blob
                                   const Item *item,
                                   bool is_eq_func) const override;
   void sql_type(String &str) const override;
+  bool extended_type_info_append_to(Extended_type_info *to) const override
+  {
+    return m_type_handler->extended_type_info_append_to(to);
+  }
   Copy_func *get_copy_func(const Field *from) const override
   {
     const Type_handler_geometry *fth=
diff --git a/include/ma_common.h b/include/ma_common.h
index c598136..3178482 100644
--- a/include/ma_common.h
+++ b/include/ma_common.h
@@ -113,3 +113,10 @@ struct st_mariadb_extension {
     (a)->options.extension->key : 0
 
 #endif
+
+
+typedef struct st_mariadb_field_extension
+{
+  const char *type_info;         /* Added in MariaDB-10.5 */
+  unsigned int type_info_length; /* Added in MariaDB-10.5 */
+} MA_FIELD_EXTENSION;
diff --git a/include/ma_priv.h b/include/ma_priv.h
index 7d30094..5ee083c 100644
--- a/include/ma_priv.h
+++ b/include/ma_priv.h
@@ -24,8 +24,27 @@
 
 void free_rows(MYSQL_DATA *cur);
 int ma_multi_command(MYSQL *mysql, enum enum_multi_status status);
-MYSQL_FIELD * unpack_fields(MYSQL_DATA *data,
+MYSQL_FIELD * unpack_fields(const MYSQL *mysql, MYSQL_DATA *data,
                             MA_MEM_ROOT *alloc,uint fields,
                             my_bool default_value);
 
+static inline my_bool ma_has_extended_type_info(const MYSQL *mysql)
+{
+  return ((mysql->extension->mariadb_server_capabilities << 32) &
+          MARIADB_CLIENT_EXTENDED_TYPE_INFO) != 0;
+}
+
+static inline uint ma_extended_type_info_rows(const MYSQL *mysql)
+{
+  return ma_has_extended_type_info(mysql) ? 1 : 0;
+}
+
+static inline uint ma_result_set_rows(const MYSQL *mysql)
+{
+  return ma_has_extended_type_info(mysql) ? 9 : 8;
+}
+
+MA_FIELD_EXTENSION *ma_field_extension_deep_dup(MA_MEM_ROOT *memroot,
+                                                const MA_FIELD_EXTENSION *from);
+
 #endif
diff --git a/include/mariadb_com.h b/include/mariadb_com.h
index 9a5da28..aafa2a5 100644
--- a/include/mariadb_com.h
+++ b/include/mariadb_com.h
@@ -170,13 +170,16 @@ enum enum_server_command
 #define MARIADB_CLIENT_PROGRESS (1ULL << 32)
 #define MARIADB_CLIENT_COM_MULTI (1ULL << 33)
 #define MARIADB_CLIENT_STMT_BULK_OPERATIONS (1ULL << 34)
+/* support of extended data type/format information, since 10.5.0 */
+#define MARIADB_CLIENT_EXTENDED_TYPE_INFO (1ULL << 35)
 
 #define IS_MARIADB_EXTENDED_SERVER(mysql)\
         !(mysql->server_capabilities & CLIENT_MYSQL)
 
 #define MARIADB_CLIENT_SUPPORTED_FLAGS (MARIADB_CLIENT_PROGRESS |\
                                        MARIADB_CLIENT_COM_MULTI |\
-                                       MARIADB_CLIENT_STMT_BULK_OPERATIONS)
+                                       MARIADB_CLIENT_STMT_BULK_OPERATIONS|\
+                                       MARIADB_CLIENT_EXTENDED_TYPE_INFO)
 
 #define CLIENT_SUPPORTED_FLAGS  (CLIENT_MYSQL |\
                                  CLIENT_FOUND_ROWS |\
diff --git a/include/mysql.h b/include/mysql.h
index dcaf316..22fa340 100644
--- a/include/mysql.h
+++ b/include/mysql.h
@@ -383,6 +383,25 @@ typedef struct
   void *extension;
 } MYSQL_PARAMETERS;
 
+
+enum mariadb_field_metadata_attr_t
+{
+  MARIADB_FIELD_METADATA_DATA_TYPE_NAME= 1,
+  MARIADB_FIELD_METADATA_FORMAT_NAME= 2
+};
+
+
+typedef struct st_mariadb_metadata_string
+{
+  const char *str;
+  size_t length;
+} MARIADB_FIELD_METADATA_STRING;
+
+
+MARIADB_FIELD_METADATA_STRING
+  mariadb_field_metadata_attr(const MYSQL_FIELD *field,
+                              enum mariadb_field_metadata_attr_t type);
+
 #ifndef _mysql_time_h_
 enum enum_mysql_timestamp_type
 {
diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c
index 1063f14..de80dd8 100644
--- a/libmariadb/mariadb_lib.c
+++ b/libmariadb/mariadb_lib.c
@@ -751,6 +751,23 @@ my_bool _mariadb_set_conf_option(MYSQL *mysql, const char *config_option, const
   return 1;
 }
 
+
+/***************************************************************************
+** Allocate a field extension deep copy
+***************************************************************************/
+
+MA_FIELD_EXTENSION *ma_field_extension_deep_dup(MA_MEM_ROOT *memroot,
+                                                const MA_FIELD_EXTENSION *from)
+{
+  MA_FIELD_EXTENSION *ext= ma_alloc_root(memroot, sizeof(MA_FIELD_EXTENSION));
+  if (!ext)
+    return NULL;
+  ext->type_info= ma_memdup_root(memroot, from->type_info,
+                                          from->type_info_length);
+  ext->type_info_length= ext->type_info ? from->type_info_length : 0;
+  return ext;
+}
+
 /***************************************************************************
 ** Change field rows to field structs
 ***************************************************************************/
@@ -771,7 +788,8 @@ static size_t rset_field_offsets[]= {
 };
 
 MYSQL_FIELD *
-unpack_fields(MYSQL_DATA *data,MA_MEM_ROOT *alloc,uint fields,
+unpack_fields(const MYSQL *mysql,
+              MYSQL_DATA *data, MA_MEM_ROOT *alloc, uint fields,
 	      my_bool default_value)
 {
   MYSQL_ROWS	*row;
@@ -804,7 +822,21 @@ unpack_fields(MYSQL_DATA *data,MA_MEM_ROOT *alloc,uint fields,
       }
     }
 
-    p= (char *)row->data[6];
+    field->extension= NULL;
+    if (ma_has_extended_type_info(mysql))
+    {
+      if (row->data[i+1] - row->data[i] > 1)
+      {
+        size_t len= row->data[i+1] - row->data[i] - 1;
+        MA_FIELD_EXTENSION *ext= ma_alloc_root(alloc, sizeof(MA_FIELD_EXTENSION));
+        ext->type_info= ma_memdup_root(alloc, (char *)row->data[i], len);
+        ext->type_info_length= (uint) len;
+        field->extension= ext;
+      }
+      i++;
+    }
+
+    p= (char *)row->data[i];
     /* filler */
     field->charsetnr= uint2korr(p);
     p+= 2;
@@ -823,10 +855,11 @@ unpack_fields(MYSQL_DATA *data,MA_MEM_ROOT *alloc,uint fields,
     if (INTERNAL_NUM_FIELD(field))
       field->flags|= NUM_FLAG;
 
+    i++;
     /* This is used by deprecated function mysql_list_fields only,
        however the reported length is not correct, so we always zero it */
-    if (default_value && row->data[7])
-      field->def=ma_strdup_root(alloc,(char*) row->data[7]);
+    if (default_value && row->data[i])
+      field->def=ma_strdup_root(alloc,(char*) row->data[i]);
     else
       field->def=0;
     field->def_length= 0;
@@ -2193,9 +2226,10 @@ get_info:
     mysql->server_status|= SERVER_STATUS_IN_TRANS;
 
   mysql->extra_info= net_field_length_ll(&pos); /* Maybe number of rec */
-  if (!(fields=mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0,8)))
+  if (!(fields=mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0,
+                                            ma_result_set_rows(mysql))))
     return(-1);
-  if (!(mysql->fields=unpack_fields(fields,&mysql->field_alloc,
+  if (!(mysql->fields=unpack_fields(mysql, fields, &mysql->field_alloc,
 				    (uint) field_count, 1)))
     return(-1);
   mysql->status=MYSQL_STATUS_GET_RESULT;
@@ -2345,6 +2379,39 @@ mysql_fetch_field(MYSQL_RES *result)
   return &result->fields[result->current_field++];
 }
 
+
+/**************************************************************************
+** Return mysql field metadata
+**************************************************************************/
+MARIADB_FIELD_METADATA_STRING
+mariadb_field_metadata_attr(const MYSQL_FIELD *field,
+                            enum mariadb_field_metadata_attr_t type)
+{
+  static const MARIADB_FIELD_METADATA_STRING null_str= {NULL, 0};
+  MA_FIELD_EXTENSION *ext;
+  const char *ptr, *end;
+  if (!(ext= (MA_FIELD_EXTENSION*) field->extension))
+    return null_str;
+  ptr= ext->type_info;
+  end= ext->type_info + ext->type_info_length;
+  for ( ; ptr < end ; )
+  {
+    MARIADB_FIELD_METADATA_STRING res;
+    size_t itype;
+    if ((uchar) *ptr > 127)
+      return null_str;
+    itype= (size_t) (*ptr++);
+    res.length= (size_t) (*ptr++);
+    res.str= ptr;
+    if (res.str + res.length > end)
+      return null_str;
+    if (itype == (size_t) type)
+      return res;
+    ptr+= res.length;
+  }
+  return null_str;
+}
+
 /**************************************************************************
 **  Return next row of the query results
 **************************************************************************/
@@ -2513,7 +2580,8 @@ mysql_list_fields(MYSQL *mysql, const char *table, const char *wild)
   length= snprintf(buff, 128, "%s%c%s", table, '\0', wild ? wild : "");
 
   if (ma_simple_command(mysql, COM_FIELD_LIST,buff,length,1,0) ||
-      !(query = mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0,8)))
+      !(query = mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0,
+                                             ma_result_set_rows(mysql))))
     return(NULL);
 
   free_old_query(mysql);
@@ -2526,7 +2594,7 @@ mysql_list_fields(MYSQL *mysql, const char *table, const char *wild)
   mysql->fields=0;
   result->eof=1;
   result->field_count = (uint) query->rows;
-  result->fields= unpack_fields(query,&result->field_alloc,
+  result->fields= unpack_fields(mysql, query, &result->field_alloc,
 				result->field_count, 1);
   if (result->fields)
     return(result);
@@ -2552,8 +2620,8 @@ mysql_list_processes(MYSQL *mysql)
   field_count=(uint) net_field_length(&pos);
   if (!(fields = mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0,5)))
     return(NULL);
-  if (!(mysql->fields=unpack_fields(fields, &mysql->field_alloc,
-                                    field_count, 0)))
+  if (!(mysql->fields= unpack_fields(mysql, fields, &mysql->field_alloc,
+                                     field_count, 0)))
     return(NULL);
   mysql->status=MYSQL_STATUS_GET_RESULT;
   mysql->field_count=field_count;
diff --git a/libmariadb/mariadb_stmt.c b/libmariadb/mariadb_stmt.c
index 86e39ca..433e495 100644
--- a/libmariadb/mariadb_stmt.c
+++ b/libmariadb/mariadb_stmt.c
@@ -1581,7 +1581,8 @@ my_bool mthd_stmt_get_param_metadata(MYSQL_STMT *stmt)
 {
   MYSQL_DATA *result;
 
-  if (!(result= stmt->mysql->methods->db_read_rows(stmt->mysql, (MYSQL_FIELD *)0, 7)))
+  if (!(result= stmt->mysql->methods->db_read_rows(stmt->mysql, (MYSQL_FIELD *)0,
+                                                   7 + ma_extended_type_info_rows(stmt->mysql))))
     return(1);
 
   free_rows(result);
@@ -1593,9 +1594,10 @@ my_bool mthd_stmt_get_result_metadata(MYSQL_STMT *stmt)
   MYSQL_DATA *result;
   MA_MEM_ROOT *fields_ma_alloc_root= &((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root;
 
-  if (!(result= stmt->mysql->methods->db_read_rows(stmt->mysql, (MYSQL_FIELD *)0, 7)))
+  if (!(result= stmt->mysql->methods->db_read_rows(stmt->mysql, (MYSQL_FIELD *)0,
+                                                   7 + ma_extended_type_info_rows(stmt->mysql))))
     return(1);
-  if (!(stmt->fields= unpack_fields(result,fields_ma_alloc_root,
+  if (!(stmt->fields= unpack_fields(stmt->mysql, result, fields_ma_alloc_root,
           stmt->field_count, 0)))
     return(1);
   return(0);
@@ -1836,6 +1838,11 @@ static int madb_alloc_stmt_fields(MYSQL_STMT *stmt)
       stmt->fields[i].decimals= stmt->mysql->fields[i].decimals;
       stmt->fields[i].charsetnr= stmt->mysql->fields[i].charsetnr;
       stmt->fields[i].max_length= stmt->mysql->fields[i].max_length;
+      stmt->fields[i].extension=
+                stmt->mysql->fields[i].extension ?
+                ma_field_extension_deep_dup(fields_ma_alloc_root,
+                                            stmt->mysql->fields[i].extension) :
+                NULL;
     }
     if (!(stmt->bind= (MYSQL_BIND *)ma_alloc_root(fields_ma_alloc_root, stmt->field_count * sizeof(MYSQL_BIND))))
     {
@@ -1912,7 +1919,6 @@ int stmt_read_execute_response(MYSQL_STMT *stmt)
         /* since  all pointers will be incorrect if another statement will
            be executed, so we need to allocate memory and copy the
            information */
-        stmt->fields[i].extension= 0; /* not in use yet */
         if (mysql->fields[i].db)
           stmt->fields[i].db= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].db);
         if (mysql->fields[i].table)
@@ -1927,6 +1933,11 @@ int stmt_read_execute_response(MYSQL_STMT *stmt)
           stmt->fields[i].catalog= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].catalog);
         if (mysql->fields[i].def)
           stmt->fields[i].def= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].def);
+        stmt->fields[i].extension=
+                mysql->fields[i].extension ?
+                ma_field_extension_deep_dup(fields_ma_alloc_root,
+                                            mysql->fields[i].extension) :
+                NULL;
       }
     }
 

Follow ups