spark-test commented on code in PR #59908:
URL: https://github.com/apache/spark/pull/59908#discussion_r4418114502
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/internal/connector/ToStringSQLBuilder.scala:
##########
@@ -58,6 +59,7 @@ class ToStringSQLBuilder extends V2ExpressionSQLBuilder with
Serializable {
// until StackOverflowError. Render it as a function call instead.
override protected def visitUnexpectedExpr(expr: Expression): String = expr
match {
case e: GeneralScalarExpression => visitSQLFunction(e.name(), e.children())
+ case p: LookupParameter => p.describe()
Review Comment:
**Nit (P3):** With this case, `describe()` of an offered null-safe candidate
such as `id <=> ?` goes through the inherited `<=>` expansion in
`V2ExpressionSQLBuilder.visitBinaryComparison`, which writes each operand three
times: `((id IS NOT NULL AND ? IS NOT NULL AND id = ?) OR (id IS NULL AND ? IS
NULL))`. That shows three `?` for one parameter, and `[id <=> ?, ts >= ?]`
prints four for a two-field parameter row. For a literal the repetition is
harmless because the value is visible; here it misstates how many parameter-row
fields the candidate contributes, in logs, error messages and any future
EXPLAIN. A narrow fix is to override the expression-level
`visitBinaryComparison(name, le, re)` in this builder, rendering `l <=> r` when
an operand is a `LookupParameter` and delegating to `super` otherwise (as
`OracleDialect` does for its own operand types). That keeps the expanded form
`V2PredicateSuite` pins for literal `<=>` filters. Only `id = ?` is rendered in
the new tests today.
**Verification:**
- **Behavior:** A null-safe lookup candidate between a column and a lookup
parameter is described with a single parameter marker, while equality and range
candidates keep their current single-marker rendering.
- **Compatibility:** The existing rendering of a null-safe comparison
between a column and a literal keeps its expanded IS NULL form, so
pushed-filter descriptions and the tests that pin them are unaffected.
##########
sql/catalyst/src/test/scala/org/apache/spark/sql/connector/read/ExternalLookupApiSuite.scala:
##########
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+package org.apache.spark.sql.connector.read
+
+import scala.reflect.ClassTag
+
+import org.apache.spark.{SparkConf, SparkFunSuite,
SparkUnsupportedOperationException}
+import org.apache.spark.serializer.JavaSerializer
+import org.apache.spark.sql.connector.expressions.{Expression, FieldReference}
+import org.apache.spark.sql.connector.expressions.filter.Predicate
+import org.apache.spark.sql.types.{IntegerType, StructType}
+
+class ExternalLookupApiSuite extends SparkFunSuite {
+
+ private val offeredPredicate: Predicate =
+ new Predicate("=", Array[Expression](
+ FieldReference.column("id"), new LookupParameter(IntegerType)))
+
+ private def javaRoundTrip[T: ClassTag](value: T): T = {
+ val serializer = new JavaSerializer(new SparkConf()).newInstance()
+ serializer.deserialize[T](serializer.serialize(value))
+ }
+
+ test("a lookup parameter is a leaf rendered as ?") {
Review Comment:
**Nit (P3):** `LookupParameter` documents identity equality ("same-shaped
candidates are distinct"), and that identity is the only thing that keeps two
same-shaped candidates apart: `Predicate` inherits
`GeneralScalarExpression.equals`/`hashCode`, which compare the name and
children. None of the tests in this suite compares two parameters or two
same-shaped predicates, so a later value-based `equals` (for consistency with
`FieldReference` or `LiteralValue`) would pass all of them and silently merge
candidates such as the two offered for `t.id = s.a AND t.id = s.b + 1`.
Equality assertions next to this test would pin the contract.
**Verification:**
- **Regression:** Two same-shaped lookup candidates built from distinct
lookup parameters compare unequal while each compares equal to itself, so
giving the lookup parameter value-based equality fails the tests. The
assertions compare with equals only; distinct objects are not guaranteed
distinct hash codes, so the tests do not assert hash-code inequality.
##########
sql/catalyst/src/main/java/org/apache/spark/sql/connector/read/ExternalLookupReader.java:
##########
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+package org.apache.spark.sql.connector.read;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import org.apache.spark.annotation.Evolving;
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.connector.metric.CustomTaskMetric;
+
+/**
+ * A task-local client that executes lookup requests.
+ * <ul>
+ * <li>Calls to a reader and its cursors are sequential, but may come from
different threads.</li>
+ * <li>Requests are not snapshot boundaries: results may mix source
versions.</li>
+ * <li>Retried or speculative tasks may repeat a request; duplicates must be
tolerated.</li>
+ * <li>Implementations should respond to thread interruption if their client
supports it.</li>
+ * </ul>
+ *
+ * @since 4.4.0
+ */
+@Evolving
+public interface ExternalLookupReader extends Closeable {
+
+ /**
+ * Looks up {@code uniqueParameters} and returns a cursor over all matches.
For example:
+ * <pre>{@code
+ * uniqueParameters: [(1), (2), (3)]
+ * cursor yields: (0, a), (0, b), (2, c) // (inputOrdinal, value row)
+ * => row 0 has two matches, row 1 is a miss, row 2 has one match
+ * }</pre>
+ * Spark guarantees that {@code uniqueParameters} is non-null, non-empty and
has at most
+ * {@link ExternalLookup#maxParameterRowsPerRequest()} non-null rows. Field
{@code i} of each row
Review Comment:
**Nit (P3):** As written, this caps only how many elements are non-null: an
array with null slots after N non-null rows would satisfy it, and the array
length itself is never bounded. The rest of the contract treats every element
as a distinct parameter row, and `inputOrdinal()` ranges over `[0,
uniqueParameters.length)`, so implementors can't tell from this sentence
whether to handle null elements or whether they can size per-request buffers
from `maxParameterRowsPerRequest()`. Suggested wording: `Spark guarantees that
{@code uniqueParameters} is non-null and holds between 1 and {@link
ExternalLookup#maxParameterRowsPerRequest()} rows, none of them null.`
**Verification:**
- **Inspection:** The reworded guarantee bounds the length of the input
array by maxParameterRowsPerRequest(), rules out null elements, and stays
consistent with the cursor's input ordinal ranging over the whole array.
- **Compile:** The connector read API documentation still builds without
Javadoc lint errors and stays within the source line-length limit.
--
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]