Skip to content

Commit

Permalink
Add --disable-triggers flag to database write commands (#5179)
Browse files Browse the repository at this point in the history
* Add --disable-triggers flag to database write commands

* Update unit tests

* Add changelog

* Address PR feedback: add test, make constructor arg required
  • Loading branch information
tohhsinpei authored Oct 26, 2022
1 parent 2b4261e commit 793253f
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
- Releases RTDB Emulator v4.11.0: Wire protocol update for `startAfter`, `endBefore`.
- Changes `superstatic` dependency to `v8`, addressing Hosting emulator issues on Windows.
- Fixes internal library that was not being correctly published.
- Adds `--disable-triggers` flag to RTDB write commands.
5 changes: 5 additions & 0 deletions src/commands/database-push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const command = new Command("database:push <path> [infile]")
"--instance <instance>",
"use the database <instance>.firebaseio.com (if omitted, use default database instance)"
)
.option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
.before(requirePermissions, ["firebasedatabase.instances.update"])
.before(requireDatabaseInstance)
.before(populateInstanceDetails)
Expand All @@ -33,6 +34,9 @@ export const command = new Command("database:push <path> [infile]")
utils.stringToStream(options.data) || (infile ? fs.createReadStream(infile) : process.stdin);
const origin = realtimeOriginOrEmulatorOrCustomUrl(options.instanceDetails.databaseUrl);
const u = new URL(utils.getDatabaseUrl(origin, options.instance, path + ".json"));
if (options.disableTriggers) {
u.searchParams.set("disableTriggers", "true");
}

if (!infile && !options.data) {
utils.explainStdin();
Expand All @@ -46,6 +50,7 @@ export const command = new Command("database:push <path> [infile]")
method: "POST",
path: u.pathname,
body: inStream,
queryParams: u.searchParams,
});
} catch (err: any) {
logger.debug(err);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/database-remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const command = new Command("database:remove <path>")
"--instance <instance>",
"use the database <instance>.firebaseio.com (if omitted, use default database instance)"
)
.option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
.before(requirePermissions, ["firebasedatabase.instances.update"])
.before(requireDatabaseInstance)
.before(populateInstanceDetails)
Expand All @@ -40,7 +41,7 @@ export const command = new Command("database:remove <path>")
return utils.reject("Command aborted.", { exit: 1 });
}

const removeOps = new DatabaseRemove(options.instance, path, origin);
const removeOps = new DatabaseRemove(options.instance, path, origin, !!options.disableTriggers);
await removeOps.execute();
utils.logSuccess("Data removed successfully");
});
5 changes: 5 additions & 0 deletions src/commands/database-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const command = new Command("database:set <path> [infile]")
"--instance <instance>",
"use the database <instance>.firebaseio.com (if omitted, use default database instance)"
)
.option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
.before(requirePermissions, ["firebasedatabase.instances.update"])
.before(requireDatabaseInstance)
.before(populateInstanceDetails)
Expand All @@ -34,6 +35,9 @@ export const command = new Command("database:set <path> [infile]")
const origin = realtimeOriginOrEmulatorOrCustomUrl(options.instanceDetails.databaseUrl);
const dbPath = utils.getDatabaseUrl(origin, options.instance, path);
const dbJsonURL = new URL(utils.getDatabaseUrl(origin, options.instance, path + ".json"));
if (options.disableTriggers) {
dbJsonURL.searchParams.set("disableTriggers", "true");
}

const confirm = await promptOnce(
{
Expand Down Expand Up @@ -61,6 +65,7 @@ export const command = new Command("database:set <path> [infile]")
method: "PUT",
path: dbJsonURL.pathname,
body: inStream,
queryParams: dbJsonURL.searchParams,
});
} catch (err: any) {
logger.debug(err);
Expand Down
5 changes: 5 additions & 0 deletions src/commands/database-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const command = new Command("database:update <path> [infile]")
"--instance <instance>",
"use the database <instance>.firebaseio.com (if omitted, use default database instance)"
)
.option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
.before(requirePermissions, ["firebasedatabase.instances.update"])
.before(requireDatabaseInstance)
.before(populateInstanceDetails)
Expand Down Expand Up @@ -51,6 +52,9 @@ export const command = new Command("database:update <path> [infile]")
(infile && fs.createReadStream(infile)) ||
process.stdin;
const jsonUrl = new URL(utils.getDatabaseUrl(origin, options.instance, path + ".json"));
if (options.disableTriggers) {
jsonUrl.searchParams.set("disableTriggers", "true");
}

