|
156 | 156 | import java.util.HashMap;
|
157 | 157 | import java.util.HashSet;
|
158 | 158 | import java.util.Iterator;
|
| 159 | +import java.util.LinkedHashMap; |
159 | 160 | import java.util.List;
|
160 | 161 | import java.util.Map;
|
161 | 162 | import java.util.Set;
|
@@ -231,6 +232,31 @@ public class ITBigQueryTest {
|
231 | 232 | .setMode(Field.Mode.REQUIRED)
|
232 | 233 | .setDescription("RecordDescription")
|
233 | 234 | .build();
|
| 235 | + |
| 236 | + private static final Field REPEATED_RECORD_FIELD_SCHEMA = |
| 237 | + Field.newBuilder( |
| 238 | + "Addresses", |
| 239 | + LegacySQLTypeName.RECORD, |
| 240 | + Field.newBuilder("Status", LegacySQLTypeName.STRING) |
| 241 | + .setMode(Field.Mode.NULLABLE) |
| 242 | + .build(), |
| 243 | + Field.newBuilder("Address", LegacySQLTypeName.STRING) |
| 244 | + .setMode(Field.Mode.NULLABLE) |
| 245 | + .build(), |
| 246 | + Field.newBuilder("City", LegacySQLTypeName.STRING) |
| 247 | + .setMode(Field.Mode.NULLABLE) |
| 248 | + .build(), |
| 249 | + Field.newBuilder("State", LegacySQLTypeName.STRING) |
| 250 | + .setMode(Field.Mode.NULLABLE) |
| 251 | + .build(), |
| 252 | + Field.newBuilder("Zip", LegacySQLTypeName.STRING) |
| 253 | + .setMode(Field.Mode.NULLABLE) |
| 254 | + .build(), |
| 255 | + Field.newBuilder("NumberOfYears", LegacySQLTypeName.STRING) |
| 256 | + .setMode(Field.Mode.NULLABLE) |
| 257 | + .build()) |
| 258 | + .setMode(Field.Mode.REPEATED) |
| 259 | + .build(); |
234 | 260 | private static final Field INTEGER_FIELD_SCHEMA =
|
235 | 261 | Field.newBuilder("IntegerField", LegacySQLTypeName.INTEGER)
|
236 | 262 | .setMode(Field.Mode.NULLABLE)
|
@@ -422,6 +448,18 @@ public class ITBigQueryTest {
|
422 | 448 | .setMode(Field.Mode.NULLABLE)
|
423 | 449 | .build());
|
424 | 450 |
|
| 451 | + private static final Schema REPEATED_RECORD_TABLE_SCHEMA = |
| 452 | + Schema.of( |
| 453 | + Field.newBuilder("ID", LegacySQLTypeName.STRING).setMode(Field.Mode.NULLABLE).build(), |
| 454 | + Field.newBuilder("FirstName", LegacySQLTypeName.STRING) |
| 455 | + .setMode(Field.Mode.NULLABLE) |
| 456 | + .build(), |
| 457 | + Field.newBuilder("LastName", LegacySQLTypeName.STRING) |
| 458 | + .setMode(Field.Mode.NULLABLE) |
| 459 | + .build(), |
| 460 | + Field.newBuilder("DOB", LegacySQLTypeName.DATE).setMode(Field.Mode.NULLABLE).build(), |
| 461 | + REPEATED_RECORD_FIELD_SCHEMA); |
| 462 | + |
425 | 463 | private static final Schema SIMPLE_SCHEMA = Schema.of(STRING_FIELD_SCHEMA);
|
426 | 464 | private static final Schema QUERY_RESULT_SCHEMA =
|
427 | 465 | Schema.of(
|
@@ -4062,6 +4100,214 @@ public void testStructNamedQueryParameters() throws InterruptedException {
|
4062 | 4100 | }
|
4063 | 4101 | }
|
4064 | 4102 |
|
| 4103 | + @Test |
| 4104 | + public void testRepeatedRecordNamedQueryParameters() throws InterruptedException { |
| 4105 | + String[] stringValues = new String[] {"test-stringField", "test-stringField2"}; |
| 4106 | + List<QueryParameterValue> tuples = new ArrayList<>(); |
| 4107 | + for (int i = 0; i < 2; i++) { |
| 4108 | + QueryParameterValue stringValue = QueryParameterValue.string(stringValues[i]); |
| 4109 | + Map<String, QueryParameterValue> struct = new HashMap<>(); |
| 4110 | + struct.put("stringField", stringValue); |
| 4111 | + QueryParameterValue recordValue = QueryParameterValue.struct(struct); |
| 4112 | + tuples.add(recordValue); |
| 4113 | + } |
| 4114 | + |
| 4115 | + QueryParameterValue repeatedRecord = |
| 4116 | + QueryParameterValue.array(tuples.toArray(), StandardSQLTypeName.STRUCT); |
| 4117 | + String query = "SELECT @repeatedRecordField AS repeatedRecord"; |
| 4118 | + QueryJobConfiguration config = |
| 4119 | + QueryJobConfiguration.newBuilder(query) |
| 4120 | + .setDefaultDataset(DATASET) |
| 4121 | + .setUseLegacySql(false) |
| 4122 | + .addNamedParameter("repeatedRecordField", repeatedRecord) |
| 4123 | + .build(); |
| 4124 | + TableResult result = bigquery.query(config); |
| 4125 | + assertEquals(1, Iterables.size(result.getValues())); |
| 4126 | + |
| 4127 | + FieldList subSchema = result.getSchema().getFields().get("repeatedRecord").getSubFields(); |
| 4128 | + for (FieldValueList values : result.iterateAll()) { |
| 4129 | + for (FieldValue value : values) { |
| 4130 | + assertEquals(FieldValue.Attribute.REPEATED, value.getAttribute()); |
| 4131 | + assertEquals(2, value.getRepeatedValue().size()); |
| 4132 | + for (int i = 0; i < 2; i++) { |
| 4133 | + FieldValue record = value.getRepeatedValue().get(i); |
| 4134 | + assertEquals(FieldValue.Attribute.RECORD, record.getAttribute()); |
| 4135 | + FieldValueList recordValue = record.getRecordValue(); |
| 4136 | + assertEquals( |
| 4137 | + stringValues[i], |
| 4138 | + FieldValueList.of(recordValue, subSchema).get("stringField").getValue()); |
| 4139 | + } |
| 4140 | + } |
| 4141 | + } |
| 4142 | + } |
| 4143 | + |
| 4144 | + @Test |
| 4145 | + public void testUnnestRepeatedRecordNamedQueryParameter() throws InterruptedException { |
| 4146 | + Boolean[] boolValues = new Boolean[] {true, false}; |
| 4147 | + List<QueryParameterValue> tuples = new ArrayList<>(); |
| 4148 | + for (int i = 0; i < 2; i++) { |
| 4149 | + QueryParameterValue boolValue = QueryParameterValue.bool(boolValues[i]); |
| 4150 | + Map<String, QueryParameterValue> struct = new HashMap<>(); |
| 4151 | + struct.put("boolField", boolValue); |
| 4152 | + QueryParameterValue recordValue = QueryParameterValue.struct(struct); |
| 4153 | + tuples.add(recordValue); |
| 4154 | + } |
| 4155 | + |
| 4156 | + QueryParameterValue repeatedRecord = |
| 4157 | + QueryParameterValue.array(tuples.toArray(), StandardSQLTypeName.STRUCT); |
| 4158 | + String query = |
| 4159 | + "SELECT * FROM (SELECT STRUCT(" |
| 4160 | + + boolValues[0] |
| 4161 | + + " AS boolField) AS repeatedRecord) WHERE repeatedRecord IN UNNEST(@repeatedRecordField)"; |
| 4162 | + QueryJobConfiguration config = |
| 4163 | + QueryJobConfiguration.newBuilder(query) |
| 4164 | + .setDefaultDataset(DATASET) |
| 4165 | + .setUseLegacySql(false) |
| 4166 | + .addNamedParameter("repeatedRecordField", repeatedRecord) |
| 4167 | + .build(); |
| 4168 | + TableResult result = bigquery.query(config); |
| 4169 | + assertEquals(1, Iterables.size(result.getValues())); |
| 4170 | + |
| 4171 | + FieldList subSchema = result.getSchema().getFields().get("repeatedRecord").getSubFields(); |
| 4172 | + for (FieldValueList values : result.iterateAll()) { |
| 4173 | + for (FieldValue value : values) { |
| 4174 | + assertEquals(FieldValue.Attribute.RECORD, value.getAttribute()); |
| 4175 | + FieldValueList recordValue = value.getRecordValue(); |
| 4176 | + assertEquals( |
| 4177 | + boolValues[0], |
| 4178 | + FieldValueList.of(recordValue, subSchema).get("boolField").getBooleanValue()); |
| 4179 | + } |
| 4180 | + } |
| 4181 | + } |
| 4182 | + |
| 4183 | + @Test |
| 4184 | + public void testUnnestRepeatedRecordNamedQueryParameterFromDataset() throws InterruptedException { |
| 4185 | + TableId tableId = TableId.of(DATASET, "test_repeated_record_table"); |
| 4186 | + setUpRepeatedRecordTable(tableId); |
| 4187 | + |
| 4188 | + List<QueryParameterValue> tuples = new ArrayList<>(); |
| 4189 | + QueryParameterValue statusValue = QueryParameterValue.string("single"); |
| 4190 | + QueryParameterValue addressValue = QueryParameterValue.string("123 this lane"); |
| 4191 | + QueryParameterValue cityValue = QueryParameterValue.string("Toronto"); |
| 4192 | + QueryParameterValue stateValue = QueryParameterValue.string("ON"); |
| 4193 | + QueryParameterValue zipValue = QueryParameterValue.string("1h2j34"); |
| 4194 | + QueryParameterValue numberOfYearsValue = QueryParameterValue.string("3"); |
| 4195 | + |
| 4196 | + Map<String, QueryParameterValue> struct = new LinkedHashMap<>(); |
| 4197 | + struct.put("statusValue", statusValue); |
| 4198 | + struct.put("addressValue", addressValue); |
| 4199 | + struct.put("cityValue", cityValue); |
| 4200 | + struct.put("stateValue", stateValue); |
| 4201 | + struct.put("zipValue", zipValue); |
| 4202 | + struct.put("numberOfYearsValue", numberOfYearsValue); |
| 4203 | + QueryParameterValue recordValue = QueryParameterValue.struct(struct); |
| 4204 | + tuples.add(recordValue); |
| 4205 | + |
| 4206 | + QueryParameterValue repeatedRecord = |
| 4207 | + QueryParameterValue.array(tuples.toArray(), StandardSQLTypeName.STRUCT); |
| 4208 | + |
| 4209 | + String query = |
| 4210 | + "SELECT * FROM " |
| 4211 | + + tableId.getTable() |
| 4212 | + + ", UNNEST(@repeatedRecord) AS TEMP where TEMP IN UNNEST(addresses);"; |
| 4213 | + QueryJobConfiguration queryConfig = |
| 4214 | + QueryJobConfiguration.newBuilder(query) |
| 4215 | + .setDefaultDataset(DATASET) |
| 4216 | + .setUseLegacySql(false) |
| 4217 | + .addNamedParameter("repeatedRecord", repeatedRecord) |
| 4218 | + .build(); |
| 4219 | + TableResult results = bigquery.query(queryConfig); |
| 4220 | + |
| 4221 | + assertEquals(1, Iterables.size(results.getValues())); |
| 4222 | + for (FieldValueList values : results.iterateAll()) { |
| 4223 | + assertEquals("1", values.get("ID").getStringValue()); |
| 4224 | + assertEquals("first_name1", values.get("FirstName").getStringValue()); |
| 4225 | + assertEquals(2, values.get("Addresses").getRecordValue().size()); |
| 4226 | + } |
| 4227 | + } |
| 4228 | + |
| 4229 | + private void setUpRepeatedRecordTable(TableId tableId) { |
| 4230 | + StandardTableDefinition tableDefinition = |
| 4231 | + StandardTableDefinition.of(REPEATED_RECORD_TABLE_SCHEMA); |
| 4232 | + TableInfo tableInfo = TableInfo.of(tableId, tableDefinition); |
| 4233 | + bigquery.create(tableInfo); |
| 4234 | + |
| 4235 | + ImmutableMap.Builder<String, Object> builder1 = ImmutableMap.builder(); |
| 4236 | + builder1.put("ID", "1"); |
| 4237 | + builder1.put("FirstName", "first_name1"); |
| 4238 | + builder1.put("LastName", "last_name1"); |
| 4239 | + builder1.put("DOB", "1995-08-09"); |
| 4240 | + builder1.put( |
| 4241 | + "Addresses", |
| 4242 | + ImmutableList.of( |
| 4243 | + ImmutableMap.of( |
| 4244 | + "Status", "single", |
| 4245 | + "Address", "123 this lane", |
| 4246 | + "City", "Toronto", |
| 4247 | + "State", "ON", |
| 4248 | + "Zip", "1h2j34", |
| 4249 | + "NumberOfYears", "3"), |
| 4250 | + ImmutableMap.of( |
| 4251 | + "Status", "couple", |
| 4252 | + "Address", "345 that lane", |
| 4253 | + "City", "Maple", |
| 4254 | + "State", "ON", |
| 4255 | + "Zip", "1h2j34", |
| 4256 | + "NumberOfYears", "5"))); |
| 4257 | + |
| 4258 | + ImmutableMap.Builder<String, Object> builder2 = ImmutableMap.builder(); |
| 4259 | + builder2.put("ID", "2"); |
| 4260 | + builder2.put("FirstName", "first_name2"); |
| 4261 | + builder2.put("LastName", "last_name2"); |
| 4262 | + builder2.put("DOB", "1992-03-19"); |
| 4263 | + builder2.put( |
| 4264 | + "Addresses", |
| 4265 | + ImmutableList.of( |
| 4266 | + ImmutableMap.of( |
| 4267 | + "Status", "single", |
| 4268 | + "Address", "97 Kota lane", |
| 4269 | + "City", "Ottawa", |
| 4270 | + "State", "ON", |
| 4271 | + "Zip", "1h2j34", |
| 4272 | + "NumberOfYears", "3"), |
| 4273 | + ImmutableMap.of( |
| 4274 | + "Status", "couple", |
| 4275 | + "Address", "75 Malta lane", |
| 4276 | + "City", "Victoria", |
| 4277 | + "State", "AL", |
| 4278 | + "Zip", "1h2j34", |
| 4279 | + "NumberOfYears", "5"))); |
| 4280 | + |
| 4281 | + InsertAllRequest request = |
| 4282 | + InsertAllRequest.newBuilder(tableInfo.getTableId()) |
| 4283 | + .addRow(builder1.build()) |
| 4284 | + .addRow(builder2.build()) |
| 4285 | + .build(); |
| 4286 | + bigquery.insertAll(request); |
| 4287 | + } |
| 4288 | + |
| 4289 | + @Test |
| 4290 | + public void testEmptyRepeatedRecordNamedQueryParameters() throws InterruptedException { |
| 4291 | + QueryParameterValue[] tuples = {}; |
| 4292 | + |
| 4293 | + QueryParameterValue repeatedRecord = |
| 4294 | + QueryParameterValue.array(tuples, StandardSQLTypeName.STRUCT); |
| 4295 | + String query = |
| 4296 | + "SELECT * FROM (SELECT STRUCT(false AS boolField) AS repeatedRecord) WHERE repeatedRecord IN UNNEST(@repeatedRecordField)"; |
| 4297 | + QueryJobConfiguration config = |
| 4298 | + QueryJobConfiguration.newBuilder(query) |
| 4299 | + .setDefaultDataset(DATASET) |
| 4300 | + .setUseLegacySql(false) |
| 4301 | + .addNamedParameter("repeatedRecordField", repeatedRecord) |
| 4302 | + .build(); |
| 4303 | + try { |
| 4304 | + bigquery.query(config); |
| 4305 | + fail("an empty array of struct query parameter shouldn't work with 'IN UNNEST'"); |
| 4306 | + } catch (BigQueryException e) { |
| 4307 | + // Nothing to do |
| 4308 | + } |
| 4309 | + } |
| 4310 | + |
4065 | 4311 | @Test
|
4066 | 4312 | public void testStructQuery() throws InterruptedException {
|
4067 | 4313 | // query into a table
|
|
0 commit comments