@@ -63,6 +63,12 @@ class ClickHouse extends SQL
6363
6464 protected string $ namespace = '' ;
6565
66+ /** @var bool Whether to log queries for debugging */
67+ private bool $ enableQueryLogging = false ;
68+
69+ /** @var array<array{sql: string, params: array<string, mixed>, duration: float, timestamp: float, success: bool, error?: string}> Query execution log */
70+ private array $ queryLog = [];
71+
6672 /**
6773 * @param string $host ClickHouse host
6874 * @param string $username ClickHouse username (default: 'default')
@@ -102,6 +108,39 @@ public function setUseFinal(bool $useFinal): self
102108 return $ this ;
103109 }
104110
111+ /**
112+ * Enable or disable query logging for debugging.
113+ *
114+ * @param bool $enable Whether to enable query logging
115+ * @return self
116+ */
117+ public function enableQueryLogging (bool $ enable = true ): self
118+ {
119+ $ this ->enableQueryLogging = $ enable ;
120+ return $ this ;
121+ }
122+
123+ /**
124+ * Get the query execution log.
125+ *
126+ * @return array<array{sql: string, params: array<string, mixed>, duration: float, timestamp: float, success: bool, error?: string}>
127+ */
128+ public function getQueryLog (): array
129+ {
130+ return $ this ->queryLog ;
131+ }
132+
133+ /**
134+ * Clear the query execution log.
135+ *
136+ * @return self
137+ */
138+ public function clearQueryLog (): self
139+ {
140+ $ this ->queryLog = [];
141+ return $ this ;
142+ }
143+
105144 /**
106145 * Get adapter name.
107146 */
@@ -310,6 +349,36 @@ private function getCounterTableName(): string
310349 return $ tableName ;
311350 }
312351
352+ /**
353+ * Log a query execution for debugging purposes.
354+ *
355+ * @param string $sql SQL query executed
356+ * @param array<string, mixed> $params Query parameters
357+ * @param float $duration Execution duration in seconds
358+ * @param bool $success Whether the query succeeded
359+ * @param string|null $error Error message if query failed
360+ */
361+ private function logQuery (string $ sql , array $ params , float $ duration , bool $ success , ?string $ error = null ): void
362+ {
363+ if (!$ this ->enableQueryLogging ) {
364+ return ;
365+ }
366+
367+ $ logEntry = [
368+ 'sql ' => $ sql ,
369+ 'params ' => $ params ,
370+ 'duration ' => $ duration ,
371+ 'timestamp ' => microtime (true ),
372+ 'success ' => $ success ,
373+ ];
374+
375+ if ($ error !== null ) {
376+ $ logEntry ['error ' ] = $ error ;
377+ }
378+
379+ $ this ->queryLog [] = $ logEntry ;
380+ }
381+
313382 /**
314383 * Execute a ClickHouse query via HTTP interface using Fetch Client.
315384 *
@@ -331,6 +400,7 @@ private function getCounterTableName(): string
331400 */
332401 private function query (string $ sql , array $ params = []): string
333402 {
403+ $ startTime = microtime (true );
334404 $ scheme = $ this ->secure ? 'https ' : 'http ' ;
335405 $ url = "{$ scheme }:// {$ this ->host }: {$ this ->port }/ " ;
336406
@@ -353,12 +423,20 @@ private function query(string $sql, array $params = []): string
353423 if ($ response ->getStatusCode () !== 200 ) {
354424 $ bodyStr = $ response ->getBody ();
355425 $ bodyStr = is_string ($ bodyStr ) ? $ bodyStr : '' ;
356- throw new Exception ("ClickHouse query failed with HTTP {$ response ->getStatusCode ()}: {$ bodyStr }" );
426+ $ duration = microtime (true ) - $ startTime ;
427+ $ errorMsg = "ClickHouse query failed with HTTP {$ response ->getStatusCode ()}: {$ bodyStr }" ;
428+ $ this ->logQuery ($ sql , $ params , $ duration , false , $ errorMsg );
429+ throw new Exception ($ errorMsg );
357430 }
358431
359432 $ body = $ response ->getBody ();
360- return is_string ($ body ) ? $ body : '' ;
433+ $ result = is_string ($ body ) ? $ body : '' ;
434+ $ duration = microtime (true ) - $ startTime ;
435+ $ this ->logQuery ($ sql , $ params , $ duration , true );
436+ return $ result ;
361437 } catch (Exception $ e ) {
438+ $ duration = microtime (true ) - $ startTime ;
439+ $ this ->logQuery ($ sql , $ params , $ duration , false , $ e ->getMessage ());
362440 // Preserve the original exception context for better debugging
363441 // Re-throw with additional context while maintaining the original exception chain
364442 throw new Exception (
@@ -384,6 +462,7 @@ private function insert(string $table, array $data): void
384462 return ;
385463 }
386464
465+ $ startTime = microtime (true );
387466 $ scheme = $ this ->secure ? 'https ' : 'http ' ;
388467 $ escapedTable = $ this ->escapeIdentifier ($ table );
389468 $ url = "{$ scheme }:// {$ this ->host }: {$ this ->port }/?query=INSERT+INTO+ {$ escapedTable }+FORMAT+JSONEachRow " ;
@@ -395,6 +474,9 @@ private function insert(string $table, array $data): void
395474 // Join JSON strings with newlines
396475 $ body = implode ("\n" , $ data );
397476
477+ $ sql = "INSERT INTO {$ escapedTable } FORMAT JSONEachRow " ;
478+ $ params = ['rows ' => count ($ data ), 'bytes ' => strlen ($ body )];
479+
398480 try {
399481 $ response = $ this ->client ->fetch (
400482 url: $ url ,
@@ -405,9 +487,17 @@ private function insert(string $table, array $data): void
405487 if ($ response ->getStatusCode () !== 200 ) {
406488 $ bodyStr = $ response ->getBody ();
407489 $ bodyStr = is_string ($ bodyStr ) ? $ bodyStr : '' ;
408- throw new Exception ("ClickHouse insert failed with HTTP {$ response ->getStatusCode ()}: {$ bodyStr }" );
490+ $ duration = microtime (true ) - $ startTime ;
491+ $ errorMsg = "ClickHouse insert failed with HTTP {$ response ->getStatusCode ()}: {$ bodyStr }" ;
492+ $ this ->logQuery ($ sql , $ params , $ duration , false , $ errorMsg );
493+ throw new Exception ($ errorMsg );
409494 }
495+
496+ $ duration = microtime (true ) - $ startTime ;
497+ $ this ->logQuery ($ sql , $ params , $ duration , true );
410498 } catch (Exception $ e ) {
499+ $ duration = microtime (true ) - $ startTime ;
500+ $ this ->logQuery ($ sql , $ params , $ duration , false , $ e ->getMessage ());
411501 throw new Exception (
412502 "ClickHouse insert execution failed: {$ e ->getMessage ()}" ,
413503 0 ,
0 commit comments