Skip to content

Commit

Permalink
Implement retry options (#575)
Browse files Browse the repository at this point in the history
* Implement max-attempts and max-backoff query options for v10

* implement contention retry options
  • Loading branch information
ptpaterson authored Feb 5, 2025
1 parent a2a4ab8 commit d31d180
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 31 deletions.
6 changes: 6 additions & 0 deletions src/commands/query.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ async function queryCommand(argv) {
performanceHints,
color,
include,
maxAttempts,
maxBackoff,
maxContentionRetries,
} = argv;

// resolve the input
Expand All @@ -114,6 +117,9 @@ async function queryCommand(argv) {
performanceHints,
format: outputFormat,
color: useColor,
maxAttempts,
maxBackoff,
maxContentionRetries,
});

if (include.length > 0) {
Expand Down
21 changes: 16 additions & 5 deletions src/commands/shell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,23 @@ async function buildCustomEval(argv) {

return async (cmd, ctx, _filename, cb) => {
try {
if (cmd.trim() === "") return cb();

const logger = container.resolve("logger");

if (cmd.trim() === "") return cb();
const secret = await getSecret(argv);

// These are options used for querying and formatting the response
const { apiVersion, color } = argv;
const {
apiVersion,
color,
timeout,
typecheck,
url,
maxAttempts,
maxBackoff,
maxContentionRetries,
} = argv;
const include = getArgvOrCtx("include", argv, ctx);
const performanceHints = getArgvOrCtx("performanceHints", argv, ctx);

Expand All @@ -180,9 +191,6 @@ async function buildCustomEval(argv) {

let res;
try {
const secret = await getSecret(argv);
const { color, timeout, typecheck, url } = argv;

res = await runQueryFromString(cmd, {
apiVersion,
secret,
Expand All @@ -191,6 +199,9 @@ async function buildCustomEval(argv) {
typecheck,
performanceHints,
format: outputFormat,
maxAttempts,
maxBackoff,
maxContentionRetries,
});

// If any query info should be displayed, print to stderr.
Expand Down
26 changes: 23 additions & 3 deletions src/lib/fauna-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,35 @@ export const runQueryFromString = (expression, argv) => {
const faunaV10 = container.resolve("faunaClientV10");

if (argv.apiVersion === "4") {
const { secret, url, timeout } = argv;
const { secret, url, timeout, maxContentionRetries } = argv;
let headers;
if (maxContentionRetries) {
headers = {
"x-fauna-max-contention-retries": maxContentionRetries,
};
}
return retryInvalidCredsOnce(secret, (secret) =>
faunaV4.runQueryFromString({
expression,
secret,
url,
client: undefined,
options: { queryTimeout: timeout },
options: { queryTimeout: timeout, headers },
}),
);
} else {
const { secret, url, timeout, format, performanceHints, ...rest } = argv;
const {
secret,
url,
timeout,
format,
performanceHints,
maxAttempts,
maxBackoff,
maxContentionRetries,
...rest
} = argv;

let apiFormat = "decorated";
if (format === Format.JSON) {
apiFormat = "simple";
Expand All @@ -80,6 +97,9 @@ export const runQueryFromString = (expression, argv) => {
/* eslint-disable camelcase */
query_timeout_ms: timeout,
performance_hints: performanceHints,
max_attempts: maxAttempts,
max_backoff: maxBackoff,
max_contention_retries: maxContentionRetries,
/* eslint-enable camelcase */
format: apiFormat,
...rest,
Expand Down
23 changes: 22 additions & 1 deletion src/lib/options.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,32 @@ export const QUERY_OPTIONS = {
default: false,
group: "API:",
},
"max-attempts": {
type: "number",
description:
"Maximum number of retry attempts when queries fail with throttling errors. Only applies to v10 queries.",
default: undefined,
group: "API:",
},
"max-backoff": {
type: "number",
description:
"Maximum backoff time (in milliseconds) between retry attempts. Only applies to v10 queries.",
default: undefined,
group: "API:",
},
"max-contention-retries": {
type: "number",
description:
"Maximum number of retry attempts when queries fail with contention errors.",
default: undefined,
group: "API:",
},
include: {
type: "array",
choices: ["all", "none", ...QUERY_INFO_CHOICES],
default: ["summary"],
describe:
description:
"Query response info to output. Pass values as a space-separated list. Ex: --include summary queryTags.",
},
};
22 changes: 9 additions & 13 deletions test/commands/query/v10.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -122,25 +122,21 @@ describe("query v10", function () {
);
});

it("can set the typecheck option to true", async function () {
await run(`query "Database.all()" --typecheck --secret=foo`, container);
expect(runQueryFromString).to.have.been.calledWith(
'"Database.all()"',
sinon.match({
typecheck: true,
}),
);
});

it("can set the performanceHints option to true", async function () {
it("can set various query options", async function () {
await run(
`query "Database.all()" --performance-hints --secret=foo`,
`query "Database.all()" --secret=foo --typecheck --performance-hints --max-attempts 5 --max-backoff 2000 --timeout 10000 --max-contention-retries 3`,
container,
);

expect(runQueryFromString).to.have.been.calledWith(
'"Database.all()"',
sinon.match(""),
sinon.match({
timeout: 10000,
typecheck: true,
performanceHints: true,
maxAttempts: 5,
maxBackoff: 2000,
maxContentionRetries: 3,
}),
);
});
Expand Down
14 changes: 14 additions & 0 deletions test/commands/query/v4.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ describe("query v4", function () {
);
});

it("can set various query options", async function () {
await run(
`query "Collection('test')" --apiVersion 4 --secret=foo --timeout 10000 --max-contention-retries 3`,
container,
);
expect(runQueryFromString).to.have.been.calledWith(
sinon.match(""),
sinon.match({
timeout: 10000,
maxContentionRetries: 3,
}),
);
});

describe("query info", function () {
it("displays metrics if `--include stats` is used", async function () {
const testResponse = createV4QuerySuccess("test response");
Expand Down
26 changes: 17 additions & 9 deletions test/commands/shell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -338,25 +338,33 @@ describe("shell", function () {

it.skip("does not colorize output if --no-color is used", async function () {});

it.skip("can eval a query with typechecking enabled", async function () {
container.resolve("performV10Query").resolves(v10Object1);
it("can open a shell and run queries with options", async function () {
runQueryFromString.resolves(v10Object1);
let query = "Database.all().take(1)";

// start the shell
const runPromise = run(`shell --secret "secret" --typecheck`, container);
const runPromise = run(
"shell --secret=foo --typecheck --performance-hints --max-attempts 5 --max-backoff 2000 --timeout 10000 --max-contention-retries 3",
container,
);

// send one command
stdin.push(`${query}\n`);
stdin.push(null);
await stdout.waitForWritten();
await runPromise;

expect(container.resolve("performV10Query")).to.have.been.calledWith(
expect(runQueryFromString).to.have.been.calledWith(
sinon.match.any,
sinon.match(query),
undefined,
sinon.match({ version: "10", typecheck: true }),
sinon.match({
timeout: 10000,
typecheck: true,
performanceHints: true,
maxAttempts: 5,
maxBackoff: 2000,
maxContentionRetries: 3,
}),
);

return runPromise;
});

it("can display performance hints", async function () {
Expand Down

0 comments on commit d31d180

Please sign in to comment.