|
31 | 31 | # Disable AQE temporarily until https://github.com/NVIDIA/spark-rapids/issues/14319 is resolved. |
32 | 32 | delta_merge_enabled_conf = copy_and_update(delta_merge_enabled_conf, {"spark.sql.adaptive.enabled": "false"}) |
33 | 33 |
|
| 34 | +delta_merge_no_cpu_bridge_conf = copy_and_update( |
| 35 | + delta_merge_enabled_conf, {"spark.rapids.sql.expression.cpuBridge.enabled": "false"}) |
| 36 | + |
34 | 37 | fallback_test_params = [{"spark.rapids.sql.format.delta.write.enabled": "false"}, |
35 | 38 | {"spark.rapids.sql.format.parquet.enabled": "false"}, |
36 | 39 | {"spark.rapids.sql.format.parquet.write.enabled": "false"}, |
|
43 | 46 | # See https://github.com/NVIDIA/spark-rapids/issues/13021#issuecomment-3166724473 for details. |
44 | 47 | fallback_test_params.append(delta_writes_enabled_conf) |
45 | 48 |
|
| 49 | + |
| 50 | +def _assert_gpu_merge_processor(do_merge, data_path, conf, expect_write=True): |
| 51 | + assert expect_write |
| 52 | + cpu_result = with_cpu_session(lambda spark: do_merge(spark, data_path + "/CPU"), conf=conf) |
| 53 | + |
| 54 | + callback = spark_jvm().org.apache.spark.sql.rapids.ExecutionPlanCaptureCallback |
| 55 | + callback.startCapture() |
| 56 | + try: |
| 57 | + gpu_result = with_gpu_session( |
| 58 | + lambda spark: do_merge(spark, data_path + "/GPU"), conf=conf) |
| 59 | + captured_plans = callback.getResultsWithTimeout(10000) |
| 60 | + finally: |
| 61 | + callback.endCapture() |
| 62 | + |
| 63 | + assert_equal(cpu_result, gpu_result) |
| 64 | + # The CPU expression bridge is disabled for this test, so finding the GPU merge processor |
| 65 | + # proves that CheckOverflowInTableWrite and the other merge expressions were replaced on GPU. |
| 66 | + class_name = "GpuRapidsProcessDeltaMergeJoinExec" |
| 67 | + assert any(callback.contains(plan, class_name) for plan in captured_plans), \ |
| 68 | + f"{class_name} was not found in the captured MERGE plans" |
| 69 | + |
| 70 | + |
| 71 | +@allow_non_gpu(*delta_meta_allow) |
| 72 | +@delta_lake |
| 73 | +@ignore_order |
| 74 | +@pytest.mark.skipif(not is_databricks173_or_later(), |
| 75 | + reason="CheckOverflowInTableWrite is required on DBR 17.3+") |
| 76 | +def test_delta_merge_check_overflow_in_table_write( |
| 77 | + spark_tmp_path, spark_tmp_table_factory): |
| 78 | + def source_df(spark): |
| 79 | + return spark.sql(""" |
| 80 | + SELECT timestamp '2024-01-01 00:00:00' AS timestampF, |
| 81 | + CAST(2 AS INT) AS byteF |
| 82 | + """) |
| 83 | + |
| 84 | + def target_df(spark): |
| 85 | + return spark.sql(""" |
| 86 | + SELECT timestamp '2024-01-01 00:00:00' AS timestampF, |
| 87 | + CAST(1 AS TINYINT) AS byteF |
| 88 | + UNION ALL |
| 89 | + SELECT timestamp '2024-01-02 00:00:00' AS timestampF, |
| 90 | + CAST(2 AS TINYINT) AS byteF |
| 91 | + """) |
| 92 | + |
| 93 | + merge_sql = "MERGE INTO {dest_table} AS target USING {src_table} AS source " \ |
| 94 | + "ON target.timestampF = source.timestampF " \ |
| 95 | + "WHEN MATCHED THEN UPDATE SET byteF = source.byteF" |
| 96 | + assert_delta_sql_merge_collect( |
| 97 | + spark_tmp_path, |
| 98 | + spark_tmp_table_factory, |
| 99 | + use_cdf=False, |
| 100 | + enable_deletion_vectors=False, |
| 101 | + src_table_func=source_df, |
| 102 | + dest_table_func=target_df, |
| 103 | + merge_sql=merge_sql, |
| 104 | + compare_logs=False, |
| 105 | + assert_func=_assert_gpu_merge_processor, |
| 106 | + conf=delta_merge_no_cpu_bridge_conf) |
| 107 | + |
| 108 | + |
| 109 | +@allow_non_gpu(*delta_meta_allow) |
| 110 | +@delta_lake |
| 111 | +@pytest.mark.skipif(not is_databricks173_or_later(), |
| 112 | + reason="CheckOverflowInTableWrite is required on DBR 17.3+") |
| 113 | +def test_delta_merge_check_overflow_in_table_write_error( |
| 114 | + spark_tmp_path, spark_tmp_table_factory): |
| 115 | + updates_view = spark_tmp_table_factory.get() |
| 116 | + |
| 117 | + def do_merge(spark): |
| 118 | + gpu_enabled = str(spark.conf.get("spark.rapids.sql.enabled", "false")).lower() == "true" |
| 119 | + target_path = spark_tmp_path + ("/GPU" if gpu_enabled else "/CPU") |
| 120 | + spark.sql(""" |
| 121 | + SELECT timestamp '2024-01-01 00:00:00' AS timestampF, |
| 122 | + CAST(1 AS TINYINT) AS byteF |
| 123 | + """).write.format("delta") \ |
| 124 | + .option("delta.enableDeletionVectors", "false") \ |
| 125 | + .mode("overwrite") \ |
| 126 | + .save(target_path) |
| 127 | + spark.sql(""" |
| 128 | + SELECT timestamp '2024-01-01 00:00:00' AS timestampF, |
| 129 | + CAST(128 AS INT) AS byteF |
| 130 | + """).createOrReplaceTempView(updates_view) |
| 131 | + return spark.sql(f""" |
| 132 | + MERGE INTO delta.`{target_path}` AS target |
| 133 | + USING {updates_view} AS source |
| 134 | + ON target.timestampF = source.timestampF |
| 135 | + WHEN MATCHED THEN UPDATE SET byteF = source.byteF |
| 136 | + """).collect() |
| 137 | + |
| 138 | + assert_gpu_and_cpu_error( |
| 139 | + do_merge, |
| 140 | + conf=delta_merge_no_cpu_bridge_conf, |
| 141 | + error_message="DELTA_CAST_OVERFLOW_IN_TABLE_WRITE") |
| 142 | + |
| 143 | + |
46 | 144 | @allow_non_gpu(delta_write_fallback_allow, *delta_meta_allow) |
47 | 145 | @delta_lake |
48 | 146 | @ignore_order |
|
0 commit comments