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