@@ -989,16 +989,9 @@ public function find(array $queries = []): array
989989 $ selectColumns = $ this ->getSelectColumns ();
990990
991991 // Build WHERE clause
992- $ whereClause = '' ;
993- $ tenantFilter = $ this ->getTenantFilter ();
994- if (!empty ($ parsed ['filters ' ]) || $ tenantFilter ) {
995- $ conditions = $ parsed ['filters ' ];
996- if ($ tenantFilter ) {
997- $ conditions [] = ltrim ($ tenantFilter , ' AND ' );
998- $ parsed ['params ' ]['tenant ' ] = $ this ->tenant ;
999- }
1000- $ whereClause = ' WHERE ' . implode (' AND ' , $ conditions );
1001- }
992+ $ whereData = $ this ->buildWhereClause ($ parsed ['filters ' ], $ parsed ['params ' ]);
993+ $ whereClause = $ whereData ['clause ' ];
994+ $ parsed ['params ' ] = $ whereData ['params ' ];
1002995
1003996 // Build ORDER BY clause
1004997 $ orderClause = '' ;
@@ -1050,25 +1043,14 @@ public function count(array $queries = []): int
10501043 // Parse queries - we only need filters and params
10511044 $ parsed = $ this ->parseQueries ($ queries );
10521045
1053- // Build WHERE clause
1054- $ whereClause = '' ;
1055- $ tenantFilter = $ this ->getTenantFilter ();
1056- if (!empty ($ parsed ['filters ' ]) || $ tenantFilter ) {
1057- $ conditions = $ parsed ['filters ' ];
1058- if ($ tenantFilter ) {
1059- $ conditions [] = ltrim ($ tenantFilter , ' AND ' );
1060- }
1061- $ whereClause = ' WHERE ' . implode (' AND ' , $ conditions );
1062- }
1063-
1064- // Remove limit and offset from params
1046+ // Remove limit and offset from params (not needed for count)
10651047 $ params = $ parsed ['params ' ];
10661048 unset($ params ['limit ' ], $ params ['offset ' ]);
10671049
1068- // Add tenant param if filter is active
1069- if ( $ tenantFilter ) {
1070- $ params [ ' tenant ' ] = $ this -> tenant ;
1071- }
1050+ // Build WHERE clause
1051+ $ whereData = $ this -> buildWhereClause ( $ parsed [ ' filters ' ], $ params );
1052+ $ whereClause = $ whereData [ ' clause ' ] ;
1053+ $ params = $ whereData [ ' params ' ];
10721054
10731055 // Count from both tables
10741056 $ sql = "
@@ -1091,6 +1073,35 @@ public function count(array $queries = []): int
10911073 return (int ) $ json ['data ' ][0 ]['total ' ];
10921074 }
10931075
1076+ /**
1077+ * Build WHERE clause from filters with optional tenant filtering.
1078+ *
1079+ * @param array<string> $filters SQL filter conditions
1080+ * @param array<string, mixed> $params Existing query parameters
1081+ * @param bool $includeTenant Whether to include tenant filter
1082+ * @return array{clause: string, params: array<string, mixed>}
1083+ */
1084+ private function buildWhereClause (array $ filters , array $ params = [], bool $ includeTenant = true ): array
1085+ {
1086+ $ conditions = $ filters ;
1087+ $ whereParams = $ params ;
1088+
1089+ if ($ includeTenant ) {
1090+ $ tenantFilter = $ this ->getTenantFilter ();
1091+ if ($ tenantFilter ) {
1092+ $ conditions [] = ltrim ($ tenantFilter , ' AND ' );
1093+ $ whereParams ['tenant ' ] = $ this ->tenant ;
1094+ }
1095+ }
1096+
1097+ $ clause = !empty ($ conditions ) ? ' WHERE ' . implode (' AND ' , $ conditions ) : '' ;
1098+
1099+ return [
1100+ 'clause ' => $ clause ,
1101+ 'params ' => $ whereParams
1102+ ];
1103+ }
1104+
10941105 /**
10951106 * Parse Query objects into SQL clauses.
10961107 *
@@ -1492,6 +1503,59 @@ public function getBetweenDates(string $metric, string $startDate, string $endDa
14921503 return $ this ->find ($ allQueries );
14931504 }
14941505
1506+ /**
1507+ * Sum metric values using Query objects.
1508+ * Sums from both aggregated and counter tables.
1509+ *
1510+ * @param array<Query> $queries
1511+ * @param string $attribute Attribute to sum (default: 'value')
1512+ * @return int
1513+ * @throws Exception
1514+ */
1515+ public function sum (array $ queries = [], string $ attribute = 'value ' ): int
1516+ {
1517+ $ tableName = $ this ->getTableName ();
1518+ $ counterTableName = $ this ->getCounterTableName ();
1519+ $ escapedTable = $ this ->escapeIdentifier ($ this ->database ) . '. ' . $ this ->escapeIdentifier ($ tableName );
1520+ $ escapedCounterTable = $ this ->escapeIdentifier ($ this ->database ) . '. ' . $ this ->escapeIdentifier ($ counterTableName );
1521+ // FINAL on both tables (SummingMergeTree and ReplacingMergeTree)
1522+ $ fromTable = $ escapedTable . ($ this ->useFinal ? ' FINAL ' : '' );
1523+ $ fromCounterTable = $ escapedCounterTable . ($ this ->useFinal ? ' FINAL ' : '' );
1524+
1525+ // Validate attribute name
1526+ $ this ->validateAttributeName ($ attribute );
1527+ $ escapedAttribute = $ this ->escapeIdentifier ($ attribute );
1528+
1529+ // Parse queries
1530+ $ parsed = $ this ->parseQueries ($ queries );
1531+
1532+ // Build WHERE clause
1533+ $ whereData = $ this ->buildWhereClause ($ parsed ['filters ' ], $ parsed ['params ' ]);
1534+ $ whereClause = $ whereData ['clause ' ];
1535+ $ params = $ whereData ['params ' ];
1536+
1537+ // Sum from both tables
1538+ $ sql = "
1539+ SELECT SUM(total) as grand_total
1540+ FROM (
1541+ SELECT sum( {$ escapedAttribute }) as total FROM {$ fromTable }{$ whereClause }
1542+ UNION ALL
1543+ SELECT sum( {$ escapedAttribute }) as total FROM {$ fromCounterTable }{$ whereClause }
1544+ )
1545+ FORMAT JSON
1546+ " ;
1547+
1548+ $ result = $ this ->query ($ sql , $ params );
1549+
1550+ $ json = json_decode ($ result , true );
1551+
1552+ if (!is_array ($ json ) || !isset ($ json ['data ' ][0 ]['grand_total ' ])) {
1553+ return 0 ;
1554+ }
1555+
1556+ return (int ) $ json ['data ' ][0 ]['grand_total ' ];
1557+ }
1558+
14951559 /**
14961560 * Count usage metrics by period.
14971561 *
@@ -1524,60 +1588,17 @@ public function countByPeriod(string $metric, string $period, array $queries = [
15241588 */
15251589 public function sumByPeriod (string $ metric , string $ period , array $ queries = []): int
15261590 {
1527- $ tableName = $ this ->getTableName ();
1528- $ counterTableName = $ this ->getCounterTableName ();
1529- $ escapedTable = $ this ->escapeIdentifier ($ this ->database ) . '. ' . $ this ->escapeIdentifier ($ tableName );
1530- $ escapedCounterTable = $ this ->escapeIdentifier ($ this ->database ) . '. ' . $ this ->escapeIdentifier ($ counterTableName );
1531- // FINAL on both tables (SummingMergeTree and ReplacingMergeTree)
1532- $ fromTable = $ escapedTable . ($ this ->useFinal ? ' FINAL ' : '' );
1533- $ fromCounterTable = $ escapedCounterTable . ($ this ->useFinal ? ' FINAL ' : '' );
1534-
1535- // Build query constraints
15361591 $ allQueries = [
15371592 Query::equal ('metric ' , [$ metric ]),
15381593 Query::equal ('period ' , [$ period ]),
15391594 ];
15401595
1596+ // Add custom queries
15411597 foreach ($ queries as $ query ) {
15421598 $ allQueries [] = $ query ;
15431599 }
15441600
1545- $ parsed = $ this ->parseQueries ($ allQueries );
1546-
1547- // Build WHERE clause
1548- $ whereClause = '' ;
1549- $ tenantFilter = $ this ->getTenantFilter ();
1550- if (!empty ($ parsed ['filters ' ]) || $ tenantFilter ) {
1551- $ conditions = $ parsed ['filters ' ];
1552- if ($ tenantFilter ) {
1553- $ conditions [] = ltrim ($ tenantFilter , ' AND ' );
1554- // Add tenant param
1555- $ parsed ['params ' ]['tenant ' ] = $ this ->tenant ;
1556- }
1557- $ whereClause = ' WHERE ' . implode (' AND ' , $ conditions );
1558- }
1559-
1560- // Sum from both tables
1561- $ sql = "
1562- SELECT SUM(total) as grand_total
1563- FROM (
1564- SELECT sum(value) as total FROM {$ fromTable }{$ whereClause }
1565- UNION ALL
1566- SELECT sum(value) as total FROM {$ fromCounterTable }{$ whereClause }
1567- )
1568-
1569- FORMAT JSON
1570- " ;
1571-
1572- $ result = $ this ->query ($ sql , $ parsed ['params ' ]);
1573-
1574- $ json = json_decode ($ result , true );
1575-
1576- if (!is_array ($ json ) || !isset ($ json ['data ' ][0 ]['grand_total ' ])) {
1577- return 0 ;
1578- }
1579-
1580- return (int ) $ json ['data ' ][0 ]['grand_total ' ];
1601+ return $ this ->sum ($ allQueries );
15811602 }
15821603
15831604 /**
0 commit comments