2
0

export.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package csvexport
  2. import (
  3. "bytes"
  4. "encoding/csv"
  5. "encoding/hex"
  6. "fmt"
  7. "io"
  8. "sort"
  9. "github.com/danfragoso/pizzasql-next/pkg/storage"
  10. )
  11. // ExportOptions configures CSV export behavior
  12. type ExportOptions struct {
  13. Table string // Required: specific table to export
  14. IncludeHeader bool // Include column names as first row (default: true)
  15. NullValue string // String representation of NULL (default: "")
  16. Delimiter rune // CSV delimiter (default: ',')
  17. }
  18. // DefaultExportOptions returns sensible defaults
  19. func DefaultExportOptions() ExportOptions {
  20. return ExportOptions{
  21. IncludeHeader: true,
  22. NullValue: "",
  23. Delimiter: ',',
  24. }
  25. }
  26. // ExportTable exports a single table to CSV format
  27. func ExportTable(w io.Writer, schema *storage.SchemaManager, table *storage.TableManager, opts ExportOptions) error {
  28. if opts.Table == "" {
  29. return fmt.Errorf("table name is required for CSV export")
  30. }
  31. // Get table schema
  32. tableSchema, err := schema.GetSchema(opts.Table)
  33. if err != nil {
  34. return fmt.Errorf("failed to get schema for table %s: %w", opts.Table, err)
  35. }
  36. // Get all rows
  37. rows, err := table.Select(opts.Table, nil)
  38. if err != nil {
  39. return fmt.Errorf("failed to select rows from table %s: %w", opts.Table, err)
  40. }
  41. // Create CSV writer
  42. csvWriter := csv.NewWriter(w)
  43. if opts.Delimiter != 0 {
  44. csvWriter.Comma = opts.Delimiter
  45. }
  46. defer csvWriter.Flush()
  47. // Get column names (excluding internal _rowid_)
  48. var columns []string
  49. for _, col := range tableSchema.Columns {
  50. if col.Name != "_rowid_" {
  51. columns = append(columns, col.Name)
  52. }
  53. }
  54. // Write header if requested
  55. if opts.IncludeHeader {
  56. if err := csvWriter.Write(columns); err != nil {
  57. return fmt.Errorf("failed to write CSV header: %w", err)
  58. }
  59. }
  60. // Write data rows
  61. for _, row := range rows {
  62. record := make([]string, len(columns))
  63. for i, colName := range columns {
  64. value := row[colName]
  65. record[i] = formatValue(value, opts.NullValue)
  66. }
  67. if err := csvWriter.Write(record); err != nil {
  68. return fmt.Errorf("failed to write CSV row: %w", err)
  69. }
  70. }
  71. return csvWriter.Error()
  72. }
  73. // ExportTableToBytes exports a single table and returns bytes
  74. func ExportTableToBytes(schema *storage.SchemaManager, table *storage.TableManager, opts ExportOptions) ([]byte, error) {
  75. var buf bytes.Buffer
  76. if err := ExportTable(&buf, schema, table, opts); err != nil {
  77. return nil, err
  78. }
  79. return buf.Bytes(), nil
  80. }
  81. // ExportMultipleTables exports multiple tables as a map of table name to CSV bytes
  82. func ExportMultipleTables(schema *storage.SchemaManager, table *storage.TableManager, tables []string, opts ExportOptions) (map[string][]byte, error) {
  83. // If no tables specified, export all
  84. if len(tables) == 0 {
  85. var err error
  86. tables, err = schema.ListTables()
  87. if err != nil {
  88. return nil, fmt.Errorf("failed to list tables: %w", err)
  89. }
  90. sort.Strings(tables)
  91. }
  92. result := make(map[string][]byte)
  93. for _, tableName := range tables {
  94. tableOpts := opts
  95. tableOpts.Table = tableName
  96. data, err := ExportTableToBytes(schema, table, tableOpts)
  97. if err != nil {
  98. return nil, fmt.Errorf("failed to export table %s: %w", tableName, err)
  99. }
  100. result[tableName] = data
  101. }
  102. return result, nil
  103. }
  104. // formatValue converts a value to its CSV string representation
  105. func formatValue(value interface{}, nullValue string) string {
  106. if value == nil {
  107. return nullValue
  108. }
  109. switch v := value.(type) {
  110. case string:
  111. return v
  112. case float64:
  113. // Check if it's actually an integer
  114. if v == float64(int64(v)) {
  115. return fmt.Sprintf("%d", int64(v))
  116. }
  117. return fmt.Sprintf("%g", v)
  118. case int64:
  119. return fmt.Sprintf("%d", v)
  120. case int:
  121. return fmt.Sprintf("%d", v)
  122. case bool:
  123. if v {
  124. return "1"
  125. }
  126. return "0"
  127. case []byte:
  128. return "0x" + hex.EncodeToString(v)
  129. default:
  130. return fmt.Sprintf("%v", v)
  131. }
  132. }