2525import java .util .ArrayList ;
2626import java .util .Collections ;
2727import java .util .List ;
28+ import java .util .stream .Collectors ;
2829
2930public abstract class SqlParserFacade {
3031
@@ -137,6 +138,14 @@ public ParsedStatement parsedStatement(String sql) {
137138 public ParsedPreparedStatement parsePreparedStatement (String sql ) {
138139 ParsedPreparedStatement stmt = new ParsedPreparedStatement ();
139140 parseSQL (sql , new ParsedPreparedStatementListener (stmt ));
141+
142+ // Combine database and table like JavaCC does
143+ String tableName = stmt .getTable ();
144+ if (stmt .getDatabase () != null && stmt .getTable () != null ) {
145+ tableName = String .format ("%s.%s" , stmt .getDatabase (), stmt .getTable ());
146+ }
147+ stmt .setTable (tableName );
148+
140149 parseParameters (sql , stmt );
141150 return stmt ;
142151 }
@@ -249,56 +258,6 @@ public void enterColumnExprPrecedence3(ClickHouseParser.ColumnExprPrecedence3Con
249258 super .enterColumnExprPrecedence3 (ctx );
250259 }
251260
252- private String unquoteTableIdentifier (String rawTableId ) {
253- if (rawTableId == null || rawTableId .isEmpty ()) {
254- return rawTableId ;
255- }
256-
257- // Parse respecting quoted identifiers - don't split dots inside quotes
258- StringBuilder result = new StringBuilder ();
259- boolean inQuote = false ;
260- char quoteChar = 0 ;
261- StringBuilder currentPart = new StringBuilder ();
262-
263- for (int i = 0 ; i < rawTableId .length (); i ++) {
264- char ch = rawTableId .charAt (i );
265-
266- if (!inQuote && (ch == '`' || ch == '"' || ch == '\'' )) {
267- inQuote = true ;
268- quoteChar = ch ;
269- currentPart .append (ch );
270- } else if (inQuote && ch == quoteChar ) {
271- // Check for escaped quote (doubled quote)
272- if (i + 1 < rawTableId .length () && rawTableId .charAt (i + 1 ) == quoteChar ) {
273- currentPart .append (ch ).append (ch );
274- i ++; // Skip the next quote
275- } else {
276- inQuote = false ;
277- currentPart .append (ch );
278- }
279- } else if (!inQuote && ch == '.' ) {
280- // Dot outside quotes - split here
281- if (result .length () > 0 ) {
282- result .append ('.' );
283- }
284- result .append (ClickHouseSqlUtils .unescape (currentPart .toString ()));
285- currentPart .setLength (0 );
286- } else {
287- currentPart .append (ch );
288- }
289- }
290-
291- // Append the last part
292- if (currentPart .length () > 0 ) {
293- if (result .length () > 0 ) {
294- result .append ('.' );
295- }
296- result .append (ClickHouseSqlUtils .unescape (currentPart .toString ()));
297- }
298-
299- return result .toString ();
300- }
301-
302261 @ Override
303262 public void visitErrorNode (ErrorNode node ) {
304263 parsedStatement .setHasErrors (true );
@@ -315,19 +274,18 @@ public void enterAssignmentValuesList(ClickHouseParser.AssignmentValuesListConte
315274 parsedStatement .setAssignValuesListStopPosition (ctx .getStop ().getStopIndex ());
316275 }
317276
318-
319277 @ Override
320278 public void enterTableExprIdentifier (ClickHouseParser .TableExprIdentifierContext ctx ) {
321279 if (ctx .tableIdentifier () != null ) {
322- parsedStatement . setTable ( unquoteTableIdentifier ( ctx .tableIdentifier (). getText () ));
280+ extractAndSetDatabaseAndTable ( ctx .tableIdentifier ());
323281 }
324282 }
325283
326284 @ Override
327285 public void enterInsertStmt (ClickHouseParser .InsertStmtContext ctx ) {
328286 ClickHouseParser .TableIdentifierContext tableId = ctx .tableIdentifier ();
329287 if (tableId != null ) {
330- parsedStatement . setTable ( unquoteTableIdentifier ( tableId . getText ()) );
288+ extractAndSetDatabaseAndTable ( tableId );
331289 }
332290
333291 ClickHouseParser .ColumnsClauseContext columns = ctx .columnsClause ();
@@ -343,6 +301,35 @@ public void enterInsertStmt(ClickHouseParser.InsertStmtContext ctx) {
343301 parsedStatement .setInsert (true );
344302 }
345303
304+ /**
305+ * Extracts database and table from parse tree using grammar structure.
306+ * Grammar: tableIdentifier = (databaseIdentifier DOT)? identifier
307+ * The grammar itself defines what's database vs table!
308+ *
309+ * Examples:
310+ * table -> databaseIdentifier=null, identifier="table"
311+ * db.table -> databaseIdentifier="db", identifier="table"
312+ * a.b.c -> databaseIdentifier="a.b", identifier="c"
313+ */
314+ private void extractAndSetDatabaseAndTable (ClickHouseParser .TableIdentifierContext tableId ) {
315+ if (tableId == null ) {
316+ return ;
317+ }
318+
319+ // Table is always the standalone identifier (last part)
320+ if (tableId .identifier () != null ) {
321+ parsedStatement .setTable (ClickHouseSqlUtils .unescape (tableId .identifier ().getText ()));
322+ }
323+
324+ // Database is the databaseIdentifier part (if present)
325+ if (tableId .databaseIdentifier () != null ) {
326+ String database = tableId .databaseIdentifier ().identifier ().stream ()
327+ .map (id -> ClickHouseSqlUtils .unescape (id .getText ()))
328+ .collect (Collectors .joining ("." ));
329+ parsedStatement .setDatabase (database );
330+ }
331+ }
332+
346333 @ Override
347334 public void enterDataClauseSelect (ClickHouseParser .DataClauseSelectContext ctx ) {
348335 parsedStatement .setInsertWithSelect (true );
@@ -366,6 +353,14 @@ private static class ANTLR4AndParamsParser extends ANTLR4Parser {
366353 public ParsedPreparedStatement parsePreparedStatement (String sql ) {
367354 ParsedPreparedStatement stmt = new ParsedPreparedStatement ();
368355 parseSQL (sql , new ParseStatementAndParamsListener (stmt ));
356+
357+ // Combine database and table like JavaCC does
358+ String tableName = stmt .getTable ();
359+ if (stmt .getDatabase () != null && stmt .getTable () != null ) {
360+ tableName = String .format ("%s.%s" , stmt .getDatabase (), stmt .getTable ());
361+ }
362+ stmt .setTable (tableName );
363+
369364 return stmt ;
370365 }
371366
0 commit comments