Just to add that it works like a charm, even with recursive queries. I have a classical parent->child relationship (website structure) and I am able to retrieve the whole structure, including translations (with fallback on a default one) in just _one_ query, this is really amazing :)
this is with https://gist.github.com/silenius/5e1c9ec7b138115d9f7271860adca2df and SQLAlchemy generates https://gist.github.com/silenius/2f9df00575daab6d009e43dff92a3902 On Fri, Jun 21, 2019 at 02:32:36AM -0700, Julien Cigar wrote: > > > On Thursday, June 20, 2019 at 4:24:14 PM UTC+2, Mike Bayer wrote: > > > > > > > > On Thu, Jun 20, 2019, at 8:39 AM, Julien Cigar wrote: > > > > > > > > On Wednesday, June 19, 2019 at 9:53:42 PM UTC+2, Mike Bayer wrote: > > > > > > > > On Wed, Jun 19, 2019, at 10:50 AM, Julien Cigar wrote: > > > > That's the (almost) final version if you're interrested: > > https://gist.github.com/silenius/568aca7545e1bc0400b53b6ec157807d > > > > > > great, I can't run it, so are you still getting any warnings about > > properties being overwritten ? > > > > > > > > yes, sorry about that, I'm not that familiar with the Declarative API (I'm > > still using the mapper())) > > > > I get a bunch of SAWarning but it works as expected: > > > > > > 2019-06-20 14:31:45,784 INFO [amnesia.translations][MainThread] > > SQLAlchemy after_configured handler _setup_translation called > > 2019-06-20 14:31:45,784 DEBUG [amnesia.translations][MainThread] Adding > > translation properties: <class 'amnesia.modules.folder.model.Folder'> to > > <class 'amnesia.modules.folder.translations.model.FolderTranslation'> > > /usr/home/jcigar/code/venvs/amnesiabbpf/lib/python3.6/site-packages/sqlalchemy/orm/mapper.py:1826: > > > > SAWarning: Property ContentTranslation.content on mapped class > > FolderTranslation->content_translation being replaced with new property > > FolderTranslation.content; the old property will be discarded > > % (self._props[key], self, prop) > > 2019-06-20 14:31:45,807 DEBUG [amnesia.translations][MainThread] Adding > > translation properties: <class 'amnesia.modules.document.model.Document'> > > to <class 'amnesia.modules.document.translations.model.DocumentTranslation'> > > /usr/home/jcigar/code/venvs/amnesiabbpf/lib/python3.6/site-packages/sqlalchemy/orm/mapper.py:1826: > > > > SAWarning: Property ContentTranslation.content on mapped class > > DocumentTranslation->document_translation being replaced with new property > > DocumentTranslation.content; the old property will be discarded > > % (self._props[key], self, prop) > > 2019-06-20 14:31:45,816 DEBUG [amnesia.translations][MainThread] Adding > > translation properties: <class 'amnesia.modules.event.model.Event'> to > > <class 'amnesia.modules.event.translations.model.EventTranslation'> > > /usr/home/jcigar/code/venvs/amnesiabbpf/lib/python3.6/site-packages/sqlalchemy/orm/mapper.py:1826: > > > > SAWarning: Property ContentTranslation.content on mapped class > > EventTranslation->event_translation being replaced with new property > > EventTranslation.content; the old property will be discarded > > % (self._props[key], self, prop) > > 2019-06-20 14:31:45,825 DEBUG [amnesia.translations][MainThread] Adding > > translation properties: <class 'amnesia.modules.file.model.File'> to <class > > 'amnesia.modules.file.translations.model.FileTranslation'> > > /usr/home/jcigar/code/venvs/amnesiabbpf/lib/python3.6/site-packages/sqlalchemy/orm/mapper.py:1826: > > > > SAWarning: Property ContentTranslation.content on mapped class > > FileTranslation->content_translation being replaced with new property > > FileTranslation.content; the old property will be discarded > > % (self._props[key], self, prop) > > > > > > What happens if you don't overwrite the "content" relationship each time > > and just leave the base one in place? the pattern you are doing there is > > not one that has explicit support. IIRC the "content" relationship you > > are adding looks just like the one that's there, except it is against more > > specific subclasses. I can see how that is valuable but it shouldn't have > > any real-world consequence...however if it does, like it is making > > lazyloads more specific perhaps, then I might want to refine that warning > > to allow for more specific relationships against subclasses. > > > > > > > Although I haven't tested all scenarios yet, it seems to work well when I'm > not overwriting the "content" relationship and leave the base one in place > ...! I'll report if anything breaks :) thanks! > > > > > > > > If you'd like to add this configuration to the SQLAlchemy test cases I > > could spend some time to make a clean one .. ? > > > > > > > > maybe, it would be more like enhancing the mapper to expect this pattern > > but yes there are also a lot of things that it implies. > > > > > > > > Also, I could also write some doc to add this configuration to the > > "Row-Limited Relationships with Window Functions" if you think it's > > worthwhile ..? > > > > > > > > im not sure since your use case for the "row-limited" part is that you > > want a LIMIT in the query which the existing example illustrates. > > > > > > > > > > > > On Wednesday, June 19, 2019 at 4:44:55 PM UTC+2, Julien Cigar wrote: > > > > > > > > On Wednesday, June 19, 2019 at 4:09:55 PM UTC+2, Mike Bayer wrote: > > > > > > > > On Wed, Jun 19, 2019, at 4:48 AM, Julien Cigar wrote: > > > > Thank you very much, it almost works ! > > > > I have one minor issue, as translation_cls is involved in joined load > > inheritance the select([translation_cls], ...) results in: > > > > SELECT > > content_translation.language_id AS language_id, > > content_translation.content_id AS content_id, > > content_translation.title AS title, > > content_translation.description AS description, > > content_translation.fts AS fts, > > document_translation.language_id AS language_id, > > document_translation.content_id AS content_id, > > document_translation.body AS body, > > (...) > > > > which lead to a 2019-06-18 17:47:04.498 CEST [51817] ERROR: column > > reference "content_id" is ambiguous at character 3155 > > for the foreign(_alias2.content_id) == content_cls.content_id, > > > > I could list and alias individual columns, but I wondered if SQLAlchemy > > could do this automatically ? > > > > > > for the joined inheirtance, you need to compose the SELECT against both > > tables individually with the JOIN you want between them, I would probably > > use something like > > > > select([MyClass]).select_from(MyClass.__mapper__.selectable) > > > > > > That's what I tried initially but it doesn't work in this case ("outer" > > query had ambigous column reference too) > > > > > > > > > > > > > > > > > > Cheers, > > Julien > > > > > > On Wednesday, June 19, 2019 at 7:32:24 AM UTC+2, Mike Bayer wrote: > > > > > > This test doesn't run yet because it looks like you need to have > > initializers for things like Content.current_translation, the setup_class > > fails right now because that isn't handled. > > > > In any case, adapting the window recipe from > > https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#row-limited-relationships-with-window-functions > > > > to the code here can be done directly: > > > > _alias = ( > > select( > > [ > > translation_cls, > > func.row_number() > > .over( > > order_by=[ > > desc( > > translation_cls.language_id > > == bindparam( > > None, > > callable_=lambda: current_locale(), > > type_=String(), > > ) > > ), > > desc( > > translation_cls.language_id > > == bindparam( > > None, > > callable_=lambda: fallback_locale(), > > type_=String(), > > ) > > ), > > ], > > partition_by=translation_cls.content_id, > > ) > > .label("index"), > > ] > > ) > > .where( > > and_( > > translation_cls.language_id.in_( > > ( > > bindparam( > > None, > > callable_=lambda: current_locale(), > > type_=String(), > > ), > > bindparam( > > None, > > callable_=lambda: fallback_locale(), > > type_=String(), > > ), > > ) > > ) > > ) > > ) > > .alias() > > ) > > > > _alias2 = aliased(translation_cls, _alias) > > > > content_mapper.add_properties( > > { > > "current_translation": relationship( > > _alias2, > > primaryjoin=and_( > > foreign(_alias2.content_id) == content_cls.content_id, > > _alias.c.index == 1, > > ), > > lazy="joined", > > uselist=False, > > innerjoin=True, > > viewonly=True, > > bake_queries=False, > > ), > > } > > ) > > > > > > On Tue, Jun 18, 2019, at 11:34 AM, Julien Cigar wrote: > > > > Hello Mike, > > > > As always thank you for your quick and useful reply. I might not need > > LATERAL but would be very interrested to see a solution with WINDOW > > functions .. > > > > I've added a small test case with some comment on what I'd like to > > achieve. Basically what I would like is to be able to select the "current" > > translation in one query, and that it is transparent (it's a Pyramid > > plugin), that's why I'm also using hybrid properties > > > > > > On Tuesday, June 18, 2019 at 3:38:36 PM UTC+2, Mike Bayer wrote: > > > > > > > > On Tue, Jun 18, 2019, at 6:21 AM, Julien Cigar wrote: > > > > Hello, > > > > I'm trying to add a 'read-only' relationship involving a subquery and I > > have some problems with remote and foreign sides, SQLAlchemy returns: > > > > sqlalchemy.exc.ArgumentError: Relationship Folder.current_translation > > could not determine any unambiguous local/remote column pairs based on join > > condition and remote_side arguments. Consider using the remote() > > annotation to accurately mark those elements of the join condition that are > > on the remote side of the relationship. > > > > I've copied my code here > > https://gist.github.com/silenius/e2c7fb393864b3f1fee8131336dd8b61 > > > > > > > > Any idea what could be wrong ..? > > > > Basically I have a "translation" table and I'd like to JOIN that table for > > the current translation or the "fallback" language, so something like: > > select c.id, t.* from content c JOIN LATERAL (select ct.* from > > content_translation ct WHERE ct.content_id=c.id order by > > ct.language_id='fr' desc, ct.language_id='en' desc limit 1) as t ON > > t.content_id = c.id WHERE c.id=4855; > > > > > > > > hiya - > > > > unfortunately JOIN LATERAL is not supported, at least in all cases, as the > > target of a relationship(), because loading such as lazy loading does not > > use JOIN at all, and it's not clear if the LATERAL construct would work > > with other forms of relationship loading as well. That is, it *might* > > work for some cases, though it's never been tested, and likely wont work > > for most/all cases. Also in your example I don't see any usage of the > > lateral() modifier on your subquery. > > > > It would be best to compose the ON clause of the join using more > > traditional methods, e.g. that the "right" side of the join is a subquery > > that does not use any correlation, and the ON clause relates the left and > > right sides together. > > > > Within the example given, the "primaryjoin" argument refers to the ON > > clause of a JOIN, so generally a subquery would not be located here. > > Additionally, "as_scalar()", which is being renamed to "scalar_subquery()", > > indicates that this query is a so-called "scalar" subquery that returns > > exactly one row in one column and therefore acts as a column expression to > > be used in the WHERE clause, but this would not work as an ON clause in a > > JOIN by itself unless it were equated to something (but again, you'd need > > LATERAL for correlation to work in the ON clause). > > > > Looking at the actual SQL you're looking for, everything about it seems to > > be fully "traditional" in how the join is composed except for the detail > > that you're trying to get the first row only that matches in > > content_translation. We have a recipe for row-limited relationships > > using window functions which will likely fit here directly, at > > https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#row-limited-relationships-with-window-functions > > > > . you'd want to add "uselist=False" to your relationship() if you are > > looking for a many-to-one style relationship. The window function works by > > including a "row number", eg. index of a row, partitioned against the > > groupings within which you'd want to be limiting, in this case it can > > perhaps be partition_by=[ct.content_id], and it then orders within those > > partitions where you could apply your interesting "lang='fr' desc, > > lang='en' desc" trick above. Then the limit is applied in the ON clause > > by asking for "partition.c.index == 1". > > > > if you can share rudimentary mappings I can show you the composition, > > although the example as given in the docs should translate fairly directly > > here. > > > > > > > > > > > > > > > > Thank you! > > > > Julien > > > > > > -- > > SQLAlchemy - > > The Python SQL Toolkit and Object Relational Mapper > > > > http://www.sqlalchemy.org/ > > > > To post example code, please provide an MCVE: Minimal, Complete, and > > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > > description. > > --- > > You received this message because you are subscribed to the Google Groups > > "sqlalchemy" group. > > To unsubscribe from this group and stop receiving emails from it, send an > > email to sqlal...@googlegroups.com. > > To post to this group, send email to sqlal...@googlegroups.com. > > Visit this group at https://groups.google.com/group/sqlalchemy. > > To view this discussion on the web visit > > https://groups.google.com/d/msgid/sqlalchemy/16dddc84-d927-44e0-95dc-da9f579da733%40googlegroups.com > > > > <https://groups.google.com/d/msgid/sqlalchemy/16dddc84-d927-44e0-95dc-da9f579da733%40googlegroups.com?utm_medium=email&utm_source=footer> > > . > > For more options, visit https://groups.google.com/d/optout. > > > > > > > > -- > > SQLAlchemy - > > The Python SQL Toolkit and Object Relational Mapper > > > > http://www.sqlalchemy.org/ > > > > To post example code, please provide an MCVE: Minimal, Complete, and > > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > > description. > > --- > > You received this message because you are subscribed to the Google Groups > > "sqlalchemy" group. > > To unsubscribe from this group and stop receiving emails from it, send an > > email to sqlal...@googlegroups.com. > > To post to this group, send email to sqlal...@googlegroups.com. > > Visit this group at https://groups.google.com/group/sqlalchemy. > > To view this discussion on the web visit > > https://groups.google.com/d/msgid/sqlalchemy/8bfe074c-0ab4-4e71-8b43-afd90a6743aa%40googlegroups.com > > > > <https://groups.google.com/d/msgid/sqlalchemy/8bfe074c-0ab4-4e71-8b43-afd90a6743aa%40googlegroups.com?utm_medium=email&utm_source=footer> > > . > > For more options, visit https://groups.google.com/d/optout. > > > > > > *Attachments:* > > > > - trans.py > > > > > > > > -- > > SQLAlchemy - > > The Python SQL Toolkit and Object Relational Mapper > > > > http://www.sqlalchemy.org/ > > > > To post example code, please provide an MCVE: Minimal, Complete, and > > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > > description. > > --- > > You received this message because you are subscribed to the Google Groups > > "sqlalchemy" group. > > To unsubscribe from this group and stop receiving emails from it, send an > > email to sqlal...@googlegroups.com. > > To post to this group, send email to sqlal...@googlegroups.com. > > Visit this group at https://groups.google.com/group/sqlalchemy. > > To view this discussion on the web visit > > https://groups.google.com/d/msgid/sqlalchemy/337cb354-4bd5-43f5-aee8-9ecce97a31f1%40googlegroups.com > > > > <https://groups.google.com/d/msgid/sqlalchemy/337cb354-4bd5-43f5-aee8-9ecce97a31f1%40googlegroups.com?utm_medium=email&utm_source=footer> > > . > > For more options, visit https://groups.google.com/d/optout. > > > > > > > > -- > > SQLAlchemy - > > The Python SQL Toolkit and Object Relational Mapper > > > > http://www.sqlalchemy.org/ > > > > To post example code, please provide an MCVE: Minimal, Complete, and > > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > > description. > > --- > > You received this message because you are subscribed to the Google Groups > > "sqlalchemy" group. > > To unsubscribe from this group and stop receiving emails from it, send an > > email to sqlal...@googlegroups.com. > > To post to this group, send email to sqlal...@googlegroups.com. > > Visit this group at https://groups.google.com/group/sqlalchemy. > > To view this discussion on the web visit > > https://groups.google.com/d/msgid/sqlalchemy/c54ac24d-933b-4d15-9991-5371bd8a28d3%40googlegroups.com > > > > <https://groups.google.com/d/msgid/sqlalchemy/c54ac24d-933b-4d15-9991-5371bd8a28d3%40googlegroups.com?utm_medium=email&utm_source=footer> > > . > > For more options, visit https://groups.google.com/d/optout. > > > > > > > > -- > > SQLAlchemy - > > The Python SQL Toolkit and Object Relational Mapper > > > > http://www.sqlalchemy.org/ > > > > To post example code, please provide an MCVE: Minimal, Complete, and > > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > > description. > > --- > > You received this message because you are subscribed to the Google Groups > > "sqlalchemy" group. > > To unsubscribe from this group and stop receiving emails from it, send an > > email to sqlal...@googlegroups.com <javascript:>. > > To post to this group, send email to sqlal...@googlegroups.com > > <javascript:>. > > Visit this group at https://groups.google.com/group/sqlalchemy. > > To view this discussion on the web visit > > https://groups.google.com/d/msgid/sqlalchemy/6139daed-fe74-479d-813c-5884ff7b01a5%40googlegroups.com > > > > <https://groups.google.com/d/msgid/sqlalchemy/6139daed-fe74-479d-813c-5884ff7b01a5%40googlegroups.com?utm_medium=email&utm_source=footer> > > . > > For more options, visit https://groups.google.com/d/optout. > > > > > > > > -- > SQLAlchemy - > The Python SQL Toolkit and Object Relational Mapper > > http://www.sqlalchemy.org/ > > To post example code, please provide an MCVE: Minimal, Complete, and > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > description. > --- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to sqlalchemy+unsubscr...@googlegroups.com. > To post to this group, send email to sqlalchemy@googlegroups.com. > Visit this group at https://groups.google.com/group/sqlalchemy. > To view this discussion on the web visit > https://groups.google.com/d/msgid/sqlalchemy/0f6149e4-c979-4494-86cc-df90e49b7b98%40googlegroups.com. > For more options, visit https://groups.google.com/d/optout. -- Julien Cigar Belgian Biodiversity Platform (http://www.biodiversity.be) PGP fingerprint: EEF9 F697 4B68 D275 7B11 6A25 B2BB 3710 A204 23C0 No trees were killed in the creation of this message. However, many electrons were terribly inconvenienced. -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. To view this discussion on the web visit https://groups.google.com/d/msgid/sqlalchemy/20190625150309.GE92534%40home.lan. For more options, visit https://groups.google.com/d/optout.
signature.asc
Description: PGP signature