bulltrackers-module 1.0.715 → 1.0.716
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -555,15 +555,15 @@ async function writeToBigQuery(result, name, dateContext, category, logger, isAl
|
|
|
555
555
|
|
|
556
556
|
const datasetId = process.env.BIGQUERY_DATASET_ID || 'bulltrackers_data';
|
|
557
557
|
|
|
558
|
-
// Use
|
|
559
|
-
//
|
|
560
|
-
|
|
558
|
+
// Use MERGE operation to overwrite existing results (by date + computation_name + category)
|
|
559
|
+
// This ensures re-running a computation overwrites the old result
|
|
560
|
+
// Key fields: date, computation_name, category (ignoring created_at)
|
|
561
|
+
const { insertRowsWithMerge } = require('../../core/utils/bigquery_utils');
|
|
562
|
+
const keyFields = ['date', 'computation_name', 'category'];
|
|
561
563
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
await insertRowsLoadJob(datasetId, 'computation_results', [row], logger);
|
|
566
|
-
}
|
|
564
|
+
// For alert computations, we still want to use MERGE but it will use load jobs (free)
|
|
565
|
+
// This ensures overwrites work correctly for both alert and non-alert computations
|
|
566
|
+
await insertRowsWithMerge(datasetId, 'computation_results', [row], keyFields, logger);
|
|
567
567
|
|
|
568
568
|
} catch (error) {
|
|
569
569
|
// Log but don't throw - BigQuery write failure shouldn't break Firestore writes
|
|
@@ -663,8 +663,29 @@ function calculateFirestoreBytes(value) {
|
|
|
663
663
|
}
|
|
664
664
|
|
|
665
665
|
function calculateExpirationDate(dateStr, ttlDays) {
|
|
666
|
+
// Validate inputs
|
|
667
|
+
if (!dateStr || typeof dateStr !== 'string') {
|
|
668
|
+
return null; // Invalid date string
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (ttlDays === undefined || ttlDays === null || isNaN(Number(ttlDays))) {
|
|
672
|
+
return null; // Invalid TTL days
|
|
673
|
+
}
|
|
674
|
+
|
|
666
675
|
const base = new Date(dateStr);
|
|
667
|
-
|
|
676
|
+
|
|
677
|
+
// Check if date is valid (invalid dates have NaN getTime())
|
|
678
|
+
if (isNaN(base.getTime())) {
|
|
679
|
+
return null; // Invalid date
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
base.setDate(base.getDate() + Number(ttlDays));
|
|
683
|
+
|
|
684
|
+
// Double-check the result is still valid
|
|
685
|
+
if (isNaN(base.getTime())) {
|
|
686
|
+
return null; // Resulting date is invalid
|
|
687
|
+
}
|
|
688
|
+
|
|
668
689
|
return base;
|
|
669
690
|
}
|
|
670
691
|
|
|
@@ -196,27 +196,17 @@ async function insertRowsWithMerge(datasetId, tableId, rows, keyFields, logger =
|
|
|
196
196
|
logger.log('INFO', `[BigQuery] Loaded ${validRows.length} rows into temp table ${tempTableId} using LOAD JOB (free)`);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
// Use MERGE to insert
|
|
199
|
+
// Use MERGE to insert new rows or update existing rows (SQL-native deduplication/overwrite)
|
|
200
200
|
// This is more efficient than checking in JavaScript
|
|
201
201
|
const mergeConditions = keyFields.map(f => `target.${f} = source.${f}`).join(' AND ');
|
|
202
|
-
const mergeQuery = `
|
|
203
|
-
MERGE \`${tablePath}\` AS target
|
|
204
|
-
USING \`${tempTablePath}\` AS source
|
|
205
|
-
ON ${mergeConditions}
|
|
206
|
-
WHEN NOT MATCHED THEN
|
|
207
|
-
INSERT ROW
|
|
208
|
-
`;
|
|
209
|
-
|
|
210
|
-
await query(mergeQuery, {}, logger);
|
|
211
202
|
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
const countBefore = existingBefore[0]?.cnt || 0;
|
|
203
|
+
// Build UPDATE clause - update all non-key fields
|
|
204
|
+
const allFields = schema.map(f => f.name);
|
|
205
|
+
const nonKeyFields = allFields.filter(f => !keyFields.includes(f));
|
|
206
|
+
const updateClause = nonKeyFields.map(f => `${f} = source.${f}`).join(', ');
|
|
217
207
|
|
|
218
|
-
//
|
|
219
|
-
//
|
|
208
|
+
// Count rows that will be inserted (don't exist in target) vs updated (already exist)
|
|
209
|
+
// Query BEFORE the MERGE to get accurate counts
|
|
220
210
|
const [insertedCountResult] = await query(`
|
|
221
211
|
SELECT COUNT(*) as inserted
|
|
222
212
|
FROM \`${tempTablePath}\` AS source
|
|
@@ -227,12 +217,30 @@ async function insertRowsWithMerge(datasetId, tableId, rows, keyFields, logger =
|
|
|
227
217
|
`, {}, logger);
|
|
228
218
|
|
|
229
219
|
const rowsInserted = insertedCountResult[0]?.inserted || 0;
|
|
220
|
+
const rowsUpdated = validRows.length - rowsInserted;
|
|
221
|
+
|
|
222
|
+
// Now perform the MERGE (inserts new rows, updates existing rows)
|
|
223
|
+
const mergeQuery = `
|
|
224
|
+
MERGE \`${tablePath}\` AS target
|
|
225
|
+
USING \`${tempTablePath}\` AS source
|
|
226
|
+
ON ${mergeConditions}
|
|
227
|
+
WHEN MATCHED THEN
|
|
228
|
+
UPDATE SET ${updateClause}
|
|
229
|
+
WHEN NOT MATCHED THEN
|
|
230
|
+
INSERT ROW
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
await query(mergeQuery, {}, logger);
|
|
230
234
|
|
|
231
235
|
// Drop temp table
|
|
232
236
|
await tempTable.delete();
|
|
233
237
|
|
|
234
238
|
if (logger) {
|
|
235
|
-
|
|
239
|
+
if (rowsUpdated > 0) {
|
|
240
|
+
logger.log('INFO', `[BigQuery] MERGE completed: ${rowsInserted} new rows inserted, ${rowsUpdated} existing rows updated in ${tablePath}`);
|
|
241
|
+
} else {
|
|
242
|
+
logger.log('INFO', `[BigQuery] MERGE completed: ${rowsInserted} new rows inserted into ${tablePath} (${validRows.length - rowsInserted} duplicates skipped via SQL)`);
|
|
243
|
+
}
|
|
236
244
|
}
|
|
237
245
|
|
|
238
246
|
return rowsInserted;
|