@@ -328,6 +328,16 @@ static const std::vector<ScisqlRestrictorTestCaseData> SCISQL_RESTRICTOR_TEST_CA
328328 " AND `o`.`objectIdObjTest`=`s`.`objectIdSourceTest`" ,
329329 std::make_shared<AreaRestrictorCircle>(" 1" , " 1" , " 1.3" )),
330330
331+ // "<=>" (null-safe equality) is equivalent to "=" here since scisql_s2PtInCircle() never
332+ // evaluates to NULL.
333+ ScisqlRestrictorTestCaseData (" select * from LSST.Object o, Source s "
334+ " WHERE scisql_s2PtInCircle(o.ra_Test, o.decl_Test, 1, 1, 1.3) <=> 1 "
335+ " AND o.objectIdObjTest = s.objectIdSourceTest;" ,
336+ " SELECT * FROM `LSST`.`Object_100` AS `o`,`LSST`.`Source_100` AS `s` "
337+ " WHERE scisql_s2PtInCircle(`o`.`ra_Test`,`o`.`decl_Test`,1,1,1.3)<=>1 "
338+ " AND `o`.`objectIdObjTest`=`s`.`objectIdSourceTest`" ,
339+ std::make_shared<AreaRestrictorCircle>(" 1" , " 1" , " 1.3" )),
340+
331341 ScisqlRestrictorTestCaseData (
332342 " select * from LSST.Object o, Source s "
333343 " WHERE scisql_s2PtInEllipse(ra_Test, decl_Test, 1.2, 3.2, 2500, 1500, 0.2) = 1 "
@@ -633,6 +643,256 @@ BOOST_AUTO_TEST_CASE(ObjectSelfJoinDistance) {
633643 BOOST_CHECK_EQUAL (actual, expected);
634644}
635645
646+ BOOST_AUTO_TEST_CASE (ObjectSelfJoinPtInCircle) {
647+ // A spatial self-join expressed with point-in-circle syntax is equivalent to a scisql_angSep()
648+ // comparison and should be recognized as a near-neighbor join.
649+ std::string stmt =
650+ " select count(*) from LSST.Object o1,LSST.Object o2 "
651+ " WHERE qserv_areaspec_box(5.5, 5.5, 6.1, 6.1) AND "
652+ " scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) = 1" ;
653+ std::string expected =
654+ " SELECT count(*) AS `QS1_COUNT` "
655+ " FROM `Subchunks_LSST_100`.`Object_100_%S\007 S%` AS `o1`,"
656+ " `Subchunks_LSST_100`.`Object_100_%S\007 S%` AS `o2` "
657+ " WHERE scisql_s2PtInBox(`o1`.`ra_Test`,`o1`.`decl_Test`,5.5,5.5,6.1,6.1)=1 "
658+ " AND scisql_s2PtInBox(`o2`.`ra_Test`,`o2`.`decl_Test`,5.5,5.5,6.1,6.1)=1 "
659+ " AND scisql_s2PtInCircle(`o1`.`ra_Test`,`o1`.`decl_Test`,"
660+ " `o2`.`ra_Test`,`o2`.`decl_Test`,0.02)=1" ;
661+ qsTest.sqlConfig =
662+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
663+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
664+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
665+ BOOST_CHECK (context);
666+ BOOST_CHECK (nullptr == context->secIdxRestrictors );
667+ BOOST_CHECK (nullptr != context->areaRestrictors );
668+ BOOST_CHECK (context->hasChunks ());
669+ BOOST_CHECK (context->hasSubChunks ());
670+ BOOST_CHECK (context->needsMerge );
671+ std::string actual2 = queryAnaHelper.buildFirstParallelQuery ();
672+ BOOST_CHECK_EQUAL (actual2, expected);
673+ }
674+
675+ BOOST_AUTO_TEST_CASE (PtInCircleJoinForms) {
676+ // scisql_s2PtInCircle() call may appear on either side of "=" or "<=>"
677+ std::vector<std::string> const predicates = {
678+ " scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) = 1" ,
679+ " 1 = scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02)" ,
680+ " scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) <=> 1" ,
681+ " 1 <=> scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02)" };
682+ for (auto const & predicate : predicates) {
683+ std::string stmt = " select count(*) from LSST.Object o1,LSST.Object o2 WHERE " + predicate;
684+ qsTest.sqlConfig =
685+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
686+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
687+ BOOST_CHECK_EQUAL (qs->getError (), " " );
688+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
689+ BOOST_CHECK (context);
690+ BOOST_CHECK (context->hasChunks ());
691+ BOOST_CHECK (context->hasSubChunks ());
692+ }
693+ }
694+
695+ BOOST_AUTO_TEST_CASE (PtInCircleJoinUnsupportedOperators) {
696+ // scisql_s2PtInCircle() only ever returns 0 or 1, so only plain equality against 1 is recognized as a
697+ // join predicate
698+ std::vector<std::string> const predicates = {
699+ " scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) != 0" ,
700+ " 0 < scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02)" };
701+ for (auto const & predicate : predicates) {
702+ std::string stmt = " select count(*) from LSST.Object o1,LSST.Object o2 WHERE " + predicate;
703+ qsTest.sqlConfig =
704+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
705+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
706+ BOOST_CHECK_EQUAL (qs->getError (), NOT_EVALUABLE_MSG );
707+ }
708+ }
709+
710+ BOOST_AUTO_TEST_CASE (PtInCircleAntiJoin) {
711+ // A point outside the circle selects distant pairs, which cannot be evaluated using partition-local data.
712+ std::string stmt =
713+ " select count(*) from LSST.Object o1,LSST.Object o2 "
714+ " WHERE scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) = 0" ;
715+ qsTest.sqlConfig =
716+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
717+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
718+ BOOST_CHECK_EQUAL (qs->getError (), NOT_EVALUABLE_MSG );
719+ }
720+
721+ BOOST_AUTO_TEST_CASE (NegatedPtInCircleJoinNotMisread) {
722+ // Similar to PtInCircleAntiJoin, but with NOT.
723+ std::string stmt =
724+ " select count(*) from LSST.Object o1,LSST.Object o2 "
725+ " WHERE NOT scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) = 1" ;
726+ qsTest.sqlConfig =
727+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
728+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
729+ BOOST_CHECK_EQUAL (qs->getError (), NOT_EVALUABLE_MSG );
730+ }
731+
732+ BOOST_AUTO_TEST_CASE (PtInCircleJoinWithConeRestrictor) {
733+ // Join AND a spatial restrictor
734+ std::string stmt =
735+ " select count(*) from LSST.Object o1,LSST.Object o2 "
736+ " WHERE scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,6,6,0.3) = 1 AND "
737+ " scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) = 1" ;
738+ qsTest.sqlConfig =
739+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
740+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
741+ BOOST_CHECK_EQUAL (qs->getError (), " " );
742+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
743+ BOOST_CHECK (context);
744+ BOOST_REQUIRE (context->areaRestrictors );
745+ BOOST_CHECK_EQUAL (context->areaRestrictors ->size (), 1U );
746+ BOOST_REQUIRE (context->areaRestrictors ->front ());
747+ BOOST_CHECK_EQUAL (*context->areaRestrictors ->front (), AreaRestrictorCircle (" 6" , " 6" , " 0.3" ));
748+ BOOST_CHECK (context->hasChunks ());
749+ BOOST_CHECK (context->hasSubChunks ());
750+ }
751+
752+ BOOST_AUTO_TEST_CASE (AngSepAsConeRestrictor) {
753+ // scisql_angSep() with a constant center, bounded above, is equivalent to scisql_s2PtInCircle() = 1
754+ std::vector<std::string> const predicates = {
755+ " scisql_angSep(ra_PS, decl_PS, 1.5, 3) < 0.1" , " scisql_angSep(ra_PS, decl_PS, 1.5, 3) <= 0.1" ,
756+ " 0.1 > scisql_angSep(ra_PS, decl_PS, 1.5, 3)" , " scisql_angSep(1.5, 3, ra_PS, decl_PS) < 0.1" ,
757+ " scisql_angSep(ra_PS, decl_PS, 1.5, 3) = 0.1" , " scisql_angSep(ra_PS, decl_PS, 1.5, 3) <=> 0.1" };
758+ for (auto const & predicate : predicates) {
759+ std::string stmt = " select * from Object where " + predicate;
760+ qsTest.sqlConfig =
761+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_PS" , " decl_PS" }}}}}));
762+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
763+ BOOST_CHECK_EQUAL (qs->getError (), " " );
764+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
765+ BOOST_CHECK (context);
766+ BOOST_REQUIRE (context->areaRestrictors );
767+ BOOST_CHECK_EQUAL (context->areaRestrictors ->size (), 1U );
768+ BOOST_REQUIRE (context->areaRestrictors ->front ());
769+ BOOST_CHECK_EQUAL (*context->areaRestrictors ->front (), AreaRestrictorCircle (" 1.5" , " 3" , " 0.1" ));
770+ BOOST_CHECK (context->hasChunks ());
771+ BOOST_CHECK (!context->hasSubChunks ());
772+ }
773+ }
774+
775+ BOOST_AUTO_TEST_CASE (AngSepLowerBoundNoRestrictor) {
776+ // Bounding scisql_angSep() from below selects points outside the cone; no restrictor produced
777+ std::string stmt = " select * from Object where scisql_angSep(ra_PS, decl_PS, 1.5, 3) > 0.1" ;
778+ qsTest.sqlConfig =
779+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_PS" , " decl_PS" }}}}}));
780+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
781+ BOOST_CHECK_EQUAL (qs->getError (), " " );
782+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
783+ BOOST_CHECK (context);
784+ BOOST_CHECK (nullptr == context->areaRestrictors );
785+ BOOST_CHECK (context->hasChunks ());
786+ }
787+
788+ BOOST_AUTO_TEST_CASE (NegativeAngSepConeThresholdRejected) {
789+ // A negative scisql_angSep() threshold can never be satisfied, so should error
790+ std::string stmt = " select * from Object where scisql_angSep(ra_PS, decl_PS, 1.5, 3) < -0.1" ;
791+ qsTest.sqlConfig =
792+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_PS" , " decl_PS" }}}}}));
793+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
794+ BOOST_CHECK_EQUAL (qs->getError (),
795+ " AnalysisError:scisql_angSep() comparison threshold must be a non-negative "
796+ " number, got -0.100000" );
797+ }
798+
799+ BOOST_AUTO_TEST_CASE (AngSepConeRestrictorWithAngSepJoin) {
800+ // Cone restrictor AND a spatial join, both via scisql_angSep()
801+ std::string stmt =
802+ " select count(*) from LSST.Object o1,LSST.Object o2 "
803+ " WHERE scisql_angSep(o1.ra_Test, o1.decl_Test, 6, 6) < 0.3 AND "
804+ " scisql_angSep(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test) < 0.02" ;
805+ qsTest.sqlConfig =
806+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
807+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
808+ BOOST_CHECK_EQUAL (qs->getError (), " " );
809+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
810+ BOOST_CHECK (context);
811+ BOOST_REQUIRE (context->areaRestrictors );
812+ BOOST_CHECK_EQUAL (context->areaRestrictors ->size (), 1U );
813+ BOOST_REQUIRE (context->areaRestrictors ->front ());
814+ BOOST_CHECK_EQUAL (*context->areaRestrictors ->front (), AreaRestrictorCircle (" 6" , " 6" , " 0.3" ));
815+ BOOST_CHECK (context->hasChunks ());
816+ BOOST_CHECK (context->hasSubChunks ());
817+ }
818+
819+ BOOST_AUTO_TEST_CASE (UnrelatedNegativeConstantNotMistakenForAngSep) {
820+ // A "col < negative constant" predicate unrelated to scisql_angSep() must not be rejected
821+ std::string stmt = " select * from LSST.Object o1 WHERE o1.mag < -5" ;
822+ qsTest.sqlConfig = SqlConfig (
823+ SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" , " mag" }}}}}));
824+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
825+ BOOST_CHECK_EQUAL (qs->getError (), " " );
826+ }
827+
828+ BOOST_AUTO_TEST_CASE (NegativeAngSepThresholdRejected) {
829+ // A negative scisql_angSep() threshold can never be satisfied, so it should error
830+ std::string stmt =
831+ " select * from LSST.Object o, Source s "
832+ " WHERE o.objectIdObjTest = s.objectIdSourceTest AND "
833+ " scisql_angSep(o.ra_Test,o.decl_Test,s.ra_Test,s.decl_Test) < -0.5;" ;
834+ qsTest.sqlConfig = SqlConfig (
835+ SqlConfig::MockDbTableColumns ({{" LSST" ,
836+ {{" Object" , {" objectIdObjTest" , " ra_Test" , " decl_Test" }},
837+ {" Source" , {" objectIdSourceTest" , " ra_Test" , " decl_Test" }}}}}));
838+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
839+ BOOST_CHECK_EQUAL (qs->getError (),
840+ " AnalysisError:scisql_angSep() comparison threshold must be a non-negative "
841+ " number, got -0.500000" );
842+ }
843+
844+ BOOST_AUTO_TEST_CASE (NegativePtInCircleRadiusRejected) {
845+ // A negative scisql_s2PtInCircle() radius can never be satisfied, so it should error
846+ std::string stmt =
847+ " select count(*) from LSST.Object o1,LSST.Object o2 "
848+ " WHERE scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,-0.5) = 1" ;
849+ qsTest.sqlConfig =
850+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
851+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
852+ BOOST_CHECK_EQUAL (qs->getError (),
853+ " AnalysisError:scisql_s2PtInCircle() radius must be a non-negative number, "
854+ " got -0.500000" );
855+ }
856+
857+ BOOST_AUTO_TEST_CASE (NegativePtInCircleRestrictorRadiusRejected) {
858+ // Same as NegativePtInCircleRadiusRejected, but for the single-table restrictor form
859+ std::string stmt =
860+ " select * from LSST.Object o WHERE scisql_s2PtInCircle(o.ra_Test, o.decl_Test, 1, 1, -0.5) = 1" ;
861+ qsTest.sqlConfig =
862+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
863+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
864+ BOOST_CHECK_EQUAL (qs->getError (),
865+ " AnalysisError:scisql_s2PtInCircle() radius must be a non-negative number, "
866+ " got -0.500000" );
867+ }
868+
869+ BOOST_AUTO_TEST_CASE (NegatedPtInCircleRestrictorNotMisread) {
870+ // "NOT scisql_s2PtInCircle(...) = 1" describes everything OUTSIDE the circle, which is not a bounded area
871+ // constraint
872+ std::string stmt =
873+ " select * from LSST.Object o "
874+ " WHERE NOT scisql_s2PtInCircle(o.ra_Test, o.decl_Test, 1, 1, 1.3) = 1" ;
875+ qsTest.sqlConfig =
876+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
877+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
878+ BOOST_CHECK_EQUAL (qs->getError (), " " );
879+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
880+ BOOST_CHECK (context);
881+ BOOST_CHECK (nullptr == context->areaRestrictors );
882+ }
883+
884+ BOOST_AUTO_TEST_CASE (NegatedAngSepRestrictorNotMisread) {
885+ // Same as above, for the scisql_angSep() as restrictor
886+ std::string stmt = " select * from Object where NOT scisql_angSep(ra_PS, decl_PS, 1.5, 3) < 0.1" ;
887+ qsTest.sqlConfig =
888+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_PS" , " decl_PS" }}}}}));
889+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
890+ BOOST_CHECK_EQUAL (qs->getError (), " " );
891+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
892+ BOOST_CHECK (context);
893+ BOOST_CHECK (nullptr == context->areaRestrictors );
894+ }
895+
636896BOOST_AUTO_TEST_CASE (SelfJoinAliased) {
637897 // o2.ra_PS and o2.ra_PS_Sigma have to be aliased in order to produce
638898 // a result that can't be stored in a table as-is.
0 commit comments