Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement retry options #575

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/commands/query.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ async function queryCommand(argv) {
performanceHints,
color,
include,
maxAttempts,
maxBackoff,
maxContentionRetries,
} = argv;

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

if (include.length > 0) {
if (include && include.length > 0) {
const queryInfo = formatQueryInfo(results, {
apiVersion,
color: useColor,
Expand Down
23 changes: 17 additions & 6 deletions src/commands/shell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,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 @@ -179,9 +190,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 @@ -190,10 +198,13 @@ async function buildCustomEval(argv) {
typecheck,
performanceHints,
format: outputFormat,
maxAttempts,
maxBackoff,
maxContentionRetries,
});

// If any query info should be displayed, print to stderr.
if (include.length > 0) {
if (include && include.length > 0) {
const queryInfo = formatQueryInfo(res, {
apiVersion,
color,
Expand Down
27 changes: 24 additions & 3 deletions src/lib/fauna-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,36 @@ 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 +98,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 @@ -124,11 +124,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.",
},
};
34 changes: 28 additions & 6 deletions test/commands/query/v10.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -122,25 +122,47 @@ describe("query v10", function () {
);
});

it("can set the typecheck option to true", async function () {
await run(`query "Database.all()" --typecheck --secret=foo`, container);
it("can set various query options", async function () {
await run(
`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,
}),
);
});

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

it("can set the maxBackoff option to true", async function () {
await run(
`query "Database.all()" --max-backoff 2000 --secret=foo`,
container,
);
expect(runQueryFromString).to.have.been.calledWith(
'"Database.all()"',
sinon.match({
maxBackoff: 2000,
}),
);
});
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 several 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
Loading