googleapis / java-spanner

Apache License 2.0
64 stars 116 forks source link

Struct Object Equality Isn't Value-Based #2610

Closed senbrow closed 1 year ago

senbrow commented 1 year ago

Environment details

OS type and version: Linux Mint 5.15.0-79-generic #86-Ubuntu Java version: 20 Spanner Java Library Version: 6.42.3

Steps to reproduce

  1. Read an ARRAY of STRUCTs in a query into a ResultSet
  2. Compare the STRUCT contents to manually-constructed Java Struct objects.
  3. Observe that value-equality still results in a "not equal" outcome.

Code example

These are JUnit4 testcases implemented with AssertJ assertions:

EXAMPLE 1 (list of structs):

    DatabaseClient client = <...>;
    var resultSet =
        client.singleUse().executeQuery(Statement.of(
            """
            SELECT ARRAY<STRUCT<INT64, STRING>>[
                STRUCT(1 AS x, "foo" AS y)
            ]
            AS StructList"""));

    assertThat(resultSet.next()).isTrue();
    assertThat(resultSet.getStructList("StructList")).containsExactly(
        Struct.newBuilder()
            .set("x").to(1L)
            .set("y").to("foo")
            .build()
    );

Result:

org.junit.ComparisonFailure: 
Expecting actual:
  [[1, foo]]
to contain exactly (and in same order):
  [[1, foo]]
but some elements were not found:
  [[1, foo]]
and others were not expected:
  [[1, foo]]
 expected:<[[1, foo]] ([ArrayList@36f1046f])> but was:<[[1, foo]] ([UnmodifiableRandomAccessList@692fd26])>
        at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67)
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)

EXAMPLE 2 (piecewise comparison):

    DatabaseClient client = <...>;
    var resultSet =
        client.singleUse().executeQuery(Statement.of(
            """
            SELECT ARRAY<STRUCT<INT64, STRING>>[
                STRUCT(1 AS x, "foo" AS y)
            ]
            AS StructList"""));

    assertThat(resultSet.next()).isTrue();
    assertThat(resultSet.getStructList("StructList")).element(0).isEqualTo(
        Struct.newBuilder()
            .set("x").to(1L)
            .set("y").to("foo")
            .build()
    );

Result:

java.lang.AssertionError: [List element at index 0] 
expected: "[1, foo] (ValueListStruct@3157e4c0)"
 but was: "[1, foo] (GrpcStruct@4e31c3ec)"
senbrow commented 1 year ago

Note: the examples above connect to an emulated spanner instance running in a local Docker container to evaluate the SQL.

arpan14 commented 1 year ago

I don't think this is a valid query. When I try to execute this against a Spanner table from the cloud console, I get the below error

Statement failed: Unsupported query shape: This query returns a STRUCT value constructed by a scalar subquery or struct construction syntax. Spanner does not yet support returning STRUCT except as arrays-of-structs. Rephrase your query to produce the struct value with an array subquery: ARRAY(SELECT AS STRUCT expr ...) or flatten the structs fields into the result.

Could you send a valid select statement against a Spanner table?