Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Interval type support #1844

Merged
merged 5 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ If you are using Maven without BOM, add this to your dependencies:
If you are using Gradle 5.x or later, add this to your dependencies

```Groovy
implementation platform('com.google.cloud:libraries-bom:24.2.0')
implementation platform('com.google.cloud:libraries-bom:24.3.0')

implementation 'com.google.cloud:google-cloud-bigquery'
```
Expand Down
4 changes: 4 additions & 0 deletions google-cloud-bigquery/pom.xml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there be future support to expose conversion to a PeriodDuration, given we accept it on the query parameter inputs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a great suggestion. I created a GH issue to keep track of this feature enhancement: #1849.

}
} 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
Original file line number Diff line number Diff line change
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