diff --git a/client/trailbase-dart/lib/src/client.dart b/client/trailbase-dart/lib/src/client.dart index c467d94..8ff6cb4 100644 --- a/client/trailbase-dart/lib/src/client.dart +++ b/client/trailbase-dart/lib/src/client.dart @@ -111,12 +111,18 @@ class Pagination { class ListResponse { final String? cursor; final List> records; + final int? totalCount; - const ListResponse({this.cursor, required this.records}); + const ListResponse({ + this.cursor, + required this.records, + this.totalCount, + }); ListResponse.fromJson(Map json) : cursor = json['cursor'], - records = (json['records'] as List).cast>(); + records = (json['records'] as List).cast>(), + totalCount = json['total_count']; } abstract class RecordId { @@ -293,6 +299,7 @@ class RecordApi { Pagination? pagination, List? order, List? filters, + bool? count, }) async { final params = {}; if (pagination != null) { @@ -304,6 +311,7 @@ class RecordApi { } if (order != null) params['order'] = order.join(','); + if (count ?? false) params['count'] = 'true'; if (filters != null) { for (final filter in filters) { diff --git a/client/trailbase-dart/test/trailbase_test.dart b/client/trailbase-dart/test/trailbase_test.dart index 4a8bb11..bc8d995 100644 --- a/client/trailbase-dart/test/trailbase_test.dart +++ b/client/trailbase-dart/test/trailbase_test.dart @@ -184,6 +184,19 @@ Future main() async { orderedEquals(messages)); } + { + final response = (await api.list( + pagination: Pagination(limit: 1), + order: ['-text_not_null'], + filters: ['text_not_null[like]=%${now}'], + count: true, + )); + + expect(response.totalCount ?? -1, 2); + // Ensure there's no extra field, i.e the count doesn't get serialized. + expect(response.records[0].keys.length, 13); + } + final record = SimpleStrict.fromJson(await api.read(ids[0])); expect(ids[0] == record.id, isTrue); diff --git a/trailbase-core/src/records/list_records.rs b/trailbase-core/src/records/list_records.rs index 33e295b..7336db3 100644 --- a/trailbase-core/src/records/list_records.rs +++ b/trailbase-core/src/records/list_records.rs @@ -121,7 +121,7 @@ pub async fn list_records_handler( formatdoc!( r#" WITH total_count AS ( - SELECT COUNT(*) + SELECT COUNT(*) AS _value_ FROM '{table_name}' as _ROW_, (SELECT :__user_id AS id) AS _USER_ @@ -129,9 +129,9 @@ pub async fn list_records_handler( {clause} ) - SELECT _ROW_.*, _COUNT_.* + SELECT _ROW_.*, total_count._value_ FROM - total_count AS _COUNT_, + total_count, '{table_name}' as _ROW_, (SELECT :__user_id AS id) AS _USER_ WHERE @@ -313,6 +313,15 @@ mod tests { .records .into_iter() .map(|v| { + match v { + serde_json::Value::Object(ref obj) => { + let keys: Vec<&str> = obj.keys().map(|s| s.as_str()).collect(); + assert_eq!(3, keys.len(), "Got: {:?}", keys); + assert_eq!(keys, vec!["id", "room", "data"]) + } + _ => panic!("expected object, got {v:?}"), + }; + let message = serde_json::from_value::(v).unwrap(); assert_eq!(None, message._owner); message.data @@ -340,6 +349,30 @@ mod tests { assert_eq!(resp.records.len(), 2); assert_eq!(resp.total_count, Some(2)); + let messages: Vec<_> = resp + .records + .into_iter() + .map(|v| { + match v { + serde_json::Value::Object(ref obj) => { + let keys: Vec<&str> = obj.keys().map(|s| s.as_str()).collect(); + assert_eq!(3, keys.len(), "Got: {:?}", keys); + assert_eq!(keys, vec!["id", "room", "data"]) + } + _ => panic!("expected object, got {v:?}"), + }; + + let message = serde_json::from_value::(v).unwrap(); + assert_eq!(None, message._owner); + message.data + }) + .collect(); + + assert_eq!( + vec!["user_y to room0".to_string(), "user_x to room0".to_string()], + messages + ); + // Let's paginate let resp0 = list_records( &state,