@@ -1702,4 +1702,226 @@ def test_date_filtering_with_group_by_count
17021702 end
17031703 end
17041704 end
1705+
1706+ def test_pointer_constraint_aggregation
1707+ skip "Docker integration tests require PARSE_TEST_USE_DOCKER=true" unless ENV [ 'PARSE_TEST_USE_DOCKER' ] == 'true'
1708+
1709+ with_parse_server do
1710+ with_timeout ( 25 , "pointer constraint aggregation test" ) do
1711+ puts "\n === Testing Pointer Constraint Aggregation ==="
1712+
1713+ # Create test data for pointer constraint testing
1714+ user1 = AggregateTestUser . new ( name : "Pointer User 1" , age : 28 , city : "Boston" , active : true )
1715+ user2 = AggregateTestUser . new ( name : "Pointer User 2" , age : 32 , city : "Seattle" , active : true )
1716+ user3 = AggregateTestUser . new ( name : "Pointer User 3" , age : 25 , city : "Denver" , active : false )
1717+
1718+ assert user1 . save , "User 1 should save successfully"
1719+ assert user2 . save , "User 2 should save successfully"
1720+ assert user3 . save , "User 3 should save successfully"
1721+
1722+ # Create posts with different authors
1723+ post1 = AggregateTestPost . new ( title : "Post by User 1" , author : user1 , category : "tech" , likes : 100 )
1724+ post2 = AggregateTestPost . new ( title : "Another Post by User 1" , author : user1 , category : "design" , likes : 75 )
1725+ post3 = AggregateTestPost . new ( title : "Post by User 2" , author : user2 , category : "tech" , likes : 120 )
1726+ post4 = AggregateTestPost . new ( title : "Post by User 3" , author : user3 , category : "writing" , likes : 50 )
1727+
1728+ assert post1 . save , "Post 1 should save successfully"
1729+ assert post2 . save , "Post 2 should save successfully"
1730+ assert post3 . save , "Post 3 should save successfully"
1731+ assert post4 . save , "Post 4 should save successfully"
1732+
1733+ puts "Created test data: 3 users, 4 posts with pointer relationships"
1734+
1735+ # Test 1: Filter by specific user pointer, then group by category
1736+ puts "\n --- Test 1: where(author: user).group_by(:category).count ---"
1737+ puts "Target user ID: #{ user1 . id } "
1738+
1739+ # First verify basic where query works
1740+ posts_by_user1 = AggregateTestPost . where ( author : user1 ) . all
1741+ puts "Direct where query found: #{ posts_by_user1 . length } posts by user1"
1742+ posts_by_user1 . each do |post |
1743+ puts " - #{ post . title } (#{ post . category } )"
1744+ end
1745+
1746+ # Show the aggregation pipeline that will be generated
1747+ puts "\n --- Debugging: Pipeline generation ---"
1748+ pipeline = AggregateTestPost . where ( author : user1 ) . group_by ( :category ) . pipeline
1749+ puts "Generated pipeline:"
1750+ puts JSON . pretty_generate ( pipeline )
1751+
1752+ # Check the exact format of the pointer constraint in the match stage
1753+ match_stage = pipeline . find { |stage | stage . key? ( "$match" ) }
1754+ if match_stage
1755+ match_conditions = match_stage [ "$match" ]
1756+ puts "\n Match stage conditions:"
1757+ match_conditions . each do |field , condition |
1758+ puts " #{ field } : #{ condition . inspect } (#{ condition . class } )"
1759+ end
1760+
1761+ # Look for author constraint specifically
1762+ author_constraint = match_conditions [ "author" ] || match_conditions [ "_p_author" ]
1763+ if author_constraint
1764+ puts "Author constraint found: #{ author_constraint . inspect } (#{ author_constraint . class } )"
1765+ else
1766+ puts "WARNING: No author constraint found in match stage"
1767+ end
1768+ end
1769+
1770+ begin
1771+ result = AggregateTestPost . where ( author : user1 ) . group_by ( :category ) . count
1772+
1773+ puts "\n Pointer constraint aggregation executed successfully!"
1774+ puts "Result type: #{ result . class } "
1775+ puts "Result: #{ result . inspect } "
1776+
1777+ if result . is_a? ( Hash )
1778+ assert !result . empty? , "Should find posts by user1"
1779+
1780+ # Verify we get the expected categories
1781+ expected_categories = [ "tech" , "design" ] # user1 has posts in these categories
1782+ result . keys . each do |category |
1783+ assert expected_categories . include? ( category ) , "Found unexpected category: #{ category } "
1784+ end
1785+
1786+ # Total should match direct query results
1787+ total_count = result . values . sum
1788+ assert total_count == posts_by_user1 . length , "Aggregation count should match direct query: expected #{ posts_by_user1 . length } , got #{ total_count } "
1789+
1790+ puts "✅ Pointer constraint aggregation works correctly"
1791+ else
1792+ flunk "Expected Hash result, got #{ result . class } : #{ result . inspect } "
1793+ end
1794+
1795+ rescue => e
1796+ puts "\n ❌ Pointer constraint aggregation failed: #{ e . class } : #{ e . message } "
1797+ puts "This confirms the issue with pointer constraints in aggregation pipelines"
1798+
1799+ # Let's also test with the raw pipeline to see if Parse Server accepts it
1800+ puts "\n --- Testing raw pipeline execution ---"
1801+ begin
1802+ raw_result = AggregateTestPost . new . client . aggregate_pipeline ( "AggregateTestPost" , pipeline )
1803+ puts "Raw pipeline result: #{ raw_result . results &.inspect || raw_result . inspect } "
1804+
1805+ if raw_result . results . is_a? ( Array ) && raw_result . results . empty?
1806+ puts "Raw pipeline returned empty results - pointer constraint format issue confirmed"
1807+ end
1808+ rescue => raw_e
1809+ puts "Raw pipeline also failed: #{ raw_e . class } : #{ raw_e . message } "
1810+ end
1811+
1812+ flunk "Pointer constraint aggregation should work: #{ e . class } : #{ e . message } "
1813+ end
1814+
1815+ # Test 2: Multiple pointer constraints
1816+ puts "\n --- Test 2: Multiple constraints including pointer ---"
1817+
1818+ begin
1819+ result2 = AggregateTestPost . where ( author : user1 , :likes . gte => 80 ) . group_by ( :category ) . count
1820+
1821+ puts "Multiple constraint result: #{ result2 . inspect } "
1822+
1823+ # Should only include posts by user1 with likes >= 80
1824+ if result2 . is_a? ( Hash )
1825+ total_count = result2 . values . sum
1826+ expected_posts = posts_by_user1 . select { |p | p . likes >= 80 }
1827+ assert total_count == expected_posts . length , "Should match posts with likes >= 80"
1828+
1829+ puts "✅ Multiple constraints including pointer work correctly"
1830+ end
1831+
1832+ rescue => e
1833+ puts "Multiple constraints failed: #{ e . class } : #{ e . message } "
1834+ end
1835+
1836+ # Test 3: Test the exact failing pattern from user's example
1837+ puts "\n --- Test 3: Test exact failing patterns ---"
1838+
1839+ # Pattern 1: Membership.where(role: x, active: true).group_by(:project).count
1840+ # We'll simulate with Post.where(author: x, category: y).group_by(:author).count
1841+ begin
1842+ simulated_result = AggregateTestPost . where ( author : user1 , category : "tech" ) . group_by ( :author ) . count
1843+ puts "Simulated membership pattern result: #{ simulated_result . inspect } "
1844+
1845+ if simulated_result . is_a? ( Hash ) && !simulated_result . empty?
1846+ puts "✅ Simulated membership pattern works"
1847+ elsif simulated_result . is_a? ( Hash ) && simulated_result . empty?
1848+ puts "❌ Simulated membership pattern returned empty results"
1849+ end
1850+ rescue => e
1851+ puts "Simulated membership pattern failed: #{ e . class } : #{ e . message } "
1852+ end
1853+
1854+ # Test 4: Debug the internal pointer format vs expected format
1855+ puts "\n --- Test 4: Pointer format debugging ---"
1856+
1857+ # Check what format Parse Server expects vs what we're sending
1858+ manual_pipeline = [
1859+ {
1860+ "$match" => {
1861+ "_p_author" => "_AggregateTestUser$#{ user1 . id } " # MongoDB internal format
1862+ }
1863+ } ,
1864+ {
1865+ "$group" => {
1866+ "_id" => "$category" ,
1867+ "count" => { "$sum" => 1 }
1868+ }
1869+ }
1870+ ]
1871+
1872+ puts "Manual pipeline with _p_author:"
1873+ puts JSON . pretty_generate ( manual_pipeline )
1874+
1875+ begin
1876+ manual_result = AggregateTestPost . new . client . aggregate_pipeline ( "AggregateTestPost" , manual_pipeline )
1877+ puts "Manual _p_author result: #{ manual_result . results &.inspect || 'nil' } "
1878+
1879+ if manual_result . results &.any?
1880+ puts "✅ _p_author format works in aggregation"
1881+ else
1882+ puts "❌ _p_author format also fails"
1883+ end
1884+ rescue => e
1885+ puts "Manual _p_author pipeline failed: #{ e . class } : #{ e . message } "
1886+ end
1887+
1888+ # Try with Parse API format
1889+ manual_pipeline2 = [
1890+ {
1891+ "$match" => {
1892+ "author" => {
1893+ "__type" => "Pointer" ,
1894+ "className" => "AggregateTestUser" ,
1895+ "objectId" => user1 . id
1896+ }
1897+ }
1898+ } ,
1899+ {
1900+ "$group" => {
1901+ "_id" => "$category" ,
1902+ "count" => { "$sum" => 1 }
1903+ }
1904+ }
1905+ ]
1906+
1907+ puts "\n Manual pipeline with Parse Pointer format:"
1908+ puts JSON . pretty_generate ( manual_pipeline2 )
1909+
1910+ begin
1911+ manual_result2 = AggregateTestPost . new . client . aggregate_pipeline ( "AggregateTestPost" , manual_pipeline2 )
1912+ puts "Manual Parse Pointer result: #{ manual_result2 . results &.inspect || 'nil' } "
1913+
1914+ if manual_result2 . results &.any?
1915+ puts "✅ Parse Pointer format works in aggregation"
1916+ else
1917+ puts "❌ Parse Pointer format also fails"
1918+ end
1919+ rescue => e
1920+ puts "Manual Parse Pointer pipeline failed: #{ e . class } : #{ e . message } "
1921+ end
1922+
1923+ puts "\n ✅ Pointer constraint aggregation test completed (debugging results above)"
1924+ end
1925+ end
1926+ end
17051927end
0 commit comments