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
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,49 @@ Expression parseExpression(@NonNull Map<String, Object> expressionMap) {
case "array_sum":
return Expression.arraySum(parseChild(args, "expression"));
case "array_slice":
throw new UnsupportedOperationException(
"Expression type 'array_slice' is not supported on Android Firestore pipeline API");
{
Expression array = parseChild(args, "expression");
Expression offset = parseChild(args, "offset");
Map<String, Object> lengthMap = (Map<String, Object>) args.get("length");
if (lengthMap == null) {
return array.arraySliceToEnd(offset);
}
return array.arraySlice(offset, parseExpression(lengthMap));
}
case "array_filter":
{
Expression array = parseChild(args, "expression");
String alias = (String) args.get("alias");
Map<String, Object> filterMap = (Map<String, Object>) args.get("filter");
if (alias == null || filterMap == null) {
throw new IllegalArgumentException("array_filter requires alias and filter");
}
return array.arrayFilter(alias, parseBooleanExpression(filterMap));
}
case "array_transform":
{
Expression array = parseChild(args, "expression");
String elementAlias = (String) args.get("element_alias");
Map<String, Object> transformMap = (Map<String, Object>) args.get("transform");
if (elementAlias == null || transformMap == null) {
throw new IllegalArgumentException(
"array_transform requires element_alias and transform");
}
return array.arrayTransform(elementAlias, parseExpression(transformMap));
}
case "array_transform_with_index":
{
Expression array = parseChild(args, "expression");
String elementAlias = (String) args.get("element_alias");
String indexAlias = (String) args.get("index_alias");
Map<String, Object> transformMap = (Map<String, Object>) args.get("transform");
if (elementAlias == null || indexAlias == null || transformMap == null) {
throw new IllegalArgumentException(
"array_transform_with_index requires element_alias, index_alias, and transform");
}
return array.arrayTransformWithIndex(
elementAlias, indexAlias, parseExpression(transformMap));
}
case "if_absent":
{
Map<String, Object> exprMap = (Map<String, Object>) args.get("expression");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,86 @@ - (FIRExprBridge *)parseExpression:(NSDictionary<NSString *, id> *)map error:(NS
return FLTNewFunctionExprBridge(@"array_concat", all);
}

// -------------------------------------------------------------------------
// expression + offset (+ optional length): array_slice
// -------------------------------------------------------------------------
if ([name isEqualToString:@"array_slice"]) {
id exprMap = args[@"expression"];
id offsetMap = args[@"offset"];
id lengthMap = args[@"length"];
if (![exprMap isKindOfClass:[NSDictionary class]] ||
![offsetMap isKindOfClass:[NSDictionary class]]) {
if (error) *error = parseError(@"array_slice requires expression and offset");
return nil;
}
FIRExprBridge *expr = [self parseExpression:exprMap error:error];
FIRExprBridge *offset = [self parseExpression:offsetMap error:error];
if (!expr || !offset) return nil;
NSMutableArray<FIRExprBridge *> *sliceArgs =
[NSMutableArray arrayWithObjects:expr, offset, nil];
if ([lengthMap isKindOfClass:[NSDictionary class]]) {
FIRExprBridge *length = [self parseExpression:lengthMap error:error];
if (!length) return nil;
[sliceArgs addObject:length];
}
return FLTNewFunctionExprBridge(@"array_slice", sliceArgs);
}

// -------------------------------------------------------------------------
// expression + alias + filter: array_filter
// -------------------------------------------------------------------------
if ([name isEqualToString:@"array_filter"]) {
id exprMap = args[@"expression"];
NSString *alias = args[@"alias"];
id filterMap = args[@"filter"];
if (![exprMap isKindOfClass:[NSDictionary class]] || ![alias isKindOfClass:[NSString class]] ||
![filterMap isKindOfClass:[NSDictionary class]]) {
if (error) *error = parseError(@"array_filter requires expression, alias, and filter");
return nil;
}
FIRExprBridge *expr = [self parseExpression:exprMap error:error];
FIRExprBridge *filter = [self parseBooleanExpression:filterMap error:error];
if (!expr || !filter) return nil;
return FLTNewFunctionExprBridge(@"array_filter",
@[ expr, [[FIRConstantBridge alloc] init:alias], filter ]);
}

// -------------------------------------------------------------------------
// expression + aliases + transform: array_transform / array_transform_with_index
// -------------------------------------------------------------------------
if ([name isEqualToString:@"array_transform"] ||
[name isEqualToString:@"array_transform_with_index"]) {
id exprMap = args[@"expression"];
NSString *elementAlias = args[@"element_alias"];
NSString *indexAlias = args[@"index_alias"];
id transformMap = args[@"transform"];
BOOL withIndex = [name isEqualToString:@"array_transform_with_index"];
if (![exprMap isKindOfClass:[NSDictionary class]] ||
![elementAlias isKindOfClass:[NSString class]] ||
(withIndex && ![indexAlias isKindOfClass:[NSString class]]) ||
![transformMap isKindOfClass:[NSDictionary class]]) {
if (error) {
NSString *message =
withIndex
? @"array_transform_with_index requires expression, element_alias, index_alias, "
@"and transform"
: @"array_transform requires expression, element_alias, and transform";
*error = parseError(message);
}
return nil;
}
FIRExprBridge *expr = [self parseExpression:exprMap error:error];
FIRExprBridge *transform = [self parseExpression:transformMap error:error];
if (!expr || !transform) return nil;
NSMutableArray<FIRExprBridge *> *transformArgs =
[NSMutableArray arrayWithObjects:expr, [[FIRConstantBridge alloc] init:elementAlias], nil];
if (withIndex) {
[transformArgs addObject:[[FIRConstantBridge alloc] init:indexAlias]];
}
[transformArgs addObject:transform];
return FLTNewFunctionExprBridge(name, transformArgs);
}

// -------------------------------------------------------------------------
// elements[]: array (construct) — Expression.array([...]) from Dart
// -------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,41 @@ abstract class Expression implements PipelineSerializable {
return _ArrayIndexOfAllExpression(this, _toExpression(element));
}

/// Returns a slice of this array starting at [offset].
///
/// When [length] is provided, at most [length] elements are returned.
Expression arraySlice(Object? offset, [Object? length]) {
return _ArraySliceExpression(
this,
_toExpression(offset),
length == null ? null : _toExpression(length),
);
}

/// Filters this array by evaluating [filter] for each element bound to [alias].
Expression arrayFilter(String alias, BooleanExpression filter) {
return _ArrayFilterExpression(this, alias, filter);
}

/// Transforms each element of this array bound to [elementAlias].
Expression arrayTransform(String elementAlias, Expression transform) {
return _ArrayTransformExpression(this, elementAlias, null, transform);
}

/// Transforms each element of this array with both element and index aliases.
Expression arrayTransformWithIndex(
String elementAlias,
String indexAlias,
Expression transform,
) {
return _ArrayTransformExpression(
this,
elementAlias,
indexAlias,
transform,
);
}

// ============================================================================
// AGGREGATE FUNCTIONS
// ============================================================================
Expand Down Expand Up @@ -1596,6 +1631,52 @@ abstract class Expression implements PipelineSerializable {
);
}

