Skip to content

Commit

Permalink
[D1] initial wrangler d1 export (#5425)
Browse files Browse the repository at this point in the history
* V1 of Wrangler support for D1 export

* run prettier

* update test, fix bug

* don't read the config file twice

* feat: implement `wrangler d1 export`

---------

Co-authored-by: Glen Maddern <glen@cloudflare.com>
  • Loading branch information
rozenmd and geelen committed Mar 29, 2024
1 parent 1af469a commit b7a6d9d
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/wicked-balloons-compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

feat: implement `wrangler d1 export`
2 changes: 2 additions & 0 deletions packages/wrangler/src/__tests__/d1/d1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe("d1", () => {
wrangler d1 delete <name> Delete D1 database
wrangler d1 backup Interact with D1 Backups
wrangler d1 execute <database> Executed command or SQL file
wrangler d1 export <name> Export the contents or schema of your database as a .sql file
wrangler d1 time-travel Use Time Travel to restore, fork or copy a database at a specific point-in-time.
wrangler d1 migrations Interact with D1 Migrations
Expand Down Expand Up @@ -60,6 +61,7 @@ describe("d1", () => {
wrangler d1 delete <name> Delete D1 database
wrangler d1 backup Interact with D1 Backups
wrangler d1 execute <database> Executed command or SQL file
wrangler d1 export <name> Export the contents or schema of your database as a .sql file
wrangler d1 time-travel Use Time Travel to restore, fork or copy a database at a specific point-in-time.
wrangler d1 migrations Interact with D1 Migrations
Expand Down
126 changes: 126 additions & 0 deletions packages/wrangler/src/d1/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import fs from "node:fs/promises";
import { fetch } from "undici";
import { printWranglerBanner } from "..";
import { fetchResult } from "../cfetch";
import { readConfig } from "../config";
import { UserError } from "../errors";
import { logger } from "../logger";
import { requireAuth } from "../user";
import { Name } from "./options";
import { getDatabaseByNameOrBinding } from "./utils";
import type { Config } from "../config";
import type {
CommonYargsArgv,
StrictYargsOptionsToInterface,
} from "../yargs-types";
import type { Database } from "./types";

export function Options(yargs: CommonYargsArgv) {
return Name(yargs)
.option("local", {
type: "boolean",
describe: "Export from your local DB you use with wrangler dev",
conflicts: "remote",
})
.option("remote", {
type: "boolean",
describe: "Export from your live D1",
conflicts: "local",
})
.option("no-schema", {
type: "boolean",
describe: "Only output table contents, not the DB schema",
conflicts: "no-data",
})
.option("no-data", {
type: "boolean",
describe:
"Only output table schema, not the contents of the DBs themselves",
conflicts: "no-schema",
})
.option("table", {
type: "string",
describe: "Specify which tables to include in export",
})
.option("output", {
type: "string",
describe: "Which .sql file to output to",
demandOption: true,
});
}

type HandlerOptions = StrictYargsOptionsToInterface<typeof Options>;
export const Handler = async (args: HandlerOptions): Promise<void> => {
const { local, remote, name, output, noSchema, noData, table } = args;
await printWranglerBanner();
const config = readConfig(args.config, args);

if (local)
throw new UserError(
`Local imports/exports will be coming in a future version of Wrangler.`
);
if (!remote)
throw new UserError(`You must specify either --local or --remote`);

// Allow multiple --table x --table y flags or none
const tables: string[] = table
? Array.isArray(table)
? table
: [table]
: [];

const result = await exportRemotely(
config,
name,
output,
tables,
noSchema,
noData
);
return result;
};

type ExportMetadata = {
signedUrl: string;
};

async function exportRemotely(
config: Config,
name: string,
output: string,
tables: string[],
noSchema?: boolean,
noData?: boolean
) {
const accountId = await requireAuth(config);
const db: Database = await getDatabaseByNameOrBinding(
config,
accountId,
name
);

logger.log(`🌀 Executing on remote database ${name} (${db.uuid}):`);
logger.log(`🌀 Creating export...`);
const metadata = await fetchResult<ExportMetadata>(
`/accounts/${accountId}/d1/database/${db.uuid}/export`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
outputFormat: "file",
dumpOptions: {
noSchema,
noData,
tables,
},
}),
}
);

logger.log(`🌀 Downloading SQL to ${output}`);
const contents = await fetch(metadata.signedUrl);
await fs.writeFile(output, contents.body || "");
logger.log(`Done!`);
}
7 changes: 7 additions & 0 deletions packages/wrangler/src/d1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Backups from "./backups";
import * as Create from "./create";
import * as Delete from "./delete";
import * as Execute from "./execute";
import * as Export from "./export";
import * as Info from "./info";
import * as Insights from "./insights";
import * as List from "./list";
Expand Down Expand Up @@ -85,6 +86,12 @@ export function d1(yargs: CommonYargsArgv) {
Execute.Options,
Execute.Handler
)
.command(
"export <name>",
"Export the contents or schema of your database as a .sql file",
Export.Options,
Export.Handler
)
.command(
"time-travel",
"Use Time Travel to restore, fork or copy a database at a specific point-in-time.",
Expand Down

0 comments on commit b7a6d9d

Please sign in to comment.