diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Field.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Field.java index 1213bc4e8..20c5acd3f 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Field.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Field.java @@ -59,6 +59,9 @@ public TableFieldSchema apply(Field field) { private final String mode; private final String description; private final PolicyTags policyTags; + private final Long maxLength; + private final Long scale; + private final Long precision; /** * Mode for a BigQuery Table field. {@link Mode#NULLABLE} fields can be set to {@code null}, @@ -79,6 +82,9 @@ public static final class Builder { private String mode; private String description; private PolicyTags policyTags; + private Long maxLength; + private Long scale; + private Long precision; private Builder() {} @@ -89,6 +95,9 @@ private Builder(Field field) { this.mode = field.mode; this.description = field.description; this.policyTags = field.policyTags; + this.maxLength = field.maxLength; + this.scale = field.scale; + this.precision = field.precision; } /** @@ -199,6 +208,43 @@ public Builder setPolicyTags(PolicyTags policyTags) { return this; } + /** + * Sets the maximum length of the field for STRING or BYTES type. + * + *

It is invalid to set value for types other than STRING or BYTES. + * + *

For STRING type, this represents the maximum UTF-8 length of strings allowed in the field. + * For BYTES type, this represents the maximum number of bytes in the field. + */ + public Builder setMaxLength(Long maxLength) { + this.maxLength = maxLength; + return this; + } + + /** + * Scale can be used to constrain the maximum number of digits in the fractional part of a + * NUMERIC or BIGNUMERIC type. If the Scale value is set, the Precision value must be set as + * well. It is invalid to set values for Scale for types other than NUMERIC or BIGNUMERIC. See + * the Precision field for additional guidance about valid values. + */ + public Builder setScale(Long scale) { + this.scale = scale; + return this; + } + + /** + * Precision can be used to constrain the maximum number of total digits allowed for NUMERIC or + * BIGNUMERIC types. It is invalid to set values for Precision for types other than // NUMERIC + * or BIGNUMERIC. For NUMERIC type, acceptable values for Precision must be: 1 ≤ (Precision - + * Scale) ≤ 29. Values for Scale must be: 0 ≤ Scale ≤ 9. For BIGNUMERIC type, acceptable values + * for Precision must be: 1 ≤ (Precision - Scale) ≤ 38. Values for Scale must be: 0 ≤ Scale ≤ + * 38. + */ + public Builder setPrecision(Long precision) { + this.precision = precision; + return this; + } + /** Creates a {@code Field} object. */ public Field build() { return new Field(this); @@ -212,6 +258,9 @@ private Field(Builder builder) { this.mode = builder.mode; this.description = builder.description; this.policyTags = builder.policyTags; + this.maxLength = builder.maxLength; + this.scale = builder.scale; + this.precision = builder.precision; } /** Returns the field name. */ @@ -244,6 +293,24 @@ public PolicyTags getPolicyTags() { return policyTags; } + /** Returns the maximum length of the field for STRING or BYTES type. */ + public Long getMaxLength() { + return maxLength; + } + + /** + * Returns the maximum number of digits set in the fractional part of a NUMERIC or BIGNUMERIC + * type. + */ + public Long getScale() { + return scale; + } + + /** Returns the maximum number of total digits allowed for NUMERIC or BIGNUMERIC types. */ + public Long getPrecision() { + return precision; + } + /** * Returns the list of sub-fields if {@link #getType()} is a {@link LegacySQLTypeName#RECORD}. * Returns {@code null} otherwise. @@ -265,6 +332,9 @@ public String toString() { .add("mode", mode) .add("description", description) .add("policyTags", policyTags) + .add("maxLength", maxLength) + .add("scale", scale) + .add("precision", precision) .toString(); } @@ -335,6 +405,15 @@ TableFieldSchema toPb() { if (policyTags != null) { fieldSchemaPb.setPolicyTags(policyTags.toPb()); } + if (maxLength != null) { + fieldSchemaPb.setMaxLength(maxLength); + } + if (scale != null) { + fieldSchemaPb.setScale(scale); + } + if (precision != null) { + fieldSchemaPb.setPrecision(precision); + } if (getSubFields() != null) { List fieldsPb = Lists.transform(getSubFields(), TO_PB_FUNCTION); fieldSchemaPb.setFields(fieldsPb); @@ -354,6 +433,15 @@ static Field fromPb(TableFieldSchema fieldSchemaPb) { if (fieldSchemaPb.getPolicyTags() != null) { fieldBuilder.setPolicyTags(PolicyTags.fromPb(fieldSchemaPb.getPolicyTags())); } + if (fieldSchemaPb.getMaxLength() != null) { + fieldBuilder.setMaxLength(fieldSchemaPb.getMaxLength()); + } + if (fieldSchemaPb.getScale() != null) { + fieldBuilder.setScale(fieldSchemaPb.getScale()); + } + if (fieldSchemaPb.getPrecision() != null) { + fieldBuilder.setPrecision(fieldSchemaPb.getPrecision()); + } FieldList subFields = fieldSchemaPb.getFields() != null ? FieldList.of(Lists.transform(fieldSchemaPb.getFields(), FROM_PB_FUNCTION)) diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/SchemaTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/SchemaTest.java index 63c6752d7..7f53680e6 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/SchemaTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/SchemaTest.java @@ -32,6 +32,9 @@ public class SchemaTest { Field.newBuilder("StringField", LegacySQLTypeName.STRING) .setMode(Field.Mode.NULLABLE) .setDescription("FieldDescription1") + .setPrecision(20L) + .setScale(20L) + .setMaxLength(10L) .build(); private static final Field FIELD_SCHEMA2 = Field.newBuilder("IntegerField", LegacySQLTypeName.INTEGER) diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java index e072cf0f7..4d7b30bef 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java @@ -90,6 +90,7 @@ import com.google.cloud.bigquery.StandardSQLDataType; import com.google.cloud.bigquery.StandardSQLField; import com.google.cloud.bigquery.StandardSQLTableType; +import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.StandardTableDefinition; import com.google.cloud.bigquery.Table; import com.google.cloud.bigquery.TableDataWriteChannel; @@ -703,6 +704,50 @@ public void testCreateTableWithRangePartitioning() { } } + @Test + public void testCreateTableWithConstraints() { + String tableName = "test_create_table_with_constraints"; + TableId tableId = TableId.of(DATASET, tableName); + Field stringFieldWithConstraint = + Field.newBuilder("stringFieldWithConstraint", StandardSQLTypeName.STRING) + .setMode(Field.Mode.NULLABLE) + .setDescription("field has a constraint") + .setMaxLength(10L) + .build(); + Field byteFieldWithConstraint = + Field.newBuilder("byteFieldWithConstraint", StandardSQLTypeName.BYTES) + .setMode(Field.Mode.NULLABLE) + .setDescription("field has a constraint") + .setMaxLength(150L) + .build(); + Field numericFieldWithConstraint = + Field.newBuilder("numericFieldWithConstraint", StandardSQLTypeName.NUMERIC) + .setMode(Field.Mode.NULLABLE) + .setDescription("field has a constraint") + .setPrecision(20L) + .build(); + Field bigNumericFieldWithConstraint = + Field.newBuilder("bigNumericFieldWithConstraint", StandardSQLTypeName.BIGNUMERIC) + .setMode(Field.Mode.NULLABLE) + .setDescription("field has a constraint") + .setPrecision(30L) + .setScale(5L) + .build(); + Schema schema = + Schema.of( + stringFieldWithConstraint, + byteFieldWithConstraint, + numericFieldWithConstraint, + bigNumericFieldWithConstraint); + StandardTableDefinition tableDefinition = + StandardTableDefinition.newBuilder().setSchema(schema).build(); + Table createdTable = bigquery.create(TableInfo.of(tableId, tableDefinition)); + assertNotNull(createdTable); + Table remoteTable = bigquery.getTable(DATASET, tableName); + assertEquals(schema, remoteTable.getDefinition().getSchema()); + bigquery.delete(tableId); + } + @Test public void testCreateAndUpdateTableWithPolicyTags() throws IOException { // Set up policy tags in the datacatalog service