用于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.

603 lines
21 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 */
#include "sql/item_geofunc_internal.h"
#include <string.h>
#include <algorithm>
#include <boost/concept/usage.hpp>
#include <boost/geometry/algorithms/centroid.hpp>
#include <boost/geometry/algorithms/is_valid.hpp>
#include <boost/geometry/algorithms/overlaps.hpp>
#include <boost/geometry/core/exception.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/index/predicates.hpp>
#include <boost/geometry/strategies/strategies.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <iterator>
#include <memory>
#include "m_ctype.h"
#include "m_string.h"
#include "my_byteorder.h"
#include "my_dbug.h"
#include "my_inttypes.h"
#include "sql/current_thd.h"
#include "sql/dd/cache/dictionary_client.h"
#include "sql/item_func.h"
#include "sql/mdl.h"
#include "sql/parse_tree_node_base.h"
#include "sql/sql_class.h" // THD
#include "sql/srs_fetcher.h"
#include "sql/system_variables.h"
#include "sql_string.h"
#include "template_utils.h"
namespace dd {
class Spatial_reference_system;
} // namespace dd
bool Srs_fetcher::lock(gis::srid_t srid, enum_mdl_type lock_type) {
DBUG_TRACE;
DBUG_ASSERT(srid != 0);
char id_str[11]; // uint32 => max 10 digits + \0
int10_to_str(srid, id_str, 10);
MDL_request mdl_request;
mdl_request.init_with_source(MDL_key::SRID, "", id_str, lock_type,
MDL_TRANSACTION, __FILE__, __LINE__);
if (m_thd->mdl_context.acquire_lock(&mdl_request,
m_thd->variables.lock_wait_timeout)) {
/* purecov: begin inspected */
// If locking fails, an error has already been flagged.
return true;
/* purecov: end */
}
return false;
}
bool Srs_fetcher::acquire(gis::srid_t srid,
const dd::Spatial_reference_system **srs) {
if (lock(srid, MDL_SHARED_READ)) return true; /* purecov: inspected */
if (m_thd->dd_client()->acquire(srid, srs))
return true; /* purecov: inspected */
return false;
}
bool Srs_fetcher::acquire_for_modification(gis::srid_t srid,
dd::Spatial_reference_system **srs) {
if (lock(srid, MDL_EXCLUSIVE)) return true; /* purecov: inspected */
if (m_thd->dd_client()->acquire_for_modification(srid, srs))
return true; /* purecov: inspected */
return false;
}
bool Srs_fetcher::srs_exists(THD *thd, gis::srid_t srid, bool *exists) {
DBUG_ASSERT(exists);
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(srid, &srs)) return true; /* purecov: inspected */
*exists = (srs != nullptr);
return false;
}
/**
Merge all components as appropriate so that the object contains only
components that don't overlap.
@tparam Coordsys Coordinate system type, specified using those defined in
boost::geometry::cs.
@param[out] pnull_value takes back null_value set during the operation.
*/
template <typename Coordsys>
void BG_geometry_collection::merge_components(bool *pnull_value) {
if (is_comp_no_overlapped()) return;
POS pos;
Item_func_st_union ifsu(pos, NULL, NULL);
bool do_again = true;
uint32 last_composition[6] = {0}, num_unchanged_composition = 0;
size_t last_num_geos = 0;
/*
After each merge_one_run call, see whether the two indicators change:
1. total number of geometry components;
2. total number of each of the 6 types of geometries
If they don't change for N*N/4 times, break out of the loop. Here N is
the total number of geometry components.
There is the rationale:
Given a geometry collection, it's likely that one effective merge_one_run
merges a polygon P and the linestring that crosses it (L) to a
polygon P'(the same one) and another linestring L', the 2 indicators above
don't change but the merge is actually done. If we merge P'
and L' again, they should not be considered cross, but given certain data
BG somehow believes L` still crosses P` even the P and P` are valid, and
it will give us a L'' and P'' which is different from L' and P'
respectively, and L'' is still considered crossing P'',
hence the loop here never breaks out.
If the collection has N components, and we have X [multi]linestrings and
N-X polygons, the number of pairs that can be merged is Y = X * (N-X),
so the largest Y is N*N/4. If the above 2 indicators stay unchanged more
than N*N/4 times the loop runs, we believe all possible combinations in
the collection are enumerated and no effective merge is being done any more.
Note that the L'' and P'' above is different from L' and P' so we can't
compare GEOMETRY byte string, and geometric comparison is expensive and may
still compare unequal and we would still be stuck in the endless loop.
*/
while (!*pnull_value && do_again) {
do_again = merge_one_run<Coordsys>(&ifsu, pnull_value);
if (!*pnull_value && do_again) {
const size_t num_geos = m_geos.size();
uint32 composition[6] = {0};
for (size_t i = 0; i < num_geos; ++i)
composition[m_geos[i]->get_type() - 1]++;
if (num_geos != last_num_geos ||
memcmp(composition, last_composition, sizeof(composition))) {
memcpy(last_composition, composition, sizeof(composition));
last_num_geos = num_geos;
num_unchanged_composition = 0;
} else
num_unchanged_composition++;
if (num_unchanged_composition > (last_num_geos * last_num_geos / 4 + 2))
break;
}
}
}
// Explicit template instantiation
template void BG_geometry_collection::merge_components<
boost::geometry::cs::cartesian>(bool *);
template <typename Coordsys>
inline bool linestring_overlaps_polygon_outerring(const Gis_line_string &ls,
const Gis_polygon &plgn) {
Gis_polygon_ring &oring = plgn.outer();
Gis_line_string ls2(oring.get_ptr(), oring.get_nbytes(), oring.get_flags(),
oring.get_srid());
return boost::geometry::overlaps(ls, ls2);
}
template <typename Coordsys>
bool linear_areal_intersect_infinite(Geometry *g1, Geometry *g2,
bool *pnull_value) {
bool res = false;
/*
If crosses check succeeds, make sure g2 is a valid [multi]polygon, invalid
ones can be accepted by BG and the cross check would be considered true,
we should reject such result and return false in this case.
*/
if (Item_func_spatial_rel::bg_geo_relation_check(
g1, g2, Item_func::SP_CROSSES_FUNC, pnull_value) &&
!*pnull_value) {
Geometry::wkbType g2_type = g2->get_type();
if (g2_type == Geometry::wkb_polygon) {
Gis_polygon plgn(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(),
g2->get_srid());
res = bg::is_valid(plgn);
} else if (g2_type == Geometry::wkb_multipolygon) {
Gis_multi_polygon mplgn(g2->get_data_ptr(), g2->get_data_size(),
g2->get_flags(), g2->get_srid());
res = bg::is_valid(mplgn);
} else
DBUG_ASSERT(false);
return res;
}
if (*pnull_value) return false;
if (g1->get_type() == Geometry::wkb_linestring) {
Gis_line_string ls(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(),
g1->get_srid());
if (g2->get_type() == Geometry::wkb_polygon) {
Gis_polygon plgn(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(),
g2->get_srid());
res = linestring_overlaps_polygon_outerring<Coordsys>(ls, plgn);
} else {
Gis_multi_polygon mplgn(g2->get_data_ptr(), g2->get_data_size(),
g2->get_flags(), g2->get_srid());
for (size_t i = 0; i < mplgn.size(); i++) {
if (linestring_overlaps_polygon_outerring<Coordsys>(ls, mplgn[i]))
return true;
}
}
} else {
Gis_multi_line_string mls(g1->get_data_ptr(), g1->get_data_size(),
g1->get_flags(), g1->get_srid());
if (g2->get_type() == Geometry::wkb_polygon) {
Gis_polygon plgn(g2->get_data_ptr(), g2->get_data_size(), g2->get_flags(),
g2->get_srid());
for (size_t i = 0; i < mls.size(); i++) {
if (linestring_overlaps_polygon_outerring<Coordsys>(mls[i], plgn))
return true;
}
} else {
Gis_multi_polygon mplgn(g2->get_data_ptr(), g2->get_data_size(),
g2->get_flags(), g2->get_srid());
for (size_t j = 0; j < mls.size(); j++) {
for (size_t i = 0; i < mplgn.size(); i++) {
if (linestring_overlaps_polygon_outerring<Coordsys>(mls[j], mplgn[i]))
return true;
}
}
}
}
return res;
}
/**
Create this class for exception safety --- destroy the objects referenced
by the pointers in the set when destroying the container.
*/
template <typename T>
class Pointer_vector : public std::vector<T *> {
typedef std::vector<T *> parent;
public:
~Pointer_vector() {
for (typename parent::iterator i = this->begin(); i != this->end(); ++i)
delete (*i);
}
};
// A unary predicate to locate a target Geometry object pointer from a sequence.
class Is_target_geometry {
Geometry *m_target;
public:
Is_target_geometry(Geometry *t) : m_target(t) {}
bool operator()(Geometry *g) { return g == m_target; }
};
class Rtree_entry_compare {
public:
Rtree_entry_compare() {}
bool operator()(const BG_rtree_entry &re1, const BG_rtree_entry &re2) const {
return re1.second < re2.second;
}
};
/**
One run of merging components.
@tparam Coordsys Coordinate system type, specified using those defined in
boost::geometry::cs.
@param ifsu the Item_func_spatial_operation object, we here rely on it to
do union operation.
@param[out] pnull_value takes back null_value set during the operation.
@return whether need another call of this function.
*/
template <typename Coordsys>
bool BG_geometry_collection::merge_one_run(Item_func_st_union *ifsu,
bool *pnull_value) {
Geometry *gres = NULL;
bool has_new = false;
bool &null_value = *pnull_value;
Pointer_vector<Geometry> added;
std::vector<String> added_wkbbufs;
added.reserve(16);
added_wkbbufs.reserve(16);
Rtree_index rtree;
make_rtree(m_geos, &rtree);
Rtree_result rtree_result;
for (Geometry_list::iterator i = m_geos.begin(); i != m_geos.end(); ++i) {
if (*i == NULL) continue;
BG_box box;
make_bg_box(*i, &box);
if (!is_box_valid(box)) continue;
rtree_result.clear();
rtree.query(bgi::intersects(box), std::back_inserter(rtree_result));
/*
Normally the rtree should be non-empty because at least there is *i
itself. But if box has NaN coordinates, the rtree can be empty since
all coordinate comparisons with NaN numbers are false. also if the
min corner point have greater coordinates than the max corner point,
the box isn't valid and the rtree can be empty.
*/
DBUG_ASSERT(rtree_result.size() != 0);
// Sort rtree_result by Rtree_entry::second in order to make
// components in fixed order.
Rtree_entry_compare rtree_entry_compare;
std::sort(rtree_result.begin(), rtree_result.end(), rtree_entry_compare);
// Used to stop the nested loop.
bool stop_it = false;
for (Rtree_result::iterator j = rtree_result.begin();
j != rtree_result.end(); ++j) {
Geometry *geom2 = m_geos[j->second];
if (*i == geom2 || geom2 == NULL) continue;
// Equals is much easier and faster to check, so check it first.
if (Item_func_spatial_rel::bg_geo_relation_check(
geom2, *i, Item_func::SP_EQUALS_FUNC, &null_value) &&
!null_value) {
*i = NULL;
break;
}
if (null_value) {
stop_it = true;
break;
}
if (Item_func_spatial_rel::bg_geo_relation_check(
*i, geom2, Item_func::SP_WITHIN_FUNC, &null_value) &&
!null_value) {
*i = NULL;
break;
}
if (null_value) {
stop_it = true;
break;
}
if (Item_func_spatial_rel::bg_geo_relation_check(
geom2, *i, Item_func::SP_WITHIN_FUNC, &null_value) &&
!null_value) {
m_geos[j->second] = NULL;
continue;
}
if (null_value) {
stop_it = true;
break;
}
/*
If *i and geom2 is a polygon and a linestring that intersect only
finite number of points, the union result is the same as the two
geometries, and we would be stuck in an infinite loop. So we must
detect and exclude this case. All other argument type combinations
always will get a geometry different from the two arguments.
*/
char d11 = (*i)->feature_dimension();
char d12 = geom2->feature_dimension();
Geometry *geom_d1 = NULL;
Geometry *geom_d2 = NULL;
bool is_linear_areal = false;
if (((d11 == 1 && d12 == 2) || (d12 == 1 && d11 == 2))) {
geom_d1 = (d11 == 1 ? *i : geom2);
geom_d2 = (d11 == 2 ? *i : geom2);
if (d11 != 1) {
const char tmp_dim = d11;
d11 = d12;
d12 = tmp_dim;
}
is_linear_areal = true;
}
/*
As said above, if one operand is linear, the other is areal, then we
only proceed the union of them if they intersect infinite number of
points, i.e. L crosses A or L touches A's outer ring. Note that if L
touches some of A's inner rings, L must be crossing A, so not gonna
check the inner rings.
*/
if ((!is_linear_areal &&
Item_func_spatial_rel::bg_geo_relation_check(
*i, geom2, Item_func::SP_INTERSECTS_FUNC, &null_value) &&
!null_value) ||
(is_linear_areal && linear_areal_intersect_infinite<Coordsys>(
geom_d1, geom_d2, &null_value))) {
String wkbres;
if (null_value) {
stop_it = true;
break;
}
gres = ifsu->bg_geo_set_op<Coordsys>(*i, geom2, &wkbres);
null_value = ifsu->null_value;
if (null_value) {
if (gres != NULL && gres != *i && gres != geom2) delete gres;
stop_it = true;
break;
}
if (gres != *i) *i = NULL;
if (gres != geom2) m_geos[j->second] = NULL;
if (gres != NULL && gres != *i && gres != geom2) {
added.push_back(gres);
String tmp_wkbbuf;
added_wkbbufs.push_back(tmp_wkbbuf);
added_wkbbufs.back().takeover(wkbres);
has_new = true;
gres = NULL;
}
/*
Done with *i, it's either adopted, or removed or merged to a new
geometry.
*/
break;
} // intersects
if (null_value) {
stop_it = true;
break;
}
} // for (*j)
if (stop_it) break;
} // for (*i)
// Remove deleted Geometry object pointers, then append new components if any.
Is_target_geometry pred(NULL);
Geometry_list::iterator jj =
std::remove_if(m_geos.begin(), m_geos.end(), pred);
m_geos.resize(jj - m_geos.begin());
for (Pointer_vector<Geometry>::iterator i = added.begin(); i != added.end();
++i) {
/*
Fill rather than directly use *i for consistent memory management.
The objects pointed by pointers in added will be automatically destroyed.
*/
fill(*i);
}
// The added and added_wkbbufs arrays are destroyed and the Geometry objects
// in 'added' are freed, and memory buffers in added_wkbbufs are freed too.
return has_new;
}
inline void reassemble_geometry(Geometry *g) {
Geometry::wkbType gtype = g->get_geotype();
if (gtype == Geometry::wkb_polygon)
down_cast<Gis_polygon *>(g)->to_wkb_unparsed();
else if (gtype == Geometry::wkb_multilinestring)
down_cast<Gis_multi_line_string *>(g)->reassemble();
else if (gtype == Geometry::wkb_multipolygon)
down_cast<Gis_multi_polygon *>(g)->reassemble();
}
template <typename BG_geotype>
bool post_fix_result(BG_result_buf_mgr *resbuf_mgr, BG_geotype &geout,
String *res) {
DBUG_ASSERT(geout.has_geom_header_space());
reassemble_geometry(&geout);
// Such objects returned by BG never have overlapped components.
if (geout.get_type() == Geometry::wkb_multilinestring ||
geout.get_type() == Geometry::wkb_multipolygon)
geout.set_components_no_overlapped(true);
if (geout.get_ptr() == NULL) return true;
if (res) {
char *resptr = geout.get_cptr() - GEOM_HEADER_SIZE;
size_t len = geout.get_nbytes();
/*
The resptr buffer is now owned by resbuf_mgr and used by res, resptr
will be released properly by resbuf_mgr.
*/
resbuf_mgr->add_buffer(resptr);
/*
Pass resptr as const pointer so that the memory space won't be reused
by res object. Reuse is forbidden because the memory comes from BG
operations and will be freed upon next same val_str call.
*/
res->set(resptr, len + GEOM_HEADER_SIZE, &my_charset_bin);
// Prefix the GEOMETRY header.
write_geometry_header(resptr, geout.get_srid(), geout.get_geotype());
/*
Give up ownership because the buffer may have to live longer than
the object.
*/
geout.set_ownmem(false);
}
return false;
}
// Explicit template instantiation
template bool post_fix_result<Gis_line_string>(BG_result_buf_mgr *,
Gis_line_string &, String *);
template bool post_fix_result<Gis_multi_line_string>(BG_result_buf_mgr *,
Gis_multi_line_string &,
String *);
template bool post_fix_result<Gis_multi_point>(BG_result_buf_mgr *,
Gis_multi_point &, String *);
template bool post_fix_result<Gis_multi_polygon>(BG_result_buf_mgr *,
Gis_multi_polygon &, String *);
template bool post_fix_result<Gis_point>(BG_result_buf_mgr *, Gis_point &,
String *);
template bool post_fix_result<Gis_polygon>(BG_result_buf_mgr *, Gis_polygon &,
String *);
class Is_empty_geometry : public WKB_scanner_event_handler {
public:
bool is_empty;
Is_empty_geometry() : is_empty(true) {}
virtual void on_wkb_start(Geometry::wkbByteOrder, Geometry::wkbType geotype,
const void *, uint32, bool) {
if (is_empty && geotype != Geometry::wkb_geometrycollection)
is_empty = false;
}
virtual void on_wkb_end(const void *) {}
virtual bool continue_scan() const { return is_empty; }
};
bool is_empty_geocollection(const Geometry *g) {
if (g->get_geotype() != Geometry::wkb_geometrycollection) return false;
uint32 num = uint4korr(g->get_cptr());
if (num == 0) return true;
Is_empty_geometry checker;
uint32 len = g->get_data_size();
wkb_scanner(current_thd, g->get_cptr(), &len,
Geometry::wkb_geometrycollection, false, &checker);
return checker.is_empty;
}
bool is_empty_geocollection(const String &wkbres) {
if (wkbres.ptr() == NULL) return true;
uint32 geotype = uint4korr(wkbres.ptr() + SRID_SIZE + 1);
if (geotype != static_cast<uint32>(Geometry::wkb_geometrycollection))
return false;
if (uint4korr(wkbres.ptr() + SRID_SIZE + WKB_HEADER_SIZE) == 0) return true;
Is_empty_geometry checker;
uint32 len = static_cast<uint32>(wkbres.length()) - GEOM_HEADER_SIZE;
wkb_scanner(current_thd, wkbres.ptr() + GEOM_HEADER_SIZE, &len,
Geometry::wkb_geometrycollection, false, &checker);
return checker.is_empty;
}