2323import com .facebook .presto .common .type .TypeManager ;
2424import com .facebook .presto .common .type .TypeSignature ;
2525import com .facebook .presto .common .type .TypeSignatureParameter ;
26- import com .facebook .presto .sql .SqlFormatter ;
2726import com .facebook .presto .sql .parser .SqlParser ;
2827import com .facebook .presto .sql .planner .LiteralEncoder ;
2928import com .facebook .presto .sql .tree .AllColumns ;
3635import com .facebook .presto .sql .tree .DropTable ;
3736import com .facebook .presto .sql .tree .DropView ;
3837import com .facebook .presto .sql .tree .Expression ;
38+ import com .facebook .presto .sql .tree .ExpressionRewriter ;
39+ import com .facebook .presto .sql .tree .ExpressionTreeRewriter ;
3940import com .facebook .presto .sql .tree .FunctionCall ;
4041import com .facebook .presto .sql .tree .Identifier ;
4142import com .facebook .presto .sql .tree .Insert ;
4243import com .facebook .presto .sql .tree .IsNullPredicate ;
4344import com .facebook .presto .sql .tree .LikeClause ;
4445import com .facebook .presto .sql .tree .LogicalBinaryExpression ;
46+ import com .facebook .presto .sql .tree .Node ;
4547import com .facebook .presto .sql .tree .Property ;
4648import com .facebook .presto .sql .tree .QualifiedName ;
4749import com .facebook .presto .sql .tree .Query ;
5153import com .facebook .presto .sql .tree .ShowCreate ;
5254import com .facebook .presto .sql .tree .SingleColumn ;
5355import com .facebook .presto .sql .tree .Statement ;
56+ import com .facebook .presto .sql .tree .SubqueryExpression ;
57+ import com .facebook .presto .sql .tree .TryExpression ;
5458import com .facebook .presto .verifier .framework .ClusterType ;
5559import com .facebook .presto .verifier .framework .Column ;
56- import com .facebook .presto .verifier .framework .JsonParseSafetyWrapper ;
5760import com .facebook .presto .verifier .framework .QueryConfiguration ;
5861import com .facebook .presto .verifier .framework .QueryException ;
5962import com .facebook .presto .verifier .framework .QueryObjectBundle ;
111114
112115public class QueryRewriter
113116{
114- private static final com .facebook .airlift .log .Logger log = com .facebook .airlift .log .Logger .get (QueryRewriter .class );
115-
116117 private final SqlParser sqlParser ;
117118 private final TypeManager typeManager ;
118119 private final BlockEncodingSerde blockEncodingSerde ;
@@ -121,6 +122,7 @@ public class QueryRewriter
121122 private final Map <ClusterType , List <Property >> tableProperties ;
122123 private final Map <ClusterType , Boolean > reuseTables ;
123124 private final Optional <FunctionCallRewriter > functionCallRewriter ;
125+ private final boolean jsonParseSafetyWrapperEnabled ;
124126
125127 public QueryRewriter (
126128 SqlParser sqlParser ,
@@ -131,7 +133,7 @@ public QueryRewriter(
131133 Map <ClusterType , List <Property >> tableProperties ,
132134 Map <ClusterType , Boolean > reuseTables )
133135 {
134- this (sqlParser , typeManager , blockEncodingSerde , prestoAction , tablePrefixes , tableProperties , reuseTables , ImmutableMultimap .of ());
136+ this (sqlParser , typeManager , blockEncodingSerde , prestoAction , tablePrefixes , tableProperties , reuseTables , ImmutableMultimap .of (), false );
135137 }
136138
137139 public QueryRewriter (
@@ -143,6 +145,20 @@ public QueryRewriter(
143145 Map <ClusterType , List <Property >> tableProperties ,
144146 Map <ClusterType , Boolean > reuseTables ,
145147 Multimap <String , FunctionCallSubstitute > functionSubstitutes )
148+ {
149+ this (sqlParser , typeManager , blockEncodingSerde , prestoAction , tablePrefixes , tableProperties , reuseTables , functionSubstitutes , false );
150+ }
151+
152+ public QueryRewriter (
153+ SqlParser sqlParser ,
154+ TypeManager typeManager ,
155+ BlockEncodingSerde blockEncodingSerde ,
156+ PrestoAction prestoAction ,
157+ Map <ClusterType , QualifiedName > tablePrefixes ,
158+ Map <ClusterType , List <Property >> tableProperties ,
159+ Map <ClusterType , Boolean > reuseTables ,
160+ Multimap <String , FunctionCallSubstitute > functionSubstitutes ,
161+ boolean jsonParseSafetyWrapperEnabled )
146162 {
147163 this .sqlParser = requireNonNull (sqlParser , "sqlParser is null" );
148164 this .typeManager = requireNonNull (typeManager , "typeManager is null" );
@@ -152,26 +168,64 @@ public QueryRewriter(
152168 this .tableProperties = ImmutableMap .copyOf (tableProperties );
153169 this .reuseTables = ImmutableMap .copyOf (reuseTables );
154170 this .functionCallRewriter = FunctionCallRewriter .getInstance (functionSubstitutes , typeManager );
171+ this .jsonParseSafetyWrapperEnabled = jsonParseSafetyWrapperEnabled ;
155172 }
156173
157174 /**
158- * Helper method to apply json_parse safety wrapper to a query .
159- * Wraps json_parse() calls with TRY() to handle malformed JSON gracefully.
160- * If re-parsing fails, returns the original query unchanged .
175+ * Wraps bare json_parse() calls with TRY() using AST rewriting .
176+ * Operates directly on the AST to avoid fragile SQL format/re-parse round-trips
177+ * that silently fail on complex queries with lambdas or internal function patterns .
161178 */
162- private Query applyJsonParseSafetyWrapper (Query query )
179+ private static Query applyJsonParseSafetyWrapper (Query query )
163180 {
164- try {
165- String sql = SqlFormatter .formatSql (query , Optional .empty ());
166- String fixedSql = JsonParseSafetyWrapper .wrapUnsafeJsonParse (sql );
167- if (!sql .equals (fixedSql )) {
168- return (Query ) sqlParser .createStatement (fixedSql , PARSING_OPTIONS );
169- }
170- }
171- catch (Exception e ) {
172- log .warn (e , "Failed to apply json_parse safety wrapper, using original query" );
181+ return (Query ) new JsonParseTryWrapper ().process (query , null );
182+ }
183+
184+ /**
185+ * AST-level rewriter that wraps json_parse() FunctionCall nodes in TryExpression.
186+ * Uses the same DefaultTreeRewriter + ExpressionTreeRewriter pattern as FunctionCallRewriter.
187+ */
188+ private static class JsonParseTryWrapper
189+ extends DefaultTreeRewriter <Void >
190+ {
191+ @ Override
192+ protected Node visitExpression (Expression node , Void context )
193+ {
194+ JsonParseTryWrapper statementRewriter = this ;
195+
196+ return ExpressionTreeRewriter .rewriteWith (new ExpressionRewriter <Boolean >()
197+ {
198+ @ Override
199+ public Expression rewriteFunctionCall (FunctionCall original , Boolean insideTry , ExpressionTreeRewriter <Boolean > treeRewriter )
200+ {
201+ FunctionCall defaultRewrite = treeRewriter .defaultRewrite (original , false );
202+ if (defaultRewrite .getName ().getSuffix ().equalsIgnoreCase ("json_parse" ) && !Boolean .TRUE .equals (insideTry )) {
203+ return new TryExpression (defaultRewrite );
204+ }
205+ return defaultRewrite ;
206+ }
207+
208+ @ Override
209+ public Expression rewriteTryExpression (TryExpression original , Boolean insideTry , ExpressionTreeRewriter <Boolean > treeRewriter )
210+ {
211+ Expression inner = treeRewriter .rewrite (original .getInnerExpression (), true );
212+ if (inner != original .getInnerExpression ()) {
213+ return new TryExpression (inner );
214+ }
215+ return original ;
216+ }
217+
218+ @ Override
219+ public Expression rewriteSubqueryExpression (SubqueryExpression expression , Boolean insideTry , ExpressionTreeRewriter <Boolean > treeRewriter )
220+ {
221+ Node rewritten = statementRewriter .process (expression .getQuery (), null );
222+ if (expression .getQuery () == rewritten ) {
223+ return expression ;
224+ }
225+ return new SubqueryExpression ((Query ) rewritten );
226+ }
227+ }, node , false );
173228 }
174- return query ;
175229 }
176230
177231 public QueryObjectBundle rewriteQuery (@ Language ("SQL" ) String query , QueryConfiguration queryConfiguration , ClusterType clusterType )
@@ -197,8 +251,9 @@ public QueryObjectBundle rewriteQuery(@Language("SQL") String query, QueryConfig
197251 FunctionCallRewriter .RewriterResult rewriterResult = functionCallRewriter .get ().rewrite (createQuery );
198252 createQuery = (Query ) rewriterResult .getRewrittenNode ();
199253 functionSubstitutions = rewriterResult .getSubstitutions ();
200-
201- // Apply safety wrapper for json_parse() calls to prevent failures on malformed JSON
254+ }
255+ // Apply safety wrapper for json_parse() calls to prevent failures on malformed JSON
256+ if (jsonParseSafetyWrapperEnabled ) {
202257 createQuery = applyJsonParseSafetyWrapper (createQuery );
203258 }
204259 if (shouldReuseTable && !functionSubstitutions .isPresent ()) {
@@ -244,8 +299,9 @@ public QueryObjectBundle rewriteQuery(@Language("SQL") String query, QueryConfig
244299 FunctionCallRewriter .RewriterResult rewriterResult = functionCallRewriter .get ().rewrite (insertQuery );
245300 insertQuery = (Query ) rewriterResult .getRewrittenNode ();
246301 functionSubstitutions = rewriterResult .getSubstitutions ();
247-
248- // Apply safety wrapper for json_parse() calls
302+ }
303+ // Apply safety wrapper for json_parse() calls
304+ if (jsonParseSafetyWrapperEnabled ) {
249305 insertQuery = applyJsonParseSafetyWrapper (insertQuery );
250306 }
251307 if (shouldReuseTable && !functionSubstitutions .isPresent ()) {
@@ -291,8 +347,9 @@ public QueryObjectBundle rewriteQuery(@Language("SQL") String query, QueryConfig
291347 FunctionCallRewriter .RewriterResult rewriterResult = functionCallRewriter .get ().rewrite (queryBody );
292348 queryBody = (Query ) rewriterResult .getRewrittenNode ();
293349 functionSubstitutions = rewriterResult .getSubstitutions ();
294-
295- // Apply safety wrapper for json_parse() calls
350+ }
351+ // Apply safety wrapper for json_parse() calls
352+ if (jsonParseSafetyWrapperEnabled ) {
296353 queryBody = applyJsonParseSafetyWrapper (queryBody );
297354 }
298355
0 commit comments