Skip to content

Commit e3ae602

Browse files
committed
Merge remote-tracking branch 'origin/staging' into worktree-dynatrace-unmute-check
# Conflicts: # apps/sim/tools/generated/tool-metadata.ts
2 parents a0e1f7e + 76b535f commit e3ae602

8 files changed

Lines changed: 8 additions & 84 deletions

File tree

apps/docs/content/docs/en/integrations/snowflake.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Insert structured JSON rows using bound values.
164164
| `database` | string | Yes | Database name |
165165
| `schema` | string | Yes | Schema name |
166166
| `table` | string | Yes | Target Snowflake table name within the selected database and schema context |
167-
| `rows` | json | Yes | Non-empty JSON array of row objects with matching keys. Max 1000 rows and 1 MB of bound data per call - stage the files and use Load Data for bulk ingest. |
167+
| `rows` | json | Yes | Non-empty JSON array of row objects with matching keys. For bulk loads, stage the files and use Load Data instead. |
168168

169169
#### Output
170170

@@ -210,7 +210,7 @@ Update matching rows with a bound MERGE statement without inserting new rows.
210210
| `database` | string | Yes | Database name |
211211
| `schema` | string | Yes | Schema name |
212212
| `table` | string | Yes | Target Snowflake table name within the selected database and schema context |
213-
| `rows` | json | Yes | Non-empty JSON array of row objects with matching keys. Max 1000 rows and 1 MB of bound data per call - stage the files and use Load Data for bulk ingest. |
213+
| `rows` | json | Yes | Non-empty JSON array of row objects with matching keys. For bulk loads, stage the files and use Load Data instead. |
214214
| `matchColumns` | array | Yes | Columns used to match target rows. Match values must be non-null and unique across the submitted rows. |
215215

216216
#### Output
@@ -257,7 +257,7 @@ Update matching rows and insert unmatched rows with a bound MERGE statement.
257257
| `database` | string | Yes | Database name |
258258
| `schema` | string | Yes | Schema name |
259259
| `table` | string | Yes | Target Snowflake table name within the selected database and schema context |
260-
| `rows` | json | Yes | Non-empty JSON array of row objects with matching keys. Max 1000 rows and 1 MB of bound data per call - stage the files and use Load Data for bulk ingest. |
260+
| `rows` | json | Yes | Non-empty JSON array of row objects with matching keys. For bulk loads, stage the files and use Load Data instead. |
261261
| `matchColumns` | array | Yes | Columns used to match target rows. Match values must be non-null and unique across the submitted rows. |
262262

263263
#### Output

apps/sim/blocks/blocks/snowflake.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ export const SnowflakeBlock: BlockConfig<SnowflakeStatementResponse> = {
649649
rows: {
650650
type: 'string',
651651
description:
652-
'Structured rows as a JSON array. Max 1000 rows and 1 MB of bound data per call - stage the files and use Load Data for bulk ingest.',
652+
'Structured rows as a JSON array. For bulk loads, stage the files and use Load Data instead.',
653653
},
654654
matchColumns: {
655655
type: 'string',

apps/sim/tools/generated/tool-metadata.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/snowflake/insert_rows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export const insertRowsTool: ToolConfig<SnowflakeInsertRowsParams, SnowflakeStat
8181
required: true,
8282
visibility: 'user-or-llm',
8383
description:
84-
'Non-empty JSON array of row objects with matching keys. Max 1000 rows and 1 MB of bound data per call - stage the files and use Load Data for bulk ingest.',
84+
'Non-empty JSON array of row objects with matching keys. For bulk loads, stage the files and use Load Data instead.',
8585
},
8686
},
8787
request: snowflakeStatementRequest((params) =>

apps/sim/tools/snowflake/sql.test.ts

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -172,45 +172,6 @@ describe('Snowflake SQL builders', () => {
172172
)
173173
})
174174

