/* Copyright (c) 2014, 2015, 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 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,
   51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */


/**
  @file

  @brief
  This file defines implementations of GIS set operation functions.
*/
#include "my_config.h"
#include "item_geofunc_internal.h"

#include <set>

#define BGOPCALL(GeoOutType, geom_out, bgop,                            \
                 GeoType1, g1, GeoType2, g2, wkbres, nullval)           \
do                                                                      \
{                                                                       \
  const void *pg1= g1->normalize_ring_order();                          \
  const void *pg2= g2->normalize_ring_order();                          \
  geom_out= NULL;                                                       \
  if (pg1 != NULL && pg2 != NULL)                                       \
  {                                                                     \
    GeoType1 geo1(pg1, g1->get_data_size(), g1->get_flags(),            \
                  g1->get_srid());                                      \
    GeoType2 geo2(pg2, g2->get_data_size(), g2->get_flags(),            \
                  g2->get_srid());                                      \
    auto_ptr<GeoOutType>geout(new GeoOutType());                        \
    geout->set_srid(g1->get_srid());                                    \
    boost::geometry::bgop(geo1, geo2, *geout);                          \
    (nullval)= false;                                                   \
    if (geout->size() == 0 ||                                           \
        (nullval= post_fix_result(&(m_ifso->bg_resbuf_mgr),             \
                                  *geout, wkbres)))                     \
    {                                                                   \
      if (nullval)                                                      \
        return NULL;                                                    \
    }                                                                   \
    else                                                                \
      geom_out= geout.release();                                        \
  }                                                                     \
  else                                                                  \
  {                                                                     \
    (nullval)= true;                                                    \
    my_error(ER_GIS_INVALID_DATA, MYF(0), "st_" #bgop);                 \
    return NULL;                                                        \
  }                                                                     \
} while (0)


Item_func_spatial_operation::~Item_func_spatial_operation()
{
}


/*
  Write an empty geometry collection's wkb encoding into str, and create a
  geometry object for this empty geometry colletion.
 */
Geometry *Item_func_spatial_operation::empty_result(String *str, uint32 srid)
{
  if ((null_value= str->reserve(GEOM_HEADER_SIZE + 4 + 16, 256)))
    return 0;

  write_geometry_header(str, srid, Geometry::wkb_geometrycollection, 0);
  Gis_geometry_collection *gcol= new Gis_geometry_collection();
  gcol->set_data_ptr(str->ptr() + GEOM_HEADER_SIZE, 4);
  gcol->has_geom_header_space(true);
  return gcol;
}


/**
  Wraps and dispatches type specific BG function calls according to operation
  type and the 1st or both operand type(s), depending on code complexity.

  We want to isolate boost header file inclusion only inside this file, so we
  can't put this class declaration in any header file. And we want to make the
  methods static since no state is needed here.
  @tparam Geom_types A wrapper for all geometry types.
*/
template<typename Geom_types>
class BG_setop_wrapper
{
  // Some computation in this class may rely on functions in
  // Item_func_spatial_operation.
  Item_func_spatial_operation *m_ifso;
  my_bool null_value; // Whether computation has error.

  // Some computation in this class may rely on functions in
  // Item_func_spatial_operation, after each call of its functions, copy its
  // null_value, we don't want to miss errors.
  void copy_ifso_state()
  {
    null_value= m_ifso->null_value;
  }

public:
  typedef typename Geom_types::Point Point;
  typedef typename Geom_types::Linestring Linestring;
  typedef typename Geom_types::Polygon Polygon;
  typedef typename Geom_types::Polygon::ring_type Polygon_ring;
  typedef typename Geom_types::Multipoint Multipoint;
  typedef typename Geom_types::Multilinestring Multilinestring;
  typedef typename Geom_types::Multipolygon Multipolygon;
  typedef typename Geom_types::Coordinate_type Coord_type;
  typedef typename Geom_types::Coordinate_system Coordsys;
  typedef Item_func_spatial_rel Ifsr;
  typedef Item_func_spatial_operation Ifso;
  typedef std::set<Point, bgpt_lt> Point_set;
  typedef std::vector<Point> Point_vector;

  BG_setop_wrapper(Item_func_spatial_operation *ifso)
  {
    m_ifso= ifso;
    null_value= 0;
  }


  my_bool get_null_value() const
  {
    return null_value;
  }


  /**
    Do point insersection point operation.
    @param g1 First geometry operand, must be a point.
    @param g2 Second geometry operand, must be a point.
    @param[out] result Holds WKB data of the result.
    @return the result Geometry whose WKB data is in result.
    */
  Geometry *point_intersection_point(Geometry *g1, Geometry *g2,
                                     String *result)
  {
    Geometry *retgeo= NULL;

    Point pt1(g1->get_data_ptr(),
              g1->get_data_size(), g1->get_flags(), g1->get_srid());
    Point pt2(g2->get_data_ptr(),
              g2->get_data_size(), g2->get_flags(), g2->get_srid());

    if (bgpt_eq()(pt1, pt2))
    {
      retgeo= g1;
      null_value= retgeo->as_geometry(result, true);
    }
    else
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  /*
    Do point intersection Multipoint operation.
    The parameters and return value has identical/similar meaning as to above
    function, which can be inferred from the function name, we won't repeat
    here or for the rest of the functions in this class.
  */
  Geometry *point_intersection_multipoint(Geometry *g1, Geometry *g2,
                                          String *result)
  {
    Geometry *retgeo= NULL;

    Point pt(g1->get_data_ptr(),
             g1->get_data_size(), g1->get_flags(), g1->get_srid());
    Multipoint mpts(g2->get_data_ptr(),
                    g2->get_data_size(), g2->get_flags(), g2->get_srid());
    Point_set ptset(mpts.begin(), mpts.end());

    if (ptset.find(pt) != ptset.end())
    {
      retgeo= g1;
      null_value= retgeo->as_geometry(result, true);
    }
    else
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *point_intersection_geometry(Geometry *g1, Geometry *g2,
                                        String *result)
  {
#if !defined(DBUG_OFF)
    Geometry::wkbType gt2= g2->get_type();
#endif
    Geometry *retgeo= NULL;

    bool is_out= !Ifsr::bg_geo_relation_check<Coordsys>
      (g1, g2, Ifsr::SP_DISJOINT_FUNC, &null_value);

    DBUG_ASSERT(gt2 == Geometry::wkb_linestring ||
                gt2 == Geometry::wkb_polygon ||
                gt2 == Geometry::wkb_multilinestring ||
                gt2 == Geometry::wkb_multipolygon);
    if (!null_value)
    {
      if (is_out)
      {
        null_value= g1->as_geometry(result, true);
        retgeo= g1;
      }
      else
      {
        retgeo= m_ifso->empty_result(result, g1->get_srid());
        copy_ifso_state();
      }
    }
    return retgeo;
  }


  Geometry *multipoint_intersection_multipoint(Geometry *g1, Geometry *g2,
                                               String *result)
  {
    Geometry *retgeo= NULL;
    Point_set ptset1, ptset2;
    Multipoint *mpts= new Multipoint();
    auto_ptr<Multipoint> guard(mpts);

    mpts->set_srid(g1->get_srid());

    Multipoint mpts1(g1->get_data_ptr(),
                     g1->get_data_size(), g1->get_flags(), g1->get_srid());
    Multipoint mpts2(g2->get_data_ptr(),
                     g2->get_data_size(), g2->get_flags(), g2->get_srid());

    ptset1.insert(mpts1.begin(), mpts1.end());
    ptset2.insert(mpts2.begin(), mpts2.end());

    Point_vector respts;
    TYPENAME Point_vector::iterator endpos;
    size_t ptset1sz= ptset1.size(), ptset2sz= ptset2.size();
    respts.resize(ptset1sz > ptset2sz ? ptset1sz : ptset2sz);

    endpos= std::set_intersection(ptset1.begin(), ptset1.end(),
                                  ptset2.begin(), ptset2.end(),
                                  respts.begin(), bgpt_lt());
    std::copy(respts.begin(), endpos, std::back_inserter(*mpts));
    if (mpts->size() > 0)
    {
      null_value= m_ifso->assign_result(mpts, result);
      retgeo= mpts;
      guard.release();
    }
    else
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }

    return retgeo;
  }


  Geometry *multipoint_intersection_geometry(Geometry *g1, Geometry *g2,
                                             String *result)
  {
    Geometry *retgeo= NULL;
#if !defined(DBUG_OFF)
    Geometry::wkbType gt2= g2->get_type();
#endif
    Point_set ptset;
    Multipoint mpts(g1->get_data_ptr(),
                    g1->get_data_size(), g1->get_flags(), g1->get_srid());
    Multipoint *mpts2= new Multipoint();
    auto_ptr<Multipoint> guard(mpts2);

    mpts2->set_srid(g1->get_srid());

    DBUG_ASSERT(gt2 == Geometry::wkb_linestring ||
                gt2 == Geometry::wkb_polygon ||
                gt2 == Geometry::wkb_multilinestring ||
                gt2 == Geometry::wkb_multipolygon);
    ptset.insert(mpts.begin(), mpts.end());

    for (TYPENAME Point_set::iterator i= ptset.begin(); i != ptset.end(); ++i)
    {
      Point &pt= const_cast<Point&>(*i);
      if (!Ifsr::bg_geo_relation_check<Coordsys>
          (&pt, g2, Ifsr::SP_DISJOINT_FUNC, &null_value) && !null_value)
      {
        mpts2->push_back(pt);
      }

      if (null_value)
        return 0;
    }

    if (mpts2->size() > 0)
    {
      null_value= m_ifso->assign_result(mpts2, result);
      retgeo= mpts2;
      guard.release();
    }
    else
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }

    return retgeo;
  }


  Geometry *linestring_intersection_linestring(Geometry *g1, Geometry *g2,
                                               String *result)
  {
    Multilinestring *res= NULL;
    Geometry *retgeo= NULL;

    // BG will return all intersection lines and points in a
    // multilinestring. Intersection points are represented as lines
    // with the same start and end points.
    BGOPCALL(Multilinestring, res, intersection,
             Linestring, g1, Linestring, g2, NULL, null_value);

    if (res == NULL)
      return m_ifso->empty_result(result, g1->get_srid());

    retgeo= m_ifso->simplify_multilinestring(res, result);
    delete res;
    return retgeo;
  }


  Geometry *linestring_intersection_polygon(Geometry *g1, Geometry *g2,
                                            String *result)
  {
    Geometry::wkbType gt2= g2->get_type();
    Geometry *retgeo= NULL, *tmp1= NULL, *tmp2= NULL;
    // It is likely for there to be discrete intersection Points.
    if (gt2 == Geometry::wkb_multipolygon)
    {
      BGOPCALL(Multilinestring, tmp1, intersection,
               Linestring, g1, Multipolygon, g2, NULL, null_value);
      BGOPCALL(Multipoint, tmp2, intersection,
               Linestring, g1, Multipolygon, g2, NULL, null_value);
    }
    else
    {
      BGOPCALL(Multilinestring, tmp1, intersection,
               Linestring, g1, Polygon, g2, NULL, null_value);
      BGOPCALL(Multipoint, tmp2, intersection,
               Linestring, g1, Polygon, g2, NULL, null_value);
    }

    // Need merge, exclude Points that are on the result Linestring.
    retgeo= m_ifso->combine_sub_results<Coordsys>
      (tmp1, tmp2, result);
    copy_ifso_state();

    return retgeo;
  }


  Geometry *linestring_intersection_multilinestring(Geometry *g1, Geometry *g2,
                                                    String *result)
  {
    Multilinestring *res= NULL;
    Geometry *retgeo= NULL;

    // BG will return all intersection lines and points in a
    // multilinestring. Intersection points are represented as lines
    // with the same start and end points.
    BGOPCALL(Multilinestring, res, intersection,
             Linestring, g1, Multilinestring, g2, NULL, null_value);

    if (res == NULL || res->size() == 0)
      return m_ifso->empty_result(result, g1->get_srid());

    retgeo= m_ifso->simplify_multilinestring(res, result);
    delete res;
    return retgeo;
  }


  Geometry *polygon_intersection_multilinestring(Geometry *g1, Geometry *g2,
                                                 String *result)
  {
    Geometry *retgeo= NULL, *tmp1= NULL;
    Multipoint *tmp2= NULL;
    auto_ptr<Geometry> guard1;


    BGOPCALL(Multilinestring, tmp1, intersection,
             Polygon, g1, Multilinestring, g2, NULL, null_value);
    guard1.reset(tmp1);

    Multilinestring mlstr(g2->get_data_ptr(), g2->get_data_size(),
                          g2->get_flags(), g2->get_srid());
    Multipoint mpts;
    Point_set ptset;

    const void *data_ptr= g1->normalize_ring_order();
    if (data_ptr == NULL)
    {
      null_value= true;
      my_error(ER_GIS_INVALID_DATA, MYF(0), "st_intersection");
      return NULL;
    }

    Polygon plgn(data_ptr, g1->get_data_size(),
                 g1->get_flags(), g1->get_srid());

    for (TYPENAME Multilinestring::iterator i= mlstr.begin();
         i != mlstr.end(); ++i)
    {
      boost::geometry::intersection(plgn, *i, mpts);
      if (mpts.size() > 0)
      {
        ptset.insert(mpts.begin(), mpts.end());
        mpts.clear();
      }
    }

    auto_ptr<Multipoint> guard2;
    if (ptset.size() > 0)
    {
      tmp2= new Multipoint;
      tmp2->set_srid(g1->get_srid());
      guard2.reset(tmp2);
      std::copy(ptset.begin(), ptset.end(), std::back_inserter(*tmp2));
    }

    retgeo= m_ifso->combine_sub_results<Coordsys>
      (guard1.release(), guard2.release(), result);
    copy_ifso_state();

    return retgeo;
  }


  /**
    Compute the multilinestring result if any for intersection of two polygons.
    First get the multilinestrings mls1 and mls2 from polygons, then compute
    mls3 = mls1 intersection mls2, and finally return mls3 - mls4 as result,
    where mls4 is got from rings of the multipolygon intersection result of
    plgn1 and plgn2.

    @param plgn1 the 1st polygon operand
    @param plgn2 the 2nd polygon operand
    @param mplgn the areal intersection result of plgn1 and plgn2
    @param [out] mls the result multilinestring
   */
  template<typename Polygon_type1, typename Polygon_type2>
  void plgn_intersection_plgn_mls(const Polygon_type1 &plgn1,
                                  const Polygon_type2 &plgn2,
                                  const Multipolygon &mplgn,
                                  Multilinestring &mls)
  {
    if (mplgn.size() > 0)
    {
      /*
        Remove the linestring parts that are part of the result polygon rings.
        The discrete intersection points that are also removed here.
      */
      Multilinestring mls3;
      bg::intersection(plgn1, plgn2, mls3);
      bg::detail::boundary_view<Multipolygon const> view(mplgn);
      bg::difference(mls3, view, mls);
    }
    else
      bg::intersection(plgn1, plgn2, mls);
  }


  Geometry *polygon_intersection_polygon(Geometry *g1, Geometry *g2,
                                         String *result)
  {
    Geometry::wkbType gt2= g2->get_type();
    Geometry *retgeo= NULL;

    const void *pg1= g1->normalize_ring_order();
    const void *pg2= g2->normalize_ring_order();
    if (pg1 == NULL || pg2 == NULL)
    {
      null_value= true;
      my_error(ER_GIS_INVALID_DATA, MYF(0), "st_intersection");
      return NULL;
    }

    Multilinestring mls;
    Polygon plgn1(pg1, g1->get_data_size(), g1->get_flags(),
                  g1->get_srid());

    auto_ptr<Multipolygon> mplgn_result(new Multipolygon());
    mplgn_result->set_srid(g1->get_srid());

    if (gt2 == Geometry::wkb_polygon)
    {
      Polygon plgn2(pg2, g2->get_data_size(), g2->get_flags(),
                    g2->get_srid());
      bg::intersection(plgn1, plgn2, *mplgn_result);
      plgn_intersection_plgn_mls(plgn1, plgn2, *mplgn_result, mls);
    }
    else
    {
      Multipolygon mplgn2(pg2, g2->get_data_size(), g2->get_flags(),
                          g2->get_srid());
      bg::intersection(plgn1, mplgn2, *mplgn_result);
      plgn_intersection_plgn_mls(plgn1, mplgn2, *mplgn_result, mls);
    }

    retgeo= combine_mls_mplgn_results(&mls, mplgn_result, result);
    copy_ifso_state();

    return retgeo;
  }

  Geometry *combine_mls_mplgn_results(Multilinestring *mls,
                                      auto_ptr<Multipolygon> mplgn_result,
                                      String *result)
  {
    Geometry *geom= NULL, *retgeo= NULL;
    DBUG_ASSERT(mls != NULL);

    if (mls->size() > 0)
      geom= m_ifso->simplify_multilinestring(mls, result);

    if (mplgn_result->size() > 0)
    {
      if (mls->size() == 0)
      {
        // The multipolygon is the only result.
        DBUG_ASSERT(result->length() == 0);
        null_value= post_fix_result(&(m_ifso->bg_resbuf_mgr),
                                    *mplgn_result, result);
        if (null_value)
          return NULL;
        else
          retgeo= mplgn_result.release();
      }
      else
      {
        String mplgn_resbuf;
        null_value= post_fix_result(&(m_ifso->bg_resbuf_mgr),
                                    *mplgn_result, &mplgn_resbuf);
        if (null_value)
          return NULL;
        if (geom->get_type() == Geometry::wkb_geometrycollection)
        {
          down_cast<Gis_geometry_collection *>(geom)->
            append_geometry(&(*mplgn_result), result);
          retgeo= geom;
        }
        else
        {
          /*
            The geom created from mls isn't a GC, so we have to create a GC to
            hole both geom and mplgn_result.
           */
          String tmp_mls_resbuf;
          tmp_mls_resbuf.takeover(*result);

          Gis_geometry_collection *tmp_gc=
            new Gis_geometry_collection(&(*mplgn_result), result);
          tmp_gc->append_geometry(geom, result);
          retgeo= tmp_gc;
          delete geom;
        }
      }
    }
    else
      retgeo= geom;

    if (retgeo == NULL)
      retgeo= m_ifso->empty_result(result, mplgn_result->get_srid());

    return retgeo;
  }


  Geometry *multilinestring_intersection_multilinestring(Geometry *g1,
                                                         Geometry *g2,
                                                         String *result)
  {
    Multilinestring *res= NULL;
    Geometry *retgeo= NULL;

    // BG will return all intersection lines and points in a
    // multilinestring. Intersection points are represented as lines
    // with the same start and end points.
    BGOPCALL(Multilinestring, res, intersection,
             Multilinestring, g1, Multilinestring, g2, NULL, null_value);

    if (res == NULL)
      return m_ifso->empty_result(result, g1->get_srid());

    retgeo= m_ifso->simplify_multilinestring(res, result);
    delete res;
    return retgeo;
  }


  Geometry *multilinestring_intersection_multipolygon(Geometry *g1,
                                                      Geometry *g2,
                                                      String *result)
  {
    Geometry *retgeo= NULL, *tmp1= NULL;
    Multipoint *tmp2= NULL;

    auto_ptr<Geometry> guard1;
    BGOPCALL(Multilinestring, tmp1, intersection,
             Multilinestring, g1, Multipolygon, g2,
             NULL, null_value);
    guard1.reset(tmp1);

    Multilinestring mlstr(g1->get_data_ptr(), g1->get_data_size(),
                          g1->get_flags(), g1->get_srid());
    Multipoint mpts;

    const void *data_ptr= g2->normalize_ring_order();
    if (data_ptr == NULL)
    {
      null_value= true;
      my_error(ER_GIS_INVALID_DATA, MYF(0), "st_intersection");
      return NULL;
    }

    Multipolygon mplgn(data_ptr, g2->get_data_size(),
                       g2->get_flags(), g2->get_srid());
    Point_set ptset;

    for (TYPENAME Multilinestring::iterator i= mlstr.begin();
         i != mlstr.end(); ++i)
    {
      boost::geometry::intersection(*i, mplgn, mpts);
      if (mpts.size() > 0)
      {
        ptset.insert(mpts.begin(), mpts.end());
        mpts.clear();
      }
    }

    auto_ptr<Multipoint> guard2;
    if (ptset.empty() == false)
    {
      tmp2= new Multipoint;
      tmp2->set_srid(g1->get_srid());
      guard2.reset(tmp2);
      std::copy(ptset.begin(), ptset.end(), std::back_inserter(*tmp2));
    }

    retgeo= m_ifso->combine_sub_results<Coordsys>
      (guard1.release(), guard2.release(), result);
    copy_ifso_state();

    return retgeo;
  }


  Geometry *multipolygon_intersection_multipolygon(Geometry *g1, Geometry *g2,
                                                   String *result)
  {
    Geometry *retgeo= NULL;

    const void *pg1= g1->normalize_ring_order();
    const void *pg2= g2->normalize_ring_order();
    if (pg1 == NULL || pg2 == NULL)
    {
      null_value= true;
      my_error(ER_GIS_INVALID_DATA, MYF(0), "st_intersection");
      return NULL;
    }

    Multilinestring mls;
    Multipolygon mplgn1(pg1, g1->get_data_size(), g1->get_flags(),
                        g1->get_srid());

    auto_ptr<Multipolygon> mplgn_result(new Multipolygon());
    mplgn_result->set_srid(g1->get_srid());

    Multipolygon mplgn2(pg2, g2->get_data_size(), g2->get_flags(),
                        g2->get_srid());
    bg::intersection(mplgn1, mplgn2, *mplgn_result);
    plgn_intersection_plgn_mls(mplgn1, mplgn2, *mplgn_result, mls);
    retgeo= combine_mls_mplgn_results(&mls, mplgn_result, result);

    copy_ifso_state();

    return retgeo;
  }


  Geometry *point_union_point(Geometry *g1, Geometry *g2, String *result)
  {
    Geometry *retgeo= NULL;
    Geometry::wkbType gt2= g2->get_type();
    Point_set ptset;// Use set to make Points unique.

    Point pt1(g1->get_data_ptr(),
              g1->get_data_size(), g1->get_flags(), g1->get_srid());
    Multipoint *mpts= new Multipoint();
    auto_ptr<Multipoint> guard(mpts);

    mpts->set_srid(g1->get_srid());
    ptset.insert(pt1);
    if (gt2 == Geometry::wkb_point)
    {
      Point pt2(g2->get_data_ptr(),
                g2->get_data_size(), g2->get_flags(), g2->get_srid());
      ptset.insert(pt2);
    }
    else
    {
      Multipoint mpts2(g2->get_data_ptr(),
                       g2->get_data_size(), g2->get_flags(), g2->get_srid());
      ptset.insert(mpts2.begin(), mpts2.end());
    }

    std::copy(ptset.begin(), ptset.end(), std::back_inserter(*mpts));
    if (mpts->size() > 0)
    {
      retgeo= mpts;
      null_value= m_ifso->assign_result(mpts, result);
      guard.release();
    }
    else
    {
      if (!null_value)
      {
        retgeo= m_ifso->empty_result(result, g1->get_srid());
        copy_ifso_state();
      }
    }
    return retgeo;
  }


  Geometry *point_union_geometry(Geometry *g1, Geometry *g2, String *result)
  {
    Geometry *retgeo= NULL;
#if !defined(DBUG_OFF)
    Geometry::wkbType gt2= g2->get_type();
#endif
    DBUG_ASSERT(gt2 == Geometry::wkb_linestring ||
                gt2 == Geometry::wkb_polygon ||
                gt2 == Geometry::wkb_multilinestring ||
                gt2 == Geometry::wkb_multipolygon);
    if (Ifsr::bg_geo_relation_check<Coordsys>
        (g1, g2, Ifsr::SP_DISJOINT_FUNC, &null_value) && !null_value)
    {
      Gis_geometry_collection *geocol= new Gis_geometry_collection(g2, result);
      null_value= (geocol == NULL || geocol->append_geometry(g1, result));
      retgeo= geocol;
    }
    else if (null_value)
    {
      retgeo= NULL;
      return 0;
    }
    else
    {
      retgeo= g2;
      null_value= retgeo->as_geometry(result, true);
    }

    return retgeo;
  }


  Geometry *linestring_union_linestring(Geometry *g1, Geometry *g2,
                                        String *result)
  {
    Linestring ls1(g1->get_data_ptr(), g1->get_data_size(),
                   g1->get_flags(), g1->get_srid());
    Linestring ls2(g2->get_data_ptr(), g2->get_data_size(),
                   g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring());
    res->set_srid(g1->get_srid());

    boost::geometry::union_(ls1, ls2, *res);
    DBUG_ASSERT(res.get() != 0);
    DBUG_ASSERT(res->size() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result))
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    return res.release();
  }


  Geometry *linestring_union_polygon(Geometry *g1, Geometry *g2,
                                     String *result)
  {
    Geometry *retgeo= NULL;
    const void *g2_wkb= g2->normalize_ring_order();
    if (g2_wkb == NULL)
    {
      // Invalid polygon
      my_error(ER_GIS_INVALID_DATA, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    Linestring ls1(g1->get_data_ptr(), g1->get_data_size(),
                   g1->get_flags(), g1->get_srid());
    Polygon py2(g2_wkb, g2->get_data_size(),
                g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> linestrings(new Multilinestring());
    linestrings->set_srid(g1->get_srid());

    // Union(LineString, Polygon) isn't supported by BG, but it's
    // equivalent to GeometryCollection(Polygon, Difference(LineString,
    // Polygon)).
    boost::geometry::difference(ls1, py2, *linestrings);
    DBUG_ASSERT(linestrings.get() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *linestrings, NULL) &&
        linestrings->size() > 0)
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    // Return the simplest result possible.
    if (linestrings->size() == 0)
    {
      // Polygon result.
      g2->as_geometry(result, true);
      retgeo= g2;
    }
    else
    {
      // GeometryCollection result containing one Polygon and one or
      // more LineStrings.
      Gis_geometry_collection *collection= new Gis_geometry_collection();
      py2.to_wkb_unparsed();
      collection->append_geometry(&py2, result);
      if (linestrings->size() > 1)
      {
        collection->append_geometry(&(*linestrings), result);
      }
      else
      {
        collection->append_geometry(&(*linestrings)[0], result);
      }
      collection->set_ownmem(false);
      retgeo= collection;
    }

    return retgeo;
  }


  Geometry *linestring_union_multilinestring(Geometry *g1, Geometry *g2,
                                             String *result)
  {
    Linestring ls1(g1->get_data_ptr(), g1->get_data_size(),
                   g1->get_flags(), g1->get_srid());
    Multilinestring mls2(g2->get_data_ptr(), g2->get_data_size(),
                         g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring);
    res->set_srid(g1->get_srid());

    boost::geometry::union_(ls1, mls2, *res);
    DBUG_ASSERT(res.get() != 0);
    DBUG_ASSERT(res->size() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result))
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    return res.release();
  }


  Geometry *linestring_union_multipolygon(Geometry *g1, Geometry *g2,
                                          String *result)
  {
    Geometry *retgeo= NULL;
    const void *g2_wkb= g2->normalize_ring_order();
    if (g2_wkb == NULL)
    {
      // Invalid polygon
      my_error(ER_GIS_INVALID_DATA, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    Linestring ls1(g1->get_data_ptr(), g1->get_data_size(),
                   g1->get_flags(), g1->get_srid());
    Multipolygon mpy2(g2_wkb, g2->get_data_size(),
                      g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> linestrings(new Multilinestring());
    linestrings->set_srid(g1->get_srid());

    // Union(LineString, MultiPolygon) isn't supported by BG, but it's
    // equivalent to GeometryCollection(MultiPolygon,
    // Difference(LineString, MultiPolygon)).
    boost::geometry::difference(ls1, mpy2, *linestrings);
    DBUG_ASSERT(linestrings.get() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *linestrings, NULL) &&
        linestrings->size() > 0)
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    // Return the simplest result possible.
    if (linestrings->size() == 0)
    {
      // MultiPolygon result.
      g2->as_geometry(result, true);
      retgeo= g2;
    }
    else
    {
      // GeometryCollection result containing one or more Polygons and
      // one or more LineStrings.
      Gis_geometry_collection *collection= new Gis_geometry_collection();

      if (mpy2.size() > 1)
      {
        collection->append_geometry(&mpy2, result);
      }
      else
      {
        mpy2[0].to_wkb_unparsed();
        collection->append_geometry(&mpy2[0], result);
      }

      if (linestrings->size() > 1)
      {
        collection->append_geometry(&(*linestrings), result);
      }
      else
      {
        collection->append_geometry(&(*linestrings)[0], result);
      }

      collection->set_ownmem(false);
      retgeo= collection;
    }

    return retgeo;
  }


  Geometry *polygon_union_multilinestring(Geometry *g1, Geometry *g2,
                                          String *result)
  {
    Geometry *retgeo= NULL;
    const void *g1_wkb= g1->normalize_ring_order();
    if (g1_wkb == NULL)
    {
      // Invalid polygon
      my_error(ER_GIS_INVALID_DATA, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    Polygon py1(g1_wkb, g1->get_data_size(),
                g1->get_flags(), g1->get_srid());
    Multilinestring mls2(g2->get_data_ptr(), g2->get_data_size(),
                         g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> linestrings(new Multilinestring());
    linestrings->set_srid(g1->get_srid());

    // Union(Polygon, MultiLineString) isn't supported by BG, but it's
    // equivalent to GeometryCollection(Polygon,
    // Difference(MultiLineString, Polygon)).
    boost::geometry::difference(mls2, py1, *linestrings);
    DBUG_ASSERT(linestrings.get() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *linestrings, NULL) &&
        linestrings->size() > 0)
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    // Return the simplest result possible.
    if (linestrings->size() == 0)
    {
      // Polygon result.
      g2->as_geometry(result, true);
      retgeo= g2;
    }
    else
    {
      // GeometryCollection result containing one Polygon and one or
      // more LineStrings.
      Gis_geometry_collection *collection= new Gis_geometry_collection();
      py1.to_wkb_unparsed();
      collection->append_geometry(&py1, result);
      if (linestrings->size() > 1)
      {
        collection->append_geometry(&(*linestrings), result);
      }
      else
      {
        collection->append_geometry(&(*linestrings)[0], result);
      }
      collection->set_ownmem(false);
      retgeo= collection;
    }

    return retgeo;
  }


  Geometry *multipoint_union_multipoint(Geometry *g1, Geometry *g2,
                                        String *result)
  {
    Geometry *retgeo= NULL;
    Point_set ptset;
    Multipoint *mpts= new Multipoint();
    auto_ptr<Multipoint> guard(mpts);

    mpts->set_srid(g1->get_srid());
    Multipoint mpts1(g1->get_data_ptr(),
                     g1->get_data_size(), g1->get_flags(), g1->get_srid());
    Multipoint mpts2(g2->get_data_ptr(),
                     g2->get_data_size(), g2->get_flags(), g2->get_srid());

    ptset.insert(mpts1.begin(), mpts1.end());
    ptset.insert(mpts2.begin(), mpts2.end());
    std::copy(ptset.begin(), ptset.end(), std::back_inserter(*mpts));

    if (mpts->size() > 0)
    {
      retgeo= mpts;
      null_value= m_ifso->assign_result(mpts, result);
      guard.release();
    }
    else
    {
      if (!null_value)
      {
        retgeo= m_ifso->empty_result(result, g1->get_srid());
        copy_ifso_state();
      }
    }
    return retgeo;
  }


  Geometry *multipoint_union_geometry(Geometry *g1, Geometry *g2,
                                      String *result)
  {
    Geometry *retgeo= NULL;
#if !defined(DBUG_OFF)
    Geometry::wkbType gt2= g2->get_type();
#endif
    Point_set ptset;
    Multipoint mpts(g1->get_data_ptr(),
                    g1->get_data_size(), g1->get_flags(), g1->get_srid());

    DBUG_ASSERT(gt2 == Geometry::wkb_linestring ||
                gt2 == Geometry::wkb_polygon ||
                gt2 == Geometry::wkb_multilinestring ||
                gt2 == Geometry::wkb_multipolygon);
    ptset.insert(mpts.begin(), mpts.end());

    Gis_geometry_collection *geocol= new Gis_geometry_collection(g2, result);
    auto_ptr<Gis_geometry_collection> guard(geocol);
    bool added= false;

    for (TYPENAME Point_set::iterator i= ptset.begin(); i != ptset.end(); ++i)
    {
      Point &pt= const_cast<Point&>(*i);
      if (Ifsr::bg_geo_relation_check<Coordsys>
          (&pt, g2, Ifsr::SP_DISJOINT_FUNC, &null_value))
      {
        if (null_value || (null_value= geocol->append_geometry(&pt, result)))
          break;
        added= true;
      }
    }

    if (null_value)
      return 0;

    if (added)
    {
      // Result is already filled above.
      retgeo= geocol;
      guard.release();
    }
    else
    {
      retgeo= g2;
      null_value= g2->as_geometry(result, true);
    }

    return retgeo;
  }


  Geometry *multilinestring_union_multilinestring(Geometry *g1, Geometry *g2,
                                                  String *result)
  {
    Multilinestring mls1(g1->get_data_ptr(), g1->get_data_size(),
                         g1->get_flags(), g1->get_srid());
    Multilinestring mls2(g2->get_data_ptr(), g2->get_data_size(),
                         g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring());
    res->set_srid(g1->get_srid());

    boost::geometry::union_(mls1, mls2, *res);
    DBUG_ASSERT(res.get() != 0);
    DBUG_ASSERT(res->size() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result))
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    return res.release();
  }


  Geometry *multilinestring_union_multipolygon(Geometry *g1, Geometry *g2,
                                               String *result)
  {
    Geometry *retgeo= NULL;
    const void *g2_wkb= g2->normalize_ring_order();
    if (g2_wkb == NULL)
    {
      // Invalid polygon
      my_error(ER_GIS_INVALID_DATA, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    Multilinestring mls1(g1->get_data_ptr(), g1->get_data_size(),
                         g1->get_flags(), g1->get_srid());
    Multipolygon mpy2(g2_wkb, g2->get_data_size(),
                      g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> linestrings(new Multilinestring());
    linestrings->set_srid(g1->get_srid());

    // Union(MultiLineString, MultiPolygon) isn't supported by BG, but
    // it's equivalent to GeometryCollection(MultiPolygon,
    // Difference(MultiLineString, MultiPolygon)).
    boost::geometry::difference(mls1, mpy2, *linestrings);
    DBUG_ASSERT(linestrings.get() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *linestrings, NULL) &&
        linestrings->size() > 0)
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    // Return the simplest result possible.
    if (linestrings->size() == 0)
    {
      // MultiPolygon result.
      g2->as_geometry(result, true);
      retgeo= g2;
    }
    else
    {
      // GeometryCollection result containing one or more Polygons and
      // one or more LineStrings.
      Gis_geometry_collection *collection= new Gis_geometry_collection();

      if (mpy2.size() > 1)
      {
        collection->append_geometry(&mpy2, result);
      }
      else
      {
        mpy2[0].to_wkb_unparsed();
        collection->append_geometry(&mpy2[0], result);
      }

      if (linestrings->size() > 1)
      {
        collection->append_geometry(&(*linestrings), result);
      }
      else
      {
        collection->append_geometry(&(*linestrings)[0], result);
      }

      collection->set_ownmem(false);
      retgeo= collection;
    }

    return retgeo;
  }


  Geometry *polygon_union_polygon(Geometry *g1, Geometry *g2, String *result)
  {
    Geometry *retgeo= NULL;
    const void *g1_wkb= g1->normalize_ring_order();
    const void *g2_wkb= g2->normalize_ring_order();
    if (g1_wkb == NULL || g2_wkb == NULL)
    {
      // Invalid polygon
      my_error(ER_GIS_INVALID_DATA, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    Polygon py1(g1->get_data_ptr(), g1->get_data_size(), g1->get_flags(),
                g1->get_srid());
    Polygon py2(g2_wkb, g2->get_data_size(), g2->get_flags(),
                g2->get_srid());
    auto_ptr<Multipolygon> res(new Multipolygon());
    res->set_srid(g1->get_srid());

    boost::geometry::union_(py1, py2, *res);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result) &&
        res->size() > 0)
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    if (res->size() == 0)
    {
      // Invalid polygon
      my_error(ER_GIS_INVALID_DATA, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    retgeo= res.release();
    return retgeo;
  }


  Geometry *polygon_union_multipolygon(Geometry *g1, Geometry *g2,
                                       String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, union_,
             Polygon, g1, Multipolygon, g2, result, null_value);
    return retgeo;
  }


  Geometry *multipolygon_union_multipolygon(Geometry *g1, Geometry *g2,
                                            String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, union_,
             Multipolygon, g1, Multipolygon, g2, result, null_value);

    return retgeo;
  }


  Geometry *point_difference_geometry(Geometry *g1, Geometry *g2,
                                      String *result)
  {
    Geometry *retgeo= NULL;
    bool is_out= Ifsr::bg_geo_relation_check<Coordsys>
      (g1, g2, Ifsr::SP_DISJOINT_FUNC, &null_value);

    if (!null_value)
    {
      if (is_out)
      {
        retgeo= g1;
        null_value= retgeo->as_geometry(result, true);
      }
      else
      {
        retgeo= m_ifso->empty_result(result, g1->get_srid());
        copy_ifso_state();
      }
    }
    return retgeo;
  }


  Geometry *multipoint_difference_geometry(Geometry *g1, Geometry *g2,
                                           String *result)
  {
    Geometry *retgeo= NULL;
    Multipoint *mpts= new Multipoint();
    auto_ptr<Multipoint> guard(mpts);

    mpts->set_srid(g1->get_srid());
    Multipoint mpts1(g1->get_data_ptr(),
                     g1->get_data_size(), g1->get_flags(), g1->get_srid());
    Point_set ptset;

    for (TYPENAME Multipoint::iterator i= mpts1.begin();
         i != mpts1.end(); ++i)
    {
      if (Ifsr::bg_geo_relation_check<Coordsys>
          (&(*i), g2, Ifsr::SP_DISJOINT_FUNC, &null_value))
      {
        if (null_value)
          return 0;
        ptset.insert(*i);
      }
    }

    if (ptset.empty() == false)
    {
      std::copy(ptset.begin(), ptset.end(), std::back_inserter(*mpts));
      null_value= m_ifso->assign_result(mpts, result);
      retgeo= mpts;
      guard.release();
    }
    else
    {
      if (!null_value)
      {
        retgeo= m_ifso->empty_result(result, g1->get_srid());
        copy_ifso_state();
      }
    }
    return retgeo;
  }


  Geometry *linestring_difference_linestring(Geometry *g1, Geometry *g2,
                                             String *result)
  {
    Geometry *retgeo= NULL;
    Linestring ls1(g1->get_data_ptr(), g1->get_data_size(),
                   g1->get_flags(), g1->get_srid());
    Linestring ls2(g2->get_data_ptr(), g2->get_data_size(),
                   g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring());
    res->set_srid(g1->get_srid());

    boost::geometry::difference(ls1, ls2, *res);

    // The call to ls->set_ptr() below assumes that result->length()
    // is the length of the LineString, which is only true if result
    // is empty to begin with.
    DBUG_ASSERT(result->length() == 0);

    if (res->size() == 0)
    {
      post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result);
      retgeo= m_ifso->empty_result(result, g1->get_srid());
    }
    else if (res->size() == 1)
    {
      if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, NULL))
      {
        my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
        null_value= TRUE;
        return NULL;
      }
      Linestring *ls= new Linestring();
      res->begin()->as_geometry(result, false);
      ls->set_ptr(const_cast<char*>(result->ptr()) + GEOM_HEADER_SIZE,
                  result->length() - GEOM_HEADER_SIZE);
      ls->set_ownmem(false);
      retgeo= ls;
    }
    else
    {
      if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result))
      {
        my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
        null_value= TRUE;
        return NULL;
      }
      retgeo= res.release();
    }

    return retgeo;
  }


  Geometry *linestring_difference_polygon(Geometry *g1, Geometry *g2,
                                          String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multilinestring, retgeo, difference,
             Linestring, g1, Polygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }

    return retgeo;
  }


  Geometry *linestring_difference_multilinestring(Geometry *g1, Geometry *g2,
                                                  String *result)
  {
    Geometry *retgeo= NULL;
    Linestring ls1(g1->get_data_ptr(), g1->get_data_size(),
                   g1->get_flags(), g1->get_srid());
    Multilinestring mls2(g2->get_data_ptr(), g2->get_data_size(),
                         g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring());
    res->set_srid(g1->get_srid());

    boost::geometry::difference(ls1, mls2, *res);

    // The call to ls->set_ptr() below assumes that result->length()
    // is the length of the LineString, which is only true if result
    // is empty to begin with.
    DBUG_ASSERT(result->length() == 0);

    if (res->size() == 0)
    {
      post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result);
      retgeo= m_ifso->empty_result(result, g1->get_srid());
    }
    else if (res->size() == 1)
    {
      if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, NULL))
      {
        my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
        null_value= TRUE;
        return NULL;
      }
      Linestring *ls= new Linestring();
      res->begin()->as_geometry(result, false);
      ls->set_ptr(const_cast<char*>(result->ptr()) + GEOM_HEADER_SIZE,
                  result->length() - GEOM_HEADER_SIZE);
      ls->set_ownmem(false);
      retgeo= ls;
    }
    else
    {
      if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result))
      {
        my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
        null_value= TRUE;
        return NULL;
      }
      retgeo= res.release();
    }

    return retgeo;
  }


  Geometry *linestring_difference_multipolygon(Geometry *g1, Geometry *g2,
                                               String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multilinestring, retgeo, difference,
             Linestring, g1, Multipolygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *polygon_difference_polygon(Geometry *g1, Geometry *g2,
                                       String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, difference,
             Polygon, g1, Polygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *polygon_difference_multipolygon(Geometry *g1, Geometry *g2,
                                            String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, difference,
             Polygon, g1, Multipolygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *multilinestring_difference_linestring(Geometry *g1, Geometry *g2,
                                                  String *result)
  {
    Geometry *retgeo= NULL;
    Multilinestring mls1(g1->get_data_ptr(), g1->get_data_size(),
                         g1->get_flags(), g1->get_srid());
    Linestring ls2(g2->get_data_ptr(), g2->get_data_size(),
                   g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring());
    res->set_srid(g1->get_srid());

    boost::geometry::difference(mls1, ls2, *res);

    // The call to ls->set_ptr() below assumes that result->length()
    // is the length of the LineString, which is only true if result
    // is empty to begin with.
    DBUG_ASSERT(result->length() == 0);

    if (res->size() == 0)
    {
      post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result);
      retgeo= m_ifso->empty_result(result, g1->get_srid());
    }
    else if (res->size() == 1)
    {
      if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, NULL))
      {
        my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
        null_value= TRUE;
        return NULL;
      }
      Linestring *ls= new Linestring();
      res->begin()->as_geometry(result, false);
      ls->set_ptr(const_cast<char*>(result->ptr()) + GEOM_HEADER_SIZE,
                  result->length() - GEOM_HEADER_SIZE);
      ls->set_ownmem(false);
      retgeo= ls;
    }
    else
    {
      if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result))
      {
        my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
        null_value= TRUE;
        return NULL;
      }
      retgeo= res.release();
    }

    return retgeo;
  }


  Geometry *multilinestring_difference_polygon(Geometry *g1, Geometry *g2,
                                               String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multilinestring, retgeo, difference,
             Multilinestring, g1, Polygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *multilinestring_difference_multilinestring(Geometry *g1,
                                                       Geometry *g2,
                                                       String *result)
  {
    Geometry *retgeo= NULL;
    Multilinestring mls1(g1->get_data_ptr(), g1->get_data_size(),
                         g1->get_flags(), g1->get_srid());
    Multilinestring mls2(g2->get_data_ptr(), g2->get_data_size(),
                         g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring());
    res->set_srid(g1->get_srid());

    boost::geometry::difference(mls1, mls2, *res);

    // The call to ls->set_ptr() below assumes that result->length()
    // is the length of the LineString, which is only true if result
    // is empty to begin with.
    DBUG_ASSERT(result->length() == 0);

    if (res->size() == 0)
    {
      post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result);
      retgeo= m_ifso->empty_result(result, g1->get_srid());
    }
    else if (res->size() == 1)
    {
      if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, NULL))
      {
        my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
        null_value= TRUE;
        return NULL;
      }
      Linestring *ls= new Linestring();
      res->begin()->as_geometry(result, false);
      ls->set_ptr(const_cast<char*>(result->ptr()) + GEOM_HEADER_SIZE,
                  result->length() - GEOM_HEADER_SIZE);
      ls->set_ownmem(false);
      retgeo= ls;
    }
    else
    {
      if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result))
      {
        my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
        null_value= TRUE;
        return NULL;
      }
      retgeo= res.release();
    }

    return retgeo;
  }


  Geometry *multilinestring_difference_multipolygon(Geometry *g1, Geometry *g2,
                                                    String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multilinestring, retgeo, difference,
             Multilinestring, g1, Multipolygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *multipolygon_difference_polygon(Geometry *g1, Geometry *g2,
                                            String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, difference,
             Multipolygon, g1, Polygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *multipolygon_difference_multipolygon(Geometry *g1, Geometry *g2,
                                                 String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, difference,
             Multipolygon, g1, Multipolygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *linestring_symdifference_linestring(Geometry *g1, Geometry *g2,
                                                String *result)
  {
    Geometry *retgeo= NULL;
    Linestring ls1(g1->get_data_ptr(), g1->get_data_size(),
                   g1->get_flags(), g1->get_srid());
    Linestring ls2(g2->get_data_ptr(), g2->get_data_size(),
                   g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring());
    res->set_srid(g1->get_srid());

    boost::geometry::sym_difference(ls1, ls2, *res);
    DBUG_ASSERT(res.get() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result) &&
        res->size() > 0)
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    if (res->size() == 0)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
    }
    else
    {
      retgeo= res.release();
    }

    return retgeo;
  }


  Geometry *linestring_symdifference_multilinestring(Geometry *g1, Geometry *g2,
                                                     String *result)
  {
    Geometry *retgeo= NULL;
    Linestring ls1(g1->get_data_ptr(), g1->get_data_size(),
                   g1->get_flags(), g1->get_srid());
    Multilinestring mls2(g2->get_data_ptr(), g2->get_data_size(),
                         g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring());
    res->set_srid(g1->get_srid());

    boost::geometry::sym_difference(ls1, mls2, *res);
    DBUG_ASSERT(res.get() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result) &&
        res->size() > 0)
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    if (res->size() == 0)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
    }
    else
    {
      retgeo= res.release();
    }

    return retgeo;
  }


  Geometry *polygon_symdifference_polygon(Geometry *g1, Geometry *g2,
                                          String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, sym_difference,
             Polygon, g1, Polygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *polygon_symdifference_multipolygon(Geometry *g1, Geometry *g2,
                                               String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, sym_difference,
             Polygon, g1, Multipolygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *multilinestring_symdifference_multilinestring(Geometry *g1,
                                                          Geometry *g2,
                                                          String *result)
  {
    Geometry *retgeo= NULL;
    Multilinestring mls1(g1->get_data_ptr(), g1->get_data_size(),
                         g1->get_flags(), g1->get_srid());
    Multilinestring mls2(g2->get_data_ptr(), g2->get_data_size(),
                         g2->get_flags(), g2->get_srid());
    auto_ptr<Multilinestring> res(new Multilinestring());
    res->set_srid(g1->get_srid());

    boost::geometry::sym_difference(mls1, mls2, *res);
    DBUG_ASSERT(res.get() != 0);
    if (post_fix_result(&m_ifso->bg_resbuf_mgr, *res, result) &&
        res->size() > 0)
    {
      my_error(ER_GIS_UNKNOWN_ERROR, MYF(0), m_ifso->func_name());
      null_value= TRUE;
      return NULL;
    }

    if (res->size() == 0)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
    }
    else
    {
      retgeo= res.release();
    }

    return retgeo;
  }


  Geometry *multipolygon_symdifference_polygon(Geometry *g1, Geometry *g2,
                                               String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, sym_difference,
             Multipolygon, g1, Polygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }


  Geometry *multipolygon_symdifference_multipolygon(Geometry *g1, Geometry *g2,
                                                    String *result)
  {
    Geometry *retgeo= NULL;

    BGOPCALL(Multipolygon, retgeo, sym_difference,
             Multipolygon, g1, Multipolygon, g2, result, null_value);

    if (!retgeo && !null_value)
    {
      retgeo= m_ifso->empty_result(result, g1->get_srid());
      copy_ifso_state();
    }
    return retgeo;
  }
};


