Skip to content

Commit 56c3ce7

Browse files
committed
fix various db manager issues, need to add a function to connect external DBs
1 parent d9ac1bf commit 56c3ce7

File tree

5 files changed

+111
-121
lines changed

5 files changed

+111
-121
lines changed

py-src/data_formulator/agents/agent_sql_data_transform.py

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -55,65 +55,6 @@
5555
'''
5656

5757
EXAMPLE='''
58-
59-
For example:
60-
61-
[CONTEXT]
62-
63-
Here are our datasets, here are their field summaries and samples:
64-
65-
table_0 (us_covid_cases) fields:
66-
Date -- type: object, values: 1/1/2021, 1/1/2022, 1/1/2023, ..., 9/8/2022, 9/9/2020, 9/9/2021, 9/9/2022
67-
Cases -- type: int64, values: -23999, -14195, -6940, ..., 1018935, 1032159, 1178403, 1433977
68-
69-
table_0 (us_covid_cases) sample:
70-
```
71-
|Date|Cases
72-
0|1/21/2020|1
73-
1|1/22/2020|0
74-
2|1/23/2020|0
75-
3|1/24/2020|1
76-
4|1/25/2020|1
77-
......
78-
```
79-
80-
[GOAL]
81-
82-
{
83-
"instruction": "calculate 7-day moving average",
84-
"visualization_fields": ["Date", "7-day average cases"]
85-
}
86-
87-
[OUTPUT]
88-
89-
{
90-
"detailed_instruction": "Calculate the 7-day moving average of COVID-19 cases over time.",
91-
"output_fields": ["Date", "Cases", "7-day average cases"],
92-
"visualization_fields": ["Date", "7-day average cases"],
93-
"reason": "To calculate the 7-day moving average, the 'Cases' field is required, but it is not needed for visualization. The provided fields are sufficient to achieve the goal."
94-
}
95-
96-
```python
97-
import pandas as pd
98-
import collections
99-
import numpy as np
100-
101-
def transform_data(df):
102-
# Convert Date column to datetime
103-
df['Date'] = pd.to_datetime(df['Date'])
104-
105-
# Sort the dataframe by Date
106-
df = df.sort_values('Date')
107-
108-
# Calculate the 7-day moving average of cases
109-
df['7-day average cases'] = df['Cases'].rolling(window=7).mean()
110-
111-
# Select the output fields
112-
transformed_df = df[['Date', 'Cases', '7-day average cases']]
113-
114-
return transformed_df
115-
```
116-
11758
[CONTEXT]
11859
11960
Here are our datasets, here are their field summaries and samples:

py-src/data_formulator/tables_routes.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,17 @@ def create_table():
287287
return jsonify({"status": "error", "message": "Invalid table name"}), 400
288288

289289
with db_manager.connection(session['session_id']) as db:
290+
# Check if table exists and generate unique name if needed
291+
base_name = sanitized_table_name
292+
counter = 1
293+
while True:
294+
# Check if table exists
295+
exists = db.execute(f"SELECT COUNT(*) FROM duckdb_tables() WHERE table_name = '{sanitized_table_name}'").fetchone()[0] > 0
296+
if not exists:
297+
break
298+
# If exists, append counter to base name
299+
sanitized_table_name = f"{base_name}_{counter}"
300+
counter += 1
290301

291302
# Read file based on extension
292303
if file.filename.endswith('.csv'):
@@ -297,7 +308,7 @@ def create_table():
297308
df = pd.read_json(file)
298309
else:
299310
return jsonify({"status": "error", "message": "Unsupported file format"}), 400
300-
311+
301312
# Create table
302313
db.register('df_temp', df)
303314
db.execute(f"CREATE TABLE {sanitized_table_name} AS SELECT * FROM df_temp")
@@ -307,7 +318,9 @@ def create_table():
307318
"status": "success",
308319
"table_name": sanitized_table_name,
309320
"row_count": len(df),
310-
"columns": list(df.columns)
321+
"columns": list(df.columns),
322+
"original_name": base_name, # Include the original name in response
323+
"is_renamed": base_name != sanitized_table_name # Flag indicating if name was changed
311324
})
312325

313326
except Exception as e:
@@ -329,10 +342,21 @@ def drop_table():
329342
return jsonify({"status": "error", "message": "No table name provided"}), 400
330343

331344
with db_manager.connection(session['session_id']) as db:
332-
# First try to drop it as a view
333-
db.execute(f"DROP VIEW IF EXISTS {table_name}")
334-
# Then try to drop it as a table
335-
db.execute(f"DROP TABLE IF EXISTS {table_name}")
345+
# First check if it exists as a view
346+
view_exists = db.execute(f"SELECT view_name FROM duckdb_views() WHERE view_name = '{table_name}'").fetchone() is not None
347+
if view_exists:
348+
db.execute(f"DROP VIEW IF EXISTS {table_name}")
349+
350+
# Then check if it exists as a table
351+
table_exists = db.execute(f"SELECT table_name FROM duckdb_tables() WHERE table_name = '{table_name}'").fetchone() is not None
352+
if table_exists:
353+
db.execute(f"DROP TABLE IF EXISTS {table_name}")
354+
355+
if not view_exists and not table_exists:
356+
return jsonify({
357+
"status": "error",
358+
"message": f"Table/view '{table_name}' does not exist"
359+
}), 404
336360

