Skip to content

Commit 438ec16

Browse files
authored
Merge pull request #23 from Brayden/bwilmoth/import-sql-dump
Import from SQL dump
2 parents 4ee57dd + 317dc1e commit 438ec16

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

src/import/dump.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { createResponse } from '../utils';
2+
import { OperationQueueItem } from '../operation';
3+
4+
function parseSqlStatements(sqlContent: string): string[] {
5+
const lines = sqlContent.split('\n');
6+
let currentStatement = '';
7+
const statements: string[] = [];
8+
9+
for (const line of lines) {
10+
const trimmedLine = line.trim();
11+
if (trimmedLine.startsWith('--') || trimmedLine === '') {
12+
continue; // Skip comments and empty lines
13+
}
14+
15+
currentStatement += line + '\n';
16+
17+
if (trimmedLine.endsWith(';')) {
18+
statements.push(currentStatement.trim());
19+
currentStatement = '';
20+
}
21+
}
22+
23+
// Add any remaining statement without a semicolon
24+
if (currentStatement.trim()) {
25+
statements.push(currentStatement.trim());
26+
}
27+
28+
return statements;
29+
}
30+
31+
export async function importDumpRoute(
32+
request: Request,
33+
sql: SqlStorage,
34+
operationQueue: Array<OperationQueueItem>,
35+
ctx: DurableObjectState,
36+
processingOperation: { value: boolean }
37+
): Promise<Response> {
38+
if (request.method !== 'POST') {
39+
return createResponse(undefined, 'Method not allowed', 405);
40+
}
41+
42+
const contentType = request.headers.get('Content-Type');
43+
if (!contentType || !contentType.includes('multipart/form-data')) {
44+
return createResponse(undefined, 'Content-Type must be multipart/form-data', 400);
45+
}
46+
47+
try {
48+
const formData = await request.formData();
49+
const sqlFile = formData.get('sqlFile');
50+
51+
if (!sqlFile || !(sqlFile instanceof File)) {
52+
return createResponse(undefined, 'No SQL file uploaded', 400);
53+
}
54+
55+
if (!sqlFile.name.endsWith('.sql')) {
56+
return createResponse(undefined, 'Uploaded file must be a .sql file', 400);
57+
}
58+
59+
let sqlContent = await sqlFile.text();
60+
61+
// Remove the SQLite format header if present
62+
if (sqlContent.startsWith('SQLite format 3')) {
63+
sqlContent = sqlContent.substring(sqlContent.indexOf('\n') + 1);
64+
}
65+
66+
const sqlStatements = parseSqlStatements(sqlContent);
67+
68+
const results = [];
69+
for (const statement of sqlStatements) {
70+
try {
71+
const result = await sql.exec(statement);
72+
results.push({ statement, success: true, result });
73+
} catch (error: any) {
74+
console.error(`Error executing statement: ${statement}`, error);
75+
results.push({ statement, success: false, error: error.message });
76+
}
77+
}
78+
79+
const successCount = results.filter(r => r.success).length;
80+
const failureCount = results.filter(r => !r.success).length;
81+
82+
return createResponse({
83+
message: `SQL dump import completed. ${successCount} statements succeeded, ${failureCount} failed.`,
84+
details: results
85+
}, undefined, failureCount > 0 ? 207 : 200);
86+
} catch (error: any) {
87+
console.error('Import Dump Error:', error);
88+
return createResponse(undefined, error.message || 'An error occurred while importing the SQL dump', 500);
89+
}
90+
}

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import handleStudioRequest from "./studio";
66
import { dumpDatabaseRoute } from './export/dump';
77
import { exportTableToJsonRoute } from './export/json';
88
import { exportTableToCsvRoute } from './export/csv';
9+
import { importDumpRoute } from './import/dump';
910

1011
const DURABLE_OBJECT_ID = 'sql-durable-object';
1112

@@ -141,6 +142,8 @@ export class DatabaseDurableObject extends DurableObject {
141142
return createResponse(undefined, 'Table name is required', 400);
142143
}
143144
return exportTableToCsvRoute(this.sql, this.operationQueue, this.ctx, this.processingOperation, tableName);
145+
} else if (request.method === 'POST' && url.pathname === '/import/dump') {
146+
return importDumpRoute(request, this.sql, this.operationQueue, this.ctx, this.processingOperation);
144147
} else {
145148
return createResponse(undefined, 'Unknown operation', 400);
146149
}

0 commit comments

Comments
 (0)