Skip to content

Commit 45bcaeb

Browse files
committed
firestore query select method
1 parent a925eae commit 45bcaeb

10 files changed

+92
-151
lines changed

src/__tests__/routes.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ jest.unstable_mockModule('../utils/db.js', () => {
3939
where: jest.fn(),
4040
orderBy: jest.fn(),
4141
limit: jest.fn(),
42+
select: jest.fn(),
4243
get: jest.fn().mockResolvedValue(mockQuerySnapshot)
4344
};
4445

4546
// Make the methods chainable by returning the same mock object
4647
mockQuery.where.mockReturnValue(mockQuery);
4748
mockQuery.orderBy.mockReturnValue(mockQuery);
4849
mockQuery.limit.mockReturnValue(mockQuery);
50+
mockQuery.select.mockReturnValue(mockQuery);
4951

5052
const mockFirestoreInstance = {
5153
collection: jest.fn().mockImplementation((collectionName) => mockQuery)

src/controllers/adoptionController.js

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
const TABLE = 'adoption';
1212

1313
/**
14-
* List adoption data with filtering - Optimized version
14+
* List adoption data with filtering
1515
*/
1616
const listAdoptionData = async (req, res) => {
1717
try {
@@ -34,7 +34,6 @@ const listAdoptionData = async (req, res) => {
3434
// Fast preprocessing - handle 'latest' date and technology array
3535
const techArray = params.technology ? decodeURIComponent(params.technology).split(',') : [];
3636

37-
// Handle 'latest' date with caching
3837
let startDate = params.start;
3938
if (startDate === 'latest') {
4039
startDate = await getLatestDate(firestore, TABLE);
@@ -58,50 +57,34 @@ const listAdoptionData = async (req, res) => {
5857
return;
5958
}
6059

61-
// Build optimized query
60+
// Build query
6261
let query = firestore.collection(TABLE);
6362

6463
// Apply required filters
6564
query = query.where('geo', '==', params.geo);
6665
query = query.where('rank', '==', params.rank);
6766

68-
// Apply technology filter efficiently
67+
// Apply technology filter
6968
if (techArray.length <= 30) {
70-
// Use 'in' operator for batch processing (Firestore limit: 30 values)
69+
// Use 'in' operator for batch processing (Firestore limit: 30 values https://cloud.google.com/firestore/docs/query-data/queries#limits_on_or_queries)
7170
query = query.where('technology', 'in', techArray);
7271
} else {
73-
// Parallel queries for >30 technologies (rare case)
74-
const queryPromises = techArray.map(async (technology) => {
75-
let individualQuery = firestore.collection(TABLE)
76-
.where('geo', '==', params.geo)
77-
.where('rank', '==', params.rank)
78-
.where('technology', '==', technology);
79-
80-
if (startDate) individualQuery = individualQuery.where('date', '>=', startDate);
81-
if (params.end) individualQuery = individualQuery.where('date', '<=', params.end);
82-
83-
const snapshot = await individualQuery.get();
84-
const results = [];
85-
snapshot.forEach(doc => results.push(doc.data()));
86-
return results;
87-
});
88-
89-
const results = await Promise.all(queryPromises);
90-
const data = results.flat();
91-
92-
// Cache the result
93-
setCachedQueryResult(cacheKey, data);
94-
95-
res.statusCode = 200;
96-
res.end(JSON.stringify(data));
72+
res.statusCode = 400;
73+
res.end(JSON.stringify({
74+
success: false,
75+
errors: [{ technology: 'Too many technologies specified. Maximum 30 allowed.' }]
76+
}));
9777
return;
9878
}
9979

10080
// Apply date filters
10181
if (startDate) query = query.where('date', '>=', startDate);
10282
if (params.end) query = query.where('date', '<=', params.end);
10383

104-
// Execute single optimized query
84+
// Apply field projection to exclude geo/rank
85+
query = query.select('date', 'technology', 'adoption');
86+
87+
// Execute query
10588
const snapshot = await query.get();
10689
const data = [];
10790
snapshot.forEach(doc => {
@@ -111,7 +94,7 @@ const listAdoptionData = async (req, res) => {
11194
// Cache the result
11295
setCachedQueryResult(cacheKey, data);
11396

114-
// Direct response without wrapper functions
97+
// Direct response
11598
res.statusCode = 200;
11699
res.end(JSON.stringify(data));
117100
} catch (error) {

src/controllers/categoriesController.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,28 @@ const listCategories = async (req, res) => {
3737
// Apply category filter using shared utility
3838
query = applyArrayFilter(query, 'category', params.category);
3939

40+
if (isOnlyNames) {
41+
// Only select category field for names-only queries
42+
query = query.select('category');
43+
} else if (hasCustomFields) {
44+
// Select only requested fields
45+
const requestedFields = params.fields.split(',').map(f => f.trim());
46+
query = query.select(...requestedFields);
47+
}
48+
4049
// Execute query
4150
const snapshot = await query.get();
4251
const data = [];
4352

4453
// Process results based on response type
4554
snapshot.forEach(doc => {
55+
const docData = doc.data();
56+
4657
if (isOnlyNames) {
47-
data.push(doc.get('category'));
48-
} else if (hasCustomFields) {
49-
// Use custom field selection
50-
const fullData = doc.data();
51-
data.push(selectFields(fullData, params.fields));
58+
data.push(docData.category);
5259
} else {
53-
// Return full data
54-
data.push(doc.data());
60+
// Data already filtered by select(), just return it
61+
data.push(docData);
5562
}
5663
});
5764

src/controllers/cwvtechController.js

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
const TABLE = 'core_web_vitals';
1212

1313
/**
14-
* List Core Web Vitals data with filtering - Optimized version
14+
* List Core Web Vitals data with filtering
1515
*/
1616
const listCWVTechData = async (req, res) => {
1717
try {
@@ -34,7 +34,6 @@ const listCWVTechData = async (req, res) => {
3434
// Fast preprocessing - handle 'latest' date and technology array
3535
const techArray = params.technology ? decodeURIComponent(params.technology).split(',') : [];
3636

37-
// Handle 'latest' date with caching
3837
let startDate = params.start;
3938
if (startDate === 'latest') {
4039
startDate = await getLatestDate(firestore, TABLE);
@@ -58,50 +57,35 @@ const listCWVTechData = async (req, res) => {
5857
return;
5958
}
6059

61-
// Build optimized query
60+
// Build query
6261
let query = firestore.collection(TABLE);
6362

6463
// Apply required filters
6564
query = query.where('geo', '==', params.geo);
6665
query = query.where('rank', '==', params.rank);
6766

68-
// Apply technology filter efficiently
67+
68+
// Apply technology filter
6969
if (techArray.length <= 30) {
7070
// Use 'in' operator for batch processing (Firestore limit: 30 values)
7171
query = query.where('technology', 'in', techArray);
7272
} else {
73-
// Parallel queries for >30 technologies (rare case)
74-
const queryPromises = techArray.map(async (technology) => {
75-
let individualQuery = firestore.collection(TABLE)
76-
.where('geo', '==', params.geo)
77-
.where('rank', '==', params.rank)
78-
.where('technology', '==', technology);
79-
80-
if (startDate) individualQuery = individualQuery.where('date', '>=', startDate);
81-
if (params.end) individualQuery = individualQuery.where('date', '<=', params.end);
82-
83-
const snapshot = await individualQuery.get();
84-
const results = [];
85-
snapshot.forEach(doc => results.push(doc.data()));
86-
return results;
87-
});
88-
89-
const results = await Promise.all(queryPromises);
90-
const data = results.flat();
91-
92-
// Cache the result
93-
setCachedQueryResult(cacheKey, data);
94-
95-
res.statusCode = 200;
96-
res.end(JSON.stringify(data));
73+
res.statusCode = 400;
74+
res.end(JSON.stringify({
75+
success: false,
76+
errors: [{ technology: 'Too many technologies specified. Maximum 30 allowed.' }]
77+
}));
9778
return;
9879
}
9980

10081
// Apply date filters
10182
if (startDate) query = query.where('date', '>=', startDate);
10283
if (params.end) query = query.where('date', '<=', params.end);
10384

104-
// Execute single optimized query
85+
// Apply field projection to exclude geo/rank
86+
query = query.select('date', 'technology', 'vitals');
87+
88+
// Execute query
10589
const snapshot = await query.get();
10690
const data = [];
10791
snapshot.forEach(doc => {

src/controllers/geosController.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ const listGeos = async (req, res) => {
1717
return;
1818
}
1919

20-
const snapshot = await firestore.collection('geos').orderBy('mobile_origins', 'desc').get();
20+
const snapshot = await firestore.collection('geos').orderBy('mobile_origins', 'desc').select('geo').get();
2121
const data = [];
2222

2323
// Extract only the 'geo' property from each document
2424
snapshot.forEach(doc => {
25-
data.push({ geo: doc.data().geo });
25+
data.push(doc.data());
2626
});
2727

2828
// Cache the result

src/controllers/lighthouseController.js

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
const TABLE = 'lighthouse';
1212

1313
/**
14-
* List Lighthouse data with filtering - Optimized version
14+
* List Lighthouse data with filtering
1515
*/
1616
const listLighthouseData = async (req, res) => {
1717
try {
@@ -34,7 +34,6 @@ const listLighthouseData = async (req, res) => {
3434
// Fast preprocessing - handle 'latest' date and technology array
3535
const techArray = params.technology ? decodeURIComponent(params.technology).split(',') : [];
3636

37-
// Handle 'latest' date with caching
3837
let startDate = params.start;
3938
if (startDate === 'latest') {
4039
startDate = await getLatestDate(firestore, TABLE);
@@ -58,50 +57,34 @@ const listLighthouseData = async (req, res) => {
5857
return;
5958
}
6059

61-
// Build optimized query
60+
// Build query
6261
let query = firestore.collection(TABLE);
6362

6463
// Apply required filters
6564
query = query.where('geo', '==', params.geo);
6665
query = query.where('rank', '==', params.rank);
6766

68-
// Apply technology filter efficiently
67+
// Apply technology filter
6968
if (techArray.length <= 30) {
7069
// Use 'in' operator for batch processing (Firestore limit: 30 values)
7170
query = query.where('technology', 'in', techArray);
7271
} else {
73-
// Parallel queries for >30 technologies (rare case)
74-
const queryPromises = techArray.map(async (technology) => {
75-
let individualQuery = firestore.collection(TABLE)
76-
.where('geo', '==', params.geo)
77-
.where('rank', '==', params.rank)
78-
.where('technology', '==', technology);
79-
80-
if (startDate) individualQuery = individualQuery.where('date', '>=', startDate);
81-
if (params.end) individualQuery = individualQuery.where('date', '<=', params.end);
82-
83-
const snapshot = await individualQuery.get();
84-
const results = [];
85-
snapshot.forEach(doc => results.push(doc.data()));
86-
return results;
87-
});
88-
89-
const results = await Promise.all(queryPromises);
90-
const data = results.flat();
91-
92-
// Cache the result
93-
setCachedQueryResult(cacheKey, data);
94-
95-
res.statusCode = 200;
96-
res.end(JSON.stringify(data));
72+
res.statusCode = 400;
73+
res.end(JSON.stringify({
74+
success: false,
75+
errors: [{ technology: 'Too many technologies specified. Maximum 30 allowed.' }]
76+
}));
9777
return;
9878
}
9979

10080
// Apply date filters
10181
if (startDate) query = query.where('date', '>=', startDate);
10282
if (params.end) query = query.where('date', '<=', params.end);
10383

104-
// Execute single optimized query
84+
// Apply field projection to exclude geo/rank
85+
query = query.select('date', 'technology', 'lighthouse');
86+
87+
// Execute query
10588
const snapshot = await query.get();
10689
const data = [];
10790
snapshot.forEach(doc => {

src/controllers/pageWeightController.js

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
const TABLE = 'page_weight';
1212

1313
/**
14-
* List Page Weight data with filtering - Optimized version
14+
* List Page Weight data with filtering
1515
*/
1616
const listPageWeightData = async (req, res) => {
1717
try {
@@ -34,7 +34,6 @@ const listPageWeightData = async (req, res) => {
3434
// Fast preprocessing - handle 'latest' date and technology array
3535
const techArray = params.technology ? decodeURIComponent(params.technology).split(',') : [];
3636

37-
// Handle 'latest' date with caching
3837
let startDate = params.start;
3938
if (startDate === 'latest') {
4039
startDate = await getLatestDate(firestore, TABLE);
@@ -58,50 +57,34 @@ const listPageWeightData = async (req, res) => {
5857
return;
5958
}
6059

61-
// Build optimized query
60+
// Build query
6261
let query = firestore.collection(TABLE);
6362

6463
// Apply required filters
6564
query = query.where('geo', '==', params.geo);
6665
query = query.where('rank', '==', params.rank);
6766

68-
// Apply technology filter efficiently
67+
// Apply technology filter
6968
if (techArray.length <= 30) {
7069
// Use 'in' operator for batch processing (Firestore limit: 30 values)
7170
query = query.where('technology', 'in', techArray);
7271
} else {
73-
// Parallel queries for >30 technologies (rare case)
74-
const queryPromises = techArray.map(async (technology) => {
75-
let individualQuery = firestore.collection(TABLE)
76-
.where('geo', '==', params.geo)
77-
.where('rank', '==', params.rank)
78-
.where('technology', '==', technology);
79-
80-
if (startDate) individualQuery = individualQuery.where('date', '>=', startDate);
81-
if (params.end) individualQuery = individualQuery.where('date', '<=', params.end);
82-
83-
const snapshot = await individualQuery.get();
84-
const results = [];
85-
snapshot.forEach(doc => results.push(doc.data()));
86-
return results;
87-
});
88-
89-
const results = await Promise.all(queryPromises);
90-
const data = results.flat();
91-
92-
// Cache the result
93-
setCachedQueryResult(cacheKey, data);
94-
95-
res.statusCode = 200;
96-
res.end(JSON.stringify(data));
72+
res.statusCode = 400;
73+
res.end(JSON.stringify({
74+
success: false,
75+
errors: [{ technology: 'Too many technologies specified. Maximum 30 allowed.' }]
76+
}));
9777
return;
9878
}
9979

10080
// Apply date filters
10181
if (startDate) query = query.where('date', '>=', startDate);
10282
if (params.end) query = query.where('date', '<=', params.end);
10383

104-
// Execute single optimized query
84+
// Apply field projection to exclude geo/rank and select only needed fields
85+
query = query.select('date', 'technology', 'pageWeight');
86+
87+
// Execute query
10588
const snapshot = await query.get();
10689
const data = [];
10790
snapshot.forEach(doc => {

0 commit comments

Comments
 (0)