Add PostgreSQL checkpoint functionality and update related database operations
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-02-16 09:21:00 +11:00
parent ff1ec3f4aa
commit 6da2da3e82
8 changed files with 412 additions and 34 deletions

View File

@@ -279,6 +279,8 @@ func SetColAutoWidth(xlsx *excelize.File, sheetName string) error {
if err != nil {
return err
}
const minColWidth = 10
const maxColWidth = 80
for idx, col := range cols {
largestWidth := 0
for _, rowCell := range col {
@@ -287,12 +289,22 @@ func SetColAutoWidth(xlsx *excelize.File, sheetName string) error {
largestWidth = cellWidth
}
}
// Keep a sane minimum so sheets that rely on computed content
// (for example pivot output populated by Excel) don't collapse to width 0.
if largestWidth < minColWidth {
largestWidth = minColWidth
}
if largestWidth > maxColWidth {
largestWidth = maxColWidth
}
//fmt.Printf("SetColAutoWidth calculated largest width for column index '%d' is '%d'\n", idx, largestWidth)
name, err := excelize.ColumnNumberToName(idx + 1)
if err != nil {
return err
}
xlsx.SetColWidth(sheetName, name, name, float64(largestWidth))
if err := xlsx.SetColWidth(sheetName, name, name, float64(largestWidth)); err != nil {
return err
}
}
// No errors at this point
return nil

View File

@@ -928,7 +928,9 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
logger.Warn("summary worksheet skipped due to invalid data range", "table", tableName, "error", err)
return
}
dataRange := fmt.Sprintf("%s!A1:%s", quoteSheetName(dataSheet), endCell)
// excelize AddPivotTable expects unquoted sheet references like:
// "Snapshot Report!A1:Z999". Quoted sheet names cause pivot creation to fail.
dataRange := fmt.Sprintf("%s!A1:%s", dataSheet, endCell)
lowerToHeader := make(map[string]string, len(headers))
for _, header := range headers {
lowerToHeader[strings.ToLower(strings.TrimSpace(header))] = header
@@ -943,7 +945,7 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
Title: "Sum of Avg vCPUs",
TitleCell: "A1",
PivotName: "PivotAvgVcpu",
PivotRange: fmt.Sprintf("%s!A3:H1000", quoteSheetName(summarySheet)),
PivotRange: fmt.Sprintf("%s!A3:H22", summarySheet),
RowFields: []string{"Datacenter", "ResourcePool"},
DataField: "AvgVcpuCount",
DataName: "Sum of Avg vCPUs",
@@ -953,7 +955,7 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
Title: "Sum of Avg RAM",
TitleCell: "J1",
PivotName: "PivotAvgRam",
PivotRange: fmt.Sprintf("%s!J3:P1000", quoteSheetName(summarySheet)),
PivotRange: fmt.Sprintf("%s!J3:P22", summarySheet),
RowFields: []string{"Datacenter"},
DataField: "AvgRamGB",
DataName: "Sum of Avg RAM",
@@ -961,9 +963,9 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
},
{
Title: "Sum of prorated VM count",
TitleCell: "A1003",
TitleCell: "A23",
PivotName: "PivotProratedVmCount",
PivotRange: fmt.Sprintf("%s!A1005:H2002", quoteSheetName(summarySheet)),
PivotRange: fmt.Sprintf("%s!A25:H44", summarySheet),
RowFields: []string{"Datacenter"},
DataField: "AvgIsPresent",
DataName: "Sum of prorated VM count",
@@ -971,9 +973,9 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
},
{
Title: "Count of VM Name",
TitleCell: "J1003",
TitleCell: "J23",
PivotName: "PivotVmNameCount",
PivotRange: fmt.Sprintf("%s!J1005:P2002", quoteSheetName(summarySheet)),
PivotRange: fmt.Sprintf("%s!J25:P44", summarySheet),
RowFields: []string{"Datacenter"},
DataField: "Name",
DataName: "Count of VM Name",

View File

@@ -0,0 +1,62 @@
package report
import (
"io"
"log/slog"
"strings"
"testing"
"github.com/xuri/excelize/v2"
)
func TestAddSummaryPivotSheetCreatesPivotTables(t *testing.T) {
xlsx := excelize.NewFile()
const dataSheet = "Snapshot Report"
if err := xlsx.SetSheetName("Sheet1", dataSheet); err != nil {
t.Fatalf("SetSheetName failed: %v", err)
}
headers := []string{"Name", "Datacenter", "ResourcePool", "AvgVcpuCount", "AvgRamGB", "AvgIsPresent"}
if err := xlsx.SetSheetRow(dataSheet, "A1", &headers); err != nil {
t.Fatalf("SetSheetRow header failed: %v", err)
}
row1 := []interface{}{"vm-1", "dc-1", "pool-1", 4.0, 16.0, 1.0}
if err := xlsx.SetSheetRow(dataSheet, "A2", &row1); err != nil {
t.Fatalf("SetSheetRow data failed: %v", err)
}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
addSummaryPivotSheet(logger, xlsx, dataSheet, headers, 1, "inventory_daily_summary_20260215")
pivots, err := xlsx.GetPivotTables("Summary")
if err != nil {
t.Fatalf("GetPivotTables failed: %v", err)
}
if len(pivots) != 4 {
t.Fatalf("expected 4 pivot tables, got %d", len(pivots))
}
expectedNames := map[string]bool{
"PivotAvgVcpu": false,
"PivotAvgRam": false,
"PivotProratedVmCount": false,
"PivotVmNameCount": false,
}
for _, pivot := range pivots {
if _, ok := expectedNames[pivot.Name]; ok {
expectedNames[pivot.Name] = true
}
if strings.Contains(pivot.DataRange, "'") {
t.Fatalf("pivot %q has quoted DataRange %q; expected unquoted sheet reference", pivot.Name, pivot.DataRange)
}
if strings.Contains(pivot.PivotTableRange, "'") {
t.Fatalf("pivot %q has quoted PivotTableRange %q; expected unquoted sheet reference", pivot.Name, pivot.PivotTableRange)
}
}
for name, seen := range expectedNames {
if !seen {
t.Fatalf("missing expected pivot table %q", name)
}
}
}

View File

@@ -202,11 +202,13 @@ func (c *CronTask) aggregateDailySummary(ctx context.Context, targetTime time.Ti
}
c.Logger.Debug("Generated daily report", "table", summaryTable, "duration", time.Since(reportStart))
checkpointStart := time.Now()
c.Logger.Debug("Checkpointing sqlite after daily aggregation", "table", summaryTable)
if err := db.CheckpointSQLite(ctx, dbConn); err != nil {
c.Logger.Warn("failed to checkpoint sqlite after daily aggregation", "error", err)
driver := strings.ToLower(dbConn.DriverName())
c.Logger.Debug("Running database checkpoint after daily aggregation", "table", summaryTable, "driver", driver)
action, err := db.CheckpointDatabase(ctx, dbConn)
if err != nil {
c.Logger.Warn("failed to run database checkpoint after daily aggregation", "driver", driver, "action", action, "error", err)
} else {
c.Logger.Debug("Checkpointed sqlite after daily aggregation", "table", summaryTable, "duration", time.Since(checkpointStart))
c.Logger.Debug("Completed database checkpoint after daily aggregation", "table", summaryTable, "driver", driver, "action", action, "duration", time.Since(checkpointStart))
}
c.Logger.Debug("Finished daily inventory aggregation", "summary_table", summaryTable)
@@ -520,11 +522,13 @@ LIMIT 1
}
c.Logger.Debug("Generated daily report", "table", summaryTable, "duration", time.Since(reportStart))
checkpointStart := time.Now()
c.Logger.Debug("Checkpointing sqlite after daily aggregation", "table", summaryTable)
if err := db.CheckpointSQLite(ctx, dbConn); err != nil {
c.Logger.Warn("failed to checkpoint sqlite after daily aggregation (Go path)", "error", err)
driver := strings.ToLower(dbConn.DriverName())
c.Logger.Debug("Running database checkpoint after daily aggregation", "table", summaryTable, "driver", driver)
action, err := db.CheckpointDatabase(ctx, dbConn)
if err != nil {
c.Logger.Warn("failed to run database checkpoint after daily aggregation (Go path)", "driver", driver, "action", action, "error", err)
} else {
c.Logger.Debug("Checkpointed sqlite after daily aggregation", "table", summaryTable, "duration", time.Since(checkpointStart))
c.Logger.Debug("Completed database checkpoint after daily aggregation", "table", summaryTable, "driver", driver, "action", action, "duration", time.Since(checkpointStart))
}
c.Logger.Debug("Finished daily inventory aggregation (Go path)",