Skip to content

feat: Add support for new Firestore types #2097

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.firestore;

import com.google.firestore.v1.MapValue;
import com.google.protobuf.ByteString;
import java.io.Serializable;
import java.util.Objects;
import javax.annotation.Nonnull;

/** Represents a BSON Binary Data type in Firestore documents. */
public class BsonBinaryData implements Serializable {
private static final long serialVersionUID = 1830984831902814656L;
private final int subtype;
@Nonnull private final ByteString data;

private BsonBinaryData(int subtype, @Nonnull ByteString data) {
// By definition the subtype should be 1 byte and should therefore
// have a value between 0 and 255
if (subtype < 0 || subtype > 255) {
throw new IllegalArgumentException(
"The subtype for BsonBinaryData must be a value in the inclusive [0, 255] range.");
}
this.subtype = subtype;
this.data = data;
}

/**
* Creates a new BsonBinaryData instance from the provided ByteString and subtype.
*
* @param subtype The subtype to use for this instance.
* @param byteString The byteString to use for this instance.
* @return The new BsonBinaryData instance
*/
@Nonnull
public static BsonBinaryData fromByteString(int subtype, @Nonnull ByteString byteString) {
return new BsonBinaryData(subtype, byteString);
}

/**
* Creates a new BsonBinaryData instance from the provided bytes and subtype. Makes a copy of the
* bytes passed in.
*
* @param subtype The subtype to use for this instance.
* @param bytes The bytes to use for this instance.
* @return The new BsonBinaryData instance
*/
@Nonnull
public static BsonBinaryData fromBytes(int subtype, @Nonnull byte[] bytes) {
return new BsonBinaryData(subtype, ByteString.copyFrom(bytes));
}

/**
* Returns the underlying data as a ByteString.
*
* @return The data as a ByteString.
*/
@Nonnull
public ByteString dataAsByteString() {
return data;
}

/**
* Returns a copy of the underlying data as a byte[] array.
*
* @return The data as a byte[] array.
*/
@Nonnull
public byte[] dataAsBytes() {
return data.toByteArray();
}

/**
* Returns the subtype of this binary data.
*
* @return The subtype of the binary data.
*/
public int subtype() {
return this.subtype;
}

/**
* Returns true if this BsonBinaryData is equal to the provided object.
*
* @param obj The object to compare against.
* @return Whether this BsonBinaryData is equal to the provided object.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
BsonBinaryData other = (BsonBinaryData) obj;
return this.subtype == other.subtype && Objects.equals(this.data, other.data);
}

@Override
public int hashCode() {
return Objects.hash(this.subtype, this.data);
}

@Nonnull
@Override
public String toString() {
return "BsonBinaryData{subtype=" + this.subtype + ", data=" + this.data.toString() + "}";
}

MapValue toProto() {
return UserDataConverter.encodeBsonBinaryData(subtype, data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.firestore;

import com.google.firestore.v1.MapValue;
import java.io.Serializable;
import java.util.Objects;
import javax.annotation.Nonnull;

/** Represents a BSON ObjectId type in Firestore documents. */
public class BsonObjectId implements Serializable {
private static final long serialVersionUID = 430753173775328933L;
@Nonnull public final String value;

/**
* Constructor that creates a new BSON ObjectId value with the given value.
*
* @param oid The 24-character hex string representing the ObjectId.
*/
public BsonObjectId(@Nonnull String oid) {
this.value = oid;
}

MapValue toProto() {
return UserDataConverter.encodeBsonObjectId(value);
}

/**
* Returns true if this BsonObjectId is equal to the provided object.
*
* @param obj The object to compare against.
* @return Whether this BsonObjectId is equal to the provided object.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
BsonObjectId other = (BsonObjectId) obj;
return Objects.equals(this.value, other.value);
}

@Override
public int hashCode() {
return Objects.hash(this.value);
}

@Nonnull
@Override
public String toString() {
return "BsonObjectId{value=" + this.value + "}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.firestore;

import com.google.firestore.v1.MapValue;
import java.io.Serializable;
import java.util.Objects;
import javax.annotation.Nonnull;

/** Represents a BSON Timestamp type in Firestore documents. */
public class BsonTimestamp implements Serializable {
private static final long serialVersionUID = -1693962317170687337L;
public final long seconds;
public final long increment;

/**
* Constructor that creates a new BSON Timestamp value with the given values.
*
* @param seconds An unsigned 32-bit integer value stored as long representing the seconds.
* @param increment An unsigned 32-bit integer value stored as long representing the increment.
*/
public BsonTimestamp(long seconds, long increment) {
if (seconds < 0 || seconds > 4294967295L) {
throw new IllegalArgumentException(
"BsonTimestamp 'seconds' must be in the range of a 32-bit unsigned integer.");
}
if (increment < 0 || increment > 4294967295L) {
throw new IllegalArgumentException(
"BsonTimestamp 'increment' must be in the range of a 32-bit unsigned integer.");
}
this.seconds = seconds;
this.increment = increment;
}

MapValue toProto() {
return UserDataConverter.encodeBsonTimestamp(seconds, increment);
}

/**
* Returns true if this BsonTimestamp is equal to the provided object.
*
* @param obj The object to compare against.
* @return Whether this BsonTimestamp is equal to the provided object.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
BsonTimestamp other = (BsonTimestamp) obj;
return this.seconds == other.seconds && this.increment == other.increment;
}

@Override
public int hashCode() {
return Objects.hash(this.seconds, this.increment);
}

@Nonnull
@Override
public String toString() {
return "BsonTimestamp{seconds=" + this.seconds + ", increment=" + this.increment + "}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.firestore;

import com.google.firestore.v1.MapValue;
import java.io.Serializable;
import java.util.Objects;
import javax.annotation.Nonnull;

/** Represents a 128-bit decimal type in Firestore documents. */
public class Decimal128Value implements Serializable {
private static final long serialVersionUID = 8091951856970036899L;

public final String stringValue;
final Quadruple value;

public Decimal128Value(String val) {
this.stringValue = val;
this.value = Quadruple.fromString(val);
}

MapValue toProto() {
return UserDataConverter.encodeDecimal128Value(stringValue);
}

/**
* Returns true if this Decimal128Value is equal to the provided object.
*
* @param obj The object to compare against.
* @return Whether this Decimal128Value is equal to the provided object.
*/
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}

Quadruple lhs = this.value;
Quadruple rhs = ((Decimal128Value) obj).value;

// Firestore considers +0 and -0 to be equal, but `Quadruple.compareTo()` does not.
if (lhs.isZero() && rhs.isZero()) return true;

return this == obj || lhs.compareTo(rhs) == 0;
}

@Override
public int hashCode() {
// Since +0 and -0 are considered equal, they should have the same hash code.
Quadruple quadruple =
(this.value.compareTo(Quadruple.NEGATIVE_ZERO) == 0) ? Quadruple.POSITIVE_ZERO : this.value;

return Objects.hash(quadruple);
}

@Nonnull
@Override
public String toString() {
return "Decimal128Value{value=" + this.stringValue + "}";
}
}
Loading
Loading