Skip to content

Commit 946a266

Browse files
committed
add pipeline canonify and eq
1 parent 1738c15 commit 946a266

File tree

6 files changed

+342
-24
lines changed

6 files changed

+342
-24
lines changed

packages/firestore/src/api/pipeline.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,12 @@ export class Pipeline<
144144

145145
return () => {};
146146
}
147+
148+
/**
149+
* @internal
150+
* @private
151+
*/
152+
_stages(): Stage[] {
153+
return this.stages;
154+
}
147155
}

packages/firestore/src/core/pipeline-util.ts

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@ import {
1818
Expr,
1919
Field,
2020
FilterCondition,
21+
FirestoreFunction,
2122
not,
22-
or
23+
or,
24+
Ordering
2325
} from '../lite-api/expressions';
24-
import { isNanValue, isNullValue } from '../model/values';
26+
import {
27+
isNanValue,
28+
isNullValue,
29+
VECTOR_MAP_VECTORS_KEY
30+
} from '../model/values';
2531
import {
2632
ArrayValue as ProtoArrayValue,
2733
Function as ProtoFunction,
@@ -41,6 +47,23 @@ import {
4147
Filter as FilterInternal,
4248
Operator
4349
} from './filter';
50+
import { Pipeline } from '../lite-api/pipeline';
51+
import {
52+
AddFields,
53+
Aggregate,
54+
CollectionGroupSource,
55+
CollectionSource,
56+
DatabaseSource,
57+
Distinct,
58+
DocumentsSource,
59+
FindNearest,
60+
Limit,
61+
Offset,
62+
Select,
63+
Sort,
64+
Stage,
65+
Where
66+
} from '../lite-api/stage';
4467

4568
/* eslint @typescript-eslint/no-explicit-any: 0 */
4669

@@ -247,3 +270,98 @@ export function toPipelineFilterCondition(
247270

248271
throw new Error(`Failed to convert filter to pipeline conditions: ${f}`);
249272
}
273+
274+
function canonifyExpr(expr: Expr): string {
275+
if (expr instanceof Field) {
276+
return `fld(${expr.fieldName()})`;
277+
}
278+
if (expr instanceof Constant) {
279+
return `cst(${expr.value})`;
280+
}
281+
if (expr instanceof FirestoreFunction) {
282+
return `fn(${expr.name},[${expr.params.map(canonifyExpr).join(',')}])`;
283+
}
284+
throw new Error(`Unrecognized expr ${expr}`);
285+
}
286+
287+
function canonifySortOrderings(orders: Ordering[]): string {
288+
return orders.map(o => `${canonifyExpr(o.expr)} ${o.direction}`).join(',');
289+
}
290+
291+
function canonifyStage(stage: Stage): string {
292+
if (stage instanceof AddFields) {
293+
return `${stage.name}(${canonifyExprMap(stage.fields)})`;
294+
}
295+
if (stage instanceof Aggregate) {
296+
let result = `${stage.name}(${canonifyExprMap(
297+
stage.accumulators as unknown as Map<string, Expr>
298+
)})`;
299+
if (stage.groups.size > 0) {
300+
result = result + `grouping(${canonifyExprMap(stage.groups)})`;
301+
}
302+
return result;
303+
}
304+
if (stage instanceof Distinct) {
305+
return `${stage.name}(${canonifyExprMap(stage.groups)})`;
306+
}
307+
if (stage instanceof CollectionSource) {
308+
return `${stage.name}(${stage.collectionPath})`;
309+
}
310+
if (stage instanceof CollectionGroupSource) {
311+
return `${stage.name}(${stage.collectionId})`;
312+
}
313+
if (stage instanceof DatabaseSource) {
314+
return `${stage.name}()`;
315+
}
316+
if (stage instanceof DocumentsSource) {
317+
return `${stage.name}(${stage.docPaths.sort()})`;
318+
}
319+
if (stage instanceof Where) {
320+
return `${stage.name}(${canonifyExpr(stage.condition)})`;
321+
}
322+
if (stage instanceof FindNearest) {
323+
const vector = stage._vectorValue.value.mapValue.fields![
324+
VECTOR_MAP_VECTORS_KEY
325+
].arrayValue?.values?.map(value => value.doubleValue);
326+
let result = `${stage.name}(${canonifyExpr(stage._field)},${
327+
stage._distanceMeasure
328+
},[${vector}]`;
329+
if (!!stage._limit) {
330+
result = result + `,${stage._limit}`;
331+
}
332+
if (!!stage._distanceField) {
333+
result = result + `,${stage._distanceField}`;
334+
}
335+
return result + ')';
336+
}
337+
if (stage instanceof Limit) {
338+
return `${stage.name}(${stage.limit})`;
339+
}
340+
if (stage instanceof Offset) {
341+
return `${stage.name}(${stage.offset})`;
342+
}
343+
if (stage instanceof Select) {
344+
return `${stage.name}(${canonifyExprMap(stage.projections)})`;
345+
}
346+
if (stage instanceof Sort) {
347+
return `${stage.name}(${canonifySortOrderings(stage.orders)})`;
348+
}
349+
350+
throw new Error(`Unrecognized stage ${stage.name}`);
351+
}
352+
353+
function canonifyExprMap(map: Map<string, Expr>): string {
354+
const sortedEntries = Array.from(map.entries()).sort();
355+
return `${sortedEntries
356+
.map(([key, val]) => `${key}=${canonifyExpr(val)}`)
357+
.join(',')}`;
358+
}
359+
360+
export function canonifyPipeline(p: Pipeline): string {
361+
return p.stages.map(s => canonifyStage(s)).join('|');
362+
}
363+
364+
// TODO(pipeline): do a proper implementation for eq.
365+
export function pipelineEq(left: Pipeline, right: Pipeline): boolean {
366+
return canonifyPipeline(left) === canonifyPipeline(right);
367+
}

packages/firestore/src/lite-api/expressions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2009,7 +2009,7 @@ export class Constant extends Expr {
20092009

20102010
private _protoValue?: ProtoValue;
20112011

2012-
private constructor(private value: any) {
2012+
private constructor(readonly value: any) {
20132013
super();
20142014
}
20152015

@@ -2191,7 +2191,7 @@ export class Constant extends Expr {
21912191
*/
21922192
export class FirestoreFunction extends Expr {
21932193
exprType: ExprType = 'Function';
2194-
constructor(private name: string, private params: Expr[]) {
2194+
constructor(readonly name: string, readonly params: Expr[]) {
21952195
super();
21962196
}
21972197

@@ -6706,8 +6706,8 @@ export function descending(expr: Expr): Ordering {
67066706
*/
67076707
export class Ordering {
67086708
constructor(
6709-
private expr: Expr,
6710-
private direction: 'ascending' | 'descending'
6709+
readonly expr: Expr,
6710+
readonly direction: 'ascending' | 'descending'
67116711
) {}
67126712

67136713
/**

packages/firestore/src/lite-api/pipeline.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export class Pipeline<AppModelType = DocumentData>
144144
* @private
145145
*/
146146
protected documentReferenceFactory: (id: DocumentKey) => DocumentReference,
147-
protected stages: Stage[],
147+
readonly stages: Stage[],
148148
// TODO(pipeline) support converter
149149
//private converter: FirestorePipelineConverter<AppModelType> = defaultPipelineConverter()
150150
protected converter: unknown = {}

packages/firestore/src/lite-api/stage.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export interface Stage extends ProtoSerializable<ProtoStage> {
4848
export class AddFields implements Stage {
4949
name = 'add_fields';
5050

51-
constructor(private fields: Map<string, Expr>) {}
51+
constructor(readonly fields: Map<string, Expr>) {}
5252

5353
/**
5454
* @internal
@@ -69,8 +69,8 @@ export class Aggregate implements Stage {
6969
name = 'aggregate';
7070

7171
constructor(
72-
private accumulators: Map<string, Accumulator>,
73-
private groups: Map<string, Expr>
72+
readonly accumulators: Map<string, Accumulator>,
73+
readonly groups: Map<string, Expr>
7474
) {}
7575

7676
/**
@@ -94,7 +94,7 @@ export class Aggregate implements Stage {
9494
export class Distinct implements Stage {
9595
name = 'distinct';
9696

97-
constructor(private groups: Map<string, Expr>) {}
97+
constructor(readonly groups: Map<string, Expr>) {}
9898

9999
/**
100100
* @internal
@@ -114,7 +114,7 @@ export class Distinct implements Stage {
114114
export class CollectionSource implements Stage {
115115
name = 'collection';
116116

117-
constructor(private collectionPath: string) {
117+
constructor(readonly collectionPath: string) {
118118
if (!this.collectionPath.startsWith('/')) {
119119
this.collectionPath = '/' + this.collectionPath;
120120
}
@@ -138,7 +138,7 @@ export class CollectionSource implements Stage {
138138
export class CollectionGroupSource implements Stage {
139139
name = 'collection_group';
140140

141-
constructor(private collectionId: string) {}
141+
constructor(readonly collectionId: string) {}
142142

143143
/**
144144
* @internal
@@ -175,7 +175,7 @@ export class DatabaseSource implements Stage {
175175
export class DocumentsSource implements Stage {
176176
name = 'documents';
177177

178-
constructor(private docPaths: string[]) {}
178+
constructor(readonly docPaths: string[]) {}
179179

180180
static of(refs: DocumentReference[]): DocumentsSource {
181181
return new DocumentsSource(refs.map(ref => '/' + ref.path));
@@ -201,7 +201,7 @@ export class DocumentsSource implements Stage {
201201
export class Where implements Stage {
202202
name = 'where';
203203

204-
constructor(private condition: FilterCondition & Expr) {}
204+
constructor(readonly condition: FilterCondition & Expr) {}
205205

206206
/**
207207
* @internal
@@ -243,11 +243,11 @@ export class FindNearest implements Stage {
243243
* @param _distanceField
244244
*/
245245
constructor(
246-
private _field: Field,
247-
private _vectorValue: ObjectValue,
248-
private _distanceMeasure: 'euclidean' | 'cosine' | 'dot_product',
249-
private _limit?: number,
250-
private _distanceField?: string
246+
readonly _field: Field,
247+
readonly _vectorValue: ObjectValue,
248+
readonly _distanceMeasure: 'euclidean' | 'cosine' | 'dot_product',
249+
readonly _limit?: number,
250+
readonly _distanceField?: string
251251
) {}
252252

253253
/**
@@ -286,7 +286,7 @@ export class FindNearest implements Stage {
286286
export class Limit implements Stage {
287287
name = 'limit';
288288

289-
constructor(private limit: number) {}
289+
constructor(readonly limit: number) {}
290290

291291
/**
292292
* @internal
@@ -306,7 +306,7 @@ export class Limit implements Stage {
306306
export class Offset implements Stage {
307307
name = 'offset';
308308

309-
constructor(private offset: number) {}
309+
constructor(readonly offset: number) {}
310310

311311
/**
312312
* @internal
@@ -326,7 +326,7 @@ export class Offset implements Stage {
326326
export class Select implements Stage {
327327
name = 'select';
328328

329-
constructor(private projections: Map<string, Expr>) {}
329+
constructor(readonly projections: Map<string, Expr>) {}
330330

331331
/**
332332
* @internal
@@ -346,7 +346,7 @@ export class Select implements Stage {
346346
export class Sort implements Stage {
347347
name = 'sort';
348348

349-
constructor(private orders: Ordering[]) {}
349+
constructor(readonly orders: Ordering[]) {}
350350

351351
/**
352352
* @internal

0 commit comments

Comments
 (0)