Skip to content

Commit 16c9c3c

Browse files
committed
handle mssql error
1 parent 007071a commit 16c9c3c

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

ibis-server/app/model/connector.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ class MSSqlConnector(SimpleConnector):
9494
def __init__(self, connection_info: ConnectionInfo):
9595
super().__init__(DataSource.mssql, connection_info)
9696

97+
@tracer.start_as_current_span("connector_query", kind=trace.SpanKind.CLIENT)
98+
def query(self, sql: str, limit: int | None = None) -> pa.Table:
99+
try:
100+
return super().query(sql, limit)
101+
except Exception as e:
102+
# To descirbe the query result, ibis will wrap the query with a subquery. MSSQL doesn't
103+
# allow order by without limit in a subquery, so we need to handle this error and provide a more user-friendly error message.
104+
# error code 1033: https://learn.microsoft.com/zh-tw/sql/relational-databases/errors-events/database-engine-events-and-errors-1000-to-1999?view=sql-server-ver15
105+
if "(1033)" in e.args[1]:
106+
raise GenericUserError(
107+
"The query with order-by requires a specific limit to be set in MSSQL."
108+
)
109+
raise
110+
97111
def dry_run(self, sql: str) -> None:
98112
try:
99113
super().dry_run(sql)
@@ -327,3 +341,12 @@ def _get_pg_type_names(connection: BaseBackend) -> dict[int, str]:
327341

328342
class QueryDryRunError(UnprocessableEntityError):
329343
pass
344+
345+
346+
class GenericUserError(UnprocessableEntityError):
347+
def __init__(self, message: str):
348+
super().__init__(message)
349+
self.message = message
350+
351+
def __str__(self) -> str:
352+
return self.message

ibis-server/tests/routers/v2/connector/test_mssql.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@
6464
],
6565
"primaryKey": "orderkey",
6666
},
67+
{
68+
"name": "null_test",
69+
"tableReference": {
70+
"schema": "dbo",
71+
"table": "null_test",
72+
},
73+
"columns": [
74+
{"name": "id", "type": "integer"},
75+
{"name": "letter", "type": "varchar"},
76+
],
77+
},
6778
],
6879
}
6980

@@ -103,6 +114,13 @@ def mssql(request) -> SqlServerContainer:
103114
@level2type = N'COLUMN', @level2name = 'o_comment';
104115
""")
105116
)
117+
conn.execute(text('CREATE TABLE "null_test" ("id" INT, "letter" TEXT)'))
118+
conn.execute(
119+
text(
120+
"INSERT INTO \"null_test\" (\"id\", \"letter\") VALUES (1, 'one'), (2, 'two'), (NULL, 'three')"
121+
)
122+
)
123+
106124
request.addfinalizer(mssql.stop)
107125
return mssql
108126

@@ -438,6 +456,58 @@ async def test_password_with_special_characters(client):
438456
assert "Microsoft SQL Server 2019" in response.text
439457

440458

459+
async def test_order_by_nulls_last(client, manifest_str, mssql: SqlServerContainer):
460+
connection_info = _to_connection_info(mssql)
461+
response = await client.post(
462+
url=f"{base_url}/query",
463+
json={
464+
"connectionInfo": connection_info,
465+
"manifestStr": manifest_str,
466+
"sql": 'SELECT letter FROM "null_test" ORDER BY id',
467+
},
468+
params={"limit": 3},
469+
)
470+
assert response.status_code == 200
471+
result = response.json()
472+
assert len(result["data"]) == 3
473+
assert result["data"][0][0] == "one"
474+
assert result["data"][1][0] == "two"
475+
assert result["data"][2][0] == "three"
476+
477+
connection_info = _to_connection_info(mssql)
478+
response = await client.post(
479+
url=f"{base_url}/query",
480+
json={
481+
"connectionInfo": connection_info,
482+
"manifestStr": manifest_str,
483+
"sql": 'SELECT letter FROM "null_test" ORDER BY id LIMIT 3',
484+
},
485+
)
486+
assert response.status_code == 200
487+
result = response.json()
488+
assert len(result["data"]) == 3
489+
assert result["data"][0][0] == "one"
490+
assert result["data"][1][0] == "two"
491+
assert result["data"][2][0] == "three"
492+
493+
494+
async def test_order_by_require_limit(client, manifest_str, mssql: SqlServerContainer):
495+
connection_info = _to_connection_info(mssql)
496+
response = await client.post(
497+
url=f"{base_url}/query",
498+
json={
499+
"connectionInfo": connection_info,
500+
"manifestStr": manifest_str,
501+
"sql": 'SELECT letter FROM "null_test" ORDER BY id NULLS LAST',
502+
},
503+
)
504+
assert response.status_code == 422
505+
assert (
506+
"The query with order-by requires a specific limit to be set in MSSQL."
507+
in response.text
508+
)
509+
510+
441511
def _to_connection_info(mssql: SqlServerContainer):
442512
return {
443513
"host": mssql.get_container_host_ip(),

0 commit comments

Comments
 (0)