Skip to content

Commit 633cd9e

Browse files
committed
Add support for scisql_s2PtInCircle spatial join
Previously spatial joins were limited to scisql_angSep constructs. This allows queries to be expressed in equivalent scisql_s2PtInCircle form. Previous constraints remain (coordinate pairs must come from different table refs, but their columns must come from the same table ref, both column pairs must be found in director tables, and the directors must have the same partitioning). This includes a minor refactor to split checks for the two scisql functions into separate helper functions.
1 parent 8fa9342 commit 633cd9e

1 file changed

Lines changed: 112 additions & 42 deletions

File tree

src/qana/RelationGraph.cc

Lines changed: 112 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,10 @@ size_t addEqEdge(std::string const& ca, std::string const& cb, bool outer, Verte
280280
return 0;
281281
}
282282

283-
/// `getAngSepFunc` returns a pointer to the IR node for the `scisql_angSep`
284-
/// call embedded in the given value expression if there is one, and null
285-
/// otherwise.
286-
FuncExpr::Ptr getAngSepFunc(ValueExprPtr const& ve) {
283+
/// `getFunc` returns a pointer to the IR node for the call to the function
284+
/// with the given name and parameter count embedded in the given value
285+
/// expression if there is one, and null otherwise.
286+
FuncExpr::Ptr getFunc(ValueExprPtr const& ve, std::string const& name, size_t numParams) {
287287
LOGS(_log, LOG_LVL_TRACE, __FUNCTION__);
288288
FuncExpr::Ptr fe;
289289
if (!ve || ve->getFactorOps().size() != 1) {
@@ -294,12 +294,107 @@ FuncExpr::Ptr getAngSepFunc(ValueExprPtr const& ve) {
294294
return fe;
295295
}
296296
fe = vf->getFuncExpr();
297-
if (!fe || fe->getName() != "scisql_angSep" || fe->params.size() != 4) {
297+
if (!fe || fe->getName() != name || fe->params.size() != numParams) {
298298
return FuncExpr::Ptr();
299299
}
300300
return fe;
301301
}
302302

303+
/// Helper to retrieve a scisql_angSep function from the given value expression
304+
FuncExpr::Ptr getAngSepFunc(ValueExprPtr const& ve) { return getFunc(ve, "scisql_angSep", 4); }
305+
306+
/// Helper to retrieve a scisql_s2PtInCircle function from the given value expression
307+
FuncExpr::Ptr getPtInCircleFunc(ValueExprPtr const& ve) { return getFunc(ve, "scisql_s2PtInCircle", 5); }
308+
309+
/**
310+
* @brief Get `scisql_angSep` call and numeric threshold in the given comparison predicate.
311+
*
312+
* Handles `scisql_angSep(lon1, lat1, lon2, lat2) OP threshold` in either operand order.
313+
*
314+
* @param cp The comparison predicate to inspect.
315+
* @return std::pair<FuncExpr::Ptr, double> Embedded scisql_angSep() and the threshold it
316+
* is compared against, or {nullptr, NaN} if inadmissable.
317+
* @throws QueryNotEvaluableError if the threshold is a numeric constant but is negative.
318+
*/
319+
std::pair<FuncExpr::Ptr, double> getAngSepJoin(CompPredicate const& cp) {
320+
FuncExpr::Ptr fe;
321+
double angSep = std::numeric_limits<double>::quiet_NaN();
322+
switch (cp.op) {
323+
case query::CompPredicate::LESS_THAN_OP: // fallthrough
324+
case query::CompPredicate::LESS_THAN_OR_EQUALS_OP:
325+
fe = getAngSepFunc(cp.left);
326+
angSep = cp.right->getNumericConst();
327+
break;
328+
case query::CompPredicate::GREATER_THAN_OP: // fallthrough
329+
case query::CompPredicate::GREATER_THAN_OR_EQUALS_OP:
330+
angSep = cp.left->getNumericConst();
331+
fe = getAngSepFunc(cp.right);
332+
break;
333+
case query::CompPredicate::EQUALS_OP:
334+
case query::CompPredicate::NULL_SAFE_EQUALS_OP:
335+
case query::CompPredicate::NOT_EQUALS_OP:
336+
case query::CompPredicate::NOT_EQUALS_OP_ALT:
337+
// While this doesn't make much sense numerically (floating
338+
// point numbers are being tested for equality), it is
339+
// technically evaluable.
340+
fe = getAngSepFunc(cp.left);
341+
if (!fe) {
342+
angSep = cp.left->getNumericConst();
343+
fe = getAngSepFunc(cp.right);
344+
} else {
345+
angSep = cp.right->getNumericConst();
346+
}
347+
break;
348+
default:
349+
throw QueryNotEvaluableError("Unhandled comparison operator:" + std::to_string(cp.op));
350+
}
351+
352+
if (fe && angSep < 0.0) {
353+
throw QueryNotEvaluableError(
354+
"scisql_angSep() comparison threshold must be a non-negative "
355+
"number, got " +
356+
std::to_string(angSep));
357+
}
358+
return {fe, angSep};
359+
}
360+
361+
/**
362+
* @brief Get `scisql_s2PtInCircle` call and its radius embedded in the given comparison predicate.
363+
*
364+
* @param cp The comparison predicate to inspect.
365+
* @return std::pair<FuncExpr::Ptr, double> Embedded scisql_s2PtInCircle() and its radius or {nullptr, NaN} if
366+
* the predicate is not admissible or the radius is not a numeric constant.
367+
* @throws QueryNotEvaluableError if the radius is a numeric constant but is negative
368+
*/
369+
std::pair<FuncExpr::Ptr, double> getPtInCircleJoin(CompPredicate const& cp) {
370+
auto const notFound = std::make_pair(FuncExpr::Ptr(), std::numeric_limits<double>::quiet_NaN());
371+
if (cp.op != query::CompPredicate::EQUALS_OP) return notFound;
372+
FuncExpr::Ptr fe = getPtInCircleFunc(cp.left);
373+
double comparisonValue = std::numeric_limits<double>::quiet_NaN();
374+
if (fe) {
375+
comparisonValue = cp.right->getNumericConst();
376+
} else {
377+
fe = getPtInCircleFunc(cp.right);
378+
if (!fe) {
379+
return notFound;
380+
}
381+
comparisonValue = cp.left->getNumericConst();
382+
}
383+
384+
if (comparisonValue != 1.0) {
385+
// scisql_s2PtInCircle() only returns 0 or 1, and this is only admissable when = 1.
386+
return notFound;
387+
}
388+
389+
double const radius = fe->params.back()->getNumericConst();
390+
if (radius < 0.0) {
391+
throw QueryNotEvaluableError("scisql_s2PtInCircle() radius must be a non-negative number, got " +
392+
std::to_string(radius));
393+
}
394+
395+
return {fe, radius};
396+
}
397+
303398
/// `getEqColumnRefs` returns the pair of column references in the equality
304399
/// predicate embedded in the given boolean factor. If that is not what
305400
/// the given boolean term corresponds to, a pair of nulls is returned instead.
@@ -523,6 +618,7 @@ size_t RelationGraph::_addSpEdges(BoolTerm::Ptr bt) {
523618
}
524619
return numEdges;
525620
}
621+
526622
// Look for a BoolFactor containing a single CompPredicate.
527623
BoolFactor::Ptr bf = std::dynamic_pointer_cast<BoolFactor>(bt);
528624
if (!bf || bf->_terms.size() != 1) {
@@ -532,46 +628,20 @@ size_t RelationGraph::_addSpEdges(BoolTerm::Ptr bt) {
532628
if (!cp) {
533629
return 0;
534630
}
535-
// Try to extract a scisql_angSep() call and a numeric constant
536-
// from the comparison predicate.
537-
FuncExpr::Ptr fe;
538-
double angSep = std::numeric_limits<double>::quiet_NaN();
539-
switch (cp->op) {
540-
case query::CompPredicate::LESS_THAN_OP: // fallthrough
541-
case query::CompPredicate::LESS_THAN_OR_EQUALS_OP:
542-
fe = getAngSepFunc(cp->left);
543-
angSep = cp->right->getNumericConst();
544-
break;
545-
case query::CompPredicate::GREATER_THAN_OP: // fallthrough
546-
case query::CompPredicate::GREATER_THAN_OR_EQUALS_OP:
547-
angSep = cp->left->getNumericConst();
548-
fe = getAngSepFunc(cp->right);
549-
break;
550-
case query::CompPredicate::EQUALS_OP:
551-
case query::CompPredicate::NULL_SAFE_EQUALS_OP:
552-
case query::CompPredicate::NOT_EQUALS_OP:
553-
case query::CompPredicate::NOT_EQUALS_OP_ALT:
554-
// While this doesn't make much sense numerically (floating
555-
// point numbers are being tested for equality), it is
556-
// technically evaluable.
557-
fe = getAngSepFunc(cp->left);
558-
if (!fe) {
559-
angSep = cp->left->getNumericConst();
560-
fe = getAngSepFunc(cp->right);
561-
} else {
562-
angSep = cp->right->getNumericConst();
563-
}
564-
break;
565-
default:
566-
throw QueryNotEvaluableError("Unhandled comparison operator:" + std::to_string(cp->op));
567-
}
631+
632+
// Try to extract a scisql_angSep() and numeric threshold from the comparison predicate.
633+
std::pair<FuncExpr::Ptr, double> const angSepJoin = getAngSepJoin(*cp);
634+
635+
// If that fails, try equivalent point in circle: scisql_s2PtInCircle(lon1, lat1, lon2, lat2, radius) = 1.
636+
auto const [fe, angSep] = angSepJoin.first ? angSepJoin : getPtInCircleJoin(*cp);
637+
568638
if (!fe || boost::math::isnan(angSep)) {
569-
// The scisql_angSep() call and/or numeric constant is missing,
570-
// or the comparison operator is invalid (e.g.
571-
// "angSep < scisql_angSep(...)")
639+
// Not angSep or PtInCircle; give up
572640
return 0;
573641
}
574-
// Extract column references from fe
642+
643+
// Extract column references from fe. Both scisql_angSep() and scisql_s2PtInCircle() take two
644+
// coordinate pairs as their first four arguments.
575645
ValueExprPtrVector::const_iterator j = fe->params.begin();
576646
ColumnRef::Ptr cr[4];
577647
Vertex* v[4];

0 commit comments

Comments
 (0)