This is being discussed in a different thread and it was suggested I  
put this up in its own brightly lit thread so that people can notice it.

now that the Query object in 0.3 has merged an enhanced set of  
capabilities from the SelectResults extension, namely that it behaves  
in a "generative" fashion whereby criteria and other modifiers are  
applied via filter(), filter_by(), join(), order_by(), etc., its time  
to create "one and preferably only one obvious way to do it" for  
Query.   the current documentation at "Basic Data Mapping" spends a  
lot of effort explaining the dichotomy between "generative" and "non- 
generative" methods, being able to say query.select() which returns  
results immediately, or query.filter().list() which allows  
generation.   I spent a lot of time getting that doc to make some  
semblance of sense but it was pretty clear that the API had become  
muddied, with a better idea moving in and the older idea seeming kind  
of redundant.

the details of the proposal would be:

- the methods select(), selectfirst(), selectone(), select_by(),  
selectfirst_by(), selectone_by() and get_by() would be deprecated.   
this means they will remain present on the Query object but the  
documentation would be reorganized to talk only about filter(),  
filter_by(), list(), scalar(), and a new method called one() which is  
like scalar() but ensures that only one row was returned.  By  
deprecating the methods but retaining their exact behavior, the  
majority of upgrade paths for those still using these methods is  
painless.  the methods would remain present until version 0.5.

- the ability to place ClauseElements at the start of filter_by()  
would be removed.

- filter_by() would gain a new positional argument, which is  
optional, which is either a string or list of strings indicating  
property names, indicating a join path.  when this argument is  
present, a self-contained join among those mapped properties will be  
generated, and the keyword criterion of the filter_by() would be  
expressed against the endpoint of that join.  by "self-contained", i  
mean that the tables used in the join will be *aliased* anonymously,  
so that different filter_by()s which specify join paths that overlap  
can be used together.  the existing behavior of being able to say  
query.join(['x', 'y', 'z']).filter_by(**kwargs) remains as well (but  
is different in that it doesnt create aliases).

- for execution of a completely literal text statement, as well as a  
fully constructed select() object, a method query.from_statement()  
will be added, and the older select_text() method (that i dont think  
anyone knows about) will be deprecated like the others.  executions  
would look like query.from_statement("select * from table").scalar()  
as well as query.from_statement(sometable.select()).list()

- the behavior of join() changes slightly, such that each call to join 
() will reset the "joinpoint" of the resulting query back to the  
beginning before creating the join.  this means that join() will  
always build its joins from the original queried class. join(None)  
resets the joinpoint back to the beginning for subsequent filter()/ 
filter_by() calls.

- assignmapper would also keep all of its current methods with  
regards to selecting/filtering.  it seems like tools like Elixir are  
going to move away from assignmapper anyway which is a good thing.

- the behavior of select_by(), get_by(), join(), filter_by(), and  
others right now is such that when you name an attribute which is not  
immediately present on the queried class, a search is performed  
through all the mapped properties of the class, into its subclasses,  
until it finds a property of that name, upon which it constructs a  
join from the initial table to the target table, adding in a  
comparison criterion for the key selected.  this feature would remain  
in the methods select_by() and the other deprecated _by() methods,  
but would be removed from filter_by() as well as join().

- for those who have built MapperExtensions into select() and  
select_by(), we'll put some MapperExtension hooks into list() which  
is the execution point for queries.

- SelectResults remains deprecated until 0.5 when its also removed.

and now for the reasons:

- the select() methods are now redundant in light of the new  
generative methods.  all of the various flags which you can use with  
select() are present generatively, i.e. order_by(), limit(),  
with_lockmode(), filter(), select_from(), etc.  By favoring the  
generative methods we get a more flexible interface and only one way  
to do it.

- by removing the word "select" from Query's API entirely, the  
timeless confusion of "sql.select()? or query.select()?" goes away.   
the identifier "select" at last loses its ambiguity.  this is a big win.

- the "multi-dispatch" behavior of select() is also little known and  
confusing.  Did you know that select() can take not just a "where"  
criterion, but also a fully constructed select() object ?  neither  
did anyone else.   now we'll have from_statement() to express "ive  
constructed a full statement and dont want any statement compilation  
to occur".

