Skip to content

Commit 2cbd653

Browse files
committed
error, ifabsent, currenttimestamp, join
1 parent 5ddc9ce commit 2cbd653

File tree

3 files changed

+322
-21
lines changed

3 files changed

+322
-21
lines changed

packages/firestore/src/api_pipelines.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ export {
163163
length as len,
164164
abs,
165165
concat,
166+
currentTimestamp,
167+
error,
168+
ifAbsent,
169+
join,
166170
Expression,
167171
AliasedExpression,
168172
Field,

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

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2050,6 +2050,71 @@ export abstract class Expression implements ProtoValueSerializable, UserData {
20502050
return new FunctionExpression('string_reverse', [this]);
20512051
}
20522052

2053+
/**
2054+
* Creates an expression that returns the `elseValue` argument if this expression results in an absent value, else
2055+
* return the result of the this expression evaluation.
2056+
*
2057+
* ```typescript
2058+
* // Returns the value of the optional field 'optional_field', or returns 'default_value'
2059+
* // if the field is absent.
2060+
* field("optional_field").ifAbsent("default_value")
2061+
* ```
2062+
*
2063+
* @param elseValue The value that will be returned if this Expression evaluates to an absent value.
2064+
* @return A new [Expression] representing the ifAbsent operation.
2065+
*/
2066+
ifAbsent(elseValue: unknown): Expression;
2067+
2068+
/**
2069+
* Creates an expression that returns the `elseValue` argument if this expression results in an absent value, else
2070+
* return the result of this expression evaluation.
2071+
*
2072+
* ```typescript
2073+
* // Returns the value of the optional field 'optional_field', or if that is
2074+
* // absent, then returns the value of the field `
2075+
* field("optional_field").ifAbsent(field('default_field'))
2076+
* ```
2077+
*
2078+
* @param elseExpression The Expression that will be evaluated if this Expression evaluates to an absent value.
2079+
* @return A new [Expression] representing the ifAbsent operation.
2080+
*/
2081+
ifAbsent(elseExpression: unknown): Expression;
2082+
2083+
ifAbsent(elseValueOrExpression: Expression | unknown): Expression {
2084+
return new FunctionExpression('if_absent', [this, valueToDefaultExpr(elseValueOrExpression)], 'ifAbsent');
2085+
}
2086+
2087+
/**
2088+
* Creates an expression that joins the elements of an array into a string.
2089+
*
2090+
* ```typescript
2091+
* // Join the elements of the 'tags' field with the delimiter from the 'separator' field.
2092+
* field("tags").join(field("separator"))
2093+
* ```
2094+
*
2095+
* @param delimiterExpression The expression that evaluates to the delimiter string.
2096+
* @return A new Expression representing the join operation.
2097+
*/
2098+
join(delimiterExpression: Expression): Expression;
2099+
2100+
/**
2101+
* Creates an expression that joins the elements of an array field into a string.
2102+
*
2103+
* ```typescript
2104+
* // Join the elements of the 'tags' field with a comma and space.
2105+
* field("tags").join(", ")
2106+
* ```
2107+
*
2108+
* @param delimiter The string to use as a delimiter.
2109+
* @return A new Expression representing the join operation.
2110+
*/
2111+
join(delimiter: string): Expression;
2112+
2113+
join(delimeterValueOrExpression: string | Expression): Expression {
2114+
return new FunctionExpression('join', [this, valueToDefaultExpr(delimeterValueOrExpression)], 'join');
2115+
}
2116+
2117+
20532118
// TODO(new-expression): Add new expression method definitions above this line
20542119

20552120
/**
@@ -6768,6 +6833,37 @@ export function timestampSubtract(
67686833
);
67696834
}
67706835

6836+
/**
6837+
* @beta
6838+
*
6839+
* Creates an expression that evaluates to the current server timestamp.
6840+
*
6841+
* ```typescript
6842+
* // Get the current server timestamp
6843+
* currentTimestamp()
6844+
* ```
6845+
*
6846+
* @return A new Expression representing the current server timestamp.
6847+
*/
6848+
export function currentTimestamp(): FunctionExpression {
6849+
return new FunctionExpression('current_timestamp', [], 'currentTimestamp');
6850+
}
6851+
6852+
/**
6853+
* Creates an expression that raises an error with the given message. This could be useful for
6854+
* debugging purposes.
6855+
*
6856+
* ```typescript
6857+
* // Raise an error with the message "simulating an evaluation error".
6858+
* error("simulating an evaluation error")
6859+
* ```
6860+
*
6861+
* @return A new Expression representing the error() operation.
6862+
*/
6863+
export function error(message: string): Expression {
6864+
return new FunctionExpression('error', [constant(message)], 'currentTimestamp');
6865+
}
6866+
67716867
/**
67726868
* @beta
67736869
*
@@ -7232,6 +7328,133 @@ export function abs(expr: Expression | string): FunctionExpression {
72327328
return fieldOrExpression(expr).abs();
72337329
}
72347330

7331+
/**
7332+
* Creates an expression that returns the `elseExpr` argument if `ifExpr` is absent, else return
7333+
* the result of the `ifExpr` argument evaluation.
7334+
*
7335+
* ```typescript
7336+
* // Returns the value of the optional field 'optional_field', or returns 'default_value'
7337+
* // if the field is absent.
7338+
* ifAbsent(field("optional_field"), constant("default_value"))
7339+
* ```
7340+
*
7341+
* @param ifExpr The expression to check for absence.
7342+
* @param elseExpr The expression that will be evaluated and returned if [ifExpr] is absent.
7343+
* @return A new Expression representing the ifAbsent operation.
7344+
*/
7345+
export function ifAbsent(ifExpr: Expression, elseExpr: Expression): Expression;
7346+
7347+
/**
7348+
* Creates an expression that returns the `elseValue` argument if `ifExpr` is absent, else
7349+
* return the result of the `ifExpr` argument evaluation.
7350+
*
7351+
* ```typescript
7352+
* // Returns the value of the optional field 'optional_field', or returns 'default_value'
7353+
* // if the field is absent.
7354+
* ifAbsent(field("optional_field"), "default_value")
7355+
* ```
7356+
*
7357+
* @param ifExpr The expression to check for absence.
7358+
* @param elseValue The value that will be returned if `ifExpr` evaluates to an absent value.
7359+
* @return A new [Expression] representing the ifAbsent operation.
7360+
*/
7361+
export function ifAbsent(ifExpr: Expression, elseValue: unknown): Expression;
7362+
7363+
/**
7364+
* Creates an expression that returns the `elseExpr` argument if `ifFieldName` is absent, else
7365+
* return the value of the field.
7366+
*
7367+
* ```typescript
7368+
* // Returns the value of the optional field 'optional_field', or returns the value of
7369+
* // 'default_field' if 'optional_field' is absent.
7370+
* ifAbsent("optional_field", field("default_field"))
7371+
* ```
7372+
*
7373+
* @param ifFieldName The field to check for absence.
7374+
* @param elseExpr The expression that will be evaluated and returned if `ifFieldName` is
7375+
* absent.
7376+
* @return A new Expression representing the ifAbsent operation.
7377+
*/
7378+
export function ifAbsent(ifFieldName: string, elseExpr: Expression): Expression;
7379+
7380+
/**
7381+
* Creates an expression that returns the `elseValue` argument if `ifFieldName` is absent, else
7382+
* return the value of the field.
7383+
*
7384+
* ```typescript
7385+
* // Returns the value of the optional field 'optional_field', or returns 'default_value'
7386+
* // if the field is absent.
7387+
* ifAbsent("optional_field", "default_value")
7388+
* ```
7389+
*
7390+
* @param ifFieldName The field to check for absence.
7391+
* @param elseValue The value that will be returned if [ifFieldName] is absent.
7392+
* @return A new Expression representing the ifAbsent operation.
7393+
*/
7394+
export function ifAbsent(ifFieldName: string | Expression, elseValue: Expression | unknown): Expression;
7395+
export function ifAbsent(fieldNameOrExpression: string | Expression, elseValue: Expression | unknown): Expression {
7396+
return fieldOrExpression(fieldNameOrExpression).ifAbsent(valueToDefaultExpr(elseValue));
7397+
}
7398+
7399+
/**
7400+
* Creates an expression that joins the elements of an array into a string.
7401+
*
7402+
* ```typescript
7403+
* // Join the elements of the 'tags' field with a comma and space.
7404+
* join("tags", ", ")
7405+
* ```
7406+
*
7407+
* @param arrayFieldName The name of the field containing the array.
7408+
* @param delimiter The string to use as a delimiter.
7409+
* @return A new Expression representing the join operation.
7410+
*/
7411+
export function join(arrayFieldName: string, delimiter: string): Expression;
7412+
7413+
/**
7414+
* Creates an expression that joins the elements of an array into a string.
7415+
*
7416+
* ```typescript
7417+
* // Join an array of string using the delimiter from the 'separator' field.
7418+
* join(array(['foo', 'bar']), field("separator"))
7419+
* ```
7420+
*
7421+
* @param arrayExpression An expression that evaluates to an array.
7422+
* @param delimiterExpression The expression that evaluates to the delimiter string.
7423+
* @return A new Expression representing the join operation.
7424+
*/
7425+
export function join(arrayExpression: Expression, delimiterExpression: Expression): Expression;
7426+
7427+
/**
7428+
* Creates an expression that joins the elements of an array into a string.
7429+
*
7430+
* ```typescript
7431+
* // Join the elements of the 'tags' field with a comma and space.
7432+
* join(field("tags"), ", ")
7433+
* ```
7434+
*
7435+
* @param arrayExpression An expression that evaluates to an array.
7436+
* @param delimiter The string to use as a delimiter.
7437+
* @return A new Expression representing the join operation.
7438+
*/
7439+
export function join(arrayExpression: Expression, delimiter: string): Expression;
7440+
7441+
/**
7442+
* Creates an expression that joins the elements of an array into a string.
7443+
*
7444+
* ```typescript
7445+
* // Join the elements of the 'tags' field with the delimiter from the 'separator' field.
7446+
* join('tags', field("separator"))
7447+
* ```
7448+
*
7449+
* @param arrayFieldName The name of the field containing the array.
7450+
* @param delimiterExpression The expression that evaluates to the delimiter string.
7451+
* @return A new Expression representing the join operation.
7452+
*/
7453+
export function join(arrayFieldName: string, delimiterExpression: Expression): Expression;
7454+
export function join(fieldNameOrExpression: string | Expression, delimiterValueOrExpression: Expression | string): Expression {
7455+
return fieldOrExpression(fieldNameOrExpression).join(valueToDefaultExpr(delimiterValueOrExpression));
7456+
}
7457+
72357458
// TODO(new-expression): Add new top-level expression function definitions above this line
72367459

72377460
/**

packages/firestore/test/integration/api/pipeline.test.ts

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ import {
139139
stringReverse,
140140
len as length,
141141
abs,
142-
concat
142+
concat,
143+
error,
144+
currentTimestamp, ifAbsent, join
143145
} from '../util/pipeline_export';
144146

145147
use(chaiAsPromised);
@@ -989,26 +991,6 @@ apiDescribe.only('Pipelines', persistence => {
989991
});
990992
});
991993

992-
describe('concat stage', () => {
993-
it('can concat fields', async () => {
994-
const snapshot = await execute(
995-
firestore
996-
.pipeline()
997-
.collection(randomCol.path)
998-
.addFields(
999-
concat('author', ' ', field('title')).as('display'),
1000-
field('author').concat(': ', field('title')).as('display2')
1001-
)
1002-
.where(equal('author', 'Douglas Adams'))
1003-
.select('display', 'display2')
1004-
);
1005-
expectResults(snapshot, {
1006-
display: "Douglas Adams The Hitchhiker's Guide to the Galaxy",
1007-
display2: "Douglas Adams: The Hitchhiker's Guide to the Galaxy"
1008-
});
1009-
});
1010-
});
1011-
1012994
describe('distinct stage', () => {
1013995
it('returns distinct values as expected', async () => {
1014996
const snapshot = await execute(
@@ -3839,6 +3821,98 @@ apiDescribe.only('Pipelines', persistence => {
38393821
});
38403822
});
38413823

3824+
it('can concat fields', async () => {
3825+
const snapshot = await execute(
3826+
firestore
3827+
.pipeline()
3828+
.collection(randomCol.path)
3829+
.addFields(
3830+
concat('author', ' ', field('title')).as('display'),
3831+
field('author').concat(': ', field('title')).as('display2')
3832+
)
3833+
.where(equal('author', 'Douglas Adams'))
3834+
.select('display', 'display2')
3835+
);
3836+
expectResults(snapshot, {
3837+
display: "Douglas Adams The Hitchhiker's Guide to the Galaxy",
3838+
display2: "Douglas Adams: The Hitchhiker's Guide to the Galaxy"
3839+
});
3840+
});
3841+
3842+
it('supports currentTimestamp', async () => {
3843+
const snapshot = await execute(
3844+
firestore
3845+
.pipeline()
3846+
.collection(randomCol.path)
3847+
.limit(1)
3848+
.addFields(
3849+
currentTimestamp().as('now')
3850+
)
3851+
.select('now')
3852+
);
3853+
let now = snapshot.results[0].get('now') as Timestamp;
3854+
expect(now).instanceof(Timestamp);
3855+
expect(now.toDate().getUTCSeconds() - (new Date()).getUTCSeconds()).lessThan(5000);
3856+
});
3857+
3858+
it('supports error', async () => {
3859+
const snapshot = await execute(
3860+
firestore
3861+
.pipeline()
3862+
.collection(randomCol.path)
3863+
.limit(1)
3864+
.select(
3865+
isError(error('test error')).as('error')
3866+
));
3867+
3868+
expectResults(snapshot, {
3869+
'error': true,
3870+
});
3871+
});
3872+
3873+
it('supports ifAbsent', async () => {
3874+
const snapshot = await execute(
3875+
firestore
3876+
.pipeline()
3877+
.collection(randomCol.path)
3878+
.limit(1)
3879+
.replaceWith(map({
3880+
title: 'foo'
3881+
}))
3882+
.select(
3883+
ifAbsent('title', 'default title').as('title'),
3884+
field('name').ifAbsent('default name').as('name'),
3885+
field('name').ifAbsent(field('title')).as('nameOrTitle'),
3886+
));
3887+
3888+
expectResults(snapshot, {
3889+
title: 'foo',
3890+
name: 'default name',
3891+
nameOrTitle: 'foo'
3892+
});
3893+
});
3894+
3895+
it.only('supports join', async () => {
3896+
const snapshot = await execute(
3897+
firestore
3898+
.pipeline()
3899+
.collection(randomCol.path)
3900+
.limit(1)
3901+
.replaceWith(map({
3902+
tags: ['foo', 'bar', 'baz'],
3903+
delimeter: '|'
3904+
}))
3905+
.select(
3906+
join('tags', ',').as('csv'),
3907+
field('tags').join('|').as('or')
3908+
));
3909+
3910+
expectResults(snapshot, {
3911+
csv: 'foo,bar,baz',
3912+
or: 'foo|bar|baz'
3913+
});
3914+
});
3915+
38423916
// TODO(new-expression): Add new expression tests above this line
38433917
});
38443918

0 commit comments

Comments
 (0)