Skip to content

Commit ea70c43

Browse files
SNOW-2241913: non-retryable error handling (#3770)
1 parent 6037cd7 commit ea70c43

12 files changed

Lines changed: 140 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104

105105
#### Improvements
106106

107+
- Improved `DataFrameReader.dbapi`(PuPr) that dbapi will not retry on non-retryable error such as SQL syntax error on external data source query.
107108
- Removed unnecessary warnings about local package version mismatch when using `session.read.option('rowTag', <tag_name>).xml(<stage_file_path>)` or `xpath` functions.
108109
- Improved `DataFrameReader.dbapi` (PuPr) reading performance by setting the default `fetch_size` parameter value to 100000.
109110
- Improved error message for XSD validation failure when reading XML files using `session.read.option('rowValidationXSDPath', <xsd_path>).xml(<stage_file_path>)`.

src/snowflake/snowpark/_internal/data_source/datasource_reader.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ def read(self, partition: str) -> Iterator[List[Any]]:
8585
batch = []
8686
else:
8787
raise ValueError("fetch size cannot be smaller than 0")
88+
except Exception as exc:
89+
if self.driver.non_retryable_error_checker(exc):
90+
raise SnowparkDataframeReaderException(message=str(exc))
91+
else:
92+
raise
8893
finally:
8994
try:
9095
cursor.close()

src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ def to_snow_type(self, schema: List[Any]) -> StructType:
5555
f"{self.__class__.__name__} has not implemented to_snow_type function"
5656
)
5757

58+
def non_retryable_error_checker(self, error: Exception) -> bool:
59+
return False
60+
5861
@staticmethod
5962
def prepare_connection(
6063
conn: "Connection",

src/snowflake/snowpark/_internal/data_source/drivers/databricks_driver.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,18 @@ def to_snow_type(self, schema: List[Any]) -> StructType:
6868
all_columns.append(StructField(column_name, data_type, True))
6969
return StructType(all_columns)
7070

71+
def non_retryable_error_checker(self, error: Exception) -> bool:
72+
import databricks.sql
73+
74+
if isinstance(error, databricks.sql.ServerOperationError):
75+
syntax_error_codes = [
76+
"PARSE_SYNTAX_ERROR", # syntax error
77+
]
78+
for error_code in syntax_error_codes:
79+
if error_code in str(error):
80+
return True
81+
return False
82+
7183
def udtf_class_builder(
7284
self,
7385
fetch_size: int = 1000,

src/snowflake/snowpark/_internal/data_source/drivers/oracledb_driver.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,30 @@ def prepare_connection(
111111
conn.outputtypehandler = output_type_handler
112112
return conn
113113

114+
def non_retryable_error_checker(self, error: Exception) -> bool:
115+
import oracledb
116+
117+
if isinstance(error, oracledb.DatabaseError):
118+
syntax_error_codes = [
119+
"ORA-00900", # invalid SQL statement
120+
"ORA-00901", # invalid CREATE command
121+
"ORA-00904", # invalid identifier
122+
"ORA-00905", # missing keyword
123+
"ORA-00906", # missing left parenthesis
124+
"ORA-00907", # missing right parenthesis
125+
"ORA-00911", # invalid character
126+
"ORA-00920", # invalid relational operator
127+
"ORA-00921", # unexpected end of SQL command
128+
"ORA-00923", # FROM keyword not found where expected
129+
"ORA-00933", # SQL command not properly ended
130+
"ORA-00936", # missing expression
131+
"ORA-00942", # table or view does not exist
132+
]
133+
for error_code in syntax_error_codes:
134+
if error_code in str(error):
135+
return True
136+
return False
137+
114138
def udtf_class_builder(
115139
self,
116140
fetch_size: int = 1000,

src/snowflake/snowpark/_internal/data_source/drivers/psycopg2_driver.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,18 @@ def to_snow_type(self, schema: List[Any]) -> StructType:
211211
fields.append(StructField(name, data_type, True))
212212
return StructType(fields)
213213

214+
def non_retryable_error_checker(self, error: Exception) -> bool:
215+
import psycopg2
216+
217+
if isinstance(error, psycopg2.errors.SyntaxError):
218+
syntax_error_codes = [
219+
"42601", # syntax error
220+
]
221+
for error_code in syntax_error_codes:
222+
if error_code == str(error.pgcode):
223+
return True
224+
return False
225+
214226
@staticmethod
215227
def to_result_snowpark_df(
216228
session: "Session", table_name, schema, _emit_ast: bool = True

src/snowflake/snowpark/_internal/data_source/drivers/pymsql_driver.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,18 @@ def to_snow_type(self, schema: List[Any]) -> StructType:
183183
fields.append(StructField(name, data_type, null_ok))
184184
return StructType(fields)
185185

186+
def non_retryable_error_checker(self, error: Exception) -> bool:
187+
import pymysql
188+
189+
if isinstance(error, pymysql.err.ProgrammingError):
190+
syntax_error_codes = [
191+
"1064", # syntax error
192+
]
193+
for error_code in syntax_error_codes:
194+
if error_code in str(error):
195+
return True
196+
return False
197+
186198
def udtf_class_builder(
187199
self,
188200
fetch_size: int = 1000,

tests/integ/datasource/test_databricks.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,18 @@ def test_unsupported_type():
265265
assert schema == StructType([StructField("TEST_COL", StringType(), nullable=True)])
266266

267267

268+
def test_databricks_non_retryable_error(session):
269+
with pytest.raises(
270+
SnowparkDataframeReaderException,
271+
match="PARSE_SYNTAX_ERROR",
272+
):
273+
session.read.dbapi(
274+
create_databricks_connection,
275+
table=TEST_TABLE_NAME,
276+
predicates=["invalid syntax"],
277+
)
278+
279+
268280
def test_session_init(session):
269281
with pytest.raises(
270282
SnowparkDataframeReaderException,

tests/integ/datasource/test_mysql.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,18 @@ def test_unsupported_type():
305305
assert schema == StructType([StructField("TEST_COL", StringType(), nullable=True)])
306306

307307

308+
def test_mysql_non_retryable_error(session):
309+
with pytest.raises(
310+
SnowparkDataframeReaderException,
311+
match="You have an error in your SQL syntax",
312+
):
313+
session.read.dbapi(
314+
create_connection_mysql,
315+
table=TEST_TABLE_NAME,
316+
predicates=["invalid syntax"],
317+
)
318+
319+
308320
def test_session_init(session):
309321
with pytest.raises(
310322
SnowparkDataframeReaderException,

tests/integ/datasource/test_oracledb.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,18 @@ def test_unsupported_type():
255255
assert schema == StructType([StructField("TEST_COL", StringType(), nullable=True)])
256256

257257

258+
def test_oracledb_non_retryable_error(session):
259+
with pytest.raises(
260+
SnowparkDataframeReaderException,
261+
match="ORA-00920: invalid relational operator",
262+
):
263+
session.read.dbapi(
264+
create_connection_oracledb,
265+
table=ORACLEDB_TABLE_NAME,
266+
predicates=["invalid syntax"],
267+
).collect()
268+
269+
258270
def test_query_timeout_and_session_init(session):
259271
statement = """
260272
BEGIN

0 commit comments

Comments
 (0)