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

Fix not able to add patch to quoted versions #1943

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

## Fixes and improvements
* Fixed crashes with older x86_64 Intel CPUs.
* Fixed inability to add patches to lowercase quoted versions

# v3.2.0

Expand Down
4 changes: 4 additions & 0 deletions src/snowflake/cli/api/project/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ def unquote_identifier(identifier: str) -> str:
string for a LIKE clause, or to match an identifier passed back as
a value from a SQL statement.
"""
# ensure input is a valid identifier - otherwise, it could accidentally uppercase
# a quoted identifier
identifier = to_identifier(identifier)

if match := re.fullmatch(QUOTED_IDENTIFIER_REGEX, identifier):
return match.group(1).replace('""', '"')
# unquoted identifiers are internally represented as uppercase
Expand Down
22 changes: 22 additions & 0 deletions tests/project/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
to_identifier,
to_quoted_identifier,
to_string_literal,
unquote_identifier,
)

VALID_UNQUOTED_IDENTIFIERS = (
Expand Down Expand Up @@ -335,3 +336,24 @@ def test_identifier_to_str(identifier, expected_value):
)
def test_sanitize_identifier(identifier, expected_value):
assert sanitize_identifier(identifier) == expected_value


@pytest.mark.parametrize(
"identifier, expected",
[
# valid unquoted id -> return upper case version
("Id_1", "ID_1"),
# valid quoted id -> remove quotes and keep case
('"Id""1"', 'Id"1'),
# unquoted id with special characters -> treat it as quoted ID and reserve case
("Id.aBc", "Id.aBc"),
# unquoted id with double quotes inside -> treat is quoted ID
('Id"1', 'Id"1'),
# quoted id with escaped double quotes -> unescape and keep case
('"Id""1"', 'Id"1'),
# empty string -> return the same
("", ""),
],
)
def test_unquote_identifier(identifier, expected):
assert unquote_identifier(identifier) == expected
2 changes: 1 addition & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def test_path_resolver(mock_system, argument, expected):
("my_app", "MY_APP"),
('"My App"', "My%20App"),
("SYSTEM$GET", "SYSTEM%24GET"),
("mailorder_!@#$%^&*()/_app", "MAILORDER_!%40%23%24%25%5E%26*()%2F_APP"),
("mailorder_!@#$%^&*()/_app", "mailorder_!%40%23%24%25%5E%26*()%2F_app"),
('"Mailorder *App* is /cool/"', "Mailorder%20*App*%20is%20%2Fcool%2F"),
],
)
Expand Down
50 changes: 50 additions & 0 deletions tests_integration/nativeapp/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,53 @@ def test_version_create_with_manage_versions_only(
]
)
assert result.exit_code == 0, result.output


@pytest.mark.integration
def test_nativeapp_version_create_quoted_identifiers(
runner,
snowflake_session,
default_username,
resource_suffix,
nativeapp_project_directory,
):
project_name = "myapp"
with nativeapp_project_directory("napp_init_v2"):
package_name = f"{project_name}_pkg_{default_username}{resource_suffix}".upper()

# create version
result = runner.invoke_with_connection_json(
["app", "version", "create", "v1.0"]
)
assert result.exit_code == 0

# create another patch
result = runner.invoke_with_connection_json(
["app", "version", "create", "v1.0"]
)
assert result.exit_code == 0

# create custom patch
result = runner.invoke_with_connection_json(
["app", "version", "create", "v1.0", "--patch", "4"]
)
assert result.exit_code == 0

# app package contains 3 patches for version v1.0
expect = row_from_snowflake_session(
snowflake_session.execute_string(
f"show versions in application package {package_name}"
)
)
assert contains_row_with(expect, {"version": "v1.0", "patch": 0})
assert contains_row_with(expect, {"version": "v1.0", "patch": 1})
assert contains_row_with(expect, {"version": "v1.0", "patch": 4})

# drop the version
result_drop = runner.invoke_with_connection_json(
["app", "version", "drop", "v1.0", "--force"]
)
assert result_drop.exit_code == 0

actual = runner.invoke_with_connection_json(["app", "version", "list"])
assert len(actual.json) == 0
Loading