propelorm / Propel2

Propel2 is an open-source high-performance Object-Relational Mapping (ORM) for modern PHP
http://propelorm.org/
MIT License
1.26k stars 399 forks source link

Bug by query with table relationships one to n #1836

Closed wundii closed 1 year ago

wundii commented 2 years ago

At this point, problems arise if there is a one to n relationship to its table (tableOne -> tableN). If you build a query from tableOne and now add a withColumn(virtualColumn), all the different ones at tableN will get the same content because the same ObjectId is always fetched by the InstancePoolTrait. Since in my view the ObjectFormatter->format is only used for the find methods, a clone would be a good bug fix for one to n relationships.

user1 (baseball & football) user2 (baseball)

UserOneQuery::create()->useHobbyManyQuery()->endUse()->withColumn(HobbyManyTableMap::COL_HOBBY)->find(); result: -user1 | football -user1 | football -user2 | baseball

** Update I have found an alternative to the clone, Propel::disableInstancePooling(). I am now a little unsure which is the better approach for the problem described.

mringler commented 2 years ago

Hey wundii,

that is an interesting issue. I think you ran into a structural problem in Propel here.

In general, it is desired behavior that Propel gives you the same object for the same row. You do not want different objects representing the same row in the database, because then a change to the first object is not reflected in the other object, meaning that if you change the first object and save it, then save object 2, the changes you made through object 1 will be overridden (it is a bit more complicated in Propel due to change detection, but you get the idea). This is not desirable. Disabling instance pooling or cloning objects is almost guaranteed to give you the weirdest anomalies somewhere down the line.

I think the "correctish" behavior of withColumn() would be to return an array in your case. Seems to me the result of these statements should be the same:

        $authorCollection1 = AuthorQuery::create()
        ->filterById(2949)
        ->joinWithBook() // <-------- join whole table
        ->find();

        var_export($authorCollection1->count());  // 1
        var_export($authorCollection1[0]->getBooks()->getColumnValues('Id')); // [3554, 3555]

        $authorCollection2 = AuthorQuery::create()
            ->filterById(2949)
            ->joinBook()->withColumn('Book.Id', 'BookIdsOnly') // <------------- join column of table
            ->find();

        var_export($authorCollection2->count());  // 2 <------ debatable 
        var_export($authorCollection2[0]->getVirtualColumn('BookIdsOnly')); // 3555 <----------- not 3554, issue of ticket, but I think should be  [3554, 3555],
        var_export($authorCollection2[1]->getVirtualColumn('BookIdsOnly')); // 3555

Throwing an exception that withColumn() does not work with a one-to-many relation would also be a valuable change.

On a side note, this here makes sense considering above, but without context, it is somewhat confusing:

        $authorCollection3 = AuthorQuery::create()
            ->useBookQuery()
                ->filterByAuthorId(2949)
            ->endUse()
        ->find();
        var_export($authorCollection3->count()); // 2
        var_export($authorCollection3[0]->getBooks()->getColumnValues('Id')); // [3554, 3555],
        var_export($authorCollection3[1]->getBooks()->getColumnValues('Id')); // [3554, 3555],

Not sure if it makes sens that a Query object can return more than one object for a row in the table. Results with duplicates are usually not usable.

tl;dr I think cloning the result is not a good idea, changing withColumn() to return an array is better, but just throwing an exception would also be a valuable change. What do you think?

codecov-commenter commented 2 years ago

Codecov Report

Merging #1836 (dd54040) into master (a9b02ae) will decrease coverage by 20.84%. The diff coverage is 75.00%.

Impacted file tree graph

@@              Coverage Diff              @@
##             master    #1836       +/-   ##
=============================================
- Coverage     87.69%   66.85%   -20.85%     
- Complexity     7759     7760        +1     
=============================================
  Files           282      282               
  Lines         21315    21232       -83     
=============================================
- Hits          18693    14195     -4498     
- Misses         2622     7037     +4415     
Flag Coverage Δ
5-max 66.85% <75.00%> (-20.85%) :arrow_down:
7.4 66.85% <75.00%> (-20.85%) :arrow_down:
agnostic 66.85% <75.00%> (+<0.01%) :arrow_up:
mysql ?
pgsql ?
sqlite ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
src/Propel/Runtime/Formatter/ObjectFormatter.php 68.49% <75.00%> (-22.42%) :arrow_down:
src/Propel/Runtime/ActiveQuery/PropelQuery.php 0.00% <0.00%> (-100.00%) :arrow_down:
.../Propel/Generator/Command/templates/propel.yml.php 0.00% <0.00%> (-100.00%) :arrow_down:
.../Propel/Generator/Command/templates/schema.xml.php 0.00% <0.00%> (-100.00%) :arrow_down:
...el/Generator/Command/templates/propel.yml.dist.php 0.00% <0.00%> (-100.00%) :arrow_down:
...el/Runtime/Validator/Constraints/DateValidator.php 0.00% <0.00%> (-100.00%) :arrow_down:
...Generator/Manager/templates/migration_template.php 0.00% <0.00%> (-100.00%) :arrow_down:
...nerator/Builder/Om/TableMapLoaderScriptBuilder.php 0.00% <0.00%> (-100.00%) :arrow_down:
...ator/Builder/Om/templates/tableMapLoaderScript.php 0.00% <0.00%> (-100.00%) :arrow_down:
...tor/Behavior/Validate/templates/objectValidate.php 0.00% <0.00%> (-100.00%) :arrow_down:
... and 140 more

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update a9b02ae...dd54040. Read the comment docs.

wundii commented 2 years ago

I also think that this is a structural problem at Propel. :( To solve this problem, one would have to make major changes, which may not make sense at the moment.

In my current example, the primary idea is a pure readout via Propel for AgGrid and I can live with the deactivation of InstancePooling in this case. In general, I would say that almost all queries with Propel and withColumn are intended for pure reading. From this point of view, I would put an exception in there. This raises the question of where this should best be built in.

I have written an idea in the ObjectFormatter.

dereuromark commented 2 years ago

Shall we close this then?