Skip to content

Commit

Permalink
SNOW-1506529 Only check errno on ProgrammingErrors (#1287)
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-fcampbell authored Jul 8, 2024
1 parent 546cde1 commit c932c57
Show file tree
Hide file tree
Showing 14 changed files with 114 additions and 85 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ tests/project/ @snowflakedb/snowcli @snowflakedb/nade

# No ownership to reduce friction
RELEASE-NOTES.md
src/snowflake/cli/api/errno.py # SQL error codes
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# General errors
NO_WAREHOUSE_SELECTED_IN_SESSION = 606

DOES_NOT_EXIST_OR_NOT_AUTHORIZED = 2003
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED = 2043

# Native Apps
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION = 93044
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES = 93045
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS = 93046
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS = 93055
APPLICATION_NO_LONGER_AVAILABLE = 93079
APPLICATION_OWNS_EXTERNAL_OBJECTS = 93128
6 changes: 0 additions & 6 deletions src/snowflake/cli/plugins/nativeapp/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,3 @@

INTERNAL_DISTRIBUTION = "internal"
EXTERNAL_DISTRIBUTION = "external"

ERROR_MESSAGE_2003 = "does not exist or not authorized"
ERROR_MESSAGE_2043 = "Object does not exist, or operation cannot be performed."
ERROR_MESSAGE_606 = "No active warehouse selected in the current session."
ERROR_MESSAGE_093079 = "Application is no longer available for use"
ERROR_MESSAGE_093128 = "The application owns one or more objects within the account"
16 changes: 9 additions & 7 deletions src/snowflake/cli/plugins/nativeapp/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
import jinja2
from click import ClickException
from snowflake.cli.api.console import cli_console as cc
from snowflake.cli.api.errno import (
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
NO_WAREHOUSE_SELECTED_IN_SESSION,
)
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
from snowflake.cli.api.project.schemas.native_app.application import (
ApplicationPostDeployHook,
Expand All @@ -48,9 +53,6 @@
from snowflake.cli.plugins.nativeapp.constants import (
ALLOWED_SPECIAL_COMMENTS,
COMMENT_COL,
ERROR_MESSAGE_606,
ERROR_MESSAGE_2003,
ERROR_MESSAGE_2043,
INTERNAL_DISTRIBUTION,
NAME_COL,
OWNER_COL,
Expand Down Expand Up @@ -87,7 +89,7 @@ def generic_sql_error_handler(
err: ProgrammingError, role: Optional[str] = None, warehouse: Optional[str] = None
):
# Potential refactor: If moving away from Python 3.8 and 3.9 to >= 3.10, use match ... case
if err.errno == 2043 or err.msg.__contains__(ERROR_MESSAGE_2043):
if err.errno == DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED:
raise ProgrammingError(
msg=dedent(
f"""\
Expand All @@ -98,7 +100,7 @@ def generic_sql_error_handler(
),
errno=err.errno,
)
elif err.errno == 606 or err.msg.__contains__(ERROR_MESSAGE_606):
elif err.errno == NO_WAREHOUSE_SELECTED_IN_SESSION:
raise ProgrammingError(
msg=dedent(
f"""\
Expand All @@ -108,7 +110,7 @@ def generic_sql_error_handler(
),
errno=err.errno,
)
elif err.msg.__contains__("does not exist or not authorized"):
elif "does not exist or not authorized" in err.msg:
raise ProgrammingError(
msg=dedent(
f"""\
Expand Down Expand Up @@ -642,7 +644,7 @@ def get_validation_result(self, use_scratch_stage: bool):
f"call system$validate_native_app_setup('{prefixed_stage_fqn}')"
)
except ProgrammingError as err:
if err.errno == 2003 and ERROR_MESSAGE_2003 in err.msg:
if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
raise ApplicationPackageDoesNotExistError(self.package_name)
generic_sql_error_handler(err)
else:
Expand Down
24 changes: 15 additions & 9 deletions src/snowflake/cli/plugins/nativeapp/run_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
import typer
from click import UsageError
from snowflake.cli.api.console import cli_console as cc
from snowflake.cli.api.errno import (
APPLICATION_NO_LONGER_AVAILABLE,
APPLICATION_OWNS_EXTERNAL_OBJECTS,
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
)
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
from snowflake.cli.api.project.util import (
Expand All @@ -33,8 +41,6 @@
from snowflake.cli.plugins.nativeapp.constants import (
ALLOWED_SPECIAL_COMMENTS,
COMMENT_COL,
ERROR_MESSAGE_093079,
ERROR_MESSAGE_093128,
LOOSE_FILES_MAGIC_VERSION,
PATCH_COL,
SPECIAL_COMMENT,
Expand All @@ -60,11 +66,11 @@

# Reasons why an `alter application ... upgrade` might fail
UPGRADE_RESTRICTION_CODES = {
93044, # Cannot upgrade dev mode application from loose stage files to version
93045, # Cannot upgrade dev mode application from version to loose stage files
93046, # Operation only permitted on dev mode application
93055, # Operation not supported on dev mode application
93079, # App package access lost
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
APPLICATION_NO_LONGER_AVAILABLE,
}


Expand Down Expand Up @@ -223,7 +229,7 @@ def drop_application_before_upgrade(
f"The following objects are owned by application {self.app_name} and need to be dropped:\n{application_objects_str}"
)
except ProgrammingError as err:
if err.errno != 93079 and ERROR_MESSAGE_093079 not in err.msg:
if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
generic_sql_error_handler(err)
cc.warning(
"The application owns other objects but they could not be determined."
Expand All @@ -245,7 +251,7 @@ def drop_application_before_upgrade(
cascade_sql = " cascade" if cascade else ""
self._execute_query(f"drop application {self.app_name}{cascade_sql}")
except ProgrammingError as err:
if (err.errno == 93128 or ERROR_MESSAGE_093128 in err.msg) and not cascade:
if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
# We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
return self.drop_application_before_upgrade(
policy, is_interactive, cascade=True
Expand Down
2 changes: 1 addition & 1 deletion src/snowflake/cli/plugins/nativeapp/teardown_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import typer
from snowflake.cli.api.console import cli_console as cc
from snowflake.cli.api.errno import APPLICATION_NO_LONGER_AVAILABLE
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
from snowflake.cli.plugins.nativeapp.constants import (
ALLOWED_SPECIAL_COMMENTS,
Expand All @@ -28,7 +29,6 @@
INTERNAL_DISTRIBUTION,
OWNER_COL,
)
from snowflake.cli.plugins.nativeapp.errno import APPLICATION_NO_LONGER_AVAILABLE
from snowflake.cli.plugins.nativeapp.exceptions import (
CouldNotDropApplicationPackageWithVersions,
)
Expand Down
21 changes: 14 additions & 7 deletions tests/git/test_git_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from unittest import mock

import pytest
from snowflake.cli.api.errno import DOES_NOT_EXIST_OR_NOT_AUTHORIZED
from snowflake.cli.plugins.stage.manager import StageManager
from snowflake.connector import DictCursor, ProgrammingError

Expand Down Expand Up @@ -187,7 +188,9 @@ def test_setup_already_exists_error(mock_om_describe, mock_connector, runner, mo
@mock.patch("snowflake.connector.connect")
@mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe")
def test_setup_invalid_url_error(mock_om_describe, mock_connector, runner, mock_ctx):
mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized")
mock_om_describe.side_effect = ProgrammingError(
errno=DOES_NOT_EXIST_OR_NOT_AUTHORIZED
)
ctx = mock_ctx()
mock_connector.return_value = ctx
communication = "http://invalid_url.git\ns"
Expand All @@ -204,7 +207,7 @@ def test_setup_no_secret_existing_api(
mock_om_describe, mock_connector, runner, mock_ctx
):
mock_om_describe.side_effect = [
ProgrammingError("does not exist or not authorized"),
ProgrammingError(errno=DOES_NOT_EXIST_OR_NOT_AUTHORIZED),
None,
]
mock_om_describe.return_value = [None, {"object_details": "something"}]
Expand Down Expand Up @@ -237,7 +240,9 @@ def test_setup_no_secret_existing_api(
@mock.patch("snowflake.connector.connect")
@mock.patch("snowflake.cli.plugins.snowpark.commands.ObjectManager.describe")
def test_setup_no_secret_create_api(mock_om_describe, mock_connector, runner, mock_ctx):
mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized")
mock_om_describe.side_effect = ProgrammingError(
errno=DOES_NOT_EXIST_OR_NOT_AUTHORIZED
)
ctx = mock_ctx()
mock_connector.return_value = ctx

Expand Down Expand Up @@ -277,7 +282,7 @@ def test_setup_existing_secret_existing_api(
mock_om_describe, mock_connector, runner, mock_ctx
):
mock_om_describe.side_effect = [
ProgrammingError("does not exist or not authorized"),
ProgrammingError(errno=DOES_NOT_EXIST_OR_NOT_AUTHORIZED),
None,
None,
]
Expand Down Expand Up @@ -319,9 +324,9 @@ def test_setup_existing_secret_create_api(
mock_om_describe, mock_connector, runner, mock_ctx
):
mock_om_describe.side_effect = [
ProgrammingError("does not exist or not authorized"),
ProgrammingError(errno=DOES_NOT_EXIST_OR_NOT_AUTHORIZED),
None,
ProgrammingError("does not exist or not authorized"),
ProgrammingError(errno=DOES_NOT_EXIST_OR_NOT_AUTHORIZED),
]
mock_om_describe.return_value = [None, "secret_details", None]
ctx = mock_ctx()
Expand Down Expand Up @@ -365,7 +370,9 @@ def test_setup_existing_secret_create_api(
def test_setup_create_secret_create_api(
mock_om_describe, mock_connector, runner, mock_ctx
):
mock_om_describe.side_effect = ProgrammingError("does not exist or not authorized")
mock_om_describe.side_effect = ProgrammingError(
errno=DOES_NOT_EXIST_OR_NOT_AUTHORIZED
)
ctx = mock_ctx()
mock_connector.return_value = ctx

Expand Down
6 changes: 4 additions & 2 deletions tests/nativeapp/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from unittest.mock import call

import pytest
from snowflake.cli.api.errno import DOES_NOT_EXIST_OR_NOT_AUTHORIZED
from snowflake.cli.api.project.definition_manager import DefinitionManager
from snowflake.cli.plugins.nativeapp.artifacts import BundleMap
from snowflake.cli.plugins.nativeapp.constants import (
Expand Down Expand Up @@ -252,7 +253,8 @@ def test_get_app_pkg_distribution_in_snowflake_throws_programming_error(
(None, mock.call("use role package_role")),
(
ProgrammingError(
msg="Application package app_pkg does not exist or not authorized."
msg="Application package app_pkg does not exist or not authorized.",
errno=DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
),
mock.call("describe application package app_pkg"),
),
Expand Down Expand Up @@ -1008,7 +1010,7 @@ def test_validate_not_deployed(mock_execute, temp_dir, mock_cursor):
(
ProgrammingError(
msg="Application package app_pkg does not exist or not authorized.",
errno=2003,
errno=DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
),
mock.call(
"call system$validate_native_app_setup('@app_pkg.app_src.stage')"
Expand Down
10 changes: 8 additions & 2 deletions tests/nativeapp/test_package_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
from unittest import mock

import pytest
from snowflake.cli.api.errno import (
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
NO_WAREHOUSE_SELECTED_IN_SESSION,
)
from snowflake.cli.api.project.definition_manager import DefinitionManager
from snowflake.cli.plugins.nativeapp.exceptions import (
InvalidPackageScriptError,
Expand Down Expand Up @@ -151,7 +155,8 @@ def test_package_scripts_w_missing_warehouse_exception(
mock_conn.return_value = MockConnectionCtx()
mock_execute_query.return_value = mock_cursor(["row"], [])
mock_execute_queries.side_effect = ProgrammingError(
msg="No active warehouse selected in the current session.", errno=606
msg="No active warehouse selected in the current session.",
errno=NO_WAREHOUSE_SELECTED_IN_SESSION,
)

working_dir: Path = project_definition_files[0].parent
Expand All @@ -173,7 +178,8 @@ def test_package_scripts_w_warehouse_access_exception(
):
mock_conn.return_value = MockConnectionCtx()
mock_execute_query.side_effect = ProgrammingError(
msg="Object does not exist, or operation cannot be performed.", errno=2043
msg="Object does not exist, or operation cannot be performed.",
errno=DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
)

working_dir: Path = project_definition_files[0].parent
Expand Down
Loading

0 comments on commit c932c57

Please sign in to comment.