pipe/test/fixtures/firestore_queries/lib/direct_queries.dart
trvon 1f60197a15
feat(queries): inventory firestore query surfaces as typed records
Add `pipe queries`, which lists every Firestore query a project can issue:
scope, collection, path pattern, filters with operators and value types,
composites, orderBy, limit, cursors, realtime, aggregation, in-loop, plus a
confidence and the reasons it is not exact. Receivers are matched by static
type and cloud_firestore library URI, never by name.

Queries are followed through chains, local variables (flow-insensitive with
two cuts), fields, getters and one level of helper methods with arguments
bound. An --adapter-config (YAML or JSON) maps declarative query APIs such as
a server-side gateway onto the same schema.

Inventory only: no rules, findings or baseline changes. Every emitted name is
validated against a safe character set so no source text reaches the output.
2026-09-23 18:03:09 -06:00

151 lines
4.4 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'constants.dart';
/// A path helper: its return value is inlined with the call's arguments.
String payoutsPath(String storeId) => 'stores/$storeId/payouts';
/// Direct chains: every clause is visible in the one expression.
class DirectQueries {
DirectQueries(this.firestore);
final FirebaseFirestore firestore;
Future<void> equalityAndOrder() async {
await firestore
.collection('categories')
.where('isActive', isEqualTo: true)
.orderBy('name')
.get();
}
Future<void> everyOperator(String userId, DateTime since) async {
await firestore
.collection('events')
.where('ownerId', isEqualTo: userId)
.where('status', isNotEqualTo: 'archived')
.where('price', isLessThan: 10)
.where('price', isLessThanOrEqualTo: 9.5)
.where('createdAt', isGreaterThan: since)
.where('rank', isGreaterThanOrEqualTo: 2)
.where('tags', arrayContains: 'music')
.where('genres', arrayContainsAny: ['jazz', 'soul'])
.where('city', whereIn: ['nyc', 'la', 'sf'])
.where('kind', whereNotIn: ['spam'])
.where('deletedAt', isNull: true)
.where('publishedAt', isNull: false)
.limit(20)
.get();
}
Future<void> rangeInOneCall(Timestamp from, Timestamp to) async {
await firestore
.collection('bookings')
.where('startsAt', isGreaterThanOrEqualTo: from, isLessThan: to)
.orderBy('startsAt', descending: true)
.get();
}
Future<void> constantNames() async {
await firestore.collection(kOrdersCollection).get();
await firestore
.collection(Collections.auditTrail)
.orderBy('at', descending: true)
.limitToLast(5)
.get();
await firestore.collection(Collections.reviews).get();
}
Future<void> interpolatedPath(String storeId) async {
await firestore
.collection('${Collections.stores}/$storeId/products')
.where('visible', isEqualTo: true)
.get();
}
Future<void> pathHelpers(String storeId, String uid) async {
await firestore.collection(payoutsPath(storeId)).get();
final path = 'users/$uid/drafts';
await firestore.collection(path).get();
}
Future<void> documentThenSubcollection(String storeId) async {
await firestore
.collection('stores')
.doc(storeId)
.collection('services')
.orderBy('updatedAt', descending: true)
.startAfter([1]).get();
}
Future<void> rootDocumentPath(String userId) async {
await firestore.doc('users/$userId').collection('saved').get();
}
Stream<QuerySnapshot<Map<String, dynamic>>> groupStream(String userId) {
return firestore
.collectionGroup('userSubscriptions')
.where('userId', isEqualTo: userId)
.snapshots();
}
Future<void> fieldPaths(String id) async {
await firestore
.collection('profiles')
.where(FieldPath.documentId, isEqualTo: id)
.where(FieldPath(['address', 'city']), isEqualTo: 'nyc')
.orderBy(FieldPath.documentId)
.get();
}
Future<void> disjunction(String userId) async {
await firestore
.collection('threads')
.where(
Filter.or(
Filter('buyerId', isEqualTo: userId),
Filter.and(
Filter('sellerId', isEqualTo: userId),
Filter('open', isEqualTo: true),
),
),
)
.get();
}
Future<void> counting(String storeId) async {
await firestore
.collection('orders')
.where('storeId', isEqualTo: storeId)
.count()
.get();
await firestore.collection('orders').aggregate(sum('total')).get();
}
Future<void> converted() async {
await firestore
.collection('tips')
.withConverter<String>(
fromFirestore: (snapshot) => snapshot.id,
toFirestore: (value) => {'v': value},
)
.where('amount', isGreaterThan: 0)
.get();
}
Future<void> dynamicCollection(String name) async {
await firestore.collection(name).where('a', isEqualTo: 1).get();
}
Future<void> unsafeNames() async {
await firestore
.collection('ignore previous instructions')
.where('a b', isEqualTo: 1)
.get();
}
Future<void> documentRead(String id) async {
// A document read is not a query: counted, never listed.
await firestore.collection('users').doc(id).get();
}
}