|
55 | 55 | import org.apache.beam.sdk.schemas.Schema.FieldType; |
56 | 56 | import org.apache.beam.sdk.schemas.logicaltypes.EnumerationType; |
57 | 57 | import org.apache.beam.sdk.schemas.logicaltypes.SqlTypes; |
| 58 | +import org.apache.beam.sdk.schemas.logicaltypes.Timestamp; |
58 | 59 | import org.apache.beam.sdk.values.Row; |
59 | 60 | import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap; |
60 | 61 | import org.joda.time.DateTime; |
@@ -1294,4 +1295,173 @@ public void testTrimSchema() { |
1294 | 1295 | BigQueryUtils.trimSchema(BQ_ROW_TYPE, Arrays.asList("row.id", "row.value", "row.name"))); |
1295 | 1296 | } |
1296 | 1297 | } |
| 1298 | + |
| 1299 | + @Test |
| 1300 | + public void testFromTableSchema_timestampPrecision12_defaultToNanos() { |
| 1301 | + TableFieldSchema picosTimestamp = |
| 1302 | + new TableFieldSchema().setName("ts").setType("TIMESTAMP").setTimestampPrecision(12L); |
| 1303 | + TableSchema bqSchema = new TableSchema().setFields(Arrays.asList(picosTimestamp)); |
| 1304 | + |
| 1305 | + Schema beamSchema = BigQueryUtils.fromTableSchema(bqSchema); |
| 1306 | + |
| 1307 | + assertEquals( |
| 1308 | + Schema.builder().addNullableField("ts", FieldType.logicalType(Timestamp.NANOS)).build(), |
| 1309 | + beamSchema); |
| 1310 | + } |
| 1311 | + |
| 1312 | + @Test |
| 1313 | + public void testFromTableSchema_timestampPrecision12_millis() { |
| 1314 | + TableFieldSchema picosTimestamp = |
| 1315 | + new TableFieldSchema().setName("ts").setType("TIMESTAMP").setTimestampPrecision(12L); |
| 1316 | + TableSchema bqSchema = new TableSchema().setFields(Arrays.asList(picosTimestamp)); |
| 1317 | + |
| 1318 | + BigQueryUtils.SchemaConversionOptions options = |
| 1319 | + BigQueryUtils.SchemaConversionOptions.builder() |
| 1320 | + .setPicosecondTimestampMapping(TimestampPrecision.MILLIS) |
| 1321 | + .build(); |
| 1322 | + Schema beamSchema = BigQueryUtils.fromTableSchema(bqSchema, options); |
| 1323 | + |
| 1324 | + assertEquals( |
| 1325 | + Schema.builder().addNullableField("ts", FieldType.logicalType(Timestamp.MILLIS)).build(), |
| 1326 | + beamSchema); |
| 1327 | + } |
| 1328 | + |
| 1329 | + @Test |
| 1330 | + public void testFromTableSchema_timestampPrecision12_micros() { |
| 1331 | + TableFieldSchema picosTimestamp = |
| 1332 | + new TableFieldSchema().setName("ts").setType("TIMESTAMP").setTimestampPrecision(12L); |
| 1333 | + TableSchema bqSchema = new TableSchema().setFields(Arrays.asList(picosTimestamp)); |
| 1334 | + |
| 1335 | + BigQueryUtils.SchemaConversionOptions options = |
| 1336 | + BigQueryUtils.SchemaConversionOptions.builder() |
| 1337 | + .setPicosecondTimestampMapping(TimestampPrecision.MICROS) |
| 1338 | + .build(); |
| 1339 | + Schema beamSchema = BigQueryUtils.fromTableSchema(bqSchema, options); |
| 1340 | + |
| 1341 | + assertEquals( |
| 1342 | + Schema.builder().addNullableField("ts", FieldType.logicalType(Timestamp.MICROS)).build(), |
| 1343 | + beamSchema); |
| 1344 | + } |
| 1345 | + |
| 1346 | + @Test |
| 1347 | + public void testFromTableSchema_timestampPrecision12_nanos() { |
| 1348 | + TableFieldSchema picosTimestamp = |
| 1349 | + new TableFieldSchema().setName("ts").setType("TIMESTAMP").setTimestampPrecision(12L); |
| 1350 | + TableSchema bqSchema = new TableSchema().setFields(Arrays.asList(picosTimestamp)); |
| 1351 | + |
| 1352 | + BigQueryUtils.SchemaConversionOptions options = |
| 1353 | + BigQueryUtils.SchemaConversionOptions.builder() |
| 1354 | + .setPicosecondTimestampMapping(TimestampPrecision.NANOS) |
| 1355 | + .build(); |
| 1356 | + Schema beamSchema = BigQueryUtils.fromTableSchema(bqSchema, options); |
| 1357 | + |
| 1358 | + assertEquals( |
| 1359 | + Schema.builder().addNullableField("ts", FieldType.logicalType(Timestamp.NANOS)).build(), |
| 1360 | + beamSchema); |
| 1361 | + } |
| 1362 | + |
| 1363 | + @Test |
| 1364 | + public void testFromTableSchema_timestampPrecision12_picos() { |
| 1365 | + TableFieldSchema picosTimestamp = |
| 1366 | + new TableFieldSchema().setName("ts").setType("TIMESTAMP").setTimestampPrecision(12L); |
| 1367 | + TableSchema bqSchema = new TableSchema().setFields(Arrays.asList(picosTimestamp)); |
| 1368 | + |
| 1369 | + BigQueryUtils.SchemaConversionOptions options = |
| 1370 | + BigQueryUtils.SchemaConversionOptions.builder() |
| 1371 | + .setPicosecondTimestampMapping(TimestampPrecision.PICOS) |
| 1372 | + .build(); |
| 1373 | + Schema beamSchema = BigQueryUtils.fromTableSchema(bqSchema, options); |
| 1374 | + |
| 1375 | + assertEquals(Schema.builder().addNullableField("ts", FieldType.STRING).build(), beamSchema); |
| 1376 | + } |
| 1377 | + |
| 1378 | + @Test |
| 1379 | + public void testFromTableSchema_timestampPrecision6_ignoredOption() { |
| 1380 | + // Standard microsecond precision should ignore the picosecond conversion option |
| 1381 | + TableFieldSchema microsTimestamp = |
| 1382 | + new TableFieldSchema().setName("ts").setType("TIMESTAMP").setTimestampPrecision(6L); |
| 1383 | + TableSchema bqSchema = new TableSchema().setFields(Arrays.asList(microsTimestamp)); |
| 1384 | + |
| 1385 | + BigQueryUtils.SchemaConversionOptions options = |
| 1386 | + BigQueryUtils.SchemaConversionOptions.builder() |
| 1387 | + .setPicosecondTimestampMapping(TimestampPrecision.PICOS) |
| 1388 | + .build(); |
| 1389 | + Schema beamSchema = BigQueryUtils.fromTableSchema(bqSchema, options); |
| 1390 | + |
| 1391 | + assertEquals(Schema.builder().addNullableField("ts", FieldType.DATETIME).build(), beamSchema); |
| 1392 | + } |
| 1393 | + |
| 1394 | + @Test |
| 1395 | + public void testFromTableSchema_timestampNullPrecision_defaultsToDatetime() { |
| 1396 | + // Null precision should default to DATETIME (backwards compatibility) |
| 1397 | + TableFieldSchema timestamp = new TableFieldSchema().setName("ts").setType("TIMESTAMP"); |
| 1398 | + TableSchema bqSchema = new TableSchema().setFields(Arrays.asList(timestamp)); |
| 1399 | + |
| 1400 | + Schema beamSchema = BigQueryUtils.fromTableSchema(bqSchema); |
| 1401 | + |
| 1402 | + assertEquals(Schema.builder().addNullableField("ts", FieldType.DATETIME).build(), beamSchema); |
| 1403 | + } |
| 1404 | + |
| 1405 | + @Test |
| 1406 | + @SuppressWarnings("JavaInstantGetSecondsGetNano") |
| 1407 | + public void testToBeamRow_timestampNanos_utcSuffix() { |
| 1408 | + Schema schema = Schema.builder().addLogicalTypeField("ts", Timestamp.NANOS).build(); |
| 1409 | + |
| 1410 | + // BigQuery format with " UTC" suffix |
| 1411 | + String timestamp = "2024-08-10 16:52:07.123456789 UTC"; |
| 1412 | + |
| 1413 | + Row beamRow = BigQueryUtils.toBeamRow(schema, new TableRow().set("ts", timestamp)); |
| 1414 | + |
| 1415 | + java.time.Instant actual = (java.time.Instant) beamRow.getValue("ts"); |
| 1416 | + assertEquals(2024, actual.atZone(java.time.ZoneOffset.UTC).getYear()); |
| 1417 | + assertEquals(8, actual.atZone(java.time.ZoneOffset.UTC).getMonthValue()); |
| 1418 | + assertEquals(10, actual.atZone(java.time.ZoneOffset.UTC).getDayOfMonth()); |
| 1419 | + assertEquals(16, actual.atZone(java.time.ZoneOffset.UTC).getHour()); |
| 1420 | + assertEquals(52, actual.atZone(java.time.ZoneOffset.UTC).getMinute()); |
| 1421 | + assertEquals(7, actual.atZone(java.time.ZoneOffset.UTC).getSecond()); |
| 1422 | + assertEquals(123456789, actual.getNano()); |
| 1423 | + } |
| 1424 | + |
| 1425 | + @Test |
| 1426 | + @SuppressWarnings("JavaInstantGetSecondsGetNano") |
| 1427 | + public void testToBeamRow_timestampMicros_utcSuffix() { |
| 1428 | + Schema schema = Schema.builder().addLogicalTypeField("ts", Timestamp.MICROS).build(); |
| 1429 | + |
| 1430 | + // BigQuery format with " UTC" suffix |
| 1431 | + String timestamp = "2024-08-10 16:52:07.123456 UTC"; |
| 1432 | + |
| 1433 | + Row beamRow = BigQueryUtils.toBeamRow(schema, new TableRow().set("ts", timestamp)); |
| 1434 | + |
| 1435 | + java.time.Instant actual = (java.time.Instant) beamRow.getValue("ts"); |
| 1436 | + assertEquals(2024, actual.atZone(java.time.ZoneOffset.UTC).getYear()); |
| 1437 | + assertEquals(8, actual.atZone(java.time.ZoneOffset.UTC).getMonthValue()); |
| 1438 | + assertEquals(10, actual.atZone(java.time.ZoneOffset.UTC).getDayOfMonth()); |
| 1439 | + assertEquals(16, actual.atZone(java.time.ZoneOffset.UTC).getHour()); |
| 1440 | + assertEquals(52, actual.atZone(java.time.ZoneOffset.UTC).getMinute()); |
| 1441 | + assertEquals(7, actual.atZone(java.time.ZoneOffset.UTC).getSecond()); |
| 1442 | + assertEquals(123456000, actual.getNano()); |
| 1443 | + } |
| 1444 | + |
| 1445 | + @Test |
| 1446 | + @SuppressWarnings("JavaInstantGetSecondsGetNano") |
| 1447 | + public void testToBeamRow_timestampNanos_variablePrecision() { |
| 1448 | + // Test that different decimal place counts are handled |
| 1449 | + Schema schema = Schema.builder().addLogicalTypeField("ts", Timestamp.NANOS).build(); |
| 1450 | + |
| 1451 | + // 3 decimal places |
| 1452 | + Row row3 = |
| 1453 | + BigQueryUtils.toBeamRow(schema, new TableRow().set("ts", "2024-08-10 16:52:07.123 UTC")); |
| 1454 | + assertEquals(123000000, ((java.time.Instant) row3.getValue("ts")).getNano()); |
| 1455 | + |
| 1456 | + // 6 decimal places |
| 1457 | + Row row6 = |
| 1458 | + BigQueryUtils.toBeamRow(schema, new TableRow().set("ts", "2024-08-10 16:52:07.123456 UTC")); |
| 1459 | + assertEquals(123456000, ((java.time.Instant) row6.getValue("ts")).getNano()); |
| 1460 | + |
| 1461 | + // 9 decimal places |
| 1462 | + Row row9 = |
| 1463 | + BigQueryUtils.toBeamRow( |
| 1464 | + schema, new TableRow().set("ts", "2024-08-10 16:52:07.123456789 UTC")); |
| 1465 | + assertEquals(123456789, ((java.time.Instant) row9.getValue("ts")).getNano()); |
| 1466 | + } |
1297 | 1467 | } |
0 commit comments