- join() will reset the join point before joining because then you  
can build multiple joins starting from the root of the query chain.   
so query.join('a').join('b') builds two joins against the root table,  
query.join(['a', 'b']) builds a join from root to 'a' to 'b'.   the  
meaning of "joinpoint" is that any subsequent filter_by() call will  
construct its criterion against the endpoint of the join most  
recently constructed.  the new approach doesnt remove any  
capabilities but adds the capability for multiple joins off the  
root.  a join of join(None) brings the joinpoint back to the beginning.

- the ability to place ClauseElements inside of select_by() stems  
from the need to query based on keyword criterion *as well as* table  
based criterion at the same time.  but now that we have generative  
behavior, you can just use individual filter() and filter_by()  
methods to combine those.  The "multiple-use" nature of various  
methods, which is also very confusing (and hard to document), is not  
necessary with the generative approach.  So it will be removed from  
filter_by(), where Im hoping people havent been putting  
ClauseElements inside of too much.  (the deprecated methods of course  
keep it for backwards compatibility).

- the new "positional" argument of filter_by() is based on Gaetan's  
idea, as well as some light that was shed on the IRC channel.  it  
looks like this:

   session.query(User).filter_by(['orders', 'items', 'keywords'],  
keyword_name='foo').filter_by(['orders', 'items'],  
item_price=49.50).list()

above, we have created two joins from the "users" table, one of which  
joins from 'orders' to 'items' to 'keywords', and the other from  
'orders' to 'items'.  the two sets of joins are constructed using  
anonymous aliases, so that you get all users who have purchased an  
item that has the 'foo' keyword, and have also purchased an item that  
costs 49.50.  the two paths across 'orders' and 'items' are isolated  
from each other.

if aliases were not used above, the redundant 'orders' and 'items'  
tables would converge into one FROM clause each in the final  
statement since thats the behavior of select(), but then the  
semantics of the query would then be "all users who purchased items  
that cost 49.50 and also have the 'foo' keyword".

the above capability is currently available by constructing your own  
Alias object and building join criterion manually.  But it was  
pointed out on IRC that the Django ORM constructs filtering queries  
using aliases as above.  for a lot of cases the approach saves a lot  
of lines of code spent creating manual aliases.

- "auto-join": In the docs, the example of select_by() searching  
through all mapped properties looking for a match is given in the  
many-to-many page with something like session.query(Article).select_by 
(keyword_name='foo'), where a join is constructed from "articles" to  
"article_keywords", to "keywords" and then adding criterion  
"keywords.keyword_name='foo'".   It was known since day one, and  
eventually pointed out by a few users in case it wasnt known (which  
it was),  that this feature does not have "deterministic" behavior.   
if multiple attribute names of "keyword_name" exist, its going to  
pick the first one that it finds, and all subsequent occurences are  
forever ignored.   if mapped properties happen to be organized  
differently, then you get a different result - or, if you are  
querying from a different point in the "join" chain, the sudden  
presence of the same attribute name elsewhere suddenly changes the  
result.  the reason this feature was created, and why its stuck  
around, is that before we added generative behavior, there was really  
no way to join among properties without spelling out the join  
explicitly using tables.   the anemic methods join_via() and join_to 
() were added to assist in constructing these joins but I knew from  
the beginning i didnt like those.  but with generative behavior, we  
got join(), and now we even get the enhanced filter_by() idea.   so  
the "automatic join" idea isnt needed as there are now explicit ways  
to construct a join as desired.  again, hoping people arent relying  
on this with filter_by() or join() already (the deprecated methods of  
course keep it for backwards compatibility).

Cons:

upgrade path:  the only pain in this upgrade path is if you are using  
filter_by() with either ClauseElements or with "auto-join", using join 
() with "auto-join", or using multiple join()'s strung together to  
build a single join from the root.  filter_by() and join() are fairly  
new methods so i dont think their adoption is so deep as of yet, and  
also the features to be removed aren't so intuitive with them anyway  
so im betting they arent used much.  any current use of select(),  
select_by(), etc. would all continue to work identically.

more typing:  it is not as succinct to say query.filter_by(id=10).one 
(), instead of query.get_by(id=10).  but removing seven redundant  
methods which are not as flexible, making the purpose of each method  
much more focused, and taking out five or six paragraphs out of the  
docs thereby leaving room for deeper exposition of whats there, seems  
to be worth it.


so......whattaya say ?



--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalchemy@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to