• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saygoweb / anorm / 18483020557

14 Oct 2025 01:51AM UTC coverage: 84.208% (+3.7%) from 80.483%
18483020557

Pull #42

github

web-flow
Merge 081631070 into 025025bcf
Pull Request #42: Implement comprehensive join optimization system to solve N+1 query problem

845 of 960 new or added lines in 19 files covered. (88.02%)

66 existing lines in 3 files now uncovered.

1637 of 1944 relevant lines covered (84.21%)

15.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.31
/src/Relationship/Strategy/DataSizeEstimator.php
1
<?php
2

3
namespace Anorm\Relationship\Strategy;
4

5
/**
6
 * Estimates data transfer sizes for different relationship loading strategies
7
 *
8
 * This class provides methods to estimate the amount of data that would be
9
 * transferred for different loading strategies, helping to choose the most
10
 * efficient approach based on network and memory constraints.
11
 */
12
class DataSizeEstimator
13
{
14
    /** @var array Cache for average record counts and sizes */
15
    private static $cache = [];
16

17
    /**
18
     * Estimate data size for IN clause batch loading strategy
19
     *
20
     * @param object $relationship The relationship to analyze
21
     * @param int $sourceCount Number of source models
22
     * @return int Estimated data size in bytes
23
     */
24
    public function estimateInClauseDataSize($relationship, int $sourceCount): int
25
    {
26
        $avgRelatedRecords = $this->getAverageRelatedRecords($relationship);
7✔
27
        $avgRecordSize = $this->getAverageRecordSize($relationship->getRelatedModelClass());
7✔
28

29
        // IN clause loads full records for all related models
30
        $totalRelatedRecords = $sourceCount * $avgRelatedRecords;
7✔
31
        return (int) ($totalRelatedRecords * $avgRecordSize);
7✔
32
    }
33

34
    /**
35
     * Estimate data size for JOIN with field selection strategy
36
     *
37
     * @param object $relationship The relationship to analyze
38
     * @param int $sourceCount Number of source models
39
     * @param array|null $fieldSelection Specific fields to load
40
     * @return int Estimated data size in bytes
41
     */
42
    public function estimateJoinDataSize($relationship, int $sourceCount, ?array $fieldSelection = null): int
43
    {
44
        $avgRelatedRecords = $this->getAverageRelatedRecords($relationship);
9✔
45

46
        if ($fieldSelection === null) {
9✔
47
            // No field selection - estimate full record size
48
            $avgRecordSize = $this->getAverageRecordSize($relationship->getRelatedModelClass());
5✔
49
        } else {
50
            // Calculate size for selected fields only
51
            $avgRecordSize = $this->calculateSelectedFieldSize($fieldSelection);
5✔
52
        }
53

54
        // JOIN creates denormalized result set
55
        $cardinality = $relationship->getCardinality();
9✔
56
        if ($cardinality === 'one-to-many' || $cardinality === 'many-to-many') {
9✔
57
            // Each source record is duplicated for each related record
58
            $totalRows = $sourceCount * $avgRelatedRecords;
7✔
59
            $sourceRecordSize = $this->getAverageRecordSize($this->getSourceModelClass($relationship));
7✔
60
            $totalSize = $totalRows * ($sourceRecordSize + $avgRecordSize);
7✔
61
        } else {
62
            // one-to-one or many-to-one: no duplication
63
            $sourceRecordSize = $this->getAverageRecordSize($this->getSourceModelClass($relationship));
4✔
64
            $totalSize = $sourceCount * ($sourceRecordSize + $avgRecordSize);
4✔
65
        }
66

67
        return (int) $totalSize;
9✔
68
    }
69

70
    /**
71
     * Get average number of related records for a relationship
72
     *
73
     * @param object $relationship The relationship to analyze
74
     * @return float Average number of related records per source model
75
     */
76
    public function getAverageRelatedRecords($relationship): float
77
    {
78
        $cacheKey = 'avg_related_' . get_class($relationship) . '_' . $relationship->getRelatedModelClass();
13✔
79

80
        if (isset(self::$cache[$cacheKey])) {
13✔
81
            return self::$cache[$cacheKey];
12✔
82
        }
83

84
        // Default estimates based on relationship type
85
        $cardinality = $relationship->getCardinality();
4✔
86
        switch ($cardinality) {
87
            case 'one-to-one':
4✔
88
            case 'many-to-one':
4✔
89
                $average = 1.0;
2✔
90
                break;
2✔
91
            case 'one-to-many':
4✔
92
                $average = 5.0; // Conservative estimate
4✔
93
                break;
4✔
94
            case 'many-to-many':
1✔
95
                $average = 3.0; // Conservative estimate
1✔
96
                break;
1✔
97
            default:
NEW
98
                $average = 2.0;
×
99
        }
100

101
        self::$cache[$cacheKey] = $average;
4✔
102
        return $average;
4✔
103
    }
104

105
    /**
106
     * Get average record size for a model class
107
     *
108
     * @param string $modelClass The model class to analyze
109
     * @return int Average record size in bytes
110
     */
111
    public function getAverageRecordSize(string $modelClass): int
112
    {
113
        $cacheKey = 'avg_size_' . $modelClass;
13✔
114

115
        if (isset(self::$cache[$cacheKey])) {
13✔
116
            return self::$cache[$cacheKey];
13✔
117
        }
118

119
        // Default estimate based on typical model sizes
120
        // This could be enhanced to analyze actual table schemas
121
        $estimatedSize = 1024; // 1KB default estimate
5✔
122

123
        self::$cache[$cacheKey] = $estimatedSize;
5✔
124
        return $estimatedSize;
5✔
125
    }
126

127
    /**
128
     * Calculate data size for selected fields
129
     *
130
     * @param array $fieldSelection Array of field names to include
131
     * @return int Estimated size in bytes for selected fields
132
     */
133
    public function calculateSelectedFieldSize(array $fieldSelection): int
134
    {
135
        $totalSize = 0;
7✔
136

137
        foreach ($fieldSelection as $field) {
7✔
138
            // Estimate field sizes based on common patterns
139
            $fieldSize = $this->estimateFieldSize($field);
6✔
140
            $totalSize += $fieldSize;
6✔
141
        }
142

143
        return $totalSize;
7✔
144
    }
145

146
    /**
147
     * Estimate size of a single field based on its name and common patterns
148
     *
149
     * @param string $fieldName Name of the field
150
     * @return int Estimated size in bytes
151
     */
152
    private function estimateFieldSize(string $fieldName): int
153
    {
154
        // Common field size estimates
155
        $fieldName = strtolower($fieldName);
8✔
156

157
        if ($fieldName === 'id' || str_ends_with($fieldName, '_id')) {
8✔
158
            return 8; // Integer primary/foreign keys
8✔
159
        }
160

161
        if (str_contains($fieldName, 'name') || str_contains($fieldName, 'title')) {
8✔
162
            return 100; // Short text fields
7✔
163
        }
164

165
        if (str_contains($fieldName, 'description') || str_contains($fieldName, 'content')) {
4✔
166
            return 500; // Longer text fields
4✔
167
        }
168

169
        if (str_contains($fieldName, 'email')) {
3✔
170
            return 50; // Email addresses
2✔
171
        }
172

173
        if (str_contains($fieldName, 'date') || str_contains($fieldName, 'time')) {
3✔
174
            return 20; // Datetime fields
3✔
175
        }
176

177
        // Default field size
178
        return 50;
3✔
179
    }
180

181
    /**
182
     * Get the source model class from a relationship
183
     *
184
     * @param object $relationship The relationship instance
185
     * @return string Source model class name
186
     */
187
    private function getSourceModelClass($relationship): string
188
    {
189
        // This would need to be implemented based on how relationships
190
        // store their source model information
191
        // For now, return a default estimate
192
        return 'DefaultModel';
10✔
193
    }
194

195
    /**
196
     * Clear the estimation cache
197
     * Useful for testing or when model characteristics change
198
     */
199
    public static function clearCache(): void
200
    {
201
        self::$cache = [];
2✔
202
    }
203

204
    /**
205
     * Set a cached value for testing or manual optimization
206
     *
207
     * @param string $key Cache key
208
     * @param mixed $value Value to cache
209
     */
210
    public static function setCacheValue(string $key, $value): void
211
    {
212
        self::$cache[$key] = $value;
1✔
213
    }
214
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc