1package fileio23import (4 "context"5 "os"6 "path/filepath"7 "testing"8 "time"910 "github.com/parquet-go/parquet-go"1112 "src.rest/cat/012/internal/sheet"13)1415type trip struct {16 City string `parquet:"city"`17 Miles float64 `parquet:"miles"`18 Riders int32 `parquet:"riders"`19 Paid bool `parquet:"paid"`20 Day int32 `parquet:"day,date"` // days since 197021 Started time.Time `parquet:"started,timestamp(millisecond)"`22 Note *string `parquet:"note,optional"`23 Tags []string `parquet:"tags,list"`24}2526func TestParquetImport(t *testing.T) {27 note := "late"28 day := time.Date(2026, 9, 26, 0, 0, 0, 0, time.UTC)29 rows := []trip{30 {City: "Boston", Miles: 12.5, Riders: 2, Paid: true, Day: int32(day.Unix() / 86400), Started: day.Add(14*time.Hour + 30*time.Minute), Note: ¬e, Tags: []string{"a", "b"}},31 {City: "=evil()", Miles: 3, Riders: 1, Day: int32(day.Unix()/86400) + 1, Started: day},32 }33 name := filepath.Join(t.TempDir(), "trips.parquet")34 f, err := os.Create(name)35 if err != nil {36 t.Fatal(err)37 }38 w := parquet.NewGenericWriter[trip](f)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 f.Close()4647 prog := NewProgress()48 res, err := Import(context.Background(), name, Options{Progress: prog})49 if err != nil {50 t.Fatal(err)51 }52 s := res.Sheet53 for cell, want := range map[string]string{54 "A1": "city", "B1": "miles", "E1": "day", "G1": "note", "H1": "tags.list.element",55 "A2": "Boston", "B2": "12.5", "C2": "2", "D2": "TRUE", "E2": "9/26/2026", "F2": "9/26/2026 14:30:00",56 "G2": "late", "H2": "a, b",57 "A3": "=evil()", "D3": "FALSE", "E3": "9/27/2026", "G3": "", "H3": "",58 } {59 if g := shown(s, addr(t, cell)); g != want {60 t.Errorf("%s shows %q, want %q", cell, g, want)61 }62 }63 if rows, frac := prog.Get(); rows != 3 || frac != 1 {64 t.Errorf("progress %d, %v", rows, frac)65 }66 if len(res.Notes) != 1 || res.Notes[0] != "repeated values joined with commas" {67 t.Errorf("notes %q", res.Notes)68 }69}7071// TestParquetPastLimit reads rows up to the cell budget and counts the72// rest from the file's metadata.73func TestParquetPastLimit(t *testing.T) {74 type num struct {75 N int32 `parquet:"n"`76 }77 rows := make([]num, 8196)78 for i := range rows {79 rows[i].N = int32(i + 1)80 }81 name := filepath.Join(t.TempDir(), "nums.parquet")82 f, err := os.Create(name)83 if err != nil {84 t.Fatal(err)85 }86 w := parquet.NewGenericWriter[num](f, parquet.MaxRowsPerRowGroup(1000))87 if _, err := w.Write(rows); err != nil {88 t.Fatal(err)89 }90 if err := w.Close(); err != nil {91 t.Fatal(err)92 }93 f.Close()9495 res, err := Import(context.Background(), name, Options{MaxCells: 8192})96 if err != nil {97 t.Fatal(err)98 }99 last := sheet.Addr{Row: 8191}100 if g := shown(res.Sheet, last); g != "8191" || res.Sheet.Len() != 8192 {101 t.Errorf("last row shows %q, want 8191", g)102 }103 if res.Rows != len(rows)+1 {104 t.Errorf("rows %d, want %d", res.Rows, len(rows)+1)105 }106 if len(res.Notes) != 1 || res.Notes[0] != "only the first 8,192 rows fit in max-cells (8,192 cells); 5 rows left out" {107 t.Errorf("notes %q", res.Notes)108 }109}110111func TestParquetNotParquet(t *testing.T) {112 name := filepath.Join(t.TempDir(), "x.parquet")113 os.WriteFile(name, []byte("not parquet"), 0o644)114 if _, err := Import(context.Background(), name, Options{}); err == nil {115 t.Errorf("imported junk")116 }117}