Skip to content

Commit f8a97f0

Browse files
fix: Do not panic in parsing identifiers with arguments (#4191)
Do not panic in parsing identifiers with arguments. ## References #4187
1 parent 945aa69 commit f8a97f0

File tree

4 files changed

+74
-1
lines changed

4 files changed

+74
-1
lines changed

MIGRATION_GUIDE.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,27 @@ for changes required after enabling given [Snowflake BCR Bundle](https://docs.sn
2626
2727
## v2.10.1 ➞ v2.10.2
2828

29+
### *(bugfix)* Improved validation of identifiers with arguments
30+
Previously, during parsing identifiers with argument types, when the identifier format was incorrect, the provider could panic with errors like:
31+
```
32+
Stack trace from the terraform-provider-snowflake_v2.8.0 plugin:
33+
34+
panic: runtime error: index out of range [1] with length 1
35+
36+
goroutine 142 [running]:
37+
github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk.ParseSchemaObjectIdentifierWithArguments({0x14000ff3a40, 0x28})
38+
github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/identifier_parsers.go:164 +0x2bc
39+
```
40+
This could happen when e.g. the passed identifier was using the old pipe-separated format.
41+
42+
In this version, the provider validates the expected number of parts, so the error message is like this:
43+
```
44+
unexpected number of parts 1 in identifier abc|def|ghi(varchar), expected 3 in a form of "<database_name>.<schema_name>.<schema_object_name>(<argname> <argtype>...)>" where <argname> is optional
45+
```
46+
No changes in configuration are required.
47+
48+
References: [#4187](https://github.com/snowflakedb/terraform-provider-snowflake/issues/4187)
49+
2950
### *(bugfix)* Disallowed setting `DATABASE ROLES` object type on `all` and `future` fields in `snowflake_grant_ownership` resource
3051
Previously, the provider allowed setting the `DATABASE ROLES` object type on `all` and `future` fields in the `grant_ownership` resource.
3152
This operation is not allowed in Snowflake, and such resource configuration resulted in errors being returned from Snowflake.
@@ -179,7 +200,7 @@ In this version we introduce a new attribute on the provider level: [`experiment
179200

180201
We treat the available values as experiments, that may become stable feature/behavior in the future provider releases if successful.
181202

182-
Currently, the only available experiment is `WAREHOUSE_SHOW_IMPROVED_PERFORMANCE`. When enabled, it uses a slightly different SHOW query to read warehouse details. It's meant to improve the performance for accounts with many warehouses.
203+
Currently, the only available experiment is `WAREHOUSE_SHOW_IMPROVED_PERFORMANCE`. When enabled, it uses a slightly different SHOW query to read warehouse details. It's meant to improve the performance for accounts with many warehouses.
183204

184205
**Important**: to benefit from this improvement, you need to have it enabled also on your Snowflake account. To do this, please reach out to us through your Snowflake Account Manager.
185206

pkg/sdk/identifier_parsers.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ func ParseSchemaObjectIdentifierWithArguments(fullyQualifiedName string) (Schema
155155
if err != nil {
156156
return SchemaObjectIdentifierWithArguments{}, err
157157
}
158+
if len(parts) != 3 {
159+
return SchemaObjectIdentifierWithArguments{}, fmt.Errorf(`unexpected number of parts %[1]d in identifier %[2]s, expected 3 in a form of "<database_name>.<schema_name>.<schema_object_name>(<argname> <argtype>...)>" where <argname> is optional`, len(parts), fullyQualifiedName)
160+
}
158161
parsedArguments, err := ParseFunctionAndProcedureArguments(fullyQualifiedName[splitIdIndex:])
159162
if err != nil {
160163
return SchemaObjectIdentifierWithArguments{}, err
@@ -179,6 +182,9 @@ func ParseSchemaObjectIdentifierWithArgumentsAndReturnType(fullyQualifiedName st
179182
if err != nil {
180183
return SchemaObjectIdentifierWithArguments{}, err
181184
}
185+
if len(parts) != 3 {
186+
return SchemaObjectIdentifierWithArguments{}, fmt.Errorf(`unexpected number of parts %[1]d in identifier %[2]s, expected 3 in a form of "<database_name>.<schema_name>.<schema_object_name>(<argname> <argtype>...):<returntype>" where <argname> is optional`, len(parts), fullyQualifiedName)
187+
}
182188
functionHeader := parts[2]
183189
leftParenthesisIndex := strings.IndexRune(functionHeader, '(')
184190
functionName := functionHeader[:leftParenthesisIndex]

pkg/sdk/identifier_parsers_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,11 @@ func TestNewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName_WithRawInp
319319
}{
320320
{RawInput: `abc.def.ghi()`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`)},
321321
{RawInput: `abc.def.ghi(FLOAT, VECTOR(INT, 20))`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")},
322+
{RawInput: `abc.def.ghi(arg1 FLOAT, arg2 VECTOR(INT, 20))`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")},
322323
// TODO(SNOW-1571674): Won't work, because of the assumption that identifiers are not containing '(' and ')' parentheses (unfortunately, we're not able to produce meaningful errors for those cases)
323324
{RawInput: `abc."(ef".ghi(FLOAT, VECTOR(INT, 20))`, Error: `unable to read identifier: abc."`},
324325
{RawInput: `abc.def.ghi`, Error: `unable to parse identifier: '(' not present`},
326+
{RawInput: `abc|def|ghi(varchar)`, Error: `unexpected number of parts 1 in identifier abc|def|ghi(varchar), expected 3 in a form of "<database_name>.<schema_name>.<schema_object_name>(<argname> <argtype>...)>" where <argname> is optional`},
325327
}
326328

327329
for _, testCase := range testCases {
@@ -350,6 +352,7 @@ func TestNewSchemaObjectIdentifierWithArgumentsAndReturnTypeFromFullyQualifiedNa
350352
{RawInput: `abc.def."ghi(FLOAT, VECTOR(INT, 20)):NUMBER(10,2)"`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")},
351353
{RawInput: `abc.def."ghi(FLOAT, VECTOR(INT, 20)):NUMBER"`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")},
352354
{RawInput: `abc.def."ghi(ab FLOAT, VECTOR VECTOR(INT, 20), FLOAT):NUMBER"`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)", DataTypeFloat)},
355+
{RawInput: `abc|def|ghi(varchar):NUMBER`, Error: `unexpected number of parts 1 in identifier abc|def|ghi(varchar):NUMBER, expected 3 in a form of "<database_name>.<schema_name>.<schema_object_name>(<argname> <argtype>...):<returntype>" where <argname> is optional`},
353356
}
354357

355358
for _, testCase := range testCases {

pkg/testacc/resource_function_java_acceptance_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package testacc
44

55
import (
66
"fmt"
7+
"regexp"
78
"testing"
89
"time"
910

@@ -96,6 +97,7 @@ func TestAcc_FunctionJava_InlineBasic(t *testing.T) {
9697
{
9798
ResourceName: functionModel.ResourceReference(),
9899
ImportState: true,
100+
ImportStateId: id.FullyQualifiedName(),
99101
ImportStateVerify: true,
100102
ImportStateVerifyIgnore: []string{"is_secure", "arguments.0.arg_data_type", "null_input_behavior", "return_results_behavior"},
101103
ImportStateCheck: assertThatImport(t,
@@ -548,3 +550,44 @@ func TestAcc_FunctionJava_handleExternalLanguageChange(t *testing.T) {
548550
},
549551
})
550552
}
553+
554+
func TestAcc_FunctionJava_Issue4187(t *testing.T) {
555+
className := "TestFunc"
556+
funcName := "echoVarchar"
557+
returnDataType := testdatatypes.DataTypeVarchar_100
558+
559+
id := testClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes()
560+
561+
handler := fmt.Sprintf("%s.%s", className, funcName)
562+
definition := testClient().Function.SampleJavaDefinitionNoArgs(t, className, funcName)
563+
564+
functionModel := model.FunctionJavaBasicInline("w", id, returnDataType, handler, definition)
565+
566+
resource.Test(t, resource.TestCase{
567+
ProtoV6ProviderFactories: functionsAndProceduresProviderFactory,
568+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
569+
tfversion.RequireAbove(tfversion.Version1_5_0),
570+
},
571+
CheckDestroy: CheckDestroy(t, resources.FunctionJava),
572+
Steps: []resource.TestStep{
573+
// CREATE BASIC
574+
{
575+
Config: config.FromModels(t, functionModel),
576+
Check: assertThat(t,
577+
resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()).
578+
HasNameString(id.Name()).
579+
HasFunctionDefinitionString(definition).
580+
HasFunctionLanguageString("JAVA").
581+
HasFullyQualifiedNameString(id.FullyQualifiedName()),
582+
),
583+
},
584+
// IMPORT
585+
{
586+
ResourceName: functionModel.ResourceReference(),
587+
ImportState: true,
588+
ImportStateId: "a|b|c()",
589+
ExpectError: regexp.MustCompile(`unexpected number of parts 1 in identifier a|b|c(), expected 3 in a form of "<database_name>.<schema_name>.<schema_object_name>(<argname> <argtype>...)>" where <argname> is optional`),
590+
},
591+
},
592+
})
593+
}

0 commit comments

Comments
 (0)