Hi Wes,
I agree with what you are proposing below. Some more comments inline ...
> Hi Michael, thanks for reviewing.
>
> I agree, it is too much to overload the existing methods. I like your
> suggestion -- the only modification I would add is that the Query
> parameter come first as I think that would be slightly more
> readable/self-documenting.
>
> e.g. addSubquery(Query sub, String variableDeclaration, String
> candidateCollectionExpression)
Sounds good.
>
> Some minor points:
> addSubquery() in all its forms should throw an exception if ...
> -- sub.getPersistenceManager() != this.getPersistenceManager()
> -- sub has been closed
> -- "this" has been closed (duh)
> -- "this" has been made unmodifiable
> -- the derived type of the candidateCollectionExpression does not
> match the declared extent type of sub
> -- the variableDeclaration type does not match the declared return
> type of sub
I agree. Just a minor change: the element type of the
candidateCollectionExpression must be compatible with the candidate
class of the subquery.
>
> It would make things simpler if the IgnoreCache setting had to be the
> same for both as well. Or we might say the IgnoreCache value for the
> outer query overrides any subqueries.
Yes, the IgnoreCache value of the outer query should win.
>
> Also, while the candidates for the subquery will be overridden at the
> time when execute() is invoked on the outer query, it would be nice if
> that was non-mutating, though we should take some advice from
> implementers on what the following use case should do:
>
> Query avgSalary = pm.newQuery(Employee.class);
> avgSalary.setResult("avg(this.salary)");
> avgSalary.setCandidates(someEmployees); // for argument's sake
>
> // Invoke directly -- against someEmployees collection
> Float result1 = (Float) avgSalary.execute();
>
> Query aboveDeptAvg = pm.newQuery(Employee.class, "this.salary >
> avgSalary");
> aboveDeptAvg.addSubquery(avgSalary, "float avgSalary",
> "this.department.employees");
>
> // Invoke as subquery -- someEmployees collection is ignored
> Collection employees = (Collection) aboveDeptAvg.execute();
>
> // Now invoke the subquery directly again -- does this use someEmployees?
> Float result2 = (Float) avgSalary.execute();
>
> I would prefer that result1.equals(result2) -- this implies that the
> implementation must internally revert the candidate extent or
> collection for avgSalary after it is used as a subquery; for the spec
> it just means that using a Query as a subquery does not modify any of
> its own candidate settings.
Yes, I would prefer this, too. So let's see whether there are any issues
with this from the implementation point of view.
Thanks again for the feedback.
Regards Michael
>
> Wes
>
>
> Michael Bouschen wrote:
>
>> Hi Wes,
>>
>> thanks for the feedback, it's definitely not too late.
>>
>> I like your proposal. It allows subqueries being supported in both
>> the SSJDOQL and the query API. I like the the idea of explicitly
>> setting subquery's candidate collection by passing an expression of
>> the outer query (and use the same mechanism for the parameters of the
>> subquery). This solves the biggest problem I had with using a
>> separate Query instance for the subquery: now the subquery instance
>> is self-contained and compiles, because it does not explicitly use an
>> expression from the outer query.
>>
>> I'm just wondering whether we could find a more intuitive syntax,
>> because name(candidateExpression[,parameterExpression...]) looks more
>> like a method than a variable. Furthermore, as a corner case, it
>> might be possible that a query uses more than one subquery. All the
>> subqueries would have to be defined in a single call of
>> outer.declareVariables. So how about, if we introduce a new method
>> called addSubquery to bind a single subquery to the outer query. The
>> method takes separate arguments for the candidate collection
>> expression and the parameters (if any). Actually the parameter
>> handling could be very similar to the parameters of the execute call:
>> addSubquery(String variableDeclaration, Query subquery, String
>> candidateCollectionExpr);
>> addSubquery(String variableDeclaration, Query subquery, String
>> candidateCollectionExpr, String parameter);
>> addSubqueryWithArray(String variableDeclaration, Query subquery,
>> String candidateCollectionExpr, String[] parameters);
>> addSubqueryWithMap(String variableDeclaration, Query subquery,
>> String candidateCollectionExpr, Map parameters);
>>
>> Looking at the first example from below, the definition of the
>> subquery would be the same. The only line that changes is the
>> declareVariable call. It is replaced by:
>> q.addSubquery(""float averageSalary", sub,
>> "this.department.employees");
>>
>> Just for completeness we should add a method to clear the subqueries,
>> such that you can reuse the outer query and bind new subqueries for
>> another execute call:
>> clearSubqueries();
>>
>> What do you think?
>>
>> If we think the above is option I would come up with an updated
>> summary of JDOQL changes to support subqueries and updated version of
>> the sample queries.
>>
>> Regards Michael
>>
>>> I'm ridiculously late in responding to this thread but if I may be
>>> so bold, I'll make a further suggestion.
>>>
>>> I like everything about the proposed approach except the requirement
>>> that subquery definitions must resort to single-string JDOQL syntax,
>>> even when using the API-based methods. I think this introduces
>>> asymmetry and discourages reuse and modularity.
>>>
>>> I would really like to see the ability to map variables to
>>> (sub)Query objects. There are two new capabilities introduced in
>>> the SSJDOQL version, and my opinion is that the API should match
>>> these feature by feature. The two features are:
>>> (1) The ability for a subquery to use an expression defined on the
>>> outer query as its candidate set.
>>> (2) The ability for a subquery to use expressions defined on the
>>> outer query as parameters.
>>>
>>> Therefore, for parity, we need an API-based way to declare these
>>> mappings, so that subqueries can be assigned both their candidate
>>> collections and their parameters dynamically.
>>>
>>> I propose an overloaded version of declareVariables that allows
>>> mapping variable names used in the outer query to (sub)Query
>>> instances that are correlated with candidates and parameters.
>>>
>>> void declareVariables(String variableList, Query... subquery)
>>>
>>> The variable declaration syntax should be extended to allow
>>> parameterized variables of the form
>>> "name(candidateExpression[,parameterExpression...])". "name"
>>> defines a variable name in the query; "candidateExpression" defines
>>> an expression (rooted in the namespace of the outer query) for the
>>> candidate extent to be bound to the subquery, where "null" signifies
>>> that the subquery candidate set is not being limited.
>>> "parameterExpression" identifies dynamic values for parameters
>>> declared by the subquery, again rooted in the namespace of the outer
>>> query doing the binding.
>>>
>>> To touch up Michael's examples:
>>>
>>> Select employees who make more than the average of their department
>>> employees?
>>>
>>> Single-string JDOQL:
>>> SELECT FROM Employee WHERE this.salary > (SELECT AVG(e.salary) FROM
>>> this.department.employees e)
>>> Query API:
>>> Query q = pm.newQuery(Employee.class);
>>> q.setFilter("this.salary > averageSalary");
>>>
>>> // Subquery definition is generic: for a given set of Employees,
>>> return the average salary
>>> Query sub = pm.newQuery(Employee.class);
>>> sub.setResult("avg(salary)");
>>>
>>> // Bind the subquery to the master query by identifying the
>>> candidate set
>>> q.declareVariables("float averageSalary(this.department.employees)",
>>> sub);
>>>
>>>> Select employees who make more than the average of the employees in
>>>> their department at the same pay scale?
>>>
>>>
>>> Single-string JDOQL:
>>> SELECT FROM Employee WHERE this.salary >
>>> (SELECT AVG(e.salary) FROM this.department.employees e WHERE
>>> e.payScale == this.payScale)
>>>
>>> Query API:
>>> Query q = pm.newQuery(Employee.class);
>>> q.setFilter("this.salary > averageSalary");
>>>
>>> // This subquery generically defines the average salary of a set of
>>> Employees at a given PayScale
>>> Query sub = pm.newQuery(Employee.class);
>>> sub.setFilter("this.payScale == ps");
>>> sub.declareParameters("PayScale ps");
>>> sub.setResult("avg(salary)");
>>>
>>> // Bind both a candidate set and the payScale parameter.
>>> q.declareVariables("float averageSalary(this.department.employees,
>>> this.payScale)", sub);
>>>
>>>> Select employees who make more than the average of all employees?
>>>
>>>
>>> Single-string JDOQL:
>>> SELECT FROM Employee WHERE this.salary > (SELECT AVG(e.salary) FROM
>>> Employee e)
>>> SELECT FROM Employee WHERE this.salary > (SELECT AVG(this.salary)
>>> FROM Employee)
>>>
>>> Query API:
>>> Query q = pm.newQuery(Employee.class);
>>> q.setFilter("this.salary > averageSalary");
>>> Query sub = pm.newQuery(Employee.class);
>>> sub.setResult("avg(salary)");
>>> // The null value indicates that we're not overriding the candidates
>>> for the subquery
>>> // and thus it uses the entire extent of Employee
>>> q.declareVariables("float averageSalary(null)", sub);
>>>
>>>
>>>> Select employees named Joe who make more than the average of all
>>>> employees?
>>>
>>>
>>> Single-string JDOQL:
>>> SELECT FROM Employee WHERE this.name == 'Joe' && this.salary >
>>> (SELECT AVG(e.salary) FROM Employee e)
>>>
>>> Query API:
>>> Query q = pm.newQuery(Employee.class);
>>> q.setFilter("this.name == 'Joe' && this.salary > averageSalary");
>>>
>>> // This subquery generically defines "the average of all employeees"
>>> Query sub = pm.newQuery(Employee.class);
>>> sub.setResult("avg(salary)");
>>>
>>> // Note we could have reused the query instance from the previous
>>> example.
>>> q.declareVariables("float averageSalary(null)", sub);
>>>
>>>>
>>>> Select employees named Joe who make more than the average of all
>>>> employees at the same pay scale?
>>>
>>>
>>> Single-string JDOQL:
>>> SELECT FROM Employee WHERE this.name == 'Joe' && this.salary >
>>> (SELECT AVG(e.salary) FROM Employee e WHERE e.payScale ==
>>> this.payScale)
>>>
>>> Query API:
>>> Query q = pm.newQuery(Employee.class);
>>> q.setFilter("this.name == 'Joe' && this.salary > averageSalary");
>>>
>>> // Note that this is the same subquery instance as the previous pay
>>> scale example
>>> Query sub = pm.newQuery(Employee.class);
>>> sub.setFilter("payScale == ps");
>>> sub.declareParameters("PayScale ps");
>>> sub.setResult("avg(salary)");
>>>
>>> q.declareVariables("float averageSalary(null)", sub);
>>>
>>>> Select employees who make more than the average of all employees at
>>>> the same pay scale?
>>>
>>>
>>> Single-string JDOQL:
>>> SELECT FROM Employee WHERE this.salary > (SELECT AVG(e.salary) FROM
>>> Employee e WHERE e.payScale == this.payScale)
>>>
>>> Query API:
>>> Query q = pm.newQuery(Employee.class);
>>> q.setFilter("this.salary > averageSalary");
>>>
>>> // Same again
>>> Query sub = pm.newQuery(Employee.class);
>>> sub.setFilter("ps == this.payScale");
>>> sub.declareParameters("PayScale ps");
>>>
>>> q.declareVariables("float averageSalary(null, this.payScale)", sub);
>>>
>>> I'd like to hear other ideas for the exact syntax, but what do you
>>> think of the general concept?
>>>
>>> Wes
>>>
>>>
> [chop]
--
Michael Bouschen Tech@Spree Engineering GmbH
mailto:mbo.tech@spree.de http://www.tech.spree.de/
Tel.:++49/30/235 520-33 Buelowstr. 66
Fax.:++49/30/2175 2012 D-10783 Berlin
|