8
8
"context"
9
9
"encoding/json"
10
10
"fmt"
11
- "io"
12
11
"io/ioutil"
13
12
"math/big"
14
13
"os"
@@ -114,7 +113,7 @@ func (c *ObservingClone) getMaxQueryTime(ctx context.Context, maxTime *float64)
114
113
115
114
// countLogErrors counts log errors.
116
115
func (c * ObservingClone ) countLogErrors (ctx context.Context , logErrors * LogErrors ) error {
117
- row := c .superUserDB .QueryRow (ctx , `select sum(count), string_agg(distinct message, ',')
116
+ row := c .superUserDB .QueryRow (ctx , `select coalesce( sum(count), 0), coalesce( string_agg(distinct message, ','), ' ')
118
117
from pg_log_errors_stats()
119
118
where type in ('ERROR', 'FATAL') and database = current_database()` )
120
119
@@ -324,50 +323,28 @@ func (c *ObservingClone) getObjectsSizeStats(ctx context.Context, stat *ObjectsS
324
323
325
324
// dumpDBStats stores collected statistics.
326
325
func (c * ObservingClone ) dumpDBStats (ctx context.Context , query , filename string ) error {
327
- tempFile , err := ioutil .TempFile ("" , filename + "_*" )
328
- if err != nil {
329
- return errors .Wrap (err , "failed to create temporary file to store statistics" )
330
- }
331
-
332
- defer func () {
333
- if err := os .Remove (tempFile .Name ()); err != nil {
334
- log .Err (err )
335
- }
336
- }()
326
+ dstFilePath := path .Join (c .currentArtifactsSessionPath (), artifactsSubDir , filename )
337
327
338
- if err := tempFile . Chmod ( os . ModePerm ); err != nil {
339
- log . Err (err )
328
+ if err := initStatFile ( dstFilePath ); err != nil {
329
+ return errors . Wrapf (err , "cannot init the stat file %s" , dstFilePath )
340
330
}
341
331
342
- defer func () {
343
- if err := tempFile .Close (); err != nil {
344
- log .Err (err )
345
- }
346
- }()
347
-
332
+ // Backslash characters (\) can be used in the COPY data to quote data characters
333
+ // that might otherwise be taken as row or column delimiters.
334
+ // In particular, the following characters must be preceded by a backslash if they appear as part of a column value:
335
+ // backslash itself, newline, carriage return, and the current delimiter character.
336
+ // https://www.postgresql.org/docs/current/sql-copy.html
337
+ //
338
+ // It will lead to producing an invalid JSON with double escaped quotes.
339
+ // To work around this issue, we can pipe the output of the COPY command to `sed` to revert the double escaping of quotes.
348
340
exportQuery := fmt .Sprintf (
349
341
`COPY (select coalesce(json_agg(row_to_json(t)), '[]'::json) from (%s) as t) TO PROGRAM $$sed 's/\\\\/\\/g' > %s$$` ,
350
- query , tempFile . Name () )
342
+ query , dstFilePath )
351
343
if _ , err := c .db .Exec (ctx , exportQuery ); err != nil {
352
344
return errors .Wrap (err , "failed to export data" )
353
345
}
354
346
355
- dstFilePath := path .Join (c .currentArtifactsSessionPath (), artifactsSubDir , filename )
356
-
357
- dstFile , err := os .OpenFile (dstFilePath , os .O_CREATE | os .O_RDWR , os .ModePerm )
358
- if err != nil {
359
- return errors .Wrapf (err , "failed to init the stats file %v" , tempFile )
360
- }
361
-
362
- defer func () { _ = dstFile .Close () }()
363
-
364
- log .Dbg ("Dump data into file" , dstFile .Name ())
365
-
366
- if _ , err := io .Copy (dstFile , tempFile ); err != nil {
367
- return errors .Wrap (err , "failed copy" )
368
- }
369
-
370
- return err
347
+ return nil
371
348
}
372
349
373
350
func (c * ObservingClone ) storeFileStats (data []byte , filename string ) error {
@@ -390,7 +367,7 @@ func initStatFile(filename string) error {
390
367
return err
391
368
}
392
369
393
- if err := os .Chmod (filename , 0777 ); err != nil {
370
+ if err := os .Chmod (filename , 0666 ); err != nil {
394
371
return err
395
372
}
396
373
0 commit comments