Skip to content

Commit

Permalink
Add listCollections (#45)
Browse files Browse the repository at this point in the history
fixes #34
  • Loading branch information
rrousselGit committed Jul 15, 2024
1 parent 8ae9e2a commit 4b9a874
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
run: dart analyze

- name: Run tests
run: firebase emulators:exec --project dart-firebase-admin --only auth,firestore "dart test"
run: ${{github.workspace}}/scripts/coverage.sh

- name: Upload coverage to codecov
run: curl -s https://codecov.io/bash | bash
1 change: 1 addition & 0 deletions packages/dart_firebase_admin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Unreleased minor

- Added `firestore.listCollections()` and `doc.listCollections()`
- Fixes some errors incorrectly coming back as "unknown".
- `Apns` parameters are no-longer required
- Fixes argument error in FMC when sending booleans
Expand Down
2 changes: 2 additions & 0 deletions packages/dart_firebase_admin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ print(user.data()?['age']);

| Firestore | |
| ------------------------------------------------ | --- |
| firestore.listCollections() ||
| reference.id ||
| reference.listCollections() ||
| reference.parent ||
| reference.path ||
| reference.== ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,32 @@ class Firestore {
// TODO batch
// TODO bulkWriter
// TODO bundle
// TODO listCollections
// TODO getAll
// TODO runTransaction
// TODO recursiveDelete

/// Fetches the root collections that are associated with this Firestore
/// database.
///
/// Returns a Promise that resolves with an array of CollectionReferences.
///
/// ```dart
/// firestore.listCollections().then((collections) {
/// for (final collection in collections) {
/// print('Found collection with id: ${collection.id}');
/// }
/// });
/// ```
Future<List<CollectionReference<DocumentData>>> listCollections() {
final rootDocument = DocumentReference._(
firestore: this,
path: _ResourcePath.empty,
converter: _jsonConverter,
);

return rootDocument.listCollections();
}

/// Gets a [DocumentReference] instance that
/// refers to the document at the specified path.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ abstract class _Path<T extends _Path<Object?>> implements Comparable<_Path<T>> {
List<String> _split(String relativePath);

/// Returns the path of the parent node.
T? _parent() {
T? parent() {
if (segments.isEmpty) return null;

return _construct(segments.sublist(0, segments.length - 1));
Expand Down Expand Up @@ -83,7 +83,6 @@ abstract class _Path<T extends _Path<Object?>> implements Comparable<_Path<T>> {
@override
bool operator ==(Object other) {
return other is _Path<T> &&
runtimeType == other.runtimeType &&
const ListEquality<String>().equals(segments, other.segments);
}

Expand Down Expand Up @@ -179,8 +178,7 @@ class _QualifiedResourcePath extends _ResourcePath {
final String _databaseId;

@override
_QualifiedResourcePath? _parent() =>
super._parent() as _QualifiedResourcePath?;
_QualifiedResourcePath? parent() => super.parent() as _QualifiedResourcePath?;

/// String representation of a ResourcePath as expected by the API.
String get _formattedName {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ part of 'firestore.dart';
final class CollectionReference<T> extends Query<T> {
CollectionReference._({
required super.firestore,
required _QualifiedResourcePath path,
required _ResourcePath path,
required _FirestoreDataConverter<T> converter,
}) : super._(
queryOptions: _QueryOptions.forCollectionQuery(path, converter),
);

_QualifiedResourcePath get _resourcePath =>
_queryOptions.parentPath._append(id) as _QualifiedResourcePath;
_ResourcePath get _resourcePath => _queryOptions.parentPath._append(id);

/// The last path element of the referenced collection.
String get id => _queryOptions.collectionId;
Expand All @@ -28,7 +27,7 @@ final class CollectionReference<T> extends Query<T> {

return DocumentReference<DocumentData>._(
firestore: firestore,
path: _queryOptions.parentPath as _QualifiedResourcePath,
path: _queryOptions.parentPath,
converter: _jsonConverter,
);
}
Expand Down Expand Up @@ -62,7 +61,7 @@ final class CollectionReference<T> extends Query<T> {
}

if (!identical(_queryOptions.converter, _jsonConverter) &&
path._parent() != _resourcePath) {
path.parent() != _resourcePath) {
throw ArgumentError.value(
documentPath,
'documentPath',
Expand Down Expand Up @@ -159,12 +158,12 @@ final class CollectionReference<T> extends Query<T> {
final class DocumentReference<T> implements _Serializable {
const DocumentReference._({
required this.firestore,
required _QualifiedResourcePath path,
required _ResourcePath path,
required _FirestoreDataConverter<T> converter,
}) : _converter = converter,
_path = path;

final _QualifiedResourcePath _path;
final _ResourcePath _path;
final _FirestoreDataConverter<T> _converter;
final Firestore firestore;

Expand All @@ -187,7 +186,7 @@ final class DocumentReference<T> implements _Serializable {
CollectionReference<T> get parent {
return CollectionReference<T>._(
firestore: firestore,
path: _path._parent()!,
path: _path.parent()!,
converter: _converter,
);
}
Expand All @@ -202,6 +201,40 @@ final class DocumentReference<T> implements _Serializable {
._formattedName;
}

/// Fetches the subcollections that are direct children of this document.
///
/// ```dart
/// final documentRef = firestore.doc('col/doc');
///
/// documentRef.listCollections().then((collections) {
/// for (final collection in collections) {
/// print('Found subcollection with id: ${collection.id}');
/// }
/// });
/// ```
Future<List<CollectionReference<DocumentData>>> listCollections() {
return this.firestore._client.v1((a) async {
final request = firestore1.ListCollectionIdsRequest(
// Setting `pageSize` to an arbitrarily large value lets the backend cap
// the page size (currently to 300). Note that the backend rejects
// MAX_INT32 (b/146883794).
pageSize: (math.pow(2, 16) - 1).toInt(),
);

final result = await a.projects.databases.documents.listCollectionIds(
request,
this._formattedName,
);

final ids = result.collectionIds ?? [];
ids.sort((a, b) => a.compareTo(b));

return [
for (final id in ids) collection(id),
];
});
}

/// Changes the de/serializing mechanism for this [DocumentReference].
///
/// This changes the return value of [DocumentSnapshot.data].
Expand Down Expand Up @@ -416,10 +449,6 @@ class _QueryCursor {

@override
bool operator ==(Object other) {
// if (other is! _QueryCursor) return false;

// print(_valuesEqual(values, other.values));

return other is _QueryCursor &&
runtimeType == other.runtimeType &&
before == other.before &&
Expand Down Expand Up @@ -510,11 +539,11 @@ class _QueryOptions<T> with _$QueryOptions<T> {

/// Returns query options for a single-collection query.
factory _QueryOptions.forCollectionQuery(
_QualifiedResourcePath collectionRef,
_ResourcePath collectionRef,
_FirestoreDataConverter<T> converter,
) {
return _QueryOptions<T>(
parentPath: collectionRef._parent()!,
parentPath: collectionRef.parent()!,
collectionId: collectionRef.id!,
converter: converter,
allDescendants: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ void main() {
group('collectionGroup', () {
late Firestore firestore;

setUp(() => firestore = createInstance());
setUp(() => firestore = createFirestore());

test('throws if collectionId contains "/"', () {
expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ void main() {
group('Collection interface', () {
late Firestore firestore;

setUp(() => firestore = createInstance());
setUp(() => firestore = createFirestore());

test('has doc() method', () {
final collection = firestore.collection('colId');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,32 @@ void main() {
late DocumentReference<Map<String, Object?>> documentRef;

setUp(() {
firestore = createInstance();
firestore = createFirestore();
documentRef = firestore.doc('collectionId/documentId');
});

test('listCollections', () async {
final doc1 = firestore.doc('collectionId/a');
final doc2 = firestore.doc('collectionId/b');

final doc1col1 = doc1.collection('a');
final doc1col2 = doc1.collection('b');

final doc2col1 = doc2.collection('c');
final doc2col2 = doc2.collection('d');

await doc1col1.add({});
await doc1col2.add({});
await doc2col1.add({});
await doc2col2.add({});

final doc1Collections = await doc1.listCollections();
final doc2Collections = await doc2.listCollections();

expect(doc1Collections, unorderedEquals([doc1col1, doc1col2]));
expect(doc2Collections, unorderedEquals([doc2col1, doc2col2]));
});

test('has collection() method', () {
final collection = documentRef.collection('col');
expect(collection.id, 'col');
Expand Down Expand Up @@ -60,7 +82,7 @@ void main() {
group('serialize document', () {
late Firestore firestore;

setUp(() => firestore = createInstance());
setUp(() => firestore = createFirestore());

test("doesn't serialize unsupported types", () {
expect(
Expand Down Expand Up @@ -98,7 +120,7 @@ void main() {
});

test('Supports BigInt', () async {
final firestore = createInstance(Settings(useBigInt: true));
final firestore = createFirestore(Settings(useBigInt: true));

await firestore.doc('collectionId/bigInt').set({
'foo': BigInt.from(9223372036854775807),
Expand Down Expand Up @@ -199,10 +221,10 @@ void main() {
group('get document', () {
late Firestore firestore;

setUp(() => firestore = createInstance());
setUp(() => firestore = createFirestore());

test('returns document', () async {
firestore = createInstance();
firestore = createFirestore();
await firestore.doc('collectionId/getdocument').set({
'foo': {
'bar': 'foobar',
Expand Down Expand Up @@ -280,7 +302,7 @@ void main() {
group('delete document', () {
late Firestore firestore;

setUp(() => firestore = createInstance());
setUp(() => firestore = createFirestore());

test('works', () async {
await firestore.doc('collectionId/deletedoc').set({});
Expand Down Expand Up @@ -333,7 +355,7 @@ void main() {
group('set documents', () {
late Firestore firestore;

setUp(() => firestore = createInstance());
setUp(() => firestore = createFirestore());

test('sends empty non-merge write even with just field transform',
() async {
Expand Down Expand Up @@ -386,7 +408,7 @@ void main() {
group('create document', () {
late Firestore firestore;

setUp(() => firestore = createInstance());
setUp(() => firestore = createFirestore());

test('creates document', () async {
await firestore.doc('collectionId/createdoc').delete();
Expand Down Expand Up @@ -433,7 +455,7 @@ void main() {
group('update document', () {
late Firestore firestore;

setUp(() => firestore = createInstance());
setUp(() => firestore = createFirestore());

test('works', () async {
await firestore.doc('collectionId/updatedoc').set({'foo': 'bar'});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:dart_firebase_admin/firestore.dart';
import 'package:test/test.dart';

import 'util/helpers.dart';

void main() {
group('Firestore', () {
late Firestore firestore;

setUp(() => firestore = createFirestore());

test('listCollections', () async {
final a = firestore.collection('a');
final b = firestore.collection('b');

await a.doc('1').set({'a': 1});
await b.doc('2').set({'b': 2});

final collections = await firestore.listCollections();

expect(collections, unorderedEquals([a, b]));
});
});
}
Loading

0 comments on commit 4b9a874

Please sign in to comment.