1package paged23import (4 "context"5 "os"6 "path/filepath"7 "strings"8 "testing"9 "time"1011 "github.com/parquet-go/parquet-go"1213 "src.rest/cat/012/internal/sheet"14)1516// priced is a row of a Parquet file whose types carry formats: a price17// in DECIMAL(12,2), a day and a moment.18type priced struct {19 ID int64 `parquet:"id"`20 Price int64 `parquet:"price,decimal(2:12)"`21 Day int32 `parquet:"day,date"`22 At int64 `parquet:"at,timestamp(millisecond)"`23}2425func writePriced(t *testing.T, name string, n int) {26 t.Helper()27 f, err := os.Create(name)28 if err != nil {29 t.Fatal(err)30 }31 defer f.Close()32 w := parquet.NewGenericWriter[priced](f, parquet.MaxRowsPerRowGroup(64))33 day := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)34 rows := make([]priced, n)35 for i := range rows {36 rows[i] = priced{ID: int64(i), Price: int64(i*7919%10000) + 100, Day: int32(day.Unix()/86400) + int32(i%90),37 At: day.Add(time.Duration(i) * time.Hour).UnixMilli()}38 }39 if _, err := w.Write(rows); err != nil {40 t.Fatal(err)41 }42 if err := w.Close(); err != nil {43 t.Fatal(err)44 }45}4647// TestFormatsOverASource checks that a formula over a source's column48// shows in the format its type gives, as the same formula over an49// imported copy does, and that its counts show as whole numbers.50func TestFormatsOverASource(t *testing.T) {51 name := filepath.Join(t.TempDir(), "sales.parquet")52 writePriced(t, name, 3000)53 w, h := linked(t, name, 100000)54 s := w.Sheet(0)55 price := sheet.Format{Kind: sheet.FmtNumber, Decimals: 2}56 cases := []struct {57 formula string58 want sheet.Format59 }{60 {"=SUM({T}[price])", price}, {"=AVERAGE({T}[price])", price}, {"=MIN({T}[price])", price},61 {"=MAX({T}[price])", price}, {"=MEDIAN({T}[price])", price}, {"=SUM({T}[price])*2", price},62 {"=MAX({T}[day])", sheet.Format{Kind: sheet.FmtDate}}, {"=MIN({T}[at])", sheet.Format{Kind: sheet.FmtDateTime}},63 {"=XLOOKUP(5,{T}[id],{T}[price])", price}, {"=SUM({T}[id])", sheet.Format{}},64 }65 for i, c := range cases {66 s.Set(sheet.Addr{Col: 10, Row: i}, strings.ReplaceAll(c.formula, "{T}", "sales"))67 s.Set(sheet.Addr{Col: 11, Row: i}, strings.ReplaceAll(c.formula, "{T}", "ref"))68 }69 counts := []string{"=ROWS(sales)", "=COUNT(sales[price])", "=COUNTA(sales[id])", `=COUNTIF(sales[price],">50")`,70 `=COUNTIFS(sales[id],">5",sales[day],">0")`, "=COUNTBLANK(sales[id])", "=COLUMNS(sales)"}71 for i, f := range counts {72 s.Set(sheet.Addr{Col: 12, Row: i}, f)73 }74 s.Set(sheet.Addr{Col: 13}, "=ROWS(ref)")75 h.Settle(context.Background(), w, Links(w, same, ""))76 for i, c := range cases {77 got, ref := s.DisplayFormat(sheet.Addr{Col: 10, Row: i}), s.DisplayFormat(sheet.Addr{Col: 11, Row: i})78 if got != c.want || ref != c.want {79 t.Errorf("%s: %+v over the source, %+v over the copy, want %+v", c.formula, got, ref, c.want)80 }81 if g, r := s.ShownText(sheet.Addr{Col: 10, Row: i}), s.ShownText(sheet.Addr{Col: 11, Row: i}); g != r {82 t.Errorf("%s shows %q over the source, %q over the copy", c.formula, g, r)83 }84 }85 for i, f := range counts {86 a := sheet.Addr{Col: 12, Row: i}87 if got := s.DisplayFormat(a); got != (sheet.Format{Kind: sheet.FmtNumber}) {88 t.Errorf("%s: %+v, want a whole number", f, got)89 }90 }91 if got := s.ShownText(sheet.Addr{Col: 12}); got != "3,000" {92 t.Errorf("ROWS shows %q", got)93 }94 if got := s.DisplayFormat(sheet.Addr{Col: 13}); !got.IsZero() {95 t.Errorf("a count over a sheet shows in %+v", got)96 }97}