Skip to content

Commit a2d9b23

Browse files
goldmedaldouenergy
authored andcommitted
chore(ibis): add the fallback mechanism for v3 API (Canner#1100)
1 parent ea68014 commit a2d9b23

File tree

7 files changed

+219
-43
lines changed

7 files changed

+219
-43
lines changed

ibis-server/README.md

+25
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,28 @@ OpenTelemetry zero-code instrumentation is highly configurable. You can set the
9090

9191
## Contributing
9292
Please see [CONTRIBUTING.md](docs/CONTRIBUTING.md) for more information.
93+
94+
### Report the Migration Issue
95+
Wren engine is migrating to v3 API (powered by Rust and DataFusion). However, there are some SQL issues currently.
96+
If you find the migration message in your log, we hope you can provide the message and related information to the Wren AI Team.
97+
Just raise an issue on GitHub or contact us in the Discord channel.
98+
99+
The message would look like the following log:
100+
```
101+
2025-03-19 22:49:08.788 | [62781772-7120-4482-b7ca-4be65c8fda96] | INFO | __init__.dispatch:14 - POST /v3/connector/postgres/query
102+
2025-03-19 22:49:08.788 | [62781772-7120-4482-b7ca-4be65c8fda96] | INFO | __init__.dispatch:15 - Request params: {}
103+
2025-03-19 22:49:08.789 | [62781772-7120-4482-b7ca-4be65c8fda96] | INFO | __init__.dispatch:22 - Request body: {"connectionInfo":"REDACTED","manifestStr":"eyJjYXRhbG9nIjoid3JlbiIsInNjaGVtYSI6InB1YmxpYyIsIm1vZGVscyI6W3sibmFtZSI6Im9yZGVycyIsInRhYmxlUmVmZXJlbmNlIjp7InNjaGVtYSI6InB1YmxpYyIsIm5hbWUiOiJvcmRlcnMifSwiY29sdW1ucyI6W3sibmFtZSI6Im9yZGVya2V5IiwidHlwZSI6InZhcmNoYXIiLCJleHByZXNzaW9uIjoiY2FzdChvX29yZGVya2V5IGFzIHZhcmNoYXIpIn1dfV19","sql":"SELECT orderkey FROM orders LIMIT 1"}
104+
2025-03-19 22:49:08.804 | [62781772-7120-4482-b7ca-4be65c8fda96] | WARN | connector.query:61 - Failed to execute v3 query, fallback to v2: DataFusion error: ModelAnalyzeRule
105+
caused by
106+
Schema error: No field named o_orderkey.
107+
Wren engine is migrating to Rust version now. Wren AI team are appreciate if you can provide the error messages and related logs for us.
108+
```
109+
110+
#### Steps to Report an Issue
111+
1. **Identify the Issue**: Look for the migration message in your log files.
112+
2. **Gather Information**: Collect the error message and any related logs.
113+
3. **Report the Issue**:
114+
- **GitHub**: Open an issue on our [GitHub repository](https://github.com/Canner/wren-engine/issues) and include the collected information.
115+
- **Discord**: Join our [Discord channel](https://discord.gg/5DvshJqG8Z) and share the details with us.
116+
117+
Providing detailed information helps us to diagnose and fix the issues more efficiently. Thank you for your cooperation!

ibis-server/app/routers/v2/analysis.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
router = APIRouter(prefix="/analysis", tags=["analysis"])
77

88

9-
@router.get("/sql")
9+
@router.get("/sql", deprecated=True)
1010
def analyze_sql(dto: AnalyzeSQLDTO) -> list[dict]:
1111
return analyze(dto.manifest_str, dto.sql)
1212

1313

14-
@router.get("/sqls")
14+
@router.get("/sqls", deprecated=True)
1515
def analyze_sql_batch(dto: AnalyzeSQLBatchDTO) -> list[list[dict]]:
1616
return analyze_batch(dto.manifest_str, dto.sqls)

ibis-server/app/routers/v2/connector.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ def get_java_engine_connector(request: Request) -> JavaEngineConnector:
3030
return request.state.java_engine_connector
3131

3232

33-
@router.post("/{data_source}/query", dependencies=[Depends(verify_query_dto)])
33+
@router.post(
34+
"/{data_source}/query", dependencies=[Depends(verify_query_dto)], deprecated=True
35+
)
3436
async def query(
3537
data_source: DataSource,
3638
dto: QueryDTO,
@@ -63,7 +65,7 @@ async def query(
6365
return ORJSONResponse(to_json(connector.query(rewritten_sql, limit=limit)))
6466

6567

66-
@router.post("/{data_source}/validate/{rule_name}")
68+
@router.post("/{data_source}/validate/{rule_name}", deprecated=True)
6769
async def validate(
6870
data_source: DataSource,
6971
rule_name: str,
@@ -87,7 +89,9 @@ async def validate(
8789
return Response(status_code=204)
8890

8991

90-
@router.post("/{data_source}/metadata/tables", response_model=list[Table])
92+
@router.post(
93+
"/{data_source}/metadata/tables", response_model=list[Table], deprecated=True
94+
)
9195
def get_table_list(
9296
data_source: DataSource,
9397
dto: MetadataDTO,
@@ -102,7 +106,11 @@ def get_table_list(
102106
).get_table_list()
103107

104108

105-
@router.post("/{data_source}/metadata/constraints", response_model=list[Constraint])
109+
@router.post(
110+
"/{data_source}/metadata/constraints",
111+
response_model=list[Constraint],
112+
deprecated=True,
113+
)
106114
def get_constraints(
107115
data_source: DataSource,
108116
dto: MetadataDTO,
@@ -117,12 +125,12 @@ def get_constraints(
117125
).get_constraints()
118126

119127

120-
@router.post("/{data_source}/metadata/version")
128+
@router.post("/{data_source}/metadata/version", deprecated=True)
121129
def get_db_version(data_source: DataSource, dto: MetadataDTO) -> str:
122130
return MetadataFactory.get_metadata(data_source, dto.connection_info).get_version()
123131

124132

125-
@router.post("/dry-plan")
133+
@router.post("/dry-plan", deprecated=True)
126134
async def dry_plan(
127135
dto: DryPlanDTO,
128136
java_engine_connector: JavaEngineConnector = Depends(get_java_engine_connector),
@@ -136,7 +144,7 @@ async def dry_plan(
136144
).rewrite(dto.sql)
137145

138146

139-
@router.post("/{data_source}/dry-plan")
147+
@router.post("/{data_source}/dry-plan", deprecated=True)
140148
async def dry_plan_for_data_source(
141149
data_source: DataSource,
142150
dto: DryPlanDTO,
@@ -154,7 +162,7 @@ async def dry_plan_for_data_source(
154162
).rewrite(dto.sql)
155163

156164

157-
@router.post("/{data_source}/model-substitute")
165+
@router.post("/{data_source}/model-substitute", deprecated=True)
158166
async def model_substitute(
159167
data_source: DataSource,
160168
dto: TranspileDTO,

ibis-server/app/routers/v3/connector.py

+82-31
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from app.config import get_config
99
from app.dependencies import verify_query_dto
1010
from app.mdl.core import get_session_context
11+
from app.mdl.java_engine import JavaEngineConnector
1112
from app.mdl.rewriter import Rewriter
1213
from app.mdl.substitute import ModelSubstitute
1314
from app.model import (
@@ -19,11 +20,16 @@
1920
from app.model.connector import Connector
2021
from app.model.data_source import DataSource
2122
from app.model.validator import Validator
23+
from app.routers import v2
24+
from app.routers.v2.connector import get_java_engine_connector
2225
from app.util import build_context, pushdown_limit, to_json
2326

2427
router = APIRouter(prefix="/connector")
2528
tracer = trace.get_tracer(__name__)
2629

30+
MIGRATION_MESSAGE = "Wren engine is migrating to Rust version now. \
31+
Wren AI team are appreciate if you can provide the error messages and related logs for us."
32+
2733

2834
@router.post("/{data_source}/query", dependencies=[Depends(verify_query_dto)])
2935
async def query(
@@ -32,6 +38,7 @@ async def query(
3238
dry_run: Annotated[bool, Query(alias="dryRun")] = False,
3339
limit: int | None = None,
3440
headers: Annotated[str | None, Header()] = None,
41+
java_engine_connector: JavaEngineConnector = Depends(get_java_engine_connector),
3542
) -> Response:
3643
span_name = (
3744
f"v3_query_{data_source}_dry_run" if dry_run else f"v3_query_{data_source}"
@@ -41,44 +48,68 @@ async def query(
4148
):
4249
try:
4350
sql = pushdown_limit(dto.sql, limit)
44-
except Exception:
45-
logger.warning("Failed to pushdown limit. Using original SQL")
46-
sql = dto.sql
47-
48-
rewritten_sql = await Rewriter(
49-
dto.manifest_str, data_source=data_source, experiment=True
50-
).rewrite(sql)
51-
connector = Connector(data_source, dto.connection_info)
52-
if dry_run:
53-
connector.dry_run(rewritten_sql)
54-
return Response(status_code=204)
55-
return ORJSONResponse(to_json(connector.query(rewritten_sql, limit=limit)))
51+
rewritten_sql = await Rewriter(
52+
dto.manifest_str, data_source=data_source, experiment=True
53+
).rewrite(sql)
54+
connector = Connector(data_source, dto.connection_info)
55+
if dry_run:
56+
connector.dry_run(rewritten_sql)
57+
return Response(status_code=204)
58+
return ORJSONResponse(to_json(connector.query(rewritten_sql, limit=limit)))
59+
except Exception as e:
60+
logger.warning(
61+
"Failed to execute v3 query, fallback to v2: {}\n" + MIGRATION_MESSAGE,
62+
str(e),
63+
)
64+
return await v2.connector.query(
65+
data_source, dto, dry_run, limit, java_engine_connector, headers
66+
)
5667

5768

5869
@router.post("/dry-plan")
5970
async def dry_plan(
6071
dto: DryPlanDTO,
6172
headers: Annotated[str | None, Header()] = None,
73+
java_engine_connector: JavaEngineConnector = Depends(get_java_engine_connector),
6274
) -> str:
6375
with tracer.start_as_current_span(
6476
name="dry_plan", kind=trace.SpanKind.SERVER, context=build_context(headers)
6577
):
66-
return await Rewriter(dto.manifest_str, experiment=True).rewrite(dto.sql)
78+
try:
79+
return await Rewriter(dto.manifest_str, experiment=True).rewrite(dto.sql)
80+
except Exception as e:
81+
logger.warning(
82+
"Failed to execute v3 dry-plan, fallback to v2: {}\n"
83+
+ MIGRATION_MESSAGE,
84+
str(e),
85+
)
86+
return await v2.connector.dry_plan(dto, java_engine_connector, headers)
6787

6888

6989
@router.post("/{data_source}/dry-plan")
7090
async def dry_plan_for_data_source(
7191
data_source: DataSource,
7292
dto: DryPlanDTO,
7393
headers: Annotated[str | None, Header()] = None,
94+
java_engine_connector: JavaEngineConnector = Depends(get_java_engine_connector),
7495
) -> str:
7596
span_name = f"v3_dry_plan_{data_source}"
7697
with tracer.start_as_current_span(
7798
name=span_name, kind=trace.SpanKind.SERVER, context=build_context(headers)
7899
):
79-
return await Rewriter(
80-
dto.manifest_str, data_source=data_source, experiment=True
81-
).rewrite(dto.sql)
100+
try:
101+
return await Rewriter(
102+
dto.manifest_str, data_source=data_source, experiment=True
103+
).rewrite(dto.sql)
104+
except Exception as e:
105+
logger.warning(
106+
"Failed to execute v3 dry-plan, fallback to v2: {}\n"
107+
+ MIGRATION_MESSAGE,
108+
str(e),
109+
)
110+
return await v2.connector.dry_plan_for_data_source(
111+
data_source, dto, java_engine_connector, headers
112+
)
82113

83114

84115
@router.post("/{data_source}/validate/{rule_name}")
@@ -87,17 +118,28 @@ async def validate(
87118
rule_name: str,
88119
dto: ValidateDTO,
89120
headers: Annotated[str | None, Header()] = None,
121+
java_engine_connector: JavaEngineConnector = Depends(get_java_engine_connector),
90122
) -> Response:
91123
span_name = f"v3_validate_{data_source}"
92124
with tracer.start_as_current_span(
93125
name=span_name, kind=trace.SpanKind.SERVER, context=build_context(headers)
94126
):
95-
validator = Validator(
96-
Connector(data_source, dto.connection_info),
97-
Rewriter(dto.manifest_str, data_source=data_source, experiment=True),
98-
)
99-
await validator.validate(rule_name, dto.parameters, dto.manifest_str)
100-
return Response(status_code=204)
127+
try:
128+
validator = Validator(
129+
Connector(data_source, dto.connection_info),
130+
Rewriter(dto.manifest_str, data_source=data_source, experiment=True),
131+
)
132+
await validator.validate(rule_name, dto.parameters, dto.manifest_str)
133+
return Response(status_code=204)
134+
except Exception as e:
135+
logger.warning(
136+
"Failed to execute v3 validate, fallback to v2: {}\n"
137+
+ MIGRATION_MESSAGE,
138+
str(e),
139+
)
140+
return await v2.connector.validate(
141+
data_source, rule_name, dto, java_engine_connector, headers
142+
)
101143

102144

103145
@router.get("/{data_source}/functions")
@@ -120,17 +162,26 @@ async def model_substitute(
120162
data_source: DataSource,
121163
dto: TranspileDTO,
122164
headers: Annotated[str | None, Header()] = None,
165+
java_engine_connector: JavaEngineConnector = Depends(get_java_engine_connector),
123166
) -> str:
124167
span_name = f"v3_model-substitute_{data_source}"
125168
with tracer.start_as_current_span(
126169
name=span_name, kind=trace.SpanKind.SERVER, context=build_context(headers)
127170
):
128-
sql = ModelSubstitute(data_source, dto.manifest_str).substitute(dto.sql)
129-
Connector(data_source, dto.connection_info).dry_run(
130-
await Rewriter(
131-
dto.manifest_str,
132-
data_source=data_source,
133-
experiment=True,
134-
).rewrite(sql)
135-
)
136-
return sql
171+
try:
172+
sql = ModelSubstitute(data_source, dto.manifest_str).substitute(dto.sql)
173+
Connector(data_source, dto.connection_info).dry_run(
174+
await Rewriter(
175+
dto.manifest_str,
176+
data_source=data_source,
177+
experiment=True,
178+
).rewrite(sql)
179+
)
180+
return sql
181+
except Exception as e:
182+
logger.warning(
183+
"Failed to execute v3 model-substitute, fallback to v2: {}", str(e)
184+
)
185+
return await v2.connector.model_substitute(
186+
data_source, dto, java_engine_connector, headers
187+
)

0 commit comments

Comments
 (0)