@@ -63,7 +63,8 @@ export interface ClickHouseIntrospectionResult {
6363 */
6464async function clickhouseRequest (
6565 config : ClickHouseConnectionConfig ,
66- statement : string
66+ statement : string ,
67+ options : { readOnly ?: boolean } = { }
6768) : Promise < ClickHouseHttpResult > {
6869 const hostValidation = await validateDatabaseHost ( config . host , 'host' )
6970 if ( ! hostValidation . isValid ) {
@@ -73,6 +74,12 @@ async function clickhouseRequest(
7374 const protocol = config . secure ? 'https' : 'http'
7475 const url = new URL ( `${ protocol } ://${ config . host } :${ config . port } /` )
7576 url . searchParams . set ( 'database' , config . database )
77+ if ( options . readOnly ) {
78+ // Server-enforced read-only: ClickHouse rejects any write/DDL and forbids the
79+ // query from re-enabling writes via `SET readonly=0`. This is the real boundary
80+ // for the query operation; the SQL-shape checks below are defense-in-depth.
81+ url . searchParams . set ( 'readonly' , '1' )
82+ }
7683
7784 const controller = new AbortController ( )
7885 const timeout = setTimeout ( ( ) => controller . abort ( ) , REQUEST_TIMEOUT_MS )
@@ -293,7 +300,9 @@ export async function executeClickHouseQuery(
293300 )
294301 }
295302 }
296- const result = await clickhouseRequest ( config , ensureJsonFormat ( query ) )
303+ const result = await clickhouseRequest ( config , ensureJsonFormat ( query ) , {
304+ readOnly : options . enforceReadOnly ,
305+ } )
297306 return parseRowsResult ( result )
298307}
299308
0 commit comments