// Query-counting Database proxy used to report merge query volumes. import 'package:serverpod_database/serverpod_database.dart'; /// Database proxy that counts every database operation it delegates. /// /// Each counted operation maps to one query issued by the caller (finds, /// inserts, updates, deletes, locks and unsafe statements). Transaction /// control ([transaction] itself) and metadata getters are not counted. class QueryCountingDatabase implements Database { /// Creates a [QueryCountingDatabase] wrapping the [_delegate] database. QueryCountingDatabase(this._delegate); final Database _delegate; /// Number of database operations delegated so far. int queryCount = 0; /// Top-level rows returned by read APIs, including CRDT metadata and repeated /// reads. This is result volume, not SQLite page reads or query-plan scans. int rowsRead = 0; /// ORM result rows grouped by model type (raw SQL results use `sql`). final Map rowsReadByType = {}; void _recordRows(String type, int count) { rowsRead += count; rowsReadByType.update(type, (value) => value + count, ifAbsent: () => count); } @override DatabaseAnalyzer get analyzer => _delegate.analyzer; @override DatabaseDialect get dialect => _delegate.dialect; @override DatabaseSerializationManager get serializationManager => _delegate.serializationManager; @override Future> find({ Expression? where, int? limit, int? offset, Column? orderBy, List? orderByList, bool orderDescending = false, Transaction? transaction, Include? include, LockMode? lockMode, LockBehavior? lockBehavior, }) async { queryCount++; final result = await _delegate.find( where: where, limit: limit, offset: offset, orderBy: orderBy, orderByList: orderByList, transaction: transaction, include: include, lockMode: lockMode, lockBehavior: lockBehavior, ); _recordRows(T.toString(), result.length); return result; } @override Future findById( Object id, { Transaction? transaction, Include? include, LockMode? lockMode, LockBehavior? lockBehavior, }) async { queryCount++; final result = await _delegate.findById( id, transaction: transaction, include: include, lockMode: lockMode, lockBehavior: lockBehavior, ); _recordRows(T.toString(), result == null ? 0 : 1); return result; } @override Future findFirstRow({ Expression? where, int? offset, Column? orderBy, List? orderByList, bool orderDescending = false, Transaction? transaction, Include? include, LockMode? lockMode, LockBehavior? lockBehavior, }) async { queryCount++; final result = await _delegate.findFirstRow( where: where, offset: offset, orderBy: orderBy, orderByList: orderByList, transaction: transaction, include: include, lockMode: lockMode, lockBehavior: lockBehavior, ); _recordRows(T.toString(), result == null ? 0 : 1); return result; } @override Future> insert( List rows, { Transaction? transaction, bool ignoreConflicts = false, bool noReturn = false, }) async { queryCount++; return _delegate.insert( rows, transaction: transaction, ignoreConflicts: ignoreConflicts, noReturn: noReturn, ); } @override Future insertRow( T row, { Transaction? transaction, }) async { queryCount++; return _delegate.insertRow(row, transaction: transaction); } @override Future> upsert( List rows, { required List conflictColumns, List? updateColumns, Expression? updateWhere, Transaction? transaction, bool noReturn = false, }) async { queryCount++; return _delegate.upsert( rows, conflictColumns: conflictColumns, updateColumns: updateColumns, updateWhere: updateWhere, transaction: transaction, noReturn: noReturn, ); } @override Future upsertRow( T row, { required List conflictColumns, List? updateColumns, Expression? updateWhere, Transaction? transaction, }) async { queryCount++; return _delegate.upsertRow( row, conflictColumns: conflictColumns, updateColumns: updateColumns, updateWhere: updateWhere, transaction: transaction, ); } @override Future> update( List rows, { List? columns, Transaction? transaction, bool noReturn = false, }) async { queryCount++; return _delegate.update( rows, columns: columns, transaction: transaction, noReturn: noReturn, ); } @override Future updateRow( T row, { List? columns, Transaction? transaction, }) async { queryCount++; return _delegate.updateRow(row, columns: columns, transaction: transaction); } @override Future updateById( Object id, { required List columnValues, Transaction? transaction, }) async { queryCount++; return _delegate.updateById( id, columnValues: columnValues, transaction: transaction, ); } @override Future> updateWhere({ required List columnValues, required Expression where, int? limit, int? offset, Column? orderBy, List? orderByList, bool orderDescending = false, Transaction? transaction, bool noReturn = false, }) async { queryCount++; return _delegate.updateWhere( columnValues: columnValues, where: where, limit: limit, offset: offset, orderBy: orderBy, orderByList: orderByList, transaction: transaction, noReturn: noReturn, ); } @override Future> delete( List rows, { Column? orderBy, List? orderByList, bool orderDescending = false, Transaction? transaction, bool noReturn = false, }) async { queryCount++; return _delegate.delete( rows, orderBy: orderBy, orderByList: orderByList, transaction: transaction, noReturn: noReturn, ); } @override Future deleteRow( T row, { Transaction? transaction, }) async { queryCount++; return _delegate.deleteRow(row, transaction: transaction); } @override Future> deleteWhere({ required Expression where, Column? orderBy, List? orderByList, bool orderDescending = false, Transaction? transaction, bool noReturn = false, }) async { queryCount++; return _delegate.deleteWhere( where: where, orderBy: orderBy, orderByList: orderByList, transaction: transaction, noReturn: noReturn, ); } @override Future count({ Expression? where, int? limit, bool useCache = true, Transaction? transaction, }) async { queryCount++; return _delegate.count( where: where, limit: limit, useCache: useCache, transaction: transaction, ); } @override Future lockRows({ required Expression where, required LockMode lockMode, required Transaction transaction, LockBehavior lockBehavior = LockBehavior.wait, }) async { queryCount++; return _delegate.lockRows( where: where, lockMode: lockMode, transaction: transaction, lockBehavior: lockBehavior, ); } @override Future unsafeQuery( String query, { int? timeoutInSeconds, Transaction? transaction, QueryParameters? parameters, }) async { queryCount++; final result = await _delegate.unsafeQuery( query, timeoutInSeconds: timeoutInSeconds, transaction: transaction, parameters: parameters, ); _recordRows('sql', result.length); return result; } @override Future unsafeExecute( String query, { int? timeoutInSeconds, Transaction? transaction, QueryParameters? parameters, }) async { queryCount++; return _delegate.unsafeExecute( query, timeoutInSeconds: timeoutInSeconds, transaction: transaction, parameters: parameters, ); } @override Future unsafeSimpleQuery( String query, { int? timeoutInSeconds, Transaction? transaction, }) async { queryCount++; final result = await _delegate.unsafeSimpleQuery( query, timeoutInSeconds: timeoutInSeconds, transaction: transaction, ); _recordRows('sql', result.length); return result; } @override Future unsafeSimpleExecute( String query, { int? timeoutInSeconds, Transaction? transaction, }) async { queryCount++; return _delegate.unsafeSimpleExecute( query, timeoutInSeconds: timeoutInSeconds, transaction: transaction, ); } @override Future transaction( TransactionFunction transactionFunction, { TransactionSettings? settings, }) async { return _delegate.transaction(transactionFunction, settings: settings); } @override Future testConnection() => _delegate.testConnection(); }