1+ /*
2+ * Copyright 2023 Flamingock (https://www.flamingock.io)
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+ package io .flamingock .template .sql .util ;
17+
18+ import io .flamingock .internal .util .log .FlamingockLoggerFactory ;
19+
20+ import java .sql .BatchUpdateException ;
21+ import java .sql .Connection ;
22+ import java .sql .SQLException ;
23+ import java .sql .Statement ;
24+ import java .util .ArrayList ;
25+ import java .util .List ;
26+
27+ import org .slf4j .Logger ;
28+
29+ public class SqlStatementParser {
30+
31+ private static final Logger logger = FlamingockLoggerFactory .getLogger (SqlStatementParser .class );
32+
33+ public static List <String > splitStatements (String sql ) {
34+ List <String > statements = new ArrayList <>();
35+ StringBuilder currentStmt = new StringBuilder ();
36+ boolean inString = false ;
37+ boolean inComment = false ;
38+ boolean inLineComment = false ;
39+ char stringChar = '"' ;
40+
41+ for (int i = 0 ; i < sql .length (); i ++) {
42+ char c = sql .charAt (i );
43+ char next = (i + 1 < sql .length ()) ? sql .charAt (i + 1 ) : '\0' ;
44+
45+ if (!inComment && !inLineComment && !inString && c == '/' && next == '*' ) {
46+ inComment = true ;
47+ i ++; // skip next char
48+ } else if (inComment && c == '*' && next == '/' ) {
49+ inComment = false ;
50+ i ++; // skip next char
51+ } else if (!inComment && !inString && c == '-' && next == '-' ) {
52+ inLineComment = true ;
53+ i ++; // skip next char
54+ } else if (inLineComment && c == '\n' ) {
55+ inLineComment = false ;
56+ } else if (!inComment && !inLineComment && !inString && (c == '"' || c == '\'' )) {
57+ inString = true ;
58+ stringChar = c ;
59+ currentStmt .append (c );
60+ } else if (inString && c == stringChar ) {
61+ // Check for escaped quote
62+ if (i > 0 && sql .charAt (i - 1 ) == '\\' ) {
63+ currentStmt .append (c );
64+ } else {
65+ inString = false ;
66+ currentStmt .append (c );
67+ }
68+ } else if (!inComment && !inLineComment && !inString && c == ';' ) {
69+ statements .add (normalizeSpaces (currentStmt .toString ().trim ()));
70+ currentStmt .setLength (0 );
71+ } else if (!inComment && !inLineComment ) {
72+ currentStmt .append (c );
73+ }
74+ // Skip comments entirely
75+ }
76+ if (currentStmt .length () > 0 ) {
77+ statements .add (normalizeSpaces (currentStmt .toString ().trim ()));
78+ }
79+ return statements .stream ().filter (s -> !s .trim ().isEmpty ()).collect (java .util .stream .Collectors .toList ());
80+ }
81+
82+ public static String getCommand (String sql ) {
83+ String trimmed = sql .trim ();
84+ if (trimmed .isEmpty ()) {
85+ return "UNKNOWN" ;
86+ }
87+ String [] parts = trimmed .split ("\\ s+" );
88+ return parts .length > 0 ? parts [0 ].toUpperCase () : "UNKNOWN" ;
89+ }
90+
91+ public static void executeSingle (Connection connection , String stmtSql ) {
92+ try (Statement stmt = connection .createStatement ()) {
93+ stmt .execute (stmtSql );
94+ } catch (SQLException e ) {
95+ String errorMsg = "SQL execution failed: " + stmtSql ;
96+ logger .error (errorMsg , e );
97+ throw new RuntimeException (errorMsg , e );
98+ }
99+ }
100+
101+ public static void executeMany (Connection connection , List <String > statements ) {
102+ try (Statement stmt = connection .createStatement ()) {
103+ for (String stmtSql : statements ) {
104+ stmt .addBatch (stmtSql );
105+ }
106+ stmt .executeBatch ();
107+ } catch (BatchUpdateException e ) {
108+ // BatchUpdateException provides updateCounts with failed index
109+ int [] updateCounts = e .getUpdateCounts ();
110+ int failedIndex = -1 ;
111+ for (int i = 0 ; i < updateCounts .length ; i ++) {
112+ if (updateCounts [i ] == Statement .EXECUTE_FAILED ) {
113+ failedIndex = i ;
114+ break ;
115+ }
116+ }
117+ String failedStmt = failedIndex >= 0 ? statements .get (failedIndex ) : "unknown" ;
118+ String errorMsg = String .format ("Batch execution failed at statement %d: %s" , failedIndex + 1 , failedStmt );
119+ logger .error (errorMsg , e );
120+ throw new RuntimeException (errorMsg , e );
121+ } catch (SQLException e ) {
122+ String errorMsg = "Batch execution failed: " + e .getMessage ();
123+ logger .error (errorMsg , e );
124+ throw new RuntimeException (errorMsg , e );
125+ }
126+ }
127+
128+ private static String normalizeSpaces (String sql ) {
129+ if (sql == null ) {
130+ return null ;
131+ }
132+ // Replace newlines and tabs with spaces
133+ String normalized = sql .replaceAll ("[\\ r\\ n\\ t]" , " " );
134+ // Replace multiple spaces with single space
135+ normalized = normalized .replaceAll ("\\ s+" , " " );
136+ return normalized .trim ();
137+ }
138+ }
0 commit comments