@firebaseextensions/firestore-bigquery-change-tracker 1.1.15 → 1.1.17
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.
- package/lib/bigquery/checkUpdates.js +53 -0
- package/lib/bigquery/index.js +63 -43
- package/lib/bigquery/partitioning.js +4 -3
- package/lib/bigquery/schema.js +12 -0
- package/lib/bigquery/snapshot.js +64 -27
- package/lib/logs.js +7 -2
- package/package.json +3 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.viewRequiresUpdate = exports.tableRequiresUpdate = void 0;
|
|
4
|
+
const partitioning_1 = require("./partitioning");
|
|
5
|
+
async function tableRequiresUpdate({ table, config, documentIdColExists, pathParamsColExists, }) {
|
|
6
|
+
/* Setup checks */
|
|
7
|
+
const { metadata } = table;
|
|
8
|
+
/** Check clustering */
|
|
9
|
+
const configCluster = JSON.stringify(config.clustering);
|
|
10
|
+
const tableCluster = JSON.stringify(metadata.clustering?.fields || []);
|
|
11
|
+
if (configCluster !== tableCluster)
|
|
12
|
+
return true;
|
|
13
|
+
/** Check wildcards */
|
|
14
|
+
if (!!config.wildcardIds !== pathParamsColExists)
|
|
15
|
+
return true;
|
|
16
|
+
/** Check document id column */
|
|
17
|
+
if (!documentIdColExists)
|
|
18
|
+
return true;
|
|
19
|
+
/** Check partitioning */
|
|
20
|
+
const partitioning = new partitioning_1.Partitioning(config, table);
|
|
21
|
+
const isValidPartition = await partitioning.isValidPartitionForExistingTable();
|
|
22
|
+
if (isValidPartition)
|
|
23
|
+
return true;
|
|
24
|
+
// No updates have occured.
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
exports.tableRequiresUpdate = tableRequiresUpdate;
|
|
28
|
+
function viewRequiresUpdate({ metadata, config, documentIdColExists, pathParamsColExists, }) {
|
|
29
|
+
/** Check if documentId column exists */
|
|
30
|
+
if (!documentIdColExists)
|
|
31
|
+
return true;
|
|
32
|
+
/** Check wildcards */
|
|
33
|
+
if (!!config.wildcardIds !== pathParamsColExists)
|
|
34
|
+
return true;
|
|
35
|
+
/** Check document id column */
|
|
36
|
+
if (!documentIdColExists)
|
|
37
|
+
return true;
|
|
38
|
+
/* Using the new query syntax for snapshots */
|
|
39
|
+
if (metadata) {
|
|
40
|
+
const query = metadata.view?.query || "";
|
|
41
|
+
const hasLegacyQuery = query.includes("FIRST_VALUE");
|
|
42
|
+
const { useNewSnapshotQuerySyntax } = config;
|
|
43
|
+
/** If enabled and has legacy query, can update */
|
|
44
|
+
if (useNewSnapshotQuerySyntax && hasLegacyQuery)
|
|
45
|
+
return true;
|
|
46
|
+
/** If not enabled and has an updated query, can update */
|
|
47
|
+
if (!useNewSnapshotQuerySyntax && !hasLegacyQuery)
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
// No updates have occured.
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
exports.viewRequiresUpdate = viewRequiresUpdate;
|
package/lib/bigquery/index.js
CHANGED
|
@@ -27,6 +27,7 @@ const tracker_1 = require("../tracker");
|
|
|
27
27
|
const logs = require("../logs");
|
|
28
28
|
const partitioning_1 = require("./partitioning");
|
|
29
29
|
const clustering_1 = require("./clustering");
|
|
30
|
+
const checkUpdates_1 = require("./checkUpdates");
|
|
30
31
|
var schema_2 = require("./schema");
|
|
31
32
|
Object.defineProperty(exports, "RawChangelogSchema", { enumerable: true, get: function () { return schema_2.RawChangelogSchema; } });
|
|
32
33
|
Object.defineProperty(exports, "RawChangelogViewSchema", { enumerable: true, get: function () { return schema_2.RawChangelogViewSchema; } });
|
|
@@ -42,7 +43,7 @@ Object.defineProperty(exports, "RawChangelogViewSchema", { enumerable: true, get
|
|
|
42
43
|
class FirestoreBigQueryEventHistoryTracker {
|
|
43
44
|
constructor(config) {
|
|
44
45
|
this.config = config;
|
|
45
|
-
this.
|
|
46
|
+
this._initialized = false;
|
|
46
47
|
this.bq = new bigquery.BigQuery();
|
|
47
48
|
this.bq.projectId = config.bqProjectId || process.env.PROJECT_ID;
|
|
48
49
|
if (!this.config.datasetLocation) {
|
|
@@ -64,6 +65,9 @@ class FirestoreBigQueryEventHistoryTracker {
|
|
|
64
65
|
document_id: event.documentId,
|
|
65
66
|
operation: tracker_1.ChangeType[event.operation],
|
|
66
67
|
data: JSON.stringify(this.serializeData(event.data)),
|
|
68
|
+
old_data: event.oldData
|
|
69
|
+
? JSON.stringify(this.serializeData(event.oldData))
|
|
70
|
+
: null,
|
|
67
71
|
...partitionValue,
|
|
68
72
|
...(this.config.wildcardIds &&
|
|
69
73
|
event.pathParams && { path_params: JSON.stringify(pathParams) }),
|
|
@@ -138,16 +142,25 @@ class FirestoreBigQueryEventHistoryTracker {
|
|
|
138
142
|
* A half a second delay is added per check while the function
|
|
139
143
|
* continually re-checks until the referenced dataset and table become available.
|
|
140
144
|
*/
|
|
141
|
-
async waitForInitialization(
|
|
145
|
+
async waitForInitialization() {
|
|
142
146
|
return new Promise((resolve) => {
|
|
143
147
|
let handle = setInterval(async () => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
try {
|
|
149
|
+
const dataset = this.bigqueryDataset();
|
|
150
|
+
const changelogName = this.rawChangeLogTableName();
|
|
151
|
+
const table = dataset.table(changelogName);
|
|
152
|
+
const [datasetExists] = await dataset.exists();
|
|
153
|
+
const [tableExists] = await table.exists();
|
|
154
|
+
if (datasetExists && tableExists) {
|
|
155
|
+
clearInterval(handle);
|
|
156
|
+
return resolve(table);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (ex) {
|
|
147
160
|
clearInterval(handle);
|
|
148
|
-
|
|
161
|
+
logs.failedToInitializeWait(ex.message);
|
|
149
162
|
}
|
|
150
|
-
},
|
|
163
|
+
}, 5000);
|
|
151
164
|
});
|
|
152
165
|
}
|
|
153
166
|
/**
|
|
@@ -163,7 +176,6 @@ class FirestoreBigQueryEventHistoryTracker {
|
|
|
163
176
|
try {
|
|
164
177
|
const dataset = this.bigqueryDataset();
|
|
165
178
|
const table = dataset.table(this.rawChangeLogTableName());
|
|
166
|
-
await this.waitForInitialization(dataset, table);
|
|
167
179
|
logs.dataInserting(rows.length);
|
|
168
180
|
await table.insert(rows, options);
|
|
169
181
|
logs.dataInserted(rows.length);
|
|
@@ -179,7 +191,7 @@ class FirestoreBigQueryEventHistoryTracker {
|
|
|
179
191
|
await handleFailedTransactions_1.default(rows, this.config, e);
|
|
180
192
|
}
|
|
181
193
|
// Reinitializing in case the destintation table is modified.
|
|
182
|
-
this.
|
|
194
|
+
this._initialized = false;
|
|
183
195
|
logs.bigQueryTableInsertErrors(e.errors);
|
|
184
196
|
throw e;
|
|
185
197
|
}
|
|
@@ -189,13 +201,19 @@ class FirestoreBigQueryEventHistoryTracker {
|
|
|
189
201
|
* After the first invokation, it skips initialization assuming these resources are still there.
|
|
190
202
|
*/
|
|
191
203
|
async initialize() {
|
|
192
|
-
|
|
193
|
-
|
|
204
|
+
try {
|
|
205
|
+
if (this._initialized) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
await this.initializeDataset();
|
|
209
|
+
await this.initializeRawChangeLogTable();
|
|
210
|
+
await this.initializeLatestView();
|
|
211
|
+
this._initialized = true;
|
|
212
|
+
}
|
|
213
|
+
catch (ex) {
|
|
214
|
+
await this.waitForInitialization();
|
|
215
|
+
this._initialized = true;
|
|
194
216
|
}
|
|
195
|
-
await this.initializeDataset();
|
|
196
|
-
await this.initializeRawChangeLogTable();
|
|
197
|
-
await this.initializeLatestView();
|
|
198
|
-
this.initialized = true;
|
|
199
217
|
}
|
|
200
218
|
/**
|
|
201
219
|
* Creates the specified dataset if it doesn't already exists.
|
|
@@ -244,7 +262,14 @@ class FirestoreBigQueryEventHistoryTracker {
|
|
|
244
262
|
logs.addNewColumn(this.rawChangeLogTableName(), schema_1.documentPathParams.name);
|
|
245
263
|
}
|
|
246
264
|
await partitioning.addPartitioningToSchema(metadata.schema.fields);
|
|
247
|
-
|
|
265
|
+
/** Updated table metadata if required */
|
|
266
|
+
const shouldUpdate = await checkUpdates_1.tableRequiresUpdate({
|
|
267
|
+
table,
|
|
268
|
+
config: this.config,
|
|
269
|
+
documentIdColExists,
|
|
270
|
+
pathParamsColExists,
|
|
271
|
+
});
|
|
272
|
+
if (shouldUpdate) {
|
|
248
273
|
await table.setMetadata(metadata);
|
|
249
274
|
}
|
|
250
275
|
}
|
|
@@ -279,7 +304,6 @@ class FirestoreBigQueryEventHistoryTracker {
|
|
|
279
304
|
const view = dataset.table(this.rawLatestView());
|
|
280
305
|
const [viewExists] = await view.exists();
|
|
281
306
|
const schema = schema_1.RawChangelogViewSchema;
|
|
282
|
-
const partitioning = new partitioning_1.Partitioning(this.config, view);
|
|
283
307
|
if (viewExists) {
|
|
284
308
|
logs.bigQueryViewAlreadyExists(view.id, dataset.id);
|
|
285
309
|
const [metadata] = await view.getMetadata();
|
|
@@ -289,45 +313,41 @@ class FirestoreBigQueryEventHistoryTracker {
|
|
|
289
313
|
}
|
|
290
314
|
const documentIdColExists = fields.find((column) => column.name === "document_id");
|
|
291
315
|
const pathParamsColExists = fields.find((column) => column.name === "path_params");
|
|
292
|
-
|
|
293
|
-
|
|
316
|
+
/** If new view or opt-in to new query syntax **/
|
|
317
|
+
const updateView = checkUpdates_1.viewRequiresUpdate({
|
|
318
|
+
metadata,
|
|
319
|
+
config: this.config,
|
|
320
|
+
documentIdColExists,
|
|
321
|
+
pathParamsColExists,
|
|
322
|
+
});
|
|
323
|
+
if (updateView) {
|
|
324
|
+
metadata.view = snapshot_1.latestConsistentSnapshotView({
|
|
325
|
+
datasetId: this.config.datasetId,
|
|
326
|
+
tableName: this.rawChangeLogTableName(),
|
|
327
|
+
schema,
|
|
328
|
+
useLegacyQuery: !this.config.useNewSnapshotQuerySyntax,
|
|
329
|
+
});
|
|
294
330
|
logs.addNewColumn(this.rawLatestView(), schema_1.documentIdField.name);
|
|
331
|
+
await view.setMetadata(metadata);
|
|
295
332
|
}
|
|
296
|
-
if (!pathParamsColExists && this.config.wildcardIds) {
|
|
297
|
-
metadata.view = snapshot_1.latestConsistentSnapshotView(this.config.datasetId, this.rawChangeLogTableName(), schema);
|
|
298
|
-
logs.addNewColumn(this.rawLatestView(), schema_1.documentPathParams.name);
|
|
299
|
-
}
|
|
300
|
-
//Add partitioning
|
|
301
|
-
await partitioning.addPartitioningToSchema(schema.fields);
|
|
302
|
-
//TODO: Tidy up and format / add test cases?
|
|
303
|
-
// if (
|
|
304
|
-
// !documentIdColExists ||
|
|
305
|
-
// (!pathParamsColExists && this.config.wildcardIds) ||
|
|
306
|
-
// partition.isValidPartitionForExistingTable(partitionColExists)
|
|
307
|
-
// ) {
|
|
308
|
-
await view.setMetadata(metadata);
|
|
309
|
-
// }
|
|
310
333
|
}
|
|
311
334
|
else {
|
|
312
335
|
const schema = { fields: [...schema_1.RawChangelogViewSchema.fields] };
|
|
313
|
-
//Add partitioning field
|
|
314
|
-
await partitioning.addPartitioningToSchema(schema.fields);
|
|
315
|
-
//TODO Create notification for a user that View cannot be Time Partitioned by the field.
|
|
316
|
-
// await partitioning.updateTableMetadata(options);
|
|
317
336
|
if (this.config.wildcardIds) {
|
|
318
337
|
schema.fields.push(schema_1.documentPathParams);
|
|
319
338
|
}
|
|
320
|
-
const latestSnapshot = snapshot_1.latestConsistentSnapshotView(
|
|
339
|
+
const latestSnapshot = snapshot_1.latestConsistentSnapshotView({
|
|
340
|
+
datasetId: this.config.datasetId,
|
|
341
|
+
tableName: this.rawChangeLogTableName(),
|
|
342
|
+
schema,
|
|
343
|
+
bqProjectId: this.bq.projectId,
|
|
344
|
+
useLegacyQuery: !this.config.useNewSnapshotQuerySyntax,
|
|
345
|
+
});
|
|
321
346
|
logs.bigQueryViewCreating(this.rawLatestView(), latestSnapshot.query);
|
|
322
347
|
const options = {
|
|
323
348
|
friendlyName: this.rawLatestView(),
|
|
324
349
|
view: latestSnapshot,
|
|
325
350
|
};
|
|
326
|
-
if (this.config.timePartitioning) {
|
|
327
|
-
options.timePartitioning = {
|
|
328
|
-
type: this.config.timePartitioning,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
351
|
try {
|
|
332
352
|
await view.create(options);
|
|
333
353
|
await view.setMetadata({ schema: schema_1.RawChangelogViewSchema });
|
|
@@ -56,7 +56,7 @@ class Partitioning {
|
|
|
56
56
|
const hasNoCustomOptions = !timePartitioningField &&
|
|
57
57
|
!timePartitioningFieldType &&
|
|
58
58
|
!timePartitioningFirestoreField;
|
|
59
|
-
/* No custom
|
|
59
|
+
/* No custom config has been set, use partition value option only */
|
|
60
60
|
if (hasNoCustomOptions)
|
|
61
61
|
return true;
|
|
62
62
|
/* check if all options have been provided to be */
|
|
@@ -107,8 +107,9 @@ class Partitioning {
|
|
|
107
107
|
return schemaFields.some((column) => column.name === this.config.timePartitioningField);
|
|
108
108
|
}
|
|
109
109
|
async isValidPartitionForExistingTable() {
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
const isPartitioned = await this.isTablePartitioned();
|
|
111
|
+
if (isPartitioned)
|
|
112
|
+
return Promise.resolve(false);
|
|
112
113
|
return this.hasValidCustomPartitionConfig();
|
|
113
114
|
}
|
|
114
115
|
isValidPartitionForNewTable() {
|
package/lib/bigquery/schema.js
CHANGED
|
@@ -79,6 +79,12 @@ exports.RawChangelogViewSchema = {
|
|
|
79
79
|
type: "STRING",
|
|
80
80
|
description: "The full JSON representation of the current document state.",
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
name: "old_data",
|
|
84
|
+
mode: "NULLABLE",
|
|
85
|
+
type: "STRING",
|
|
86
|
+
description: "The full JSON representation of the document state before the indicated operation is applied.",
|
|
87
|
+
},
|
|
82
88
|
exports.documentIdField,
|
|
83
89
|
],
|
|
84
90
|
};
|
|
@@ -114,6 +120,12 @@ exports.RawChangelogSchema = {
|
|
|
114
120
|
type: "STRING",
|
|
115
121
|
description: "The full JSON representation of the document state after the indicated operation is applied. This field will be null for DELETE operations.",
|
|
116
122
|
},
|
|
123
|
+
{
|
|
124
|
+
name: "old_data",
|
|
125
|
+
mode: "NULLABLE",
|
|
126
|
+
type: "STRING",
|
|
127
|
+
description: "The full JSON representation of the document state before the indicated operation is applied. This field will be null for CREATE operations.",
|
|
128
|
+
},
|
|
117
129
|
exports.documentIdField,
|
|
118
130
|
],
|
|
119
131
|
};
|
package/lib/bigquery/snapshot.js
CHANGED
|
@@ -19,49 +19,86 @@ exports.buildLatestSnapshotViewQuery = exports.latestConsistentSnapshotView = vo
|
|
|
19
19
|
const sqlFormatter = require("sql-formatter");
|
|
20
20
|
const schema_1 = require("./schema");
|
|
21
21
|
const excludeFields = ["document_name", "document_id"];
|
|
22
|
-
exports.latestConsistentSnapshotView = (datasetId, tableName, schema, bqProjectId) => ({
|
|
23
|
-
query: buildLatestSnapshotViewQuery(
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
exports.latestConsistentSnapshotView = ({ datasetId, tableName, schema, bqProjectId, useLegacyQuery = false, }) => ({
|
|
23
|
+
query: buildLatestSnapshotViewQuery({
|
|
24
|
+
datasetId,
|
|
25
|
+
tableName,
|
|
26
|
+
timestampColumnName: schema_1.timestampField.name,
|
|
27
|
+
groupByColumns: schema["fields"]
|
|
28
|
+
.map((field) => field.name)
|
|
29
|
+
.filter((name) => excludeFields.indexOf(name) === -1),
|
|
30
|
+
bqProjectId,
|
|
31
|
+
useLegacyQuery,
|
|
32
|
+
}),
|
|
26
33
|
useLegacySql: false,
|
|
27
34
|
});
|
|
28
|
-
function buildLatestSnapshotViewQuery(datasetId, tableName, timestampColumnName, groupByColumns, bqProjectId) {
|
|
35
|
+
function buildLatestSnapshotViewQuery({ datasetId, tableName, timestampColumnName, groupByColumns, bqProjectId, useLegacyQuery = true, }) {
|
|
29
36
|
if (datasetId === "" || tableName === "" || timestampColumnName === "") {
|
|
30
37
|
throw Error(`Missing some query parameters!`);
|
|
31
38
|
}
|
|
32
|
-
for (let columnName
|
|
39
|
+
for (let columnName of groupByColumns) {
|
|
33
40
|
if (columnName === "") {
|
|
34
41
|
throw Error(`Found empty group by column!`);
|
|
35
42
|
}
|
|
36
43
|
}
|
|
44
|
+
const legacyQuery = sqlFormatter.format(` -- Retrieves the latest document change events for all live documents.
|
|
45
|
+
-- timestamp: The Firestore timestamp at which the event took place.
|
|
46
|
+
-- operation: One of INSERT, UPDATE, DELETE, IMPORT.
|
|
47
|
+
-- event_id: The id of the event that triggered the cloud function mirrored the event.
|
|
48
|
+
-- data: A raw JSON payload of the current state of the document.
|
|
49
|
+
-- document_id: The document id as defined in the Firestore database
|
|
50
|
+
SELECT
|
|
51
|
+
document_name,
|
|
52
|
+
document_id${groupByColumns.length > 0 ? `,` : ``}
|
|
53
|
+
${groupByColumns.join(",")}
|
|
54
|
+
FROM (
|
|
55
|
+
SELECT
|
|
56
|
+
document_name,
|
|
57
|
+
document_id,
|
|
58
|
+
${groupByColumns
|
|
59
|
+
.map((columnName) => `FIRST_VALUE(${columnName})
|
|
60
|
+
OVER(PARTITION BY document_name ORDER BY ${timestampColumnName} DESC)
|
|
61
|
+
AS ${columnName}`)
|
|
62
|
+
.join(",")}${groupByColumns.length > 0 ? `,` : ``}
|
|
63
|
+
FIRST_VALUE(operation)
|
|
64
|
+
OVER(PARTITION BY document_name ORDER BY ${timestampColumnName} DESC) = "DELETE"
|
|
65
|
+
AS is_deleted
|
|
66
|
+
FROM \`${bqProjectId || process.env.PROJECT_ID}.${datasetId}.${tableName}\`
|
|
67
|
+
ORDER BY document_name, ${timestampColumnName} DESC
|
|
68
|
+
)
|
|
69
|
+
WHERE NOT is_deleted
|
|
70
|
+
GROUP BY document_name, document_id${groupByColumns.length > 0 ? `, ` : ``}${groupByColumns.join(",")}`);
|
|
71
|
+
const nonGroupFields = ["event_id", "data", "old_data"];
|
|
72
|
+
const joinFields = ["document_name"];
|
|
73
|
+
const addSelectField = (field) => {
|
|
74
|
+
if (joinFields.includes(field))
|
|
75
|
+
return `t.${field}`;
|
|
76
|
+
return nonGroupFields.includes(field)
|
|
77
|
+
? `ANY_VALUE(${field}) as ${field}`
|
|
78
|
+
: `${field} as ${field}`;
|
|
79
|
+
};
|
|
80
|
+
const filterGroupField = (field) => {
|
|
81
|
+
return nonGroupFields.includes(field);
|
|
82
|
+
};
|
|
37
83
|
const query = sqlFormatter.format(` -- Retrieves the latest document change events for all live documents.
|
|
38
84
|
-- timestamp: The Firestore timestamp at which the event took place.
|
|
39
85
|
-- operation: One of INSERT, UPDATE, DELETE, IMPORT.
|
|
40
86
|
-- event_id: The id of the event that triggered the cloud function mirrored the event.
|
|
41
87
|
-- data: A raw JSON payload of the current state of the document.
|
|
42
88
|
-- document_id: The document id as defined in the Firestore database
|
|
89
|
+
WITH latest AS (
|
|
90
|
+
SELECT max(${timestampColumnName}) as latest_timestamp, document_name
|
|
91
|
+
FROM \`${bqProjectId || process.env.PROJECT_ID}.${datasetId}.${tableName}\`
|
|
92
|
+
GROUP BY document_name
|
|
93
|
+
)
|
|
43
94
|
SELECT
|
|
44
|
-
document_name,
|
|
95
|
+
t.document_name,
|
|
45
96
|
document_id${groupByColumns.length > 0 ? `,` : ``}
|
|
46
|
-
${groupByColumns.join(",")}
|
|
47
|
-
FROM
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.map((columnName) => `FIRST_VALUE(${columnName})
|
|
53
|
-
OVER(PARTITION BY document_name ORDER BY ${timestampColumnName} DESC)
|
|
54
|
-
AS ${columnName}`)
|
|
55
|
-
.join(",")}${groupByColumns.length > 0 ? `,` : ``}
|
|
56
|
-
FIRST_VALUE(operation)
|
|
57
|
-
OVER(PARTITION BY document_name ORDER BY ${timestampColumnName} DESC) = "DELETE"
|
|
58
|
-
AS is_deleted
|
|
59
|
-
FROM \`${bqProjectId ||
|
|
60
|
-
process.env.PROJECT_ID}.${datasetId}.${tableName}\`
|
|
61
|
-
ORDER BY document_name, ${timestampColumnName} DESC
|
|
62
|
-
)
|
|
63
|
-
WHERE NOT is_deleted
|
|
64
|
-
GROUP BY document_name, document_id${groupByColumns.length > 0 ? `, ` : ``}${groupByColumns.join(",")}`);
|
|
65
|
-
return query;
|
|
97
|
+
${groupByColumns.map((f) => addSelectField(f)).join(",")}
|
|
98
|
+
FROM \`${bqProjectId || process.env.PROJECT_ID}.${datasetId}.${tableName}\` AS t
|
|
99
|
+
JOIN latest ON (t.document_name = latest.document_name AND (IFNULL(t.${timestampColumnName}, timestamp("1970-01-01 00:00:00+00"))) = (IFNULL(latest.latest_timestamp, timestamp("1970-01-01 00:00:00+00"))))
|
|
100
|
+
WHERE operation != "DELETE"
|
|
101
|
+
GROUP BY document_name, document_id${groupByColumns.length > 0 ? `, ` : ``}${groupByColumns.filter((c) => !filterGroupField(c)).join(",")}`);
|
|
102
|
+
return useLegacyQuery ? legacyQuery : query;
|
|
66
103
|
}
|
|
67
104
|
exports.buildLatestSnapshotViewQuery = buildLatestSnapshotViewQuery;
|
package/lib/logs.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.tableCreationError = exports.invalidClustering = exports.hourAndDatePartitioningWarning = exports.invalidTableReference = exports.invalidProjectIdWarning = exports.cannotPartitionExistingTable = exports.removedClustering = exports.updatedClustering = exports.bigQueryTableInsertErrors = exports.firestoreTimePartitioningParametersWarning = exports.firestoreTimePartitionFieldError = exports.addPartitionFieldColumn = exports.addNewColumn = exports.timestampMissingValue = exports.error = exports.dataTypeInvalid = exports.dataInserting = exports.dataInsertRetried = exports.dataInserted = exports.complete = exports.bigQueryViewValidating = exports.bigQueryViewValidated = exports.bigQueryViewUpToDate = exports.bigQueryViewUpdating = exports.bigQueryViewUpdated = exports.bigQueryViewAlreadyExists = exports.bigQueryViewCreating = exports.bigQueryViewCreated = exports.bigQueryUserDefinedFunctionCreated = exports.bigQueryUserDefinedFunctionCreating = exports.bigQueryTableValidating = exports.bigQueryTableValidated = exports.bigQueryTableUpToDate = exports.bigQueryTableUpdating = exports.bigQueryTableUpdated = exports.bigQueryTableCreating = exports.bigQueryTableCreated = exports.bigQueryTableAlreadyExists = exports.bigQuerySchemaViewCreated = exports.bigQueryLatestSnapshotViewQueryCreated = exports.bigQueryErrorRecordingDocumentChange = exports.bigQueryDatasetExists = exports.bigQueryDatasetCreating = exports.bigQueryDatasetCreated = exports.arrayFieldInvalid = void 0;
|
|
18
|
+
exports.failedToInitializeWait = exports.tableCreationError = exports.invalidClustering = exports.hourAndDatePartitioningWarning = exports.invalidTableReference = exports.invalidProjectIdWarning = exports.cannotPartitionExistingTable = exports.removedClustering = exports.updatedClustering = exports.bigQueryTableInsertErrors = exports.firestoreTimePartitioningParametersWarning = exports.firestoreTimePartitionFieldError = exports.addPartitionFieldColumn = exports.addNewColumn = exports.timestampMissingValue = exports.error = exports.dataTypeInvalid = exports.dataInserting = exports.dataInsertRetried = exports.dataInserted = exports.complete = exports.bigQueryViewValidating = exports.bigQueryViewValidated = exports.bigQueryViewUpToDate = exports.bigQueryViewUpdating = exports.bigQueryViewUpdated = exports.bigQueryViewAlreadyExists = exports.bigQueryViewCreating = exports.bigQueryViewCreated = exports.bigQueryUserDefinedFunctionCreated = exports.bigQueryUserDefinedFunctionCreating = exports.bigQueryTableValidating = exports.bigQueryTableValidated = exports.bigQueryTableUpToDate = exports.bigQueryTableUpdating = exports.bigQueryTableUpdated = exports.bigQueryTableCreating = exports.bigQueryTableCreated = exports.bigQueryTableAlreadyExists = exports.bigQuerySchemaViewCreated = exports.bigQueryLatestSnapshotViewQueryCreated = exports.bigQueryErrorRecordingDocumentChange = exports.bigQueryDatasetExists = exports.bigQueryDatasetCreating = exports.bigQueryDatasetCreated = exports.arrayFieldInvalid = void 0;
|
|
19
19
|
const firebase_functions_1 = require("firebase-functions");
|
|
20
20
|
exports.arrayFieldInvalid = (fieldName) => {
|
|
21
21
|
firebase_functions_1.logger.warn(`Array field '${fieldName}' does not contain an array, skipping`);
|
|
@@ -137,7 +137,9 @@ exports.bigQueryTableInsertErrors = (insertErrors) => {
|
|
|
137
137
|
insertErrors.forEach((error) => {
|
|
138
138
|
firebase_functions_1.logger.warn("ROW DATA JSON:");
|
|
139
139
|
firebase_functions_1.logger.warn(error.row);
|
|
140
|
-
|
|
140
|
+
if (error && error.errors) {
|
|
141
|
+
error.errors.forEach((error) => firebase_functions_1.logger.warn(`ROW ERROR MESSAGE: ${error.message}`));
|
|
142
|
+
}
|
|
141
143
|
});
|
|
142
144
|
};
|
|
143
145
|
exports.updatedClustering = (fields) => {
|
|
@@ -168,3 +170,6 @@ exports.invalidClustering = invalidClustering;
|
|
|
168
170
|
exports.tableCreationError = (table, message) => {
|
|
169
171
|
firebase_functions_1.logger.warn(`Error caught creating table`, message);
|
|
170
172
|
};
|
|
173
|
+
exports.failedToInitializeWait = (message) => {
|
|
174
|
+
firebase_functions_1.logger.warn(`Failed while waiting to initialize.`, message);
|
|
175
|
+
};
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"url": "github.com/firebase/extensions.git",
|
|
6
6
|
"directory": "firestore-bigquery-export/firestore-bigquery-change-tracker"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.1.
|
|
8
|
+
"version": "1.1.17",
|
|
9
9
|
"description": "Core change-tracker library for Cloud Firestore Collection BigQuery Exports",
|
|
10
10
|
"main": "./lib/index.js",
|
|
11
11
|
"scripts": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"clean": "rimraf lib",
|
|
14
14
|
"compile": "tsc",
|
|
15
15
|
"test:local": "firebase ext:dev:emulators:exec ./node_modules/.bin/jest --test-params=./src/__tests__/emulator-params.env --project=extensions-testing --config=./src/__tests__/firebase.json",
|
|
16
|
-
"prepare": "npm run build"
|
|
16
|
+
"prepare": "npm run build",
|
|
17
|
+
"generate-stresstest-table": "bq query --project_id=extensions-testing --use_legacy_sql=false < ./src/__tests__/fixtures/sql/generateSnapshotStresstestTable.sql"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"lib/*.js",
|