Skip to content

Commit ded7eda

Browse files
Add Index Encoding code from backend (#2883)
1 parent 14935e9 commit ded7eda

File tree

6 files changed

+931
-0
lines changed

6 files changed

+931
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.index;
16+
17+
import com.google.protobuf.ByteString;
18+
19+
/** An index value encoder. */
20+
public abstract class DirectionalIndexByteEncoder {
21+
// Note: This code is copied from the backend. Code that is not used by Firestore was removed.
22+
23+
public abstract void writeBytes(ByteString val);
24+
25+
public abstract void writeString(String val);
26+
27+
public abstract void writeLong(long val);
28+
29+
public abstract void writeDouble(double val);
30+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.index;
16+
17+
import com.google.firebase.firestore.model.ResourcePath;
18+
import com.google.firestore.v1.ArrayValue;
19+
import com.google.firestore.v1.MapValue;
20+
import com.google.firestore.v1.Value;
21+
import com.google.protobuf.Timestamp;
22+
import com.google.type.LatLng;
23+
import java.util.Map;
24+
25+
/**
26+
* Firestore index value writer.
27+
*
28+
* <p>See the <a
29+
* href="https://g3doc.corp.google.com/cloud/datastore/g3doc/architecture/spanner/storage_format.md">storage
30+
* format design</a> for details.
31+
*/
32+
public class FirestoreIndexValueWriter {
33+
// Note: This code is copied from the backend. Code that is not used by Firestore was removed.
34+
35+
public static final int INDEX_TYPE_NULL = 5;
36+
public static final int INDEX_TYPE_BOOLEAN = 10;
37+
public static final int INDEX_TYPE_NAN = 13;
38+
public static final int INDEX_TYPE_NUMBER = 15;
39+
public static final int INDEX_TYPE_TIMESTAMP = 10;
40+
public static final int INDEX_TYPE_STRING = 25;
41+
public static final int INDEX_TYPE_BLOB = 30;
42+
public static final int INDEX_TYPE_REFERENCE = 37;
43+
public static final int INDEX_TYPE_GEOPOINT = 45;
44+
public static final int INDEX_TYPE_ARRAY = 50;
45+
public static final int INDEX_TYPE_MAP = 55;
46+
public static final int INDEX_TYPE_REFERENCE_SEGMENT = 60;
47+
48+
public static final FirestoreIndexValueWriter INSTANCE = new FirestoreIndexValueWriter();
49+
50+
private FirestoreIndexValueWriter() {}
51+
52+
// The write methods below short-circuit writing terminators for values containing a (terminating)
53+
// truncated value.
54+
//
55+
// As an example, consider the resulting encoding for:
56+
//
57+
// ["bar", [2, "foo"]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TERM, TERM, TERM)
58+
// ["bar", [2, truncated("foo")]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TRUNC)
59+
// ["bar", truncated(["foo"])] -> (STRING, "bar", TERM, ARRAY. STRING, "foo", TERM, TRUNC)
60+
61+
/** Writes an index value. */
62+
public void writeIndexValue(Value value, DirectionalIndexByteEncoder encoder) {
63+
writeIndexValueAux(value, encoder);
64+
}
65+
66+
private void writeIndexValueAux(Value indexValue, DirectionalIndexByteEncoder encoder) {
67+
switch (indexValue.getValueTypeCase()) {
68+
case NULL_VALUE:
69+
writeValueTypeLabel(encoder, INDEX_TYPE_NULL);
70+
break;
71+
case BOOLEAN_VALUE:
72+
writeValueTypeLabel(encoder, INDEX_TYPE_BOOLEAN);
73+
encoder.writeLong(indexValue.getBooleanValue() ? 1 : 0);
74+
break;
75+
case DOUBLE_VALUE:
76+
double number = indexValue.getDoubleValue();
77+
if (Double.isNaN(number)) {
78+
writeValueTypeLabel(encoder, INDEX_TYPE_NAN);
79+
break;
80+
}
81+
writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
82+
encoder.writeDouble(number);
83+
break;
84+
case INTEGER_VALUE:
85+
writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
86+
// Double and long sort the same
87+
encoder.writeDouble(indexValue.getIntegerValue());
88+
break;
89+
case TIMESTAMP_VALUE:
90+
Timestamp timestamp = indexValue.getTimestampValue();
91+
writeValueTypeLabel(encoder, INDEX_TYPE_TIMESTAMP);
92+
encoder.writeLong(timestamp.getSeconds());
93+
encoder.writeLong(timestamp.getNanos());
94+
break;
95+
case STRING_VALUE:
96+
writeIndexString(indexValue.getStringValue(), encoder);
97+
98+
break;
99+
case BYTES_VALUE:
100+
writeValueTypeLabel(encoder, INDEX_TYPE_BLOB);
101+
encoder.writeBytes(indexValue.getBytesValue());
102+
break;
103+
case REFERENCE_VALUE:
104+
writeIndexEntityRef(indexValue.getReferenceValue(), encoder);
105+
106+
break;
107+
case GEO_POINT_VALUE:
108+
LatLng geoPoint = indexValue.getGeoPointValue();
109+
writeValueTypeLabel(encoder, INDEX_TYPE_GEOPOINT);
110+
encoder.writeDouble(geoPoint.getLatitude());
111+
encoder.writeDouble(geoPoint.getLongitude());
112+
break;
113+
case MAP_VALUE:
114+
writeIndexMap(indexValue.getMapValue(), encoder);
115+
break;
116+
case ARRAY_VALUE:
117+
writeIndexArray(indexValue.getArrayValue(), encoder);
118+
break;
119+
default:
120+
throw new IllegalArgumentException(
121+
"unknown index value type " + indexValue.getValueTypeCase());
122+
}
123+
}
124+
125+
private void writeIndexString(String stringIndexValue, DirectionalIndexByteEncoder encoder) {
126+
writeValueTypeLabel(encoder, INDEX_TYPE_STRING);
127+
writeUnlabeledIndexString(stringIndexValue, encoder);
128+
}
129+
130+
private void writeUnlabeledIndexString(
131+
String stringIndexValue, DirectionalIndexByteEncoder encoder) {
132+
encoder.writeString(stringIndexValue);
133+
}
134+
135+
private void writeIndexMap(MapValue mapIndexValue, DirectionalIndexByteEncoder encoder) {
136+
writeValueTypeLabel(encoder, INDEX_TYPE_MAP);
137+
for (Map.Entry<String, Value> entry : mapIndexValue.getFieldsMap().entrySet()) {
138+
String key = entry.getKey();
139+
Value value = entry.getValue();
140+
writeIndexString(key, encoder);
141+
writeIndexValueAux(value, encoder);
142+
}
143+
}
144+
145+
private void writeIndexArray(ArrayValue arrayIndexValue, DirectionalIndexByteEncoder encoder) {
146+
writeValueTypeLabel(encoder, INDEX_TYPE_ARRAY);
147+
for (Value element : arrayIndexValue.getValuesList()) {
148+
writeIndexValueAux(element, encoder);
149+
}
150+
}
151+
152+
private void writeIndexEntityRef(String referenceValue, DirectionalIndexByteEncoder encoder) {
153+
writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE);
154+
155+
ResourcePath path = ResourcePath.fromString(referenceValue);
156+
157+
int numSegments = path.length();
158+
for (int index = 6; index < numSegments; ++index) {
159+
String segment = path.getSegment(index);
160+
161+
writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE_SEGMENT);
162+
writeUnlabeledIndexString(segment, encoder);
163+
}
164+
}
165+
166+
private void writeValueTypeLabel(DirectionalIndexByteEncoder encoder, int typeOrder) {
167+
encoder.writeLong(typeOrder);
168+
}
169+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.index;
16+
17+
import static java.lang.Math.abs;
18+
import static java.math.RoundingMode.HALF_EVEN;
19+
import static java.math.RoundingMode.HALF_UP;
20+
21+
import java.math.RoundingMode;
22+
23+
/**
24+
* A class for arithmetic on values of type {@code int}. Where possible, methods are defined and
25+
* named analogously to their {@code BigInteger} counterparts.
26+
*
27+
* <p>The implementations of many methods in this class are based on material from Henry S. Warren,
28+
* Jr.'s <i>Hacker's Delight</i>, (Addison Wesley, 2002).
29+
*
30+
* <p>Similar functionality for {@code long} and for {@link BigInteger} can be found in {@link
31+
* LongMath} and {@link BigIntegerMath} respectively. For other common operations on {@code int}
32+
* values, see {@link com.google.common.primitives.Ints}.
33+
*
34+
* @author Louis Wasserman
35+
* @since 11.0
36+
*/
37+
public final class IntMath {
38+
// Note: This code is copied from the backend. Code that is not used by Firestore was removed.
39+
40+
/**
41+
* Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code
42+
* RoundingMode}.
43+
*
44+
* @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a}
45+
* is not an integer multiple of {@code b}
46+
*/
47+
@SuppressWarnings("fallthrough")
48+
public static int divide(int p, int q, RoundingMode mode) {
49+
if (q == 0) {
50+
throw new ArithmeticException("/ by zero"); // for GWT
51+
}
52+
int div = p / q;
53+
int rem = p - q * div; // equal to p % q
54+
55+
if (rem == 0) {
56+
return div;
57+
}
58+
59+
/*
60+
* Normal Java division rounds towards 0, consistently with RoundingMode.DOWN. We just have to
61+
* deal with the cases where rounding towards 0 is wrong, which typically depends on the sign of
62+
* p / q.
63+
*
64+
* signum is 1 if p and q are both nonnegative or both negative, and -1 otherwise.
65+
*/
66+
int signum = 1 | ((p ^ q) >> (Integer.SIZE - 1));
67+
boolean increment;
68+
switch (mode) {
69+
case UNNECESSARY:
70+
// fall through
71+
case DOWN:
72+
increment = false;
73+
break;
74+
case UP:
75+
increment = true;
76+
break;
77+
case CEILING:
78+
increment = signum > 0;
79+
break;
80+
case FLOOR:
81+
increment = signum < 0;
82+
break;
83+
case HALF_EVEN:
84+
case HALF_DOWN:
85+
case HALF_UP:
86+
int absRem = abs(rem);
87+
int cmpRemToHalfDivisor = absRem - (abs(q) - absRem);
88+
// subtracting two nonnegative ints can't overflow
89+
// cmpRemToHalfDivisor has the same sign as compare(abs(rem), abs(q) / 2).
90+
if (cmpRemToHalfDivisor == 0) { // exactly on the half mark
91+
increment = (mode == HALF_UP || (mode == HALF_EVEN & (div & 1) != 0));
92+
} else {
93+
increment = cmpRemToHalfDivisor > 0; // closer to the UP value
94+
}
95+
break;
96+
default:
97+
throw new AssertionError();
98+
}
99+
return increment ? div + signum : div;
100+
}
101+
102+
private IntMath() {}
103+
}

0 commit comments

Comments
 (0)