@@ -79,6 +79,28 @@ apiRouter.post('/disconnect', async (req: Request, res: Response) => {
7979 }
8080} ) ;
8181
82+ // Check if SQL statement can be paginated (only SELECT-like queries)
83+ function canPaginate ( sql : string ) : boolean {
84+ // Normalize: trim whitespace and remove leading comments
85+ let normalized = sql . trim ( ) ;
86+
87+ // Remove block comments /* ... */
88+ normalized = normalized . replace ( / \/ \* [ \s \S ] * ?\* \/ / g, '' ) ;
89+ // Remove line comments -- ...
90+ normalized = normalized . replace ( / - - [ ^ \n ] * / g, '' ) ;
91+ // Trim again after removing comments
92+ normalized = normalized . trim ( ) ;
93+
94+ // Get the first keyword (case-insensitive)
95+ const firstWord = normalized . split ( / \s + / ) [ 0 ] ?. toUpperCase ( ) || '' ;
96+
97+ // Only SELECT, WITH (CTE), TABLE, and VALUES can be paginated
98+ // Note: EXPLAIN could return rows but wrapping it would change semantics
99+ const paginatableKeywords = [ 'SELECT' , 'WITH' , 'TABLE' , 'VALUES' ] ;
100+
101+ return paginatableKeywords . includes ( firstWord ) ;
102+ }
103+
82104// Execute SQL query with optional pagination
83105apiRouter . post ( '/query' , async ( req : Request , res : Response ) => {
84106 try {
@@ -100,26 +122,35 @@ apiRouter.post('/query', async (req: Request, res: Response) => {
100122 return ;
101123 }
102124
103- // Apply pagination by wrapping the query
104- const pageLimit = typeof limit === 'number' ? limit : 1000 ; // Default page size
105- const pageOffset = typeof offset === 'number' ? offset : 0 ;
125+ // Only apply pagination to SELECT-like queries
126+ if ( canPaginate ( sql ) ) {
127+ const pageLimit = typeof limit === 'number' ? limit : 1000 ; // Default page size
128+ const pageOffset = typeof offset === 'number' ? offset : 0 ;
106129
107- // Request one extra row to detect if there are more results
108- const paginatedSql = `SELECT * FROM (${ sql . replace ( / ; + \s * $ / , '' ) } ) AS __paginated_query LIMIT ${ pageLimit + 1 } OFFSET ${ pageOffset } ` ;
130+ // Request one extra row to detect if there are more results
131+ const paginatedSql = `SELECT * FROM (${ sql . replace ( / ; + \s * $ / , '' ) } ) AS __paginated_query LIMIT ${ pageLimit + 1 } OFFSET ${ pageOffset } ` ;
109132
110- const result = await service . execute ( paginatedSql ) ;
133+ const result = await service . execute ( paginatedSql ) ;
111134
112- // Check if there are more results
113- const hasMore = result . rows . length > pageLimit ;
114- if ( hasMore ) {
115- result . rows = result . rows . slice ( 0 , pageLimit ) ;
116- result . rowCount = pageLimit ;
117- }
135+ // Check if there are more results
136+ const hasMore = result . rows . length > pageLimit ;
137+ if ( hasMore ) {
138+ result . rows = result . rows . slice ( 0 , pageLimit ) ;
139+ result . rowCount = pageLimit ;
140+ }
118141
119- res . json ( {
120- ...result ,
121- hasMore,
122- } ) ;
142+ res . json ( {
143+ ...result ,
144+ hasMore,
145+ } ) ;
146+ } else {
147+ // DDL, DML, and other non-SELECT statements: execute directly without pagination
148+ const result = await service . execute ( sql ) ;
149+ res . json ( {
150+ ...result ,
151+ hasMore : false ,
152+ } ) ;
153+ }
123154 } catch ( error ) {
124155 const message = error instanceof Error ? error . message : 'Query execution failed' ;
125156 res . status ( 500 ) . json ( { error : message } ) ;
0 commit comments