private StructImpl(Struct value) { super(false, value.getType(), value); }
@Override public int hashCode() { int result = getType().hashCode(); for (int i = 0; i < getColumnCount(); ++i) { result = 31 * result + Objects.hashCode(getAsObject(i)); } return result; }
PrePopulatedResultSet(Type type, Iterable<Struct> rows) { Preconditions.checkNotNull(rows); Preconditions.checkNotNull(type); Preconditions.checkArgument(type.getCode() == Type.Code.STRUCT); for (StructField field : type.getStructFields()) { if (field.getType().getCode() == Code.STRUCT) { throw new UnsupportedOperationException( "STRUCT-typed columns are not supported inside ResultSets."); } } this.type = type; this.rows = rows instanceof List<?> ? (List<Struct>) rows : Lists.newArrayList(rows); for (Struct row : rows) { Preconditions.checkArgument(row.getType().equals(type)); } }
@Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Struct)) { return false; } Struct that = (Struct) o; if (!getType().equals(that.getType())) { return false; } for (int i = 0; i < getColumnCount(); ++i) { if (!Objects.equals(getAsObject(i), that.getAsObject(i))) { return false; } } return true; }
/** * Returns a {@code STRUCT} value of {@code Type} type. * * @param type the type of the {@code STRUCT} value * @param v the struct {@code STRUCT} value. This may be {@code null} to produce a value for which * {@code isNull()} is {@code true}. If non-{@code null}, {@link Struct#getType()} must match * type. */ public static Value struct(Type type, @Nullable Struct v) { if (v == null) { Preconditions.checkArgument( type.getCode() == Code.STRUCT, "Illegal call to create a NULL struct with a non-struct type."); return new StructImpl(type); } else { Preconditions.checkArgument( type.equals(v.getType()), "Mismatch between struct value and type."); return new StructImpl(v); } }
@Test public void bindArrayOfStruct() { Struct arrayElement = structValue(); List<Struct> p = asList(arrayElement, null); List<Struct> rows = resultRows( Statement.newBuilder("SELECT * FROM UNNEST(@p)") .bind("p") .toStructArray(arrayElement.getType(), p) .build(), arrayElement.getType()); assertThat(rows).hasSize(p.size()); assertThat(rows.get(0)).isEqualTo(p.get(0)); // Field accesses on a null struct element (because of SELECT *) return null values. Struct structElementFromNull = rows.get(1); // assertThat(structElementFromNull.isNull()).isFalse(); for (int i = 0; i < arrayElement.getType().getStructFields().size(); ++i) { assertThat(structElementFromNull.isNull(i)).isTrue(); } }
@Test public void unsupportedSelectArrayStructValue() { Struct p = structValue(); expectedException.expect(isSpannerException(ErrorCode.UNIMPLEMENTED)); expectedException.expectMessage( "Unsupported query shape: " + "This query can return a null-valued array of struct, " + "which is not supported by Spanner."); execute( Statement.newBuilder("SELECT @p").bind("p").toStructArray(p.getType(), asList(p)).build(), p.getType()); }
@Ignore // Not yet supported by the backend. @Test public void arrayOfStructNullElement() { Type structType = Type.struct(StructField.of("", Type.string()), StructField.of("", Type.int64())); Struct row = execute( Statement.of( "SELECT ARRAY(SELECT AS STRUCT 'a', 1" + " UNION ALL SELECT CAST (NULL AS STRUCT<string,int64>))"), Type.array(structType)); assertThat(row.isNull(0)).isFalse(); List<Struct> value = row.getStructList(0); assertThat(value.size()).isEqualTo(2); assertThat(value.get(0).getType()).isEqualTo(structType); assertThat(value.get(0).getString(0)).isEqualTo("a"); assertThat(value.get(0).getLong(1)).isEqualTo(1); assertThat(value.get(1)).isNull(); }
@Test public void struct() { Struct struct = Struct.newBuilder().set("f1").to("v1").set("f2").to(30).build(); Value v1 = Value.struct(struct); assertThat(v1.getType()).isEqualTo(struct.getType()); assertThat(v1.isNull()).isFalse(); assertThat(v1.getStruct()).isEqualTo(struct); assertThat(v1.toString()).isEqualTo("[v1, 30]"); Value v2 = Value.struct(struct.getType(), struct); assertThat(v2).isEqualTo(v1); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Mismatch between struct value and type."); Value.struct(Type.struct(Arrays.asList(StructField.of("f3", Type.string()))), struct); }
@Test public void bindStructWithArrayOfStructField() { Struct arrayElement1 = Struct.newBuilder().set("ff1").to("abc").build(); Struct arrayElement2 = Struct.newBuilder().set("ff1").to("def").build(); Struct p = Struct.newBuilder() .set("f1") .toStructArray(arrayElement1.getType(), asList(arrayElement1, arrayElement2)) .build(); List<Struct> rows = resultRows( Statement.newBuilder("SELECT * FROM UNNEST(@p.f1)").bind("p").to(p).build(), arrayElement1.getType()); assertThat(rows.get(0).getString(0)).isEqualTo("abc"); assertThat(rows.get(1).getString(0)).isEqualTo("def"); }
@Test public void bindStruct() { Struct p = structValue(); String query = "SELECT " + "@p.f_int," + "@p.f_bool," + "@p.f_double," + "@p.f_timestamp," + "@p.f_date," + "@p.f_string," + "@p.f_bytes"; Struct row = executeWithRowResultType(Statement.newBuilder(query).bind("p").to(p).build(), p.getType()); assertThat(row).isEqualTo(p); }
@Test public void duplicateFields() { // Duplicate fields are allowed - some SQL queries produce this type of value. Struct struct = Struct.newBuilder().set("").to("x").set("").to(Value.int64(2)).build(); assertThat(struct.getType()) .isEqualTo( Type.struct( Type.StructField.of("", Type.string()), Type.StructField.of("", Type.int64()))); assertThat(struct.isNull(0)).isFalse(); assertThat(struct.isNull(1)).isFalse(); assertThat(struct.getString(0)).isEqualTo("x"); assertThat(struct.getLong(1)).isEqualTo(2); }
@Test public void unnamedFields() { Struct struct = Struct.newBuilder().add(Value.int64(2)).add(Value.int64(3)).build(); assertThat(struct.getType()) .isEqualTo( Type.struct( Type.StructField.of("", Type.int64()), Type.StructField.of("", Type.int64()))); assertThat(struct.getLong(0)).isEqualTo(2); assertThat(struct.getLong(1)).isEqualTo(3); }
@Test public void bindStructWithUnnamedFields() { Struct p = Struct.newBuilder().add(Value.int64(1337)).add(Value.int64(7331)).build(); Struct row = executeWithRowResultType( Statement.newBuilder("SELECT * FROM UNNEST([@p])").bind("p").to(p).build(), p.getType()); assertThat(row.getLong(0)).isEqualTo(1337); assertThat(row.getLong(1)).isEqualTo(7331); }
@Test public void structArrayFieldNull() { Type elementType = Type.struct( Arrays.asList( Type.StructField.of("ff1", Type.string()), Type.StructField.of("ff2", Type.int64()))); Struct struct = Struct.newBuilder().set("f1").to("x").set("f2").toStructArray(elementType, null).build(); assertThat(struct.getType()) .isEqualTo( Type.struct( Type.StructField.of("f1", Type.string()), Type.StructField.of("f2", Type.array(elementType)))); assertThat(struct.isNull(0)).isFalse(); assertThat(struct.isNull(1)).isTrue(); }
@Test public void unsupportedSelectStructValue() { Struct p = structValue(); expectedException.expect(isSpannerException(ErrorCode.UNIMPLEMENTED)); expectedException.expectMessage( "Unsupported query shape: " + "A struct value cannot be returned as a column value."); execute(Statement.newBuilder("SELECT @p").bind("p").to(p).build(), p.getType()); }
@Test public void structWithStructField() { Struct nestedStruct = Struct.newBuilder().set("f2f1").to(10).build(); Struct struct = Struct.newBuilder() .set("f1") .to("v1") .set("f2") .to(nestedStruct) .set("f3") .to(nestedStruct.getType(), null) .build(); assertThat(struct.getType()) .isEqualTo( Type.struct( Type.StructField.of("f1", Type.string()), Type.StructField.of("f2", Type.struct(Type.StructField.of("f2f1", Type.int64()))), Type.StructField.of("f3", Type.struct(Type.StructField.of("f2f1", Type.int64()))))); assertThat(struct.isNull(0)).isFalse(); assertThat(struct.isNull(1)).isFalse(); assertThat(struct.isNull(2)).isTrue(); assertThat(struct.getString(0)).isEqualTo("v1"); assertThat(struct.getString("f1")).isEqualTo("v1"); assertThat(struct.getStruct(1)).isEqualTo(nestedStruct); assertThat(struct.getStruct("f2")).isEqualTo(nestedStruct); }
@Test public void builder() { // These tests are basic: AbstractStructReaderTypesTest already covers all type getters. Struct struct = Struct.newBuilder() .set("f1") .to("x") .set("f2") .to(2) .set("f3") .to(Value.bool(null)) .build(); assertThat(struct.getType()) .isEqualTo( Type.struct( Type.StructField.of("f1", Type.string()), Type.StructField.of("f2", Type.int64()), Type.StructField.of("f3", Type.bool()))); assertThat(struct.isNull(0)).isFalse(); assertThat(struct.isNull(1)).isFalse(); assertThat(struct.isNull(2)).isTrue(); assertThat(struct.getString(0)).isEqualTo("x"); assertThat(struct.getLong(1)).isEqualTo(2); }
@Test public void bindStructWithStructField() { Struct nestedStruct = Struct.newBuilder().set("ff1").to("abc").build(); Struct p = Struct.newBuilder().set("f1").to(nestedStruct).build(); Struct row = executeWithRowResultType( Statement.newBuilder("SELECT @p.f1.ff1").bind("p").to(p).build(), nestedStruct.getType()); assertThat(row.getString(0)).isEqualTo("abc"); }
@Test public void bindStructWithDuplicateFieldNames() { Struct p = Struct.newBuilder() .set("f1") .to(Value.int64(1337)) .set("f1") .to(Value.string("1337")) .build(); Struct row = executeWithRowResultType( Statement.newBuilder("SELECT * FROM UNNEST([@p])").bind("p").to(p).build(), p.getType()); assertThat(row.getLong(0)).isEqualTo(1337); assertThat(row.getString(1)).isEqualTo("1337"); }