MDEV-27128: Implement JSON Schema Validation FUNCTION

Implementation:
Implementation is made according to json schema validation draft 2020

JSON schema basically has same structure as that of json object, consisting
of key-value pairs. So it can be parsed in the same manner as
any json object.

However, none of the keywords are mandatory, so making guess about the
json value type based only on the keywords would be incorrect.
Hence we need separate objects denoting each keyword.

So during create_object_and_handle_keyword() we create appropriate objects
based on the keywords and validate each of them individually on the json
document by calling respective validate() function if the type matches.
If any of them fails, return false, else return true.
This commit is contained in:
Rucha Deodhar 2022-10-28 13:03:13 +05:30
parent af0e0ad18d
commit 358b8495f5
17 changed files with 7477 additions and 33 deletions

View file

@ -65,6 +65,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
../sql/item_geofunc.cc ../sql/item_row.cc ../sql/item_strfunc.cc
../sql/item_subselect.cc ../sql/item_sum.cc ../sql/item_timefunc.cc
../sql/item_xmlfunc.cc ../sql/item_jsonfunc.cc
../sql/json_schema.cc ../sql/json_schema_helper.cc
../sql/key.cc ../sql/lock.cc ../sql/log.cc
../sql/log_event.cc ../sql/log_event_server.cc
../sql/mf_iocache.cc ../sql/my_decimal.cc

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,13 @@ SET @json1= '{"key1":"val1"}';
SET @json2= '{"key1":"val1"}';
SELECT JSON_OVERLAPS(@json1, @json2);
ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable.
SET @schema_array= '{
"type":"array",
"items": {"type":"number"},
"maxItems": 4,
"minItems": 2}';
SELECT JSON_SCHEMA_VALID(@schema_array, '[1, 2, 3]');
ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable.
SET @@debug_dbug= @saved_dbug;
#
# End of 10.9 test

View file

@ -15,6 +15,15 @@ SET @json2= '{"key1":"val1"}';
--error ER_STACK_OVERRUN_NEED_MORE
SELECT JSON_OVERLAPS(@json1, @json2);
SET @schema_array= '{
"type":"array",
"items": {"type":"number"},
"maxItems": 4,
"minItems": 2}';
--replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/
--error ER_STACK_OVERRUN_NEED_MORE
SELECT JSON_SCHEMA_VALID(@schema_array, '[1, 2, 3]');
SET @@debug_dbug= @saved_dbug;
--echo #

View file

