Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add identifier parsers #2957

Merged
merged 12 commits into from
Aug 5, 2024
129 changes: 123 additions & 6 deletions pkg/helpers/identifier_string_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import (
"encoding/csv"
"fmt"
"strings"

sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
)

const (
ParameterIDDelimiter = '.'
IdDelimiter = '.'
ResourceIdDelimiter = '|'
)

func ParseIdentifierString(identifier string) ([]string, error) {
return parseIdentifierStringWithOpts(identifier, func(r *csv.Reader) {
r.Comma = ParameterIDDelimiter
})
}
type identifierParsingFunc func(string) ([]string, error)

func parseIdentifierStringWithOpts(identifier string, opts func(*csv.Reader)) ([]string, error) {
reader := csv.NewReader(strings.NewReader(identifier))
Expand All @@ -25,8 +24,126 @@ func parseIdentifierStringWithOpts(identifier string, opts func(*csv.Reader)) ([
if err != nil {
return nil, fmt.Errorf("unable to read identifier: %s, err = %w", identifier, err)
}
if lines == nil {
return make([]string, 0), nil
}
if len(lines) != 1 {
return nil, fmt.Errorf("incompatible identifier: %s", identifier)
}
return lines[0], nil
}

func ParseIdentifierString(identifier string) ([]string, error) {
sfc-gh-jmichalak marked this conversation as resolved.
Show resolved Hide resolved
return parseIdentifierStringWithOpts(identifier, func(r *csv.Reader) {
r.Comma = IdDelimiter
})
}

func ParseResourceIdentifier(identifier string) []string {
if identifier == "" {
return make([]string, 0)
}
return strings.Split(identifier, string(ResourceIdDelimiter))
}

func EncodeResourceIdentifier(parts ...string) string {
return strings.Join(parts, string(ResourceIdDelimiter))
}

func parseAccountObjectIdentifier(identifier string, parser identifierParsingFunc) (sdk.AccountObjectIdentifier, error) {
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
parts, err := parser(identifier)
if err != nil {
return sdk.AccountObjectIdentifier{}, err
}
if len(parts) != 1 {
return sdk.AccountObjectIdentifier{}, fmt.Errorf(`unexpected number of parts %d in identifier %s, expected 1 in a form of "<account_object_name>"`, len(parts), identifier)
}
return sdk.NewAccountObjectIdentifier(parts[0]), nil
}

func ParseAccountObjectIdentifier(identifier string) (sdk.AccountObjectIdentifier, error) {
return parseAccountObjectIdentifier(identifier, ParseIdentifierString)
}

func parseDatabaseObjectIdentifier(identifier string, parser identifierParsingFunc, delimiter rune) (sdk.DatabaseObjectIdentifier, error) {
parts, err := parser(identifier)
if err != nil {
return sdk.DatabaseObjectIdentifier{}, err
}
if len(parts) != 2 {
return sdk.DatabaseObjectIdentifier{}, fmt.Errorf(`unexpected number of parts %d in identifier %s, expected 2 in a form of "<database_name>%c<database_object_name>"`, len(parts), identifier, delimiter)
}
return sdk.NewDatabaseObjectIdentifier(parts[0], parts[1]), nil
}

func ParseDatabaseObjectIdentifier(identifier string) (sdk.DatabaseObjectIdentifier, error) {
return parseDatabaseObjectIdentifier(identifier, ParseIdentifierString, IdDelimiter)
}

func parseSchemaObjectIdentifier(identifier string, parser identifierParsingFunc, delimiter rune) (sdk.SchemaObjectIdentifier, error) {
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
parts, err := parser(identifier)
if err != nil {
return sdk.SchemaObjectIdentifier{}, err
}
if len(parts) != 3 {
return sdk.SchemaObjectIdentifier{}, fmt.Errorf(`unexpected number of parts %[1]d in identifier %[2]s, expected 3 in a form of "<database_name>%[3]c<schema_name>%[3]c<schema_object_name>"`, len(parts), identifier, delimiter)
}
return sdk.NewSchemaObjectIdentifier(parts[0], parts[1], parts[2]), nil
}

func ParseSchemaObjectIdentifier(identifier string) (sdk.SchemaObjectIdentifier, error) {
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
return parseSchemaObjectIdentifier(identifier, ParseIdentifierString, IdDelimiter)
}

func parseTableColumnObjectIdentifier(identifier string, parser identifierParsingFunc, delimiter rune) (sdk.TableColumnIdentifier, error) {
parts, err := parser(identifier)
if err != nil {
return sdk.TableColumnIdentifier{}, err
}
if len(parts) != 4 {
return sdk.TableColumnIdentifier{}, fmt.Errorf(`unexpected number of parts %[1]d in identifier %[2]s, expected 4 in a form of "<database_name>%[3]c<schema_name>%[3]c<table_name>%[3]c<table_column_name>"`, len(parts), identifier, delimiter)
}
return sdk.NewTableColumnIdentifier(parts[0], parts[1], parts[2], parts[3]), nil
}

func ParseTableColumnIdentifier(identifier string) (sdk.TableColumnIdentifier, error) {
return parseTableColumnObjectIdentifier(identifier, ParseIdentifierString, IdDelimiter)
}

func parseAccountIdentifier(identifier string, parser identifierParsingFunc, delimiter rune) (sdk.AccountIdentifier, error) {
sfc-gh-jmichalak marked this conversation as resolved.
Show resolved Hide resolved
parts, err := parser(identifier)
if err != nil {
return sdk.AccountIdentifier{}, err
}
if len(parts) != 2 {
return sdk.AccountIdentifier{}, fmt.Errorf(`unexpected number of parts %d in identifier %s, expected 2 in a form of "<organization_name>%c<account_name>"`, len(parts), identifier, delimiter)
}
return sdk.NewAccountIdentifier(parts[0], parts[1]), nil
}

// ParseAccountIdentifier is implemented with an assumption that the recommended format is used that contains two parts,
// organization name and account name.
func ParseAccountIdentifier(identifier string) (sdk.AccountIdentifier, error) {
return parseAccountIdentifier(identifier, ParseIdentifierString, IdDelimiter)
}

func parseExternalObjectIdentifier(identifier string, parser identifierParsingFunc, delimiter rune) (sdk.ExternalObjectIdentifier, error) {
parts, err := parser(identifier)
if err != nil {
return sdk.ExternalObjectIdentifier{}, err
}
if len(parts) != 3 {
return sdk.ExternalObjectIdentifier{}, fmt.Errorf(`unexpected number of parts %[1]d in identifier %[2]s, expected 3 in a form of "<organization_name>%[3]c<account_name>%[3]c<external_object_name>"`, len(parts), identifier, delimiter)
}
return sdk.NewExternalObjectIdentifier(sdk.NewAccountIdentifier(parts[0], parts[1]), sdk.NewAccountObjectIdentifier(parts[2])), nil
}

// ParseExternalObjectIdentifier is implemented with an assumption that the identifier consists of three parts, because:
// - After identifier rework, we expect account identifiers to always have two parts "<organization_name>.<account_name>".
// - So far, the only external things that we referred to with external identifiers had only one part (not including the account identifier),
// meaning it will always be represented as sdk.AccountObjectIdentifier. Documentation also doesn't describe any case where
// account identifier would be used as part of the identifier that would refer to the "lower level" object.
// Reference: https://docs.snowflake.com/en/user-guide/admin-account-identifier#where-are-account-identifiers-used.
func ParseExternalObjectIdentifier(identifier string) (sdk.ExternalObjectIdentifier, error) {
return parseExternalObjectIdentifier(identifier, ParseIdentifierString, IdDelimiter)
}
Loading
Loading