Implemented subquery expressions.

Added missing getters in CPExpression.
Added a type: argument to FunctionExpression initializer used by  subclasses to init super with their own type.
Fixed evaluation with bindings dictionary in CPExpression_variable. Allow values in bindings to be a constant or an expression (per cocoa).

Tests: added tests for subqueryExpressions, for variableExpression evaluation where the value in the bindings dictionary can be either a constant or another expression.
Parsing a simple variable expression works (e.g. '$x = 12'), parsing a variable in a combined path (e.g.
'$x.path = 12' won't yet.
This commit is contained in:
cacaodev
2010-10-29 18:58:29 +02:00
parent a80732f13a
commit 4b1fbf1eff
11 changed files with 250 additions and 69 deletions
+2 -11
View File
@@ -478,17 +478,8 @@ var CPComparisonPredicateModifier,
- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
{
var left = _left,
right = _right;
if (variables != nil)
{
left = [left _expressionWithSubstitutionVariables:variables];
right = [right _expressionWithSubstitutionVariables:variables];
}
var leftValue = [left expressionValueWithObject:object context:nil],
rightValue = [right expressionValueWithObject:object context:nil];
var leftValue = [_left expressionValueWithObject:object context:variables],
rightValue = [_right expressionValueWithObject:object context:variables];
if (_modifier == CPDirectPredicateModifier)
return [self _evaluateValue:leftValue rightValue:rightValue];
+59 -11
View File
@@ -227,13 +227,11 @@ CPMinusSetExpressionType = 9;
return [[CPExpression_function alloc] initWithTarget:target selector:CPSelectorFromString(function_name) arguments:parameters];
}
+ (CPExpression)expressionForSubquery:(CPExpression)expression usingIteratorVariable:(CPString)variable predicate:(id)predicate
+ (CPExpression)expressionForSubquery:(CPExpression)expression usingIteratorVariable:(CPString)variable predicate:(CPPredicate)predicate
{
return nil; // UNIMPLEMENTED
return [[CPExpression_subquery alloc] initWithExpression:expression usingIteratorVariable:variable predicate:predicate];
}
// Getting Information About an Expression
/*!
Returns the expression type for the receiver.
@@ -252,7 +250,7 @@ CPMinusSetExpressionType = 9;
*/
- (id)constantValue
{
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPConstantValueExpressionType"];
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
@@ -263,7 +261,7 @@ CPMinusSetExpressionType = 9;
*/
- (CPString)variable
{
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPVariableExpressionType"];
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
@@ -274,7 +272,7 @@ CPMinusSetExpressionType = 9;
*/
- (CPString)keyPath
{
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPKeyPathExpressionType"];
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
@@ -285,7 +283,7 @@ CPMinusSetExpressionType = 9;
*/
- (CPString)function
{
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPFunctionExpressionType"];
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
@@ -296,21 +294,70 @@ CPMinusSetExpressionType = 9;
*/
- (CPArray)arguments
{
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPFunctionExpressionType"];
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
/*!
Returns the collection of expressions in an aggregate expression, or the collection element of a subquery expression.
@return Returns the collection of expressions in an aggregate expression, or the collection element of a subquery expression.
@return The collection of expressions in an aggregate expression, or the collection element of a subquery expression.
This method raises an exception if it is not applicable to the receiver.
*/
- (id)collection
{
[CPException raise:CPInvalidArgumentException reason:@"self is not of CPAggregateExpressionType"];
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
/*!
Returns the predicate in a subquery expression.
@return The predicate in a subquery expression..
This method raises an exception if it is not applicable to the receiver.
*/
- (CPPredicate)predicate
{
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
/*!
Returns the operand for the receiver.
@return The operand for the receiver—that is, the object on which the selector will be invoked.
This method raises an exception if it is not applicable to the receiver.
*/
- (CPExpression)operand
{
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
/*!
Returns the left expression of a set expression.
@return The left expression of a set expression.
This method raises an exception if it is not applicable to the receiver.
*/
- (CPExpression)leftExpression
{
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
/*!
Returns the right expression of a set expression.
@return The right expression of a set expression.
This method raises an exception if it is not applicable to the receiver.
*/
- (CPExpression)rightExpression
{
_CPRaiseInvalidAbstractInvocation(self, _cmd);
return nil;
}
- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables
{
return self;
}
@end
@import "CPExpression_constant.j"
@@ -320,3 +367,4 @@ CPMinusSetExpressionType = 9;
@import "CPExpression_function.j"
@import "CPExpression_aggregate.j"
@import "CPExpression_set.j"
@import "CPExpression_subquery.j"
@@ -89,4 +89,4 @@ var CPCollectionKey = @"CPCollection";
[coder encodeObject:_aggregate forKey:CPCollectionKey];
}
@end
@end
@@ -17,7 +17,7 @@
- (BOOL)isEqual:(id)object
{
if (self == object)
if (self === object)
return YES;
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object constantValue] isEqual:[self constantValue]])
@@ -31,16 +31,11 @@
return _value;
}
- (id)expressionValueWithObject:object context:(CPDictionary)context
- (id)expressionValueWithObject:(id)object context:(CPDictionary)context
{
return _value;
}
- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables
{
return self;
}
- (CPString)description
{
if ([_value isKindOfClass:[CPString class]])
+17 -9
View File
@@ -22,14 +22,19 @@
return [self initWithTarget:operand selector:aSelector arguments:parameters];
}
- (id)initWithTarget:(CPExpression)targetExpression selector:aSelector arguments:parameters
- (id)initWithTarget:(CPExpression)operand selector:(SEL)aSelector arguments:(CPArray)parameters
{
[super initWithExpressionType:CPFunctionExpressionType];
return [self initWithTarget:operand selector:aSelector arguments:parameters type:CPFunctionExpressionType];
}
- (id)initWithTarget:(CPExpression)operand selector:(SEL)aSelector arguments:(CPArray)parameters type:(int)type
{
[super initWithExpressionType:type];
// Cocoa doc: "This method throws an exception immediately if the selector is unknown"
// but targetExpression's value (selector's target) may be resolved only at runtime.
// but operand's value (the target) may be resolved only at runtime.
_selector = aSelector;
_operand = targetExpression;
_operand = operand;
_arguments = parameters;
_argc = [parameters count];
@@ -98,7 +103,7 @@
{
var array = [CPArray array],
i;
// should we also allow variables for target and selectors ?
for (i = 0; i < _argc; i++)
[array addObject:[[_arguments objectAtIndex:i] _expressionWithSubstitutionVariables:variables]];
@@ -109,17 +114,19 @@
var CPSelectorNameKey = @"CPSelectorName",
CPArgumentsKey = @"CPArguments",
CPOperandKey = @"CPOperand";
CPOperandKey = @"CPOperand",
CPExpressionTypeKey = @"CPExpressionType";
@implementation CPExpression_function (CPCoding)
- (id)initWithCoder:(CPCoder)coder
{
var target = [coder decodeObjectForKey:CPOperandKey],
var type = [coder decodeIntForKey:CPExpressionTypeKey],
operand = [coder decodeObjectForKey:CPOperandKey],
selector = CPSelectorFromString([coder decodeObjectForKey:CPSelectorNameKey]),
arguments = [coder decodeObjectForKey:CPArgumentsKey];
parameters = [coder decodeObjectForKey:CPArgumentsKey];
return [self initWithTarget:target selector:selector arguments:arguments];
return [self initWithTarget:operand selector:selector arguments:parameters type:type];
}
- (void)encodeWithCoder:(CPCoder)coder
@@ -127,6 +134,7 @@ var CPSelectorNameKey = @"CPSelectorName",
[coder encodeObject:[self _function] forKey:CPSelectorNameKey];
[coder encodeObject:_arguments forKey:CPArgumentsKey];
[coder encodeObject:_operand forKey:CPOperandKey];
[coder encodeInt:_type forKey:CPExpressionTypeKey];
}
@end
@@ -17,11 +17,19 @@
{
var arg = [CPExpression expressionForConstantValue:keyPath];
// Cocoa: if it's a direct path selector is valueForKey:
self = [super initWithTarget:operand selector:@selector(valueForKeyPath:) arguments:[arg]];
self = [super initWithTarget:operand selector:@selector(valueForKeyPath:) arguments:[arg] type:CPKeyPathExpressionType];
return self;
}
- (BOOL)isEqual:(id)object
{
if (object === self)
return YES;
return ([object keyPath] == [self keyPath]); //If it appears that parsing generates nested keypaths with different targets and same keyPath, comparing -keyPath won't work.
}
- (CPExpression)pathExpression
{
return [[self arguments] objectAtIndex:0];
+4 -14
View File
@@ -4,20 +4,15 @@
@import <Foundation/CPDictionary.j>
@import <Foundation/CPCoder.j>
var defaultInstance = nil;
@implementation CPExpression_self : CPExpression
{
}
- (id)init
{
if (defaultInstance == nil)
{
[super initWithExpressionType:CPEvaluatedObjectExpressionType];
defaultInstance = self;
}
return defaultInstance;
[super initWithExpressionType:CPEvaluatedObjectExpressionType];
return self;
}
- (id)initWithCoder:(CPCoder)coder
@@ -31,7 +26,7 @@ var defaultInstance = nil;
- (BOOL)isEqual:(id)object
{
return (object === self);
return (_type == CPEvaluatedObjectExpressionType);
}
- (id)expressionValueWithObject:(id)object context:(CPDictionary)context
@@ -39,11 +34,6 @@ var defaultInstance = nil;
return object;
}
- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables
{
return self;
}
- (CPString)description
{
return @"SELF";
@@ -58,6 +58,7 @@
- (CPExpression )_expressionWithSubstitutionVariables:(CPDictionary )variables
{
// UNIMPLEMENTED
return self;
}
@@ -0,0 +1,103 @@
@import "CPExpression.j"
@implementation CPExpression_subquery : CPExpression
{
CPExpression _collection;
CPExpression _variableExpression;
CPPredicate _subpredicate;
}
- (id)initWithExpression:(CPExpression)collection usingIteratorVariable:(CPString)variable predicate:(CPPredicate)subpredicate
{
[super initWithExpressionType:CPSubqueryExpressionType];
_subpredicate = subpredicate;
_collection = collection;
_variableExpression = [CPExpression expressionForVariable:variable];
return self;
}
- (id)expressionValueWithObject:(id)object context:(id)context
{
var collection = [_collection expressionValueWithObject:object context:context],
count = [collection count],
result = [CPArray array],
bindings = [CPDictionary dictionaryWithObject:[CPExpression expressionForEvaluatedObject] forKey:[self variable]];
for (var i = 0; i < count; i++)
{
var item = [collection objectAtIndex:i];
if ([_subpredicate evaluateWithObject:item substitutionVariables:bindings])
[result addObject:item];
}
return result;
}
- (BOOL)isEqual:(id)object;
{
if (self === object)
return YES;
if (![_collection isEqual:[object collection]] || ![_subpredicate isEqual:[object predicate]])
return NO;
return YES;
}
- (id)collection
{
return _collection;
}
- (id)copy
{
return [[CPExpression_subquery alloc] initWithExpression:[_collection copy] usingIteratorExpression:[_variableExpression copy] predicate:[_subpredicate copy]];
}
- (id)predicate
{
return _subpredicate;
}
- (id)predicateFormat
{
return @"SUBQUERY(" + [_collection description] + ", " + [_variableExpression description] + ", " + [_subpredicate predicateFormat] + ")";
}
- (id)variable
{
return [_variableExpression variable];
}
- (id)variableExpression
{
return _variableExpression;
}
@end
var CPExpressionKey = @"CPExpression",
CPSubpredicateKey = @"CPSubpredicate",
CPVariableKey = @"CPVariable";
@implementation CPExpression_subquery (CPCoding)
- (id)initWithCoder:(CPCoder)coder
{
var collection = [coder decodeObjectForKey:CPExpressionKey],
subpredicate = [coder decodeObjectForKey:CPSubpredicateKey],
variable = [coder decodeObjectForKey:CPVariableKey];
return [self initWithExpression:collection usingIteratorVariable:variable predicate:subpredicate];
}
- (void)encodeWithCoder:(CPCoder)coder
{
[coder encodeObject:_collection forKey:CPExpressionKey];
[coder encodeObject:_subpredicate forKey:CPSubpredicateKey];
[coder encodeObject:[variableExpression variable] forKey:CPVariableKey];
}
@end
+14 -10
View File
@@ -12,7 +12,7 @@
{
[super initWithExpressionType:CPVariableExpressionType];
_variable = [variable copy];
return self;
}
@@ -20,10 +20,10 @@
{
if (self == object)
return YES;
if (object.isa != self.isa || [object expressionType] != [self expressionType] || ![[object variable] isEqualToString:[self variable]])
return NO;
return YES;
}
@@ -34,7 +34,9 @@
- (id)expressionValueWithObject:object context:(CPDictionary)context
{
return [context objectForKey:_variable];
var expression = [self _expressionWithSubstitutionVariables:context];
return [expression expressionValueWithObject:object context:context];
}
- (CPString)description
@@ -44,12 +46,14 @@
- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables
{
var aconstant = [variables objectForKey:_variable];
if (aconstant != nil)
return [CPExpression expressionForConstantValue:aconstant];
return self;
var value = [variables objectForKey:_variable];
if (value == nil)
[CPException raise:CPInvalidArgumentException reason:@"Can't get value for '" + _variable + "' in bindings" + variables];
if ([value isKindOfClass:[CPExpression class]])
return value;
return [CPExpression expressionForConstantValue:value];
}
@end
+38 -5
View File
@@ -62,7 +62,7 @@
[self assertNotNull:expression_aggregate message:"Aggregate Expression should not be nil"];
var expression_subquery = [CPExpression expressionForSubquery:expression_collection usingIteratorVariable:@"self" predicate:[CPPredicate predicateWithValue:YES]];
// [self assertNotNull:expression_subquery message:"Subquery Expression should not be nil"];
[self assertNotNull:expression_subquery message:"Subquery Expression should not be nil"];
var set = [CPExpression expressionForConstantValue:[CPSet setWithObjects:@"a",@"b",@"c"]];
var array = [CPExpression expressionForConstantValue:[CPArray arrayWithObjects:@"a",@"b",@"d"]];
@@ -112,12 +112,34 @@
- (void)testVariableExpressionEvaluation
{
var variable_exp = [CPExpression expressionForVariable:@"variable"];
var pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForKeyPath:@"Record1.Age"] rightExpression:variable_exp modifier:CPDirectPredicateModifier type:CPGreaterThanPredicateOperatorType options:0];
// Replace with constant
var expression = [CPExpression expressionForVariable:@"variable"],
bindings = [CPDictionary dictionaryWithObject:20 forKey:@"variable"],
eval = [expression expressionValueWithObject:@"variable" context:bindings];
[self assertTrue:(eval == 20) message:"'"+ eval + "' should be 20"];
var variables = [CPDictionary dictionaryWithObject:20 forKey:@"variable"];
// Replace with constant expression
bindings = [CPDictionary dictionaryWithObject:[CPExpression expressionForConstantValue:10] forKey:@"variable"];
eval = [expression expressionValueWithObject:nil context:bindings];
[self assertTrue:(eval == 10) message:"'"+ eval + "' should be 10"];
[self assertTrue:[pred evaluateWithObject:dict substitutionVariables:variables] message:"'"+ [pred description] + "' should be true"];
// Replace with keypath expression
bindings = [CPDictionary dictionaryWithObject:[CPExpression expressionForKeyPath:@"Record1.Age"] forKey:@"variable"];
eval = [expression expressionValueWithObject:dict context:bindings];
[self assertTrue:(eval == 34) message:"'"+ eval + "' should be 34"];
}
- (void)testSubqueryExpressionEvaluation
{
var collection = [CPExpression expressionForKeyPath:@"Record1.Children"],
iteratorVariable = @"x",
predicate = [CPPredicate predicateWithFormat:@"$x BEGINSWITH 'Kid'"];
var expression = [CPExpression expressionForSubquery:collection usingIteratorVariable:iteratorVariable predicate:predicate];
var eval = [expression expressionValueWithObject:dict context:nil];
var expected = [CPArray arrayWithObjects:"Kid1", "Kid2"];
[self assertTrue:([eval isEqual:expected]) message:"'"+ [expression predicateFormat] + "' result is "+ eval + "but should be " + expected];
}
- (void)testOptions
@@ -281,8 +303,19 @@
predicate = [CPPredicate predicateWithFormat: @"a BETWEEN {%f,%f}", 20, 40];
[self assertTrue:([predicate predicateOperatorType] == CPBetweenPredicateOperatorType) message:[predicate description] + " operator should be a CPBetweenPredicateOperatorType"];
// TEST Empty string
predicate = [CPPredicate predicateWithFormat: @"a CONTAINS \"\""];
[self assertNotNull:predicate message:[predicate description] + " should not be nil"];
// TEST variable
predicate = [CPPredicate predicateWithFormat: @"$x CONTAINS \"\""];
[self assertTrue:[[predicate leftExpression] expressionType] == CPVariableExpressionType message:"Left Expression should be a CPVariableExpressionType"];
/*
Variable in multiple paths will fail. Left exp should be FUNCTION(keyPathExp"keypath", valueForKey:, variableExp"variable")
predicate = [CPPredicate predicateWithFormat: @"keypath.$variable CONTAINS \"\""];
var type = [[predicate leftExpression] expressionType];
[self assertTrue: type == CPFunctionExpressionType message:"Left Expression should be a CPFunctionExpressionType is " + type];
*/
}
@end