mariadb/sql/json_table.cc
Alexey Botchkov a2ce788f02 MDEV-25259 JSON_TABLE: Illegal mix of collations upon executing query with combination of charsets via view.
The ::print method didn't print the explicitly specified charset.
2021-04-21 10:21:47 +04:00

1591 lines
44 KiB
C++

/*
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
*/
#include "mariadb.h"
#include "sql_priv.h"
#include "sql_class.h" /* TMP_TABLE_PARAM */
#include "table.h"
#include "item_jsonfunc.h"
#include "json_table.h"
#include "sql_show.h"
#include "sql_select.h"
#define HA_ERR_JSON_TABLE (HA_ERR_LAST+1)
class table_function_handlerton
{
public:
handlerton m_hton;
table_function_handlerton()
{
bzero(&m_hton, sizeof(m_hton));
m_hton.tablefile_extensions= hton_no_exts;
m_hton.slot= HA_SLOT_UNDEF;
}
};
static table_function_handlerton table_function_hton;
/*
@brief
Collect a bitmap of tables that a given table function cannot have
references to.
@detail
According to the SQL standard, a table function can refer to any table
that's "preceding" it in the FROM clause.
The other limitation we would like to enforce is that the inner side of
an outer join cannot refer to the outer side. An example:
SELECT * from JSON_TABLE(t1.col, ...) left join t1 on ...
This function implements both of the above restrictions.
Basic idea: the "join_list" contains the tables in the order that's a
reverse of the order they were specified in the query.
If we walk the join_list, we will encounter:
1. First, the tables that table function cannot refer to (collect them in a
bitmap)
2. Then, the table function itself (put it in the bitmap, too, as self-
references are not allowed, and stop the walk)
3. Tables that the table function CAN refer to (we don't walk these as
we've stopped on step #2).
The above can be applied recursively for nested joins (this covers NATURAL
JOIN, and JOIN ... USING constructs).
Enforcing the "refer to only preceding tables" rule means that outer side
of LEFT JOIN cannot refer to the inner side.
Handing RIGHT JOINs: There are no RIGHT JOINs in the join_list data
structures. They were converted to LEFT JOINs (see calls to st_select_lex::
convert_right_join). This conversion changes the order of tables, but
we are ok with operating on the tables "in the left join order".
@return
TRUE - enumeration has found the Table Function instance. The bitmap is
ready.
FALSE - Otherwise
*/
static
bool get_disallowed_table_deps_for_list(table_map table_func_bit,
List<TABLE_LIST> *join_list,
table_map *disallowed_tables)
{
TABLE_LIST *table;
NESTED_JOIN *nested_join;
List_iterator<TABLE_LIST> li(*join_list);
while ((table= li++))
{
if ((nested_join= table->nested_join))
{
if (get_disallowed_table_deps_for_list(table_func_bit,
&nested_join->join_list,
disallowed_tables))
return true;
}
else
{
*disallowed_tables |= table->table->map;
if (table_func_bit == table->table->map)
{
// This is the JSON_TABLE(...) that are we're computing dependencies
// for.
return true;
}
}
}
return false;
}
/*
@brief
Given a join and a table function in it (specified by its table_func_bit),
produce a bitmap of tables that the table function can NOT have references
to.
@detail
See get_disallowed_table_deps_for_list
@return
Bitmap of tables that table function can NOT have references to.
*/
static
table_map get_disallowed_table_deps(JOIN *join, table_map table_func_bit)
{
table_map disallowed_tables= 0;
if (!get_disallowed_table_deps_for_list(table_func_bit, join->join_list,
&disallowed_tables))
{
// We haven't found the table with table_func_bit in all tables?
DBUG_ASSERT(0);
}
return disallowed_tables;
}
/*
A table that produces output rows for JSON_TABLE().
*/
class ha_json_table: public handler
{
Table_function_json_table *m_jt;
String *m_js; // The JSON document we're reading
String m_tmps; // Buffer for the above
int fill_column_values(uchar * buf, uchar *pos);
public:
ha_json_table(TABLE_SHARE *share_arg, Table_function_json_table *jt):
handler(&table_function_hton.m_hton, share_arg), m_jt(jt)
{
/*
set the mark_trx_read_write_done to avoid the
handler::mark_trx_read_write_internal() call.
It relies on &ha_thd()->ha_data[ht->slot].ha_info[0] to be set.
But we don't set the ha_data for the ha_json_table, and
that call makes no sence for ha_json_table.
*/
mark_trx_read_write_done= 1;
/* See ha_json_table::position for format definition */
ref_length= m_jt->m_columns.elements * 4;
}
~ha_json_table() {}
handler *clone(const char *name, MEM_ROOT *mem_root) override { return NULL; }
/* Rows also use a fixed-size format */
enum row_type get_row_type() const override { return ROW_TYPE_FIXED; }
const char *table_type() const override
{
return "JSON_TABLE function";
}
ulonglong table_flags() const override
{
return (HA_FAST_KEY_READ | /*HA_NO_BLOBS |*/ HA_NULL_IN_KEY |
HA_CAN_SQL_HANDLER |
HA_REC_NOT_IN_SEQ | HA_NO_TRANSACTIONS |
HA_HAS_RECORDS);
}
ulong index_flags(uint inx, uint part, bool all_parts) const override
{
return HA_ONLY_WHOLE_INDEX | HA_KEY_SCAN_NOT_ROR;
}
ha_rows records() override { return HA_POS_ERROR; }
int open(const char *name, int mode, uint test_if_locked) override
{ return 0; }
int close(void) override { return 0; }
int rnd_init(bool scan) override;
int rnd_next(uchar *buf) override;
int rnd_pos(uchar * buf, uchar *pos) override;
void position(const uchar *record) override;
int info(uint) override;
int extra(enum ha_extra_function operation) override { return 0; }
THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to,
enum thr_lock_type lock_type) override
{ return NULL; }
int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info)
override { return 1; }
/* Give no message. */
bool get_error_message(int error, String *buf) override
{
buf->length(0);
return TRUE;
}
};
/*
Helper class that creates the temporary table that
represents the table function in the query.
*/
class Create_json_table: public Data_type_statistics
{
// The following members are initialized only in start()
Field **m_default_field;
uchar *m_bitmaps;
// The following members are initialized in ctor
uint m_temp_pool_slot;
uint m_null_count;
public:
Create_json_table(const TMP_TABLE_PARAM *param,
bool save_sum_fields)
:m_temp_pool_slot(MY_BIT_NONE),
m_null_count(0)
{ }
void add_field(TABLE *table, Field *field, uint fieldnr);
TABLE *start(THD *thd,
TMP_TABLE_PARAM *param,
Table_function_json_table *jt,
const LEX_CSTRING *table_alias);
bool add_json_table_fields(THD *thd, TABLE *table,
Table_function_json_table *jt);
bool finalize(THD *thd, TABLE *table, TMP_TABLE_PARAM *param,
Table_function_json_table *jt);
};
/*
@brief
Start scanning the JSON document in [str ... end]
@detail
Note: non-root nested paths are set to scan one JSON node (that is, a
"subdocument").
*/
void Json_table_nested_path::scan_start(CHARSET_INFO *i_cs,
const uchar *str, const uchar *end)
{
json_get_path_start(&m_engine, i_cs, str, end, &m_cur_path);
m_cur_nested= NULL;
m_null= false;
m_ordinality_counter= 0;
}
/*
@brief
Find the next JSON element that matches the search path.
*/
int Json_table_nested_path::scan_next()
{
bool no_records_found= false;
if (m_cur_nested)
{
for (;;)
{
if (m_cur_nested->scan_next() == 0)
return 0;
if (!(m_cur_nested= m_cur_nested->m_next_nested))
break;
handle_new_nested:
m_cur_nested->scan_start(m_engine.s.cs, m_engine.value_begin,
m_engine.s.str_end);
}
if (no_records_found)
return 0;
}
DBUG_ASSERT(!m_cur_nested);
while (!json_get_path_next(&m_engine, &m_cur_path))
{
if (json_path_compare(&m_path, &m_cur_path, m_engine.value_type))
continue;
/* path found. */
++m_ordinality_counter;
if (!m_nested)
return 0;
m_cur_nested= m_nested;
no_records_found= true;
goto handle_new_nested;
}
m_null= true;
return 1;
}
int ha_json_table::rnd_init(bool scan)
{
Json_table_nested_path &p= m_jt->m_nested_path;
DBUG_ENTER("ha_json_table::rnd_init");
if ((m_js= m_jt->m_json->val_str(&m_tmps)))
{
p.scan_start(m_js->charset(),
(const uchar *) m_js->ptr(), (const uchar *) m_js->end());
}
DBUG_RETURN(0);
}
/*
@brief
Store JSON value in an SQL field, doing necessary special conversions
for JSON's null, true, and false.
*/
static int store_json_in_field(Field *f, const json_engine_t *je)
{
switch (je->value_type)
{
case JSON_VALUE_NULL:
f->set_null();
return 0;
case JSON_VALUE_TRUE:
case JSON_VALUE_FALSE:
{
Item_result rt= f->result_type();
if (rt == INT_RESULT || rt == DECIMAL_RESULT || rt == REAL_RESULT)
return f->store(je->value_type == JSON_VALUE_TRUE, false);
break;
}
default:
break;
};
return f->store((const char *) je->value, (uint32) je->value_len, je->s.cs);
}
bool Json_table_nested_path::check_error(const char *str)
{
if (m_engine.s.error)
{
report_json_error_ex(str, &m_engine, "JSON_TABLE", 0,
Sql_condition::WARN_LEVEL_ERROR);
return true; // Error
}
return false; // Ok
}
int ha_json_table::rnd_next(uchar *buf)
{
int res;
enum_check_fields cf_orig;
if (!m_js)
return HA_ERR_END_OF_FILE;
/*
Step 1: Move the root nested path to the next record (this implies moving
its child nested paths accordingly)
*/
if (m_jt->m_nested_path.scan_next())
{
if (m_jt->m_nested_path.check_error(m_js->ptr()))
{
/*
We already reported an error, so returning an
error code that just doesn't produce extra
messages.
*/
return HA_ERR_JSON_TABLE;
}
return HA_ERR_END_OF_FILE;
}
/*
Step 2: Read values for all columns (the columns refer to nested paths
they are in).
*/
cf_orig= table->in_use->count_cuted_fields;
table->in_use->count_cuted_fields= CHECK_FIELD_EXPRESSION;
res= fill_column_values(buf, NULL);
table->in_use->count_cuted_fields= cf_orig;
return res ? HA_ERR_JSON_TABLE : 0;
}
/*
@brief
Fill values of table columns, taking data either from Json_nested_path
objects, or from the rowid value
@param pos NULL means the data should be read from Json_nested_path
objects.
Non-null value is a pointer to previously saved rowid (see
ha_json_table::position() for description)
*/
int ha_json_table::fill_column_values(uchar * buf, uchar *pos)
{
Field **f= table->field;
Json_table_column *jc;
List_iterator_fast<Json_table_column> jc_i(m_jt->m_columns);
my_ptrdiff_t ptrdiff= buf - table->record[0];
Abort_on_warning_instant_set ao_set(table->in_use, FALSE);
while ((jc= jc_i++))
{
bool is_null_value;
uint int_pos= 0; /* just to make compilers happy. */
if (!bitmap_is_set(table->read_set, (*f)->field_index))
goto cont_loop;
if (ptrdiff)
(*f)->move_field_offset(ptrdiff);
/*
Read the NULL flag:
- if we are reading from a rowid value, 0 means SQL NULL.
- if scanning json document, read it from the nested path
*/
if (pos)
is_null_value= !(int_pos= uint4korr(pos));
else
is_null_value= jc->m_nest->m_null;
if (is_null_value)
{
(*f)->set_null();
}
else
{
(*f)->set_notnull();
switch (jc->m_column_type)
{
case Json_table_column::FOR_ORDINALITY:
{
/*
Read the cardinality counter:
- read it from nested path when scanning the json document
- or, read it from rowid when in rnd_pos() call
*/
longlong counter= pos? int_pos: jc->m_nest->m_ordinality_counter;
(*f)->store(counter, TRUE);
break;
}
case Json_table_column::PATH:
case Json_table_column::EXISTS_PATH:
{
json_engine_t je;
json_path_step_t *cur_step;
uint array_counters[JSON_DEPTH_LIMIT];
int not_found;
const uchar* node_start;
const uchar* node_end;
/*
Get the JSON context node that we will need to evaluate PATH or
EXISTS against:
- when scanning the json document, read it from nested path
- when in rnd_pos call, the rowid has the start offset.
*/
if (pos)
{
node_start= (const uchar *) (m_js->ptr() + (int_pos-1));
node_end= (const uchar *) m_js->end();
}
else
{
node_start= jc->m_nest->get_value();
node_end= jc->m_nest->get_value_end();
}
json_scan_start(&je, m_js->charset(), node_start, node_end);
cur_step= jc->m_path.steps;
not_found= json_find_path(&je, &jc->m_path, &cur_step, array_counters) ||
json_read_value(&je);
if (jc->m_column_type == Json_table_column::EXISTS_PATH)
{
(*f)->store(!not_found);
}
else /*PATH*/
{
if (not_found)
{
if (jc->m_on_empty.respond(jc, *f, ER_JSON_TABLE_ERROR_ON_FIELD))
goto error_return;
}
else
{
if (!json_value_scalar(&je) ||
store_json_in_field(*f, &je))
{
if (jc->m_on_error.respond(jc, *f, ER_JSON_TABLE_SCALAR_EXPECTED))
goto error_return;
}
else
{
/*
If the path contains wildcards, check if there are
more matches for it in json and report an error if so.
*/
if (jc->m_path.types_used &
(JSON_PATH_WILD | JSON_PATH_DOUBLE_WILD) &&
(json_scan_next(&je) ||
!json_find_path(&je, &jc->m_path, &cur_step,
array_counters)))
{
if (jc->m_on_error.respond(jc, *f, ER_JSON_TABLE_MULTIPLE_MATCHES))
goto error_return;
}
}
}
}
break;
}
};
}
if (ptrdiff)
(*f)->move_field_offset(-ptrdiff);
cont_loop:
f++;
if (pos)
pos+= 4;
}
return 0;
error_return:
return 1;
}
int ha_json_table::rnd_pos(uchar * buf, uchar *pos)
{
return fill_column_values(buf, pos) ? HA_ERR_JSON_TABLE : 0;
}
/*
The reference has 4 bytes for every column of the JSON_TABLE.
There it keeps 0 for the NULL values, ordinality index for
the ORDINALITY columns and the offset of the field's data in
the JSON for other column types.
*/
void ha_json_table::position(const uchar *record)
{
uchar *c_ref= ref;
Json_table_column *jc;
List_iterator_fast<Json_table_column> jc_i(m_jt->m_columns);
while ((jc= jc_i++))
{
if (jc->m_nest->m_null)
{
int4store(c_ref, 0);
}
else
{
switch (jc->m_column_type)
{
case Json_table_column::FOR_ORDINALITY:
int4store(c_ref, jc->m_nest->m_ordinality_counter);
break;
case Json_table_column::PATH:
case Json_table_column::EXISTS_PATH:
{
size_t pos= jc->m_nest->get_value() -
(const uchar *) m_js->ptr() + 1;
int4store(c_ref, pos);
break;
}
};
}
c_ref+= 4;
}
}
int ha_json_table::info(uint)
{
/*
We don't want 0 or 1 in stats.records.
Though this value shouldn't matter as the optimizer
supposed to use Table_function_json_table::get_estimates
to obtain this data.
*/
stats.records= 4;
return 0;
}
void Create_json_table::add_field(TABLE *table, Field *field, uint fieldnr)
{
DBUG_ASSERT(!field->field_name.str ||
strlen(field->field_name.str) == field->field_name.length);
if (!(field->flags & NOT_NULL_FLAG))
m_null_count++;
table->s->reclength+= field->pack_length();
// Assign it here, before update_data_type_statistics() changes m_blob_count
if (field->flags & BLOB_FLAG)
table->s->blob_field[m_blob_count]= fieldnr;
table->field[fieldnr]= field;
field->field_index= fieldnr;
field->update_data_type_statistics(this);
}
/**
Create a json table according to a field list.
@param thd thread handle
@param param a description used as input to create the table
@param jt json_table specificaion
@param table_alias alias
*/
TABLE *Create_json_table::start(THD *thd,
TMP_TABLE_PARAM *param,
Table_function_json_table *jt,
const LEX_CSTRING *table_alias)
{
MEM_ROOT *mem_root_save, own_root;
TABLE *table;
TABLE_SHARE *share;
uint copy_func_count= param->func_count;
char *tmpname,path[FN_REFLEN];
Field **reg_field;
uint *blob_field;
DBUG_ENTER("Create_json_table::start");
DBUG_PRINT("enter",
("table_alias: '%s' ", table_alias->str));
if (use_temp_pool && !(test_flags & TEST_KEEP_TMP_TABLES))
m_temp_pool_slot = bitmap_lock_set_next(&temp_pool);
if (m_temp_pool_slot != MY_BIT_NONE) // we got a slot
sprintf(path, "%s-%lx-%i", tmp_file_prefix,
current_pid, m_temp_pool_slot);
else
{
/* if we run out of slots or we are not using tempool */
sprintf(path, "%s-%lx-%lx-%x", tmp_file_prefix,current_pid,
(ulong) thd->thread_id, thd->tmp_table++);
}
/*
No need to change table name to lower case.
*/
fn_format(path, path, mysql_tmpdir, "",
MY_REPLACE_EXT|MY_UNPACK_FILENAME);
const uint field_count= param->field_count;
DBUG_ASSERT(field_count);
init_sql_alloc(key_memory_TABLE, &own_root,
TABLE_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC));
if (!multi_alloc_root(&own_root,
&table, sizeof(*table),
&share, sizeof(*share),
&reg_field, sizeof(Field*) * (field_count+1),
&m_default_field, sizeof(Field*) * (field_count),
&blob_field, sizeof(uint)*(field_count+1),
&param->items_to_copy,
sizeof(param->items_to_copy[0])*(copy_func_count+1),
&param->keyinfo, sizeof(*param->keyinfo),
&param->start_recinfo,
sizeof(*param->recinfo)*(field_count*2+4),
&tmpname, (uint) strlen(path)+1,
&m_bitmaps, bitmap_buffer_size(field_count)*6,
NullS))
{
DBUG_RETURN(NULL); /* purecov: inspected */
}
strmov(tmpname, path);
/* make table according to fields */
bzero((char*) table,sizeof(*table));
bzero((char*) reg_field, sizeof(Field*) * (field_count+1));
bzero((char*) m_default_field, sizeof(Field*) * (field_count));
table->mem_root= own_root;
mem_root_save= thd->mem_root;
thd->mem_root= &table->mem_root;
table->field=reg_field;
table->alias.set(table_alias->str, table_alias->length, table_alias_charset);
table->reginfo.lock_type=TL_WRITE; /* Will be updated */
table->map=1;
table->temp_pool_slot= m_temp_pool_slot;
table->copy_blobs= 1;
table->in_use= thd;
table->no_rows_with_nulls= param->force_not_null_cols;
table->s= share;
init_tmp_table_share(thd, share, "", 0, "(temporary)", tmpname);
share->blob_field= blob_field;
share->table_charset= param->table_charset;
share->primary_key= MAX_KEY; // Indicate no primary key
share->not_usable_by_query_cache= FALSE;
if (param->schema_table)
share->db= INFORMATION_SCHEMA_NAME;
param->using_outer_summary_function= 0;
share->db_plugin= NULL;
if (!(table->file= new (&table->mem_root) ha_json_table(share, jt)))
DBUG_RETURN(NULL);
table->file->init();
thd->mem_root= mem_root_save;
DBUG_RETURN(table);
}
bool Create_json_table::finalize(THD *thd, TABLE *table,
TMP_TABLE_PARAM *param,
Table_function_json_table *jt)
{
DBUG_ENTER("Create_json_table::finalize");
DBUG_ASSERT(table);
uint null_pack_length;
bool use_packed_rows= false;
uchar *pos;
uchar *null_flags;
TMP_ENGINE_COLUMNDEF *recinfo;
TABLE_SHARE *share= table->s;
MEM_ROOT *mem_root_save= thd->mem_root;
thd->mem_root= &table->mem_root;
DBUG_ASSERT(param->field_count >= share->fields);
DBUG_ASSERT(param->field_count >= share->blob_fields);
if (table->file->set_ha_share_ref(&share->ha_share))
{
delete table->file;
goto err;
}
if (share->blob_fields == 0)
m_null_count++;
null_pack_length= (m_null_count + m_uneven_bit_length + 7) / 8;
share->reclength+= null_pack_length;
if (!share->reclength)
share->reclength= 1; // Dummy select
{
uint alloc_length= ALIGN_SIZE(share->reclength + MI_UNIQUE_HASH_LENGTH+1);
share->rec_buff_length= alloc_length;
if (!(table->record[0]= (uchar*)
alloc_root(&table->mem_root, alloc_length*3)))
goto err;
table->record[1]= table->record[0]+alloc_length;
share->default_values= table->record[1]+alloc_length;
}
setup_tmp_table_column_bitmaps(table, m_bitmaps, table->s->fields);
recinfo=param->start_recinfo;
null_flags=(uchar*) table->record[0];
pos=table->record[0]+ null_pack_length;
if (null_pack_length)
{
bzero((uchar*) recinfo,sizeof(*recinfo));
recinfo->type=FIELD_NORMAL;
recinfo->length=null_pack_length;
recinfo++;
bfill(null_flags,null_pack_length,255); // Set null fields
table->null_flags= (uchar*) table->record[0];
share->null_fields= m_null_count;
share->null_bytes= share->null_bytes_for_compare= null_pack_length;
}
m_null_count= (share->blob_fields == 0) ? 1 : 0;
for (uint i= 0; i < share->fields; i++, recinfo++)
{
Field *field= table->field[i];
uint length;
bzero((uchar*) recinfo,sizeof(*recinfo));
if (!(field->flags & NOT_NULL_FLAG))
{
recinfo->null_bit= (uint8)1 << (m_null_count & 7);
recinfo->null_pos= m_null_count/8;
field->move_field(pos, null_flags + m_null_count/8,
(uint8)1 << (m_null_count & 7));
m_null_count++;
}
else
field->move_field(pos,(uchar*) 0,0);
if (field->type() == MYSQL_TYPE_BIT)
{
/* We have to reserve place for extra bits among null bits */
((Field_bit*) field)->set_bit_ptr(null_flags + m_null_count / 8,
m_null_count & 7);
m_null_count+= (field->field_length & 7);
}
field->reset();
/*
Test if there is a default field value. The test for ->ptr is to skip
'offset' fields generated by initialize_tables
*/
if (m_default_field[i] && m_default_field[i]->ptr)
{
/*
default_field[i] is set only in the cases when 'field' can
inherit the default value that is defined for the field referred
by the Item_field object from which 'field' has been created.
*/
const Field *orig_field= m_default_field[i];
/* Get the value from default_values */
if (orig_field->is_null_in_record(orig_field->table->s->default_values))
field->set_null();
else
{
field->set_notnull();
memcpy(field->ptr,
orig_field->ptr_in_record(orig_field->table->s->default_values),
field->pack_length_in_rec());
}
}
length=field->pack_length();
pos+= length;
/* Make entry for create table */
recinfo->length=length;
recinfo->type= field->tmp_engine_column_type(use_packed_rows);
// fix table name in field entry
field->set_table_name(&table->alias);
}
param->recinfo= recinfo; // Pointer to after last field
store_record(table,s->default_values); // Make empty default record
share->max_rows= ~(ha_rows) 0;
param->end_write_records= HA_POS_ERROR;
share->db_record_offset= 1;
if (unlikely(table->file->ha_open(table, table->s->path.str, O_RDWR,
HA_OPEN_TMP_TABLE | HA_OPEN_INTERNAL_TABLE)))
goto err;
table->db_stat= HA_OPEN_KEYFILE;
table->set_created();
thd->mem_root= mem_root_save;
DBUG_RETURN(false);
err:
thd->mem_root= mem_root_save;
DBUG_RETURN(true);
}
/*
@brief
Read the JSON_TABLE's field definitions from @jt and add the fields to
table @table.
*/
bool Create_json_table::add_json_table_fields(THD *thd, TABLE *table,
Table_function_json_table *jt)
{
TABLE_SHARE *share= table->s;
Json_table_column *jc;
uint fieldnr= 0;
MEM_ROOT *mem_root_save= thd->mem_root;
List_iterator_fast<Json_table_column> jc_i(jt->m_columns);
DBUG_ENTER("add_json_table_fields");
thd->mem_root= &table->mem_root;
while ((jc= jc_i++))
{
Create_field *sql_f= jc->m_field;
List_iterator_fast<Json_table_column> it2(jt->m_columns);
Json_table_column *jc2;
/*
Initialize length from its original value (number of characters),
which was set in the parser. This is necessary if we're
executing a prepared statement for the second time.
*/
sql_f->length= sql_f->char_length;
if (!(jc->m_explicit_cs= sql_f->charset))
sql_f->charset= &my_charset_utf8mb4_general_ci;
if (sql_f->prepare_stage1(thd, thd->mem_root, table->file,
table->file->ha_table_flags()))
goto err_exit;
while ((jc2= it2++) != jc)
{
if (lex_string_cmp(system_charset_info,
&sql_f->field_name, &jc2->m_field->field_name) == 0)
{
my_error(ER_DUP_FIELDNAME, MYF(0), sql_f->field_name.str);
goto err_exit;
}
}
it2.rewind();
}
jc_i.rewind();
while ((jc= jc_i++))
{
Create_field *sql_f= jc->m_field;
Record_addr addr(!(sql_f->flags & NOT_NULL_FLAG));
Bit_addr bit(addr.null());
sql_f->prepare_stage2(table->file, table->file->ha_table_flags());
if (!sql_f->charset)
sql_f->charset= &my_charset_utf8mb4_bin;
Field *f= sql_f->type_handler()->make_table_field_from_def(share,
thd->mem_root, &sql_f->field_name, addr, bit, sql_f, sql_f->flags);
if (!f)
goto err_exit;
f->init(table);
add_field(table, f, fieldnr++);
}
share->fields= fieldnr;
share->blob_fields= m_blob_count;
table->field[fieldnr]= 0; // End marker
share->blob_field[m_blob_count]= 0; // End marker
share->column_bitmap_size= bitmap_buffer_size(share->fields);
thd->mem_root= mem_root_save;
DBUG_RETURN(FALSE);
err_exit:
thd->mem_root= mem_root_save;
DBUG_RETURN(TRUE);
}
/*
@brief
Given a TABLE_LIST representing JSON_TABLE(...) syntax, create a temporary
table for it.
@detail
The temporary table will have:
- fields whose names/datatypes are specified in JSON_TABLE(...) syntax
- a ha_json_table as the storage engine.
The uses of the temporary table are:
- name resolution: the query may have references to the columns of
JSON_TABLE(...). A TABLE object will allow to resolve them.
- query execution: ha_json_table will produce JSON_TABLE's rows.
*/
TABLE *create_table_for_function(THD *thd, TABLE_LIST *sql_table)
{
TMP_TABLE_PARAM tp;
TABLE *table;
uint field_count= sql_table->table_function->m_columns.elements+1;
DBUG_ENTER("create_table_for_function");
tp.init();
tp.table_charset= system_charset_info;
tp.field_count= field_count;
{
Create_json_table maker(&tp, false);
if (!(table= maker.start(thd, &tp,
sql_table->table_function, &sql_table->alias)) ||
maker.add_json_table_fields(thd, table, sql_table->table_function) ||
maker.finalize(thd, table, &tp, sql_table->table_function))
{
if (table)
free_tmp_table(thd, table);
DBUG_RETURN(NULL);
}
}
sql_table->schema_table_name.length= 0;
my_bitmap_map* bitmaps=
(my_bitmap_map*) thd->alloc(bitmap_buffer_size(field_count));
my_bitmap_init(&table->def_read_set, (my_bitmap_map*) bitmaps, field_count,
FALSE);
table->read_set= &table->def_read_set;
bitmap_clear_all(table->read_set);
table->alias_name_used= true;
table->next= thd->derived_tables;
thd->derived_tables= table;
table->s->tmp_table= INTERNAL_TMP_TABLE;
table->grant.privilege= SELECT_ACL;
sql_table->table= table;
DBUG_RETURN(table);
}
int Json_table_column::set(THD *thd, enum_type ctype, const LEX_CSTRING &path)
{
set(ctype);
if (json_path_setup(&m_path, thd->variables.collation_connection,
(const uchar *) path.str, (const uchar *)(path.str + path.length)))
{
report_path_error_ex(path.str, &m_path, "JSON_TABLE", 1,
Sql_condition::WARN_LEVEL_ERROR);
return 1;
}
/*
This is done so the ::print function can just print the path string.
Can be removed if we redo that function to print the path using it's
anctual content. Not sure though if we should.
*/
m_path.s.c_str= (const uchar *) path.str;
return 0;
}
static int print_path(String *str, const json_path_t *p)
{
return str->append('\'') ||
str->append_for_single_quote((const char *) p->s.c_str,
p->s.str_end - p->s.c_str) ||
str->append('\'');
}
/*
Print the string representation of the Json_table_column.
@param thd - the thread
@param f - the remaining array of Field-s from the table
if the Json_table_column
@param str - the string where to print
*/
int Json_table_column::print(THD *thd, Field **f, String *str)
{
StringBuffer<MAX_FIELD_WIDTH> column_type(str->charset());
if (append_identifier(thd, str, &m_field->field_name) ||
str->append(' '))
return 1;
switch (m_column_type)
{
case FOR_ORDINALITY:
if (str->append("FOR ORDINALITY"))
return 1;
break;
case EXISTS_PATH:
case PATH:
(*f)->sql_type(column_type);
if (str->append(column_type) ||
(m_explicit_cs &&
(str->append(" CHARSET ") || str->append(m_explicit_cs->csname))) ||
str->append(m_column_type == PATH ? " PATH " : " EXISTS PATH ") ||
print_path(str, &m_path))
return 1;
break;
};
if (m_on_empty.print("EMPTY", str) ||
m_on_error.print("ERROR", str))
return 1;
return 0;
}
int Json_table_nested_path::set_path(THD *thd, const LEX_CSTRING &path)
{
if (json_path_setup(&m_path, thd->variables.collation_connection,
(const uchar *) path.str, (const uchar *)(path.str + path.length)))
{
report_path_error_ex(path.str, &m_path, "JSON_TABLE", 1,
Sql_condition::WARN_LEVEL_ERROR);
return 1;
}
/*
This is done so the ::print function can just print the path string.
Can be removed if we redo that function to print the path using its
actual content. Not sure though if we should.
*/
m_path.s.c_str= (const uchar *) path.str;
return 0;
}
/*
@brief
Perform the action of this response on field @f (emit an error, or set @f
to NULL, or set it to default value).
error_num supposed to have the error message with field_name and table_name
arguments.
*/
int Json_table_column::On_response::respond(Json_table_column *jc, Field *f,
uint error_num)
{
switch (m_response)
{
case Json_table_column::RESPONSE_NOT_SPECIFIED:
case Json_table_column::RESPONSE_NULL:
f->set_null();
break;
case Json_table_column::RESPONSE_ERROR:
f->set_null();
my_error(error_num, MYF(0), f->field_name.str, f->table->alias.ptr());
return 1;
case Json_table_column::RESPONSE_DEFAULT:
f->set_notnull();
f->store(m_default.str,
m_default.length, jc->m_defaults_cs);
break;
}
return 0;
}
int Json_table_column::On_response::print(const char *name, String *str) const
{
const char *resp;
const LEX_CSTRING *ds= NULL;
if (m_response == Json_table_column::RESPONSE_NOT_SPECIFIED)
return 0;
switch (m_response)
{
case Json_table_column::RESPONSE_NULL:
resp= "NULL";
break;
case Json_table_column::RESPONSE_ERROR:
resp= "ERROR";
break;
case Json_table_column::RESPONSE_DEFAULT:
{
resp= "DEFAULT";
ds= &m_default;
break;
}
default:
resp= NULL;
DBUG_ASSERT(FALSE); /* should never happen. */
}
return
(str->append(' ') || str->append(resp) ||
(ds && (str->append(" '") ||
str->append_for_single_quote(ds->str, ds->length) ||
str->append('\''))) ||
str->append(" ON ") ||
str->append(name));
}
void Table_function_json_table::start_nested_path(Json_table_nested_path *np)
{
np->m_parent= cur_parent;
*last_sibling_hook= np;
// Make the newly added path the parent
cur_parent= np;
last_sibling_hook= &np->m_nested;
}
void Table_function_json_table::end_nested_path()
{
last_sibling_hook= &cur_parent->m_next_nested;
cur_parent= cur_parent->m_parent;
}
/*
@brief Create a name resolution context for doing name resolution in table
function argument.
@seealso
push_new_name_resolution_context
*/
bool push_table_function_arg_context(LEX *lex, MEM_ROOT *alloc)
{
// Walk the context stack until we find a context that is used for resolving
// the SELECT's WHERE clause.
List_iterator<Name_resolution_context> it(lex->context_stack);
Name_resolution_context *ctx;
while ((ctx= it++))
{
if (ctx->select_lex && ctx == &ctx->select_lex->context)
break;
}
DBUG_ASSERT(ctx);
// Then, create a copy of it and return it.
Name_resolution_context *new_ctx= new (alloc) Name_resolution_context;
// Note: not all fields of *ctx are initialized yet at this point.
// We will get all of the fields filled in Table_function_json_table::setup
// (search for the "Prepare the name resolution context" comment).
*new_ctx= *ctx;
return lex->push_context(new_ctx);
}
/*
@brief
Perform name-resolution phase tasks
@detail
- The only argument that needs resolution is the JSON text
- Then, we need to set dependencies: if JSON_TABLE refers to table's
column, e.g.
JSON_TABLE (t1.col ... ) AS t2
then it can be computed only after table t1.
- The dependencies must not form a loop.
*/
int Table_function_json_table::setup(THD *thd, TABLE_LIST *sql_table,
SELECT_LEX *s_lex)
{
TABLE *t= sql_table->table;
thd->where= "JSON_TABLE argument";
{
bool save_is_item_list_lookup;
bool res;
save_is_item_list_lookup= thd->lex->current_select->is_item_list_lookup;
thd->lex->current_select->is_item_list_lookup= 0;
// Prepare the name resolution context. First, copy the context that is
// used for name resolution of the WHERE clause
*m_context= thd->lex->current_select->context;
// Then, restrict it to only allow to refer to tables that come before the
// table function reference
m_context->ignored_tables= get_disallowed_table_deps(s_lex->join, t->map);
res= m_json->fix_fields_if_needed(thd, &m_json);
thd->lex->current_select->is_item_list_lookup= save_is_item_list_lookup;
if (res)
return TRUE;
}
{
/*
The m_json defines it's charset during the fix_fields stage so we're
changing the field's current charsets with it.
The complicated part is that the length of the field can be changed so
in this case we should move all the consequent fiedlds.
*/
List_iterator_fast<Json_table_column> jc_i(m_columns);
int field_offset= 0;
Field *f;
for (uint i= 0; (f= t->field[i]); i++)
{
Json_table_column *jc= jc_i++;
uint32 old_pack_length= f->pack_length();
f->change_charset(
jc->m_explicit_cs ? jc->m_explicit_cs : m_json->collation);
if (field_offset)
f->move_field(f->ptr + field_offset, f->null_ptr, f->null_bit);
f->reset();
field_offset= (field_offset + f->pack_length()) - old_pack_length;
/*
The m_field->charset is going to be reused if it's the prepared
statement running several times. So should restored the original
value.
*/
jc->m_field->charset= jc->m_explicit_cs;
}
}
return FALSE;
}
int Table_function_json_table::walk_items(Item_processor processor,
bool walk_subquery, void *argument)
{
return m_json->walk(processor, walk_subquery, argument);
}
void Table_function_json_table::get_estimates(ha_rows *out_rows,
double *scan_time,
double *startup_cost)
{
*out_rows= 40;
*scan_time= 0.0;
*startup_cost= 0.0;
}
/*
Check if a column belongs to the nested path
or a path that nested into it.
It only supposed to be used in the Json_table_nested_path::print, and
since the nested path should have at least one field we
don't have to loop through the m_next_nested.
*/
bool Json_table_nested_path::column_in_this_or_nested(
const Json_table_nested_path *p, const Json_table_column *jc)
{
for (; p; p= p->m_nested)
{
if (jc->m_nest == p)
return TRUE;
}
return FALSE;
}
/*
Print the string representation of the Json_nested_path object.
Which is the COLUMNS(...) part of the JSON_TABLE definition.
@param thd - the thread
@param f - the remaining part of the array of Field* objects
taken from the TABLE.
It's needed as Json_table_column objects
don't have links to the related Field-s.
@param str - the string where to print
@param it - the remaining part of the Json_table_column list
@param last_column - the last column taken from the list.
*/
int Json_table_nested_path::print(THD *thd, Field ***f, String *str,
List_iterator_fast<Json_table_column> &it,
Json_table_column **last_column)
{
Json_table_nested_path *c_path= this;
Json_table_nested_path *c_nested= m_nested;
Json_table_column *jc= *last_column;
bool first_column= TRUE;
if (str->append("COLUMNS ("))
return 1;
/* loop while jc belongs to the current or nested paths. */
while(jc &&
(jc->m_nest == c_path || column_in_this_or_nested(c_nested, jc)))
{
if (first_column)
first_column= FALSE;
else if (str->append(", "))
return 1;
if (jc->m_nest == c_path)
{
if (jc->print(thd, *f, str))
return 1;
if ((jc= it++))
++(*f);
}
else
{
DBUG_ASSERT(column_in_this_or_nested(c_nested, jc));
if (str->append("NESTED PATH ") ||
print_path(str, &jc->m_nest->m_path) ||
str->append(' ') ||
c_nested->print(thd, f, str, it, &jc))
return 1;
c_nested= c_nested->m_next_nested;
}
}
if (str->append(")"))
return 1;
*last_column= jc;
return 0;
}
/*
Print the SQL definition of the JSON_TABLE.
Used mostly as a part of the CREATE VIEW statement.
@param thd - the thread
@param sql_table - the corresponding TABLE_LIST object
@param str - the string where to print
@param query_type - the query type
*/
int Table_function_json_table::print(THD *thd, TABLE_LIST *sql_table,
String *str, enum_query_type query_type)
{
List_iterator_fast<Json_table_column> jc_i(m_columns);
Json_table_column *jc= jc_i++;
Field **f_list= sql_table->table->field;
DBUG_ENTER("Table_function_json_table::print");
if (str->append("JSON_TABLE("))
DBUG_RETURN(TRUE);
m_json->print(str, query_type);
if (str->append(", ") ||
print_path(str, &m_nested_path.m_path) ||
str->append(' ') ||
m_nested_path.print(thd, &f_list, str, jc_i, &jc) ||
str->append(')'))
DBUG_RETURN(TRUE);
DBUG_RETURN(0);
}
void Table_function_json_table::fix_after_pullout(TABLE_LIST *sql_table,
st_select_lex *new_parent, bool merge)
{
m_json->fix_after_pullout(new_parent, &m_json, merge);
sql_table->dep_tables= used_tables();
}
/*
@brief
Recursively make all tables in the join_list also depend on deps.
*/
static void add_extra_deps(List<TABLE_LIST> *join_list, table_map deps)
{
TABLE_LIST *table;
List_iterator<TABLE_LIST> li(*join_list);
while ((table= li++))
{
table->dep_tables |= deps;
NESTED_JOIN *nested_join;
if ((nested_join= table->nested_join))
{
// set the deps inside, too
add_extra_deps(&nested_join->join_list, deps);
}
}
}
/*
@brief
Add table dependencies that are directly caused by table functions, also
add extra dependencies so that the join optimizer does not construct
"dead-end" join prefixes.
@detail
There are two kinds of limitations on join order:
1A. Outer joins require that inner tables follow outer.
1B. Tables within a join nest must be present in the join order
"without interleaving". See check_interleaving_with_nj for details.
2. Table function argument may refer to *any* table that precedes the
current table in the query text. The table maybe outside of the current
nested join and/or inside another nested join.
One may think that adding dependency according to #2 would be sufficient,
but this is not the case.
@example
select ...
from
t20 left join t21 on t20.a=t21.a
join
(t31 left join (t32 join
JSON_TABLE(t21.js,
'$' COLUMNS (ab INT PATH '$.a')) AS jt
) on t31.a<3
)
Here, jt's argument refers to t21.
Table dependencies are:
t21 -> t20
t32 -> t31
jt -> t21 t31 (also indirectly depends on t20 through t21)
This allows to construct a "dead-end" join prefix, like:
t31, t32
Here, "no interleaving" rule requires the next table to be jt, but we
can't add it, because it depends on t21 which is not in the join prefix.
@end example
Dead-end join prefixes do not work with join prefix pruning done for
@@optimizer_prune_level: it is possible that all non-dead-end prefixes are
pruned away.
The solution is as follows: if there is an outer join that contains
(directly on indirectly) a table function JT which has a reference JREF
outside of the outer join:
left join ( T_I ... json_table(JREF, ...) as JT ...)
then make *all* tables T_I also dependent on outside references in JREF.
This way, the optimizer will put table T_I into the join prefix only when
JT can be put there as well, and "dead-end" prefixes will not be built.
@param join_list List of tables to process. Initial invocation should
supply the JOIN's top-level table list.
@param nest_tables Bitmap of all tables in the join list.
@return Bitmap of all outside references that tables in join_list have
*/
table_map add_table_function_dependencies(List<TABLE_LIST> *join_list,
table_map nest_tables)
{
TABLE_LIST *table;
table_map res= 0;
List_iterator<TABLE_LIST> li(*join_list);
// Recursively compute extra dependencies
while ((table= li++))
{
NESTED_JOIN *nested_join;
if ((nested_join= table->nested_join))
{
res |= add_table_function_dependencies(&nested_join->join_list,
nested_join->used_tables);
}
else if (table->table_function)
{
table->dep_tables |= table->table_function->used_tables();
res |= table->dep_tables;
}
}
res= res & ~nest_tables & ~PSEUDO_TABLE_BITS;
// Then, make all "peers" have them:
if (res)
add_extra_deps(join_list, res);
return res;
}