@@ -80,6 +80,61 @@ public void templateLiterals(SessionFactoryScope scope) {
8080 "CAST({@}.foo AS signed)" , factory );
8181 }
8282
83+ @ Test
84+ @ JiraKey ("HHH-19695" )
85+ public void testFetchGrammarVsColumnNames (SessionFactoryScope scope ) {
86+ SessionFactoryImplementor factory = scope .getSessionFactory ();
87+
88+ // Test that "first" and "next" are treated as keywords when part of FETCH grammar
89+ assertWhereStringTemplate ( "fetch first 10 rows only" , "fetch first 10 rows only" , factory );
90+ assertWhereStringTemplate ( "fetch next 5 rows only" , "fetch next 5 rows only" , factory );
91+ assertWhereStringTemplate ( "select * from table fetch first 1 row only" ,
92+ "select * from table fetch first 1 row only" , factory );
93+
94+ // Mixed scenarios: ensure identifiers around FETCH grammar are still qualified
95+ assertWhereStringTemplate ( "select first_name from users fetch first 10 rows only" ,
96+ "select {@}.first_name from users fetch first 10 rows only" , factory );
97+ assertWhereStringTemplate ( "where fetch_count > 5 and fetch next 1 row only" ,
98+ "where {@}.fetch_count > 5 and fetch next 1 row only" , factory );
99+ assertWhereStringTemplate ( "select first from users fetch first 10 rows only" ,
100+ "select {@}.first from users fetch first 10 rows only" , factory );
101+ assertWhereStringTemplate ( "select next from users fetch next 10 rows only" ,
102+ "select {@}.next from users fetch next 10 rows only" , factory );
103+ }
104+
105+ @ Test
106+ @ JiraKey ("HHH-19695" )
107+ public void testFetchGrammarVariants (SessionFactoryScope scope ) {
108+ SessionFactoryImplementor factory = scope .getSessionFactory ();
109+ Dialect dialect = factory .getJdbcServices ().getDialect ();
110+
111+ // Variants of FETCH FIRST/NEXT
112+ assertWhereStringTemplate ( "fetch first 1 row only" , "fetch first 1 row only" , factory );
113+ assertWhereStringTemplate ( "fetch next 10 rows only" , "fetch next 10 rows only" , factory );
114+
115+ // Parameterized row count
116+ assertWhereStringTemplate ( "fetch next ? rows only" , "fetch next ? rows only" , factory );
117+
118+ // Casing variants
119+ assertWhereStringTemplate ( "FETCH First 10 ROWS ONLY" , "FETCH First 10 ROWS ONLY" , factory );
120+
121+ // Extra whitespace and newlines
122+ assertWhereStringTemplate ( "fetch first 10 rows only" , "fetch first 10 rows only" , factory );
123+ assertWhereStringTemplate ( "fetch\n first 3 rows only" , "fetch\n first 3 rows only" , factory );
124+
125+ // State reset after ONLY: trailing 'next' should be qualified
126+ assertWhereStringTemplate ( "fetch next 1 rows only and next > 5" ,
127+ "fetch next 1 rows only and {@}.next > 5" , factory );
128+
129+ // Qualified identifier should remain as-is
130+ assertWhereStringTemplate ( "select u.first from users u fetch first 1 row only" ,
131+ "select u.first from users u fetch first 1 row only" , factory );
132+
133+ // Quoted identifier should be qualified, while FETCH clause remains unqualified
134+ assertWhereStringTemplate ( "select `first` from users fetch first 1 row only" ,
135+ "select {@}." + dialect .quote ("`first`" ) + " from users fetch first 1 row only" , factory );
136+ }
137+
83138 private static void assertWhereStringTemplate (String sql , SessionFactoryImplementor sf ) {
84139 assertEquals ( sql ,
85140 Template .renderWhereStringTemplate (
0 commit comments