175-
it('caps the number of rows per structured write', () => {
176-
const rows = Array.from({ length: 1001 }, (_, index) => ({ id: index }))
177-
expect(() => buildInsertRows({ ...table, rows })).toThrow('cannot exceed 1000 per call')
178-
expect(() => buildInsertRows({ ...table, rows })).toThrow('snowflake_load_data')
179-
expect(() => buildInsertRows({ ...table, rows: [{ blob: 'x'.repeat(1_000_001) }] })).toThrow(
180-
'statement budget'
181-
)
182-
})
183-
184-
it('counts the bound value budget in UTF-8 bytes, not UTF-16 code units', () => {
185-
expect(() => buildInsertRows({ ...table, rows: [{ blob: '中'.repeat(999_999) }] })).toThrow(
186-
'statement budget'
187-
)
188-
expect(() =>
189-
buildInsertRows({ ...table, rows: [{ blob: '中'.repeat(333_333) }] })
190-
).not.toThrow()
191-
})
192-
193-
it('applies the same statement budget to explicit bindings', () => {
194-
expect(() =>
195-
normalizeBindings({ '1': { type: 'TEXT', value: 'x'.repeat(1_000_001) } })
196-
).toThrow('statement budget')
197-
expect(() =>
198-
normalizeBindings({
199-
'1': { type: 'TEXT', value: 'x'.repeat(600_000) },
200-
'2': { type: 'TEXT', value: 'x'.repeat(600_000) },
201-
})
202-
).toThrow('statement budget')
203-
expect(() =>
204-
buildCallProcedure({
205-
...context,
206-
database: 'ANALYTICS',
207-
schema: 'PUBLIC',
208-
procedureName: 'REFRESH_MODEL',
209-
procedureArguments: [{ type: 'TEXT', value: '中'.repeat(999_999) }],
210-
})
211-
).toThrow('statement budget')
212-
})
213-
214175
it('routes a mixed semi-structured column through a single whole-column PARSE_JSON', () => {
215176
const result = buildInsertRows({
216177
...table,

apps/sim/tools/snowflake/sql.ts

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -90,26 +90,6 @@ function requireQueryId(queryId: string): string {
9090
return trimmed
9191
}
9292

93-
/**
94-
* Snowflake recommends limiting query text to 1 MB per statement, and that limit explicitly covers
95-
* values supplied through bindings. Statements above it still execute but are truncated before the
96-
* metadata store persists them, so they can no longer be retried or inspected.
97-
*
98-
* The budget is a byte budget, so it is measured against the UTF-8 encoding rather than the
99-
* JavaScript string length — a multi-byte string is up to 3x longer on the wire than in code units.
100-
*/
101-
const MAX_BOUND_VALUE_BYTES = 1_000_000
102-
103-
const BULK_INGEST_HINT = 'stage the data and use snowflake_load_data for bulk ingest'
104-
105-
function assertBoundBytesWithinBudget(boundBytes: number): void {
106-
if (boundBytes > MAX_BOUND_VALUE_BYTES) {
107-
throw new Error(
108-
`Snowflake bound values exceed the ${MAX_BOUND_VALUE_BYTES} byte statement budget; send fewer rows per call or ${BULK_INGEST_HINT}`
109-
)
110-
}
111-
}
112-
11393
export function normalizeBindings(
11494
input?: Record<string, SnowflakeBinding>
11595
): Record<string, SnowflakeBinding> | undefined {
@@ -119,7 +99,6 @@ export function normalizeBindings(
11999
}
120100
const normalized: Record<string, SnowflakeBinding> = {}
121101
let hasBindings = false
122-
let boundBytes = 0
123102
for (const position in input) {
124103
if (!Object.hasOwn(input, position)) continue
125104
hasBindings = true
@@ -136,27 +115,16 @@ export function normalizeBindings(
136115
if (typeof binding.value !== 'string') {
137116
throw new Error(`binding ${position} value must be a string`)
138117
}
139-
boundBytes += Buffer.byteLength(binding.value, 'utf8')
140-
assertBoundBytesWithinBudget(boundBytes)
141118
normalized[position] = { type: binding.type, value: binding.value }
142119
}
143120
return hasBindings ? normalized : undefined
144121
}
145122

146-
/**
147-
* Structured writes build a single statement holding every row, so the row count is capped well
148-
* below the byte budget to keep a typical write far away from the 1 MB statement recommendation.
149-
*/
150-
const MAX_WRITE_ROWS = 1_000
151-
152123
class BindingsBuilder {
153124
readonly bindings: Record<string, SnowflakeBinding> = {}
154125
private position = 0
155-
private boundBytes = 0
156126

157127
private addBinding(type: SnowflakeBinding['type'], value: string): string {
158-
this.boundBytes += Buffer.byteLength(value, 'utf8')
159-
assertBoundBytesWithinBudget(this.boundBytes)
160128
this.position += 1
161129
const key = String(this.position)
162130
this.bindings[key] = { type, value }
@@ -195,11 +163,6 @@ class BindingsBuilder {
195163

196164
function validateRows(rows: Array<Record<string, unknown>>): string[] {
197165
if (!Array.isArray(rows) || rows.length === 0) throw new Error('rows must be a non-empty array')
198-
if (rows.length > MAX_WRITE_ROWS) {
199-
throw new Error(
200-
`rows cannot exceed ${MAX_WRITE_ROWS} per call; send the rows in smaller batches or ${BULK_INGEST_HINT}`
201-
)
202-
}
203166
const columns = Object.keys(rows[0] ?? {})
204167
if (columns.length === 0) throw new Error('rows must contain at least one column')
205168
const identifierKeys = new Set<string>()

apps/sim/tools/snowflake/update_rows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export const updateRowsTool: ToolConfig<SnowflakeUpdateRowsParams, SnowflakeStat
9191
required: true,
9292
visibility: 'user-or-llm',
9393
description:
94-
'Non-empty JSON array of row objects with matching keys. Max 1000 rows and 1 MB of bound data per call - stage the files and use Load Data for bulk ingest.',
94+
'Non-empty JSON array of row objects with matching keys. For bulk loads, stage the files and use Load Data instead.',
9595
},
9696
matchColumns: {
9797
type: 'array',

apps/sim/tools/snowflake/upsert_rows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export const upsertRowsTool: ToolConfig<SnowflakeUpsertRowsParams, SnowflakeStat
9191
required: true,
9292
visibility: 'user-or-llm',
9393
description:
94-
'Non-empty JSON array of row objects with matching keys. Max 1000 rows and 1 MB of bound data per call - stage the files and use Load Data for bulk ingest.',
94+
'Non-empty JSON array of row objects with matching keys. For bulk loads, stage the files and use Load Data instead.',
9595
},
9696
matchColumns: {
9797
type: 'array',

0 commit comments

Comments
 (0)