OmniSciDB  72c90bc290
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
RelAlgTranslatorGeo.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2022 HEAVY.AI, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <memory>
18 #include <vector>
19 
20 #include "Geospatial/Compression.h"
21 #include "Geospatial/Types.h"
25 
27 
28 std::vector<std::shared_ptr<Analyzer::Expr>> RelAlgTranslator::translateGeoColumn(
29  const RexInput* rex_input,
30  SQLTypeInfo& ti,
31  const bool with_bounds,
32  const bool expand_geo_col) const {
33  std::vector<std::shared_ptr<Analyzer::Expr>> args;
34  const auto source = rex_input->getSourceNode();
35  const auto it_rte_idx = input_to_nest_level_.find(source);
36  CHECK(it_rte_idx != input_to_nest_level_.end());
37  const int rte_idx = it_rte_idx->second;
38  const auto& in_metainfo = source->getOutputMetainfo();
39 
40  int32_t db_id{0};
41  int32_t table_id{0};
42  int column_id{-1};
43  const Catalog_Namespace::Catalog* catalog{nullptr};
44  const auto scan_source = dynamic_cast<const RelScan*>(source);
45  if (scan_source) {
46  // We're at leaf (scan) level and not supposed to have input metadata,
47  // the name and type information come directly from the catalog.
48  CHECK(in_metainfo.empty());
49 
50  const auto td = scan_source->getTableDescriptor();
51  table_id = td->tableId;
52 
53  catalog = &scan_source->getCatalog();
54  db_id = catalog->getDatabaseId();
55  const auto gcd =
56  catalog->getMetadataForColumnBySpi(table_id, rex_input->getIndex() + 1);
57  CHECK(gcd);
58  ti = gcd->columnType;
59  column_id = gcd->columnId;
60 
61  } else {
62  // Likely backed by a temp table. Read the table ID from the source node and negate it
63  // (see RelAlgTranslator::translateInput)
64  table_id = -source->getId();
65 
66  if (with_bounds) {
67  throw QueryNotSupported(
68  "Geospatial columns not yet supported in intermediate results.");
69  }
70 
71  CHECK(!in_metainfo.empty());
72  CHECK_GE(rte_idx, 0);
73  column_id = rex_input->getIndex();
74  CHECK_LT(static_cast<size_t>(column_id), in_metainfo.size());
75  ti = in_metainfo[column_id].get_type_info();
76  if (expand_geo_col && ti.is_geometry()) {
77  throw QueryNotSupported(
78  "Geospatial columns not yet supported in this temporary table context.");
79  }
80  }
81 
82  if (!IS_GEO(ti.get_type())) {
83  throw QueryNotSupported(
84  "Geospatial expression and operator require geospatial column as their input "
85  "argument(s)");
86  }
87 
88  // Return geo column reference. The geo column may be expanded if required for extension
89  // function arguments. Otherwise, the geo column reference will be translated into
90  // physical columns as required. Bounds column will be added if present and requested.
91  if (expand_geo_col) {
92  for (auto i = 0; i < ti.get_physical_coord_cols(); i++) {
93  CHECK(catalog);
94  const auto pcd = catalog->getMetadataForColumnBySpi(
95  table_id, SPIMAP_GEO_PHYSICAL_INPUT(rex_input->getIndex(), i + 1));
96  auto pcol_ti = pcd->columnType;
97  args.push_back(std::make_shared<Analyzer::ColumnVar>(
98  pcol_ti, shared::ColumnKey{db_id, table_id, pcd->columnId}, rte_idx));
99  }
100  } else {
101  args.push_back(std::make_shared<Analyzer::ColumnVar>(
102  ti, shared::ColumnKey{db_id, table_id, column_id}, rte_idx));
103  }
104  if (with_bounds && ti.has_bounds()) {
105  CHECK(catalog);
106  const auto bounds_cd = catalog->getMetadataForColumnBySpi(
107  table_id,
108  SPIMAP_GEO_PHYSICAL_INPUT(rex_input->getIndex(),
109  ti.get_physical_coord_cols() + 1));
110  auto bounds_ti = bounds_cd->columnType;
111  args.push_back(std::make_shared<Analyzer::ColumnVar>(
112  bounds_ti, shared::ColumnKey{db_id, table_id, bounds_cd->columnId}, rte_idx));
113  }
114  return args;
115 }
116 
117 std::vector<std::shared_ptr<Analyzer::Expr>> RelAlgTranslator::translateGeoLiteral(
118  const RexLiteral* rex_literal,
119  SQLTypeInfo& ti,
120  bool with_bounds) const {
121  CHECK(rex_literal);
122  if (rex_literal->getType() != kTEXT) {
123  throw std::runtime_error("Geo literals must be strings");
124  }
125 
126  // TODO: use geo conversion here
127  const auto e = translateLiteral(rex_literal);
128  auto wkt = std::dynamic_pointer_cast<Analyzer::Constant>(e);
129  CHECK(wkt);
130  std::vector<double> coords;
131  std::vector<double> bounds;
132  std::vector<int> ring_sizes;
133  std::vector<int> poly_rings;
134  int32_t srid = ti.get_output_srid();
135  const bool validate_with_geos_if_available = false;
136  if (!Geospatial::GeoTypesFactory::getGeoColumns(*wkt->get_constval().stringval,
137  ti,
138  coords,
139  bounds,
140  ring_sizes,
141  poly_rings,
142  validate_with_geos_if_available)) {
143  throw QueryNotSupported("Could not read geometry from text");
144  }
146  ti.set_input_srid(srid);
147  ti.set_output_srid(srid);
148  // Compress geo literals by default
149  if (srid == 4326) {
151  ti.set_comp_param(32);
152  }
153 
154  std::vector<std::shared_ptr<Analyzer::Expr>> args;
155 
156  std::vector<uint8_t> compressed_coords = Geospatial::compress_coords(coords, ti);
157  std::list<std::shared_ptr<Analyzer::Expr>> compressed_coords_exprs;
158  for (auto cc : compressed_coords) {
159  Datum d;
160  d.tinyintval = cc;
161  auto e = makeExpr<Analyzer::Constant>(kTINYINT, false, d);
162  compressed_coords_exprs.push_back(e);
163  }
164  SQLTypeInfo arr_ti = SQLTypeInfo(kARRAY, true);
165  arr_ti.set_subtype(kTINYINT);
166  arr_ti.set_size(compressed_coords.size() * sizeof(int8_t));
167  arr_ti.set_compression(ti.get_compression());
168  arr_ti.set_comp_param((ti.get_compression() == kENCODING_GEOINT) ? 32 : 64);
169  args.push_back(makeExpr<Analyzer::Constant>(arr_ti, false, compressed_coords_exprs));
170 
171  auto lit_type = ti.get_type();
172  if (lit_type == kMULTILINESTRING || lit_type == kPOLYGON || lit_type == kMULTIPOLYGON) {
173  // [linest]ring sizes
174  std::list<std::shared_ptr<Analyzer::Expr>> ring_size_exprs;
175  for (auto c : ring_sizes) {
176  Datum d;
177  d.intval = c;
178  auto e = makeExpr<Analyzer::Constant>(kINT, false, d);
179  ring_size_exprs.push_back(e);
180  }
181  SQLTypeInfo arr_ti = SQLTypeInfo(kARRAY, true);
182  arr_ti.set_subtype(kINT);
183  arr_ti.set_size(ring_sizes.size() * sizeof(int32_t));
184  args.push_back(makeExpr<Analyzer::Constant>(arr_ti, false, ring_size_exprs));
185 
186  // poly rings
187  if (lit_type == kMULTIPOLYGON) {
188  std::list<std::shared_ptr<Analyzer::Expr>> poly_rings_exprs;
189  for (auto c : poly_rings) {
190  Datum d;
191  d.intval = c;
192  auto e = makeExpr<Analyzer::Constant>(kINT, false, d);
193  poly_rings_exprs.push_back(e);
194  }
195  SQLTypeInfo arr_ti = SQLTypeInfo(kARRAY, true);
196  arr_ti.set_subtype(kINT);
197  arr_ti.set_size(poly_rings.size() * sizeof(int32_t));
198  args.push_back(makeExpr<Analyzer::Constant>(arr_ti, false, poly_rings_exprs));
199  }
200  }
201 
202  if (with_bounds && ti.has_bounds()) {
203  // bounds
204  std::list<std::shared_ptr<Analyzer::Expr>> bounds_exprs;
205  for (auto b : bounds) {
206  Datum d;
207  d.doubleval = b;
208  auto e = makeExpr<Analyzer::Constant>(kDOUBLE, false, d);
209  bounds_exprs.push_back(e);
210  }
211  SQLTypeInfo arr_ti = SQLTypeInfo(kARRAY, true);
212  arr_ti.set_subtype(kDOUBLE);
213  arr_ti.set_size(bounds.size() * sizeof(double));
214  args.push_back(makeExpr<Analyzer::Constant>(arr_ti, false, bounds_exprs));
215  }
216 
217  return args;
218 }
219 
220 namespace {
221 
222 std::string suffix(SQLTypes type) {
223  if (type == kPOINT) {
224  return std::string("_Point");
225  }
226  if (type == kMULTIPOINT) {
227  return std::string("_MultiPoint");
228  }
229  if (type == kLINESTRING) {
230  return std::string("_LineString");
231  }
232  if (type == kMULTILINESTRING) {
233  return std::string("_MultiLineString");
234  }
235  if (type == kPOLYGON) {
236  return std::string("_Polygon");
237  }
238  if (type == kMULTIPOLYGON) {
239  return std::string("_MultiPolygon");
240  }
241  throw QueryNotSupported("Unsupported argument type");
242 }
243 
245  CHECK(geo);
246  switch (geo->getType()) {
248  return kPOINT;
249  }
251  return kMULTIPOINT;
252  }
254  return kLINESTRING;
255  }
257  return kMULTILINESTRING;
258  }
260  return kPOLYGON;
261  }
263  return kMULTIPOLYGON;
264  }
265  default:
266  UNREACHABLE();
267  return kNULLT;
268  }
269 }
270 
271 } // namespace
272 
273 std::vector<std::shared_ptr<Analyzer::Expr>> RelAlgTranslator::translateGeoFunctionArg(
274  const RexScalar* rex_scalar,
275  SQLTypeInfo& arg_ti,
276  const bool with_bounds,
277  const bool expand_geo_col,
278  const bool is_projection,
279  const bool use_geo_expressions,
280  const bool try_to_compress,
281  const bool allow_gdal_transforms) const {
282  std::vector<std::shared_ptr<Analyzer::Expr>> geoargs;
283 
284  const auto rex_input = dynamic_cast<const RexInput*>(rex_scalar);
285  if (rex_input) {
286  const auto input = translateInput(rex_input);
287  const auto column = dynamic_cast<const Analyzer::ColumnVar*>(input.get());
288  if (!column || !column->get_type_info().is_geometry()) {
289  throw QueryNotSupported("Geo function is expecting a geo column argument");
290  }
291  if (use_geo_expressions) {
292  arg_ti = column->get_type_info();
293  return {makeExpr<Analyzer::GeoColumnVar>(column, with_bounds)};
294  }
295  return translateGeoColumn(rex_input, arg_ti, with_bounds, expand_geo_col);
296  }
297  const auto rex_function = dynamic_cast<const RexFunctionOperator*>(rex_scalar);
298  if (rex_function) {
299  if (rex_function->getName() == "ST_Transform"sv) {
300  CHECK_EQ(size_t(2), rex_function->size());
301  const auto rex_scalar0 =
302  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
303  if (!rex_scalar0) {
304  throw QueryNotSupported(rex_function->getName() + ": unexpected first argument");
305  }
306 
307  const auto rex_literal =
308  dynamic_cast<const RexLiteral*>(rex_function->getOperand(1));
309  if (!rex_literal) {
310  throw QueryNotSupported(rex_function->getName() +
311  ": second argument is expected to be a literal");
312  }
313  const auto e = translateLiteral(rex_literal);
314  auto ce = std::dynamic_pointer_cast<Analyzer::Constant>(e);
315  if (!ce || !e->get_type_info().is_integer()) {
316  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
317  }
318  int32_t srid = 0;
319  if (e->get_type_info().get_type() == kSMALLINT) {
320  srid = static_cast<int32_t>(ce->get_constval().smallintval);
321  } else if (e->get_type_info().get_type() == kTINYINT) {
322  srid = static_cast<int32_t>(ce->get_constval().tinyintval);
323  } else if (e->get_type_info().get_type() == kINT) {
324  srid = static_cast<int32_t>(ce->get_constval().intval);
325  } else {
326  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
327  }
328  bool allow_result_gdal_transform = false;
329  const auto rex_function0 = dynamic_cast<const RexFunctionOperator*>(rex_scalar0);
330  if (rex_function0 && func_resolve(rex_function0->getName(),
331  "ST_Intersection"sv,
332  "ST_Difference"sv,
333  "ST_Union"sv,
334  "ST_Buffer"sv,
335  "ST_ConcaveHull"sv,
336  "ST_ConvexHull"sv)) {
337  // TODO: the design of geo operators currently doesn't allow input srid overrides.
338  // For example, in case of ST_Area(ST_Transform(ST_Buffer(geo_column,0), 900913))
339  // we can ask geos runtime to transform ST_Buffer's output from 4326 to 900913,
340  // however, ST_Area geo operator would still rely on the first arg's typeinfo
341  // to codegen srid arg values in the ST_Area_ extension function call. And it will
342  // still pick up that transform so the coords will be transformed to 900913 twice.
343 
344  // Sink result transform into geos runtime
345  // allow_result_gdal_transform = true;
346  }
347  if (!allow_gdal_transforms && !allow_result_gdal_transform) {
348  if (srid != 900913 && ((use_geo_expressions || is_projection) && srid != 4326 &&
350  throw QueryNotSupported(rex_function->getName() + ": unsupported output SRID " +
351  std::to_string(srid));
352  }
353  }
354  arg_ti.set_output_srid(srid); // Forward output srid down to argument translation
355  bool arg0_use_geo_expressions = is_projection ? true : use_geo_expressions;
356  if (allow_gdal_transforms) {
357  arg0_use_geo_expressions = false;
358  }
359  auto arg0 = translateGeoFunctionArg(rex_scalar0,
360  arg_ti,
361  with_bounds,
362  expand_geo_col,
363  is_projection,
364  arg0_use_geo_expressions);
365 
366  if (use_geo_expressions) {
367  CHECK_EQ(arg0.size(), size_t(1));
368  auto arg0_ti = arg0.front()->get_type_info(); // make a copy so we can override
369  arg0_ti.set_output_srid(srid);
370  if (arg0_ti.get_type() == kPOINT) {
371  // the output type is going to be fully transformed, so set the input srid to
372  // the output srid
373  const auto input_srid = arg0_ti.get_input_srid();
374  arg0_ti.set_input_srid(srid);
375  // geo transforms projections leave the result decompressed in a register
376  arg0_ti.set_compression(kENCODING_NONE);
377  arg0_ti.set_comp_param(0);
378  // reset recursive arg_ti, as the output type of transform will be properly
379  // transformed to the desired SRID
380  arg_ti.set_output_srid(srid);
381  arg_ti.set_input_srid(srid);
382  return {makeExpr<Analyzer::GeoTransformOperator>(
383  arg0_ti, rex_function->getName(), arg0, input_srid, srid)};
384  } else {
385  if (auto geo_constant =
386  std::dynamic_pointer_cast<Analyzer::GeoConstant>(arg0.front())) {
387  // fold transform
388  auto cast_geo_constant = geo_constant->add_cast(arg0_ti);
389  // update return type info
390  arg_ti = cast_geo_constant->get_type_info();
391  return {cast_geo_constant};
392  } else if (auto col_var =
393  std::dynamic_pointer_cast<Analyzer::ColumnVar>(arg0.front())) {
394  const auto& col_ti = col_var->get_type_info();
395  CHECK(col_ti.is_geometry());
396  if (col_ti.get_type() != kPOINT) {
397  arg_ti.set_input_srid(col_ti.get_input_srid());
398  // fall through to transform code below
399  }
400  } else {
401  if (!allow_gdal_transforms && !allow_result_gdal_transform) {
402  throw std::runtime_error(
403  "Transform on non-POINT geospatial types not yet supported in this "
404  "context.");
405  }
406  }
407  }
408  }
409 
410  if (arg_ti.get_input_srid() > 0) {
411  if (!allow_gdal_transforms && !allow_result_gdal_transform) {
412  if (arg_ti.get_input_srid() != 4326) {
413  throw QueryNotSupported(rex_function->getName() +
414  ": unsupported input SRID " +
415  std::to_string(arg_ti.get_input_srid()));
416  }
417  }
418  // Established that the input SRID is valid
419  if (allow_result_gdal_transform) {
420  // If gdal transform has been allowed, then it has been sunk into geos runtime.
421  // The returning geometry has already been transformed, de-register transform.
422  if (arg_ti.get_input_srid() != srid) {
423  arg_ti.set_input_srid(srid);
424  }
425  }
426  arg_ti.set_output_srid(srid);
427  } else {
428  throw QueryNotSupported(rex_function->getName() +
429  ": unexpected input SRID, unable to transform");
430  }
431  return arg0;
432  } else if (func_resolve(
433  rex_function->getName(), "ST_GeomFromText"sv, "ST_GeogFromText"sv)) {
434  CHECK(rex_function->size() == size_t(1) || rex_function->size() == size_t(2));
435  if (use_geo_expressions) {
436  int32_t srid = 0;
437  if (rex_function->size() == 2) {
438  // user supplied srid
439  const auto rex_literal =
440  dynamic_cast<const RexLiteral*>(rex_function->getOperand(1));
441  if (!rex_literal) {
442  throw QueryNotSupported(rex_function->getName() +
443  ": second argument is expected to be a literal");
444  }
445  const auto e = translateLiteral(rex_literal);
446  auto ce = std::dynamic_pointer_cast<Analyzer::Constant>(e);
447  if (!ce || !e->get_type_info().is_integer()) {
448  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
449  }
450  if (e->get_type_info().get_type() == kSMALLINT) {
451  srid = static_cast<int32_t>(ce->get_constval().smallintval);
452  } else if (e->get_type_info().get_type() == kTINYINT) {
453  srid = static_cast<int32_t>(ce->get_constval().tinyintval);
454  } else if (e->get_type_info().get_type() == kINT) {
455  srid = static_cast<int32_t>(ce->get_constval().intval);
456  } else {
457  throw QueryNotSupported(rex_function->getName() + " expecting integer SRID");
458  }
459  if (srid != 0 && srid != 4326 && srid != 900913) {
460  throw QueryNotSupported(rex_function->getName() + ": unsupported SRID " +
461  std::to_string(srid));
462  }
463  }
464  arg_ti.set_input_srid(srid); // Input SRID
465  // leave the output srid unset in case a transform was above us
466 
467  if (rex_function->getName() == "ST_GeogFromText"sv) {
468  arg_ti.set_subtype(kGEOGRAPHY);
469  } else {
470  arg_ti.set_subtype(kGEOMETRY);
471  }
472 
473  auto func_args = translateGeoFunctionArg(rex_function->getOperand(0),
474  arg_ti,
475  with_bounds,
476  expand_geo_col,
478  use_geo_expressions);
479  CHECK_GE(func_args.size(), size_t(1));
480  return func_args;
481  }
482 
483  // First - register srid, then send it to geo literal translation
484  int32_t srid = 0;
485  if (rex_function->size() == 2) {
486  const auto rex_literal =
487  dynamic_cast<const RexLiteral*>(rex_function->getOperand(1));
488  if (!rex_literal) {
489  throw QueryNotSupported(rex_function->getName() +
490  ": second argument is expected to be a literal");
491  }
492  const auto e = translateLiteral(rex_literal);
493  auto ce = std::dynamic_pointer_cast<Analyzer::Constant>(e);
494  if (!ce || !e->get_type_info().is_integer()) {
495  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
496  }
497  if (e->get_type_info().get_type() == kSMALLINT) {
498  srid = static_cast<int32_t>(ce->get_constval().smallintval);
499  } else if (e->get_type_info().get_type() == kTINYINT) {
500  srid = static_cast<int32_t>(ce->get_constval().tinyintval);
501  } else if (e->get_type_info().get_type() == kINT) {
502  srid = static_cast<int32_t>(ce->get_constval().intval);
503  } else {
504  throw QueryNotSupported(rex_function->getName() + " expecting integer SRID");
505  }
506  if (srid != 0 && srid != 4326 && srid != 900913) {
507  throw QueryNotSupported(rex_function->getName() + ": unsupported SRID " +
508  std::to_string(srid));
509  }
510  }
511  arg_ti.set_input_srid(srid); // Input SRID
512  arg_ti.set_output_srid(srid); // Output SRID is the same - no transform
513 
514  const auto rex_literal =
515  dynamic_cast<const RexLiteral*>(rex_function->getOperand(0));
516  if (!rex_literal) {
517  throw QueryNotSupported(rex_function->getName() +
518  " expects a string literal as first argument");
519  }
520  auto arg0 = translateGeoLiteral(rex_literal, arg_ti, with_bounds);
521  arg_ti.set_subtype((rex_function->getName() == "ST_GeogFromText"sv) ? kGEOGRAPHY
522  : kGEOMETRY);
523  return arg0;
524  } else if (rex_function->getName() == "ST_PointN"sv) {
525  // uses geo expressions
526  const auto rex_scalar0 =
527  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
528  if (!rex_scalar0) {
529  throw QueryNotSupported(rex_function->getName() +
530  ": expects scalar as first argument");
531  }
532  auto arg0 = translateGeoFunctionArg(rex_scalar0,
533  arg_ti,
534  with_bounds,
535  expand_geo_col,
536  /*is_projection=*/false,
537  /*use_geo_expressions=*/true);
538  CHECK_EQ(arg0.size(), size_t(1));
539  CHECK(arg0.front());
540  if (arg0.front()->get_type_info().get_type() != kLINESTRING) {
541  throw QueryNotSupported(rex_function->getName() +
542  " expects LINESTRING as first argument");
543  }
544  const auto rex_literal =
545  dynamic_cast<const RexLiteral*>(rex_function->getOperand(1));
546  if (!rex_literal) {
547  throw QueryNotSupported(rex_function->getName() +
548  ": second argument is expected to be a literal");
549  }
550  const auto e = translateLiteral(rex_literal);
551  auto ce = std::dynamic_pointer_cast<Analyzer::Constant>(e);
552  if (!ce || !e->get_type_info().is_integer()) {
553  throw QueryNotSupported(rex_function->getName() +
554  ": expecting integer index as second argument");
555  }
556  int32_t index = 0;
557  if (e->get_type_info().get_type() == kSMALLINT) {
558  index = static_cast<int32_t>(ce->get_constval().smallintval);
559  } else if (e->get_type_info().get_type() == kTINYINT) {
560  index = static_cast<int32_t>(ce->get_constval().tinyintval);
561  } else if (e->get_type_info().get_type() == kINT) {
562  index = static_cast<int32_t>(ce->get_constval().intval);
563  } else {
564  throw QueryNotSupported(rex_function->getName() + " expecting integer index");
565  }
566  if (index == 0) {
567  // maybe we will just return NULL here?
568  throw QueryNotSupported(rex_function->getName() + ": invalid index");
569  }
570  arg0.push_back(e);
571  auto oper_ti =
572  arg0.front()->get_type_info(); // make a copy so we can reset nullness and type
573  oper_ti.set_type(kPOINT);
574  oper_ti.set_notnull(false);
575 
576  arg_ti = oper_ti; // TODO: remove
577 
578  return {makeExpr<Analyzer::GeoOperator>(oper_ti, rex_function->getName(), arg0)};
579 
580  } else if (rex_function->getName() == "ST_StartPoint"sv ||
581  rex_function->getName() == "ST_EndPoint"sv) {
582  std::vector<std::shared_ptr<Analyzer::Expr>> args;
583  CHECK_EQ(size_t(1), rex_function->size());
584  const auto arg_exprs = translateGeoFunctionArg(rex_function->getOperand(0),
585  arg_ti,
586  with_bounds,
587  expand_geo_col,
589  /*use_geo_expressions=*/true);
590  CHECK_EQ(arg_exprs.size(), size_t(1));
591  CHECK(arg_exprs.front());
592  const auto arg_expr_ti = arg_exprs.front()->get_type_info();
593  if (arg_expr_ti.get_type() != kLINESTRING) {
594  throw QueryNotSupported(rex_function->getName() +
595  " expected LINESTRING argument. Received " +
596  arg_expr_ti.toString());
597  }
598  args.push_back(arg_exprs.front());
599 
600  auto oper_ti = args.back()->get_type_info(); // make a copy so we can override type
601  oper_ti.set_type(kPOINT);
602 
603  arg_ti = oper_ti; // TODO: remove
604 
605  return {makeExpr<Analyzer::GeoOperator>(oper_ti, rex_function->getName(), args)};
606  } else if (rex_function->getName() == "ST_SRID"sv) {
607  CHECK_EQ(size_t(1), rex_function->size());
608  const auto rex_scalar0 =
609  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
610  if (!rex_scalar0) {
611  throw QueryNotSupported(rex_function->getName() +
612  ": expects scalar as first argument");
613  }
614  auto arg0 =
615  translateGeoFunctionArg(rex_scalar0, arg_ti, with_bounds, expand_geo_col);
616  if (!IS_GEO(arg_ti.get_type())) {
617  throw QueryNotSupported(rex_function->getName() + " expects geometry argument");
618  }
619  return arg0;
620  } else if (rex_function->getName() == "ST_SetSRID"sv) {
621  CHECK_EQ(size_t(2), rex_function->size());
622  const auto rex_literal =
623  dynamic_cast<const RexLiteral*>(rex_function->getOperand(1));
624  if (!rex_literal) {
625  throw QueryNotSupported(rex_function->getName() +
626  ": second argument is expected to be a literal");
627  }
628  const auto e = translateLiteral(rex_literal);
629  auto ce = std::dynamic_pointer_cast<Analyzer::Constant>(e);
630  if (!ce || !e->get_type_info().is_integer()) {
631  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
632  }
633  int32_t srid = 0;
634  if (e->get_type_info().get_type() == kSMALLINT) {
635  srid = static_cast<int32_t>(ce->get_constval().smallintval);
636  } else if (e->get_type_info().get_type() == kTINYINT) {
637  srid = static_cast<int32_t>(ce->get_constval().tinyintval);
638  } else if (e->get_type_info().get_type() == kINT) {
639  srid = static_cast<int32_t>(ce->get_constval().intval);
640  } else {
641  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
642  }
643 
644  const auto rex_scalar0 =
645  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
646  if (!rex_scalar0) {
647  throw QueryNotSupported(rex_function->getName() +
648  ": expects scalar as first argument");
649  }
650 
651  // Only convey the request to compress if dealing with 4326 geo
652  auto arg0 = translateGeoFunctionArg(rex_scalar0,
653  arg_ti,
654  with_bounds,
655  expand_geo_col,
656  is_projection,
657  use_geo_expressions,
658  (try_to_compress && (srid == 4326)));
659 
660  CHECK(!arg0.empty() && arg0.front());
661  if (!IS_GEO(arg_ti.get_type()) && !use_geo_expressions) {
662  throw QueryNotSupported(rex_function->getName() + " expects geometry argument");
663  }
664  arg_ti.set_input_srid(srid); // Input SRID
665  arg_ti.set_output_srid(srid); // Output SRID is the same - no transform
666  if (auto geo_expr = std::dynamic_pointer_cast<Analyzer::GeoExpr>(arg0.front())) {
667  CHECK_EQ(arg0.size(), size_t(1));
668  auto ti = geo_expr->get_type_info();
669  ti.set_input_srid(srid);
670  ti.set_output_srid(srid);
671  return {geo_expr->add_cast(ti)};
672  }
673  return arg0;
674  } else if (rex_function->getName() == "CastToGeography"sv) {
675  CHECK_EQ(size_t(1), rex_function->size());
676  const auto rex_scalar0 =
677  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
678  if (!rex_scalar0) {
679  throw QueryNotSupported(rex_function->getName() +
680  ": expects scalar as first argument");
681  }
682  auto arg0 = translateGeoFunctionArg(rex_scalar0,
683  arg_ti,
684  with_bounds,
685  expand_geo_col,
686  /*is_projection=*/false,
687  use_geo_expressions);
688  CHECK(!arg0.empty());
689  if (auto geo_expr = std::dynamic_pointer_cast<Analyzer::GeoExpr>(arg0.front())) {
690  auto arg_ti = geo_expr->get_type_info(); // make a copy
691  arg_ti.set_subtype(kGEOGRAPHY);
692  return {geo_expr->add_cast(arg_ti)};
693  }
694  if (use_geo_expressions) {
695  arg_ti = arg0.front()->get_type_info();
696  arg_ti.set_subtype(kGEOGRAPHY);
697  arg0.front()->set_type_info(arg_ti);
698  }
699  if (!IS_GEO(arg_ti.get_type())) {
700  throw QueryNotSupported(rex_function->getName() + " expects geometry argument");
701  }
702  if (arg_ti.get_output_srid() != 4326) {
703  throw QueryNotSupported(rex_function->getName() +
704  " expects geometry with SRID=4326");
705  }
706  arg_ti.set_subtype(kGEOGRAPHY);
707  return arg0;
708  } else if (rex_function->getName() == "ST_Point"sv) {
709  CHECK_EQ(size_t(2), rex_function->size());
710  arg_ti.set_type(kPOINT);
711  arg_ti.set_subtype(kGEOMETRY);
712  arg_ti.set_input_srid(0);
713  arg_ti.set_output_srid(0);
715 
716  auto coord1 = translateScalarRex(rex_function->getOperand(0));
717  auto coord2 = translateScalarRex(rex_function->getOperand(1));
718  auto d_ti = SQLTypeInfo(kDOUBLE, false);
719  auto cast_coord1 = coord1->add_cast(d_ti);
720  auto cast_coord2 = coord2->add_cast(d_ti);
721  // First try to fold to geo literal
722  auto folded_coord1 = fold_expr(cast_coord1.get());
723  auto folded_coord2 = fold_expr(cast_coord2.get());
724  auto const_coord1 = std::dynamic_pointer_cast<Analyzer::Constant>(folded_coord1);
725  auto const_coord2 = std::dynamic_pointer_cast<Analyzer::Constant>(folded_coord2);
726  if (const_coord1 && const_coord2 && !use_geo_expressions) {
727  CHECK(const_coord1->get_type_info().get_type() == kDOUBLE);
728  CHECK(const_coord2->get_type_info().get_type() == kDOUBLE);
729  std::string wkt = "POINT(" +
730  std::to_string(const_coord1->get_constval().doubleval) + " " +
731  std::to_string(const_coord2->get_constval().doubleval) + ")";
732  RexLiteral rex_literal{wkt, kTEXT, kNULLT, 0, 0, 0, 0};
733  auto args = translateGeoLiteral(&rex_literal, arg_ti, false);
734  CHECK(arg_ti.get_type() == kPOINT);
735  return args;
736  }
737  const auto is_local_alloca = !is_projection;
738  if (!is_local_alloca || use_geo_expressions) {
739  if (try_to_compress) {
740  arg_ti.set_input_srid(4326);
741  arg_ti.set_output_srid(4326);
742  }
743  return {makeExpr<Analyzer::GeoOperator>(
744  arg_ti,
745  rex_function->getName(),
746  std::vector<std::shared_ptr<Analyzer::Expr>>{folded_coord1, folded_coord2})};
747  }
748  // Couldn't fold to geo literal, construct [and compress] on the fly
749  auto da_ti = SQLTypeInfo(kARRAY, true);
750  da_ti.set_subtype(kDOUBLE);
751  da_ti.set_size(16);
752  if (try_to_compress) {
753  // Switch to compressed coord array
754  da_ti.set_subtype(kINT);
755  da_ti.set_size(8);
756  da_ti.set_input_srid(4326);
757  da_ti.set_output_srid(4326);
758  da_ti.set_compression(kENCODING_GEOINT);
759  da_ti.set_comp_param(32);
760  // Register point compression
761  arg_ti.set_input_srid(4326);
762  arg_ti.set_output_srid(4326);
764  arg_ti.set_comp_param(32);
765  }
766  auto cast_coords = {folded_coord1, folded_coord2};
767  auto ae = makeExpr<Analyzer::ArrayExpr>(da_ti, cast_coords, false, is_local_alloca);
768  SQLTypeInfo tia_ti = da_ti;
769  tia_ti.set_subtype(kTINYINT);
770  return {makeExpr<Analyzer::UOper>(tia_ti, false, kCAST, ae)};
771  } else if (rex_function->getName() == "ST_Centroid"sv) {
772  CHECK_EQ(size_t(1), rex_function->size());
773  arg_ti.set_type(kPOINT);
774  arg_ti.set_subtype(kGEOMETRY);
775  arg_ti.set_input_srid(0);
776  arg_ti.set_output_srid(0);
778 
779  SQLTypeInfo geo_ti;
780  int legacy_transform_srid = 0; // discard
781  auto geoargs = translateGeoFunctionArg(rex_function->getOperand(0),
782  geo_ti,
783  /*with_bounds=*/false,
784  /*expand_geo_col=*/true,
785  /*is_projection=*/false,
786  /*use_geo_expressions=*/true);
787  CHECK_EQ(geoargs.size(), size_t(1));
788  if (geo_ti.get_output_srid() > 0) {
789  // Pick up the arg's srid
790  arg_ti.set_input_srid(geo_ti.get_output_srid());
791  arg_ti.set_output_srid(geo_ti.get_output_srid());
792  }
793  if (try_to_compress) {
794  // Point compression is requested by a higher level [4326] operation
795  if (geo_ti.get_output_srid() == 0) {
796  // srid-less geo is considered and is forced to be 4326
797  arg_ti.set_input_srid(4326);
798  arg_ti.set_output_srid(4326);
799  } else {
800  CHECK_EQ(arg_ti.get_output_srid(), 4326);
801  }
803  arg_ti.set_comp_param(32);
804  }
805  if (geo_ti.get_input_srid() != geo_ti.get_output_srid() &&
806  geo_ti.get_output_srid() > 0 &&
807  std::dynamic_pointer_cast<Analyzer::ColumnVar>(geoargs.front())) {
808  // Centroid argument is transformed before use,
809  // pass the transform to the geo operator
810  legacy_transform_srid = geo_ti.get_output_srid();
811  }
812  return {makeExpr<Analyzer::GeoOperator>(
813  arg_ti,
814  rex_function->getName(),
815  std::vector<std::shared_ptr<Analyzer::Expr>>{geoargs.front()},
816  legacy_transform_srid > 0 ? std::make_optional<int>(legacy_transform_srid)
817  : std::nullopt)};
818  } else if (func_resolve(rex_function->getName(), "ST_ConvexHull"sv)) {
819  CHECK_EQ(size_t(1), rex_function->size());
820  // What geo type will the constructor return? Could be anything.
821  return {translateUnaryGeoConstructor(rex_function, arg_ti, with_bounds)};
822  } else if (func_resolve(rex_function->getName(),
823  "ST_Intersection"sv,
824  "ST_Difference"sv,
825  "ST_Union"sv,
826  "ST_Buffer"sv,
827  "ST_ConcaveHull"sv)) {
828  CHECK_EQ(size_t(2), rex_function->size());
829  // What geo type will the constructor return? Could be anything.
830  return {translateBinaryGeoConstructor(rex_function, arg_ti, with_bounds)};
831  } else if (func_resolve(rex_function->getName(), "ST_IsEmpty"sv, "ST_IsValid"sv)) {
832  CHECK_EQ(size_t(1), rex_function->size());
833  return {translateUnaryGeoPredicate(rex_function, arg_ti, with_bounds)};
834  } else if (func_resolve(rex_function->getName(), "ST_Equals"sv)) {
835  CHECK_EQ(size_t(2), rex_function->size());
836  return {translateBinaryGeoPredicate(rex_function, arg_ti, with_bounds)};
837  } else {
838  throw QueryNotSupported("Unsupported argument: " + rex_function->getName());
839  }
840  }
841  const auto rex_literal = dynamic_cast<const RexLiteral*>(rex_scalar);
842  if (rex_literal) {
843  if (use_geo_expressions) {
844  const auto translated_literal = translateLiteral(rex_literal);
845  auto const translated_literal_type = translated_literal->get_type_info().get_type();
846  if (!IS_STRING(translated_literal_type) && !IS_GEO(translated_literal_type)) {
847  // This stops crashes in the createGeoType call below due to datum.stringval
848  // being uninitialized when the datum isn't even a string, let alone a geo string
849  // There needs to be specific handling for ST_NumGeometries in the code above
850  // but I don't know what category it would fall over (it's not GEOS, and it
851  // returns an INT, not a BOOL or other geo)
852  // simon.eves 8/15/22
853  throw QueryNotSupported("Geospatial function requires geo literal.");
854  }
855  const auto constant_expr =
856  dynamic_cast<const Analyzer::Constant*>(translated_literal.get());
857  CHECK(constant_expr);
858  if (constant_expr->get_is_null()) {
859  // TODO: we could lift this limitation by assuming a minimum type per function
860  throw QueryNotSupported("Geospatial functions require typed nulls.");
861  }
862  const auto& datum = constant_expr->get_constval();
863  CHECK(datum.stringval);
864  const bool validate_with_geos_if_available = false;
865  auto geospatial_base = Geospatial::GeoTypesFactory::createGeoType(
866  *datum.stringval, validate_with_geos_if_available);
867  CHECK(geospatial_base);
868  SQLTypeInfo ti;
869  ti.set_type(get_ti_from_geo(geospatial_base.get()));
870  if (arg_ti.get_subtype() == kGEOGRAPHY) {
872  } else {
874  }
875  ti.set_input_srid(arg_ti.get_input_srid());
876  ti.set_output_srid(arg_ti.get_output_srid() == 0 ? arg_ti.get_input_srid()
877  : arg_ti.get_output_srid());
878  // TODO: remove dependence on arg_ti
879  if (ti.get_output_srid() == 4326 || arg_ti.get_compression() == kENCODING_GEOINT) {
881  ti.set_comp_param(32);
882  }
883  ti.set_notnull(true);
884  // Before removing dependence on arg_ti need to note that ST_Transform uses it
885  // as a vehicle to pass transform output SRID to its args.
886  // arg_ti is also expected to be filled with relevant data, which wasn't done here.
887  // Not filling arg_ti with the geo constant data (which went to ti instead)
888  // resulted in GeoConstant::add_cast adopting a corrupt type info,
889  // which later killed codegen. Need to complete arg_ti composition:
890  arg_ti = ti;
891  return {makeExpr<Analyzer::GeoConstant>(std::move(geospatial_base), ti)};
892  }
893  return translateGeoLiteral(rex_literal, arg_ti, with_bounds);
894  }
895  throw QueryNotSupported("Geo function argument not supported");
896 }
897 
898 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateGeoProjection(
899  const RexFunctionOperator* rex_function,
900  SQLTypeInfo& ti,
901  const bool with_bounds) const {
902  // note that this is a bit of a misnomer, as ST_SetSRID embedded in a transform will
903  // eventually use geo expressions -- just not here
904  const bool use_geo_projections = !(rex_function->getName() == "ST_GeomFromText" ||
905  rex_function->getName() == "ST_GeogFromText" ||
906  rex_function->getName() == "ST_SetSRID");
907  auto geoargs = translateGeoFunctionArg(rex_function,
908  ti,
909  /*with_bounds=*/false,
910  /*expand_geo_col=*/true,
911  /*is_projection=*/true,
912  /*use_geo_expressions=*/use_geo_projections);
913  CHECK(!geoargs.empty());
914  if (std::dynamic_pointer_cast<const Analyzer::GeoExpr>(geoargs.front()) &&
915  !geoargs.front()->get_type_info().is_array()) {
916  if (rex_function->getName() == "ST_Transform" &&
917  std::dynamic_pointer_cast<const Analyzer::GeoConstant>(geoargs.front())) {
918  return makeExpr<Analyzer::GeoUOper>(
919  Geospatial::GeoBase::GeoOp::kPROJECTION, ti, ti, geoargs);
920  }
921  // GeoExpression
922  return geoargs.front();
923  }
924  bool allow_gdal_transform = false;
925  if (rex_function->getName() == "ST_Transform") {
926  const auto rex_scalar0 = dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
927  const auto rex_function0 = dynamic_cast<const RexFunctionOperator*>(rex_scalar0);
928  if (rex_function0 && func_resolve(rex_function0->getName(),
929  "ST_Intersection"sv,
930  "ST_Difference"sv,
931  "ST_Union"sv,
932  "ST_Buffer"sv,
933  "ST_ConcaveHull"sv,
934  "ST_ConvexHull"sv)) {
935  // Allow projection of gdal-transformed geos outputs
936  allow_gdal_transform = true;
937  }
938  }
939  if (use_geo_projections && !allow_gdal_transform) {
940  throw std::runtime_error("Geospatial projection for function " +
941  rex_function->toString(RelRexToStringConfig::defaults()) +
942  " not yet supported in this context");
943  }
944  return makeExpr<Analyzer::GeoUOper>(
945  Geospatial::GeoBase::GeoOp::kPROJECTION, ti, ti, geoargs);
946 }
947 
948 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateBinaryGeoConstructor(
949  const RexFunctionOperator* rex_function,
950  SQLTypeInfo& ti,
951  const bool with_bounds) const {
952 #ifndef ENABLE_GEOS
953  throw QueryNotSupported(rex_function->getName() +
954  " geo constructor requires enabled GEOS support");
955 #endif
957  if (rex_function->getName() == "ST_Difference"sv) {
959  } else if (rex_function->getName() == "ST_Union"sv) {
961  } else if (rex_function->getName() == "ST_Buffer"sv) {
963  } else if (rex_function->getName() == "ST_ConcaveHull"sv) {
965  }
966 
969  SQLTypeInfo arg0_ti;
970  SQLTypeInfo arg1_ti;
971  if (func_resolve(rex_function->getName(),
972  "ST_Intersection"sv,
973  "ST_Difference"sv,
974  "ST_Union"sv,
975  "ST_Buffer"sv,
976  "ST_ConcaveHull"sv)) {
977  // First arg: geometry
978  geoargs0 = translateGeoFunctionArg(rex_function->getOperand(0),
979  arg0_ti,
980  false,
981  true,
982  true,
983  false,
984  false,
985  /* allow_gdal_transforms = */ true);
986  }
987  if (func_resolve(rex_function->getName(),
988  "ST_Intersection"sv,
989  "ST_Difference"sv,
990  "ST_Union"sv)) {
991  // Second arg: geometry
992  geoargs1 = translateGeoFunctionArg(rex_function->getOperand(1),
993  arg1_ti,
994  false,
995  true,
996  true,
997  false,
998  false,
999  /* allow_gdal_transforms = */ true);
1000  if (arg0_ti.get_output_srid() != arg1_ti.get_output_srid()) {
1001  throw QueryNotSupported(rex_function->getName() +
1002  " geo constructor requires arguments with matching srids");
1003  }
1004  } else if (func_resolve(rex_function->getName(), "ST_Buffer"sv, "ST_ConcaveHull"sv)) {
1005  // Second arg: double scalar
1006  auto param_expr = translateScalarRex(rex_function->getOperand(1));
1007  arg1_ti = SQLTypeInfo(kDOUBLE, false);
1008  if (param_expr->get_type_info().get_type() != kDOUBLE) {
1009  param_expr = param_expr->add_cast(arg1_ti);
1010  }
1011  geoargs1 = {param_expr};
1012  }
1013 
1014  // Record the optional transform request that can be sent by an ecompassing TRANSFORM
1015  auto srid = ti.get_output_srid();
1016  // Build the typeinfo of the constructed geometry
1017  SQLTypeInfo arg_ti = arg0_ti;
1018  arg_ti.set_type(kMULTIPOLYGON);
1019  arg_ti.set_subtype(kGEOMETRY);
1020  arg_ti.set_compression(kENCODING_NONE); // Constructed geometries are not compressed
1021  arg_ti.set_comp_param(0);
1022  arg_ti.set_input_srid(arg0_ti.get_output_srid());
1023  if (srid > 0) {
1024  if (arg_ti.get_input_srid() > 0) {
1025  // Constructed geometry to be transformed to srid given by encompassing transform
1026  arg_ti.set_output_srid(srid);
1027  } else {
1028  throw QueryNotSupported("Transform of geo constructor " + rex_function->getName() +
1029  " requires its argument(s) to have a valid srid");
1030  }
1031  } else {
1032  arg_ti.set_output_srid(arg_ti.get_input_srid()); // No encompassing transform
1033  }
1034  // If there was an output transform, it's now embedded into arg_ti and the geo operator.
1035  // Now de-register the transform from the return typeinfo:
1036  ti = arg_ti;
1038  return makeExpr<Analyzer::GeoBinOper>(op, arg_ti, arg0_ti, arg1_ti, geoargs0, geoargs1);
1039 }
1040 
1041 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateUnaryGeoPredicate(
1042  const RexFunctionOperator* rex_function,
1043  SQLTypeInfo& ti,
1044  const bool with_bounds) const {
1045 #ifndef ENABLE_GEOS
1046  throw QueryNotSupported(rex_function->getName() +
1047  " geo predicate requires enabled GEOS support");
1048 #endif
1049  SQLTypeInfo arg_ti;
1050  auto geoargs =
1051  translateGeoFunctionArg(rex_function->getOperand(0), arg_ti, false, true, true);
1052  ti = SQLTypeInfo(kBOOLEAN, false);
1053  auto op = (rex_function->getName() == "ST_IsEmpty"sv)
1056  return makeExpr<Analyzer::GeoUOper>(op, ti, arg_ti, geoargs);
1057 }
1058 
1059 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateBinaryGeoPredicate(
1060  const RexFunctionOperator* rex_function,
1061  SQLTypeInfo& ti,
1062  const bool with_bounds) const {
1063  if (rex_function->getName() != "ST_Equals"sv) {
1064  throw QueryNotSupported(rex_function->getName() + " geo predicate is not supported");
1065  }
1066 #ifndef ENABLE_GEOS
1067  throw QueryNotSupported(rex_function->getName() +
1068  " geo predicate requires enabled GEOS support");
1069 #endif
1070  SQLTypeInfo arg0_ti;
1071  auto geoargs0 =
1072  translateGeoFunctionArg(rex_function->getOperand(0), arg0_ti, false, true, true);
1073  SQLTypeInfo arg1_ti;
1074  auto geoargs1 =
1075  translateGeoFunctionArg(rex_function->getOperand(1), arg1_ti, false, true, true);
1076  ti = SQLTypeInfo(kBOOLEAN, false);
1078  return makeExpr<Analyzer::GeoBinOper>(op, ti, arg0_ti, arg1_ti, geoargs0, geoargs1);
1079 }
1080 
1081 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateUnaryGeoConstructor(
1082  const RexFunctionOperator* rex_function,
1083  SQLTypeInfo& ti,
1084  const bool with_bounds) const {
1085 #ifndef ENABLE_GEOS
1086  throw QueryNotSupported(rex_function->getName() +
1087  " geo constructor requires enabled GEOS support");
1088 #endif
1090 
1091  Analyzer::ExpressionPtrVector geoargs0{};
1092  SQLTypeInfo arg0_ti;
1093  if (func_resolve(rex_function->getName(), "ST_ConvexHull"sv)) {
1094  // First arg: geometry
1095  geoargs0 = translateGeoFunctionArg(rex_function->getOperand(0),
1096  arg0_ti,
1097  false,
1098  true,
1099  true,
1100  false,
1101  false,
1102  /* allow_gdal_transforms = */ true);
1103  }
1104 
1105  // Record the optional transform request that can be sent by an ecompassing TRANSFORM
1106  auto srid = ti.get_output_srid();
1107  // Build the typeinfo of the constructed geometry
1108  SQLTypeInfo arg_ti = arg0_ti;
1109  arg_ti.set_type(kMULTIPOLYGON);
1110  arg_ti.set_subtype(kGEOMETRY);
1111  arg_ti.set_compression(kENCODING_NONE); // Constructed geometries are not compressed
1112  arg_ti.set_comp_param(0);
1113  arg_ti.set_input_srid(arg0_ti.get_output_srid());
1114  if (srid > 0) {
1115  if (arg_ti.get_input_srid() > 0) {
1116  // Constructed geometry to be transformed to srid given by encompassing transform
1117  arg_ti.set_output_srid(srid);
1118  } else {
1119  throw QueryNotSupported("Transform of geo constructor " + rex_function->getName() +
1120  " requires its argument(s) to have a valid srid");
1121  }
1122  } else {
1123  arg_ti.set_output_srid(arg_ti.get_input_srid()); // No encompassing transform
1124  }
1125  // If there was an output transform, it's now embedded into arg_ti and the geo operator.
1126  // Now de-register the transform from the return typeinfo:
1127  ti = arg_ti;
1129  return makeExpr<Analyzer::GeoUOper>(op, arg_ti, arg0_ti, geoargs0);
1130 }
1131 
1132 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateUnaryGeoFunction(
1133  const RexFunctionOperator* rex_function) const {
1134  CHECK_EQ(size_t(1), rex_function->size());
1135 
1136  std::string specialized_geofunc{rex_function->getName()};
1137 
1138  // Geo function calls which do not need the coords col but do need cols associated
1139  // with physical coords (e.g. ring_sizes / poly_rings)
1140  if (rex_function->getName() == "ST_NRings"sv) {
1141  SQLTypeInfo arg_ti;
1142  auto geoargs = translateGeoFunctionArg(rex_function->getOperand(0),
1143  arg_ti,
1144  /*with_bounds=*/false,
1145  /*expand_geo_col=*/true,
1146  /*is_projection=*/false,
1147  /*use_geo_expressions=*/true);
1148  if (!IS_GEO_POLY(arg_ti.get_type())) {
1149  throw QueryNotSupported(rex_function->getName() +
1150  " expects a POLYGON or MULTIPOLYGON");
1151  }
1152  CHECK_EQ(geoargs.size(), size_t(1));
1153  arg_ti = rex_function->getType(); // TODO: remove
1154  return makeExpr<Analyzer::GeoOperator>(
1155  rex_function->getType(),
1156  rex_function->getName(),
1157  std::vector<std::shared_ptr<Analyzer::Expr>>{geoargs.front()});
1158  } else if (rex_function->getName() == "ST_NumGeometries"sv) {
1159  SQLTypeInfo arg_ti;
1160  auto geoargs = translateGeoFunctionArg(rex_function->getOperand(0),
1161  arg_ti,
1162  /*with_bounds=*/false,
1163  /*expand_geo_col=*/true,
1164  /*is_projection=*/false,
1165  /*use_geo_expressions=*/true);
1166  if (!IS_GEO(arg_ti.get_type())) {
1167  throw QueryNotSupported(rex_function->getName() + " expects a geo parameter");
1168  }
1169  CHECK_EQ(geoargs.size(), size_t(1));
1170  arg_ti = rex_function->getType(); // TODO: remove
1171  return makeExpr<Analyzer::GeoOperator>(
1172  rex_function->getType(),
1173  rex_function->getName(),
1174  std::vector<std::shared_ptr<Analyzer::Expr>>{geoargs.front()});
1175  } else if (rex_function->getName() == "ST_NPoints"sv) {
1176  SQLTypeInfo arg_ti;
1177  auto geoargs = translateGeoFunctionArg(rex_function->getOperand(0),
1178  arg_ti,
1179  /*with_bounds=*/false,
1180  /*expand_geo_col=*/true,
1181  /*is_projection=*/false,
1182  /*use_geo_expressions=*/true);
1183  CHECK_EQ(geoargs.size(), size_t(1));
1184  return makeExpr<Analyzer::GeoOperator>(
1185  rex_function->getType(),
1186  rex_function->getName(),
1187  std::vector<std::shared_ptr<Analyzer::Expr>>{geoargs.front()});
1188  } else if (func_resolve(rex_function->getName(), "ST_Perimeter"sv, "ST_Area"sv)) {
1189  SQLTypeInfo arg_ti;
1190  int legacy_transform_srid = 0; // discard
1191  auto geoargs = translateGeoFunctionArg(rex_function->getOperand(0),
1192  arg_ti,
1193  /*with_bounds=*/false,
1194  /*expand_geo_col=*/true,
1195  /*is_projection=*/false,
1196  /*use_geo_expressions=*/true);
1197  CHECK_EQ(geoargs.size(), size_t(1));
1198  if (arg_ti.get_input_srid() != arg_ti.get_output_srid() &&
1199  arg_ti.get_output_srid() > 0 &&
1200  std::dynamic_pointer_cast<Analyzer::ColumnVar>(geoargs.front())) {
1201  // legacy transform
1202  legacy_transform_srid = arg_ti.get_output_srid();
1203  // Reset the transform, transform will be given to the operator as an override
1204  arg_ti = geoargs.front()->get_type_info();
1205  }
1206  if (!IS_GEO_POLY(arg_ti.get_type())) {
1207  throw QueryNotSupported(rex_function->getName() +
1208  " expects a POLYGON or MULTIPOLYGON");
1209  }
1210  return makeExpr<Analyzer::GeoOperator>(
1211  rex_function->getType(),
1212  rex_function->getName(),
1213  std::vector<std::shared_ptr<Analyzer::Expr>>{geoargs.front()},
1214  legacy_transform_srid > 0 ? std::make_optional<int>(legacy_transform_srid)
1215  : std::nullopt);
1216  }
1217 
1218  // Accessor for poly bounds for in-situ poly render queries
1219  if (func_resolve(rex_function->getName(), "HeavyDB_Geo_PolyBoundsPtr"sv)) {
1220  SQLTypeInfo arg_ti;
1221  // get geo column plus bounds only (not expanded)
1222  auto geoargs =
1223  translateGeoFunctionArg(rex_function->getOperand(0), arg_ti, true, false, false);
1224  // this function only works on polys
1225  if (!IS_GEO_POLY(arg_ti.get_type())) {
1226  throw QueryNotSupported(rex_function->getName() +
1227  " expects a POLYGON or MULTIPOLYGON");
1228  }
1229  // only need the bounds argument (last), discard the rest
1230  geoargs.erase(geoargs.begin(), geoargs.end() - 1);
1231  // done
1232  return makeExpr<Analyzer::FunctionOper>(
1233  rex_function->getType(), specialized_geofunc, geoargs);
1234  }
1235 
1236  // start to move geo expressions above the generic translation call, as geo expression
1237  // error handling can differ
1238  if (func_resolve(rex_function->getName(), "ST_X"sv, "ST_Y"sv)) {
1239  SQLTypeInfo arg_ti;
1240  auto new_geoargs = translateGeoFunctionArg(rex_function->getOperand(0),
1241  arg_ti,
1242  /*with_bounds=*/false,
1243  /*expand_geo_col=*/true,
1244  /*is_projection=*/true,
1245  /*use_geo_expressions=*/true);
1246  CHECK_EQ(new_geoargs.size(), size_t(1));
1247  CHECK(new_geoargs.front());
1248  const auto& arg_expr_ti = new_geoargs.front()->get_type_info();
1249  if (arg_expr_ti.get_type() != kPOINT) {
1250  throw QueryNotSupported(rex_function->getName() + " expects a POINT");
1251  }
1252  auto function_ti = rex_function->getType();
1253  if (std::dynamic_pointer_cast<Analyzer::GeoOperator>(new_geoargs.front())) {
1254  function_ti.set_notnull(false);
1255  }
1256  if (std::dynamic_pointer_cast<Analyzer::GeoConstant>(new_geoargs.front())) {
1257  // TODO(adb): fixup null handling
1258  function_ti.set_notnull(true);
1259  }
1260  return makeExpr<Analyzer::GeoOperator>(
1261  function_ti,
1262  rex_function->getName(),
1263  std::vector<std::shared_ptr<Analyzer::Expr>>{new_geoargs.front()});
1264  }
1265 
1266  // All functions below use geo col as reference and expand it as necessary
1267  SQLTypeInfo arg_ti;
1268  bool with_bounds = true;
1269  auto geoargs =
1270  translateGeoFunctionArg(rex_function->getOperand(0), arg_ti, with_bounds, false);
1271 
1272  if (rex_function->getName() == "ST_SRID"sv) {
1273  Datum output_srid;
1274  output_srid.intval = arg_ti.get_output_srid();
1275  return makeExpr<Analyzer::Constant>(kINT, false, output_srid);
1276  }
1277 
1278  if (func_resolve(
1279  rex_function->getName(), "ST_XMin"sv, "ST_YMin"sv, "ST_XMax"sv, "ST_YMax"sv)) {
1280  // If type has bounds - use them, otherwise look at coords
1281  if (arg_ti.has_bounds()) {
1282  // Only need the bounds argument, discard the rest
1283  geoargs.erase(geoargs.begin(), geoargs.end() - 1);
1284 
1285  // Supply srids too - transformed geo would have a transformed bounding box
1286  Datum input_srid;
1287  input_srid.intval = arg_ti.get_input_srid();
1288  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid));
1289  Datum output_srid;
1290  output_srid.intval = arg_ti.get_output_srid();
1291  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
1292 
1293  specialized_geofunc += "_Bounds"s;
1294  return makeExpr<Analyzer::FunctionOper>(
1295  rex_function->getType(), specialized_geofunc, geoargs);
1296  }
1297  }
1298 
1299  // Unless overriden, function is assumed to be interested in the first geoarg only,
1300  // which may be a geo object (e.g. geo column), or a coord array (e.g. geo literal)
1301  auto discard_after_arg = 1;
1302 
1303  if (rex_function->getName() == "ST_Length"sv) {
1304  if (arg_ti.get_type() != kLINESTRING && arg_ti.get_type() != kMULTILINESTRING) {
1305  throw QueryNotSupported(rex_function->getName() +
1306  " expects LINESTRING or MULTILINESTRING");
1307  }
1308  if (arg_ti.get_type() == kMULTILINESTRING) {
1309  auto ti0 = geoargs[0]->get_type_info();
1310  if (ti0.get_type() == kARRAY && ti0.get_subtype() == kTINYINT) {
1311  // Received expanded geo: widen the reach to grab linestring size array as well
1312  discard_after_arg = 2;
1313  }
1314  }
1315  specialized_geofunc += suffix(arg_ti.get_type());
1316  if (arg_ti.get_subtype() == kGEOGRAPHY && arg_ti.get_output_srid() == 4326) {
1317  if (arg_ti.get_type() == kMULTILINESTRING) {
1318  throw QueryNotSupported(rex_function->getName() +
1319  " Geodesic is not supported for MULTILINESTRING");
1320  }
1321  specialized_geofunc += "_Geodesic"s;
1322  }
1323  }
1324 
1325  geoargs.erase(geoargs.begin() + discard_after_arg, geoargs.end());
1326 
1327  // Add input compression mode and SRID args to enable on-the-fly
1328  // decompression/transforms
1329  Datum input_compression;
1330  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
1331  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
1332  Datum input_srid;
1333  input_srid.intval = arg_ti.get_input_srid();
1334  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid));
1335 
1336  // Add output SRID arg to enable on-the-fly transforms
1337  Datum output_srid;
1338  output_srid.intval = arg_ti.get_output_srid();
1339  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
1340 
1341  return makeExpr<Analyzer::FunctionOper>(
1342  rex_function->getType(), specialized_geofunc, geoargs);
1343 }
1344 
1345 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateBinaryGeoFunction(
1346  const RexFunctionOperator* rex_function) const {
1347  auto function_name = rex_function->getName();
1348  auto return_type = rex_function->getType();
1349 
1350  if (function_name == "ST_IntersectsBox"sv) {
1351  // Bounding box intersection is the only implementation supported for now, only
1352  // translate bounds
1353  CHECK_EQ(size_t(2), rex_function->size());
1354  auto extract_geo_bounds_from_input =
1355  [this, &rex_function](const size_t index) -> std::shared_ptr<Analyzer::Expr> {
1356  const auto rex_input =
1357  dynamic_cast<const RexInput*>(rex_function->getOperand(index));
1358  if (rex_input) {
1359  SQLTypeInfo ti;
1360  const auto exprs = translateGeoColumn(rex_input, ti, true, false);
1361  CHECK_GT(exprs.size(), size_t(0));
1362  if (ti.get_type() == kPOINT) {
1363  throw std::runtime_error(
1364  "ST_IntersectsBox is not supported for point arguments.");
1365  } else {
1366  return exprs.back();
1367  }
1368  } else {
1369  throw std::runtime_error(
1370  "Only inputs are supported as arguments to ST_IntersectsBox for now.");
1371  }
1372  };
1373  std::vector<std::shared_ptr<Analyzer::Expr>> geo_args;
1374  geo_args.push_back(extract_geo_bounds_from_input(0));
1375  geo_args.push_back(extract_geo_bounds_from_input(1));
1376 
1377  return makeExpr<Analyzer::FunctionOper>(return_type, function_name, geo_args);
1378  }
1379 
1380  if (function_name == "ST_Distance"sv || function_name == "ST_MaxDistance"sv) {
1381  CHECK_EQ(size_t(2), rex_function->size());
1382  std::vector<std::shared_ptr<Analyzer::Expr>> args;
1383  int legacy_transform_srid = 0;
1384  for (size_t i = 0; i < rex_function->size(); i++) {
1385  SQLTypeInfo arg0_ti; // discard
1386  auto geoargs = translateGeoFunctionArg(rex_function->getOperand(i),
1387  arg0_ti,
1388  /*with_bounds=*/false, // TODO
1389  /*expand_geo_col=*/false,
1390  /*is_projection = */ false,
1391  /*use_geo_expressions=*/true);
1392  if (arg0_ti.get_input_srid() != arg0_ti.get_output_srid() &&
1393  arg0_ti.get_output_srid() > 0 &&
1394  std::dynamic_pointer_cast<Analyzer::ColumnVar>(geoargs.front())) {
1395  // legacy transform
1396  CHECK(legacy_transform_srid == 0 ||
1397  legacy_transform_srid == arg0_ti.get_output_srid());
1398  legacy_transform_srid = arg0_ti.get_output_srid();
1399  }
1400  args.insert(args.end(), geoargs.begin(), geoargs.end());
1401  }
1402  return makeExpr<Analyzer::GeoOperator>(
1403  SQLTypeInfo(kDOUBLE, /*not_null=*/false),
1404  function_name,
1405  args,
1406  legacy_transform_srid > 0 ? std::make_optional<int>(legacy_transform_srid)
1407  : std::nullopt);
1408  }
1409 
1410  bool swap_args = false;
1411  bool with_bounds = false;
1412  bool negate_result = false;
1413  Analyzer::ExpressionPtr threshold_expr = nullptr;
1414  Analyzer::ExpressionPtr compare_expr = nullptr;
1415  if (function_name == "ST_DWithin"sv) {
1416  CHECK_EQ(size_t(3), rex_function->size());
1417  function_name = "ST_Distance";
1418  return_type = SQLTypeInfo(kDOUBLE, false);
1419  // Inject ST_DWithin's short-circuiting threshold into ST_MaxDistance
1420  threshold_expr = translateScalarRex(rex_function->getOperand(2));
1421  } else if (function_name == "ST_Equals"sv) {
1422  // Translate ST_Equals(g1,g2) to ST_Distance(g1,g2)<=0.0
1423  CHECK_EQ(size_t(2), rex_function->size());
1424  function_name = "ST_Distance";
1425  return_type = SQLTypeInfo(kDOUBLE, false);
1426  threshold_expr = nullptr;
1427  Datum d;
1428  d.doubleval = 0.0;
1429  compare_expr = makeExpr<Analyzer::Constant>(kDOUBLE, false, d);
1430  } else if (function_name == "ST_DFullyWithin"sv) {
1431  CHECK_EQ(size_t(3), rex_function->size());
1432  function_name = "ST_MaxDistance";
1433  return_type = SQLTypeInfo(kDOUBLE, false);
1434  // TODO: inject ST_DFullyWithin's short-circuiting threshold into ST_MaxDistance
1435  threshold_expr = nullptr;
1436  } else if (function_name == "ST_Distance"sv) {
1437  // TODO: pick up an outside short-circuiting threshold and inject into ST_Distance
1438  threshold_expr = nullptr;
1439  } else if (function_name == "ST_MaxDistance"sv) {
1440  // TODO: pick up an outside short-circuiting threshold and inject into
1441  // ST_MaxDistance
1442  threshold_expr = nullptr;
1443  } else {
1444  CHECK_EQ(size_t(2), rex_function->size());
1445  }
1446  if (function_name == "ST_Within"sv) {
1447  function_name = "ST_Contains";
1448  swap_args = true;
1449  } else if (function_name == "ST_Disjoint"sv) {
1450  function_name = "ST_Intersects";
1451  negate_result = true;
1452  }
1453  if (func_resolve(
1454  function_name, "ST_Contains"sv, "ST_Intersects"sv, "ST_Approx_Overlaps"sv)) {
1455  with_bounds = true;
1456  }
1457 
1458  std::vector<std::shared_ptr<Analyzer::Expr>> geoargs;
1459  SQLTypeInfo arg0_ti;
1460  SQLTypeInfo arg1_ti;
1461 
1462  // Proactively try to compress the first arg of ST_Intersects to preempt arg swap
1463  bool try_to_compress_arg0 = g_enable_geo_ops_on_uncompressed_coords &&
1464  func_resolve(function_name, "ST_Intersects"sv);
1465 
1466  auto geoargs0 = translateGeoFunctionArg(rex_function->getOperand(swap_args ? 1 : 0),
1467  arg0_ti,
1468  with_bounds,
1469  false,
1470  false,
1471  false,
1472  try_to_compress_arg0);
1473  geoargs.insert(geoargs.end(), geoargs0.begin(), geoargs0.end());
1474 
1475  // If first arg is compressed, try to compress the second one to be able to
1476  // switch to faster implementations working directly on uncompressed coords
1477  bool try_to_compress_arg1 =
1479  func_resolve(function_name, "ST_Contains"sv, "ST_Intersects"sv) &&
1480  arg0_ti.get_compression() == kENCODING_GEOINT &&
1481  arg0_ti.get_output_srid() == 4326);
1482 
1483  auto geoargs1 = translateGeoFunctionArg(rex_function->getOperand(swap_args ? 0 : 1),
1484  arg1_ti,
1485  with_bounds,
1486  false,
1487  false,
1488  false,
1489  try_to_compress_arg1);
1490  geoargs.insert(geoargs.end(), geoargs1.begin(), geoargs1.end());
1491 
1492  if (arg0_ti.get_subtype() != kNULLT && arg0_ti.get_subtype() != arg1_ti.get_subtype()) {
1493  throw QueryNotSupported(rex_function->getName() +
1494  " accepts either two GEOGRAPHY or two GEOMETRY arguments");
1495  }
1496  // Check SRID match if at least one is set/valid
1497  if ((arg0_ti.get_output_srid() > 0 || arg1_ti.get_output_srid() > 0) &&
1498  arg0_ti.get_output_srid() != arg1_ti.get_output_srid()) {
1499  throw QueryNotSupported(rex_function->getName() + " cannot accept different SRIDs");
1500  }
1501  if (compare_expr) {
1502  // We could fold the check to false here if argument geo types are different, e.g.
1503  // POLYGON vs POINT. However, tiny POLYGON could be "spatially" equal to a POINT.
1504  if (arg0_ti.get_type() != kPOINT || arg1_ti.get_type() != kPOINT) {
1505  // ST_Equals is translated to a simple distance check for POINTs,
1506  // otherwise geometries are passed to GEOS's Equals
1507  return nullptr;
1508  }
1509  // Look at POINT compression modes.
1510  if (arg0_ti.get_compression() != arg1_ti.get_compression()) {
1511  if ((arg0_ti.get_compression() == kENCODING_GEOINT &&
1512  arg0_ti.get_comp_param() == 32 &&
1513  arg1_ti.get_compression() == kENCODING_NONE) ||
1514  (arg0_ti.get_compression() == kENCODING_NONE &&
1515  arg1_ti.get_compression() == kENCODING_GEOINT &&
1516  arg0_ti.get_comp_param() == 32)) {
1517  // Spatial equality comparison of a compressed point vs uncompressed point.
1518  // Introduce tolerance into distance calculation and comparison, translate
1519  // ST_Equals(g1,g2) to ST_Distance(g1,g2,thereshold=tolerance)<=tolerance
1520  Datum tolerance;
1521  // Tolerance representing 0.44" to cover shifts due to GEOINT(32) compression
1522  tolerance.doubleval = TOLERANCE_GEOINT32;
1523  threshold_expr = makeExpr<Analyzer::Constant>(kDOUBLE, false, tolerance);
1524  compare_expr = threshold_expr;
1525  } else {
1526  throw QueryNotSupported(
1527  rex_function->getName() +
1528  " unable to calculate compression tolerance for arguments");
1529  }
1530  }
1531  }
1532  if (arg0_ti.get_type() == kMULTILINESTRING || arg1_ti.get_type() == kMULTILINESTRING) {
1533  throw QueryNotSupported(rex_function->getName() +
1534  " currently doesn't support this argument combination");
1535  }
1536 
1537  auto can_use_compressed_coords = [](const SQLTypeInfo& i0_ti,
1538  const Analyzer::ExpressionPtrVector& i0_operands,
1539  const SQLTypeInfo& i1_ti,
1540  const Analyzer::ExpressionPtrVector& i1_operands) {
1541  const bool i0_is_poly =
1542  i0_ti.get_type() == kPOLYGON || i0_ti.get_type() == kMULTIPOLYGON;
1543  const bool i1_is_point = i1_ti.get_type() == kPOINT;
1544  const bool i1_is_literal =
1545  i1_operands.size() == 1 && std::dynamic_pointer_cast<const Analyzer::Constant>(
1546  i1_operands.front()) != nullptr;
1547  return (i0_is_poly && !i1_is_literal && i1_is_point &&
1548  i0_ti.get_compression() == kENCODING_GEOINT &&
1549  i0_ti.get_input_srid() == i0_ti.get_output_srid() &&
1550  i0_ti.get_compression() == i1_ti.get_compression() &&
1551  i1_ti.get_input_srid() == i1_ti.get_output_srid());
1552  };
1553  if (g_enable_geo_ops_on_uncompressed_coords && function_name == "ST_Contains"sv) {
1554  if (can_use_compressed_coords(arg0_ti, geoargs0, arg1_ti, geoargs1)) {
1555  // Switch to Contains implementation working directly on uncompressed coords
1556  function_name = "ST_cContains";
1557  }
1558  }
1559  if (g_enable_geo_ops_on_uncompressed_coords && function_name == "ST_Intersects"sv) {
1560  if (can_use_compressed_coords(arg0_ti, geoargs0, arg1_ti, geoargs1)) {
1561  // Switch to Intersects implementation working directly on uncompressed coords
1562  function_name = "ST_cIntersects";
1563  } else if (can_use_compressed_coords(arg1_ti, geoargs1, arg0_ti, geoargs0)) {
1564  // Switch to Intersects implementation working on uncompressed coords, swapped args
1565  function_name = "ST_cIntersects";
1566  geoargs.clear();
1567  geoargs.insert(geoargs.end(), geoargs1.begin(), geoargs1.end());
1568  geoargs.insert(geoargs.end(), geoargs0.begin(), geoargs0.end());
1569  auto tmp_ti = arg0_ti;
1570  arg0_ti = arg1_ti;
1571  arg1_ti = tmp_ti;
1572  }
1573  }
1574 
1575  std::string specialized_geofunc{function_name + suffix(arg0_ti.get_type()) +
1576  suffix(arg1_ti.get_type())};
1577 
1578  if (arg0_ti.get_subtype() == kGEOGRAPHY && arg0_ti.get_output_srid() == 4326) {
1579  // Need to call geodesic runtime functions
1580  if (function_name == "ST_Distance"sv) {
1581  if ((arg0_ti.get_type() == kPOINT && arg1_ti.get_type() == kPOINT) ||
1582  (arg0_ti.get_type() == kLINESTRING && arg1_ti.get_type() == kPOINT) ||
1583  (arg0_ti.get_type() == kPOINT && arg1_ti.get_type() == kLINESTRING)) {
1584  // Geodesic distance between points
1585  specialized_geofunc += "_Geodesic"s;
1586  } else {
1587  throw QueryNotSupported(function_name +
1588  " currently doesn't accept non-POINT geographies");
1589  }
1590  } else if (rex_function->getName() == "ST_Contains"sv) {
1591  // We currently don't have a geodesic implementation of ST_Contains,
1592  // allowing calls to a [less precise] cartesian implementation.
1593  } else {
1594  throw QueryNotSupported(function_name + " doesn't accept geographies");
1595  }
1596  } else if (function_name == "ST_Distance"sv && rex_function->size() == 3) {
1597  if (arg0_ti.get_type() == kPOINT && arg1_ti.get_type() == kPOINT) {
1598  // Cartesian distance between points used by ST_DWithin - switch to faster Squared
1599  specialized_geofunc += "_Squared"s;
1600  }
1601  }
1602 
1603  // Add first input's compression mode and SRID args to enable on-the-fly
1604  // decompression/transforms
1605  Datum input_compression0;
1606  input_compression0.intval = Geospatial::get_compression_scheme(arg0_ti);
1607  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression0));
1608  Datum input_srid0;
1609  input_srid0.intval = arg0_ti.get_input_srid();
1610  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid0));
1611 
1612  // Add second input's compression mode and SRID args to enable on-the-fly
1613  // decompression/transforms
1614  Datum input_compression1;
1615  input_compression1.intval = Geospatial::get_compression_scheme(arg1_ti);
1616  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression1));
1617  Datum input_srid1;
1618  input_srid1.intval = arg1_ti.get_input_srid();
1619  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid1));
1620 
1621  // Add output SRID arg to enable on-the-fly transforms
1622  Datum output_srid;
1623  output_srid.intval = arg0_ti.get_output_srid();
1624  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
1625 
1626  // Some geo distance functions will be injected with a short-circuit threshold.
1627  // Threshold value would come from Geo comparison operations or from other outer
1628  // geo operations, e.g. ST_DWithin
1629  // At this point, only ST_Distance_LineString_LineString requires a threshold arg.
1630  // TODO: Other combinations that involve LINESTRING, POLYGON and MULTIPOLYGON args
1631  // TODO: Inject threshold into ST_MaxDistance
1632  if (function_name == "ST_Distance"sv && arg0_ti.get_subtype() != kGEOGRAPHY &&
1633  (arg0_ti.get_type() != kPOINT || arg1_ti.get_type() != kPOINT)) {
1634  if (threshold_expr) {
1635  if (threshold_expr->get_type_info().get_type() != kDOUBLE) {
1636  const auto& threshold_ti = SQLTypeInfo(kDOUBLE, false);
1637  threshold_expr = threshold_expr->add_cast(threshold_ti);
1638  }
1639  threshold_expr = fold_expr(threshold_expr.get());
1640  } else {
1641  Datum d;
1642  d.doubleval = 0.0;
1643  threshold_expr = makeExpr<Analyzer::Constant>(kDOUBLE, false, d);
1644  }
1645  geoargs.push_back(threshold_expr);
1646  }
1647 
1648  auto result =
1649  makeExpr<Analyzer::FunctionOper>(return_type, specialized_geofunc, geoargs);
1650  if (negate_result) {
1651  return makeExpr<Analyzer::UOper>(kBOOLEAN, kNOT, result);
1652  }
1653  if (compare_expr) {
1654  return makeExpr<Analyzer::BinOper>(kBOOLEAN, kLE, kONE, result, compare_expr);
1655  }
1656  return result;
1657 }
1658 
1659 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateTernaryGeoFunction(
1660  const RexFunctionOperator* rex_function) const {
1661  CHECK_EQ(size_t(3), rex_function->size());
1662 
1663  auto distance_expr = translateScalarRex(rex_function->getOperand(2));
1664  const auto& distance_ti = SQLTypeInfo(kDOUBLE, false);
1665  if (distance_expr->get_type_info().get_type() != kDOUBLE) {
1666  distance_expr = distance_expr->add_cast(distance_ti);
1667  }
1668 
1669  auto function_name = rex_function->getName();
1670  if (function_name == "ST_DWithin"sv) {
1671  auto return_type = rex_function->getType();
1672  bool swap_args = false;
1673  bool with_bounds = true;
1674  SQLTypeInfo arg0_ti;
1675  SQLTypeInfo arg1_ti;
1676 
1677  auto geoargs0 =
1678  translateGeoFunctionArg(rex_function->getOperand(0), arg0_ti, with_bounds, false);
1679  auto geoargs1 =
1680  translateGeoFunctionArg(rex_function->getOperand(1), arg1_ti, with_bounds, false);
1681  if (arg0_ti.get_subtype() != arg1_ti.get_subtype()) {
1682  throw QueryNotSupported(rex_function->getName() +
1683  " cannot accept mixed GEOMETRY/GEOGRAPHY arguments");
1684  }
1685  auto is_geodesic = false;
1686  if (arg0_ti.get_subtype() == kGEOGRAPHY) {
1687  if (arg0_ti.get_type() == kPOINT && arg1_ti.get_type() == kPOINT) {
1688  is_geodesic = true;
1689  } else {
1690  throw QueryNotSupported(
1691  rex_function->getName() +
1692  " in geodesic form can only accept POINT GEOGRAPHY arguments");
1693  }
1694  }
1695  // Check SRID match if at least one is set/valid
1696  if ((arg0_ti.get_output_srid() > 0 || arg1_ti.get_output_srid() > 0) &&
1697  arg0_ti.get_output_srid() != arg1_ti.get_output_srid()) {
1698  throw QueryNotSupported(rex_function->getName() + " cannot accept different SRIDs");
1699  }
1700 
1701  if ((arg1_ti.get_type() == kPOINT && arg0_ti.get_type() != kPOINT) ||
1702  (arg1_ti.get_type() == kLINESTRING && arg0_ti.get_type() == kPOLYGON) ||
1703  (arg1_ti.get_type() == kPOLYGON && arg0_ti.get_type() == kMULTIPOLYGON)) {
1704  // Swap arguments and use single implementation per arg pair
1705  swap_args = true;
1706  }
1707 
1708  // First input's compression mode and SRID args to enable on-the-fly
1709  // decompression/transforms
1710  Datum input_compression0;
1711  input_compression0.intval = Geospatial::get_compression_scheme(arg0_ti);
1712  Datum input_srid0;
1713  input_srid0.intval = arg0_ti.get_input_srid();
1714 
1715  // Second input's compression mode and SRID args to enable on-the-fly
1716  // decompression/transforms
1717  Datum input_compression1;
1718  input_compression1.intval = Geospatial::get_compression_scheme(arg1_ti);
1719  Datum input_srid1;
1720  input_srid1.intval = arg1_ti.get_input_srid();
1721 
1722  // Output SRID arg to enable on-the-fly transforms
1723  Datum output_srid;
1724  output_srid.intval = arg0_ti.get_output_srid();
1725 
1726  std::string specialized_geofunc{function_name};
1727  std::vector<std::shared_ptr<Analyzer::Expr>> geoargs;
1728  if (swap_args) {
1729  specialized_geofunc += suffix(arg1_ti.get_type()) + suffix(arg0_ti.get_type());
1730  geoargs.insert(geoargs.end(), geoargs1.begin(), geoargs1.end());
1731  geoargs.insert(geoargs.end(), geoargs0.begin(), geoargs0.end());
1732  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression1));
1733  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid1));
1734  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression0));
1735  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid0));
1736  } else {
1737  specialized_geofunc += suffix(arg0_ti.get_type()) + suffix(arg1_ti.get_type());
1738  if (is_geodesic) {
1739  specialized_geofunc += "_Geodesic"s;
1740  }
1741  geoargs.insert(geoargs.end(), geoargs0.begin(), geoargs0.end());
1742  geoargs.insert(geoargs.end(), geoargs1.begin(), geoargs1.end());
1743  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression0));
1744  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid0));
1745  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression1));
1746  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid1));
1747  }
1748  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
1749  // Also add the within distance
1750  geoargs.push_back(distance_expr);
1751 
1752  auto result =
1753  makeExpr<Analyzer::FunctionOper>(return_type, specialized_geofunc, geoargs);
1754  return result;
1755  }
1756 
1757  // Otherwise translate function as binary geo to get distance,
1758  // with optional short-circuiting threshold held in the third operand
1759  const auto geo_distance = translateBinaryGeoFunction(rex_function);
1760  // and generate the comparison
1761  return makeExpr<Analyzer::BinOper>(kBOOLEAN, kLE, kONE, geo_distance, distance_expr);
1762 }
1763 
1764 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateGeoComparison(
1765  const RexOperator* rex_operator) const {
1766  if (rex_operator->size() != size_t(2)) {
1767  return nullptr;
1768  }
1769 
1770  auto geo_distance_expr = translateScalarRex(rex_operator->getOperand(0));
1771  auto func_oper = dynamic_cast<Analyzer::GeoOperator*>(geo_distance_expr.get());
1772  if (func_oper && func_oper->getName() == "ST_Distance"sv) {
1773  const auto& distance_ti = SQLTypeInfo(kDOUBLE, false);
1774  auto distance_expr = translateScalarRex(rex_operator->getOperand(1));
1775  if (distance_expr->get_type_info().get_type() != kDOUBLE) {
1776  distance_expr = distance_expr->add_cast(distance_ti);
1777  }
1778  distance_expr = fold_expr(distance_expr.get());
1779  return makeExpr<Analyzer::BinOper>(
1780  kBOOLEAN, rex_operator->getOperator(), kONE, geo_distance_expr, distance_expr);
1781  }
1782  return nullptr;
1783 }
1784 
1785 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateFunctionWithGeoArg(
1786  const RexFunctionOperator* rex_function) const {
1787  std::string specialized_geofunc{rex_function->getName()};
1788  if (func_resolve(rex_function->getName(),
1789  "convert_meters_to_pixel_width"sv,
1790  "convert_meters_to_pixel_height"sv)) {
1791  CHECK_EQ(rex_function->size(), 6u);
1792  SQLTypeInfo arg_ti;
1793  std::vector<std::shared_ptr<Analyzer::Expr>> args;
1794  args.push_back(translateScalarRex(rex_function->getOperand(0)));
1795  auto geoargs =
1796  translateGeoFunctionArg(rex_function->getOperand(1), arg_ti, false, false);
1797  // only works on points
1798  if (arg_ti.get_type() != kPOINT) {
1799  throw QueryNotSupported(rex_function->getName() +
1800  " expects a point for the second argument");
1801  }
1802 
1803  args.insert(args.end(), geoargs.begin(), geoargs.begin() + 1);
1804 
1805  // Add compression information
1806  Datum input_compression;
1807  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
1808  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
1809  if (arg_ti.get_input_srid() != 4326) {
1810  throw QueryNotSupported(
1811  rex_function->getName() +
1812  " currently only supports points of with SRID WGS84/EPSG:4326");
1813  }
1814  Datum input_srid;
1815  input_srid.intval = arg_ti.get_input_srid();
1816  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid));
1817  Datum output_srid;
1818  // Forcing web-mercator projection for now
1819  // TODO(croot): check that the input-to-output conversion routines exist?
1820  output_srid.intval =
1821  arg_ti.get_output_srid() != 900913 ? 900913 : arg_ti.get_output_srid();
1822  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
1823 
1824  args.push_back(translateScalarRex(rex_function->getOperand(2)));
1825  args.push_back(translateScalarRex(rex_function->getOperand(3)));
1826  args.push_back(translateScalarRex(rex_function->getOperand(4)));
1827  args.push_back(translateScalarRex(rex_function->getOperand(5)));
1828  return makeExpr<Analyzer::FunctionOper>(
1829  rex_function->getType(), specialized_geofunc, args);
1830  } else if (rex_function->getName() == "is_point_in_view"sv) {
1831  CHECK_EQ(rex_function->size(), 5u);
1832  SQLTypeInfo arg_ti;
1833  std::vector<std::shared_ptr<Analyzer::Expr>> args;
1834  auto geoargs =
1835  translateGeoFunctionArg(rex_function->getOperand(0), arg_ti, false, false);
1836  // only works on points
1837  if (arg_ti.get_type() != kPOINT) {
1838  throw QueryNotSupported(rex_function->getName() +
1839  " expects a point for the second argument");
1840  }
1841 
1842  args.insert(args.end(), geoargs.begin(), geoargs.begin() + 1);
1843 
1844  // Add compression information
1845  Datum input_compression;
1846  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
1847  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
1848  if (arg_ti.get_input_srid() != 4326) {
1849  throw QueryNotSupported(
1850  rex_function->getName() +
1851  " currently only supports points of with SRID WGS84/EPSG:4326");
1852  }
1853  args.push_back(translateScalarRex(rex_function->getOperand(1)));
1854  args.push_back(translateScalarRex(rex_function->getOperand(2)));
1855  args.push_back(translateScalarRex(rex_function->getOperand(3)));
1856  args.push_back(translateScalarRex(rex_function->getOperand(4)));
1857  return makeExpr<Analyzer::FunctionOper>(
1858  rex_function->getType(), specialized_geofunc, args);
1859  } else if (rex_function->getName() == "is_point_size_in_view"sv) {
1860  CHECK_EQ(rex_function->size(), 6u);
1861  SQLTypeInfo arg_ti;
1862  std::vector<std::shared_ptr<Analyzer::Expr>> args;
1863  auto geoargs =
1864  translateGeoFunctionArg(rex_function->getOperand(0), arg_ti, false, false);
1865  // only works on points
1866  if (arg_ti.get_type() != kPOINT) {
1867  throw QueryNotSupported(rex_function->getName() +
1868  " expects a point for the second argument");
1869  }
1870 
1871  args.insert(args.end(), geoargs.begin(), geoargs.begin() + 1);
1872 
1873  // Add compression information
1874  Datum input_compression;
1875  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
1876  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
1877  if (arg_ti.get_input_srid() != 4326) {
1878  throw QueryNotSupported(
1879  rex_function->getName() +
1880  " currently only supports points of with SRID WGS84/EPSG:4326");
1881  }
1882  args.push_back(translateScalarRex(rex_function->getOperand(1)));
1883  args.push_back(translateScalarRex(rex_function->getOperand(2)));
1884  args.push_back(translateScalarRex(rex_function->getOperand(3)));
1885  args.push_back(translateScalarRex(rex_function->getOperand(4)));
1886  args.push_back(translateScalarRex(rex_function->getOperand(5)));
1887  return makeExpr<Analyzer::FunctionOper>(
1888  rex_function->getType(), specialized_geofunc, args);
1889  }
1890  CHECK(false);
1891  return nullptr;
1892 }
1893 
1895  const RexOperator* rex_operator) const {
1896  CHECK_EQ(rex_operator->size(), 2u);
1897 
1898  auto translate_input =
1899  [&](const RexScalar* operand) -> std::shared_ptr<Analyzer::Expr> {
1900  const auto input = dynamic_cast<const RexInput*>(operand);
1901  CHECK(input);
1902 
1903  SQLTypeInfo ti;
1904  const auto exprs = translateGeoColumn(input, ti, true, false);
1905  CHECK_GT(exprs.size(), 0u);
1906  if (ti.get_type() == kPOINT) {
1907  return exprs.front();
1908  } else {
1909  return exprs.back();
1910  }
1911  };
1912 
1913  SQLQualifier sql_qual{kONE};
1914  SQLOps sql_op{kBBOX_INTERSECT};
1915  return makeExpr<Analyzer::BinOper>(SQLTypeInfo(kBOOLEAN, false),
1916  false,
1917  sql_op,
1918  sql_qual,
1919  translate_input(rex_operator->getOperand(1)),
1920  translate_input(rex_operator->getOperand(0)));
1921 }
int8_t tinyintval
Definition: Datum.h:71
HOST DEVICE SQLTypes get_subtype() const
Definition: sqltypes.h:392
void set_compression(EncodingType c)
Definition: sqltypes.h:479
void set_size(int s)
Definition: sqltypes.h:476
#define CHECK_EQ(x, y)
Definition: Logger.h:301
auto func_resolve
std::vector< std::shared_ptr< Analyzer::Expr > > translateGeoColumn(const RexInput *, SQLTypeInfo &, const bool with_bounds, const bool expand_geo_col) const
std::shared_ptr< Analyzer::Expr > translateBinaryGeoPredicate(const RexFunctionOperator *, SQLTypeInfo &, const bool with_bounds) const
class for a per-database catalog. also includes metadata for the current database and the current use...
Definition: Catalog.h:143
SQLTypes
Definition: sqltypes.h:65
#define SPIMAP_GEO_PHYSICAL_INPUT(c, i)
Definition: Catalog.h:84
SQLQualifier
Definition: sqldefs.h:71
std::shared_ptr< Analyzer::Expr > translateScalarRex(const RexScalar *rex) const
const SQLTypeInfo & getType() const
Definition: RelAlgDag.h:378
size_t size() const
Definition: RelAlgDag.h:364
const RexScalar * getOperand(const size_t idx) const
Definition: RelAlgDag.h:366
SQLOps
Definition: sqldefs.h:28
Definition: sqldefs.h:34
int32_t get_compression_scheme(const SQLTypeInfo &ti)
Definition: Compression.cpp:23
#define UNREACHABLE()
Definition: Logger.h:338
HOST DEVICE void set_subtype(SQLTypes st)
Definition: sqltypes.h:469
#define CHECK_GE(x, y)
Definition: Logger.h:306
Definition: sqldefs.h:48
std::shared_ptr< Analyzer::Expr > ExpressionPtr
Definition: Analyzer.h:184
bool g_enable_geo_ops_on_uncompressed_coords
Definition: Execute.cpp:121
HOST DEVICE SQLTypes get_type() const
Definition: sqltypes.h:391
#define CHECK_GT(x, y)
Definition: Logger.h:305
std::shared_ptr< Analyzer::Expr > translateGeoProjection(const RexFunctionOperator *, SQLTypeInfo &, const bool with_bounds) const
int32_t intval
Definition: Datum.h:73
#define TOLERANCE_GEOINT32
std::string suffix(SQLTypes type)
Definition: Codegen.cpp:69
std::string to_string(char const *&&v)
std::shared_ptr< Analyzer::Expr > translateInput(const RexInput *) const
std::shared_ptr< Analyzer::Expr > translateUnaryGeoFunction(const RexFunctionOperator *) const
std::vector< std::shared_ptr< Analyzer::Expr > > translateGeoLiteral(const RexLiteral *, SQLTypeInfo &, bool) const
void set_input_srid(int d)
Definition: sqltypes.h:472
unsigned getIndex() const
Definition: RelAlgDag.h:174
std::vector< uint8_t > compress_coords(const std::vector< double > &coords, const SQLTypeInfo &ti)
Definition: Compression.cpp:52
static std::shared_ptr< Analyzer::Expr > translateLiteral(const RexLiteral *)
SQLOps getOperator() const
Definition: RelAlgDag.h:376
bool has_bounds() const
Definition: sqltypes.h:455
static bool getGeoColumns(const std::string &wkt_or_wkb_hex, SQLTypeInfo &ti, std::vector< double > &coords, std::vector< double > &bounds, std::vector< int > &ring_sizes, std::vector< int > &poly_rings, const bool validate_with_geos_if_available)
Definition: Types.cpp:1121
void set_output_srid(int s)
Definition: sqltypes.h:474
const std::unordered_map< const RelAlgNode *, int > input_to_nest_level_
void set_comp_param(int p)
Definition: sqltypes.h:480
#define CHECK_LT(x, y)
Definition: Logger.h:303
Definition: sqltypes.h:79
std::shared_ptr< Analyzer::Expr > translateUnaryGeoPredicate(const RexFunctionOperator *, SQLTypeInfo &, const bool with_bounds) const
Definition: sqldefs.h:71
HOST DEVICE EncodingType get_compression() const
Definition: sqltypes.h:399
static std::unique_ptr< GeoBase > createGeoType(const std::string &wkt_or_wkb_hex, const bool validate_with_geos_if_available)
Definition: Types.cpp:1085
std::shared_ptr< Analyzer::Expr > translateUnaryGeoConstructor(const RexFunctionOperator *, SQLTypeInfo &, const bool with_bounds) const
bool is_projection(const RelAlgExecutionUnit &ra_exe_unit)
static RelRexToStringConfig defaults()
Definition: RelAlgDag.h:78
const RelAlgNode * getSourceNode() const
Definition: RelAlgDag.h:1056
static bool isUtm(unsigned const srid)
Definition: Transform.h:42
std::vector< std::shared_ptr< Analyzer::Expr > > translateGeoFunctionArg(const RexScalar *rex_scalar, SQLTypeInfo &arg_ti, const bool with_bounds, const bool expand_geo_col, const bool is_projection=false, const bool use_geo_expressions=false, const bool try_to_compress=false, const bool allow_gdal_transforms=false) const
#define IS_STRING(T)
Definition: sqltypes.h:309
HOST DEVICE int get_comp_param() const
Definition: sqltypes.h:402
HOST DEVICE int get_input_srid() const
Definition: sqltypes.h:395
void set_notnull(bool n)
Definition: sqltypes.h:475
#define CHECK(condition)
Definition: Logger.h:291
std::shared_ptr< Analyzer::Expr > translateTernaryGeoFunction(const RexFunctionOperator *) const
std::shared_ptr< Analyzer::Expr > translateBinaryGeoFunction(const RexFunctionOperator *) const
std::vector< ExpressionPtr > ExpressionPtrVector
Definition: Analyzer.h:186
std::string toString(RelRexToStringConfig config=RelRexToStringConfig::defaults()) const override
Definition: RelAlgDag.h:508
std::shared_ptr< Analyzer::Expr > translateFunctionWithGeoArg(const RexFunctionOperator *) const
Definition: sqltypes.h:72
const std::string & getName() const
Definition: RelAlgDag.h:506
Definition: Datum.h:69
std::shared_ptr< Analyzer::Expr > translateGeoComparison(const RexOperator *) const
int get_physical_coord_cols() const
Definition: sqltypes.h:449
#define IS_GEO(T)
Definition: sqltypes.h:310
std::shared_ptr< Analyzer::Expr > translateBinaryGeoConstructor(const RexFunctionOperator *, SQLTypeInfo &, const bool with_bounds) const
Definition: sqldefs.h:38
SQLTypes get_ti_from_geo(const Geospatial::GeoBase *geo)
Definition: Analyzer.cpp:3984
std::shared_ptr< Analyzer::Expr > fold_expr(const Analyzer::Expr *expr)
std::shared_ptr< Analyzer::Expr > translateGeoBoundingBoxIntersectOper(const RexOperator *) const
double doubleval
Definition: Datum.h:76
HOST DEVICE int get_output_srid() const
Definition: sqltypes.h:397
virtual GeoType getType() const =0
#define IS_GEO_POLY(T)
Definition: sqltypes.h:315
HOST DEVICE void set_type(SQLTypes t)
Definition: sqltypes.h:468