用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

677 lines
26 KiB

5 months ago
/* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
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, version 2.0, 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 */
/**
@file
@brief
This file contains the implementation for the Item that implements
ST_Buffer().
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <algorithm>
#include <boost/concept/usage.hpp>
#include <boost/geometry/algorithms/buffer.hpp>
#include <boost/geometry/strategies/agnostic/buffer_distance_symmetric.hpp>
#include <boost/geometry/strategies/buffer.hpp>
#include <boost/geometry/strategies/cartesian/buffer_end_flat.hpp>
#include <boost/geometry/strategies/cartesian/buffer_end_round.hpp>
#include <boost/geometry/strategies/cartesian/buffer_join_miter.hpp>
#include <boost/geometry/strategies/cartesian/buffer_join_round.hpp>
#include <boost/geometry/strategies/cartesian/buffer_point_circle.hpp>
#include <boost/geometry/strategies/cartesian/buffer_point_square.hpp>
#include <boost/geometry/strategies/cartesian/buffer_side_straight.hpp>
#include <boost/geometry/strategies/strategies.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <memory> // std::unique_ptr
#include <vector>
#include "m_ctype.h"
#include "m_string.h"
#include "my_byteorder.h"
#include "my_dbug.h"
#include "my_inttypes.h"
#include "my_sys.h"
#include "mysqld_error.h"
#include "sql/current_thd.h"
#include "sql/dd/cache/dictionary_client.h"
#include "sql/dd/types/spatial_reference_system.h"
#include "sql/derror.h" // ER_THD
#include "sql/item.h"
#include "sql/item_geofunc.h"
#include "sql/item_geofunc_internal.h"
#include "sql/item_strfunc.h"
#include "sql/parse_tree_node_base.h"
#include "sql/spatial.h"
#include "sql/sql_class.h" // THD
#include "sql/sql_error.h"
#include "sql/sql_exception_handler.h"
#include "sql/srs_fetcher.h"
#include "sql/system_variables.h"
#include "sql_string.h"
#include "template_utils.h"
class PT_item_list;
namespace boost {
namespace geometry {
namespace cs {
struct cartesian;
} // namespace cs
} // namespace geometry
} // namespace boost
static const char *const buffer_strategy_names[] = {
"invalid_strategy", "end_round", "end_flat", "join_round",
"join_miter", "point_circle", "point_square"};
template <typename Char_type>
inline int char_icmp(const Char_type a, const Char_type b) {
const int a1 = std::tolower(a);
const int b1 = std::tolower(b);
return a1 > b1 ? 1 : (a1 < b1 ? -1 : 0);
}
/**
Case insensitive comparison of two ascii strings.
@param a '\0' ended string.
@param b '\0' ended string.
*/
template <typename Char_type>
int str_icmp(const Char_type *a, const Char_type *b) {
int ret = 0, i;
for (i = 0; a[i] != 0 && b[i] != 0; i++)
if ((ret = char_icmp(a[i], b[i]))) return ret;
if (a[i] == 0 && b[i] != 0) return -1;
if (a[i] != 0 && b[i] == 0) return 1;
return 0;
}
/*
Convert strategies stored in String objects into Strategy_setting objects.
*/
void Item_func_buffer::set_strategies() {
for (int i = 0; i < num_strats; i++) {
String *pstr = strategies[i];
const uchar *pstrat = pointer_cast<const uchar *>(pstr->ptr());
uint32 snum = 0;
if (pstr->length() != 12 ||
!((snum = uint4korr(pstrat)) > invalid_strategy &&
snum <= max_strategy)) {
my_error(ER_WRONG_ARGUMENTS, MYF(0), "st_buffer");
null_value = true;
return;
}
const enum_buffer_strategies strat = (enum_buffer_strategies)snum;
double value;
float8get(&value, pstrat + 4);
enum_buffer_strategy_types strategy_type = invalid_strategy_type;
switch (strat) {
case end_round:
case end_flat:
strategy_type = end_strategy;
break;
case join_round:
case join_miter:
strategy_type = join_strategy;
break;
case point_circle:
case point_square:
strategy_type = point_strategy;
break;
default:
my_error(ER_WRONG_ARGUMENTS, MYF(0), "st_buffer");
null_value = true;
return;
break;
}
// Each strategy option can be set no more than once for every ST_Buffer()
// call.
if (settings[strategy_type].strategy != invalid_strategy) {
my_error(ER_WRONG_ARGUMENTS, MYF(0), "st_buffer");
null_value = true;
return;
} else {
settings[strategy_type].strategy = (enum_buffer_strategies)snum;
settings[strategy_type].value = value;
}
}
}
Item_func_buffer_strategy::Item_func_buffer_strategy(const POS &pos,
PT_item_list *ilist)
: Item_str_func(pos, ilist) {
// Here we want to use the String::set(const char*, ..) version.
const char *pbuf = tmp_buffer;
tmp_value.set(pbuf, 0, NULL);
}
bool Item_func_buffer_strategy::resolve_type(THD *) {
collation.set(&my_charset_bin);
decimals = 0;
max_length = 16;
maybe_null = true;
return false;
}
String *Item_func_buffer_strategy::val_str(String * /* str_arg */) {
String str;
String *strat_name = args[0]->val_str_ascii(&str);
if ((null_value = args[0]->null_value)) {
DBUG_ASSERT(maybe_null);
return NULL;
}
// Get the NULL-terminated ascii string.
const char *pstrat_name = strat_name->c_ptr_safe();
bool found = false;
tmp_value.set_charset(&my_charset_bin);
// The tmp_value is supposed to always stores a {uint32,double} pair,
// and it uses a char tmp_buffer[16] array data member.
uchar *result_buf = pointer_cast<uchar *>(tmp_value.ptr());
// Although the result of this item node is never persisted, we still have to
// use portable endianess access otherwise unaligned access will crash
// on sparc CPUs.
for (uint32 i = 0; i <= Item_func_buffer::max_strategy; i++) {
// The above var_str_ascii() call makes the strat_name an ascii string so
// we can do below comparison.
if (str_icmp(pstrat_name, buffer_strategy_names[i]) != 0) continue;
int4store(result_buf, i);
result_buf += 4;
Item_func_buffer::enum_buffer_strategies istrat =
static_cast<Item_func_buffer::enum_buffer_strategies>(i);
/*
The end_flat and point_square strategies must have no more arguments;
The rest strategies must have 2nd parameter which must be a positive
numeric value, and we will store it as a double.
We use float8store to ensure that the value is independent of endianness.
*/
if (istrat != Item_func_buffer::end_flat &&
istrat != Item_func_buffer::point_square) {
if (arg_count != 2) {
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
double val = args[1]->val_real();
if ((null_value = args[1]->null_value)) {
DBUG_ASSERT(maybe_null);
return NULL;
}
if (val <= 0) {
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
if (istrat != Item_func_buffer::join_miter &&
val > current_thd->variables.max_points_in_geometry) {
my_error(ER_GIS_MAX_POINTS_IN_GEOMETRY_OVERFLOWED, MYF(0),
"points_per_circle",
current_thd->variables.max_points_in_geometry, func_name());
return error_str();
}
float8store(result_buf, val);
} else if (arg_count != 1) {
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
} else
float8store(result_buf, 0.0);
found = true;
break;
}
// Unrecognized strategy names, report error.
if (!found) {
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
tmp_value.length(12);
return &tmp_value;
}
#define CALL_BG_BUFFER(result, geom, geom_out, dist_strategy, side_strategy, \
join_strategy, end_strategy, point_strategy) \
do { \
(result) = false; \
switch ((geom)->get_type()) { \
case Geometry::wkb_point: { \
BG_models<bgcs::cartesian>::Point bg( \
(geom)->get_data_ptr(), (geom)->get_data_size(), \
(geom)->get_flags(), (geom)->get_srid()); \
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy), \
(join_strategy), (end_strategy), (point_strategy)); \
break; \
} \
case Geometry::wkb_multipoint: { \
BG_models<bgcs::cartesian>::Multipoint bg( \
(geom)->get_data_ptr(), (geom)->get_data_size(), \
(geom)->get_flags(), (geom)->get_srid()); \
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy), \
(join_strategy), (end_strategy), (point_strategy)); \
break; \
} \
case Geometry::wkb_linestring: { \
BG_models<bgcs::cartesian>::Linestring bg( \
(geom)->get_data_ptr(), (geom)->get_data_size(), \
(geom)->get_flags(), (geom)->get_srid()); \
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy), \
(join_strategy), (end_strategy), (point_strategy)); \
break; \
} \
case Geometry::wkb_multilinestring: { \
BG_models<bgcs::cartesian>::Multilinestring bg( \
(geom)->get_data_ptr(), (geom)->get_data_size(), \
(geom)->get_flags(), (geom)->get_srid()); \
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy), \
(join_strategy), (end_strategy), (point_strategy)); \
break; \
} \
case Geometry::wkb_polygon: { \
const void *data_ptr = (geom)->normalize_ring_order(); \
if (data_ptr == NULL) { \
my_error(ER_GIS_INVALID_DATA, MYF(0), "st_buffer"); \
(result) = true; \
break; \
} \
BG_models<bgcs::cartesian>::Polygon bg( \
data_ptr, (geom)->get_data_size(), (geom)->get_flags(), \
(geom)->get_srid()); \
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy), \
(join_strategy), (end_strategy), (point_strategy)); \
break; \
} \
case Geometry::wkb_multipolygon: { \
const void *data_ptr = (geom)->normalize_ring_order(); \
if (data_ptr == NULL) { \
my_error(ER_GIS_INVALID_DATA, MYF(0), "st_buffer"); \
(result) = true; \
break; \
} \
BG_models<bgcs::cartesian>::Multipolygon bg( \
data_ptr, (geom)->get_data_size(), (geom)->get_flags(), \
(geom)->get_srid()); \
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy), \
(join_strategy), (end_strategy), (point_strategy)); \
break; \
} \
default: \
DBUG_ASSERT(false); \
break; \
} \
} while (0)
Item_func_buffer::Item_func_buffer(const POS &pos, PT_item_list *ilist)
: Item_geometry_func(pos, ilist) {
num_strats = 0;
memset(settings, 0, sizeof(settings));
memset(strategies, 0, sizeof(strategies));
}
namespace bgst = boost::geometry::strategy::buffer;
String *Item_func_buffer::val_str(String *str_value_arg) {
DBUG_TRACE;
DBUG_ASSERT(fixed == 1);
String strat_bufs[side_strategy + 1];
String *obj = args[0]->val_str(&tmp_value);
if (!obj || args[0]->null_value) return error_str();
double dist = args[1]->val_real();
if (args[1]->null_value) return error_str();
Geometry_buffer buffer;
Geometry *geom;
String *str_result = str_value_arg;
null_value = false;
bg_resbuf_mgr.free_result_buffer();
// Reset the two arrays, set_strategies() requires the settings array to
// be brand new on every ST_Buffer() call.
memset(settings, 0, sizeof(settings));
memset(strategies, 0, sizeof(strategies));
// Strategies options start from 3rd argument, the 1st two arguments are
// never strategies: the 1st is input geometry, and the 2nd is distance.
num_strats = arg_count - 2;
for (uint i = 2; i < arg_count; i++) {
strategies[i - 2] = args[i]->val_str(&strat_bufs[i]);
if (strategies[i - 2] == NULL || args[i]->null_value) return error_str();
}
/*
Do this before simplify_multi_geometry() in order to exclude invalid
WKB/WKT data.
*/
if (!(geom = Geometry::construct(&buffer, obj))) {
my_error(ER_GIS_INVALID_DATA, MYF(0), func_name());
return error_str();
}
if (geom->get_srid() != 0) {
THD *thd = current_thd;
std::unique_ptr<dd::cache::Dictionary_client::Auto_releaser> releaser(
new dd::cache::Dictionary_client::Auto_releaser(thd->dd_client()));
Srs_fetcher fetcher(thd);
const dd::Spatial_reference_system *srs = nullptr;
if (fetcher.acquire(geom->get_srid(), &srs))
return error_str(); // Error has already been flagged.
if (srs == nullptr) {
my_error(ER_SRS_NOT_FOUND, MYF(0), geom->get_srid());
return error_str();
}
if (!srs->is_cartesian()) {
DBUG_ASSERT(srs->is_geographic());
std::string parameters(geom->get_class_info()->m_name.str);
parameters.append(", ...");
my_error(ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS, MYF(0), func_name(),
parameters.c_str());
return error_str();
}
}
/*
If the input geometry is a multi-geometry or geometry collection that has
only one component, extract that component as input argument.
*/
Geometry::wkbType geom_type = geom->get_type();
if (geom_type == Geometry::wkb_multipoint ||
geom_type == Geometry::wkb_multipolygon ||
geom_type == Geometry::wkb_multilinestring ||
geom_type == Geometry::wkb_geometrycollection) {
/*
Make a copy of the geometry byte string argument to work on it,
don't modify the original one since it is assumed to be stable.
Simplifying the argument is worth the effort because buffer computation
is less expensive with simplified geometry.
The copy's buffer may be directly returned as result so it has to be a
data member.
Here we assume that if obj->is_alloced() is false, obj's referring to some
geometry data stored somewhere else so here we cache the simplified
version into m_tmp_geombuf without modifying obj's original referred copy;
otherwise we believe the geometry data
is solely owned by obj and that each call of this ST_Buffer() is given
a valid GEOMETRY byte string, i.e. it is structually valid and if it was
simplified before, the obj->m_length was correctly set to the new length
after the simplification operation.
*/
const bool use_buffer = !obj->is_alloced();
if (simplify_multi_geometry(obj, (use_buffer ? &m_tmp_geombuf : NULL)) &&
use_buffer)
obj = &m_tmp_geombuf;
if (!(geom = Geometry::construct(&buffer, obj))) {
my_error(ER_GIS_INVALID_DATA, MYF(0), func_name());
return error_str();
}
}
/*
If distance passed to ST_Buffer is too small, then we return the
original geometry as its buffer. This is needed to avoid division
overflow in buffer calculation, as well as for performance purposes.
*/
if (std::abs(dist) <= GIS_ZERO || is_empty_geocollection(geom)) {
null_value = 0;
str_result = obj;
return str_result;
}
Geometry::wkbType gtype = geom->get_type();
if (dist < 0 && gtype != Geometry::wkb_polygon &&
gtype != Geometry::wkb_multipolygon &&
gtype != Geometry::wkb_geometrycollection) {
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
set_strategies();
if (null_value) return error_str();
/*
str_result will refer to BG object's memory directly if any, here we remove
last call's remainings so that if this call doesn't produce any result,
this call won't note down last address(already freed above) and
next call won't free already free'd memory.
*/
str_result->set(NullS, 0, &my_charset_bin);
bool had_except = false;
try {
Strategy_setting ss1 = settings[end_strategy];
Strategy_setting ss2 = settings[join_strategy];
Strategy_setting ss3 = settings[point_strategy];
const bool is_pts =
(gtype == Geometry::wkb_point || gtype == Geometry::wkb_multipoint);
const bool is_plygn =
(gtype == Geometry::wkb_polygon || gtype == Geometry::wkb_multipolygon);
const bool is_ls = (gtype == Geometry::wkb_linestring ||
gtype == Geometry::wkb_multilinestring);
/*
Some strategies can be applied to only part of the geometry types and
coordinate systems. For now we only have cartesian coordinate system
so no check for them.
*/
if ((is_pts && (ss1.strategy != invalid_strategy ||
ss2.strategy != invalid_strategy)) ||
(is_plygn && (ss1.strategy != invalid_strategy ||
ss3.strategy != invalid_strategy)) ||
(is_ls && ss3.strategy != invalid_strategy)) {
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
bgst::distance_symmetric<double> dist_strat(dist);
bgst::side_straight side_strat;
bgst::end_round bgst_end_round(
ss1.strategy == invalid_strategy ? 32 : ss1.value);
bgst::end_flat bgst_end_flat;
bgst::join_round bgst_join_round(
ss2.strategy == invalid_strategy ? 32 : ss2.value);
bgst::join_miter bgst_join_miter(ss2.value);
bgst::point_circle bgst_point_circle(
ss3.strategy == invalid_strategy ? 32 : ss3.value);
bgst::point_square bgst_point_square;
/*
Default strategies if not specified:
end_round(32), join_round(32), point_circle(32)
The order of enum items in enum enum_buffer_strategies is crucial for
this setting to be correct, don't modify it.
Although point strategy isn't needed for linear and areal geometries,
we have to specify it because of bg::buffer interface, and BG will
silently ignore it. Similarly for other strategies.
*/
int strats_combination = 0;
if (ss1.strategy == end_flat) strats_combination |= 1;
if (ss2.strategy == join_miter) strats_combination |= 2;
if (ss3.strategy == point_square) strats_combination |= 4;
BG_models<bgcs::cartesian>::Multipolygon result;
result.set_srid(geom->get_srid());
if (geom->get_type() != Geometry::wkb_geometrycollection) {
bool ret = false;
switch (strats_combination) {
case 0:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_round, bgst_end_round, bgst_point_circle);
break;
case 1:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_round, bgst_end_flat, bgst_point_circle);
break;
case 2:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_miter, bgst_end_round, bgst_point_circle);
break;
case 3:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_miter, bgst_end_flat, bgst_point_circle);
break;
case 4:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_round, bgst_end_round, bgst_point_square);
break;
case 5:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_round, bgst_end_flat, bgst_point_square);
break;
case 6:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_miter, bgst_end_round, bgst_point_square);
break;
case 7:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_miter, bgst_end_flat, bgst_point_square);
break;
default:
DBUG_ASSERT(false);
break;
}
if (ret) return error_str();
if (result.size() == 0) {
str_result->reserve(GEOM_HEADER_SIZE + 4);
write_geometry_header(str_result, geom->get_srid(),
Geometry::wkb_geometrycollection, 0);
return str_result;
} else if (post_fix_result(&bg_resbuf_mgr, result, str_result))
return error_str();
bg_resbuf_mgr.set_result_buffer(str_result->ptr());
} else {
// Compute buffer for a geometry collection(GC). We first compute buffer
// for each component of the GC, and put the buffer polygons into another
// collection, finally merge components of the collection.
BG_geometry_collection bggc, bggc2;
bggc.fill(geom);
for (BG_geometry_collection::Geometry_list::iterator i =
bggc.get_geometries().begin();
i != bggc.get_geometries().end(); ++i) {
BG_models<bgcs::cartesian>::Multipolygon res;
String temp_result;
res.set_srid((*i)->get_srid());
Geometry::wkbType gtype = (*i)->get_type();
if (dist < 0 && gtype != Geometry::wkb_multipolygon &&
gtype != Geometry::wkb_polygon) {
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
bool ret = false;
switch (strats_combination) {
case 0:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_round, bgst_end_round, bgst_point_circle);
break;
case 1:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_round, bgst_end_flat, bgst_point_circle);
break;
case 2:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_miter, bgst_end_round, bgst_point_circle);
break;
case 3:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_miter, bgst_end_flat, bgst_point_circle);
break;
case 4:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_round, bgst_end_round, bgst_point_square);
break;
case 5:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_round, bgst_end_flat, bgst_point_square);
break;
case 6:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_miter, bgst_end_round, bgst_point_square);
break;
case 7:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_miter, bgst_end_flat, bgst_point_square);
break;
default:
DBUG_ASSERT(false);
break;
}
if (ret) return error_str();
if (res.size() == 0) continue;
if (post_fix_result(&bg_resbuf_mgr, res, &temp_result))
return error_str();
// A single component's buffer is computed above and stored here.
bggc2.fill(&res);
}
// Merge the accumulated polygons because they may overlap.
bggc2.merge_components<bgcs::cartesian>(&null_value);
Gis_geometry_collection *gc = bggc2.as_geometry_collection(str_result);
delete gc;
}
/*
If the result geometry is a multi-geometry or geometry collection that has
only one component, extract that component as result.
*/
simplify_multi_geometry(str_result, NULL);
} catch (...) {
had_except = true;
handle_gis_exception("st_buffer");
}
if (had_except) return error_str();
return str_result;
}