working, but certain create, delete update queries are not

This commit is contained in:
aadamgough
2025-11-13 22:37:01 -08:00
parent 273d4cda2e
commit e3dca6635a
7 changed files with 905 additions and 4 deletions

View File

@@ -9,7 +9,7 @@ export const SnowflakeBlock: BlockConfig<SnowflakeResponse> = {
description: 'Execute queries on Snowflake data warehouse',
authMode: AuthMode.OAuth,
longDescription:
'Integrate Snowflake into your workflow. Execute SQL queries, list databases, schemas, and tables, and describe table structures in your Snowflake data warehouse.',
'Integrate Snowflake into your workflow. Execute SQL queries, insert, update, and delete rows, list databases, schemas, and tables, and describe table structures in your Snowflake data warehouse.',
docsLink: 'https://docs.sim.ai/tools/snowflake',
category: 'tools',
bgColor: '#E0E0E0',
@@ -21,6 +21,9 @@ export const SnowflakeBlock: BlockConfig<SnowflakeResponse> = {
type: 'dropdown',
options: [
{ label: 'Execute Query', id: 'execute_query' },
{ label: 'Insert Rows', id: 'insert_rows' },
{ label: 'Update Rows', id: 'update_rows' },
{ label: 'Delete Rows', id: 'delete_rows' },
{ label: 'List Databases', id: 'list_databases' },
{ label: 'List Schemas', id: 'list_schemas' },
{ label: 'List Tables', id: 'list_tables' },
@@ -36,7 +39,6 @@ export const SnowflakeBlock: BlockConfig<SnowflakeResponse> = {
id: 'credential',
title: 'Snowflake Account',
type: 'oauth-input',
provider: 'snowflake',
serviceId: 'snowflake',
requiredScopes: [],
placeholder: 'Select Snowflake account',
@@ -235,6 +237,9 @@ Return ONLY the SQL query - no explanations, no markdown code blocks, no extra t
'list_file_formats',
'list_stages',
'describe_table',
'insert_rows',
'update_rows',
'delete_rows',
],
},
},
@@ -246,7 +251,16 @@ Return ONLY the SQL query - no explanations, no markdown code blocks, no extra t
required: true,
condition: {
field: 'operation',
value: ['list_tables', 'list_views', 'list_file_formats', 'list_stages', 'describe_table'],
value: [
'list_tables',
'list_views',
'list_file_formats',
'list_stages',
'describe_table',
'insert_rows',
'update_rows',
'delete_rows',
],
},
},
{
@@ -257,7 +271,51 @@ Return ONLY the SQL query - no explanations, no markdown code blocks, no extra t
required: true,
condition: {
field: 'operation',
value: 'describe_table',
value: ['describe_table', 'insert_rows', 'update_rows', 'delete_rows'],
},
},
{
id: 'columns',
title: 'Columns',
type: 'long-input',
placeholder: '["column1", "column2", "column3"]',
required: true,
condition: {
field: 'operation',
value: 'insert_rows',
},
},
{
id: 'values',
title: 'Values',
type: 'long-input',
placeholder: '[["value1", "value2", "value3"], ["value4", "value5", "value6"]]',
required: true,
condition: {
field: 'operation',
value: 'insert_rows',
},
},
{
id: 'updates',
title: 'Updates',
type: 'long-input',
placeholder: '{"column1": "new_value", "column2": 123, "updated_at": "2024-01-01"}',
required: true,
condition: {
field: 'operation',
value: 'update_rows',
},
},
{
id: 'whereClause',
title: 'WHERE Clause',
type: 'long-input',
placeholder: 'id = 123 (leave empty to update/delete ALL rows)',
required: false,
condition: {
field: 'operation',
value: ['update_rows', 'delete_rows'],
},
},
{
@@ -274,6 +332,9 @@ Return ONLY the SQL query - no explanations, no markdown code blocks, no extra t
tools: {
access: [
'snowflake_execute_query',
'snowflake_insert_rows',
'snowflake_update_rows',
'snowflake_delete_rows',
'snowflake_list_databases',
'snowflake_list_schemas',
'snowflake_list_tables',
@@ -288,6 +349,12 @@ Return ONLY the SQL query - no explanations, no markdown code blocks, no extra t
switch (params.operation) {
case 'execute_query':
return 'snowflake_execute_query'
case 'insert_rows':
return 'snowflake_insert_rows'
case 'update_rows':
return 'snowflake_update_rows'
case 'delete_rows':
return 'snowflake_delete_rows'
case 'list_databases':
return 'snowflake_list_databases'
case 'list_schemas':
@@ -405,6 +472,83 @@ Return ONLY the SQL query - no explanations, no markdown code blocks, no extra t
break
}
case 'insert_rows': {
if (!params.database || !params.schema || !params.table) {
throw new Error('Database, Schema, and Table are required for insert_rows operation')
}
if (!params.columns || !params.values) {
throw new Error('Columns and Values are required for insert_rows operation')
}
// Parse columns and values if they are strings
let columns = params.columns
let values = params.values
if (typeof columns === 'string') {
try {
columns = JSON.parse(columns)
} catch (e) {
throw new Error('Columns must be a valid JSON array')
}
}
if (typeof values === 'string') {
try {
values = JSON.parse(values)
} catch (e) {
throw new Error('Values must be a valid JSON array of arrays')
}
}
baseParams.database = params.database
baseParams.schema = params.schema
baseParams.table = params.table
baseParams.columns = columns
baseParams.values = values
if (params.timeout) baseParams.timeout = Number.parseInt(params.timeout)
break
}
case 'update_rows': {
if (!params.database || !params.schema || !params.table) {
throw new Error('Database, Schema, and Table are required for update_rows operation')
}
if (!params.updates) {
throw new Error('Updates object is required for update_rows operation')
}
// Parse updates if it's a string
let updates = params.updates
if (typeof updates === 'string') {
try {
updates = JSON.parse(updates)
} catch (e) {
throw new Error('Updates must be a valid JSON object')
}
}
baseParams.database = params.database
baseParams.schema = params.schema
baseParams.table = params.table
baseParams.updates = updates
if (params.whereClause) baseParams.whereClause = params.whereClause
if (params.timeout) baseParams.timeout = Number.parseInt(params.timeout)
break
}
case 'delete_rows': {
if (!params.database || !params.schema || !params.table) {
throw new Error('Database, Schema, and Table are required for delete_rows operation')
}
baseParams.database = params.database
baseParams.schema = params.schema
baseParams.table = params.table
if (params.whereClause) baseParams.whereClause = params.whereClause
if (params.timeout) baseParams.timeout = Number.parseInt(params.timeout)
break
}
default:
throw new Error(`Unknown operation: ${operation}`)
}
@@ -426,6 +570,13 @@ Return ONLY the SQL query - no explanations, no markdown code blocks, no extra t
database: { type: 'string', description: 'Database name' },
schema: { type: 'string', description: 'Schema name' },
table: { type: 'string', description: 'Table name' },
columns: { type: 'json', description: 'Array of column names for insert operation' },
values: { type: 'json', description: 'Array of arrays containing values for insert operation' },
updates: {
type: 'json',
description: 'Object containing column-value pairs for update operation',
},
whereClause: { type: 'string', description: 'WHERE clause for update/delete operations' },
timeout: { type: 'string', description: 'Query timeout in seconds' },
},
outputs: {

View File

@@ -1011,8 +1011,10 @@ import {
import { smsSendTool } from '@/tools/sms'
import { smtpSendMailTool } from '@/tools/smtp'
import {
snowflakeDeleteRowsTool,
snowflakeDescribeTableTool,
snowflakeExecuteQueryTool,
snowflakeInsertRowsTool,
snowflakeListDatabasesTool,
snowflakeListFileFormatsTool,
snowflakeListSchemasTool,
@@ -1020,6 +1022,7 @@ import {
snowflakeListTablesTool,
snowflakeListViewsTool,
snowflakeListWarehousesTool,
snowflakeUpdateRowsTool,
} from '@/tools/snowflake'
import {
checkCommandExistsTool as sshCheckCommandExistsTool,
@@ -2437,6 +2440,9 @@ export const tools: Record<string, ToolConfig> = {
zoom_delete_recording: zoomDeleteRecordingTool,
zoom_list_past_participants: zoomListPastParticipantsTool,
snowflake_execute_query: snowflakeExecuteQueryTool,
snowflake_insert_rows: snowflakeInsertRowsTool,
snowflake_update_rows: snowflakeUpdateRowsTool,
snowflake_delete_rows: snowflakeDeleteRowsTool,
snowflake_list_databases: snowflakeListDatabasesTool,
snowflake_list_schemas: snowflakeListSchemasTool,
snowflake_list_tables: snowflakeListTablesTool,

View File

@@ -0,0 +1,194 @@
import { createLogger } from '@/lib/logs/console/logger'
import type {
SnowflakeDeleteRowsParams,
SnowflakeDeleteRowsResponse,
} from '@/tools/snowflake/types'
import { parseAccountUrl } from '@/tools/snowflake/utils'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('SnowflakeDeleteRowsTool')
/**
* Build DELETE SQL statement from parameters
*/
function buildDeleteSQL(
database: string,
schema: string,
table: string,
whereClause?: string
): string {
const fullTableName = `${database}.${schema}.${table}`
let sql = `DELETE FROM ${fullTableName}`
// Add WHERE clause if provided
if (whereClause?.trim()) {
sql += ` WHERE ${whereClause}`
}
return sql
}
export const snowflakeDeleteRowsTool: ToolConfig<
SnowflakeDeleteRowsParams,
SnowflakeDeleteRowsResponse
> = {
id: 'snowflake_delete_rows',
name: 'Snowflake Delete Rows',
description: 'Delete rows from a Snowflake table',
version: '1.0.0',
oauth: {
required: true,
provider: 'snowflake',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Snowflake',
},
accountUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Snowflake account URL (e.g., xy12345.us-east-1.snowflakecomputing.com)',
},
database: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Database name',
},
schema: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Schema name',
},
table: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Table name',
},
whereClause: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'WHERE clause to filter rows to delete (e.g., "id = 123" or "status = \'inactive\' AND created_at < \'2024-01-01\'"). WARNING: If not provided, ALL rows will be deleted.',
},
warehouse: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Warehouse to use (optional)',
},
role: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Role to use (optional)',
},
timeout: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Query timeout in seconds (default: 60)',
},
},
request: {
url: (params: SnowflakeDeleteRowsParams) => {
const cleanUrl = parseAccountUrl(params.accountUrl)
return `https://${cleanUrl}/api/v2/statements`
},
method: 'POST',
headers: (params: SnowflakeDeleteRowsParams) => ({
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
'X-Snowflake-Authorization-Token-Type': 'OAUTH',
}),
body: (params: SnowflakeDeleteRowsParams) => {
// Build DELETE SQL
const deleteSQL = buildDeleteSQL(
params.database,
params.schema,
params.table,
params.whereClause
)
logger.info('Building DELETE statement', {
database: params.database,
schema: params.schema,
table: params.table,
hasWhereClause: !!params.whereClause,
})
// Log warning if no WHERE clause provided
if (!params.whereClause) {
logger.warn('DELETE statement has no WHERE clause - ALL rows will be deleted', {
table: `${params.database}.${params.schema}.${params.table}`,
})
}
const requestBody: Record<string, any> = {
statement: deleteSQL,
timeout: params.timeout || 60,
database: params.database,
schema: params.schema,
}
if (params.warehouse) {
requestBody.warehouse = params.warehouse
}
if (params.role) {
requestBody.role = params.role
}
return requestBody
},
},
transformResponse: async (response: Response, params?: SnowflakeDeleteRowsParams) => {
if (!response.ok) {
const errorText = await response.text()
logger.error('Failed to delete rows from Snowflake table', {
status: response.status,
errorText,
table: params ? `${params.database}.${params.schema}.${params.table}` : 'unknown',
})
throw new Error(`Failed to delete rows: ${response.status} - ${errorText}`)
}
const data = await response.json()
// Extract number of rows deleted from response
const rowsDeleted = data.statementStatusUrl ? 'unknown' : 0
return {
success: true,
output: {
statementHandle: data.statementHandle,
rowsDeleted,
message: `Successfully deleted rows from ${params?.database}.${params?.schema}.${params?.table}`,
ts: new Date().toISOString(),
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Operation success status',
},
output: {
type: 'object',
description: 'Delete operation result',
},
},
}