337361
return jsonify({
338362
"status": "success",

src/app/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
618618
<Divider orientation="vertical" variant="middle" flexItem />
619619
<DBTableSelectionDialog buttonElement={
620620
<Typography sx={{ display: 'flex', fontSize: 14, alignItems: 'center', gap: 1, textTransform: 'none' }}>
621-
<CloudQueueIcon fontSize="small" /> View Database
621+
<CloudQueueIcon fontSize="small" /> Database
622622
</Typography>
623623
} />
624624
<Divider orientation="vertical" variant="middle" flexItem />

src/views/DBTableManager.tsx

Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,14 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
226226
const [dbTables, setDbTables] = useState<DBTable[]>([]);
227227
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
228228

229-
const [errorMessage, setErrorMessage] = useState<string | null>(null);
229+
const [errorMessage, setErrorMessage] = useState<{content: string, severity: "error" | "warning" | "info" | "success"} | null>(null);
230230
const [showError, setShowError] = useState(false);
231231
const [isUploading, setIsUploading] = useState<boolean>(false);
232232

233+
useEffect(() => {
234+
fetchTables();
235+
}, []);
236+
233237
// Fetch list of tables
234238
const fetchTables = async () => {
235239
try {
@@ -267,7 +271,7 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
267271
}
268272
} catch (error) {
269273
console.error('Failed to upload table:', error);
270-
setErrorMessage('Failed to upload table. The server may need to be restarted.');
274+
setErrorMessage({content: 'Failed to upload table. The server may need to be restarted.', severity: "error"});
271275
setShowError(true);
272276
} finally {
273277
setIsUploading(false);
@@ -304,7 +308,7 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
304308
URL.revokeObjectURL(url);
305309
} catch (error) {
306310
console.error('Failed to download database:', error);
307-
setErrorMessage('Failed to download database file');
311+
setErrorMessage({content: 'Failed to download database file', severity: "error"});
308312
setShowError(true);
309313
}
310314
};
@@ -325,18 +329,25 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
325329
});
326330
const data = await response.json();
327331
if (data.status === 'success') {
332+
if (data.is_renamed) {
333+
setErrorMessage({content: `Table ${data.original_name} already exists. Renamed to ${data.table_name}`, severity: "warning"});
334+
setShowError(true);
335+
}
328336
fetchTables(); // Refresh table list
329337
} else {
330-
// Handle error from server
331-
setErrorMessage(data.error || 'Failed to upload table');
338+
setErrorMessage({content: data.error || 'Failed to upload table', severity: "error"});
332339
setShowError(true);
333340
}
334341
} catch (error) {
335342
console.error('Failed to upload table:', error);
336-
setErrorMessage('Failed to upload table. The server may need to be restarted.');
343+
setErrorMessage({content: 'Failed to upload table. The server may need to be restarted.', severity: "error"});
337344
setShowError(true);
338345
} finally {
339346
setIsUploading(false);
347+
// Clear the file input value to allow uploading the same file again
348+
if (event.target) {
349+
event.target.value = '';
350+
}
340351
}
341352
};
342353

@@ -354,7 +365,7 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
354365
}
355366
} catch (error) {
356367
console.error('Failed to reset database:', error);
357-
setErrorMessage('Failed to reset database');
368+
setErrorMessage({content: 'Failed to reset database', severity: "error"});
358369
setShowError(true);
359370
}
360371
}
@@ -409,7 +420,7 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
409420
}
410421
} catch (error) {
411422
console.error('Failed to analyze table data:', error);
412-
setErrorMessage('Failed to analyze table data');
423+
setErrorMessage({content: 'Failed to analyze table data', severity: "error"});
413424
setShowError(true);
414425
}
415426
};
@@ -476,6 +487,44 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
476487
}
477488
}, [tableDialogOpen]);
478489