/// Returns a slice of [array] starting at [offset].
static Expression arraySliceStatic(
Expression array,
Object? offset, [
Object? length,
]) {
return _ArraySliceExpression(
array,
_toExpression(offset),
length == null ? null : _toExpression(length),
);
}

/// Filters [array] by evaluating [filter] for each element bound to [alias].
static Expression arrayFilterStatic(
Expression array,
String alias,
BooleanExpression filter,
) {
return _ArrayFilterExpression(array, alias, filter);
}

/// Transforms each element of [array] bound to [elementAlias].
static Expression arrayTransformStatic(
Expression array,
String elementAlias,
Expression transform,
) {
return _ArrayTransformExpression(array, elementAlias, null, transform);
}

/// Transforms each element of [array] with both element and index aliases.
static Expression arrayTransformWithIndexStatic(
Expression array,
String elementAlias,
String indexAlias,
Expression transform,
) {
return _ArrayTransformExpression(
array,
elementAlias,
indexAlias,
transform,
);
}

/// Creates a raw/custom function expression
static Expression rawFunction(
String name,
Expand Down Expand Up @@ -2423,6 +2504,92 @@ class _ArraySumExpression extends FunctionExpression {
}
}

/// Represents an array slice expression.
class _ArraySliceExpression extends FunctionExpression {
final Expression expression;
final Expression offset;
final Expression? sliceLength;

_ArraySliceExpression(this.expression, this.offset, this.sliceLength);

@override
String get name => 'array_slice';

@override
Map<String, dynamic> toMap() {
final args = <String, dynamic>{
'expression': expression.toMap(),
'offset': offset.toMap(),
};
if (sliceLength != null) {
args['length'] = sliceLength!.toMap();
}
return {
'name': name,
'args': args,
};
}
}

/// Represents an array filter expression.
class _ArrayFilterExpression extends FunctionExpression {
final Expression expression;
final String elementAlias;
final BooleanExpression filter;

_ArrayFilterExpression(this.expression, this.elementAlias, this.filter);

@override
String get name => 'array_filter';

@override
Map<String, dynamic> toMap() {
return {
'name': name,
'args': {
'expression': expression.toMap(),
'alias': elementAlias,
'filter': filter.toMap(),
},
};
}
}

/// Represents an array transform expression.
class _ArrayTransformExpression extends FunctionExpression {
final Expression expression;
final String elementAlias;
final String? indexAlias;
final Expression transform;

_ArrayTransformExpression(
this.expression,
this.elementAlias,
this.indexAlias,
this.transform,
);

@override
String get name =>
indexAlias == null ? 'array_transform' : 'array_transform_with_index';

@override
Map<String, dynamic> toMap() {
final args = <String, dynamic>{
'expression': expression.toMap(),
'element_alias': elementAlias,
'transform': transform.toMap(),
};
if (indexAlias != null) {
args['index_alias'] = indexAlias;
}
return {
'name': name,
'args': args,
};
}
}

// ============================================================================
// CONDITIONAL / LOGIC OPERATION EXPRESSION CLASSES
// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,24 @@ void runPipelineExpressionsTests() {
expect(snapshot.result[0].data()!['tags_rev'], ['q', 'p']);
});

test('addFields with arraySlice returns sliced array', () async {
final snapshot = await firestore
.pipeline()
.collection('pipeline-e2e')
.where(Expression.field('test').equalValue('expressions'))
.where(Expression.field('score').equalValue(50))
.addFields(Expression.field('arr').arraySlice(1, 2).as('arr_slice'))
.limit(1)
.execute();

expectResultCount(snapshot, 1);
expectResultsData(snapshot, [
{
'arr_slice': [4, 6],
},
]);
});

test(
'arraySum addFields succeeds on Android',
() async {
Expand Down
Loading
Loading