View File

@@ -1,5 +1,7 @@
import { snowflakeDeleteRowsTool } from '@/tools/snowflake/delete_rows'
import { snowflakeDescribeTableTool } from '@/tools/snowflake/describe_table'
import { snowflakeExecuteQueryTool } from '@/tools/snowflake/execute_query'
import { snowflakeInsertRowsTool } from '@/tools/snowflake/insert_rows'
import { snowflakeListDatabasesTool } from '@/tools/snowflake/list_databases'
import { snowflakeListFileFormatsTool } from '@/tools/snowflake/list_file_formats'
import { snowflakeListSchemasTool } from '@/tools/snowflake/list_schemas'
@@ -7,6 +9,7 @@ import { snowflakeListStagesTool } from '@/tools/snowflake/list_stages'
import { snowflakeListTablesTool } from '@/tools/snowflake/list_tables'
import { snowflakeListViewsTool } from '@/tools/snowflake/list_views'
import { snowflakeListWarehousesTool } from '@/tools/snowflake/list_warehouses'
import { snowflakeUpdateRowsTool } from '@/tools/snowflake/update_rows'
export {
snowflakeExecuteQueryTool,
@@ -18,4 +21,7 @@ export {
snowflakeListWarehousesTool,
snowflakeListFileFormatsTool,
snowflakeListStagesTool,
snowflakeInsertRowsTool,
snowflakeUpdateRowsTool,
snowflakeDeleteRowsTool,
}

View File

@@ -0,0 +1,231 @@
import { createLogger } from '@/lib/logs/console/logger'
import type {
SnowflakeInsertRowsParams,
SnowflakeInsertRowsResponse,
} from '@/tools/snowflake/types'
import { parseAccountUrl } from '@/tools/snowflake/utils'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('SnowflakeInsertRowsTool')
/**
* Build INSERT SQL statement from parameters
*/
function buildInsertSQL(
database: string,
schema: string,
table: string,
columns: string[],
values: any[][]
): string {
const fullTableName = `${database}.${schema}.${table}`
const columnList = columns.join(', ')
// Build values clause for multiple rows
const valuesClause = values
.map((rowValues) => {
const formattedValues = rowValues.map((val) => {
if (val === null || val === undefined) {
return 'NULL'
}
if (typeof val === 'string') {
// Escape single quotes by doubling them
return `'${val.replace(/'/g, "''")}'`
}
if (typeof val === 'boolean') {
return val ? 'TRUE' : 'FALSE'
}
return String(val)
})
return `(${formattedValues.join(', ')})`
})
.join(', ')
return `INSERT INTO ${fullTableName} (${columnList}) VALUES ${valuesClause}`
}
export const snowflakeInsertRowsTool: ToolConfig<
SnowflakeInsertRowsParams,
SnowflakeInsertRowsResponse
> = {
id: 'snowflake_insert_rows',
name: 'Snowflake Insert Rows',
description: 'Insert rows into a Snowflake table',
version: '1.0.0',
oauth: {
required: true,
provider: 'snowflake',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Snowflake',
},
accountUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Snowflake account URL (e.g., xy12345.us-east-1.snowflakecomputing.com)',
},
database: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Database name',
},
schema: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Schema name',
},
table: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Table name',
},
columns: {
type: 'array',
required: true,
visibility: 'user-only',
description: 'Array of column names to insert data into',
},
values: {
type: 'array',
required: true,
visibility: 'user-only',
description:
'Array of arrays containing values to insert. Each inner array represents one row and must match the order of columns.',
},
warehouse: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Warehouse to use (optional)',
},
role: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Role to use (optional)',
},
timeout: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Query timeout in seconds (default: 60)',
},
},
request: {
url: (params: SnowflakeInsertRowsParams) => {
const cleanUrl = parseAccountUrl(params.accountUrl)
return `https://${cleanUrl}/api/v2/statements`
},
method: 'POST',
headers: (params: SnowflakeInsertRowsParams) => ({
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
'X-Snowflake-Authorization-Token-Type': 'OAUTH',
}),
body: (params: SnowflakeInsertRowsParams) => {
// Validate inputs
if (!Array.isArray(params.columns) || params.columns.length === 0) {
throw new Error('Columns must be a non-empty array')
}
if (!Array.isArray(params.values) || params.values.length === 0) {
throw new Error('Values must be a non-empty array')
}
// Validate each row has correct number of values
for (let i = 0; i < params.values.length; i++) {
if (!Array.isArray(params.values[i])) {
throw new Error(`Values row ${i} must be an array`)
}
if (params.values[i].length !== params.columns.length) {
throw new Error(
`Values row ${i} has ${params.values[i].length} values but ${params.columns.length} columns were specified`
)
}
}
// Build INSERT SQL
const insertSQL = buildInsertSQL(
params.database,
params.schema,
params.table,
params.columns,
params.values
)
logger.info('Building INSERT statement', {
database: params.database,
schema: params.schema,
table: params.table,
columnCount: params.columns.length,
rowCount: params.values.length,
})
const requestBody: Record<string, any> = {
statement: insertSQL,
timeout: params.timeout || 60,
database: params.database,
schema: params.schema,
}
if (params.warehouse) {
requestBody.warehouse = params.warehouse
}
if (params.role) {
requestBody.role = params.role
}
return requestBody
},
},
transformResponse: async (response: Response, params?: SnowflakeInsertRowsParams) => {
if (!response.ok) {
const errorText = await response.text()
logger.error('Failed to insert rows into Snowflake table', {
status: response.status,
errorText,
table: params ? `${params.database}.${params.schema}.${params.table}` : 'unknown',
})
throw new Error(`Failed to insert rows: ${response.status} - ${errorText}`)
}
const data = await response.json()
// Get number of rows inserted from response
const rowsInserted = data.statementStatusUrl ? params?.values.length || 0 : 0
return {
success: true,
output: {
statementHandle: data.statementHandle,
rowsInserted,
message: `Successfully inserted ${rowsInserted} row(s) into ${params?.database}.${params?.schema}.${params?.table}`,
ts: new Date().toISOString(),
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Operation success status',
},
output: {
type: 'object',
description: 'Insert operation result with row count',
},
},
}

