Skip to content

Commit be7ccb8

Browse files
Finish OrderedCode (#5821)
1 parent 4f3662b commit be7ccb8

File tree

3 files changed

+122
-35
lines changed

3 files changed

+122
-35
lines changed

packages/firestore/src/index/ordered_code_writer.ts

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17-
import { debugAssert, fail } from '../util/assert';
17+
import { debugAssert } from '../util/assert';
1818
import { ByteString } from '../util/byte_string';
1919

2020
/** These constants are taken from the backend. */
@@ -26,6 +26,7 @@ const NULL_BYTE = 0xff; // Combined with ESCAPE1
2626
const SEPARATOR = 0x01; // Combined with ESCAPE1
2727

2828
const ESCAPE2 = 0xff;
29+
const INFINITY = 0xff; // Combined with ESCAPE2
2930
const FF_BYTE = 0x00; // Combined with ESCAPE2
3031

3132
const LONG_SIZE = 64;
@@ -111,6 +112,26 @@ export class OrderedCodeWriter {
111112
buffer = new Uint8Array(DEFAULT_BUFFER_SIZE);
112113
position = 0;
113114

115+
writeBytesAscending(value: ByteString): void {
116+
const it = value[Symbol.iterator]();
117+
let byte = it.next();
118+
while (!byte.done) {
119+
this.writeByteAscending(byte.value);
120+
byte = it.next();
121+
}
122+
this.writeSeparatorAscending();
123+
}
124+
125+
writeBytesDescending(value: ByteString): void {
126+
const it = value[Symbol.iterator]();
127+
let byte = it.next();
128+
while (!byte.done) {
129+
this.writeByteDescending(byte.value);
130+
byte = it.next();
131+
}
132+
this.writeSeparatorDescending();
133+
}
134+
114135
/** Writes utf8 bytes into this byte sequence, ascending. */
115136
writeUtf8Ascending(sequence: string): void {
116137
for (const c of sequence) {
@@ -183,6 +204,43 @@ export class OrderedCodeWriter {
183204
}
184205
}
185206

207+
/**
208+
* Writes the "infinity" byte sequence that sorts after all other byte
209+
* sequences written in ascending order.
210+
*/
211+
writeInfinityAscending(): void {
212+
this.writeEscapedByteAscending(ESCAPE2);
213+
this.writeEscapedByteAscending(INFINITY);
214+
}
215+
216+
/**
217+
* Writes the "infinity" byte sequence that sorts before all other byte
218+
* sequences written in descending order.
219+
*/
220+
writeInfinityDescending(): void {
221+
this.writeEscapedByteDescending(ESCAPE2);
222+
this.writeEscapedByteDescending(INFINITY);
223+
}
224+
225+
/**
226+
* Resets the buffer such that it is the same as when it was newly
227+
* constructed.
228+
*/
229+
reset(): void {
230+
this.position = 0;
231+
}
232+
233+
seed(encodedBytes: Uint8Array): void {
234+
this.ensureAvailable(encodedBytes.length);
235+
this.buffer.set(encodedBytes, this.position);
236+
this.position += encodedBytes.length;
237+
}
238+
239+
/** Makes a copy of the encoded bytes in this buffer. */
240+
encodedBytes(): Uint8Array {
241+
return this.buffer.slice(0, this.position);
242+
}
243+
186244
/**
187245
* Encodes `val` into an encoding so that the order matches the IEEE 754
188246
* floating-point comparison results with the following exceptions:
@@ -204,16 +262,6 @@ export class OrderedCodeWriter {
204262
return value;
205263
}
206264

207-
/** Resets the buffer such that it is the same as when it was newly constructed. */
208-
reset(): void {
209-
this.position = 0;
210-
}
211-
212-
/** Makes a copy of the encoded bytes in this buffer. */
213-
encodedBytes(): Uint8Array {
214-
return this.buffer.slice(0, this.position);
215-
}
216-
217265
/** Writes a single byte ascending to the buffer. */
218266
private writeByteAscending(b: number): void {
219267
const masked = b & 0xff;
@@ -262,26 +310,6 @@ export class OrderedCodeWriter {
262310
this.buffer[this.position++] = ~b;
263311
}
264312

265-
writeBytesAscending(value: ByteString): void {
266-
fail('Not implemented');
267-
}
268-
269-
writeBytesDescending(value: ByteString): void {
270-
fail('Not implemented');
271-
}
272-
273-
writeInfinityAscending(): void {
274-
fail('Not implemented');
275-
}
276-
277-
writeInfinityDescending(): void {
278-
fail('Not implemented');
279-
}
280-
281-
seed(encodedBytes: Uint8Array): void {
282-
fail('Not implemented');
283-
}
284-
285313
private ensureAvailable(bytes: number): void {
286314
const minCapacity = bytes + this.position;
287315
if (minCapacity <= this.buffer.length) {

packages/firestore/src/util/byte_string.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ export class ByteString {
4343
return new ByteString(binaryString);
4444
}
4545

46+
[Symbol.iterator](): Iterator<number> {
47+
let i = 0;
48+
return {
49+
next: () => {
50+
if (i < this.binaryString.length) {
51+
return { value: this.binaryString.charCodeAt(i++), done: false };
52+
} else {
53+
return { value: undefined, done: true };
54+
}
55+
}
56+
};
57+
}
58+
4659
toBase64(): string {
4760
return encodeBase64(this.binaryString);
4861
}

packages/firestore/test/unit/index/ordered_code_writer.test.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
numberOfLeadingZerosInByte,
2121
OrderedCodeWriter
2222
} from '../../../src/index/ordered_code_writer';
23+
import { hardAssert } from '../../../src/util/assert';
24+
import { ByteString } from '../../../src/util/byte_string';
2325

2426
class ValueTestCase<T> {
2527
constructor(
@@ -108,6 +110,23 @@ const STRING_TEST_CASES: Array<ValueTestCase<string>> = [
108110
)
109111
];
110112

113+
const BYTES_TEST_CASES: Array<ValueTestCase<Uint8Array>> = [
114+
new ValueTestCase(fromHex(''), '0001', 'fffe'),
115+
new ValueTestCase(fromHex('00'), '00ff0001', 'ff00fffe'),
116+
new ValueTestCase(fromHex('0000'), '00ff00ff0001', 'ff00ff00fffe'),
117+
new ValueTestCase(fromHex('0001'), '00ff010001', 'ff00fefffe'),
118+
new ValueTestCase(fromHex('0041'), '00ff410001', 'ff00befffe'),
119+
new ValueTestCase(fromHex('00ff'), '00ffff000001', 'ff0000fffffe'),
120+
new ValueTestCase(fromHex('01'), '010001', 'fefffe'),
121+
new ValueTestCase(fromHex('0100'), '0100ff0001', 'feff00fffe'),
122+
new ValueTestCase(fromHex('6f776c'), '6f776c0001', '908893fffe'),
123+
new ValueTestCase(fromHex('ff'), 'ff000001', '00fffffe'),
124+
new ValueTestCase(fromHex('ff00'), 'ff0000ff0001', '00ffff00fffe'),
125+
new ValueTestCase(fromHex('ff01'), 'ff00010001', '00fffefffe'),
126+
new ValueTestCase(fromHex('ffff'), 'ff00ff000001', '00ff00fffffe'),
127+
new ValueTestCase(fromHex('ffffff'), 'ff00ff00ff000001', '00ff00ff00fffffe')
128+
];
129+
111130
describe('Ordered Code Writer', () => {
112131
it('computes number of leading zeros', () => {
113132
for (let i = 0; i < 0xff; ++i) {
@@ -139,6 +158,32 @@ describe('Ordered Code Writer', () => {
139158
verifyOrdering(STRING_TEST_CASES);
140159
});
141160

161+
it('converts bytes to bits', () => {
162+
verifyEncoding(BYTES_TEST_CASES);
163+
});
164+
165+
it('orders bytes correctly', () => {
166+
verifyOrdering(BYTES_TEST_CASES);
167+
});
168+
169+
it('encodes infinity', () => {
170+
const writer = new OrderedCodeWriter();
171+
writer.writeInfinityAscending();
172+
expect(writer.encodedBytes()).to.deep.equal(fromHex('ffff'));
173+
174+
writer.reset();
175+
writer.writeInfinityDescending();
176+
expect(writer.encodedBytes()).to.deep.equal(fromHex('0000'));
177+
});
178+
179+
it('seeds bytes', () => {
180+
const writer = new OrderedCodeWriter();
181+
writer.seed(fromHex('01'));
182+
writer.writeInfinityAscending();
183+
writer.seed(fromHex('02'));
184+
expect(writer.encodedBytes()).to.deep.equal(fromHex('01ffff02'));
185+
});
186+
142187
function verifyEncoding(testCases: Array<ValueTestCase<unknown>>): void {
143188
for (let i = 0; i < testCases.length; ++i) {
144189
const bytes = getBytes(testCases[i].val);
@@ -159,7 +204,6 @@ describe('Ordered Code Writer', () => {
159204
const left = testCases[i].val;
160205
const leftBytes = getBytes(left);
161206
const right = testCases[j].val;
162-
163207
const rightBytes = getBytes(right);
164208
expect(compare(leftBytes.asc, rightBytes.asc)).to.equal(
165209
i === j ? 0 : -1,
@@ -176,8 +220,8 @@ describe('Ordered Code Writer', () => {
176220

177221
function fromHex(hexString: string): Uint8Array {
178222
const bytes = new Uint8Array(hexString.length / 2);
179-
for (let c = 0; c < hexString.length; c += 2) {
180-
bytes[c / 2] = parseInt(hexString.substr(c, 2), 16);
223+
for (let i = 0; i < hexString.length; i += 2) {
224+
bytes[i / 2] = parseInt(hexString.substr(i, 2), 16);
181225
}
182226
return bytes;
183227
}
@@ -204,7 +248,9 @@ function getBytes(val: unknown): { asc: Uint8Array; desc: Uint8Array } {
204248
ascWriter.writeUtf8Ascending(val);
205249
descWriter.writeUtf8Descending(val);
206250
} else {
207-
throw new Error('Encoding not yet supported for ' + val);
251+
hardAssert(val instanceof Uint8Array);
252+
ascWriter.writeBytesAscending(ByteString.fromUint8Array(val));
253+
descWriter.writeBytesDescending(ByteString.fromUint8Array(val));
208254
}
209255
return { asc: ascWriter.encodedBytes(), desc: descWriter.encodedBytes() };
210256
}

0 commit comments

Comments
 (0)