if (!infile && !options.data) {
utils.explainStdin();
Expand All @@ -62,6 +66,7 @@ export const command = new Command("database:update <path> [infile]")
method: "PATCH",
path: jsonUrl.pathname,
body: inStream,
queryParams: jsonUrl.searchParams,
});
} catch (err: any) {
throw new FirebaseError("Unexpected error while setting data");
Expand Down
5 changes: 3 additions & 2 deletions src/database/remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ export default class DatabaseRemove {
* @param instance RTBD instance ID.
* @param path path to delete.
* @param host db host.
* @param disableTriggers if true, suppresses any Cloud functions that would be triggered by this operation.
*/
constructor(instance: string, path: string, host: string) {
constructor(instance: string, path: string, host: string, disableTriggers: boolean) {
this.path = path;
this.remote = new RTDBRemoveRemote(instance, host);
this.remote = new RTDBRemoveRemote(instance, host, disableTriggers);
this.deleteJobStack = new Stack({
name: "delete stack",
concurrency: 1,
Expand Down
10 changes: 8 additions & 2 deletions src/database/removeRemote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ export class RTDBRemoveRemote implements RemoveRemote {
private instance: string;
private host: string;
private apiClient: Client;
private disableTriggers: boolean;

constructor(instance: string, host: string) {
constructor(instance: string, host: string, disableTriggers: boolean) {
this.instance = instance;
this.host = host;
this.disableTriggers = disableTriggers;

const url = new URL(utils.getDatabaseUrl(this.host, this.instance, "/"));
this.apiClient = new Client({ urlPrefix: url.origin, auth: true });
Expand All @@ -46,7 +48,11 @@ export class RTDBRemoveRemote implements RemoveRemote {
private async patch(path: string, body: any, note: string): Promise<boolean> {
const t0 = Date.now();
const url = new URL(utils.getDatabaseUrl(this.host, this.instance, path + ".json"));
const queryParams = { print: "silent", writeSizeLimit: "tiny" };
const queryParams = {
print: "silent",
writeSizeLimit: "tiny",
disableTriggers: this.disableTriggers.toString(),
};
const res = await this.apiClient.request({
method: "PATCH",
path: url.pathname,
Expand Down
18 changes: 14 additions & 4 deletions src/test/database/remove.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const HOST = "https://firebaseio.com";
describe("DatabaseRemove", () => {
it("should remove tiny tree", async () => {
const fakeDb = new FakeRemoveRemote({ c: 1 });
const removeOps = new DatabaseRemove("test-tiny-tree", "/", HOST);
const removeOps = new DatabaseRemove("test-tiny-tree", "/", HOST, /* disableTriggers= */ false);
removeOps.remote = fakeDb;
await removeOps.execute();
expect(fakeDb.data).to.eql(null);
Expand All @@ -29,7 +29,7 @@ describe("DatabaseRemove", () => {
const fakeList = new FakeListRemote(data);
const fakeDb = new FakeRemoveRemote(data);

const removeOps = new DatabaseRemove("test-sub-path", "/a", HOST);
const removeOps = new DatabaseRemove("test-sub-path", "/a", HOST, /* disableTriggers= */ false);
removeOps.remote = fakeDb;
removeOps.listRemote = fakeList;
await removeOps.execute();
Expand Down Expand Up @@ -57,7 +57,12 @@ describe("DatabaseRemove", () => {
const data = buildData(3, 5);
const fakeDb = new FakeRemoveRemote(data, threshold);
const fakeLister = new FakeListRemote(data);
const removeOps = new DatabaseRemove("test-nested-tree", "/", HOST);
const removeOps = new DatabaseRemove(
"test-nested-tree",
"/",
HOST,
/* disableTriggers= */ false
);
removeOps.remote = fakeDb;
removeOps.listRemote = fakeLister;
await removeOps.execute();
Expand All @@ -68,7 +73,12 @@ describe("DatabaseRemove", () => {
const data = buildData(1232, 1);
const fakeDb = new FakeRemoveRemote(data, threshold);
const fakeList = new FakeListRemote(data);
const removeOps = new DatabaseRemove("test-remover", "/", HOST);
const removeOps = new DatabaseRemove(
"test-remover",
"/",
HOST,
/* disableTriggers= */ false
);
removeOps.remote = fakeDb;
removeOps.listRemote = fakeList;
await removeOps.execute();
Expand Down
23 changes: 18 additions & 5 deletions src/test/database/removeRemote.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { RTDBRemoveRemote } from "../../database/removeRemote";
describe("RemoveRemote", () => {
const instance = "fake-db";
const host = "https://firebaseio.com";
const remote = new RTDBRemoveRemote(instance, host);
const remote = new RTDBRemoveRemote(instance, host, /* disableTriggers= */ false);
const serverUrl = utils.getDatabaseUrl(host, instance, "");

afterEach(() => {
Expand All @@ -17,15 +17,15 @@ describe("RemoveRemote", () => {
it("should return true when patch is small", () => {
nock(serverUrl)
.patch("/a/b.json")
.query({ print: "silent", writeSizeLimit: "tiny" })
.query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "false" })
.reply(200, {});
return expect(remote.deletePath("/a/b")).to.eventually.eql(true);
});

it("should return false whem patch is large", () => {
nock(serverUrl)
.patch("/a/b.json")
.query({ print: "silent", writeSizeLimit: "tiny" })
.query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "false" })
.reply(400, {
error:
"Data requested exceeds the maximum size that can be accessed with a single request.",
Expand All @@ -36,19 +36,32 @@ describe("RemoveRemote", () => {
it("should return true when multi-path patch is small", () => {
nock(serverUrl)
.patch("/a/b.json")
.query({ print: "silent", writeSizeLimit: "tiny" })
.query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "false" })
.reply(200, {});
return expect(remote.deleteSubPath("/a/b", ["1", "2", "3"])).to.eventually.eql(true);
});

it("should return false when multi-path patch is large", () => {
nock(serverUrl)
.patch("/a/b.json")
.query({ print: "silent", writeSizeLimit: "tiny" })
.query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "false" })
.reply(400, {
error:
"Data requested exceeds the maximum size that can be accessed with a single request.",
});
return expect(remote.deleteSubPath("/a/b", ["1", "2", "3"])).to.eventually.eql(false);
});

it("should send disableTriggers param", () => {
const remoteWithDisableTriggers = new RTDBRemoveRemote(
instance,
host,
/* disableTriggers= */ true
);
nock(serverUrl)
.patch("/a/b.json")
.query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "true" })
.reply(200, {});
return expect(remoteWithDisableTriggers.deletePath("/a/b")).to.eventually.eql(true);
});
});

0 comments on commit 793253f

Please sign in to comment.