490+
let importButton = (buttonElement: React.ReactNode) => {
491+
return <Tooltip title="import a duckdb .db file to the local database">
492+
<Button variant="text" sx={{fontSize: "inherit", minWidth: "auto"}} component="label" disabled={isUploading}>
493+
{buttonElement}
494+
<input type="file" hidden onChange={handleDBUpload} accept=".db" disabled={isUploading} />
495+
</Button>
496+
</Tooltip>
497+
}
498+
499+
let exportButton =
500+
<Tooltip title="save the local database to a duckdb .db file">
501+
<Button variant="text" size="small" onClick={handleDBDownload} disabled={isUploading || dbTables.length === 0}>
502+
export
503+
</Button>
504+
</Tooltip>
505+
506+
function uploadFileButton(element: React.ReactNode, buttonSx?: SxProps) {
507+
return (
508+
<Tooltip title="upload a csv/tsv file to the local database">
509+
<Button
510+
variant="text"
511+
component="label"
512+
sx={{ fontSize: "inherit", ...buttonSx}}
513+
disabled={isUploading}
514+
>
515+
{element}
516+
<input
517+
type="file"
518+
hidden
519+
onChange={handleFileUpload}
520+
accept=".csv,.xlsx,.json"
521+
disabled={isUploading}
522+
/>
523+
</Button>
524+
</Tooltip>
525+
);
526+
}
527+
479528
let mainContent = dbTables.length > 0 ?
480529
<Box sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', flexDirection: 'row', height: '100%' }}>
481530
<Box sx={{display: "flex", flexDirection: "column", width: "120px", borderRight: 1, borderColor: 'divider'}}>
@@ -485,7 +534,7 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
485534
value={selectedTabIndex}
486535
onChange={handleTabChange}
487536
aria-label="Database tables"
488-
sx={{ width: '120px' }}
537+
sx={{ width: '120px', maxHeight: '360px' }}
489538
>
490539
{dbTables.map((t, i) => (
491540
<Tab
@@ -501,25 +550,7 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
501550
))}
502551
</Tabs>
503552
<Divider sx={{my: 1}} textAlign='left'> <TuneIcon sx={{fontSize: 12, color: "text.secondary"}} /></Divider>
504-
<Tooltip title="upload a csv/tsv file to the local database">
505-
<Button
506-
variant="text"
507-
component="label"
508-
size="small"
509-
sx={{ width: '100%', textTransform: "none"}}
510-
disabled={isUploading}
511-
startIcon={<UploadIcon fontSize="small"/>}
512-
>
513-
{isUploading ? 'uploading...' : 'upload file'}
514-
<input
515-
type="file"
516-
hidden
517-
onChange={handleFileUpload}
518-
accept=".csv,.xlsx,.json"
519-
disabled={isUploading}
520-
/>
521-
</Button>
522-
</Tooltip>
553+
{uploadFileButton(<Typography component="span" fontSize={12}>{isUploading ? 'uploading...' : 'upload file'}</Typography>)}
523554
</Box>
524555
{dbTables.map((t, i) => {
525556
const currentTable = t;
@@ -582,8 +613,12 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
582613
</Box> :
583614
<Box sx={{ p: 3, textAlign: 'center', height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }}>
584615
<StorageIcon sx={{ fontSize: 60, color: 'text.secondary', mb: 2 }} />
585-
<Typography variant="h6" gutterBottom>
586-
Please upload a file to create a table
616+
<Typography variant="caption"> Database is currently empty. </Typography>
617+
<Typography>
618+
{uploadFileButton(<Typography component="span">Upload a csv dataset </Typography>)}
619+
or
620+
{importButton(<Typography component="span">Import a db file</Typography>)}
621+
<Typography component="span"> to get started.</Typography>
587622
</Typography>
588623
</Box>
589624

@@ -602,8 +637,8 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
602637
onClose={handleCloseError}
603638
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
604639
>
605-
<Alert onClose={handleCloseError} severity="error" sx={{ width: '100%' }}>
606-
{errorMessage}
640+
<Alert onClose={handleCloseError} severity={errorMessage?.severity} sx={{ width: '100%' }}>
641+
{errorMessage?.content}
607642
</Alert>
608643
</Snackbar>
609644

@@ -649,30 +684,9 @@ export const DBTableSelectionDialog: React.FC<{ buttonElement: any }> = function
649684
</DialogContent>
650685
<DialogActions>
651686
<Typography variant="caption" sx={{ mr: 'auto', '& .MuiButton-root': { minWidth: 'auto', textTransform: "none" } }}>
652-
<Button
653-
variant="text"
654-
size="small"
655-
component="label"
656-
disabled={isUploading}
657-
>
658-
Reload
659-
<input
660-
type="file"
661-
hidden
662-
onChange={handleDBUpload}
663-
accept=".db"
664-
disabled={isUploading}
665-
/>
666-
</Button>
687+
{importButton(<Typography component="span" fontSize="inherit">Import</Typography>)}
667688
,
668-
<Button
669-
variant="text" size="small"
670-
// endIcon={<DownloadIcon />}
671-
onClick={handleDBDownload}
672-
disabled={isUploading || dbTables.length === 0}
673-
>
674-
download
675-
</Button>
689+
{exportButton}
676690
or
677691
<Button
678692
variant="text" size="small"

src/views/TableSelectionView.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,17 @@ export const TableUploadDialog: React.FC<TableUploadDialogProps> = ({ buttonElem
284284
file.name.endsWith('.csv') ||
285285
file.name.endsWith('.tsv') ||
286286
file.name.endsWith('.json')) {
287+
288+
// Check if file is larger than 5MB
289+
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB in bytes
290+
if (file.size > MAX_FILE_SIZE) {
291+
dispatch(dfActions.addMessages({
292+
"timestamp": Date.now(),
293+
"type": "error",
294+
"value": `File ${file.name} is too large (${(file.size / (1024 * 1024)).toFixed(2)}MB), upload it via DATABASE option instead.`
295+
}));
296+
continue; // Skip this file and process the next one
297+
}
287298

288299
// Handle text files
289300
file.text().then((text) => {

0 commit comments

Comments
 (0)