@ -161,7 +161,7 @@ SET (SQL_SOURCE
opt_table_elimination.cc sql_expression_cache.cc
gcalc_slicescan.cc gcalc_tools.cc
my_apc.cc mf_iocache_encr.cc item_jsonfunc.cc
my_json_writer.cc
my_json_writer.cc json_schema.cc json_schema_helper.cc
rpl_gtid.cc rpl_parallel.cc
semisync.cc semisync_master.cc semisync_slave.cc
semisync_master_ack_receiver.cc

View file

@ -1352,6 +1352,18 @@ protected:
virtual ~Create_func_json_overlaps() {}
};
class Create_func_json_schema_valid: public Create_func_arg2
{
public:
virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2);
static Create_func_json_schema_valid s_singleton;
protected:
Create_func_json_schema_valid() {}
virtual ~Create_func_json_schema_valid() {}
};
class Create_func_last_day : public Create_func_arg1
{
@ -4413,6 +4425,15 @@ Create_func_last_insert_id::create_native(THD *thd, const LEX_CSTRING *name,
return func;
}
Create_func_json_schema_valid Create_func_json_schema_valid::s_singleton;
Item*
Create_func_json_schema_valid::create_2_arg(THD *thd, Item *arg1, Item *arg2)
{
status_var_increment(thd->status_var.feature_json);
return new (thd->mem_root) Item_func_json_schema_valid(thd, arg1, arg2);
}
Create_func_lcase Create_func_lcase::s_singleton;
@ -5824,6 +5845,7 @@ Native_func_registry func_array[] =
{ { STRING_WITH_LEN("JSON_OVERLAPS") }, BUILDER(Create_func_json_overlaps)},
{ { STRING_WITH_LEN("JSON_REMOVE") }, BUILDER(Create_func_json_remove)},
{ { STRING_WITH_LEN("JSON_REPLACE") }, BUILDER(Create_func_json_replace)},
{ { STRING_WITH_LEN("JSON_SCHEMA_VALID") }, BUILDER(Create_func_json_schema_valid)},
{ { STRING_WITH_LEN("JSON_SET") }, BUILDER(Create_func_json_set)},
{ { STRING_WITH_LEN("JSON_SEARCH") }, BUILDER(Create_func_json_search)},
{ { STRING_WITH_LEN("JSON_TYPE") }, BUILDER(Create_func_json_type)},

View file

@ -19,21 +19,7 @@
#include "sql_class.h"
#include "item.h"
#include "sql_parse.h" // For check_stack_overrun
/*
Allocating memory and *also* using it (reading and
writing from it) because some build instructions cause
compiler to optimize out stack_used_up. Since alloca()
here depends on stack_used_up, it doesnt get executed
correctly and causes json_debug_nonembedded to fail
( --error ER_STACK_OVERRUN_NEED_MORE does not occur).
*/
#define ALLOCATE_MEM_ON_STACK(A) do \
{ \
uchar *array= (uchar*)alloca(A); \
bzero(array, A); \
my_checksum(0, array, A); \
} while(0)
#include "json_schema_helper.h"
/*
Compare ASCII string against the string with the specified
@ -4736,3 +4722,104 @@ bool Item_func_json_overlaps::fix_length_and_dec(THD *thd)
return Item_bool_func::fix_length_and_dec(thd);
}
longlong Item_func_json_schema_valid::val_int()
{
json_engine_t ve;
int is_valid= 1;
if (!schema_parsed)
return 0;
val= args[1]->val_json(&tmp_val);
if (!val || !val->length())
{
null_value= 0;
return 1;
}
json_scan_start(&ve, val->charset(), (const uchar *) val->ptr(),
(const uchar *) val->end());
if (json_read_value(&ve))
goto end;
if (!keyword_list.is_empty())
{
List_iterator <Json_schema_keyword> it(keyword_list);;
Json_schema_keyword* curr_keyword= NULL;
while ((curr_keyword=it++))
{
if (curr_keyword->validate(&ve, NULL, NULL))
{
is_valid= 0;
break;
}
}
}
end:
if (unlikely(ve.s.error))
{
is_valid= 0;
report_json_error(val, &ve, 2);
}
return is_valid;
}
/*
Idea behind implementation:
JSON schema basically has same structure as that of json object, consisting of
key-value pairs. So it can be parsed in the same manner as any json object.
However, none of the keywords are mandatory, so making guess about the json value
type based only on the keywords would be incorrect. Hence we need separate objects
denoting each keyword.
So during create_object_and_handle_keyword() we create appropriate objects
based on the keywords and validate each of them individually on the json
document by calling respective validate() function if the type matches.
If any of them fails, return false, else return true.
*/
bool Item_func_json_schema_valid::fix_length_and_dec(THD *thd)
{
json_engine_t je;
bool res= 0;
String *js= args[0]->val_json(&tmp_js);
if ((null_value= args[0]->null_value))
return 0;
json_scan_start(&je, js->charset(), (const uchar *) js->ptr(),
(const uchar *) js->ptr() + js->length());
if (!create_object_and_handle_keyword(thd, &je, &keyword_list,
&all_keywords))
schema_parsed= true;
else
res= true;
if (je.s.error)
report_json_error(js, &je, 1);
return res || Item_bool_func::fix_length_and_dec(thd);
}
void Item_func_json_schema_valid::cleanup()
{
DBUG_ENTER("Item_func_json_schema_valid::cleanup");
Item_bool_func::cleanup();
List_iterator<Json_schema_keyword> it2(all_keywords);
Json_schema_keyword *curr_schema;
while ((curr_schema= it2++))
{
delete curr_schema;
curr_schema= nullptr;
}
all_keywords.empty();
keyword_list.empty();
DBUG_VOID_RETURN;
}

View file

@ -25,6 +25,7 @@
#include "item_strfunc.h" // Item_str_func
#include "item_sum.h"
#include "sql_type_json.h"
#include "json_schema.h"
class json_path_with_flags
{
@ -779,7 +780,8 @@ class Item_func_json_overlaps: public Item_bool_func
String tmp_val, *val;
public:
Item_func_json_overlaps(THD *thd, Item *a, Item *b):
Item_bool_func(thd, a, b) {}
Item_bool_func(thd, a, b)
{}
LEX_CSTRING func_name_cstring() const override
{
static LEX_CSTRING name= {STRING_WITH_LEN("json_overlaps") };
@ -791,4 +793,31 @@ public:
{ return get_item_copy<Item_func_json_overlaps>(thd, this); }
};
class Item_func_json_schema_valid: public Item_bool_func
{
String tmp_js;
bool schema_parsed;
String tmp_val, *val;
List<Json_schema_keyword> keyword_list;
List<Json_schema_keyword> all_keywords;
public:
Item_func_json_schema_valid(THD *thd, Item *a, Item *b):
Item_bool_func(thd, a, b)
{
val= NULL;
schema_parsed= false;
}
LEX_CSTRING func_name_cstring() const override
{
static LEX_CSTRING name= {STRING_WITH_LEN("json_schema_valid") };
return name;
}
bool fix_length_and_dec(THD *thd) override;
longlong val_int() override;
Item *get_copy(THD *thd) override
{ return get_item_copy<Item_func_json_schema_valid>(thd, this); }
void cleanup() override;
};
#endif /* ITEM_JSONFUNC_INCLUDED */

2819
sql/json_schema.cc Normal file

File diff suppressed because it is too large Load diff

827
sql/json_schema.h Normal file
View file

@ -0,0 +1,827 @@
#ifndef JSON_SCHEMA_INCLUDED
#define JSON_SCHEMA_INCLUDED
/* Copyright (c) 2016, 2021, MariaDB
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-1301 USA */
/* This file defines all json schema classes. */
#include "sql_class.h"
#include "sql_type_json.h"
#include "json_schema_helper.h"
struct st_json_schema_keyword_map;
class Json_schema_keyword : public Sql_alloc
{
public:
Json_schema_keyword *alternate_schema;
st_json_schema_keyword_map *keyword_map;
double value;
uint priority;
bool allowed;
Json_schema_keyword() : alternate_schema(NULL), keyword_map(NULL),
value(0), priority(0), allowed(true)
{
}
virtual ~Json_schema_keyword() = default;
/*
Called for each keyword on the current level.
*/
virtual bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL)
{ return false; }
virtual bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords)
{
return false;
}
virtual List<Json_schema_keyword>* get_validation_keywords()
{
return NULL;
}
void set_alternate_schema(Json_schema_keyword *schema)
{
alternate_schema= schema;
}
virtual bool fall_back_on_alternate_schema(const json_engine_t *je,
const uchar* k_start= NULL,
const uchar* k_end= NULL);
virtual bool validate_as_alternate(const json_engine_t *je,
const uchar* k_start= NULL,
const uchar* k_end= NULL)
{
return false;
}
virtual bool validate_schema_items(const json_engine_t *je,
List<Json_schema_keyword>*schema_items);
virtual void set_alternate_schema_choice(Json_schema_keyword *schema1,
Json_schema_keyword *schema2)
{
return;
}
virtual void set_dependents(Json_schema_keyword *schema1,
Json_schema_keyword *schema2)
{
return;
}
};
/*
Additional and unvaluated keywords and items handle
keywords and validate schema in same way, so it makes sense
to have a base class for them.
*/
class Json_schema_additional_and_unevaluated : public Json_schema_keyword
{
public:
List<Json_schema_keyword> schema_list;
Json_schema_additional_and_unevaluated()
{
allowed= true;
}
void set_allowed(bool allowed_val)
{
allowed= allowed_val;
}
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
bool validate(const json_engine_t *je,
const uchar *k_start= NULL,
const uchar *k_end= NULL) override
{
return false;
}
bool validate_as_alternate(const json_engine_t *je, const uchar *k_start,
const uchar *k_end) override;
};
class Json_schema_annotation : public Json_schema_keyword
{
public:
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_format : public Json_schema_keyword
{
public:
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
typedef List<Json_schema_keyword> List_schema_keyword;
class Json_schema_type : public Json_schema_keyword
{
private:
uint type;
public:
bool validate(const json_engine_t *je,
const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd,
json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
Json_schema_type()
{
type= 0;
}
};
class Json_schema_const : public Json_schema_keyword
{
private:
char *const_json_value;
public:
enum json_value_types type;
bool validate(const json_engine_t *je,
const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
Json_schema_const()
{
const_json_value= NULL;
}
};
enum enum_scalar_values {
HAS_NO_VAL= 0, HAS_TRUE_VAL= 2,
HAS_FALSE_VAL= 4, HAS_NULL_VAL= 8
};
class Json_schema_enum : public Json_schema_keyword
{
private:
HASH enum_values;
uint enum_scalar;
public:
bool validate(const json_engine_t *je,
const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
Json_schema_enum()
{
enum_scalar= HAS_NO_VAL;
}
~Json_schema_enum()
{
my_hash_free(&enum_values);
}
};
class Json_schema_maximum : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je,
const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_minimum : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je,
const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_multiple_of : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_ex_maximum : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_ex_minimum : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_max_len : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_min_len : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_pattern : public Json_schema_keyword
{
private:
Regexp_processor_pcre re;
Item *pattern;
Item_string *str;
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
Json_schema_pattern()
{
str= NULL;
pattern= NULL;
}
~Json_schema_pattern() { re.cleanup(); }
};
class Json_schema_max_items : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_min_items : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_max_contains : public Json_schema_keyword
{
public:
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_min_contains : public Json_schema_keyword
{
public:
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
/*
The value of max_contains and min_contains is only
relevant when contains keyword is present.
Hence the pointers to access them directly.
*/
class Json_schema_contains : public Json_schema_keyword
{
public:
List <Json_schema_keyword> contains;
Json_schema_keyword *max_contains;
Json_schema_keyword *min_contains;
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
void set_dependents(Json_schema_keyword *min, Json_schema_keyword *max)
{
min_contains= min;
max_contains= max;
}
};
class Json_schema_unique_items : public Json_schema_keyword
{
private:
bool is_unique;
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_prefix_items : public Json_schema_keyword
{
public:
List <List_schema_keyword> prefix_items;
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
Json_schema_prefix_items()
{
priority= 1;
}
};
class Json_schema_unevaluated_items :
public Json_schema_additional_and_unevaluated
{
public:
Json_schema_unevaluated_items()
{
priority= 4;
}
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
};
class Json_schema_additional_items :
public Json_schema_additional_and_unevaluated
{
public:
Json_schema_additional_items()
{
priority= 3;
}
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
};
class Json_schema_items : public Json_schema_keyword
{
private:
List<Json_schema_keyword> items_schema;
public:
Json_schema_items()
{
priority= 2;
}
void set_allowed(bool allowed_val) { allowed= allowed_val; }
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
bool validate_as_alternate(const json_engine_t *je, const uchar *k_start,
const uchar *k_end) override;
};
class Json_schema_property_names : public Json_schema_keyword
{
protected:
List <Json_schema_keyword> property_names;
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
typedef struct property
{
List<Json_schema_keyword> *curr_schema;
char *key_name;
} st_property;
class Json_schema_properties : public Json_schema_keyword
{
private:
HASH properties;
bool is_hash_inited;
public:
Json_schema_properties()
{
priority= 1;
}
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
~Json_schema_properties()
{
if (is_hash_inited)
my_hash_free(&properties);
}
bool validate_as_alternate(const json_engine_t *je, const uchar *k_start,
const uchar *k_end) override;
};
class Json_schema_dependent_schemas : public Json_schema_keyword
{
private:
HASH properties;
bool is_hash_inited;
public:
~Json_schema_dependent_schemas()
{
if (is_hash_inited)
my_hash_free(&properties);
}
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_additional_properties :
public Json_schema_additional_and_unevaluated
{
public:
Json_schema_additional_properties()
{
priority= 3;
}
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
};
class Json_schema_unevaluated_properties :
public Json_schema_additional_and_unevaluated
{
public:
Json_schema_unevaluated_properties()
{
priority= 4;
}
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
};
typedef struct pattern_to_property : public Sql_alloc
{
Regexp_processor_pcre re;
Item *pattern;
List<Json_schema_keyword> *curr_schema;
}st_pattern_to_property;
class Json_schema_pattern_properties : public Json_schema_keyword
{
private:
Item_string *str;
List<st_pattern_to_property> pattern_properties;
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
Json_schema_pattern_properties()
{
priority= 2;
}
~Json_schema_pattern_properties()
{
str= NULL;
if (!pattern_properties.is_empty())
{
st_pattern_to_property *curr_pattern_to_property= NULL;
List_iterator<st_pattern_to_property> it(pattern_properties);
while((curr_pattern_to_property= it++))
{
curr_pattern_to_property->re.cleanup();
curr_pattern_to_property->pattern= NULL;
delete curr_pattern_to_property;
curr_pattern_to_property= nullptr;
}
}
}
bool validate_as_alternate(const json_engine_t *je, const uchar *k_start,
const uchar *k_end) override;
};
class Json_schema_max_prop : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_min_prop : public Json_schema_keyword
{
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_required : public Json_schema_keyword
{
private:
List <String> required_properties;
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
typedef struct dependent_keyowrds
{
String *property;
List <String> dependents;
} st_dependent_keywords;
class Json_schema_dependent_required : public Json_schema_keyword
{
private:
List<st_dependent_keywords> dependent_required;
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
enum logic_enum { HAS_ALL_OF= 2, HAS_ANY_OF= 4, HAS_ONE_OF= 8, HAS_NOT= 16};
class Json_schema_logic : public Json_schema_keyword
{
protected:
uint logic_flag;
List <List_schema_keyword> schema_items;
Json_schema_keyword *alternate_choice1, *alternate_choice2;
public:
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
Json_schema_logic()
{
logic_flag= 0;
alternate_choice1= alternate_choice2= NULL;
priority= 1;
}
virtual bool validate_count(uint* count, uint* total) { return false; }
void set_alternate_schema_choice(Json_schema_keyword *schema1,
Json_schema_keyword* schema2) override
{
alternate_choice1= schema1;
alternate_choice2= schema2;
}
bool check_validation(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL);
};
class Json_schema_not : public Json_schema_logic
{
private:
List <Json_schema_keyword> schema_list;
public:
Json_schema_not()
{
logic_flag= HAS_NOT;
}
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
bool validate_count(uint *count, uint *total) override
{
return *count !=0;
}
};
class Json_schema_one_of : public Json_schema_logic
{
public:
Json_schema_one_of()
{
logic_flag= HAS_ONE_OF;
}
bool validate_count(uint *count, uint *total) override
{
return !(*count == 1);
}
};
class Json_schema_any_of : public Json_schema_logic
{
public:
Json_schema_any_of()
{
logic_flag= HAS_ANY_OF;
}
bool validate_count(uint *count, uint *total) override
{
return *count == 0;
}
};
class Json_schema_all_of : public Json_schema_logic
{
public:
Json_schema_all_of()
{
logic_flag= HAS_ALL_OF;
}
bool validate_count(uint *count, uint *total) override
{
return *count != *total;
}
};
class Json_schema_conditional : public Json_schema_keyword
{
private:
Json_schema_keyword *if_cond, *else_cond, *then_cond;
public:
List<Json_schema_keyword> conditions_schema;
Json_schema_conditional()
{
if_cond= NULL;
then_cond= NULL;
else_cond= NULL;
}
bool validate(const json_engine_t *je, const uchar *k_start= NULL,
const uchar *k_end= NULL) override;
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
void set_conditions(Json_schema_keyword *if_val,
Json_schema_keyword* then_val,
Json_schema_keyword *else_val)
{
if_cond= if_val;
then_cond= then_val;
else_cond= else_val;
}
List<Json_schema_keyword>* get_validation_keywords() override
{
return &conditions_schema;
}
};
class Json_schema_if : public Json_schema_conditional
{
};
class Json_schema_else : public Json_schema_conditional
{
};
class Json_schema_then : public Json_schema_conditional
{
};
class Json_schema_media_string : public Json_schema_keyword
{
public:
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
class Json_schema_reference : public Json_schema_keyword
{
public:
bool handle_keyword(THD *thd, json_engine_t *je,
const char* key_start,
const char* key_end,
List<Json_schema_keyword> *all_keywords) override;
};
bool create_object_and_handle_keyword(THD *thd, json_engine_t *je,
List<Json_schema_keyword> *keyword_list,
List<Json_schema_keyword> *all_keywords);
uchar* get_key_name_for_property(const char *key_name, size_t *length,
my_bool /* unused */);
uchar* get_key_name_for_func(const char *key_name, size_t *length,
my_bool /* unused */);
enum keyword_flag
{
JSON_SCHEMA_COMMON_KEYWORD= 0,
JSON_SCHEMA_NUMBER_KEYWORD= 1,
JSON_SCHEMA_STRING_KEYWORD= 2,
JSON_SCHEMA_ARRAY_KEYWORD= 3,
JSON_SCHEMA_OBJECT_KEYWORD= 4,
JSON_SCHEMA_LOGIC_KEYWORD= 5,
JSON_SCHEMA_CONDITION_KEYWORD= 6,
JSON_SCHEMA_ANNOTATION_KEYWORD= 7,
JSON_SCHEMA_FORMAT_KEYWORD= 8,
JSON_SCHEMA_MEDIA_KEYWORD= 9,
JSON_SCHEMA_REFERENCE_KEYWORD= 10,
JSON_SCHEMA_EMPTY_KEYWORD= 11
};
typedef struct st_json_schema_keyword_map
{
LEX_CSTRING func_name;
Json_schema_keyword*(*func)(THD*);
enum keyword_flag flag;
} json_schema_keyword_map;
bool setup_json_schema_keyword_hash();
void cleanup_json_schema_keyword_hash();
#endif

102
sql/json_schema_helper.cc Normal file
View file

@ -0,0 +1,102 @@
/* Copyright (c) 2016, 2022, 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-1301 USA */
#include "mariadb.h"
#include "sql_class.h"
#include "sql_parse.h" // For check_stack_overrun
#include <m_string.h>
#include "json_schema_helper.h"
bool json_key_equals(const char* key, LEX_CSTRING val, int key_len)
{
return (size_t)key_len == val.length && !strncmp(key, val.str, key_len);
}
bool json_assign_type(uint *curr_type, json_engine_t *je)
{
const char* curr_value= (const char*)je->value;
int len= je->value_len;
if (json_key_equals(curr_value, { STRING_WITH_LEN("number") }, len))
*curr_type|= (1 << JSON_VALUE_NUMBER);
else if(json_key_equals(curr_value, { STRING_WITH_LEN("string") }, len))
*curr_type|= (1 << JSON_VALUE_STRING);
else if(json_key_equals(curr_value, { STRING_WITH_LEN("array") }, len))
*curr_type|= (1 << JSON_VALUE_ARRAY);
else if(json_key_equals(curr_value, { STRING_WITH_LEN("object") }, len))
*curr_type|= (1 << JSON_VALUE_OBJECT);
else if (json_key_equals(curr_value, { STRING_WITH_LEN("boolean") }, len))
*curr_type|= ((1 << JSON_VALUE_TRUE) | (1 << JSON_VALUE_FALSE));
else if (json_key_equals(curr_value, { STRING_WITH_LEN("null") }, len))
*curr_type|= (1 << JSON_VALUE_NULL);
else
{
my_error(ER_JSON_INVALID_VALUE_FOR_KEYWORD, MYF(0), "type");
return true;
}
return false;
}
uchar* get_key_name(const char *key_name, size_t *length,
my_bool /* unused */)
{
*length= strlen(key_name);
return (uchar*) key_name;
}
void json_get_normalized_string(json_engine_t *je, String *res,
int *error)
{
char *val_begin= (char*)je->value, *val_end;
String val;
DYNAMIC_STRING a_res;
if (init_dynamic_string(&a_res, NULL, 0, 0))
goto error;
if (!json_value_scalar(je))
{
if (json_skip_level(je))
goto error;
}
val_end= json_value_scalar(je) ? val_begin+je->value_len :
(char *)je->s.c_str;
val.set((const char*)val_begin, val_end-val_begin, je->s.cs);
if (je->value_type == JSON_VALUE_NUMBER ||
je->value_type == JSON_VALUE_ARRAY ||
je->value_type == JSON_VALUE_OBJECT)
{
if (json_normalize(&a_res, (const char*)val.ptr(),
val_end-val_begin, je->s.cs))
goto error;
}
else if(je->value_type == JSON_VALUE_STRING)
{
strncpy((char*)a_res.str, val.ptr(), je->value_len);
a_res.length= je->value_len;
}
res->append(a_res.str, a_res.length, je->s.cs);
*error= 0;
error:
dynstr_free(&a_res);
return;
}

30
sql/json_schema_helper.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef JSON_SCHEMA_HELPER
#define JSON_SCHEMA_HELPER
/* Copyright (c) 2016, 2021, MariaDB
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-1301 USA */
#include "sql_type_json.h"
#include <m_string.h>
#include "json_schema.h"
bool json_key_equals(const char* key, LEX_CSTRING val, int key_len);
bool json_assign_type(uint *curr_type, json_engine_t *je);
uchar* get_key_name(const char *key_name, size_t *length,
my_bool /* unused */);
void json_get_normalized_string(json_engine_t *je, String *res,
int *error);
#endif

View file

@ -29,22 +29,6 @@
#define HA_ERR_JSON_TABLE (HA_ERR_LAST+1)
/*
Allocating memory and *also* using it (reading and
writing from it) because some build instructions cause
compiler to optimize out stack_used_up. Since alloca()
here depends on stack_used_up, it doesnt get executed
correctly and causes json_debug_nonembedded to fail
( --error ER_STACK_OVERRUN_NEED_MORE does not occur).
*/
#define ALLOCATE_MEM_ON_STACK(A) do \
{ \
uchar *array= (uchar*)alloca(A); \
array[0]= 1; \
array[0]++; \
array[0] ? array[0]++ : array[0]--; \
} while(0)
class table_function_handlerton
{
public:

View file

@ -44,6 +44,7 @@
#include "sql_base.h"
#include "sql_test.h" // mysql_print_status
#include "item_create.h" // item_create_cleanup, item_create_init
#include "json_schema.h"
#include "sql_servers.h" // servers_free, servers_init
#include "init.h" // unireg_init
#include "derror.h" // init_errmessage
@ -1991,6 +1992,7 @@ static void clean_up(bool print_message)
item_func_sleep_free();
lex_free(); /* Free some memory */
item_create_cleanup();
cleanup_json_schema_keyword_hash();
tdc_start_shutdown();
#ifdef HAVE_REPLICATION
semi_sync_master_deinit();
@ -4264,6 +4266,7 @@ static int init_common_variables()
if (item_create_init())
return 1;
item_init();
setup_json_schema_keyword_hash();
/*
Process a comma-separated character set list and choose
the first available character set. This is mostly for

View file

@ -10780,3 +10780,7 @@ ER_CM_OPTION_MISSING_REQUIREMENT
eng "CHANGE MASTER TO option '%s=%s' is missing requirement %s"
ER_SLAVE_STATEMENT_TIMEOUT 70100
eng "Slave log event execution was interrupted (slave_max_statement_time exceeded)"
ER_JSON_INVALID_VALUE_FOR_KEYWORD
eng "Invalid value for keyword %s"
ER_JSON_SCHEMA_KEYWORD_UNSUPPORTED
eng "%s keyword is not supported"

View file

@ -189,4 +189,20 @@ check_table_access(THD *thd, privilege_t requirements,TABLE_LIST *tables,
{ return false; }
#endif /*NO_EMBEDDED_ACCESS_CHECKS*/
/*
Allocating memory and *also* using it (reading and
writing from it) because some build instructions cause
compiler to optimize out stack_used_up. Since alloca()
here depends on stack_used_up, it doesnt get executed
correctly and causes json_debug_nonembedded to fail
( --error ER_STACK_OVERRUN_NEED_MORE does not occur).
*/
#define ALLOCATE_MEM_ON_STACK(A) do \
{ \
uchar *array= (uchar*)alloca(A); \
bzero(array, A); \
my_checksum(0, array, A); \
} while(0)
#endif /* SQL_PARSE_INCLUDED */