11package cmd
22
33import (
4+ "bytes"
5+ "context"
46 "encoding/json"
57 "fmt"
68 "io"
@@ -13,15 +15,15 @@ import (
1315 "github.com/spf13/cobra"
1416)
1517
18+ const masterDataPageSize = 1000
19+
1620var (
1721 systemMasterListOutput string
1822 systemMasterListJQ string
1923 systemMasterShowOutput string
2024 systemMasterShowJQ string
2125
22- systemMasterDataType int
23- systemMasterDataRows int
24- systemMasterDataOffset int
26+ systemMasterDataType string
2527 systemMasterDataFormat string
2628 systemMasterDataFileName string
2729 systemMasterDataDelimiter string
@@ -67,8 +69,8 @@ var systemMasterDataCmd = &cobra.Command{
6769 Long : `Export master rows via GET /api/v1/system/master/{master_code}/data.
6870
6971--type (required) selects the master kind:
70- 0 simple master
71- 1 user-specific master (pass the table_name as <master_code>)
72+ simple simple master
73+ user user-specific master (pass the table_name as <master_code>)
7274
7375--format defaults to json. Use --format csv for CSV output; the CSV
7476payload is written to stdout (or --output FILE / DIR/).` ,
@@ -117,16 +119,14 @@ func init() {
117119 sf .StringVar (& systemMasterShowJQ , "jq" , "" , "apply a gojq filter to the JSON response (forces JSON output)" )
118120
119121 df := systemMasterDataCmd .Flags ()
120- df .IntVar (& systemMasterDataType , "type" , - 1 , "master_type: 0=simple master, 1=user-specific master (required)" )
121- df .IntVar (& systemMasterDataRows , "rows" , 0 , "number of rows to fetch (0 = omit; server default 100; max 1000)" )
122- df .IntVar (& systemMasterDataOffset , "offset" , 0 , "offset (0 = omit; server default 0)" )
122+ df .StringVar (& systemMasterDataType , "type" , "" , "master type: simple | user (required)" )
123123 df .StringVar (& systemMasterDataFormat , "format" , "json" , "output format: json | csv" )
124124 df .StringVar (& systemMasterDataFileName , "file-name" , "" , "CSV file name hint (CSV only; default: {master_code}.csv)" )
125125 df .StringVar (& systemMasterDataDelimiter , "delimiter" , "" , "CSV delimiter: comma | tab (CSV only; default comma)" )
126126 df .BoolVar (& systemMasterDataTitle , "title" , false , "CSV only (user-specific master): include field names on the first row (default: true)" )
127127 df .BoolVar (& systemMasterDataNoTitle , "no-title" , false , "CSV only (user-specific master): omit field names from the first row" )
128128 df .StringVar (& systemMasterDataFields , "fields" , "" , "CSV only (simple master): comma-separated list of field names to include" )
129- df .StringVarP (& systemMasterDataOutput , "output" , "o" , "" , "output path: FILE, DIR/, - for stdout (default: stdout for JSON, server-provided filename for CSV )" )
129+ df .StringVarP (& systemMasterDataOutput , "output" , "o" , "" , "output path: FILE, DIR/, - for stdout (default: stdout)" )
130130 df .StringVar (& systemMasterDataJQ , "jq" , "" , "apply a gojq filter to the JSON response (JSON format only)" )
131131 _ = systemMasterDataCmd .MarkFlagRequired ("type" )
132132
@@ -203,8 +203,14 @@ func runSystemMasterData(cmd *cobra.Command, args []string) error {
203203 if masterCode == "" {
204204 return fmt .Errorf ("master_code is required" )
205205 }
206- if systemMasterDataType != 0 && systemMasterDataType != 1 {
207- return fmt .Errorf ("--type must be 0 (simple) or 1 (user-specific), got %d" , systemMasterDataType )
206+ var masterType int
207+ switch strings .ToLower (strings .TrimSpace (systemMasterDataType )) {
208+ case "simple" :
209+ masterType = 0
210+ case "user" :
211+ masterType = 1
212+ default :
213+ return fmt .Errorf ("--type must be simple or user, got %q" , systemMasterDataType )
208214 }
209215 format := strings .ToLower (strings .TrimSpace (systemMasterDataFormat ))
210216 switch format {
@@ -218,60 +224,51 @@ func runSystemMasterData(cmd *cobra.Command, args []string) error {
218224 return fmt .Errorf ("--title and --no-title are mutually exclusive" )
219225 }
220226
221- p := xpoint.MasterDataParams {MasterType : systemMasterDataType }
222- if cmd .Flags ().Changed ("rows" ) {
223- v := systemMasterDataRows
224- p .Rows = & v
225- }
226- if cmd .Flags ().Changed ("offset" ) {
227- v := systemMasterDataOffset
228- p .Offset = & v
229- }
230- if format == "csv" {
231- p .FileName = systemMasterDataFileName
232- p .Delimiter = systemMasterDataDelimiter
233- p .Fields = systemMasterDataFields
234- if systemMasterDataNoTitle {
235- b := false
236- p .Title = & b
237- } else if cmd .Flags ().Changed ("title" ) {
238- v := systemMasterDataTitle
239- p .Title = & v
240- }
241- }
242-
243227 client , err := newClientFromFlags (cmd .Context ())
244228 if err != nil {
245229 return err
246230 }
247- filename , body , _ , err := client .GetMasterData (cmd .Context (), masterCode , format , p )
248- if err != nil {
249- return err
250- }
251231
252232 if format == "json" {
233+ merged , err := fetchAllMasterDataJSON (cmd .Context (), client , masterCode , masterType )
234+ if err != nil {
235+ return err
236+ }
253237 if systemMasterDataJQ != "" {
254- return runJQ (json .RawMessage (body ), systemMasterDataJQ )
238+ return runJQ (json .RawMessage (merged ), systemMasterDataJQ )
255239 }
256240 switch systemMasterDataOutput {
257241 case "" , "-" :
258- _ , werr := os .Stdout .Write (body )
242+ _ , werr := os .Stdout .Write (merged )
259243 return werr
260244 }
261- dst := resolveDownloadPath (systemMasterDataOutput , fallbackName ( filename , masterCode + ".json" ) , 0 )
262- if err := os .WriteFile (dst , body , 0o600 ); err != nil {
245+ dst := resolveDownloadPath (systemMasterDataOutput , masterCode + ".json" , 0 )
246+ if err := os .WriteFile (dst , merged , 0o600 ); err != nil {
263247 return fmt .Errorf ("write master data: %w" , err )
264248 }
265- fmt .Fprintf (os .Stderr , "saved: %s (%d bytes)\n " , dst , len (body ))
249+ fmt .Fprintf (os .Stderr , "saved: %s (%d bytes)\n " , dst , len (merged ))
266250 return nil
267251 }
268252
269- // CSV
270- if systemMasterDataOutput == "-" {
271- _ , werr := os .Stdout .Write (body )
272- return werr
253+ p := xpoint.MasterDataParams {
254+ MasterType : masterType ,
255+ FileName : systemMasterDataFileName ,
256+ Delimiter : systemMasterDataDelimiter ,
257+ Fields : systemMasterDataFields ,
273258 }
274- if systemMasterDataOutput == "" && ! isTerminal (os .Stdout ) {
259+ if systemMasterDataNoTitle {
260+ b := false
261+ p .Title = & b
262+ } else if cmd .Flags ().Changed ("title" ) {
263+ v := systemMasterDataTitle
264+ p .Title = & v
265+ }
266+ filename , body , err := fetchAllMasterDataCSV (cmd .Context (), client , masterCode , p )
267+ if err != nil {
268+ return err
269+ }
270+
271+ if systemMasterDataOutput == "" || systemMasterDataOutput == "-" {
275272 _ , werr := os .Stdout .Write (body )
276273 return werr
277274 }
@@ -344,6 +341,112 @@ func runSystemMasterUpload(cmd *cobra.Command, args []string) error {
344341 return writeJSON (os .Stdout , res )
345342}
346343
344+ // fetchAllMasterDataJSON fetches master data as JSON with rows=1000, paging
345+ // until total_count (or a short/empty page) is reached. The merged JSON keeps
346+ // the first page's envelope with the concatenated data array.
347+ func fetchAllMasterDataJSON (ctx context.Context , client * xpoint.Client , masterCode string , masterType int ) ([]byte , error ) {
348+ rows := masterDataPageSize
349+ offset := 0
350+ p := xpoint.MasterDataParams {MasterType : masterType , Rows : & rows , Offset : & offset }
351+
352+ var (
353+ firstMaster map [string ]json.RawMessage
354+ allData []json.RawMessage
355+ totalCount int
356+ haveTotal bool
357+ )
358+
359+ for {
360+ p .Offset = & offset
361+ _ , body , _ , err := client .GetMasterData (ctx , masterCode , "json" , p )
362+ if err != nil {
363+ return nil , err
364+ }
365+ var env struct {
366+ Master map [string ]json.RawMessage `json:"master"`
367+ }
368+ if err := json .Unmarshal (body , & env ); err != nil {
369+ return nil , fmt .Errorf ("parse master data: %w" , err )
370+ }
371+
372+ if offset == 0 {
373+ firstMaster = env .Master
374+ if raw , ok := env .Master ["total_count" ]; ok {
375+ if err := json .Unmarshal (raw , & totalCount ); err == nil {
376+ haveTotal = true
377+ }
378+ }
379+ }
380+
381+ var data []json.RawMessage
382+ if raw , ok := env .Master ["data" ]; ok {
383+ if err := json .Unmarshal (raw , & data ); err != nil {
384+ return nil , fmt .Errorf ("parse master.data: %w" , err )
385+ }
386+ }
387+ allData = append (allData , data ... )
388+
389+ if haveTotal && len (allData ) >= totalCount {
390+ break
391+ }
392+ if len (data ) < masterDataPageSize {
393+ break
394+ }
395+ offset += masterDataPageSize
396+ }
397+
398+ if firstMaster == nil {
399+ firstMaster = map [string ]json.RawMessage {}
400+ }
401+ encoded , err := json .Marshal (allData )
402+ if err != nil {
403+ return nil , err
404+ }
405+ firstMaster ["data" ] = encoded
406+ return json .Marshal (struct {
407+ Master map [string ]json.RawMessage `json:"master"`
408+ }{Master : firstMaster })
409+ }
410+
411+ // fetchAllMasterDataCSV fetches master data as CSV with rows=1000, paging
412+ // until the server returns an empty page. The first page honors the caller's
413+ // title setting; subsequent pages force title=false so the server omits the
414+ // header and the bodies can simply be concatenated.
415+ func fetchAllMasterDataCSV (ctx context.Context , client * xpoint.Client , masterCode string , base xpoint.MasterDataParams ) (string , []byte , error ) {
416+ rows := masterDataPageSize
417+ offset := 0
418+ p := base
419+ p .Rows = & rows
420+ p .Offset = & offset
421+
422+ titleFalse := false
423+ var (
424+ firstFilename string
425+ csvBuf bytes.Buffer
426+ )
427+ for {
428+ p .Offset = & offset
429+ if offset > 0 {
430+ p .Title = & titleFalse
431+ }
432+ filename , body , _ , err := client .GetMasterData (ctx , masterCode , "csv" , p )
433+ if err != nil {
434+ return "" , nil , err
435+ }
436+ if offset == 0 {
437+ firstFilename = filename
438+ csvBuf .Write (body )
439+ } else {
440+ if len (bytes .TrimSpace (body )) == 0 {
441+ break
442+ }
443+ csvBuf .Write (body )
444+ }
445+ offset += masterDataPageSize
446+ }
447+ return firstFilename , csvBuf .Bytes (), nil
448+ }
449+
347450// fallbackName returns name when non-empty, else alt.
348451func fallbackName (name , alt string ) string {
349452 if name != "" {
0 commit comments