OmniSciDB  a667adc9c8
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
RelAlgTranslatorGeo.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017 MapD Technologies, 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 
18 
19 #include <memory>
20 #include <vector>
21 
22 #include "Geospatial/Compression.h"
23 #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  CHECK(!expand_geo_col);
61  if (with_bounds || with_render_group) {
62  throw QueryNotSupported(
63  "Geospatial columns not yet supported in intermediate results.");
64  }
65 
66  CHECK(!in_metainfo.empty());
67  CHECK_GE(rte_idx, 0);
68  column_id = rex_input->getIndex();
69  CHECK_LT(static_cast<size_t>(column_id), in_metainfo.size());
70  ti = in_metainfo[column_id].get_type_info();
71  }
72  CHECK(IS_GEO(ti.get_type()));
73 
74  // Return geo column reference. The geo column may be expanded if required for extension
75  // function arguments. Otherwise, the geo column reference will be translated into
76  // physical columns as required. Bounds column will be added if present and requested.
77  if (expand_geo_col) {
78  for (auto i = 0; i < ti.get_physical_coord_cols(); i++) {
79  const auto pcd = cat_.getMetadataForColumnBySpi(
80  table_id, SPIMAP_GEO_PHYSICAL_INPUT(rex_input->getIndex(), i + 1));
81  auto pcol_ti = pcd->columnType;
82  args.push_back(std::make_shared<Analyzer::ColumnVar>(
83  pcol_ti, table_id, pcd->columnId, rte_idx));
84  }
85  } else {
86  args.push_back(
87  std::make_shared<Analyzer::ColumnVar>(ti, table_id, column_id, rte_idx));
88  }
89  if (with_bounds && ti.has_bounds()) {
90  const auto bounds_cd = cat_.getMetadataForColumnBySpi(
91  table_id,
93  ti.get_physical_coord_cols() + 1));
94  auto bounds_ti = bounds_cd->columnType;
95  args.push_back(std::make_shared<Analyzer::ColumnVar>(
96  bounds_ti, table_id, bounds_cd->columnId, rte_idx));
97  }
98  if (with_render_group && ti.has_render_group()) {
99  const auto render_group_cd = cat_.getMetadataForColumnBySpi(
100  table_id,
101  SPIMAP_GEO_PHYSICAL_INPUT(rex_input->getIndex(),
102  ti.get_physical_coord_cols() + 2));
103  auto render_group_ti = render_group_cd->columnType;
104  args.push_back(std::make_shared<Analyzer::ColumnVar>(
105  render_group_ti, table_id, render_group_cd->columnId, rte_idx));
106  }
107  return args;
108 }
109 
110 std::vector<std::shared_ptr<Analyzer::Expr>> RelAlgTranslator::translateGeoLiteral(
111  const RexLiteral* rex_literal,
112  SQLTypeInfo& ti,
113  bool with_bounds) const {
114  CHECK(rex_literal);
115  if (rex_literal->getType() != kTEXT) {
116  throw std::runtime_error("Geo literals must be strings");
117  }
118  const auto e = translateLiteral(rex_literal);
119  auto wkt = std::dynamic_pointer_cast<Analyzer::Constant>(e);
120  CHECK(wkt);
121  std::vector<double> coords;
122  std::vector<double> bounds;
123  std::vector<int> ring_sizes;
124  std::vector<int> poly_rings;
125  int32_t srid = ti.get_output_srid();
127  *wkt->get_constval().stringval, ti, coords, bounds, ring_sizes, poly_rings)) {
128  throw QueryNotSupported("Could not read geometry from text");
129  }
131  ti.set_input_srid(srid);
132  ti.set_output_srid(srid);
133  // Compress geo literals by default
134  if (srid == 4326) {
136  ti.set_comp_param(32);
137  }
138 
139  std::vector<std::shared_ptr<Analyzer::Expr>> args;
140 
141  std::vector<uint8_t> compressed_coords = Geospatial::compress_coords(coords, ti);
142  std::list<std::shared_ptr<Analyzer::Expr>> compressed_coords_exprs;
143  for (auto cc : compressed_coords) {
144  Datum d;
145  d.tinyintval = cc;
146  auto e = makeExpr<Analyzer::Constant>(kTINYINT, false, d);
147  compressed_coords_exprs.push_back(e);
148  }
149  SQLTypeInfo arr_ti = SQLTypeInfo(kARRAY, true);
150  arr_ti.set_subtype(kTINYINT);
151  arr_ti.set_size(compressed_coords.size() * sizeof(int8_t));
152  arr_ti.set_compression(ti.get_compression());
153  arr_ti.set_comp_param((ti.get_compression() == kENCODING_GEOINT) ? 32 : 64);
154  args.push_back(makeExpr<Analyzer::Constant>(arr_ti, false, compressed_coords_exprs));
155 
156  auto lit_type = ti.get_type();
157  if (lit_type == kPOLYGON || lit_type == kMULTIPOLYGON) {
158  // ring sizes
159  std::list<std::shared_ptr<Analyzer::Expr>> ring_size_exprs;
160  for (auto c : ring_sizes) {
161  Datum d;
162  d.intval = c;
163  auto e = makeExpr<Analyzer::Constant>(kINT, false, d);
164  ring_size_exprs.push_back(e);
165  }
166  SQLTypeInfo arr_ti = SQLTypeInfo(kARRAY, true);
167  arr_ti.set_subtype(kINT);
168  arr_ti.set_size(ring_sizes.size() * sizeof(int32_t));
169  args.push_back(makeExpr<Analyzer::Constant>(arr_ti, false, ring_size_exprs));
170 
171  // poly rings
172  if (lit_type == kMULTIPOLYGON) {
173  std::list<std::shared_ptr<Analyzer::Expr>> poly_rings_exprs;
174  for (auto c : poly_rings) {
175  Datum d;
176  d.intval = c;
177  auto e = makeExpr<Analyzer::Constant>(kINT, false, d);
178  poly_rings_exprs.push_back(e);
179  }
180  SQLTypeInfo arr_ti = SQLTypeInfo(kARRAY, true);
181  arr_ti.set_subtype(kINT);
182  arr_ti.set_size(poly_rings.size() * sizeof(int32_t));
183  args.push_back(makeExpr<Analyzer::Constant>(arr_ti, false, poly_rings_exprs));
184  }
185  }
186 
187  if (with_bounds && ti.has_bounds()) {
188  // bounds
189  std::list<std::shared_ptr<Analyzer::Expr>> bounds_exprs;
190  for (auto b : bounds) {
191  Datum d;
192  d.doubleval = b;
193  auto e = makeExpr<Analyzer::Constant>(kDOUBLE, false, d);
194  bounds_exprs.push_back(e);
195  }
196  SQLTypeInfo arr_ti = SQLTypeInfo(kARRAY, true);
197  arr_ti.set_subtype(kDOUBLE);
198  arr_ti.set_size(bounds.size() * sizeof(double));
199  args.push_back(makeExpr<Analyzer::Constant>(arr_ti, false, bounds_exprs));
200  }
201 
202  return args;
203 }
204 
205 namespace {
206 
207 std::string suffix(SQLTypes type) {
208  if (type == kPOINT) {
209  return std::string("_Point");
210  }
211  if (type == kLINESTRING) {
212  return std::string("_LineString");
213  }
214  if (type == kPOLYGON) {
215  return std::string("_Polygon");
216  }
217  if (type == kMULTIPOLYGON) {
218  return std::string("_MultiPolygon");
219  }
220  throw QueryNotSupported("Unsupported argument type");
221 }
222 
223 } // namespace
224 
225 std::vector<std::shared_ptr<Analyzer::Expr>> RelAlgTranslator::translateGeoFunctionArg(
226  const RexScalar* rex_scalar,
227  SQLTypeInfo& arg_ti,
228  int32_t& lindex,
229  const bool with_bounds,
230  const bool with_render_group,
231  const bool expand_geo_col,
232  const bool is_projection) const {
233  std::vector<std::shared_ptr<Analyzer::Expr>> geoargs;
234 
235  const auto rex_input = dynamic_cast<const RexInput*>(rex_scalar);
236  if (rex_input) {
237  const auto input = translateInput(rex_input);
238  const auto column = dynamic_cast<const Analyzer::ColumnVar*>(input.get());
239  if (!column || !column->get_type_info().is_geometry()) {
240  throw QueryNotSupported("Geo function is expecting a geo column argument");
241  }
242  return translateGeoColumn(
243  rex_input, arg_ti, with_bounds, with_render_group, expand_geo_col);
244  }
245  const auto rex_function = dynamic_cast<const RexFunctionOperator*>(rex_scalar);
246  if (rex_function) {
247  if (rex_function->getName() == "ST_Transform"sv) {
248  CHECK_EQ(size_t(2), rex_function->size());
249  const auto rex_scalar0 =
250  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
251  if (!rex_scalar0) {
252  throw QueryNotSupported(rex_function->getName() + ": unexpected first argument");
253  }
254 
255  const auto rex_literal =
256  dynamic_cast<const RexLiteral*>(rex_function->getOperand(1));
257  if (!rex_literal) {
258  throw QueryNotSupported(rex_function->getName() +
259  ": second argument is expected to be a literal");
260  }
261  const auto e = translateLiteral(rex_literal);
262  auto ce = std::dynamic_pointer_cast<Analyzer::Constant>(e);
263  if (!ce || !e->get_type_info().is_integer()) {
264  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
265  }
266  int32_t srid = 0;
267  if (e->get_type_info().get_type() == kSMALLINT) {
268  srid = static_cast<int32_t>(ce->get_constval().smallintval);
269  } else if (e->get_type_info().get_type() == kTINYINT) {
270  srid = static_cast<int32_t>(ce->get_constval().tinyintval);
271  } else if (e->get_type_info().get_type() == kINT) {
272  srid = static_cast<int32_t>(ce->get_constval().intval);
273  } else {
274  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
275  }
276  if (srid != 900913) {
277  throw QueryNotSupported(rex_function->getName() + ": unsupported output SRID " +
278  std::to_string(srid));
279  }
280  arg_ti.set_output_srid(srid); // Forward output srid down to argument translation
281  auto arg0 = translateGeoFunctionArg(
282  rex_scalar0, arg_ti, lindex, with_bounds, with_render_group, expand_geo_col);
283 
284  if (arg_ti.get_input_srid() > 0) {
285  if (arg_ti.get_input_srid() != 4326) {
286  throw QueryNotSupported(rex_function->getName() + ": unsupported input SRID " +
287  std::to_string(arg_ti.get_input_srid()));
288  }
289  arg_ti.set_output_srid(
290  srid); // We have a valid input SRID, register the output SRID for transform
291  } else {
292  throw QueryNotSupported(rex_function->getName() +
293  ": unexpected input SRID, unable to transform");
294  }
295  return arg0;
296  } else if (func_resolve(
297  rex_function->getName(), "ST_GeomFromText"sv, "ST_GeogFromText"sv)) {
298  CHECK(rex_function->size() == size_t(1) || rex_function->size() == size_t(2));
299  // First - register srid, then send it to geo literal translation
300  int32_t srid = 0;
301  if (rex_function->size() == 2) {
302  const auto rex_literal =
303  dynamic_cast<const RexLiteral*>(rex_function->getOperand(1));
304  if (!rex_literal) {
305  throw QueryNotSupported(rex_function->getName() +
306  ": second argument is expected to be a literal");
307  }
308  const auto e = translateLiteral(rex_literal);
309  auto ce = std::dynamic_pointer_cast<Analyzer::Constant>(e);
310  if (!ce || !e->get_type_info().is_integer()) {
311  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
312  }
313  if (e->get_type_info().get_type() == kSMALLINT) {
314  srid = static_cast<int32_t>(ce->get_constval().smallintval);
315  } else if (e->get_type_info().get_type() == kTINYINT) {
316  srid = static_cast<int32_t>(ce->get_constval().tinyintval);
317  } else if (e->get_type_info().get_type() == kINT) {
318  srid = static_cast<int32_t>(ce->get_constval().intval);
319  } else {
320  throw QueryNotSupported(rex_function->getName() + " expecting integer SRID");
321  }
322  if (srid != 0 && srid != 4326 && srid != 900913) {
323  throw QueryNotSupported(rex_function->getName() + ": unsupported SRID " +
324  std::to_string(srid));
325  }
326  }
327  arg_ti.set_input_srid(srid); // Input SRID
328  arg_ti.set_output_srid(srid); // Output SRID is the same - no transform
329 
330  const auto rex_literal =
331  dynamic_cast<const RexLiteral*>(rex_function->getOperand(0));
332  if (!rex_literal) {
333  throw QueryNotSupported(rex_function->getName() +
334  " expects a string literal as first argument");
335  }
336  auto arg0 = translateGeoLiteral(rex_literal, arg_ti, with_bounds);
337  arg_ti.set_subtype((rex_function->getName() == "ST_GeogFromText"sv) ? kGEOGRAPHY
338  : kGEOMETRY);
339  return arg0;
340  } else if (rex_function->getName() == "ST_PointN"sv) {
341  CHECK_EQ(size_t(2), rex_function->size());
342  const auto rex_scalar0 =
343  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
344  if (!rex_scalar0) {
345  throw QueryNotSupported(rex_function->getName() +
346  ": expects scalar as first argument");
347  }
348  auto arg0 = translateGeoFunctionArg(
349  rex_scalar0, arg_ti, lindex, with_bounds, with_render_group, expand_geo_col);
350  if (arg_ti.get_type() != kLINESTRING) {
351  throw QueryNotSupported(rex_function->getName() +
352  " expects LINESTRING as first argument");
353  }
354  const auto rex_literal =
355  dynamic_cast<const RexLiteral*>(rex_function->getOperand(1));
356  if (!rex_literal) {
357  throw QueryNotSupported(rex_function->getName() +
358  ": second argument is expected to be a literal");
359  }
360  const auto e = translateLiteral(rex_literal);
361  auto ce = std::dynamic_pointer_cast<Analyzer::Constant>(e);
362  if (!ce || !e->get_type_info().is_integer()) {
363  throw QueryNotSupported(rex_function->getName() +
364  ": expecting integer index as second argument");
365  }
366  int32_t index = 0;
367  if (e->get_type_info().get_type() == kSMALLINT) {
368  index = static_cast<int32_t>(ce->get_constval().smallintval);
369  } else if (e->get_type_info().get_type() == kTINYINT) {
370  index = static_cast<int32_t>(ce->get_constval().tinyintval);
371  } else if (e->get_type_info().get_type() == kINT) {
372  index = static_cast<int32_t>(ce->get_constval().intval);
373  } else {
374  throw QueryNotSupported(rex_function->getName() + " expecting integer index");
375  }
376  if (lindex != 0) {
377  throw QueryNotSupported(rex_function->getName() +
378  ": LINESTRING is already indexed");
379  }
380  if (index == 0) {
381  throw QueryNotSupported(rex_function->getName() + ": invalid index");
382  }
383  lindex = index;
384  return arg0;
385  } else if (rex_function->getName() == "ST_StartPoint"sv) {
386  CHECK_EQ(size_t(1), rex_function->size());
387  const auto rex_scalar0 =
388  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
389  if (!rex_scalar0) {
390  throw QueryNotSupported(rex_function->getName() +
391  ": expects scalar as first argument");
392  }
393  auto arg0 = translateGeoFunctionArg(
394  rex_scalar0, arg_ti, lindex, with_bounds, with_render_group, expand_geo_col);
395  if (arg_ti.get_type() != kLINESTRING) {
396  throw QueryNotSupported(rex_function->getName() +
397  " expects LINESTRING as first argument");
398  }
399  if (lindex != 0) {
400  throw QueryNotSupported(rex_function->getName() +
401  ": LINESTRING is already indexed");
402  }
403  lindex = 1;
404  return arg0;
405  } else if (rex_function->getName() == "ST_EndPoint"sv) {
406  CHECK_EQ(size_t(1), rex_function->size());
407  const auto rex_scalar0 =
408  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
409  if (!rex_scalar0) {
410  throw QueryNotSupported(rex_function->getName() +
411  ": expects scalar as first argument");
412  }
413  auto arg0 = translateGeoFunctionArg(
414  rex_scalar0, arg_ti, lindex, with_bounds, with_render_group, expand_geo_col);
415  if (arg_ti.get_type() != kLINESTRING) {
416  throw QueryNotSupported(rex_function->getName() +
417  " expects LINESTRING as first argument");
418  }
419  if (lindex != 0) {
420  throw QueryNotSupported(rex_function->getName() +
421  ": LINESTRING is already indexed");
422  }
423  lindex = -1;
424  return arg0;
425  } else if (rex_function->getName() == "ST_SRID"sv) {
426  CHECK_EQ(size_t(1), rex_function->size());
427  const auto rex_scalar0 =
428  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
429  if (!rex_scalar0) {
430  throw QueryNotSupported(rex_function->getName() +
431  ": expects scalar as first argument");
432  }
433  auto arg0 = translateGeoFunctionArg(
434  rex_scalar0, arg_ti, lindex, with_bounds, with_render_group, expand_geo_col);
435  if (!IS_GEO(arg_ti.get_type())) {
436  throw QueryNotSupported(rex_function->getName() + " expects geometry argument");
437  }
438  return arg0;
439  } else if (rex_function->getName() == "ST_SetSRID"sv) {
440  CHECK_EQ(size_t(2), rex_function->size());
441  const auto rex_scalar0 =
442  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
443  if (!rex_scalar0) {
444  throw QueryNotSupported(rex_function->getName() +
445  ": expects scalar as first argument");
446  }
447  auto arg0 = translateGeoFunctionArg(rex_scalar0,
448  arg_ti,
449  lindex,
450  with_bounds,
451  with_render_group,
452  expand_geo_col,
453  is_projection);
454  if (!IS_GEO(arg_ti.get_type())) {
455  throw QueryNotSupported(rex_function->getName() + " expects geometry argument");
456  }
457  const auto rex_literal =
458  dynamic_cast<const RexLiteral*>(rex_function->getOperand(1));
459  if (!rex_literal) {
460  throw QueryNotSupported(rex_function->getName() +
461  ": second argument is expected to be a literal");
462  }
463  const auto e = translateLiteral(rex_literal);
464  auto ce = std::dynamic_pointer_cast<Analyzer::Constant>(e);
465  if (!ce || !e->get_type_info().is_integer()) {
466  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
467  }
468  int32_t srid = 0;
469  if (e->get_type_info().get_type() == kSMALLINT) {
470  srid = static_cast<int32_t>(ce->get_constval().smallintval);
471  } else if (e->get_type_info().get_type() == kTINYINT) {
472  srid = static_cast<int32_t>(ce->get_constval().tinyintval);
473  } else if (e->get_type_info().get_type() == kINT) {
474  srid = static_cast<int32_t>(ce->get_constval().intval);
475  } else {
476  throw QueryNotSupported(rex_function->getName() + ": expecting integer SRID");
477  }
478  arg_ti.set_input_srid(srid); // Input SRID
479  arg_ti.set_output_srid(srid); // Output SRID is the same - no transform
480  return arg0;
481  } else if (rex_function->getName() == "CastToGeography"sv) {
482  CHECK_EQ(size_t(1), rex_function->size());
483  const auto rex_scalar0 =
484  dynamic_cast<const RexScalar*>(rex_function->getOperand(0));
485  if (!rex_scalar0) {
486  throw QueryNotSupported(rex_function->getName() +
487  ": expects scalar as first argument");
488  }
489  auto arg0 = translateGeoFunctionArg(
490  rex_scalar0, arg_ti, lindex, with_bounds, with_render_group, expand_geo_col);
491  if (!IS_GEO(arg_ti.get_type())) {
492  throw QueryNotSupported(rex_function->getName() + " expects geometry argument");
493  }
494  if (arg_ti.get_output_srid() != 4326) {
495  throw QueryNotSupported(rex_function->getName() +
496  " expects geometry with SRID=4326");
497  }
498  arg_ti.set_subtype(kGEOGRAPHY);
499  return arg0;
500  } else if (rex_function->getName() == "ST_Point"sv) {
501  CHECK_EQ(size_t(2), rex_function->size());
502  arg_ti.set_type(kPOINT);
503  arg_ti.set_subtype(kGEOMETRY);
504  arg_ti.set_input_srid(0);
505  arg_ti.set_output_srid(0);
507 
508  auto coord1 = translateScalarRex(rex_function->getOperand(0));
509  auto coord2 = translateScalarRex(rex_function->getOperand(1));
510  auto d_ti = SQLTypeInfo(kDOUBLE, true);
511  auto cast_coord1 = coord1->add_cast(d_ti);
512  auto cast_coord2 = coord2->add_cast(d_ti);
513  // First try to fold to geo literal
514  auto fold1 = fold_expr(cast_coord1.get());
515  auto fold2 = fold_expr(cast_coord2.get());
516  auto const_coord1 = std::dynamic_pointer_cast<Analyzer::Constant>(fold1);
517  auto const_coord2 = std::dynamic_pointer_cast<Analyzer::Constant>(fold2);
518  if (const_coord1 && const_coord2) {
519  CHECK(const_coord1->get_type_info().get_type() == kDOUBLE);
520  CHECK(const_coord2->get_type_info().get_type() == kDOUBLE);
521  std::string wkt = "POINT(" +
522  std::to_string(const_coord1->get_constval().doubleval) + " " +
523  std::to_string(const_coord2->get_constval().doubleval) + ")";
524  RexLiteral rex_literal{wkt, kTEXT, kNULLT, 0, 0, 0, 0};
525  auto args = translateGeoLiteral(&rex_literal, arg_ti, false);
526  CHECK(arg_ti.get_type() == kPOINT);
527  return args;
528  }
529  // Couldn't fold to geo literal, construct on the fly
530  auto da_ti = SQLTypeInfo(kARRAY, true);
531  da_ti.set_subtype(kDOUBLE);
532  da_ti.set_size(16);
533  auto cast_coords = {cast_coord1, cast_coord2};
534  auto is_local_alloca = !is_projection;
535  auto ae = makeExpr<Analyzer::ArrayExpr>(da_ti, cast_coords, false, is_local_alloca);
536  // cast it to tinyint[16]
537  SQLTypeInfo tia_ti = SQLTypeInfo(kARRAY, true);
538  tia_ti.set_subtype(kTINYINT);
539  tia_ti.set_size(16);
540  return {makeExpr<Analyzer::UOper>(tia_ti, false, kCAST, ae)};
541  } else if (rex_function->getName() == "ST_Centroid"sv) {
542  CHECK_EQ(size_t(1), rex_function->size());
543  arg_ti.set_type(kPOINT);
544  arg_ti.set_subtype(kGEOMETRY);
545  arg_ti.set_input_srid(0);
546  arg_ti.set_output_srid(0);
548 
549  SQLTypeInfo geo_ti;
550  bool with_bounds = false;
551  auto geoargs = translateGeoFunctionArg(
552  rex_function->getOperand(0), geo_ti, lindex, with_bounds, false, false);
553 
554  auto specialized_geofunc = rex_function->getName() + suffix(geo_ti.get_type());
555  if (lindex != 0) {
556  throw QueryNotSupported(rex_function->getName() +
557  " doesn't support indexed LINESTRINGs");
558  }
559  Datum input_compression;
560  input_compression.intval = Geospatial::get_compression_scheme(geo_ti);
561  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
562  Datum input_srid;
563  input_srid.intval = geo_ti.get_input_srid();
564  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid));
565  Datum output_srid;
566  output_srid.intval = geo_ti.get_output_srid();
567  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
568 
569  // TODO: find a better way to get 2 coords from the centroid functions
570  Datum coord_selector;
571  coord_selector.boolval = false;
572  geoargs.push_back(makeExpr<Analyzer::Constant>(kBOOLEAN, false, coord_selector));
573  std::shared_ptr<Analyzer::Expr> coord1 = makeExpr<Analyzer::FunctionOper>(
574  rex_function->getType(), specialized_geofunc, geoargs);
575  geoargs.pop_back();
576  coord_selector.boolval = true;
577  geoargs.push_back(makeExpr<Analyzer::Constant>(kBOOLEAN, false, coord_selector));
578  std::shared_ptr<Analyzer::Expr> coord2 = makeExpr<Analyzer::FunctionOper>(
579  rex_function->getType(), specialized_geofunc, geoargs);
580 
581  auto da_ti = SQLTypeInfo(kARRAY, true);
582  da_ti.set_subtype(kDOUBLE);
583  da_ti.set_size(16);
584  auto centroid_coords = {coord1, coord2};
585  auto is_local_alloca = !is_projection;
586  auto ae =
587  makeExpr<Analyzer::ArrayExpr>(da_ti, centroid_coords, false, is_local_alloca);
588  // cast it to tinyint[16]
589  SQLTypeInfo tia_ti = SQLTypeInfo(kARRAY, true);
590  tia_ti.set_subtype(kTINYINT);
591  tia_ti.set_size(16);
592  return {makeExpr<Analyzer::UOper>(tia_ti, false, kCAST, ae)};
593  } else if (func_resolve(rex_function->getName(),
594  "ST_Intersection"sv,
595  "ST_Difference"sv,
596  "ST_Union"sv,
597  "ST_Buffer"sv)) {
598  CHECK_EQ(size_t(2), rex_function->size());
599  // What geo type will the constructor return? Could be anything.
600  return {translateGeoBinaryConstructor(rex_function, arg_ti, with_bounds)};
601  } else if (func_resolve(rex_function->getName(), "ST_IsEmpty"sv, "ST_IsValid"sv)) {
602  CHECK_EQ(size_t(1), rex_function->size());
603  return {translateGeoPredicate(rex_function, arg_ti, with_bounds)};
604  } else {
605  throw QueryNotSupported("Unsupported argument: " + rex_function->getName());
606  }
607  }
608  const auto rex_literal = dynamic_cast<const RexLiteral*>(rex_scalar);
609  if (rex_literal) {
610  return translateGeoLiteral(rex_literal, arg_ti, with_bounds);
611  }
612  throw QueryNotSupported("Geo function argument not supported");
613 }
614 
615 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateGeoProjection(
616  const RexFunctionOperator* rex_function,
617  SQLTypeInfo& ti,
618  const bool with_bounds) const {
619  int32_t lindex = 0;
620  auto geoargs =
621  translateGeoFunctionArg(rex_function, ti, lindex, false, false, true, true);
622  if (lindex != 0) {
623  throw QueryNotSupported(
624  "Indexed LINESTRING geometries not supported in this context");
625  }
626  return makeExpr<Analyzer::GeoUOper>(
627  Geospatial::GeoBase::GeoOp::kPROJECTION, ti, ti, geoargs);
628 }
629 
630 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateGeoBinaryConstructor(
631  const RexFunctionOperator* rex_function,
632  SQLTypeInfo& ti,
633  const bool with_bounds) const {
634 #ifndef ENABLE_GEOS
635  throw QueryNotSupported(rex_function->getName() +
636  " geo constructor requires enabled GEOS support");
637 #endif
639  if (rex_function->getName() == "ST_Difference"sv) {
641  } else if (rex_function->getName() == "ST_Union"sv) {
643  } else if (rex_function->getName() == "ST_Buffer"sv) {
645  }
646 
649  SQLTypeInfo arg0_ti;
650  SQLTypeInfo arg1_ti;
651  if (func_resolve(rex_function->getName(),
652  "ST_Intersection"sv,
653  "ST_Difference"sv,
654  "ST_Union"sv,
655  "ST_Buffer"sv)) {
656  // First arg: geometry
657  int32_t lindex0 = 0;
658  geoargs0 = translateGeoFunctionArg(
659  rex_function->getOperand(0), arg0_ti, lindex0, false, false, true, true);
660  if (arg0_ti.get_type() == kLINESTRING) {
661  if (lindex0 != 0) {
662  throw QueryNotSupported(
663  "Indexed LINESTRING geometries not supported in this context");
664  }
665  }
666  }
667  if (func_resolve(rex_function->getName(),
668  "ST_Intersection"sv,
669  "ST_Difference"sv,
670  "ST_Union"sv)) {
671  // Second arg: geometry
672  int32_t lindex1 = 0;
673  geoargs1 = translateGeoFunctionArg(
674  rex_function->getOperand(1), arg1_ti, lindex1, false, false, true, true);
675  if (arg1_ti.get_type() == kLINESTRING) {
676  if (lindex1 != 0) {
677  throw QueryNotSupported(
678  "Indexed LINESTRING geometries not supported in this context");
679  }
680  }
681  } else if (func_resolve(rex_function->getName(), "ST_Buffer"sv)) {
682  // Second arg: double scalar
683  auto param_expr = translateScalarRex(rex_function->getOperand(1));
684  arg1_ti = SQLTypeInfo(kDOUBLE, false);
685  if (param_expr->get_type_info().get_type() != kDOUBLE) {
686  param_expr = param_expr->add_cast(arg1_ti);
687  }
688  geoargs1 = {param_expr};
689  }
690 
691  auto srid = ti.get_output_srid();
692  ti = arg0_ti;
694  ti.set_compression(kENCODING_NONE); // Constructed geometries are not compressed
695  ti.set_comp_param(0);
696  if (srid > 0) {
697  ti.set_output_srid(srid); // Requested SRID sent from above
698  }
699  return makeExpr<Analyzer::GeoBinOper>(op, ti, arg0_ti, arg1_ti, geoargs0, geoargs1);
700 }
701 
702 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateGeoPredicate(
703  const RexFunctionOperator* rex_function,
704  SQLTypeInfo& ti,
705  const bool with_bounds) const {
706 #ifndef ENABLE_GEOS
707  throw QueryNotSupported(rex_function->getName() +
708  " geo predicate requires enabled GEOS support");
709 #endif
710  int32_t lindex = 0;
711  SQLTypeInfo arg_ti;
712  auto geoargs = translateGeoFunctionArg(
713  rex_function->getOperand(0), arg_ti, lindex, false, false, true, true);
714  if (lindex != 0) {
715  throw QueryNotSupported(
716  "Indexed LINESTRING geometries not supported in this context");
717  }
718  ti = SQLTypeInfo(kBOOLEAN, false);
719  auto op = (rex_function->getName() == "ST_IsEmpty"sv)
722  return makeExpr<Analyzer::GeoUOper>(op, ti, arg_ti, geoargs);
723 }
724 
725 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateUnaryGeoFunction(
726  const RexFunctionOperator* rex_function) const {
727  CHECK_EQ(size_t(1), rex_function->size());
728 
729  int32_t lindex = 0;
730  std::string specialized_geofunc{rex_function->getName()};
731 
732  // Geo function calls which do not need the coords col but do need cols associated with
733  // physical coords (e.g. ring_sizes / poly_rings)
734  if (rex_function->getName() == "ST_NRings"sv) {
735  SQLTypeInfo arg_ti;
736  auto geoargs = translateGeoFunctionArg(
737  rex_function->getOperand(0), arg_ti, lindex, false, false, true);
738  if (arg_ti.get_type() == kPOLYGON) {
739  CHECK_EQ(geoargs.size(), 2u);
740  geoargs.erase(geoargs.begin(), geoargs.begin() + 1); // remove the coords
741  return makeExpr<Analyzer::FunctionOper>(
742  rex_function->getType(), specialized_geofunc, geoargs);
743  } else if (arg_ti.get_type() == kMULTIPOLYGON) {
744  CHECK_EQ(geoargs.size(), 3u);
745  geoargs.erase(geoargs.begin(), geoargs.begin() + 1); // remove the coords
746  geoargs.erase(geoargs.begin() + 1, geoargs.end()); // remove the poly_rings
747  return makeExpr<Analyzer::FunctionOper>(
748  rex_function->getType(), specialized_geofunc, geoargs);
749  } else {
750  throw QueryNotSupported(rex_function->getName() +
751  " expects a POLYGON or MULTIPOLYGON");
752  }
753  } else if (rex_function->getName() == "ST_NPoints"sv) {
754  SQLTypeInfo arg_ti;
755  auto geoargs = translateGeoFunctionArg(
756  rex_function->getOperand(0), arg_ti, lindex, false, false, true);
757  geoargs.erase(geoargs.begin() + 1, geoargs.end()); // remove all but coords
758  // Add compression information
759  Datum input_compression;
760  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
761  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
762  return makeExpr<Analyzer::FunctionOper>(
763  rex_function->getType(), specialized_geofunc, geoargs);
764  } else if (func_resolve(rex_function->getName(), "ST_Perimeter"sv, "ST_Area"sv)) {
765  SQLTypeInfo arg_ti;
766  auto geoargs = translateGeoFunctionArg(
767  rex_function->getOperand(0), arg_ti, lindex, false, false, true);
768  if (arg_ti.get_type() != kPOLYGON && arg_ti.get_type() != kMULTIPOLYGON) {
769  throw QueryNotSupported(rex_function->getName() +
770  " expects a POLYGON or MULTIPOLYGON");
771  }
772  specialized_geofunc += suffix(arg_ti.get_type());
773  if (arg_ti.get_subtype() == kGEOGRAPHY && arg_ti.get_output_srid() == 4326) {
774  specialized_geofunc += "_Geodesic"s;
775  }
776  // Add compression information
777  Datum input_compression;
778  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
779  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
780  Datum input_srid;
781  input_srid.intval = arg_ti.get_input_srid();
782  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid));
783  Datum output_srid;
784  output_srid.intval = arg_ti.get_output_srid();
785  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
786  return makeExpr<Analyzer::FunctionOper>(
787  rex_function->getType(), specialized_geofunc, geoargs);
788  }
789 
790  // Accessors for poly bounds and render group for in-situ poly render queries
791  if (func_resolve(rex_function->getName(),
792  "MapD_GeoPolyBoundsPtr"sv /* deprecated */,
793  "OmniSci_Geo_PolyBoundsPtr"sv)) {
794  SQLTypeInfo arg_ti;
795  // get geo column plus bounds only (not expanded)
796  auto geoargs = translateGeoFunctionArg(
797  rex_function->getOperand(0), arg_ti, lindex, true, false, false);
798  // this function only works on polys
799  if (!IS_GEO_POLY(arg_ti.get_type())) {
800  throw QueryNotSupported(rex_function->getName() +
801  " expects a POLYGON or MULTIPOLYGON");
802  }
803  // only need the bounds argument (last), discard the rest
804  geoargs.erase(geoargs.begin(), geoargs.end() - 1);
805  // done
806  return makeExpr<Analyzer::FunctionOper>(
807  rex_function->getType(), specialized_geofunc, geoargs);
808  } else if (func_resolve(rex_function->getName(),
809  "MapD_GeoPolyRenderGroup"sv /* deprecated */,
810  "OmniSci_Geo_PolyRenderGroup"sv)) {
811  SQLTypeInfo arg_ti;
812  // get geo column plus render_group only (not expanded)
813  auto geoargs = translateGeoFunctionArg(
814  rex_function->getOperand(0), arg_ti, lindex, false, true, false);
815  // this function only works on polys
816  if (!IS_GEO_POLY(arg_ti.get_type())) {
817  throw QueryNotSupported(rex_function->getName() +
818  " expects a POLYGON or MULTIPOLYGON");
819  }
820  // only need the render_group argument (last), discard the rest
821  geoargs.erase(geoargs.begin(), geoargs.end() - 1);
822  // done
823  return makeExpr<Analyzer::FunctionOper>(
824  rex_function->getType(), specialized_geofunc, geoargs);
825  }
826 
827  // All functions below use geo col as reference and expand it as necessary
828  SQLTypeInfo arg_ti;
829  bool with_bounds = true;
830  auto geoargs = translateGeoFunctionArg(
831  rex_function->getOperand(0), arg_ti, lindex, with_bounds, false, false);
832 
833  if (rex_function->getName() == "ST_SRID"sv) {
834  Datum output_srid;
835  output_srid.intval = arg_ti.get_output_srid();
836  return makeExpr<Analyzer::Constant>(kINT, false, output_srid);
837  }
838 
839  if (func_resolve(
840  rex_function->getName(), "ST_XMin"sv, "ST_YMin"sv, "ST_XMax"sv, "ST_YMax"sv)) {
841  // If type has bounds - use them, otherwise look at coords
842  if (arg_ti.has_bounds()) {
843  if (lindex != 0) {
844  throw QueryNotSupported(rex_function->getName() +
845  " doesn't support indexed LINESTRINGs");
846  }
847  // Only need the bounds argument, discard the rest
848  geoargs.erase(geoargs.begin(), geoargs.end() - 1);
849 
850  // Supply srids too - transformed geo would have a transformed bounding box
851  Datum input_srid;
852  input_srid.intval = arg_ti.get_input_srid();
853  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid));
854  Datum output_srid;
855  output_srid.intval = arg_ti.get_output_srid();
856  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
857 
858  specialized_geofunc += "_Bounds"s;
859  return makeExpr<Analyzer::FunctionOper>(
860  rex_function->getType(), specialized_geofunc, geoargs);
861  }
862  }
863 
864  // All geo function calls translated below only need the coords, extras e.g. ring_sizes
865  // are dropped. Specialize for other/new functions if needed.
866  geoargs.erase(geoargs.begin() + 1, geoargs.end());
867 
868  if (func_resolve(rex_function->getName(), "ST_X"sv, "ST_Y"sv)) {
869  if (arg_ti.get_type() == kLINESTRING) {
870  if (lindex == 0) {
871  throw QueryNotSupported(
872  rex_function->getName() +
873  " expects a POINT, use LINESTRING accessor, e.g. ST_POINTN");
874  }
875  Datum index;
876  index.intval = lindex;
877  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, index));
878  } else if (arg_ti.get_type() != kPOINT) {
879  throw QueryNotSupported(rex_function->getName() + " expects a POINT");
880  }
881  specialized_geofunc += suffix(arg_ti.get_type());
882  } else if (rex_function->getName() == "ST_Length"sv) {
883  if (arg_ti.get_type() != kLINESTRING || lindex != 0) {
884  throw QueryNotSupported(rex_function->getName() + " expects unindexed LINESTRING");
885  }
886  specialized_geofunc += suffix(arg_ti.get_type());
887  if (arg_ti.get_subtype() == kGEOGRAPHY && arg_ti.get_output_srid() == 4326) {
888  specialized_geofunc += "_Geodesic"s;
889  }
890  }
891 
892  // Add input compression mode and SRID args to enable on-the-fly
893  // decompression/transforms
894  Datum input_compression;
895  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
896  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
897  Datum input_srid;
898  input_srid.intval = arg_ti.get_input_srid();
899  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid));
900 
901  // Add output SRID arg to enable on-the-fly transforms
902  Datum output_srid;
903  output_srid.intval = arg_ti.get_output_srid();
904  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
905 
906  return makeExpr<Analyzer::FunctionOper>(
907  rex_function->getType(), specialized_geofunc, geoargs);
908 }
909 
910 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateBinaryGeoFunction(
911  const RexFunctionOperator* rex_function) const {
912  auto function_name = rex_function->getName();
913  auto return_type = rex_function->getType();
914 
915  if (function_name == "ST_Overlaps"sv) {
916  // Overlaps join is the only implementation supported for now, only translate bounds
917  CHECK_EQ(size_t(2), rex_function->size());
918  auto extract_geo_bounds_from_input =
919  [this, &rex_function](const size_t index) -> std::shared_ptr<Analyzer::Expr> {
920  const auto rex_input =
921  dynamic_cast<const RexInput*>(rex_function->getOperand(index));
922  if (rex_input) {
923  SQLTypeInfo ti;
924  const auto exprs = translateGeoColumn(rex_input, ti, true, false, false);
925  CHECK_GT(exprs.size(), size_t(0));
926  if (ti.get_type() == kPOINT) {
927  throw std::runtime_error("ST_Overlaps is not supported for point arguments.");
928  } else {
929  return exprs.back();
930  }
931  } else {
932  throw std::runtime_error(
933  "Only inputs are supported as arguments to ST_Overlaps for now.");
934  }
935  };
936  std::vector<std::shared_ptr<Analyzer::Expr>> geo_args;
937  geo_args.push_back(extract_geo_bounds_from_input(0));
938  geo_args.push_back(extract_geo_bounds_from_input(1));
939 
940  return makeExpr<Analyzer::FunctionOper>(return_type, function_name, geo_args);
941  }
942 
943  bool swap_args = false;
944  bool with_bounds = false;
945  bool negate_result = false;
946  Analyzer::ExpressionPtr threshold_expr = nullptr;
947  if (function_name == "ST_DWithin"sv) {
948  CHECK_EQ(size_t(3), rex_function->size());
949  function_name = "ST_Distance";
950  return_type = SQLTypeInfo(kDOUBLE, false);
951  // Inject ST_DWithin's short-circuiting threshold into ST_MaxDistance
952  threshold_expr = translateScalarRex(rex_function->getOperand(2));
953  } else if (function_name == "ST_DFullyWithin"sv) {
954  CHECK_EQ(size_t(3), rex_function->size());
955  function_name = "ST_MaxDistance";
956  return_type = SQLTypeInfo(kDOUBLE, false);
957  // TODO: inject ST_DFullyWithin's short-circuiting threshold into ST_MaxDistance
958  threshold_expr = nullptr;
959  } else if (function_name == "ST_Distance"sv) {
960  // TODO: pick up an outside short-circuiting threshold and inject into ST_Distance
961  threshold_expr = nullptr;
962  } else if (function_name == "ST_MaxDistance"sv) {
963  // TODO: pick up an outside short-circuiting threshold and inject into ST_MaxDistance
964  threshold_expr = nullptr;
965  } else {
966  CHECK_EQ(size_t(2), rex_function->size());
967  }
968  if (function_name == "ST_Within"sv) {
969  function_name = "ST_Contains";
970  swap_args = true;
971  } else if (function_name == "ST_Disjoint"sv) {
972  function_name = "ST_Intersects";
973  negate_result = true;
974  }
975  if (func_resolve(
976  function_name, "ST_Contains"sv, "ST_Intersects"sv, "ST_Approx_Overlaps"sv)) {
977  with_bounds = true;
978  }
979 
980  std::vector<std::shared_ptr<Analyzer::Expr>> geoargs;
981  SQLTypeInfo arg0_ti;
982  SQLTypeInfo arg1_ti;
983  int32_t lindex0 = 0;
984  int32_t lindex1 = 0;
985 
986  auto geoargs0 = translateGeoFunctionArg(rex_function->getOperand(swap_args ? 1 : 0),
987  arg0_ti,
988  lindex0,
989  with_bounds,
990  false,
991  false);
992  if (arg0_ti.get_type() == kLINESTRING) {
993  Datum index;
994  index.intval = lindex0;
995  geoargs0.push_back(makeExpr<Analyzer::Constant>(kINT, false, index));
996  }
997  geoargs.insert(geoargs.end(), geoargs0.begin(), geoargs0.end());
998  auto geoargs1 = translateGeoFunctionArg(rex_function->getOperand(swap_args ? 0 : 1),
999  arg1_ti,
1000  lindex1,
1001  with_bounds,
1002  false,
1003  false);
1004  if (arg1_ti.get_type() == kLINESTRING) {
1005  Datum index;
1006  index.intval = lindex1;
1007  geoargs1.push_back(makeExpr<Analyzer::Constant>(kINT, false, index));
1008  }
1009  geoargs.insert(geoargs.end(), geoargs1.begin(), geoargs1.end());
1010 
1011  if (arg0_ti.get_subtype() != kNULLT && arg0_ti.get_subtype() != arg1_ti.get_subtype()) {
1012  throw QueryNotSupported(rex_function->getName() +
1013  " accepts either two GEOGRAPHY or two GEOMETRY arguments");
1014  }
1015  // Check SRID match if at least one is set/valid
1016  if ((arg0_ti.get_output_srid() > 0 || arg1_ti.get_output_srid() > 0) &&
1017  arg0_ti.get_output_srid() != arg1_ti.get_output_srid()) {
1018  throw QueryNotSupported(rex_function->getName() + " cannot accept different SRIDs");
1019  }
1020 
1021  if (function_name == "ST_Contains"sv) {
1022  const bool lhs_is_literal =
1023  geoargs1.size() == 1 &&
1024  std::dynamic_pointer_cast<const Analyzer::Constant>(geoargs1.front()) != nullptr;
1025  const bool lhs_is_point = arg1_ti.get_type() == kPOINT;
1026  if (!lhs_is_literal && lhs_is_point &&
1027  arg0_ti.get_compression() == kENCODING_GEOINT &&
1028  arg0_ti.get_input_srid() == arg0_ti.get_output_srid() &&
1029  arg0_ti.get_compression() == arg1_ti.get_compression() &&
1030  arg1_ti.get_input_srid() == arg1_ti.get_output_srid()) {
1031  // use the compressed version of ST_Contains
1032  function_name = "ST_cContains";
1033  }
1034  }
1035 
1036  std::string specialized_geofunc{function_name + suffix(arg0_ti.get_type()) +
1037  suffix(arg1_ti.get_type())};
1038 
1039  if (arg0_ti.get_subtype() == kGEOGRAPHY && arg0_ti.get_output_srid() == 4326) {
1040  // Need to call geodesic runtime functions
1041  if (function_name == "ST_Distance"sv) {
1042  if ((arg0_ti.get_type() == kPOINT ||
1043  (arg0_ti.get_type() == kLINESTRING && lindex0 != 0)) &&
1044  (arg1_ti.get_type() == kPOINT ||
1045  (arg1_ti.get_type() == kLINESTRING && lindex1 != 0))) {
1046  // Geodesic distance between points (or indexed linestrings)
1047  specialized_geofunc += "_Geodesic"s;
1048  } else {
1049  throw QueryNotSupported(function_name +
1050  " currently doesn't accept non-POINT geographies");
1051  }
1052  } else if (rex_function->getName() == "ST_Contains"sv) {
1053  // We currently don't have a geodesic implementation of ST_Contains,
1054  // allowing calls to a [less precise] cartesian implementation.
1055  } else {
1056  throw QueryNotSupported(function_name + " doesn't accept geographies");
1057  }
1058  } else if (function_name == "ST_Distance"sv && rex_function->size() == 3) {
1059  if (arg0_ti.get_type() == kPOINT && arg1_ti.get_type() == kPOINT) {
1060  // Cartesian distance between points used by ST_DWithin - switch to faster Squared
1061  specialized_geofunc += "_Squared"s;
1062  }
1063  }
1064 
1065  // Add first input's compression mode and SRID args to enable on-the-fly
1066  // decompression/transforms
1067  Datum input_compression0;
1068  input_compression0.intval = Geospatial::get_compression_scheme(arg0_ti);
1069  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression0));
1070  Datum input_srid0;
1071  input_srid0.intval = arg0_ti.get_input_srid();
1072  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid0));
1073 
1074  // Add second input's compression mode and SRID args to enable on-the-fly
1075  // decompression/transforms
1076  Datum input_compression1;
1077  input_compression1.intval = Geospatial::get_compression_scheme(arg1_ti);
1078  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression1));
1079  Datum input_srid1;
1080  input_srid1.intval = arg1_ti.get_input_srid();
1081  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid1));
1082 
1083  // Add output SRID arg to enable on-the-fly transforms
1084  Datum output_srid;
1085  output_srid.intval = arg0_ti.get_output_srid();
1086  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
1087 
1088  // Some geo distance functions will be injected with a short-circuit threshold.
1089  // Threshold value would come from Geo comparison operations or from other outer
1090  // geo operations, e.g. ST_DWithin
1091  // At this point, only ST_Distance_LineString_LineString requires a threshold arg.
1092  // TODO: Other combinations that involve LINESTRING, POLYGON and MULTIPOLYGON args
1093  // TODO: Inject threshold into ST_MaxDistance
1094  if (function_name == "ST_Distance"sv && arg0_ti.get_subtype() != kGEOGRAPHY &&
1095  (arg0_ti.get_type() != kPOINT || arg1_ti.get_type() != kPOINT)) {
1096  if (threshold_expr) {
1097  if (threshold_expr->get_type_info().get_type() != kDOUBLE) {
1098  const auto& threshold_ti = SQLTypeInfo(kDOUBLE, false);
1099  threshold_expr = threshold_expr->add_cast(threshold_ti);
1100  }
1101  threshold_expr = fold_expr(threshold_expr.get());
1102  } else {
1103  Datum d;
1104  d.doubleval = 0.0;
1105  threshold_expr = makeExpr<Analyzer::Constant>(kDOUBLE, false, d);
1106  }
1107  geoargs.push_back(threshold_expr);
1108  }
1109 
1110  auto result =
1111  makeExpr<Analyzer::FunctionOper>(return_type, specialized_geofunc, geoargs);
1112  if (negate_result) {
1113  return makeExpr<Analyzer::UOper>(kBOOLEAN, kNOT, result);
1114  }
1115  return result;
1116 }
1117 
1118 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateTernaryGeoFunction(
1119  const RexFunctionOperator* rex_function) const {
1120  CHECK_EQ(size_t(3), rex_function->size());
1121 
1122  auto distance_expr = translateScalarRex(rex_function->getOperand(2));
1123  const auto& distance_ti = SQLTypeInfo(kDOUBLE, false);
1124  if (distance_expr->get_type_info().get_type() != kDOUBLE) {
1125  distance_expr = distance_expr->add_cast(distance_ti);
1126  }
1127 
1128  auto function_name = rex_function->getName();
1129  if (function_name == "ST_DWithin"sv) {
1130  auto return_type = rex_function->getType();
1131  bool swap_args = false;
1132  bool with_bounds = true;
1133  SQLTypeInfo arg0_ti;
1134  SQLTypeInfo arg1_ti;
1135  int32_t lindex0 = 0;
1136  int32_t lindex1 = 0;
1137 
1138  auto geoargs0 = translateGeoFunctionArg(
1139  rex_function->getOperand(0), arg0_ti, lindex0, with_bounds, false, false);
1140  if (arg0_ti.get_type() == kLINESTRING) {
1141  Datum index;
1142  index.intval = lindex0;
1143  geoargs0.push_back(makeExpr<Analyzer::Constant>(kINT, false, index));
1144  }
1145  auto geoargs1 = translateGeoFunctionArg(
1146  rex_function->getOperand(1), arg1_ti, lindex1, with_bounds, false, false);
1147  if (arg1_ti.get_type() == kLINESTRING) {
1148  Datum index;
1149  index.intval = lindex1;
1150  geoargs1.push_back(makeExpr<Analyzer::Constant>(kINT, false, index));
1151  }
1152  if (arg0_ti.get_subtype() == kGEOMETRY &&
1153  arg0_ti.get_subtype() != arg1_ti.get_subtype()) {
1154  throw QueryNotSupported(rex_function->getName() +
1155  " accepts only accepts GEOMETRY arguments");
1156  }
1157  // Check SRID match if at least one is set/valid
1158  if ((arg0_ti.get_output_srid() > 0 || arg1_ti.get_output_srid() > 0) &&
1159  arg0_ti.get_output_srid() != arg1_ti.get_output_srid()) {
1160  throw QueryNotSupported(rex_function->getName() + " cannot accept different SRIDs");
1161  }
1162 
1163  if ((arg1_ti.get_type() == kPOINT && arg0_ti.get_type() != kPOINT) ||
1164  (arg1_ti.get_type() == kLINESTRING && arg0_ti.get_type() == kPOLYGON) ||
1165  (arg1_ti.get_type() == kPOLYGON && arg0_ti.get_type() == kMULTIPOLYGON)) {
1166  // Swap arguments and use single implementation per arg pair
1167  swap_args = true;
1168  }
1169 
1170  // First input's compression mode and SRID args to enable on-the-fly
1171  // decompression/transforms
1172  Datum input_compression0;
1173  input_compression0.intval = Geospatial::get_compression_scheme(arg0_ti);
1174  Datum input_srid0;
1175  input_srid0.intval = arg0_ti.get_input_srid();
1176 
1177  // Second input's compression mode and SRID args to enable on-the-fly
1178  // decompression/transforms
1179  Datum input_compression1;
1180  input_compression1.intval = Geospatial::get_compression_scheme(arg1_ti);
1181  Datum input_srid1;
1182  input_srid1.intval = arg1_ti.get_input_srid();
1183 
1184  // Output SRID arg to enable on-the-fly transforms
1185  Datum output_srid;
1186  output_srid.intval = arg0_ti.get_output_srid();
1187 
1188  std::string specialized_geofunc{function_name};
1189  std::vector<std::shared_ptr<Analyzer::Expr>> geoargs;
1190  if (swap_args) {
1191  specialized_geofunc += suffix(arg1_ti.get_type()) + suffix(arg0_ti.get_type());
1192  geoargs.insert(geoargs.end(), geoargs1.begin(), geoargs1.end());
1193  geoargs.insert(geoargs.end(), geoargs0.begin(), geoargs0.end());
1194  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression1));
1195  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid1));
1196  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression0));
1197  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid0));
1198  } else {
1199  specialized_geofunc += suffix(arg0_ti.get_type()) + suffix(arg1_ti.get_type());
1200  geoargs.insert(geoargs.end(), geoargs0.begin(), geoargs0.end());
1201  geoargs.insert(geoargs.end(), geoargs1.begin(), geoargs1.end());
1202  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression0));
1203  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid0));
1204  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression1));
1205  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid1));
1206  }
1207  geoargs.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
1208  // Also add the within distance
1209  geoargs.push_back(distance_expr);
1210 
1211  auto result =
1212  makeExpr<Analyzer::FunctionOper>(return_type, specialized_geofunc, geoargs);
1213  return result;
1214  }
1215 
1216  // Otherwise translate function as binary geo to get distance,
1217  // with optional short-circuiting threshold held in the third operand
1218  const auto geo_distance = translateBinaryGeoFunction(rex_function);
1219  // and generate the comparison
1220  return makeExpr<Analyzer::BinOper>(kBOOLEAN, kLE, kONE, geo_distance, distance_expr);
1221 }
1222 
1223 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateGeoComparison(
1224  const RexOperator* rex_operator) const {
1225  if (rex_operator->size() != size_t(2)) {
1226  return nullptr;
1227  }
1228 
1229  const auto rex_operand = rex_operator->getOperand(0);
1230  const auto rex_function = dynamic_cast<const RexFunctionOperator*>(rex_operand);
1231  if (!rex_function) {
1232  return nullptr;
1233  }
1234  if (rex_function->getName() == "ST_Distance"sv && rex_operator->getOperator() == kLE) {
1235  // TODO: fixup
1236  return nullptr;
1237  /*
1238  auto ti = rex_operator->getType();
1239  std::vector<std::unique_ptr<const RexScalar>> st_dwithin_operands;
1240  st_dwithin_operands.emplace_back(rex_function->getOperandAndRelease(0));
1241  st_dwithin_operands.emplace_back(rex_function->getOperandAndRelease(1));
1242  st_dwithin_operands.emplace_back(rex_operator->getOperandAndRelease(1));
1243  std::unique_ptr<RexFunctionOperator> st_dwithin(
1244  new RexFunctionOperator("ST_DWithin", st_dwithin_operands, ti));
1245  return translateTernaryGeoFunction(st_dwithin.get());
1246  */
1247  }
1248 
1249  return nullptr;
1250 }
1251 
1252 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateFunctionWithGeoArg(
1253  const RexFunctionOperator* rex_function) const {
1254  int32_t lindex = 0;
1255  std::string specialized_geofunc{rex_function->getName()};
1256  if (func_resolve(rex_function->getName(),
1257  "convert_meters_to_pixel_width"sv,
1258  "convert_meters_to_pixel_height"sv)) {
1259  CHECK_EQ(rex_function->size(), 6u);
1260  SQLTypeInfo arg_ti;
1261  std::vector<std::shared_ptr<Analyzer::Expr>> args;
1262  args.push_back(translateScalarRex(rex_function->getOperand(0)));
1263  auto geoargs = translateGeoFunctionArg(
1264  rex_function->getOperand(1), arg_ti, lindex, false, true, false);
1265  // only works on points
1266  if (arg_ti.get_type() != kPOINT) {
1267  throw QueryNotSupported(rex_function->getName() +
1268  " expects a point for the second argument");
1269  }
1270 
1271  args.insert(args.end(), geoargs.begin(), geoargs.begin() + 1);
1272 
1273  // Add compression information
1274  Datum input_compression;
1275  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
1276  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
1277  if (arg_ti.get_input_srid() != 4326) {
1278  throw QueryNotSupported(
1279  rex_function->getName() +
1280  " currently only supports points of with SRID WGS84/EPSG:4326");
1281  }
1282  Datum input_srid;
1283  input_srid.intval = arg_ti.get_input_srid();
1284  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_srid));
1285  Datum output_srid;
1286  // Forcing web-mercator projection for now
1287  // TODO(croot): check that the input-to-output conversion routines exist?
1288  output_srid.intval =
1289  arg_ti.get_output_srid() != 900913 ? 900913 : arg_ti.get_output_srid();
1290  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, output_srid));
1291 
1292  args.push_back(translateScalarRex(rex_function->getOperand(2)));
1293  args.push_back(translateScalarRex(rex_function->getOperand(3)));
1294  args.push_back(translateScalarRex(rex_function->getOperand(4)));
1295  args.push_back(translateScalarRex(rex_function->getOperand(5)));
1296  return makeExpr<Analyzer::FunctionOper>(
1297  rex_function->getType(), specialized_geofunc, args);
1298  } else if (rex_function->getName() == "is_point_in_view"sv) {
1299  CHECK_EQ(rex_function->size(), 5u);
1300  SQLTypeInfo arg_ti;
1301  std::vector<std::shared_ptr<Analyzer::Expr>> args;
1302  auto geoargs = translateGeoFunctionArg(
1303  rex_function->getOperand(0), arg_ti, lindex, false, true, false);
1304  // only works on points
1305  if (arg_ti.get_type() != kPOINT) {
1306  throw QueryNotSupported(rex_function->getName() +
1307  " expects a point for the second argument");
1308  }
1309 
1310  args.insert(args.end(), geoargs.begin(), geoargs.begin() + 1);
1311 
1312  // Add compression information
1313  Datum input_compression;
1314  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
1315  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
1316  if (arg_ti.get_input_srid() != 4326) {
1317  throw QueryNotSupported(
1318  rex_function->getName() +
1319  " currently only supports points of with SRID WGS84/EPSG:4326");
1320  }
1321  args.push_back(translateScalarRex(rex_function->getOperand(1)));
1322  args.push_back(translateScalarRex(rex_function->getOperand(2)));
1323  args.push_back(translateScalarRex(rex_function->getOperand(3)));
1324  args.push_back(translateScalarRex(rex_function->getOperand(4)));
1325  return makeExpr<Analyzer::FunctionOper>(
1326  rex_function->getType(), specialized_geofunc, args);
1327  } else if (rex_function->getName() == "is_point_size_in_view"sv) {
1328  CHECK_EQ(rex_function->size(), 6u);
1329  SQLTypeInfo arg_ti;
1330  std::vector<std::shared_ptr<Analyzer::Expr>> args;
1331  auto geoargs = translateGeoFunctionArg(
1332  rex_function->getOperand(0), arg_ti, lindex, false, true, false);
1333  // only works on points
1334  if (arg_ti.get_type() != kPOINT) {
1335  throw QueryNotSupported(rex_function->getName() +
1336  " expects a point for the second argument");
1337  }
1338 
1339  args.insert(args.end(), geoargs.begin(), geoargs.begin() + 1);
1340 
1341  // Add compression information
1342  Datum input_compression;
1343  input_compression.intval = Geospatial::get_compression_scheme(arg_ti);
1344  args.push_back(makeExpr<Analyzer::Constant>(kINT, false, input_compression));
1345  if (arg_ti.get_input_srid() != 4326) {
1346  throw QueryNotSupported(
1347  rex_function->getName() +
1348  " currently only supports points of with SRID WGS84/EPSG:4326");
1349  }
1350  args.push_back(translateScalarRex(rex_function->getOperand(1)));
1351  args.push_back(translateScalarRex(rex_function->getOperand(2)));
1352  args.push_back(translateScalarRex(rex_function->getOperand(3)));
1353  args.push_back(translateScalarRex(rex_function->getOperand(4)));
1354  args.push_back(translateScalarRex(rex_function->getOperand(5)));
1355  return makeExpr<Analyzer::FunctionOper>(
1356  rex_function->getType(), specialized_geofunc, args);
1357  }
1358  CHECK(false);
1359  return nullptr;
1360 }
1361 
1362 std::shared_ptr<Analyzer::Expr> RelAlgTranslator::translateGeoOverlapsOper(
1363  const RexOperator* rex_operator) const {
1364  CHECK_EQ(rex_operator->size(), 2u);
1365 
1366  auto translate_input =
1367  [&](const RexScalar* operand) -> std::shared_ptr<Analyzer::Expr> {
1368  const auto input = dynamic_cast<const RexInput*>(operand);
1369  CHECK(input);
1370 
1371  SQLTypeInfo ti;
1372  const auto exprs = translateGeoColumn(input, ti, true, false, false);
1373  CHECK_GT(exprs.size(), 0u);
1374  if (ti.get_type() == kPOINT) {
1375  return exprs.front();
1376  } else {
1377  return exprs.back();
1378  }
1379  };
1380 
1381  SQLQualifier sql_qual{kONE};
1382  SQLOps sql_op{kOVERLAPS};
1383  return makeExpr<Analyzer::BinOper>(SQLTypeInfo(kBOOLEAN, false),
1384  false,
1385  sql_op,
1386  sql_qual,
1387  translate_input(rex_operator->getOperand(1)),
1388  translate_input(rex_operator->getOperand(0)));
1389 }
int8_t tinyintval
Definition: sqltypes.h:206
HOST DEVICE SQLTypes get_subtype() const
Definition: sqltypes.h:315
void set_compression(EncodingType c)
Definition: sqltypes.h:414
void set_size(int s)
Definition: sqltypes.h:412
#define CHECK_EQ(x, y)
Definition: Logger.h:205
auto func_resolve
SQLTypes
Definition: sqltypes.h:37
tuple d
Definition: test_fsi.py:9
std::vector< uint8_t > compress_coords(std::vector< double > &coords, const SQLTypeInfo &ti)
Definition: Compression.cpp:52
#define SPIMAP_GEO_PHYSICAL_INPUT(c, i)
Definition: Catalog.h:75
SQLQualifier
Definition: sqldefs.h:69
std::shared_ptr< Analyzer::Expr > translateScalarRex(const RexScalar *rex) const
const SQLTypeInfo & getType() const
bool boolval
Definition: sqltypes.h:205
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
HOST DEVICE void set_subtype(SQLTypes st)
Definition: sqltypes.h:405
#define CHECK_GE(x, y)
Definition: Logger.h:210
Definition: sqldefs.h:49
std::shared_ptr< Analyzer::Expr > ExpressionPtr
Definition: Analyzer.h:180
HOST DEVICE SQLTypes get_type() const
Definition: sqltypes.h:314
#define CHECK_GT(x, y)
Definition: Logger.h:209
std::shared_ptr< Analyzer::Expr > translateGeoProjection(const RexFunctionOperator *, SQLTypeInfo &, const bool with_bounds) const
int32_t intval
Definition: sqltypes.h:208
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:394
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:408
unsigned getIndex() const
static std::shared_ptr< Analyzer::Expr > translateLiteral(const RexLiteral *)
SQLOps getOperator() const
bool has_bounds() const
Definition: sqltypes.h:383
const ColumnDescriptor * getMetadataForColumnBySpi(const int tableId, const size_t spi) const
Definition: Catalog.cpp:1548
void set_output_srid(int s)
Definition: sqltypes.h:410
const std::unordered_map< const RelAlgNode *, int > input_to_nest_level_
void set_comp_param(int p)
Definition: sqltypes.h:415
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:907
#define CHECK_LT(x, y)
Definition: Logger.h:207
Definition: sqltypes.h:51
Definition: sqldefs.h:69
HOST DEVICE EncodingType get_compression() const
Definition: sqltypes.h:322
const RelAlgNode * getSourceNode() const
std::shared_ptr< Analyzer::Expr > translateGeoBinaryConstructor(const RexFunctionOperator *, SQLTypeInfo &, const bool with_bounds) const
HOST DEVICE int get_input_srid() const
Definition: sqltypes.h:318
#define CHECK(condition)
Definition: Logger.h:197
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:182
std::shared_ptr< Analyzer::Expr > translateFunctionWithGeoArg(const RexFunctionOperator *) const
Definition: sqltypes.h:44
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:350
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:245
Definition: sqldefs.h:39
std::shared_ptr< Analyzer::Expr > fold_expr(const Analyzer::Expr *expr)
std::vector< std::shared_ptr< Analyzer::Expr > > translateGeoFunctionArg(const RexScalar *rex_scalar, SQLTypeInfo &arg_ti, int32_t &lindex, const bool with_bounds, const bool with_render_group, const bool expand_geo_col, const bool is_projection=false) const
double doubleval
Definition: sqltypes.h:211
const Catalog_Namespace::Catalog & cat_
HOST DEVICE int get_output_srid() const
Definition: sqltypes.h:320
std::shared_ptr< Analyzer::Expr > translateGeoPredicate(const RexFunctionOperator *, SQLTypeInfo &, const bool with_bounds) const
#define IS_GEO_POLY(T)
Definition: sqltypes.h:249
HOST DEVICE void set_type(SQLTypes t)
Definition: sqltypes.h:404