View File

@@ -247,6 +247,83 @@ export interface SnowflakeListStagesResponse extends ToolResponse {
}
}
/**
* Parameters for inserting rows
*/
export interface SnowflakeInsertRowsParams extends SnowflakeBaseParams {
database: string
schema: string
table: string
columns: string[]
values: any[][]
warehouse?: string
role?: string
timeout?: number
}
/**
* Parameters for updating rows
*/
export interface SnowflakeUpdateRowsParams extends SnowflakeBaseParams {
database: string
schema: string
table: string
updates: Record<string, any>
whereClause?: string
warehouse?: string
role?: string
timeout?: number
}
/**
* Parameters for deleting rows
*/
export interface SnowflakeDeleteRowsParams extends SnowflakeBaseParams {
database: string
schema: string
table: string
whereClause?: string
warehouse?: string
role?: string
timeout?: number
}
/**
* Response for insert rows operation
*/
export interface SnowflakeInsertRowsResponse extends ToolResponse {
output: {
statementHandle?: string
rowsInserted?: number
message?: string
ts: string
}
}
/**
* Response for update rows operation
*/
export interface SnowflakeUpdateRowsResponse extends ToolResponse {
output: {
statementHandle?: string
rowsUpdated?: number | string
message?: string
ts: string
}
}
/**
* Response for delete rows operation
*/
export interface SnowflakeDeleteRowsResponse extends ToolResponse {
output: {
statementHandle?: string
rowsDeleted?: number | string
message?: string
ts: string
}
}
/**
* Generic Snowflake response type for the block
*/
@@ -260,3 +337,6 @@ export type SnowflakeResponse =
| SnowflakeListWarehousesResponse
| SnowflakeListFileFormatsResponse
| SnowflakeListStagesResponse
| SnowflakeInsertRowsResponse
| SnowflakeUpdateRowsResponse
| SnowflakeDeleteRowsResponse

