/
githubmirror
/
spark
Обзор
Документация
Войти
/
githubmirror
/
spark
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
python/pyspark/sql/tests/test_nearest_by_join.py
263 строки
9 KB
Zero Qu
[SPARK-57088][SQL] Allow non-deterministic ranking expression for EXACT NEAREST BY
27 май 2026, 08:47
27 май 2026, 08:47
2603f6a
Код
Авторство
О чём код?
# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # from pyspark.errors import AnalysisException from pyspark.sql import Row from pyspark.sql import functions as sf from pyspark.testing import assertDataFrameEqual from pyspark.testing.sqlutils import ReusedSQLTestCase class NearestByJoinTestsMixin: """Mixin run against both classic (`ReusedSQLTestCase`) and Connect (`ReusedConnectTestCase`) to ensure parity between the two paths.""" @property def users(self): return self.spark.createDataFrame([(1, 10.0), (2, 20.0), (3, 30.0)], ["user_id", "score"]) @property def products(self): return self.spark.createDataFrame( [("A", 11.0), ("B", 22.0), ("C", 5.0)], ["product", "pscore"] ) def test_inner_similarity_k1(self): users, products = self.users, self.products result = ( users.nearestByJoin( products, -sf.abs(users.score - products.pscore), numResults=1, mode="approx", direction="similarity", ) .select("user_id", "product") .orderBy("user_id") ) assertDataFrameEqual( result, [Row(user_id=1, product="A"), Row(user_id=2, product="B"), Row(user_id=3, product="B")], ) def test_inner_distance_k2(self): users, products = self.users, self.products result = ( users.nearestByJoin( products, sf.abs(users.score - products.pscore), numResults=2, mode="approx", direction="distance", ) .select("user_id", "product") .orderBy("user_id", "product") ) assertDataFrameEqual( result, [ Row(user_id=1, product="A"), Row(user_id=1, product="C"), Row(user_id=2, product="A"), Row(user_id=2, product="B"), Row(user_id=3, product="A"), Row(user_id=3, product="B"), ], ) def test_left_outer_with_empty_right(self): users, products = self.users, self.products empty = products.filter(sf.lit(False)) result = ( users.nearestByJoin( empty, -sf.abs(users.score - empty.pscore), numResults=1, mode="exact", direction="similarity", joinType="leftouter", ) .select("user_id", "product") .orderBy("user_id") ) assertDataFrameEqual( result, [ Row(user_id=1, product=None), Row(user_id=2, product=None), Row(user_id=3, product=None), ], ) def test_select_star_schema_has_no_internal_columns(self): users, products = self.users, self.products result = users.nearestByJoin( products, -sf.abs(users.score - products.pscore), numResults=1, mode="exact", direction="similarity", ) # No `__qid`, `__nearest_matches__`, or other rewrite-internal columns leak through. assert sorted(result.columns) == ["product", "pscore", "score", "user_id"] def test_invalid_num_results_low(self): users, products = self.users, self.products with self.assertRaises(AnalysisException) as pe: users.nearestByJoin( products, -sf.abs(users.score - products.pscore), numResults=0, mode="approx", direction="similarity", ) self.check_error( exception=pe.exception, errorClass="NEAREST_BY_JOIN.NUM_RESULTS_OUT_OF_RANGE", messageParameters={"numResults": "0", "min": "1", "max": "100000"}, ) def test_invalid_num_results_high(self): users, products = self.users, self.products with self.assertRaises(AnalysisException) as pe: users.nearestByJoin( products, -sf.abs(users.score - products.pscore), numResults=200000, mode="approx", direction="similarity", ) self.check_error( exception=pe.exception, errorClass="NEAREST_BY_JOIN.NUM_RESULTS_OUT_OF_RANGE", messageParameters={"numResults": "200000", "min": "1", "max": "100000"}, ) def test_invalid_join_type(self): users, products = self.users, self.products with self.assertRaises(AnalysisException) as pe: users.nearestByJoin( products, -sf.abs(users.score - products.pscore), numResults=1, mode="approx", direction="similarity", joinType="outer", ) self.check_error( exception=pe.exception, errorClass="NEAREST_BY_JOIN.UNSUPPORTED_JOIN_TYPE", messageParameters={"joinType": "outer", "supported": "'INNER', 'LEFT OUTER'"}, ) def test_invalid_mode(self): users, products = self.users, self.products with self.assertRaises(AnalysisException) as pe: users.nearestByJoin( products, -sf.abs(users.score - products.pscore), numResults=1, mode="bogus", direction="similarity", ) self.check_error( exception=pe.exception, errorClass="NEAREST_BY_JOIN.UNSUPPORTED_MODE", messageParameters={"mode": "bogus", "supported": "'approx', 'exact'"}, ) def test_invalid_direction(self): users, products = self.users, self.products with self.assertRaises(AnalysisException) as pe: users.nearestByJoin( products, -sf.abs(users.score - products.pscore), numResults=1, mode="approx", direction="elsewhere", ) self.check_error( exception=pe.exception, errorClass="NEAREST_BY_JOIN.UNSUPPORTED_DIRECTION", messageParameters={ "direction": "elsewhere", "supported": "'distance', 'similarity'", }, ) def test_rejected_when_crossjoin_disabled(self): users, products = self.users, self.products with self.sql_conf({"spark.sql.crossJoin.enabled": "false"}): with self.assertRaises(AnalysisException) as pe: users.nearestByJoin( products, -sf.abs(users.score - products.pscore), numResults=1, mode="exact", direction="similarity", ).collect() self.check_error( exception=pe.exception, errorClass="NEAREST_BY_JOIN.CROSS_JOIN_NOT_ENABLED", messageParameters={}, ) def test_exact_with_nondeterministic_ranking_accepted(self): users, products = self.users, self.products # Result rows are nondeterministic; only assert that each left row gets exactly one match. count = users.nearestByJoin( products, sf.rand(0) + products.pscore, numResults=1, mode="exact", direction="similarity", ).count() self.assertEqual(count, 3) def test_streaming_inputs_rejected(self): streaming_users = ( self.spark.readStream.format("rate") .option("rowsPerSecond", 1) .load() .selectExpr("CAST(value AS INT) AS user_id", "CAST(value AS DOUBLE) AS score") ) products = self.products with self.assertRaises(AnalysisException) as pe: # `.schema` forces analysis without starting the streaming query. _ = streaming_users.nearestByJoin( products, -sf.abs(streaming_users.score - products.pscore), numResults=1, mode="exact", direction="similarity", ).schema self.check_error( exception=pe.exception, errorClass="NEAREST_BY_JOIN.STREAMING_NOT_SUPPORTED", messageParameters={}, ) class NearestByJoinTests(NearestByJoinTestsMixin, ReusedSQLTestCase): pass if __name__ == "__main__": from pyspark.testing import main main()