Skip to content

Commit 3d8109c

Browse files
Port IndexValueWriter (#5826)
1 parent 4c042b2 commit 3d8109c

File tree

5 files changed

+392
-0
lines changed

5 files changed

+392
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { ByteString } from '../util/byte_string';
19+
20+
/** An index value encoder. */
21+
export interface DirectionalIndexByteEncoder {
22+
// Note: This code is copied from the backend. Code that is not used by
23+
// Firestore was removed.
24+
writeBytes(value: ByteString): void;
25+
writeString(value: string): void;
26+
writeNumber(value: number): void;
27+
writeInfinity(): void;
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { DocumentKey } from '../model/document_key';
19+
import { normalizeByteString, normalizeNumber } from '../model/normalize';
20+
import { isMaxValue } from '../model/values';
21+
import { ArrayValue, MapValue, Value } from '../protos/firestore_proto_api';
22+
import { fail } from '../util/assert';
23+
import { isNegativeZero } from '../util/types';
24+
25+
import { DirectionalIndexByteEncoder } from './directional_index_byte_encoder';
26+
27+
// Note: This code is copied from the backend. Code that is not used by
28+
// Firestore was removed.
29+
30+
const INDEX_TYPE_NULL = 5;
31+
const INDEX_TYPE_BOOLEAN = 10;
32+
const INDEX_TYPE_NAN = 13;
33+
const INDEX_TYPE_NUMBER = 15;
34+
const INDEX_TYPE_TIMESTAMP = 20;
35+
const INDEX_TYPE_STRING = 25;
36+
const INDEX_TYPE_BLOB = 30;
37+
const INDEX_TYPE_REFERENCE = 37;
38+
const INDEX_TYPE_GEOPOINT = 45;
39+
const INDEX_TYPE_ARRAY = 50;
40+
const INDEX_TYPE_MAP = 55;
41+
const INDEX_TYPE_REFERENCE_SEGMENT = 60;
42+
43+
// A terminator that indicates that a truncatable value was not truncated.
44+
// This must be smaller than all other type labels.
45+
const NOT_TRUNCATED = 2;
46+
47+
/** Firestore index value writer. */
48+
export class FirestoreIndexValueWriter {
49+
static INSTANCE = new FirestoreIndexValueWriter();
50+
51+
private constructor() {}
52+
53+
// The write methods below short-circuit writing terminators for values
54+
// containing a (terminating) truncated value.
55+
//
56+
// As an example, consider the resulting encoding for:
57+
//
58+
// ["bar", [2, "foo"]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TERM, TERM, TERM)
59+
// ["bar", [2, truncated("foo")]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TRUNC)
60+
// ["bar", truncated(["foo"])] -> (STRING, "bar", TERM, ARRAY. STRING, "foo", TERM, TRUNC)
61+
62+
/** Writes an index value. */
63+
writeIndexValue(value: Value, encoder: DirectionalIndexByteEncoder): void {
64+
this.writeIndexValueAux(value, encoder);
65+
// Write separator to split index values
66+
// (see go/firestore-storage-format#encodings).
67+
encoder.writeInfinity();
68+
}
69+
70+
private writeIndexValueAux(
71+
indexValue: Value,
72+
encoder: DirectionalIndexByteEncoder
73+
): void {
74+
if ('nullValue' in indexValue) {
75+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NULL);
76+
} else if ('booleanValue' in indexValue) {
77+
this.writeValueTypeLabel(encoder, INDEX_TYPE_BOOLEAN);
78+
encoder.writeNumber(indexValue.booleanValue ? 1 : 0);
79+
} else if ('integerValue' in indexValue) {
80+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
81+
encoder.writeNumber(normalizeNumber(indexValue.integerValue));
82+
} else if ('doubleValue' in indexValue) {
83+
const n = normalizeNumber(indexValue.doubleValue);
84+
if (isNaN(n)) {
85+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NAN);
86+
} else {
87+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
88+
if (isNegativeZero(n)) {
89+
// -0.0, 0 and 0.0 are all considered the same
90+
encoder.writeNumber(0.0);
91+
} else {
92+
encoder.writeNumber(n);
93+
}
94+
}
95+
} else if ('timestampValue' in indexValue) {
96+
const timestamp = indexValue.timestampValue!;
97+
this.writeValueTypeLabel(encoder, INDEX_TYPE_TIMESTAMP);
98+
if (typeof timestamp === 'string') {
99+
encoder.writeString(timestamp);
100+
} else {
101+
encoder.writeString(`${timestamp.seconds || ''}`);
102+
encoder.writeNumber(timestamp.nanos || 0);
103+
}
104+
} else if ('stringValue' in indexValue) {
105+
this.writeIndexString(indexValue.stringValue!, encoder);
106+
this.writeTruncationMarker(encoder);
107+
} else if ('bytesValue' in indexValue) {
108+
this.writeValueTypeLabel(encoder, INDEX_TYPE_BLOB);
109+
encoder.writeBytes(normalizeByteString(indexValue.bytesValue!));
110+
this.writeTruncationMarker(encoder);
111+
} else if ('referenceValue' in indexValue) {
112+
this.writeIndexEntityRef(indexValue.referenceValue!, encoder);
113+
} else if ('geoPointValue' in indexValue) {
114+
const geoPoint = indexValue.geoPointValue!;
115+
this.writeValueTypeLabel(encoder, INDEX_TYPE_GEOPOINT);
116+
encoder.writeNumber(geoPoint.latitude || 0);
117+
encoder.writeNumber(geoPoint.longitude || 0);
118+
} else if ('mapValue' in indexValue) {
119+
if (isMaxValue(indexValue)) {
120+
this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER);
121+
} else {
122+
this.writeIndexMap(indexValue.mapValue!, encoder);
123+
this.writeTruncationMarker(encoder);
124+
}
125+
} else if ('arrayValue' in indexValue) {
126+
this.writeIndexArray(indexValue.arrayValue!, encoder);
127+
this.writeTruncationMarker(encoder);
128+
} else {
129+
fail('unknown index value type ' + indexValue);
130+
}
131+
}
132+
133+
private writeIndexString(
134+
stringIndexValue: string,
135+
encoder: DirectionalIndexByteEncoder
136+
): void {
137+
this.writeValueTypeLabel(encoder, INDEX_TYPE_STRING);
138+
this.writeUnlabeledIndexString(stringIndexValue, encoder);
139+
}
140+
141+
private writeUnlabeledIndexString(
142+
stringIndexValue: string,
143+
encoder: DirectionalIndexByteEncoder
144+
): void {
145+
encoder.writeString(stringIndexValue);
146+
}
147+
148+
private writeIndexMap(
149+
mapIndexValue: MapValue,
150+
encoder: DirectionalIndexByteEncoder
151+
): void {
152+
const map = mapIndexValue.fields || {};
153+
this.writeValueTypeLabel(encoder, INDEX_TYPE_MAP);
154+
for (const key of Object.keys(map)) {
155+
this.writeIndexString(key, encoder);
156+
this.writeIndexValueAux(map[key], encoder);
157+
}
158+
}
159+
160+
private writeIndexArray(
161+
arrayIndexValue: ArrayValue,
162+
encoder: DirectionalIndexByteEncoder
163+
): void {
164+
const values = arrayIndexValue.values || [];
165+
this.writeValueTypeLabel(encoder, INDEX_TYPE_ARRAY);
166+
for (const element of values) {
167+
this.writeIndexValueAux(element, encoder);
168+
}
169+
}
170+
171+
private writeIndexEntityRef(
172+
referenceValue: string,
173+
encoder: DirectionalIndexByteEncoder
174+
): void {
175+
this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE);
176+
const path = DocumentKey.fromName(referenceValue).path;
177+
path.forEach(segment => {
178+
this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE_SEGMENT);
179+
this.writeUnlabeledIndexString(segment, encoder);
180+
});
181+
}
182+
183+
private writeValueTypeLabel(
184+
encoder: DirectionalIndexByteEncoder,
185+
typeOrder: number
186+
): void {
187+
encoder.writeNumber(typeOrder);
188+
}
189+
190+
private writeTruncationMarker(encoder: DirectionalIndexByteEncoder): void {
191+
// While the SDK does not implement truncation, the truncation marker is
192+
// used to terminate all variable length values (which are strings, bytes,
193+
// references, arrays and maps).
194+
encoder.writeNumber(NOT_TRUNCATED);
195+
}
196+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { ByteString } from '../util/byte_string';
18+
19+
import { DirectionalIndexByteEncoder } from './directional_index_byte_encoder';
20+
import { OrderedCodeWriter } from './ordered_code_writer';
21+
22+
class AscendingIndexByteEncoder implements DirectionalIndexByteEncoder {
23+
constructor(private orderedCode: OrderedCodeWriter) {}
24+
writeBytes(value: ByteString): void {
25+
this.orderedCode.writeBytesAscending(value);
26+
}
27+
28+
writeString(value: string): void {
29+
this.orderedCode.writeUtf8Ascending(value);
30+
}
31+
32+
writeNumber(value: number): void {
33+
this.orderedCode.writeNumberAscending(value);
34+
}
35+
36+
writeInfinity(): void {
37+
this.orderedCode.writeInfinityAscending();
38+
}
39+
}
40+
41+
class DescendingIndexByteEncoder implements DirectionalIndexByteEncoder {
42+
constructor(private orderedCode: OrderedCodeWriter) {}
43+
writeBytes(value: ByteString): void {
44+
this.orderedCode.writeBytesDescending(value);
45+
}
46+
47+
writeString(value: string): void {
48+
this.orderedCode.writeUtf8Descending(value);
49+
}
50+
51+
writeNumber(value: number): void {
52+
this.orderedCode.writeNumberDescending(value);
53+
}
54+
55+
writeInfinity(): void {
56+
this.orderedCode.writeInfinityDescending();
57+
}
58+
}
59+
/**
60+
* Implements `DirectionalIndexByteEncoder` using `OrderedCodeWriter` for the
61+
* actual encoding.
62+
*/
63+
export class IndexByteEncoder {
64+
private orderedCode = new OrderedCodeWriter();
65+
private ascending = new AscendingIndexByteEncoder(this.orderedCode);
66+
private descending = new DescendingIndexByteEncoder(this.orderedCode);
67+
68+
seed(encodedBytes: Uint8Array): void {
69+
this.orderedCode.seed(encodedBytes);
70+
}
71+
72+
forKind(kind: 'asc' | 'desc'): DirectionalIndexByteEncoder {
73+
return kind === 'asc' ? this.ascending : this.descending;
74+
}
75+
76+
encodedBytes(): Uint8Array {
77+
return this.orderedCode.encodedBytes();
78+
}
79+
80+
reset(): void {
81+
this.orderedCode.reset();
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law | agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES | CONDITIONS OF ANY KIND, either express | implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { fail } from '../util/assert';
18+
import { ByteString } from '../util/byte_string';
19+
20+
export class OrderedCodeWriter {
21+
writeBytesAscending(value: ByteString): void {
22+
fail('Not implemented');
23+
}
24+
25+
writeBytesDescending(value: ByteString): void {
26+
fail('Not implemented');
27+
}
28+
29+
writeUtf8Ascending(sequence: string): void {
30+
fail('Not implemented');
31+
}
32+
33+
writeUtf8Descending(sequence: string): void {
34+
fail('Not implemented');
35+
}
36+
37+
writeNumberAscending(val: number): void {
38+
fail('Not implemented');
39+
}
40+
41+
writeNumberDescending(val: number): void {
42+
fail('Not implemented');
43+
}
44+
45+
writeInfinityAscending(): void {
46+
fail('Not implemented');
47+
}
48+
49+
writeInfinityDescending(): void {
50+
fail('Not implemented');
51+
}
52+
53+
reset(): void {
54+
fail('Not implemented');
55+
}
56+
57+
encodedBytes(): Uint8Array {
58+
fail('Not implemented');
59+
}
60+
61+
seed(encodedBytes: Uint8Array): void {
62+
fail('Not implemented');
63+
}
64+
}

0 commit comments

Comments
 (0)