Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add listCollections #45

Merged
merged 2 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading