From 37cd30f0b63a492272099b0d070991f8b2afacc8 Mon Sep 17 00:00:00 2001 From: Julia Yan Date: Tue, 26 May 2026 16:10:54 -0400 Subject: [PATCH] Add ability to run create and bind statements in notebooks --- src/notebooks/Controller.ts | 66 ++++++++++++++++++++++++++++++++----- src/views/examples/index.ts | 49 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/notebooks/Controller.ts b/src/notebooks/Controller.ts index 7bc3c1ce..bef647f0 100644 --- a/src/notebooks/Controller.ts +++ b/src/notebooks/Controller.ts @@ -79,21 +79,69 @@ export class IBMiController { let content = cell.document.getText().trim(); let chartDetail: ChartDetail | undefined; - ({ chartDetail, content } = getStatementDetail(content, eol)); + // Parse bind parameters BEFORE any other processing + let parameters: any[] | undefined; + const bindMatch = content.match(/bind:\s*\((.*?)\)\s*;?\s*$/s); + if (bindMatch) { + // Remove the bind statement from content + content = content.substring(0, bindMatch.index).trim(); + + // Parse the parameters + try { + const paramString = bindMatch[1]; + // Simple parsing: split by comma, handle strings and numbers + parameters = paramString.split(',').map(p => { + p = p.trim(); + // String literal + if ((p.startsWith("'") && p.endsWith("'")) || (p.startsWith('"') && p.endsWith('"'))) { + return p.substring(1, p.length - 1); + } + // Number + if (!isNaN(Number(p))) { + return Number(p); + } + // Null + if (p.toLowerCase() === 'null') { + return null; + } + // Default to string + return p; + }); + } catch (e) { + items.push(vscode.NotebookCellOutputItem.stderr(`Failed to parse bind parameters: ${e.message}`)); + break; + } + } + // For CREATE statements, keep the content as-is (they need semicolons) + // For other statements, use getStatementDetail to handle chart settings + const isCreateStatement = content.trim().toUpperCase().startsWith('CREATE'); + + if (!isCreateStatement) { + ({ chartDetail, content } = getStatementDetail(content, eol)); + } + // Execute the query - const query = selected.job.query(content); + const query = selected.job.query(content, parameters ? { parameters } : undefined); const results = await query.execute(1000); const table = results.data; - const columnNames = results.metadata.columns.map(c => c.name); + const columnNames = results.metadata?.columns?.map(c => c.name) || []; + + // Check for OUT/INOUT parameters and display them + if (results.output_parms && results.output_parms.length > 0) { + const paramOutput = results.output_parms.map((param, index) => { + const paramName = param.name ? `**${param.name}**` : `**Parameter ${index + 1}**`; + const paramValue = param.value !== undefined && param.value !== null ? param.value : '-'; + return `${paramName}: \`${paramValue}\``; + }).join('\n\n'); + + items.push(vscode.NotebookCellOutputItem.text(`### Output Parameters\n\n${paramOutput}`, `text/markdown`)); + } if (table === undefined && results.success && !results.has_results) { items.push(vscode.NotebookCellOutputItem.text(`Statement executed successfully. ${results.update_count ? `${results.update_count} rows affected.` : ``}`, `text/markdown`)); - break; - } - - if (table.length > 0) { + } else if (table && table.length > 0) { // Add `-` for blanks. table.forEach(row => { columnNames.forEach(key => { @@ -104,7 +152,7 @@ export class IBMiController { let fallbackToTable = true; - if (chartDetail.type) { + if (chartDetail && chartDetail.type) { if (chartJsTypes.includes(chartDetail.type as ChartJsType)) { const possibleChart = generateChart(execution.executionOrder, chartDetail, columnNames, table, generateChartHTMLCell); if (possibleChart) { @@ -117,7 +165,7 @@ export class IBMiController { if (fallbackToTable) { items.push(vscode.NotebookCellOutputItem.text(mdTable(table, columnNames), `text/markdown`)); } - } else { + } else if (table && table.length === 0 && results.has_results) { items.push(vscode.NotebookCellOutputItem.stderr(`No rows returned from statement.`)); } diff --git a/src/views/examples/index.ts b/src/views/examples/index.ts index f3911fa1..dfcb6f15 100644 --- a/src/views/examples/index.ts +++ b/src/views/examples/index.ts @@ -26,6 +26,55 @@ export const ServiceInfoLabel = `IBM i (SQL) Services`; export const Examples: SQLExamplesList = { "Notebooks": [ + { + name: "Bind Parameters with OUT Parameters", + content: [ + `## `.concat([ + `This notebook demonstrates how to use bind parameters with stored procedures that have OUT or INOUT parameters.`, + `Use the \`bind:()\` syntax at the end of a CALL statement to pass parameter values.`, + `OUT and INOUT parameters will be displayed after execution.`, + ].join('\n\n')), + + `## Create a simple procedure with OUT parameter`, + [ + `CREATE OR REPLACE PROCEDURE MYLIB.GREET_USER (`, + ` IN USER_NAME VARCHAR(50),`, + ` OUT GREETING VARCHAR(100)`, + `)`, + `LANGUAGE SQL`, + `BEGIN`, + ` SET GREETING = 'Hello, ' || USER_NAME || '! Welcome to IBM i.';`, + `END`, + ].join('\n'), + + `## Call the procedure with bind parameters`, + [ + `CALL MYLIB.GREET_USER(?, ?);`, + `bind:('Alice', ' ');`, + ].join('\n'), + + `## Create a procedure with multiple OUT parameters`, + [ + `CREATE OR REPLACE PROCEDURE MYLIB.CALCULATE_VALUES (`, + ` IN INPUT_NUM INTEGER,`, + ` OUT DOUBLED INTEGER,`, + ` OUT SQUARED INTEGER`, + `)`, + `LANGUAGE SQL`, + `BEGIN`, + ` SET DOUBLED = INPUT_NUM * 2;`, + ` SET SQUARED = INPUT_NUM * INPUT_NUM;`, + `END`, + ].join('\n'), + + `## Call with multiple OUT parameters`, + [ + `CALL MYLIB.CALCULATE_VALUES(?, ?, ?);`, + `bind:(5, 0, 0);`, + ].join('\n'), + ], + isNotebook: true + }, { name: "IBM i History", content: [