Skip to content

Commit

Permalink
feat: add Interval type support (#1844)
Browse files Browse the repository at this point in the history
* feat: add Interval type support

Fixes b/208051516

* 🦉 Updates from OwlBot

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* add threeten-extra PeriodDuration support

* add unit test coverage

Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
  • Loading branch information
stephaniewang526 and gcf-owl-bot[bot] committed Feb 11, 2022
1 parent 7ffe963 commit fd3751a
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 3 deletions.
4 changes: 4 additions & 0 deletions google-cloud-bigquery/pom.xml
Expand Up @@ -85,6 +85,10 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.threeten</groupId>
<artifactId>threeten-extra</artifactId>
</dependency>

<!-- auto-value creates a class that uses an annotation from error_prone_annotations -->
<dependency>
Expand Down
Expand Up @@ -96,6 +96,9 @@ public LegacySQLTypeName apply(String constant) {
/** Represents JSON data */
public static final LegacySQLTypeName JSON =
type.createAndRegister("JSON").setStandardType(StandardSQLTypeName.JSON);
/** Represents duration or amount of time. */
public static final LegacySQLTypeName INTERVAL =
type.createAndRegister("INTERVAL").setStandardType(StandardSQLTypeName.INTERVAL);

private static Map<StandardSQLTypeName, LegacySQLTypeName> standardToLegacyMap = new HashMap<>();

Expand Down
Expand Up @@ -43,6 +43,7 @@
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.DateTimeFormatterBuilder;
import org.threeten.bp.format.DateTimeParseException;
import org.threeten.extra.PeriodDuration;

/**
* A value for a QueryParameter along with its type.
Expand All @@ -63,6 +64,7 @@
* <li>BigDecimal: StandardSQLTypeName.NUMERIC
* <li>BigNumeric: StandardSQLTypeName.BIGNUMERIC
* <li>JSON: StandardSQLTypeName.JSON
* <li>INTERVAL: StandardSQLTypeName.INTERVAL
* </ul>
*
* <p>No other types are supported through that entry point. The other types can be created by
Expand Down Expand Up @@ -308,12 +310,26 @@ public static QueryParameterValue time(String value) {

/**
* Creates a {@code QueryParameterValue} object with a type of DATETIME. Must be in the format
* "yyyy-MM-dd HH:mm:ss.SSSSSS", e.g. ""2014-08-19 12:41:35.220000".
* "yyyy-MM-dd HH:mm:ss.SSSSSS", e.g. "2014-08-19 12:41:35.220000".
*/
public static QueryParameterValue dateTime(String value) {
return of(value, StandardSQLTypeName.DATETIME);
}

/**
* Creates a {@code QueryParameterValue} object with a type of INTERVAL. Must be in the canonical
* format "[sign]Y-M [sign]D [sign]H:M:S[.F]", e.g. "123-7 -19 0:24:12.000006" or ISO 8601
* duration format, e.g. "P123Y7M-19DT0H24M12.000006S"
*/
public static QueryParameterValue interval(String value) {
return of(value, StandardSQLTypeName.INTERVAL);
}

/** Creates a {@code QueryParameterValue} object with a type of INTERVAL. */
public static QueryParameterValue interval(PeriodDuration value) {
return of(value, StandardSQLTypeName.INTERVAL);
}

/**
* Creates a {@code QueryParameterValue} object with a type of ARRAY, and an array element type
* based on the given class.
Expand Down Expand Up @@ -408,6 +424,8 @@ private static <T> String valueToStringOrNull(T value, StandardSQLTypeName type)
return value.toString();
case JSON:
if (value instanceof String || value instanceof JsonObject) return value.toString();
case INTERVAL:
if (value instanceof String || value instanceof PeriodDuration) return value.toString();
break;
case STRUCT:
throw new IllegalArgumentException("Cannot convert STRUCT to String value");
Expand Down
Expand Up @@ -57,6 +57,8 @@ public enum StandardSQLTypeName {
DATETIME,
/** Represents a set of geographic points, represented as a Well Known Text (WKT) string. */
GEOGRAPHY,
/** Represents JSON data */
JSON
/** Represents JSON data. */
JSON,
/** Represents duration or amount of time. */
INTERVAL
}
Expand Up @@ -27,6 +27,7 @@
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.text.ParseException;
import java.time.Period;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
Expand All @@ -38,6 +39,7 @@
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.DateTimeFormatterBuilder;
import org.threeten.bp.jdk8.Jdk8Methods;
import org.threeten.extra.PeriodDuration;

public class QueryParameterValueTest {

Expand Down Expand Up @@ -212,6 +214,24 @@ public void testJson() {
assertThat(value1.getArrayType()).isNull();
}

@Test
public void testInterval() {
QueryParameterValue value = QueryParameterValue.interval("123-7 -19 0:24:12.000006");
QueryParameterValue value1 = QueryParameterValue.interval("P123Y7M-19DT0H24M12.000006S");
QueryParameterValue value2 =
QueryParameterValue.interval(
PeriodDuration.of(Period.of(1, 2, 25), java.time.Duration.ofHours(8)));
assertThat(value.getValue()).isEqualTo("123-7 -19 0:24:12.000006");
assertThat(value1.getValue()).isEqualTo("P123Y7M-19DT0H24M12.000006S");
assertThat(value2.getValue()).isEqualTo("P1Y2M25DT8H");
assertThat(value.getType()).isEqualTo(StandardSQLTypeName.INTERVAL);
assertThat(value1.getType()).isEqualTo(StandardSQLTypeName.INTERVAL);
assertThat(value2.getType()).isEqualTo(StandardSQLTypeName.INTERVAL);
assertThat(value.getArrayType()).isNull();
assertThat(value1.getArrayType()).isNull();
assertThat(value2.getArrayType()).isNull();
}

@Test
public void testBytes() {
QueryParameterValue value = QueryParameterValue.bytes(new byte[] {1, 3});
Expand Down
Expand Up @@ -130,6 +130,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.time.Instant;
import java.time.Period;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -151,6 +152,7 @@
import org.junit.Test;
import org.junit.rules.Timeout;
import org.threeten.bp.Duration;
import org.threeten.extra.PeriodDuration;

public class ITBigQueryTest {

Expand Down Expand Up @@ -816,6 +818,77 @@ public void testJsonType() throws InterruptedException {
}
}

@Test
public void testIntervalType() throws InterruptedException {
String tableName = "test_create_table_intervaltype";
TableId tableId = TableId.of(DATASET, tableName);
Schema schema = Schema.of(Field.of("intervalField", StandardSQLTypeName.INTERVAL));
StandardTableDefinition standardTableDefinition = StandardTableDefinition.of(schema);
try {
// Create a table with a JSON column
Table createdTable = bigquery.create(TableInfo.of(tableId, standardTableDefinition));
assertNotNull(createdTable);

// Insert 3 rows of Interval data into the Interval column
Map<String, Object> intervalRow1 =
Collections.singletonMap("intervalField", "123-7 -19 0:24:12.000006");
Map<String, Object> intervalRow2 =
Collections.singletonMap("intervalField", "P123Y7M-19DT0H24M12.000006S");

InsertAllRequest request =
InsertAllRequest.newBuilder(tableId).addRow(intervalRow1).addRow(intervalRow2).build();
InsertAllResponse response = bigquery.insertAll(request);
assertFalse(response.hasErrors());
assertEquals(0, response.getInsertErrors().size());

// Insert another Interval row parsed from a String with Interval positional query parameter
String dml = "INSERT INTO " + tableId.getTable() + " (intervalField) VALUES(?)";
// Parsing from ISO 8610 format String
QueryParameterValue intervalParameter =
QueryParameterValue.interval("P125Y7M-19DT0H24M12.000006S");
QueryJobConfiguration dmlQueryJobConfiguration =
QueryJobConfiguration.newBuilder(dml)
.setDefaultDataset(DatasetId.of(DATASET))
.setUseLegacySql(false)
.addPositionalParameter(intervalParameter)
.build();
bigquery.query(dmlQueryJobConfiguration);
Page<FieldValueList> rows = bigquery.listTableData(tableId);
assertEquals(3, Iterables.size(rows.getValues()));

// Parsing from threeten-extra PeriodDuration
QueryParameterValue intervalParameter1 =
QueryParameterValue.interval(
PeriodDuration.of(Period.of(1, 2, 25), java.time.Duration.ofHours(8)));
QueryJobConfiguration dmlQueryJobConfiguration1 =
QueryJobConfiguration.newBuilder(dml)
.setDefaultDataset(DatasetId.of(DATASET))
.setUseLegacySql(false)
.addPositionalParameter(intervalParameter1)
.build();
bigquery.query(dmlQueryJobConfiguration1);
Page<FieldValueList> rows1 = bigquery.listTableData(tableId);
assertEquals(4, Iterables.size(rows1.getValues()));

// Query the Interval column with Interval positional query parameter
String sql = "SELECT intervalField FROM " + tableId.getTable() + " WHERE intervalField = ? ";
QueryParameterValue intervalParameter2 =
QueryParameterValue.interval("P125Y7M-19DT0H24M12.000006S");
QueryJobConfiguration queryJobConfiguration =
QueryJobConfiguration.newBuilder(sql)
.setDefaultDataset(DatasetId.of(DATASET))
.setUseLegacySql(false)
.addPositionalParameter(intervalParameter2)
.build();
TableResult result = bigquery.query(queryJobConfiguration);
for (FieldValueList values : result.iterateAll()) {
assertEquals("125-7 -19 0:24:12.000006", values.get(0).getValue());
}
} finally {
assertTrue(bigquery.delete(tableId));
}
}

@Test
public void testCreateTableWithConstraints() {
String tableName = "test_create_table_with_constraints";
Expand Down
8 changes: 8 additions & 0 deletions pom.xml
Expand Up @@ -93,12 +93,20 @@
<version>${google-api-services-bigquery.version}</version>
</dependency>

<!-- Used for JSON type -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>

<!-- Used for Interval and Range types -->
<dependency>
<groupId>org.threeten</groupId>
<artifactId>threeten-extra</artifactId>
<version>1.7.0</version>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
Expand Down

0 comments on commit fd3751a

Please sign in to comment.