Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion handwritten/firestore/api-report/firestore.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,9 @@ function multiply(first: Expression, second: Expression | unknown): FunctionExpr
// @beta
function multiply(fieldName: string, second: Expression | unknown): FunctionExpression;

// @beta
function nor(first: BooleanExpression, second: BooleanExpression, ...more: BooleanExpression[]): BooleanExpression;

// @beta
function not(booleanExpr: BooleanExpression): BooleanExpression;

Expand Down Expand Up @@ -2119,7 +2122,9 @@ declare namespace Pipelines {
stringIndexOf,
stringRepeat,
stringReplaceAll,
stringReplaceOne
stringReplaceOne,
nor,
switchOn
}
}
export { Pipelines }
Expand Down Expand Up @@ -2642,6 +2647,9 @@ function sum(expression: Expression): AggregateFunction;
// @beta
function sum(fieldName: string): AggregateFunction;

// @beta
function switchOn(condition: BooleanExpression, result: Expression, ...others: Array<BooleanExpression | Expression>): FunctionExpression;

// @public
export class Timestamp implements firestore.Timestamp {
// Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}'
Expand Down
64 changes: 64 additions & 0 deletions handwritten/firestore/dev/src/pipelines/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8608,6 +8608,70 @@ export function or(
return new FunctionExpression('or', [first, second, ...more]).asBoolean();
}

/**
* @beta
* Creates an expression that performs a logical 'NOR' operation on multiple filter conditions.
*
* @example
* ```typescript
* // Check if neither the 'age' field is greater than 18 nor the 'city' field is "London"
* const condition = nor(
* greaterThan("age", 18),
* equal("city", "London")
* );
* ```
*
* @param first - The first filter condition.
* @param second - The second filter condition.
* @param more - Additional filter conditions to 'NOR' together.
* @returns A new `Expression` representing the logical 'NOR' operation.
*/
export function nor(
first: BooleanExpression,
second: BooleanExpression,
...more: BooleanExpression[]
): BooleanExpression {
return new FunctionExpression('nor', [first, second, ...more]).asBoolean();
}

/**
* @beta
* Creates an expression that evaluates to the result corresponding to the first true condition.
*
* @remarks
* This function behaves like a `switch` statement. It accepts an alternating sequence of conditions
* and their corresponding results.
* If an odd number of arguments is provided, the final argument serves as a default fallback result.
* If no default is provided and no condition evaluates to true, it throws an error.
*
* @example
* ```typescript
* // Return "Active" if field "status" is 1, "Pending" if field "status" is 2,
* // and default to "Unknown" if none of the conditions are true.
* switchOn(
* equal(field("status"), 1), "Active",
* equal(field("status"), 2), "Pending",
* "Unknown"
* )
* ```
*
* @param condition - The first condition to check.
* @param result - The result if the first condition is true.
* @param others - Additional conditions and results, and optionally a default value.
* @returns A new `Expression` representing the switch operation.
*/
export function switchOn(
condition: BooleanExpression,
result: Expression,
...others: Array<BooleanExpression | Expression>
): FunctionExpression {
return new FunctionExpression('switch_on', [
valueToDefaultExpr(condition),
valueToDefaultExpr(result),
...others.map(valueToDefaultExpr),
]);
}

/**
* @beta
* Creates an expression that returns the value of the base expression raised to the power of the exponent expression.
Expand Down
2 changes: 2 additions & 0 deletions handwritten/firestore/dev/src/pipelines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,5 +155,7 @@ export {
stringRepeat,
stringReplaceAll,
stringReplaceOne,
nor,
switchOn,
// TODO(new-expression): Add new expression exports above this line
} from './expression';
124 changes: 124 additions & 0 deletions handwritten/firestore/dev/system-test/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ import {
isType,
timestampTruncate,
split,
switchOn,
nor,
// TODO(new-expression): add new expression imports above this line
} from '../src/pipelines';

Expand Down Expand Up @@ -1644,6 +1646,27 @@ describe.skipClassic('Pipeline class', () => {
);
});

it('where with nor', async () => {
const snapshot = await firestore
.pipeline()
.collection(randomCol.path)
.where(
nor(
equal('genre', 'Romance'),
equal('genre', 'Dystopian'),
equal('genre', 'Fantasy'),
greaterThan('published', 1949),
),
)
.select('title')
.execute();
expectResults(
snapshot,
{title: 'Crime and Punishment'},
{title: 'The Great Gatsby'},
);
});

it('supports options', async () => {
const snapshot = await firestore
.pipeline()
Expand Down Expand Up @@ -5230,6 +5253,107 @@ describe.skipClassic('Pipeline class', () => {
});
});

it('supports nor', async () => {
const snapshot = await firestore
.pipeline()
.collection(randomCol.path)
.limit(1)
.replaceWith(
map({
a: false,
b: false,
c: true,
d: null,
}),
)
.select(
nor(field('a').asBoolean(), field('b').asBoolean()).as(
'twoConditions',
),
nor(
field('a').asBoolean(),
field('b').asBoolean(),
field('c').asBoolean(),
).as('threeConditions'),
nor(
field('a').asBoolean(),
field('b').asBoolean(),
field('d').asBoolean(),
).as('threeConditionsWithNull'),
)
.execute();

expectResults(snapshot, {
twoConditions: true,
threeConditions: false,
threeConditionsWithNull: null,
});
});

describe('switchOn', () => {
it('supports basic switch', async () => {
const snapshot = await firestore
.pipeline()
.collection(randomCol.path)
.limit(1)
.replaceWith(map({value: 1}))
.select(
switchOn(
equal(field('value'), 1),
constant('one'),
constant('NA'),
).as('result1'),
switchOn(
equal(field('value'), 2),
constant('two'),
constant('NA'),
).as('result2'),
)
.execute();
expectResults(snapshot, {result1: 'one', result2: 'NA'});
});

it('supports multi-branch switch', async () => {
const snapshot = await firestore
.pipeline()
.collection(randomCol.path)
.limit(1)
.replaceWith(map({value: 2}))
.select(
switchOn(
equal(field('value'), 1),
constant('one'),
equal(field('value'), 2),
constant('two'),
equal(field('value'), 3),
constant('three'),
constant('default'),
).as('result'),
)
.execute();
expectResults(snapshot, {result: 'two'});
});

it('throws if no match and no default', async () => {
await expect(
firestore
.pipeline()
.collection(randomCol.path)
.limit(1)
.replaceWith(map({value: 5}))
.select(
switchOn(
equal(field('value'), 1),
constant('one'),
equal(field('value'), 2),
constant('two'),
).as('result'),
)
.execute(),
).to.be.rejectedWith(/all switch cases evaluate to false/);
});
});

// TODO(new-expression): Add new expression tests above this line
});

Expand Down
56 changes: 56 additions & 0 deletions handwritten/firestore/types/firestore.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10582,6 +10582,62 @@ declare namespace FirebaseFirestore {
second: BooleanExpression,
...more: BooleanExpression[]
): BooleanExpression;

/**
* @beta
* Creates an expression that performs a logical 'NOR' operation on multiple filter conditions.
*
* @example
* ```typescript
* // Check if neither the 'age' field is greater than 18 nor the 'city' field is "London"
* const condition = nor(
* greaterThan("age", 18),
* equal("city", "London")
* );
* ```
* @param first The first filter condition.
* @param second The second filter condition.
* @param more - Additional filter conditions to 'NOR' together.
* @returns A new {@code Expression} representing the logical 'NOR' operation.
*/
export function nor(
first: BooleanExpression,
second: BooleanExpression,
...more: BooleanExpression[]
): BooleanExpression;

/**
* @beta
* Creates an expression that evaluates to the result corresponding to the first true condition.
*
* @remarks
* This function behaves like a `switch` statement. It accepts an alternating sequence of conditions
* and their corresponding results.
* If an odd number of arguments is provided, the final argument serves as a default fallback result.
* If no default is provided and no condition evaluates to true, it throws an error.
*
* @example
* ```typescript
* // Return "Active" if field "status" is 1, "Pending" if field "status" is 2,
* // and default to "Unknown" if none of the conditions are true.
* switchOn(
* equal(field("status"), 1), "Active",
* equal(field("status"), 2), "Pending",
* "Unknown"
* )
* ```
*
* @param condition - The first condition to check.
* @param result - The result if the first condition is true.
* @param others - Additional conditions and results, and optionally a default value.
* @returns A new {@code Expression} representing the switch operation.
*/
export function switchOn(
condition: BooleanExpression,
result: Expression,
...others: Array<BooleanExpression | Expression>
): FunctionExpression;

/**
* @beta
* Creates an expression that returns the value of the base expression raised to the power of the exponent expression.
Expand Down
Loading