View File

@@ -0,0 +1,233 @@
import { createLogger } from '@/lib/logs/console/logger'
import type {
SnowflakeUpdateRowsParams,
SnowflakeUpdateRowsResponse,
} from '@/tools/snowflake/types'
import { parseAccountUrl } from '@/tools/snowflake/utils'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('SnowflakeUpdateRowsTool')
/**
* Build UPDATE SQL statement from parameters
*/
function buildUpdateSQL(
database: string,
schema: string,
table: string,
updates: Record<string, any>,
whereClause?: string
): string {
const fullTableName = `${database}.${schema}.${table}`
// Build SET clause
const setClause = Object.entries(updates)
.map(([column, value]) => {
let formattedValue: string
if (value === null || value === undefined) {
formattedValue = 'NULL'
} else if (typeof value === 'string') {
// Escape single quotes by doubling them
formattedValue = `'${value.replace(/'/g, "''")}'`
} else if (typeof value === 'boolean') {
formattedValue = value ? 'TRUE' : 'FALSE'
} else {
formattedValue = String(value)
}
return `${column} = ${formattedValue}`
})
.join(', ')
let sql = `UPDATE ${fullTableName} SET ${setClause}`
// Add WHERE clause if provided
if (whereClause?.trim()) {
sql += ` WHERE ${whereClause}`
}
return sql
}
export const snowflakeUpdateRowsTool: ToolConfig<
SnowflakeUpdateRowsParams,
SnowflakeUpdateRowsResponse
> = {
id: 'snowflake_update_rows',
name: 'Snowflake Update Rows',
description: 'Update rows in a Snowflake table',
version: '1.0.0',
oauth: {
required: true,
provider: 'snowflake',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Snowflake',
},
accountUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Snowflake account URL (e.g., xy12345.us-east-1.snowflakecomputing.com)',
},
database: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Database name',
},
schema: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Schema name',
},
table: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Table name',
},
updates: {
type: 'object',
required: true,
visibility: 'user-only',
description:
'Object containing column-value pairs to update (e.g., {"status": "active", "updated_at": "2024-01-01"})',
},
whereClause: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'WHERE clause to filter rows to update (e.g., "id = 123" or "status = \'pending\' AND created_at < \'2024-01-01\'"). If not provided, all rows will be updated.',
},
warehouse: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Warehouse to use (optional)',
},
role: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Role to use (optional)',
},
timeout: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Query timeout in seconds (default: 60)',
},
},
request: {
url: (params: SnowflakeUpdateRowsParams) => {
const cleanUrl = parseAccountUrl(params.accountUrl)
return `https://${cleanUrl}/api/v2/statements`
},
method: 'POST',
headers: (params: SnowflakeUpdateRowsParams) => ({
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
'X-Snowflake-Authorization-Token-Type': 'OAUTH',
}),
body: (params: SnowflakeUpdateRowsParams) => {
// Validate inputs
if (
!params.updates ||
typeof params.updates !== 'object' ||
Object.keys(params.updates).length === 0
) {
throw new Error('Updates must be a non-empty object with column-value pairs')
}
// Build UPDATE SQL
const updateSQL = buildUpdateSQL(
params.database,
params.schema,
params.table,
params.updates,
params.whereClause
)
logger.info('Building UPDATE statement', {
database: params.database,
schema: params.schema,
table: params.table,
updateColumnCount: Object.keys(params.updates).length,
hasWhereClause: !!params.whereClause,
})
// Log warning if no WHERE clause provided
if (!params.whereClause) {
logger.warn('UPDATE statement has no WHERE clause - all rows will be updated', {
table: `${params.database}.${params.schema}.${params.table}`,
})
}
const requestBody: Record<string, any> = {
statement: updateSQL,
timeout: params.timeout || 60,
database: params.database,
schema: params.schema,
}
if (params.warehouse) {
requestBody.warehouse = params.warehouse
}
if (params.role) {
requestBody.role = params.role
}
return requestBody
},
},
transformResponse: async (response: Response, params?: SnowflakeUpdateRowsParams) => {
if (!response.ok) {
const errorText = await response.text()
logger.error('Failed to update rows in Snowflake table', {
status: response.status,
errorText,
table: params ? `${params.database}.${params.schema}.${params.table}` : 'unknown',
})
throw new Error(`Failed to update rows: ${response.status} - ${errorText}`)
}
const data = await response.json()
// Extract number of rows updated from response
const rowsUpdated = data.statementStatusUrl ? 'unknown' : 0
return {
success: true,
output: {
statementHandle: data.statementHandle,
rowsUpdated,
message: `Successfully updated rows in ${params?.database}.${params?.schema}.${params?.table}`,
ts: new Date().toISOString(),
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Operation success status',
},
output: {
type: 'object',
description: 'Update operation result',
},
},
}