Skip to content

Commit a5a76c7

Browse files
authored
Support edit sql tool (#260)
* support edit sql tool * reuse other tools
1 parent f7a50a9 commit a5a76c7

File tree

3 files changed

+72
-4
lines changed

3 files changed

+72
-4
lines changed

apps/src/metabase/appController.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ import {
3636
getParameters,
3737
getVariablesAndUuidsInQuery,
3838
MetabaseStateSnippetsDict,
39-
getAllTemplateTagsInQuery
39+
getAllTemplateTagsInQuery,
40+
applySQLEdits,
41+
SQLEdits
4042
} from "./helpers/sqlQuery";
4143
import axios from 'axios'
4244
import { getSelectedDbId, getCurrentUserInfo as getUserInfo, getSnippets, getCurrentCard, getDashboardState } from "./helpers/metabaseStateAPI";
@@ -400,6 +402,24 @@ export class MetabaseController extends AppController<MetabaseAppState> {
400402
}
401403
}
402404

405+
@Action({
406+
labelRunning: "Executes the SQL query with parameters",
407+
labelDone: "Executed query",
408+
labelTask: "Executed SQL query",
409+
description: "Executes the SQL query in the Metabase SQL editor with support for template tags and parameters.",
410+
renderBody: ({ explanation, sql_edits}: { explanation: string, sql_edits: SQLEdits }, appState: MetabaseAppStateSQLEditor) => {
411+
const sqlQuery = appState?.sqlQuery
412+
const newQuery = applySQLEdits(sqlQuery, sql_edits);
413+
return {text: explanation, code: newQuery, oldCode: sqlQuery, language: "sql"}
414+
}
415+
})
416+
async EditAndExecuteQuery({ sql_edits, _ctes = [], explanation = "", template_tags={}, parameters=[] }: { sql_edits: SQLEdits, _ctes?: CTE[], explanation?: string, template_tags?: object, parameters?: any[] }) {
417+
const appState = (await this.app.getState()) as MetabaseAppStateSQLEditor;
418+
let sql = appState.sqlQuery || "";
419+
sql = applySQLEdits(sql, sql_edits);
420+
return await this.ExecuteQuery({ sql, _ctes, explanation, template_tags, parameters });
421+
}
422+
403423
@Action({
404424
labelRunning: "Executes the SQL query",
405425
labelDone: "Executed query",

apps/src/metabase/helpers/sqlQuery.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,52 @@ export const getAllTemplateTagsInQuery = (query: string, allSnippets?: MetabaseS
150150
const snippetTags = allSnippets ? getSnippetsInQuery(query, allSnippets) : {};
151151
const modelTags = getModelsInQuery(query);
152152
return { ...snippetTags, ...modelTags };
153-
}
153+
}
154+
155+
156+
export interface SQLEdit {
157+
old_sql: string;
158+
new_sql: string;
159+
replace_all: boolean; // replace all occurrences if true, otherwise replace only the first occurrence
160+
}
161+
export type SQLEdits = SQLEdit[];
162+
163+
164+
export const applySQLEdits = (sql: string, edits: SQLEdits): string => {
165+
//Rules
166+
// - The edits will be applied in the order they are provided.
167+
// - Use sql_edits to apply edits to the SQL query (have to provide old_sql, new_sql, and replace_all flag)
168+
// - If you want to replace the entire current SQL query, set old_sql to <ENTIRE_QUERY> and new_sql to the new query.
169+
// - If you want to replace a specific part of the SQL query, set old_sql to the part you want to replace and new_sql to the new part.
170+
// - If the current sql query is empty, you can set old_sql to an empty string and new_sql to the new query.
171+
// - If you want to not change the SQL query, you can set old_sql to an empty string and new_sql to an empty string.
172+
// - If the diff is too complicated (>5 edits), consider just replacing the entire query with a new one.
173+
try {
174+
if (typeof edits === 'string') {
175+
edits = JSON.parse(edits) as SQLEdits;
176+
}
177+
} catch (error) {
178+
console.error("Error parsing sql_edits:", error);
179+
return sql; // Return the original SQL if parsing fails
180+
}
181+
182+
183+
let updatedSQL = sql;
184+
for (const edit of edits) {
185+
const { old_sql, new_sql, replace_all } = edit;
186+
if (old_sql === "<ENTIRE_QUERY>") {
187+
// Replace the entire SQL query
188+
updatedSQL = new_sql;
189+
} else if (old_sql === "") {
190+
// If old_sql is empty, set new_sql as the updated SQL
191+
updatedSQL = new_sql;
192+
} else if (replace_all) {
193+
// Replace all occurrences of old_sql with new_sql
194+
updatedSQL = updatedSQL.split(old_sql).join(new_sql);
195+
} else {
196+
// Replace only the first occurrence of old_sql with new_sql
197+
updatedSQL = updatedSQL.replace(old_sql, new_sql);
198+
}
199+
}
200+
return updatedSQL;
201+
};

web/src/components/common/ActionStack.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { get, isEmpty } from 'lodash';
2525

2626
// Todo: Vivek: Hardcoding here, need to fix this later
2727
// This is a list of actions that are undo/redoable
28-
const UNDO_REDO_ACTIONS = ['ExecuteSQLClient', 'ExecuteQuery']
28+
const UNDO_REDO_ACTIONS = ['ExecuteSQLClient', 'ExecuteQuery', 'EditAndExecuteQuery']
2929

3030

3131
function removeThinkingTags(input: string): string {
@@ -96,7 +96,7 @@ export const ActionStack: React.FC<{status: string, actions: Array<ActionStatusV
9696
event.stopPropagation();
9797
executeAction({
9898
index: -1,
99-
function: fn,
99+
function: 'ExecuteQuery',
100100
args: {sql: sql},
101101
});
102102
};

0 commit comments

Comments
 (0)