/**
  Do intersection operation for two geometries, dispatch to specific BG
  function wrapper calls according to set operation type, and the 1st or
  both operand types.

  @tparam Geom_types A wrapper for all geometry types.
  @param g1 First Geometry operand, not a geometry collection.
  @param g2 Second Geometry operand, not a geometry collection.
  @param[out] result Holds WKB data of the result.
  @return The result geometry whose WKB data is held in result.
 */
template <typename Geom_types>
Geometry *Item_func_spatial_operation::
intersection_operation(Geometry *g1, Geometry *g2,
                       String *result)
{
  typedef typename Geom_types::Coordinate_type Coord_type;
  typedef typename Geom_types::Coordinate_system Coordsys;

  BG_setop_wrapper<Geom_types> wrap(this);
  Geometry *retgeo= NULL;
  Geometry::wkbType gt1= g1->get_type();
  Geometry::wkbType gt2= g2->get_type();

  switch (gt1)
  {
  case Geometry::wkb_point:
    switch (gt2)
    {
    case Geometry::wkb_point:
      retgeo= wrap.point_intersection_point(g1, g2, result);
      break;
    case Geometry::wkb_multipoint:
      retgeo= wrap.point_intersection_multipoint(g1, g2, result);
      break;
    case Geometry::wkb_linestring:
    case Geometry::wkb_polygon:
    case Geometry::wkb_multilinestring:
    case Geometry::wkb_multipolygon:
      retgeo= wrap.point_intersection_geometry(g1, g2, result);
      break;
    default:
      break;
    }

    break;
  case Geometry::wkb_multipoint:
    switch (gt2)
    {
    case Geometry::wkb_point:
      retgeo= wrap.point_intersection_multipoint(g2, g1, result);
      break;

    case Geometry::wkb_multipoint:
      retgeo= wrap.multipoint_intersection_multipoint(g1, g2, result);
      break;
    case Geometry::wkb_linestring:
    case Geometry::wkb_polygon:
    case Geometry::wkb_multilinestring:
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multipoint_intersection_geometry(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_linestring:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
      retgeo= intersection_operation<Geom_types>(g2, g1, result);
      break;
    case Geometry::wkb_linestring:
      retgeo= wrap.linestring_intersection_linestring(g1, g2, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.linestring_intersection_multilinestring(g1, g2, result);
      break;
    case Geometry::wkb_polygon:
    case Geometry::wkb_multipolygon:
      retgeo= wrap.linestring_intersection_polygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_polygon:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
      retgeo= intersection_operation<Geom_types>(g2, g1, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.polygon_intersection_multilinestring(g1, g2, result);
      break;
    case Geometry::wkb_polygon:
    case Geometry::wkb_multipolygon:
      // Note: for now BG's set operations don't allow returning a
      // Multilinestring, thus this result isn't complete.
      retgeo= wrap.polygon_intersection_polygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_multilinestring:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
    case Geometry::wkb_polygon:
      retgeo= intersection_operation<Geom_types>(g2, g1, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.multilinestring_intersection_multilinestring(g1, g2, result);
      break;

    case Geometry::wkb_multipolygon:
      retgeo= wrap.multilinestring_intersection_multipolygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_multipolygon:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
    case Geometry::wkb_multilinestring:
    case Geometry::wkb_polygon:
      retgeo= intersection_operation<Geom_types>(g2, g1, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multipolygon_intersection_multipolygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  default:
    break;
  }

  if (!null_value)
    null_value= wrap.get_null_value();
  return retgeo;
}


/**
  Do union operation for two geometries, dispatch to specific BG
  function wrapper calls according to set operation type, and the 1st or
  both operand types.

  @tparam Geom_types A wrapper for all geometry types.
  @param g1 First Geometry operand, not a geometry collection.
  @param g2 Second Geometry operand, not a geometry collection.
  @param[out] result Holds WKB data of the result.
  @return The result geometry whose WKB data is held in result.
 */
template <typename Geom_types>
Geometry *Item_func_spatial_operation::
union_operation(Geometry *g1, Geometry *g2, String *result)
{
  typedef typename Geom_types::Coordinate_type Coord_type;
  typedef typename Geom_types::Coordinate_system Coordsys;

  BG_setop_wrapper<Geom_types> wrap(this);
  Geometry *retgeo= NULL;
  Geometry::wkbType gt1= g1->get_type();
  Geometry::wkbType gt2= g2->get_type();

  // Note that union can't produce empty point set unless given two empty
  // point set arguments.
  switch (gt1)
  {
  case Geometry::wkb_point:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
      retgeo= wrap.point_union_point(g1, g2, result);
      break;
    case Geometry::wkb_linestring:
    case Geometry::wkb_multilinestring:
    case Geometry::wkb_polygon:
    case Geometry::wkb_multipolygon:
      retgeo= wrap.point_union_geometry(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_multipoint:
    switch (gt2)
    {
    case Geometry::wkb_point:
      retgeo= wrap.point_union_point(g2, g1, result);
      break;
    case Geometry::wkb_multipoint:
      retgeo= wrap.multipoint_union_multipoint(g1, g2, result);
      break;
    case Geometry::wkb_linestring:
    case Geometry::wkb_multilinestring:
    case Geometry::wkb_polygon:
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multipoint_union_geometry(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_linestring:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
      retgeo= union_operation<Geom_types>(g2, g1, result);
      break;
    case Geometry::wkb_linestring:
      retgeo= wrap.linestring_union_linestring(g1, g2, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.linestring_union_multilinestring(g1, g2, result);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.linestring_union_polygon(g1, g2, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.linestring_union_multipolygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_polygon:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
      retgeo= union_operation<Geom_types>(g2, g1, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.polygon_union_multilinestring(g1, g2, result);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.polygon_union_polygon(g1, g2, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.polygon_union_multipolygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_multilinestring:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
    case Geometry::wkb_polygon:
      retgeo= union_operation<Geom_types>(g2, g1, result);
      break;
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.multilinestring_union_multilinestring(g1, g2, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multilinestring_union_multipolygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_multipolygon:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
    case Geometry::wkb_polygon:
    case Geometry::wkb_multilinestring:
      retgeo= union_operation<Geom_types>(g2, g1, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multipolygon_union_multipolygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  default:
    break;
  }

  if (!null_value)
    null_value= wrap.get_null_value() && maybe_null;
  if (!null_value && retgeo == NULL)
  {
    my_error(ER_GIS_INVALID_DATA, MYF(0), func_name());
    error_str();
    return NULL;
  }
  return retgeo;
}


/**
  Do difference operation for two geometries, dispatch to specific BG
  function wrapper calls according to set operation type, and the 1st or
  both operand types.

  @tparam Geom_types A wrapper for all geometry types.
  @param g1 First Geometry operand, not a geometry collection.
  @param g2 Second Geometry operand, not a geometry collection.
  @param[out] result Holds WKB data of the result.
  @return The result geometry whose WKB data is held in result.
 */
template <typename Geom_types>
Geometry *Item_func_spatial_operation::
difference_operation(Geometry *g1, Geometry *g2, String *result)
{
  BG_setop_wrapper<Geom_types> wrap(this);
  Geometry *retgeo= NULL;
  Geometry::wkbType gt1= g1->get_type();
  Geometry::wkbType gt2= g2->get_type();

  /*
    Given two geometries g1 and g2, where g1.dimension < g2.dimension, then
    g2 - g1 is equal to g2, this is always true. This is how postgis works.
    Below implementation uses this fact.
   */
  switch (gt1)
  {
  case Geometry::wkb_point:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
    case Geometry::wkb_polygon:
    case Geometry::wkb_multilinestring:
    case Geometry::wkb_multipolygon:
      retgeo= wrap.point_difference_geometry(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_multipoint:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
    case Geometry::wkb_polygon:
    case Geometry::wkb_multilinestring:
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multipoint_difference_geometry(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_linestring:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
      retgeo= g1;
      null_value= g1->as_geometry(result, true);
      break;
    case Geometry::wkb_linestring:
      retgeo= wrap.linestring_difference_linestring(g1, g2, result);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.linestring_difference_polygon(g1, g2, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.linestring_difference_multilinestring(g1, g2, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.linestring_difference_multipolygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_polygon:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
    case Geometry::wkb_multilinestring:
      retgeo= g1;
      null_value= g1->as_geometry(result, true);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.polygon_difference_polygon(g1, g2, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.polygon_difference_multipolygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_multilinestring:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
      retgeo= g1;
      null_value= g1->as_geometry(result, true);
      break;
    case Geometry::wkb_linestring:
      retgeo= wrap.multilinestring_difference_linestring(g1, g2, result);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.multilinestring_difference_polygon(g1, g2, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.multilinestring_difference_multilinestring(g1, g2, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multilinestring_difference_multipolygon(g1, g2,
                                                           result);
      break;
    default:
      break;
    }
    break;
  case Geometry::wkb_multipolygon:
    switch (gt2)
    {
    case Geometry::wkb_point:
    case Geometry::wkb_multipoint:
    case Geometry::wkb_linestring:
    case Geometry::wkb_multilinestring:
      retgeo= g1;
      null_value= g1->as_geometry(result, true);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.multipolygon_difference_polygon(g1, g2, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multipolygon_difference_multipolygon(g1, g2, result);
      break;
    default:
      break;
    }
    break;
  default:
    break;
  }

  if (!null_value)
    null_value= wrap.get_null_value();
  return retgeo;
}


/**
  Do symdifference operation for two geometries, dispatch to specific BG
  function wrapper calls according to set operation type, and the 1st or
  both operand types.

  @tparam Geom_types A wrapper for all geometry types.
  @param g1 First Geometry operand, not a geometry collection.
  @param g2 Second Geometry operand, not a geometry collection.
  @param[out] result Holds WKB data of the result.
  @return The result geometry whose WKB data is held in result.
 */
template <typename Geom_types>
Geometry *Item_func_spatial_operation::
symdifference_operation(Geometry *g1, Geometry *g2, String *result)
{
  typedef typename Geom_types::Coordinate_type Coord_type;
  typedef typename Geom_types::Coordinate_system Coordsys;

  BG_setop_wrapper<Geom_types> wrap(this);
  Geometry *retgeo= NULL;
  Geometry::wkbType gt1= g1->get_type();
  Geometry::wkbType gt2= g2->get_type();

  /*
    SymDifference(L, A) isn't supported by BG, but it's equivalent
    to

        GeometryCollection(Difference(A, L), Difference(L, A))

    Since geometries must be closed, Difference(A, L) is equivalent
    to A, so we can simplify to

        GeometryCollection(A, Difference(L, A))

    This is equivalent to Union(L, A), so we reuse that implementation.
   */
  bool do_geocol_setop= false;

  switch (gt1)
  {
  case Geometry::wkb_linestring:
    switch (gt2)
    {
    case Geometry::wkb_linestring:
      retgeo= wrap.linestring_symdifference_linestring(g1, g2, result);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.linestring_union_polygon(g1, g2, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.linestring_symdifference_multilinestring(g1, g2, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.linestring_union_multipolygon(g1, g2, result);
      break;
    default:
      do_geocol_setop= true;
      break;
    }
    break;
  case Geometry::wkb_polygon:

    switch (gt2)
    {
    case Geometry::wkb_linestring:
      retgeo= wrap.linestring_union_polygon(g2, g1, result);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.polygon_symdifference_polygon(g1, g2, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.polygon_union_multilinestring(g1, g2, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.polygon_symdifference_multipolygon(g1, g2, result);
      break;
    default:
      do_geocol_setop= true;
      break;
    }
    break;
  case Geometry::wkb_multilinestring:
    switch (gt2)
    {
    case Geometry::wkb_linestring:
      retgeo= wrap.linestring_symdifference_multilinestring(g2, g1, result);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.polygon_union_multilinestring(g2, g1, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.multilinestring_symdifference_multilinestring(g1, g2,
                                                                 result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multilinestring_union_multipolygon(g1, g2, result);
      break;
    default:
      do_geocol_setop= true;
      break;
    }
    break;
  case Geometry::wkb_multipolygon:
    switch (gt2)
    {
    case Geometry::wkb_linestring:
      retgeo= wrap.linestring_union_multipolygon(g2, g1, result);
      break;
    case Geometry::wkb_polygon:
      retgeo= wrap.multipolygon_symdifference_polygon(g1, g2, result);
      break;
    case Geometry::wkb_multilinestring:
      retgeo= wrap.multilinestring_union_multipolygon(g2, g1, result);
      break;
    case Geometry::wkb_multipolygon:
      retgeo= wrap.multipolygon_symdifference_multipolygon(g1, g2, result);
      break;
    default:
      do_geocol_setop= true;
      break;
    }
    break;
  default:
    do_geocol_setop= true;
    break;
  }

  if (do_geocol_setop)
    retgeo= geometry_collection_set_operation<
      Coordsys>(g1, g2, result);
  else if (!null_value)
    null_value= wrap.get_null_value();
  return retgeo;
}


/**
  Call boost geometry set operations to compute set operation result, and
  returns the result as a Geometry object.

  @tparam Coordsys Coordinate system type, specified using those defined in
          boost::geometry::cs.
  @param g1 First Geometry operand, not a geometry collection.
  @param g2 Second Geometry operand, not a geometry collection.
  @param[out] result buffer containing the GEOMETRY byte string of
  the returned geometry.

  @return If the set operation results in an empty point set, return a
  geometry collection containing 0 objects. If null_value is set to
  true, always returns 0. The returned geometry object can be used in the same
  val_str call.
 */
template<typename Coordsys>
Geometry *Item_func_spatial_operation::
bg_geo_set_op(Geometry *g1, Geometry *g2, String *result)
{
  typedef BG_models<Coordsys> Geom_types;

  Geometry *retgeo= NULL;

  if (g1->get_coordsys() != g2->get_coordsys())
    return 0;


  switch (spatial_op)
  {
  case op_intersection:
    retgeo= intersection_operation<Geom_types>(g1, g2, result);
    break;
  case op_union:
    retgeo= union_operation<Geom_types>(g1, g2, result);
    break;
  case op_difference:
    retgeo= difference_operation<Geom_types>(g1, g2, result);
    break;
  case op_symdifference:
    retgeo= symdifference_operation<Geom_types>(g1, g2, result);
    break;
  default:
    // Other operations are not set operations.
    DBUG_ASSERT(false);
    break;
  }

  /*
    null_value is set in above xxx_operatoin calls if error occured.
  */
  if (null_value)
  {
    error_str();
    DBUG_ASSERT(retgeo == NULL);
  }

  // If we got effective result, the wkb encoding is written to 'result', and
  // the retgeo is effective geometry object whose data points into
  // 'result''s data.
  return retgeo;

}


/**
  Combine sub-results of set operation into a geometry collection.
  This function eliminates points in geo2 that are within
  geo1(polygons or linestrings). We have to do so
  because BG set operations return results in 3 forms --- multipolygon,
  multilinestring and multipoint, however given a type of set operation and
  the operands, the returned 3 types of results may intersect, and we want to
  eliminate the points already in the polygons/linestrings. And in future we
  also need to remove the linestrings that are already in the polygons, this
  isn't done now because there are no such set operation results to combine.

  @tparam Coordsys Coordinate system type, specified using those defined in
          boost::geometry::cs.
  @param geo1 First operand, a Multipolygon or Multilinestring object
              computed by BG set operation.
  @param geo2 Second operand, a Multipoint object
              computed by BG set operation.
  @param result Holds result geometry's WKB data in GEOMETRY format.
  @return A geometry combined from geo1 and geo2. Either or both of
  geo1 and geo2 can be NULL, so we may end up with a multipoint,
  a multipolygon/multilinestring, a geometry collection, or an
  empty geometry collection.
 */
template<typename Coordsys>
Geometry *Item_func_spatial_operation::
combine_sub_results(Geometry *geo1, Geometry *geo2, String *result)
{
  typedef BG_models<Coordsys> Geom_types;
  typedef typename Geom_types::Multipoint Multipoint;
  Geometry *retgeo= NULL;
  bool isin= false, added= false;

  if (null_value)
  {
    delete geo1;
    delete geo2;
    return NULL;
  }

  auto_ptr<Geometry> guard1(geo1), guard2(geo2);

  Gis_geometry_collection *geocol= NULL;
  if (geo1 == NULL && geo2 == NULL)
    retgeo= empty_result(result, Geometry::default_srid);
  else if (geo1 != NULL && geo2 == NULL)
  {
    retgeo= geo1;
    null_value= assign_result(geo1, result);
    guard1.release();
  }
  else if (geo1 == NULL && geo2 != NULL)
  {
    retgeo= geo2;
    null_value= assign_result(geo2, result);
    guard2.release();
  }

  if (geo1 == NULL || geo2 == NULL)
  {
    if (null_value)
      retgeo= NULL;
    return retgeo;
  }

  DBUG_ASSERT((geo1->get_type() == Geometry::wkb_multilinestring ||
               geo1->get_type() == Geometry::wkb_multipolygon) &&
              geo2->get_type() == Geometry::wkb_multipoint);
  Multipoint mpts(geo2->get_data_ptr(), geo2->get_data_size(),
                  geo2->get_flags(), geo2->get_srid());
  geocol= new Gis_geometry_collection(geo1, result);
  geocol->set_components_no_overlapped(geo1->is_components_no_overlapped());
  auto_ptr<Gis_geometry_collection> guard3(geocol);
  my_bool had_error= false;

  for (TYPENAME Multipoint::iterator i= mpts.begin();
       i != mpts.end(); ++i)
  {
    isin= !Item_func_spatial_rel::bg_geo_relation_check<
      Coordsys>(&(*i), geo1, SP_DISJOINT_FUNC, &had_error);

    if (had_error)
    {
      error_str();
      return NULL;
    }

    if (!isin)
    {
      geocol->append_geometry(&(*i), result);
      added= true;
    }
  }

  if (added)
  {
    retgeo= geocol;
    guard3.release();
  }
  else
  {
    retgeo= geo1;
    guard1.release();
    null_value= assign_result(geo1, result);
  }

  if (null_value)
    error_str();

  return retgeo;
}


/**
  Simplify a multilinestring result from the BG intersection function.

  The multilinestring may contain linestrings with two points where
  both points are the same. Those are intersection points that should
  be returned to the user as points. This function loops through the
  multilinestring, separating true linestrings and linestring-encoded
  points, and returns the simplest geometric result possible: point,
  linestring, multilinestring, multipoint, or geometry collection.

  @param mls The multilinestring to simplify
  @param[out] result The GEOMETRY string (SRID+WKB) of the returned object
  @return The simplest geometry type representing the input.
*/
Geometry *Item_func_spatial_operation::
simplify_multilinestring(Gis_multi_line_string *mls, String *result)
{
  // Null values are handled by caller.
  DBUG_ASSERT(mls != NULL);
  Geometry *retgeo;

  // Loop through the multilinestring and separate true linestrings
  // from points.
  auto_ptr<Gis_multi_line_string> linestrings(new Gis_multi_line_string());
  auto_ptr<Gis_multi_point> points(new Gis_multi_point());
  linestrings->set_srid(mls->get_srid());
  points->set_srid(mls->get_srid());
  // BG may return duplicate points, so use a point set to get unique
  // points before storing them into 'points'.
  typedef std::set<Gis_point, bgpt_lt> Point_set;
  Point_set point_set(points->begin(), points->end());
  Gis_point prev_point;
  bool has_prev_point= false;

  for (Gis_multi_line_string::iterator i= mls->begin();
       i != mls->end();
       ++i)
  {
    if (i->size() != 2)
    {
      DBUG_ASSERT(i->size() > 2);
      linestrings->push_back(*i);
      continue;
    }

    const Gis_point &start= (*i)[0];
    const Gis_point &end= (*i)[1];
    if (start == end)
    {
      if (!has_prev_point || prev_point != start)
      {
        has_prev_point= true;
        prev_point= start;
        point_set.insert(start);
      }
    }
    else
      linestrings->push_back(*i);
  }

  for (Point_set::iterator i= point_set.begin();
       i != point_set.end(); ++i)
  {
    /*
      When computing [m]ls intersection [m]ls, BG may return points that are
      on the linestrings, so here we have to exclude such points.
    */
    if (bg::disjoint(*i, *linestrings))
      points->push_back(*i);
  }

  String dummy;
  post_fix_result(&bg_resbuf_mgr, *linestrings, &dummy);
  post_fix_result(&bg_resbuf_mgr, *points, &dummy);

  // Return the simplest type possible
  if (points->size() == 0 && linestrings->size() == 1)
  {
    // Linestring result
    Gis_line_string *linestringresult= new Gis_line_string();
    size_t oldlength= result->length();
    linestrings->begin()->as_geometry(result, false);
    size_t newlength= result->length();
    linestringresult->set_ptr(const_cast<char*>(result->ptr()) + oldlength +
                              GEOM_HEADER_SIZE,
                              (newlength - oldlength) - GEOM_HEADER_SIZE);
    linestringresult->set_ownmem(false);
    retgeo= linestringresult;
  }
  else if (points->size() == 0 && linestrings->size() > 1)
  {
    // Multilinestring result
    linestrings->as_geometry(result, false);
    linestrings->set_ownmem(false);
    retgeo= linestrings.release();
  }
  else if (points->size() == 1 && linestrings->size() == 0)
  {
    // Point result
    Gis_point *pointresult= new Gis_point();
    size_t oldlength= result->length();
    points->begin()->as_geometry(result, false);
    size_t newlength= result->length();
    pointresult->set_ptr(const_cast<char*>(result->ptr()) + oldlength +
                         GEOM_HEADER_SIZE,
                         (newlength - oldlength) - GEOM_HEADER_SIZE);
    pointresult->set_ownmem(false);
    retgeo= pointresult;
  }
  else if (points->size() > 1 && linestrings->size() == 0)
  {
    // Multipoint result
    points->as_geometry(result, false);
    points->set_ownmem(false);
    retgeo= points.release();
  }
  else
  {
    // GeometryCollection result
    Gis_geometry_collection *collection= new Gis_geometry_collection();

    if (points->size() > 1)
    {
      collection->append_geometry(&(*points), result);
    }
    else
    {
      collection->append_geometry(&(*points)[0], result);
    }

    if (linestrings->size() > 1)
    {
      collection->append_geometry(&(*linestrings), result);
    }
    else
    {
      collection->append_geometry(&(*linestrings)[0], result);
    }

    collection->set_ownmem(false);
    retgeo= collection;
  }

  return retgeo;
}


/**
  Extract a basic geometry component from a multi geometry or a geometry
  collection, if it's the only one in it.
 */
class Singleton_extractor : public WKB_scanner_event_handler
{
  /*
    If we see the nested geometries as a forest, seeing the outmost one as the
    ground where the trees grow, and seeing each of its components
    as a tree, then the search for a singleton in a geometry collection(GC) or
    multi-geometry(i.e. multipoint, multilinestring, multipolygon) is identical
    to searching on the ground to see if there is only one tree on the ground,
    if so we also need to record its starting address within the root node's
    memory buffer.

    Some details complicate the problem:
    1. GCs can be nested into another GC, a nested GC should be see also as
       the 'ground' rather than a tree.
    2. A single multi-geometry contained in a GC may be a singleton or not.
       a. When it has only one component in itself, that component is
          the singleton;
       b. Otherwise itself is the singleton.
    3. Basic geometries are always atomic(undevidible).
    4. A multi-geometry can't be nested into another multigeometry, it can
       only be a component of a GC.

    Below comment for the data members are based on this context information.
  */
  // The number of trees on the ground.
  int ntrees;
  // The number of trees inside all multi-geometries.
  int nsubtrees;
  // Current tree travasal stack depth, i.e. tree height.
  int depth;
  // The depth of the multi-geometry, if any.
  int mg_depth;
  // The effective stack depth, i.e. excludes the nested GCs.
  int levels;
  // The stack depth of heighest GC in current ground.
  int gc_depth;
  // Starting and ending address of tree on ground.
  const char *start, *end;
  // Starting address of and type of the basic geometry which is on top of the
  // multi-geometry.
  const char *bg_start;
  Geometry::wkbType bg_type;

  // The type of the geometry on the ground.
  Geometry::wkbType gtype;
public:
  Singleton_extractor()
  {
    ntrees= nsubtrees= depth= mg_depth= levels= gc_depth= 0;
    bg_start= start= end= NULL;
    bg_type= gtype= Geometry::wkb_invalid_type;
  }

  static bool is_basic_type(const Geometry::wkbType t)
  {
    return t == Geometry::wkb_point || t == Geometry::wkb_linestring ||
      t == Geometry::wkb_polygon;
  }

  bool has_single_component() const
  {
    return ntrees == 1;
  }

  // Functions to get singleton information.

  /*
    Returns start of singleton. If only one sub-tree, the basic geometry
    is returned instead of the multi-geometry, otherwise the multi-geometry
    is returned.
   */
  const char *get_start() const
  {
    return nsubtrees == 1 ? bg_start : start;
  }

  /*
    Returns the end of the singleton geometry. For a singleton,
    its end is always also the end of the root geometry, so this function
    is correct only when the root geometry really contains a singleton.
   */
  const char *get_end() const
  {
    return end;
  }

  Geometry::wkbType get_type() const
  {
    return nsubtrees == 1 ? bg_type : gtype;
  }


  virtual void on_wkb_start(Geometry::wkbByteOrder bo,
                            Geometry::wkbType geotype,
                            const void *wkb, uint32 len, bool has_hdr)
  {
    if (geotype != Geometry::wkb_geometrycollection)
    {
      if (gc_depth == 0)
      {
        gc_depth= depth;
        start= static_cast<const char *>(wkb);
        end= start + len;
        gtype= geotype;
      }

      if (!is_basic_type(geotype))
        mg_depth= depth;

      if (mg_depth + 1 == depth)
      {
        bg_type= geotype;
        bg_start= static_cast<const char *>(wkb);
      }

      levels++;
    }
    else
      gc_depth= 0;

    depth++;
  }


  virtual void on_wkb_end(const void *wkb)
  {
    depth--;
    DBUG_ASSERT(depth >= 0);

    if (levels > 0)
    {
      levels--;
      if (levels == 0)
      {
        DBUG_ASSERT(depth == gc_depth);
        ntrees++;
        end= static_cast<const char *>(wkb);
        mg_depth= 0;
        gc_depth= 0;
      }
    }

    // The subtree is either a multi-geometry or a basic geometry.
    if (mg_depth != 0 && levels == 1)
      nsubtrees++;
  }
};


inline Geometry::wkbType base_type(Geometry::wkbType gt)
{
  Geometry::wkbType ret;

  switch (gt)
  {
  case Geometry::wkb_multipoint:
    ret= Geometry::wkb_point;
    break;
  case Geometry::wkb_multilinestring:
    ret= Geometry::wkb_linestring;
    break;
  case Geometry::wkb_multipolygon:
    ret= Geometry::wkb_polygon;
    break;
  default:
    ret= gt;
  }
  return ret;
}


/**
  Simplify multi-geometry data. If str contains a multi-geometry or geometry
  collection with one component, the component is made as content of str.
  If str contains a nested geometry collection, the effective concrete geometry
  object is returned.
  @param str A string buffer containing a GEOMETRY byte string.
  @param [out] result_buffer if not NULL and if a simplification is to be done,
               we will copy the GEOMETRY byte string from str into result_buffer
               and simplify the one stored in result_buffer, and the one in str
               is intact. If there is any data in result_buffer before calling
               this function, it is overwritten; If no simplification done, the
               result_buffer is intact if it is provided.
  @return whether the geometry is simplified or not.
 */
bool simplify_multi_geometry(String *str, String *result_buffer)
{
  if (str->length() < GEOM_HEADER_SIZE)
    return false;

  char *p= const_cast<char *>(str->ptr());
  Geometry::wkbType gtype= get_wkb_geotype(p + 5);
  bool ret= false;

  if (gtype == Geometry::wkb_multipoint ||
      gtype == Geometry::wkb_multilinestring ||
      gtype == Geometry::wkb_multipolygon)
  {
    if (uint4korr(p + GEOM_HEADER_SIZE) == 1)
    {
      if (result_buffer)
      {
        result_buffer->length(0);
        result_buffer->append(*str);
        p= const_cast<char *>(result_buffer->ptr());
        str= result_buffer;
      }
      DBUG_ASSERT((str->length() - GEOM_HEADER_SIZE - 4 - WKB_HEADER_SIZE) > 0);
      int4store(p + 5, static_cast<uint32>(base_type(gtype)));
      memmove(p + GEOM_HEADER_SIZE, p + GEOM_HEADER_SIZE + 4 + WKB_HEADER_SIZE,
              str->length() - GEOM_HEADER_SIZE - 4 - WKB_HEADER_SIZE);
      str->length(str->length() - 4 - WKB_HEADER_SIZE);
      ret= true;
    }
  }
  else if (gtype == Geometry::wkb_geometrycollection)
  {
    Singleton_extractor ex;
    uint32 wkb_len= str->length() - GEOM_HEADER_SIZE;
    wkb_scanner(p + GEOM_HEADER_SIZE, &wkb_len,
                Geometry::wkb_geometrycollection, false, &ex);
    if (ex.has_single_component())
    {
      if (result_buffer)
      {
        result_buffer->length(0);
        result_buffer->append(*str);
        p= const_cast<char *>(result_buffer->ptr());
        str= result_buffer;
      }
      p= write_wkb_header(p + 4, ex.get_type());
      ptrdiff_t len= ex.get_end() - ex.get_start();
      DBUG_ASSERT(len > 0);
      memmove(p, ex.get_start(), len);
      str->length(GEOM_HEADER_SIZE + len);
      ret= true;
    }
  }

  return ret;
}


/*
  Do set operations on geometries.
  Writes geometry set operation result into str_value_arg in wkb format.
 */
String *Item_func_spatial_operation::val_str(String *str_value_arg)
{
  DBUG_ENTER("Item_func_spatial_operation::val_str");
  DBUG_ASSERT(fixed == 1);

  tmp_value1.length(0);
  tmp_value2.length(0);
  String *res1= args[0]->val_str(&tmp_value1);
  String *res2= args[1]->val_str(&tmp_value2);
  Geometry_buffer buffer1, buffer2;
  Geometry *g1= NULL, *g2= NULL, *gres= NULL;
  bool had_except1= false, had_except2= false;
  bool result_is_args= false;

  // Release last call's result buffer.
  bg_resbuf_mgr.free_result_buffer();

  // Clean up the result first, since caller may give us one with non-NULL
  // buffer, we don't need it here.
  str_value_arg->set(NullS, 0, &my_charset_bin);

  if ((null_value= (!res1 || args[0]->null_value ||
                    !res2 || args[1]->null_value)))
    goto exit;
  if (!(g1= Geometry::construct(&buffer1, res1)) ||
      !(g2= Geometry::construct(&buffer2, res2)))
  {
    my_error(ER_GIS_INVALID_DATA, MYF(0), func_name());
    DBUG_RETURN(error_str());
  }

  // The two geometry operand must be in the same coordinate system.
  if (g1->get_srid() != g2->get_srid())
  {
    my_error(ER_GIS_DIFFERENT_SRIDS, MYF(0), func_name(),
             g1->get_srid(), g2->get_srid());
    DBUG_RETURN(error_str());
  }

  str_value_arg->set_charset(&my_charset_bin);
  str_value_arg->length(0);


  /*
    Catch all exceptions to make sure no exception can be thrown out of
    current function. Put all and any code that calls Boost.Geometry functions,
    STL functions into this try block. Code out of the try block should never
    throw any exception.
  */
  try
  {
    if (g1->get_type() != Geometry::wkb_geometrycollection &&
        g2->get_type() != Geometry::wkb_geometrycollection)
      gres= bg_geo_set_op<bgcs::cartesian>(g1, g2, str_value_arg);
    else
      gres= geometry_collection_set_operation<bgcs::cartesian>
        (g1, g2, str_value_arg);

  }
  catch (...)
  {
    had_except1= true;
    handle_gis_exception(func_name());
  }

  try
  {
    /*
      The buffers in res1 and res2 either belong to argument Item_xxx objects
      or simply belong to tmp_value1 or tmp_value2, they will be deleted
      properly by their owners, not by our bg_resbuf_mgr, so here we must
      forget them in order not to free the buffers before the Item_xxx
      owner nodes are destroyed.
    */
    bg_resbuf_mgr.forget_buffer(const_cast<char *>(res1->ptr()));
    bg_resbuf_mgr.forget_buffer(const_cast<char *>(res2->ptr()));
    bg_resbuf_mgr.forget_buffer(const_cast<char *>(tmp_value1.ptr()));
    bg_resbuf_mgr.forget_buffer(const_cast<char *>(tmp_value2.ptr()));

    /*
      Release intermediate geometry data buffers accumulated during execution
      of this set operation.
    */
    if (!str_value_arg->is_alloced() && gres != g1 && gres != g2)
      bg_resbuf_mgr.set_result_buffer(const_cast<char *>(str_value_arg->ptr()));
    bg_resbuf_mgr.free_intermediate_result_buffers();
  }
  catch (...)
  {
    had_except2= true;
    handle_gis_exception(func_name());
  }

  if (had_except1 || had_except2 || null_value)
  {
    if (gres != NULL && gres != g1 && gres != g2)
    {
      delete gres;
      gres= NULL;
    }
    DBUG_RETURN(error_str());
  }

  DBUG_ASSERT(gres != NULL && !null_value && str_value_arg->length() > 0);

  /*
    There are 4 ways to create the result geometry object and allocate
    memory for the result String object:
    1. Created in BGOPCALL and allocated by BG code using gis_wkb_alloc
       functions; The geometry result object's memory is took over by
       str_value_arg, thus not allocated by str_value_arg.
    2. Created as a new geometry object and allocated by
       str_value_arg's String member functions.
    3. One of g1 or g2 used as result and g1/g2's String object is used as
       final result without duplicating their byte strings. Also, g1 and/or
       g2 may be used as intermediate result and their byte strings are
       assigned to intermediate String objects without giving the ownerships
       to them, so they are always owned by tmp_value1 and/or tmp_value2.
    4. A geometry duplicated from a component of BG_geometry_collection.
       when both GCs have 1 member, we do set operation for the two members
       directly, and if such a component is the result we have to duplicate
       it and its WKB String buffer.

    Among above 4 ways, #1, #2 and #4 write the byte string only once without
    any data copying, #3 doesn't write any byte strings.

    And here we always have a GEOMETRY byte string in str_value_arg, although
    in some cases gres->has_geom_header_space() is false.
   */
  if (!str_value_arg->is_alloced() && gres != g1 && gres != g2)
  {
    DBUG_ASSERT(gres->has_geom_header_space() || gres->is_bg_adapter());
  }
  else
  {
    DBUG_ASSERT(gres->has_geom_header_space() || (gres == g1 || gres == g2));
    if (gres == g1)
    {
      str_value_arg= res1;
      result_is_args= true;
    }
    else if (gres == g2)
    {
      str_value_arg= res2;
      result_is_args= true;
    }
  }

  /*
    We can not modify the geometry argument because it is assumed to be stable.
    So if returning one of the arguments as result directly, make sure the
    simplification is done in a separate buffer.
  */
  if (simplify_multi_geometry(str_value_arg,
                              (result_is_args ? &m_result_buffer : NULL)) &&
      result_is_args)
    str_value_arg= &m_result_buffer;

exit:
  if (gres != g1 && gres != g2 && gres != NULL)
    delete gres;
  DBUG_RETURN(null_value ? NULL : str_value_arg);
}


inline bool is_areal(const Geometry *g)
{
  return g != NULL && (g->get_type() == Geometry::wkb_polygon ||
                       g->get_type() == Geometry::wkb_multipolygon);
}


/**
  Do set operation on geometry collections.
  BG doesn't directly support geometry collections in any function, so we
  have to do so by computing the set operation result of all two operands'
  components, which must be the 6 basic types of geometries, and then we
  combine the sub-results.

  This function dispatches to specific set operation types.

  @tparam Coordsys Coordinate system type, specified using those defined in
          boost::geometry::cs.
  @param g1 First geometry operand, a geometry collection.
  @param g2 Second geometry operand, a geometry collection.
  @param[out] result Holds WKB data of the result, which must be a
                geometry collection.
  @return The set operation result, whose WKB data is stored in 'result'.
 */
template<typename Coordsys>
Geometry *Item_func_spatial_operation::
geometry_collection_set_operation(Geometry *g1, Geometry *g2,
                                  String *result)
{
  Geometry *gres= NULL;
  BG_geometry_collection bggc1, bggc2;

  bggc1.set_srid(g1->get_srid());
  bggc2.set_srid(g2->get_srid());
  bool empty1= is_empty_geocollection(g1);
  bool empty2= is_empty_geocollection(g2);

  /* Short cut for either one operand being empty. */
  if (empty1 || empty2)
  {
    if (spatial_op == op_intersection ||
        (empty1 && empty2 && (spatial_op == op_symdifference ||
                              spatial_op == op_union)) ||
        (empty1 && spatial_op == op_difference))
    {
      return empty_result(result, g1->get_srid());
    }

    // If spatial_op is op_union or op_symdifference and one of g1/g2 is empty,
    // we will continue in order to merge components in the argument.

    if (empty2 && spatial_op == op_difference)
    {
      null_value= g1->as_geometry(result, true/* shallow copy */);
      return g1;
    }
  }

  bggc1.fill(g1);
  bggc2.fill(g2);
  if (spatial_op != op_union)
  {
    bggc1.merge_components<Coordsys>(&null_value);
    if (null_value)
      return gres;
    bggc2.merge_components<Coordsys>(&null_value);
    if (null_value)
      return gres;
  }

  BG_geometry_collection::Geometry_list &gv1= bggc1.get_geometries();
  BG_geometry_collection::Geometry_list &gv2= bggc2.get_geometries();

  /*
    If there is only one component in one argument and the other is empty,
    no merge is possible.
  */
  if (spatial_op == op_union ||
      spatial_op == op_symdifference)
  {
    if (gv1.size() == 0 && gv2.size() == 1)
    {
      null_value= g2->as_geometry(result, true/* shallow copy */);
      return g2;
    }

    if (gv1.size() == 1 && gv2.size() == 0)
    {
      null_value= g1->as_geometry(result, true/* shallow copy */);
      return g1;
    }
  }

  /*
    If both collections have only one basic component, do basic set operation.
    The exception is symdifference with at least one operand being not a
    polygon or multipolygon, in which case this exact function is called to
    perform symdifference for the two basic components.
   */
  if (gv1.size() == 1 && gv2.size() == 1 &&
      (spatial_op != op_symdifference ||
       (is_areal(*(gv1.begin())) && is_areal(*(gv2.begin())))))
  {
    gres= bg_geo_set_op<Coordsys>(*(gv1.begin()), *(gv2.begin()),
                                              result);
    if (null_value)
      return NULL;
    if (gres == NULL && !null_value)
    {
      gres= empty_result(result, g1->get_srid());
      return gres;
    }

    /*
      If this set operation gives us a gres that's a component/member of either
      bggc1 or bggc2, we have to duplicate the object and its buffer because
      they will be destroyed when bggc1/bggc2 goes out of scope.
     */
    bool do_dup= false;
    for (BG_geometry_collection::Geometry_list::iterator i= gv1.begin();
         i != gv1.end(); ++i)
      if (*i == gres)
        do_dup= true;
    if (!do_dup)
      for (BG_geometry_collection::Geometry_list::iterator i= gv2.begin();
           i != gv2.end(); ++i)
        if (*i == gres)
          do_dup= true;

    if (do_dup)
    {
      String tmpres;
      Geometry *gres2= NULL;
      tmpres.append(result->ptr(), result->length());
      const void *data_start= static_cast<const char *>(tmpres.ptr()) +
        GEOM_HEADER_SIZE;

      switch (gres->get_geotype())
      {
      case Geometry::wkb_point:
        gres2= new Gis_point;
        break;
      case Geometry::wkb_linestring:
        gres2= new Gis_line_string;
        break;
      case Geometry::wkb_polygon:
        gres2= new Gis_polygon;
        break;
      case Geometry::wkb_multipoint:
        gres2= new Gis_multi_point;
        break;
      case Geometry::wkb_multilinestring:
        gres2= new Gis_multi_line_string;
        break;
      case Geometry::wkb_multipolygon:
        gres2= new Gis_multi_polygon;
        break;
      default:
        DBUG_ASSERT(false);
      }

      gres2->set_data_ptr(data_start, tmpres.length() - GEOM_HEADER_SIZE);
      gres2->has_geom_header_space(true);
      gres2->set_bg_adapter(false);
      result->takeover(tmpres);
      gres= gres2;
    }

    return gres;
  }


  switch (this->spatial_op)
  {
  case op_intersection:
    gres= geocol_intersection<Coordsys>(bggc1, bggc2, result);
    break;
  case op_union:
    gres= geocol_union<Coordsys>(bggc1, bggc2, result);
    break;
  case op_difference:
    gres= geocol_difference<Coordsys>(bggc1, bggc2, result);
    break;
  case op_symdifference:
    gres= geocol_symdifference<Coordsys>(bggc1, bggc2, result);
    break;
  default:
    /* Only above four supported. */
    DBUG_ASSERT(false);
    break;
  }

  if (gres == NULL && !null_value)
    gres= empty_result(result, g1->get_srid());
  return gres;
}


/**
  Do intersection operation on geometry collections. We do intersection for
  all pairs of components in g1 and g2, put the results in a geometry
  collection. If all subresults can be computed successfully, the geometry
  collection is our result.

  @tparam Coordsys Coordinate system type, specified using those defined in
          boost::geometry::cs.
  @param bggc1 First geometry operand, a geometry collection.
  @param bggc2 Second geometry operand, a geometry collection.
  @param[out] result Holds WKB data of the result, which must be a
                geometry collection.
  @return The intersection result, whose WKB data is stored in 'result'.
 */
template<typename Coordsys>
Geometry *Item_func_spatial_operation::
geocol_intersection(const BG_geometry_collection &bggc1,
                    const BG_geometry_collection &bggc2,
                    String *result)
{
  Geometry *gres= NULL;
  String wkbres;
  Geometry *g0= NULL;
  BG_geometry_collection bggc;
  const BG_geometry_collection::Geometry_list &gv1= bggc1.get_geometries();
  const BG_geometry_collection::Geometry_list &gv2= bggc2.get_geometries();
  bggc.set_srid(bggc1.get_srid());

  if (gv1.size() == 0 || gv2.size() == 0)
  {
    return empty_result(result, bggc1.get_srid());
  }

  const typename BG_geometry_collection::Geometry_list *gv= NULL, *gvr= NULL;

  if (gv1.size() > gv2.size())
  {
    gv= &gv2;
    gvr= &gv1;
  }
  else
  {
    gv= &gv1;
    gvr= &gv2;
  }

  Rtree_index rtree;
  make_rtree(*gvr, &rtree);

  for (BG_geometry_collection::
       Geometry_list::const_iterator i= gv->begin();
       i != gv->end(); ++i)
  {
    BG_box box;
    make_bg_box(*i, &box);

    Rtree_index::const_query_iterator j= rtree.qbegin(bgi::intersects(box));
    if (j == rtree.qend())
      continue;

    for (; j != rtree.qend(); ++j)
    {
      Geometry *geom= (*gvr)[j->second];
      // Free before using it, wkbres may have WKB data from last execution.
      wkbres.mem_free();
      wkbres.length(0);
      g0= bg_geo_set_op<Coordsys>(*i, geom, &wkbres);

      if (null_value)
      {
        if (g0 != NULL && g0 != *i && g0 != geom)
          delete g0;
        return 0;
      }

      if (g0 && !is_empty_geocollection(wkbres))
        bggc.fill(g0);
      if (g0 != NULL && g0 != *i && g0 != geom)
      {
        delete g0;
        g0= NULL;
      }
    }
  }

  /*
    Note: result unify and merge

    The result may have geometry elements that overlap, caused by overlap
    geos in either or both gc1 and/or gc2. Also, there may be geometries
    that can be merged into a larger one of the same type in the result.
    We will need to figure out how to make such enhancements.
   */
  bggc.merge_components<Coordsys>(&null_value);
  if (null_value)
    return NULL;
  gres= bggc.as_geometry_collection(result);

  return gres;
}


/**
  Do union operation on geometry collections. We do union for
  all pairs of components in g1 and g2, whenever a union can be done, we do
  so and put the results in a geometry collection GC and remove the two
  components from g1 and g2 respectively. Finally no components in g1 and g2
  overlap and GC is our result.

  @tparam Coordsys Coordinate system type, specified using those defined in
          boost::geometry::cs.
  @param bggc1 First geometry operand, a geometry collection.
  @param bggc2 Second geometry operand, a geometry collection.
  @param[out] result Holds WKB data of the result, which must be a
                geometry collection.
  @return The union result, whose WKB data is stored in 'result'.
 */
template<typename Coordsys>
Geometry *Item_func_spatial_operation::
geocol_union(const BG_geometry_collection &bggc1,
             const BG_geometry_collection &bggc2,
             String *result)
{
  Geometry *gres= NULL;
  BG_geometry_collection bggc;
  BG_geometry_collection::Geometry_list &gv= bggc.get_geometries();
  gv.insert(gv.end(), bggc1.get_geometries().begin(),
            bggc1.get_geometries().end());
  gv.insert(gv.end(), bggc2.get_geometries().begin(),
            bggc2.get_geometries().end());
  bggc.set_srid(bggc1.get_srid());

  // It's likely that there are overlapping components in bggc because it
  // has components from both bggc1 and bggc2.
  bggc.merge_components<Coordsys>(&null_value);
  if (!null_value)
    gres= bggc.as_geometry_collection(result);

  return gres;
}


/**
  Do difference operation on geometry collections. For each component CX in g1,
  we do CX:= CX difference CY for all components CY in g2. When at last CX isn't
  empty, it's put into result geometry collection GC.
  If all subresults can be computed successfully, the geometry
  collection GC is our result.

  @tparam Coordsys Coordinate system type, specified using those defined in
          boost::geometry::cs.
  @param bggc1 First geometry operand, a geometry collection.
  @param bggc2 Second geometry operand, a geometry collection.
  @param[out] result Holds WKB data of the result, which must be a
                geometry collection.
  @return The difference result, whose WKB data is stored in 'result'.
 */
template<typename Coordsys>
Geometry *Item_func_spatial_operation::
geocol_difference(const BG_geometry_collection &bggc1,
                  const BG_geometry_collection &bggc2,
                  String *result)
{
  Geometry *gres= NULL;
  String *wkbres= NULL;
  BG_geometry_collection bggc;
  const BG_geometry_collection::Geometry_list *gv1= &(bggc1.get_geometries());
  const BG_geometry_collection::Geometry_list *gv2= &(bggc2.get_geometries());

  bggc.set_srid(bggc1.get_srid());

  // Difference isn't symetric so we have to always build rtree tndex on gv2.
  Rtree_index rtree;
  make_rtree(*gv2, &rtree);

  for (BG_geometry_collection::
       Geometry_list::const_iterator i= gv1->begin();
       i != gv1->end(); ++i)
  {
    bool g11_isempty= false;
    auto_ptr<Geometry> guard11;
    Geometry *g11= NULL;
    g11= *i;
    Inplace_vector<String> wkbstrs(PSI_INSTRUMENT_ME);

    BG_box box;
    make_bg_box(*i, &box);

    /*
      Above theory makes sure all results are in rtree_result, the logic
      here is sufficient when rtree_result is empty.
    */
    for (Rtree_index::const_query_iterator
         j= rtree.qbegin(bgi::intersects(box));
         j != rtree.qend(); ++j)
    {
      Geometry *geom= (*gv2)[j->second];

      wkbres= wkbstrs.append_object();
      if (wkbres == NULL)
      {
        null_value= TRUE;
        return NULL;
      }
      Geometry *g0= bg_geo_set_op<Coordsys>(g11, geom, wkbres);
      auto_ptr<Geometry> guard0(g0);

      if (null_value)
      {
        if (!(g0 != NULL && g0 != *i && g0 != geom))
          guard0.release();
        if (!(g11 != NULL && g11 != g0 && g11 != *i && g11 != geom))
          guard11.release();
        return NULL;
      }

      if (g0 != NULL && !is_empty_geocollection(*wkbres))
      {
        if (g11 != NULL && g11 != *i && g11 != geom && g11 != g0)
          delete guard11.release();
        else
          guard11.release();
        guard0.release();
        g11= g0;
        if (g0 != NULL && g0 != *i && g0 != geom)
          guard11.reset(g11);
      }
      else
      {
        g11_isempty= true;
        if (!(g0 != NULL && g0 != *i && g0 != geom && g0 != g11))
          guard0.release();
        break;
      }
    }

    if (!g11_isempty)
      bggc.fill(g11);
    if (!(g11 != NULL && g11 != *i))
      guard11.release();
    else
      guard11.reset(NULL);
  }

  bggc.merge_components<Coordsys>(&null_value);
  if (null_value)
    return NULL;
  gres= bggc.as_geometry_collection(result);

  return gres;
}


/**
  Symmetric difference of geometry collections.

  Symdifference(g1, g2) is implemented as
  union(difference(g1, g2), difference(g2, g1)).

  @tparam Coordsys Coordinate system type, specified using those defined in
          boost::geometry::cs.
  @param bggc1 First geometry operand, a geometry collection.
  @param bggc2 Second geometry operand, a geometry collection.
  @param[out] result Holds WKB data of the result, which must be a
                geometry collection.
  @return The symdifference result, whose WKB data is stored in 'result'.
 */
template<typename Coordsys>
Geometry *Item_func_spatial_operation::
geocol_symdifference(const BG_geometry_collection &bggc1,
                     const BG_geometry_collection &bggc2,
                     String *result)
{
  Geometry *res= NULL;
  auto_ptr<Geometry> diff12(NULL);
  auto_ptr<Geometry> diff21(NULL);
  String diff12_wkb;
  String diff21_wkb;

  Var_resetter<op_type>
    var_reset(&spatial_op, op_symdifference);

  spatial_op= op_difference;
  diff12.reset(geocol_difference<Coordsys>(bggc1, bggc2, &diff12_wkb));
  if (null_value)
    return NULL;
  DBUG_ASSERT(diff12.get() != NULL);

  diff21.reset(geocol_difference<Coordsys>(bggc2, bggc1, &diff21_wkb));
  if (null_value)
    return NULL;
  DBUG_ASSERT(diff21.get() != NULL);

  spatial_op= op_union;
  res= geometry_collection_set_operation<Coordsys>(diff12.get(), diff21.get(),
                                                   result);
  if (diff12.get() == res)
  {
    result->takeover(diff12_wkb);
    diff12.release();
  }
  else if (diff21.get() == res)
  {
    result->takeover(diff21_wkb);
    diff21.release();
  }

  if (null_value)
  {
    if (res != NULL)
      delete res;
    return NULL;
  }

  return res;
}


bool Item_func_spatial_operation::assign_result(Geometry *geo, String *result)
{
  DBUG_ASSERT(geo->has_geom_header_space());
  char *p= geo->get_cptr() - GEOM_HEADER_SIZE;
  write_geometry_header(p, geo->get_srid(), geo->get_geotype());
  result->set(p, GEOM_HEADER_SIZE + geo->get_nbytes(), &my_charset_bin);
  bg_resbuf_mgr.add_buffer(p);
  geo->set_ownmem(false);

  return false;
}


const char *Item_func_spatial_operation::func_name() const
{
  switch (spatial_op) {
    case op_intersection:
      return "st_intersection";
    case op_difference:
      return "st_difference";
    case op_union:
      return "st_union";
    case op_symdifference:
      return "st_symdifference";
    default:
      DBUG_ASSERT(0);  // Should never happen
      return "sp_unknown";
  }
}
