@@ -633,6 +633,192 @@ BOOST_AUTO_TEST_CASE(ObjectSelfJoinDistance) {
633633 BOOST_CHECK_EQUAL (actual, expected);
634634}
635635
636+ BOOST_AUTO_TEST_CASE (ObjectSelfJoinPtInCircle) {
637+ // A spatial self-join expressed with point-in-circle syntax is equivalent to a scisql_angSep()
638+ // comparison and should be recognized as a near-neighbor join.
639+ std::string stmt =
640+ " select count(*) from LSST.Object o1,LSST.Object o2 "
641+ " WHERE qserv_areaspec_box(5.5, 5.5, 6.1, 6.1) AND "
642+ " scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) = 1" ;
643+ std::string expected =
644+ " SELECT count(*) AS `QS1_COUNT` "
645+ " FROM `Subchunks_LSST_100`.`Object_100_%S\007 S%` AS `o1`,"
646+ " `Subchunks_LSST_100`.`Object_100_%S\007 S%` AS `o2` "
647+ " WHERE scisql_s2PtInBox(`o1`.`ra_Test`,`o1`.`decl_Test`,5.5,5.5,6.1,6.1)=1 "
648+ " AND scisql_s2PtInBox(`o2`.`ra_Test`,`o2`.`decl_Test`,5.5,5.5,6.1,6.1)=1 "
649+ " AND scisql_s2PtInCircle(`o1`.`ra_Test`,`o1`.`decl_Test`,"
650+ " `o2`.`ra_Test`,`o2`.`decl_Test`,0.02)=1" ;
651+ qsTest.sqlConfig =
652+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
653+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
654+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
655+ BOOST_CHECK (context);
656+ BOOST_CHECK (nullptr == context->secIdxRestrictors );
657+ BOOST_CHECK (nullptr != context->areaRestrictors );
658+ BOOST_CHECK (context->hasChunks ());
659+ BOOST_CHECK (context->hasSubChunks ());
660+ BOOST_CHECK (context->needsMerge );
661+ std::string actual2 = queryAnaHelper.buildFirstParallelQuery ();
662+ BOOST_CHECK_EQUAL (actual2, expected);
663+ }
664+
665+ BOOST_AUTO_TEST_CASE (PtInCircleJoinForms) {
666+ // scisql_s2PtInCircle() call may appear on either side of "="
667+ std::vector<std::string> const predicates = {
668+ " scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) = 1" ,
669+ " 1 = scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02)" };
670+ for (auto const & predicate : predicates) {
671+ std::string stmt = " select count(*) from LSST.Object o1,LSST.Object o2 WHERE " + predicate;
672+ qsTest.sqlConfig =
673+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
674+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
675+ BOOST_CHECK_EQUAL (qs->getError (), " " );
676+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
677+ BOOST_CHECK (context);
678+ BOOST_CHECK (context->hasChunks ());
679+ BOOST_CHECK (context->hasSubChunks ());
680+ }
681+ }
682+
683+ BOOST_AUTO_TEST_CASE (PtInCircleJoinUnsupportedOperators) {
684+ // scisql_s2PtInCircle() only ever returns 0 or 1, so only plain equality against 1 is recognized as a
685+ // join predicate
686+ std::vector<std::string> const predicates = {
687+ " scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) != 0" ,
688+ " 0 < scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02)" };
689+ for (auto const & predicate : predicates) {
690+ std::string stmt = " select count(*) from LSST.Object o1,LSST.Object o2 WHERE " + predicate;
691+ qsTest.sqlConfig =
692+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
693+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
694+ BOOST_CHECK_EQUAL (qs->getError (), NOT_EVALUABLE_MSG );
695+ }
696+ }
697+
698+ BOOST_AUTO_TEST_CASE (PtInCircleAntiJoin) {
699+ // A point outside the circle selects distant pairs, which cannot be evaluated using partition-local data.
700+ std::string stmt =
701+ " select count(*) from LSST.Object o1,LSST.Object o2 "
702+ " WHERE scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) = 0" ;
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+ BOOST_AUTO_TEST_CASE (PtInCircleJoinWithConeRestrictor) {
710+ // Join AND a spatial restrictor
711+ std::string stmt =
712+ " select count(*) from LSST.Object o1,LSST.Object o2 "
713+ " WHERE scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,6,6,0.3) = 1 AND "
714+ " scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,0.02) = 1" ;
715+ qsTest.sqlConfig =
716+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
717+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
718+ BOOST_CHECK_EQUAL (qs->getError (), " " );
719+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
720+ BOOST_CHECK (context);
721+ BOOST_REQUIRE (context->areaRestrictors );
722+ BOOST_CHECK_EQUAL (context->areaRestrictors ->size (), 1U );
723+ BOOST_REQUIRE (context->areaRestrictors ->front ());
724+ BOOST_CHECK_EQUAL (*context->areaRestrictors ->front (), AreaRestrictorCircle (" 6" , " 6" , " 0.3" ));
725+ BOOST_CHECK (context->hasChunks ());
726+ BOOST_CHECK (context->hasSubChunks ());
727+ }
728+
729+ BOOST_AUTO_TEST_CASE (AngSepAsConeRestrictor) {
730+ // scisql_angSep() with a constant center, bounded above, is equivalent to scisql_s2PtInCircle() = 1
731+ std::vector<std::string> const predicates = {
732+ " scisql_angSep(ra_PS, decl_PS, 1.5, 3) < 0.1" , " scisql_angSep(ra_PS, decl_PS, 1.5, 3) <= 0.1" ,
733+ " 0.1 > scisql_angSep(ra_PS, decl_PS, 1.5, 3)" , " scisql_angSep(1.5, 3, ra_PS, decl_PS) < 0.1" };
734+ for (auto const & predicate : predicates) {
735+ std::string stmt = " select * from Object where " + predicate;
736+ qsTest.sqlConfig =
737+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_PS" , " decl_PS" }}}}}));
738+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
739+ BOOST_CHECK_EQUAL (qs->getError (), " " );
740+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
741+ BOOST_CHECK (context);
742+ BOOST_REQUIRE (context->areaRestrictors );
743+ BOOST_CHECK_EQUAL (context->areaRestrictors ->size (), 1U );
744+ BOOST_REQUIRE (context->areaRestrictors ->front ());
745+ BOOST_CHECK_EQUAL (*context->areaRestrictors ->front (), AreaRestrictorCircle (" 1.5" , " 3" , " 0.1" ));
746+ BOOST_CHECK (context->hasChunks ());
747+ BOOST_CHECK (!context->hasSubChunks ());
748+ }
749+ }
750+
751+ BOOST_AUTO_TEST_CASE (AngSepLowerBoundNoRestrictor) {
752+ // Bounding scisql_angSep() from below selects points outside the cone; no restrictor produced
753+ std::string stmt = " select * from Object where scisql_angSep(ra_PS, decl_PS, 1.5, 3) > 0.1" ;
754+ qsTest.sqlConfig =
755+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_PS" , " decl_PS" }}}}}));
756+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
757+ BOOST_CHECK_EQUAL (qs->getError (), " " );
758+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
759+ BOOST_CHECK (context);
760+ BOOST_CHECK (nullptr == context->areaRestrictors );
761+ BOOST_CHECK (context->hasChunks ());
762+ }
763+
764+ BOOST_AUTO_TEST_CASE (AngSepConeRestrictorWithAngSepJoin) {
765+ // Cone restrictor AND a spatial join, both via scisql_angSep()
766+ std::string stmt =
767+ " select count(*) from LSST.Object o1,LSST.Object o2 "
768+ " WHERE scisql_angSep(o1.ra_Test, o1.decl_Test, 6, 6) < 0.3 AND "
769+ " scisql_angSep(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test) < 0.02" ;
770+ qsTest.sqlConfig =
771+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
772+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
773+ BOOST_CHECK_EQUAL (qs->getError (), " " );
774+ std::shared_ptr<QueryContext> context = qs->dbgGetContext ();
775+ BOOST_CHECK (context);
776+ BOOST_REQUIRE (context->areaRestrictors );
777+ BOOST_CHECK_EQUAL (context->areaRestrictors ->size (), 1U );
778+ BOOST_REQUIRE (context->areaRestrictors ->front ());
779+ BOOST_CHECK_EQUAL (*context->areaRestrictors ->front (), AreaRestrictorCircle (" 6" , " 6" , " 0.3" ));
780+ BOOST_CHECK (context->hasChunks ());
781+ BOOST_CHECK (context->hasSubChunks ());
782+ }
783+
784+ BOOST_AUTO_TEST_CASE (UnrelatedNegativeConstantNotMistakenForAngSep) {
785+ // A "col < negative constant" predicate unrelated to scisql_angSep() must not be rejected
786+ std::string stmt = " select * from LSST.Object o1 WHERE o1.mag < -5" ;
787+ qsTest.sqlConfig = SqlConfig (
788+ SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" , " mag" }}}}}));
789+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt);
790+ BOOST_CHECK_EQUAL (qs->getError (), " " );
791+ }
792+
793+ BOOST_AUTO_TEST_CASE (NegativeAngSepThresholdRejected) {
794+ // A negative scisql_angSep() threshold can never be satisfied, so it must error, not be ignored
795+ std::string stmt =
796+ " select * from LSST.Object o, Source s "
797+ " WHERE o.objectIdObjTest = s.objectIdSourceTest AND "
798+ " scisql_angSep(o.ra_Test,o.decl_Test,s.ra_Test,s.decl_Test) < -0.5;" ;
799+ qsTest.sqlConfig = SqlConfig (
800+ SqlConfig::MockDbTableColumns ({{" LSST" ,
801+ {{" Object" , {" objectIdObjTest" , " ra_Test" , " decl_Test" }},
802+ {" Source" , {" objectIdSourceTest" , " ra_Test" , " decl_Test" }}}}}));
803+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
804+ BOOST_CHECK_EQUAL (qs->getError (),
805+ " AnalysisError:scisql_angSep() comparison threshold must be a non-negative "
806+ " number, got -0.500000" );
807+ }
808+
809+ BOOST_AUTO_TEST_CASE (NegativePtInCircleRadiusRejected) {
810+ // A negative scisql_s2PtInCircle() radius can never be satisfied, so it must error, not be ignored
811+ std::string stmt =
812+ " select count(*) from LSST.Object o1,LSST.Object o2 "
813+ " WHERE scisql_s2PtInCircle(o1.ra_Test,o1.decl_Test,o2.ra_Test,o2.decl_Test,-0.5) = 1" ;
814+ qsTest.sqlConfig =
815+ SqlConfig (SqlConfig::MockDbTableColumns ({{" LSST" , {{" Object" , {" ra_Test" , " decl_Test" }}}}}));
816+ std::shared_ptr<QuerySession> qs = queryAnaHelper.buildQuerySession (qsTest, stmt, true );
817+ BOOST_CHECK_EQUAL (qs->getError (),
818+ " AnalysisError:scisql_s2PtInCircle() radius must be a non-negative number, "
819+ " got -0.500000" );
820+ }
821+
636822BOOST_AUTO_TEST_CASE (SelfJoinAliased) {
637823 // o2.ra_PS and o2.ra_PS_Sigma have to be aliased in order to produce
638824 // a result that can't be stored in a table as-is.
0 commit comments