cloud-fan commented on code in PR #59545:
URL: https://github.com/apache/spark/pull/59545#discussion_r4323384269


##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/constraints.scala:
##########
@@ -142,6 +160,17 @@ case class CheckConstraint(
       .build()
   }
 
+  /**
+   * Whether the rules that compute the current time, date, catalog and the 
like change part of `e`,
+   * applied to `e` as the optimizer has it, with its `RuntimeReplaceable`s 
replaced.
+   */
+  private def readsCurrentContext(e: Expression): Boolean = {
+    val replaced = ReplaceExpressions.replace(e)
+    replaced.containsPattern(CURRENT_LIKE) ||
+      (ComputeCurrentTime.applyForExpression(replaced) ne replaced) ||
+      (SpecialDatetimeValues.applyForExpression(replaced) ne replaced)

Review Comment:
   **Nit (P3):** `SpecialDatetimeValues` also folds `'epoch'`, and 
`DateTimeUtils.convertSpecialDate` / `convertSpecialTimestamp` / 
`convertSpecialTimestampNTZ` map it to a fixed 1970-01-01 value that doesn't 
depend on the current time. So a constant condition like `created >= 
CAST('epoch' AS TIMESTAMP)`, or just `created >= 'epoch'` after implicit 
casting, counts as reading the current context here and is stored with 
`predicate = null`. But the predicate built from the optimized condition is 
faithful, and it is what the merge base stored.
   
   Connectors then lose a predicate that could be expressed. And if the 
condition also uses a table-qualified column (`tbl.created >= 'epoch'`), every 
later write fails with UNRESOLVED_COLUMN. The PR description accepts that 
outcome only for conditions that really read the current context.
   
   Could this check count only the special values that depend on the current 
time (`now`, `today`, `tomorrow`, `yesterday`)? One way is to reuse the 
special-value extraction in `DateTimeUtils` (`extractSpecialValue` is private 
there now) instead of checking whether the rewrite changed the tree. A test 
with an ALTER-added `'epoch'` condition would cover it.
   
   **Verification:**
   - **Regression:** An ALTER TABLE CHECK constraint comparing a timestamp 
column with CAST('epoch' AS TIMESTAMP), and one using a table-qualified column 
reference, is stored with a non-null predicate. Later writes accept a row at or 
after the epoch and reject a row before it with a CHECK constraint violation, 
instead of failing to resolve the column.
   - **Behavior:** ALTER TABLE CHECK constraints casting 'now' or 'today' to 
TIMESTAMP, TIMESTAMP_NTZ or DATE are still stored without a predicate and keep 
accepting a row written with the current value and rejecting one written a day 
ahead.
   - **Compatibility:** An ALTER TABLE CHECK constraint comparing with an 
ordinary, non-special datetime string keeps the predicate built from the 
optimized condition, so constraints that read no current context store exactly 
what they did before this change.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/CheckConstraintSuite.scala:
##########
@@ -170,6 +170,60 @@ class CheckConstraintSuite extends QueryTest with 
CommandSuiteBase with DDLComma
     }
   }
 
+  test("SPARK-60032: Check constraint added by ALTER TABLE reading the current 
time") {
+    // The check compares with the time of the write, not the time of the 
ALTER TABLE.
+    Seq(
+      ("TIMESTAMP", "current_timestamp()", "current_timestamp()", "INTERVAL 1 
DAY"),
+      ("TIMESTAMP", "CAST('now' AS TIMESTAMP)", "current_timestamp()", 
"INTERVAL 1 DAY"),
+      ("TIMESTAMP", "to_timestamp('now')", "current_timestamp()", "INTERVAL 1 
DAY"),
+      ("TIMESTAMP_NTZ", "CAST('now' AS TIMESTAMP_NTZ)", "localtimestamp()", 
"INTERVAL 1 DAY"),
+      ("DATE", "current_date()", "current_date()", "1"),

Review Comment:
   **Nit (P3):** For the two DATE rows, the ALTER TABLE and both INSERTs run on 
the same day. So a predicate frozen to the ALTER-time date (`created <= DATE 
'<today>'`, which is what the merge base stores) gives the same accept/reject 
results and the same error parameters, since `expression` comes from 
`predicateSql`. These four cases (2 shapes x ANSI on/off) pass with or without 
the fix, and only the TIMESTAMP / TIMESTAMP_NTZ rows catch the regression. 
Also, no row casts a TIME value to a timestamp type, so nothing covers the 
`ComputeCurrentTime` branch of `readsCurrentContext`, the only check that 
catches those casts.
   
   After each ALTER TABLE, could the test assert 
`getCheckConstraint(table).predicate() == null` and that `predicateSql` is the 
condition text, as 'Predicate should be null if it can't be converted to V2 
predicate' already does? That gives every row a failure signal that doesn't 
depend on the date. A row such as `created <= CAST(TIME '12:00:00' AS 
TIMESTAMP)` that asserts its stored predicate is null would cover the TIME 
branch.
   
   **Verification:**
   - **Regression:** With current-context detection disabled so that every 
ALTER-added constraint stores a predicate built from the optimized condition, 
the current_date() and CAST('today' AS DATE) cases fail on the stored-predicate 
assertion under both ANSI modes, as the timestamp cases already do.
   - **Regression:** With only the TIME-to-timestamp part of current-context 
detection removed, the new TIME cast case fails on its stored-predicate 
assertion.
   - **Behavior:** With the change as implemented, every case in the ALTER 
TABLE current-time test passes under ANSI mode on and off, including the accept 
and reject writes and the new stored-predicate assertions.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to