Skip to content

Commit ffaa10d

Browse files
committed
add full export
1 parent 284cc02 commit ffaa10d

File tree

8 files changed

+188
-45
lines changed

8 files changed

+188
-45
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
/*.exe
1+
/*.exe
2+
/*.sql

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ sqlparser [-format=txt|csv|json|jsonl] [-output=filename] [-workers=N] <sqlfile>
3838
- `jsonl`: JSON lines format with table structure
3939
- `-output`: Output file path (default: stdout)
4040
- `-workers`: Number of worker threads (default: 1)
41+
- `-all`: Export all tables (default: false)
4142
- `<sqlfile>`: Input SQL file containing INSERT statements
4243

4344
### Environment Variables

cmd/sqlparser/main.go

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,66 +12,99 @@ import (
1212
)
1313

1414
func main() {
15-
format := flag.String("format", "txt", "Output format (txt, csv, json, jsonl)")
16-
output := flag.String("output", "", "Output file (if not specified, prints to stdout)")
15+
format := flag.String("format", "", "Output format (txt, csv, json, jsonl)")
16+
output := flag.String("output", "", "Output file (for single table export)")
1717
workers := flag.Int("workers", getWorkerCount(), "Number of worker threads")
18+
exportAll := flag.Bool("all", false, "Export all tables (creates a directory named after the input file)")
1819
flag.Parse()
1920

2021
args := flag.Args()
2122
if len(args) < 1 {
22-
fmt.Printf("Usage: sqlparser [-format=txt|csv|json] [-output=filename] [-workers=N] <sqlfile>\n")
23-
fmt.Printf(" -format: Output format (default: txt)\n")
24-
fmt.Printf(" -output: Output file (default: stdout)\n")
23+
fmt.Printf("Usage: sqlparser [-format=txt|csv|json] [-output=filename] [-workers=N] [-all] <sqlfile>\n")
24+
fmt.Printf(" -format: Output format (txt, csv, json, jsonl). If specified without -output, creates files in a directory\n")
25+
fmt.Printf(" -output: Output file (optional, defaults to directory output if format is specified)\n")
2526
fmt.Printf(" -workers: Number of worker threads (default: %d)\n", getWorkerCount())
27+
fmt.Printf(" -all: Export all tables into separate files (default: false)\n")
2628
os.Exit(1)
2729
}
2830

2931
filename := args[0]
3032

31-
// Scan for tables and prompt for selection
33+
// Scan for tables
3234
tables, err := parser.ScanTables(filename)
3335
if err != nil {
3436
fmt.Printf("Error scanning tables: %v\n", err)
3537
os.Exit(1)
3638
}
3739

38-
selectedTable, err := parser.PromptTableSelection(tables)
39-
if err != nil {
40-
fmt.Printf("Error selecting table: %v\n", err)
41-
os.Exit(1)
40+
var selectedTables []*parser.TableInfo
41+
if *exportAll {
42+
selectedTables = make([]*parser.TableInfo, len(tables))
43+
for i := range tables {
44+
selectedTables[i] = &tables[i]
45+
}
46+
fmt.Printf("\nExporting all %d tables\n", len(tables))
47+
} else {
48+
selectedTable, err := parser.PromptTableSelection(tables)
49+
if err != nil {
50+
// Check if all tables were selected from the menu
51+
if allSelected, ok := err.(*parser.AllTablesSelected); ok {
52+
selectedTables = make([]*parser.TableInfo, len(allSelected.Tables))
53+
for i := range allSelected.Tables {
54+
selectedTables[i] = &allSelected.Tables[i]
55+
}
56+
fmt.Printf("\nExporting all %d tables\n", len(tables))
57+
} else {
58+
fmt.Printf("Error selecting table: %v\n", err)
59+
os.Exit(1)
60+
}
61+
} else {
62+
selectedTables = []*parser.TableInfo{selectedTable}
63+
fmt.Printf("\nSelected table: %s\n", selectedTable.Name)
64+
}
4265
}
4366

44-
fmt.Printf("\nSelected table: %s\n", selectedTable.Name)
45-
67+
// If format is specified but no output, use directory output by default
68+
useDirectoryOutput := *exportAll || len(selectedTables) > 1 || (*format != "" && *output == "")
4669
outputFormat := models.OutputFormat(*format)
70+
if *format == "" {
71+
outputFormat = models.FormatText // default to text if no format specified
72+
}
4773

48-
// Create output file or use stdout
49-
var out *os.File
50-
if *output != "" {
51-
out, err = os.Create(*output)
52-
if err != nil {
53-
fmt.Printf("Error creating output file: %v\n", err)
54-
os.Exit(1)
55-
}
56-
defer out.Close()
74+
var w writer.Writer
75+
if useDirectoryOutput {
76+
w, err = writer.CreateMultiWriter(outputFormat, filename)
5777
} else {
58-
out = os.Stdout
78+
if *output == "" {
79+
w, err = writer.CreateWriter(outputFormat, os.Stdout)
80+
} else {
81+
out, err := os.Create(*output)
82+
if err != nil {
83+
fmt.Printf("Error creating output file: %v\n", err)
84+
os.Exit(1)
85+
}
86+
defer out.Close()
87+
w, err = writer.CreateWriter(outputFormat, out)
88+
if err != nil {
89+
fmt.Printf("Error creating writer: %v\n", err)
90+
os.Exit(1)
91+
}
92+
}
5993
}
6094

61-
// Initialize the output writer based on format
62-
w, err := writer.CreateWriter(outputFormat, out)
6395
if err != nil {
6496
fmt.Printf("Error creating writer: %v\n", err)
6597
os.Exit(1)
6698
}
6799
defer w.Close()
68100

69-
// Process the file
101+
// Process each selected table
70102
fmt.Printf("Processing with %d workers...\n", *workers)
71-
err = parser.ProcessSQLFileInBatches(filename, w, *workers, selectedTable)
72-
if err != nil {
73-
fmt.Printf("Error processing SQL file: %v\n", err)
74-
os.Exit(1)
103+
for _, table := range selectedTables {
104+
if err := parser.ProcessSQLFileInBatches(filename, w, *workers, table); err != nil {
105+
fmt.Printf("Error processing table %s: %v\n", table.Name, err)
106+
os.Exit(1)
107+
}
75108
}
76109
}
77110

pkg/models/types.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ var (
99
BatchSize = getBatchSize()
1010
)
1111

12+
const (
13+
FormatText OutputFormat = "txt"
14+
FormatCSV OutputFormat = "csv"
15+
FormatJSON OutputFormat = "json"
16+
FormatJSONL OutputFormat = "jsonl"
17+
)
18+
1219
func getBatchSize() int {
1320
if val := os.Getenv("BATCH_SIZE"); val != "" {
1421
if size, err := strconv.Atoi(val); err == nil && size > 0 {
@@ -20,12 +27,9 @@ func getBatchSize() int {
2027

2128
type OutputFormat string
2229

23-
const (
24-
FormatText OutputFormat = "txt"
25-
FormatCSV OutputFormat = "csv"
26-
FormatJSON OutputFormat = "json"
27-
FormatJSONL OutputFormat = "jsonl"
28-
)
30+
func (f OutputFormat) Extension() string {
31+
return string(f)
32+
}
2933

3034
type Row struct {
3135
TableName string `json:"table_name,omitempty"`

pkg/parser/database.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,29 @@ func PromptTableSelection(tables []TableInfo) (*TableInfo, error) {
7878
}
7979

8080
fmt.Println("\nFound the following tables with INSERT statements:")
81+
fmt.Printf("0. Export all tables\n")
8182
for i, table := range tables {
8283
fmt.Printf("%d. %s\n", i+1, table.Name)
8384
}
8485

8586
var choice int
86-
fmt.Print("\nEnter the number of the table you want to parse (1-" + fmt.Sprint(len(tables)) + "): ")
87+
fmt.Print("\nEnter the number of the table you want to parse (0-" + fmt.Sprint(len(tables)) + "): ")
8788
_, err := fmt.Scanf("%d", &choice)
88-
if err != nil || choice < 1 || choice > len(tables) {
89+
if err != nil || choice < 0 || choice > len(tables) {
8990
return nil, fmt.Errorf("invalid selection")
9091
}
9192

93+
if choice == 0 {
94+
return nil, &AllTablesSelected{Tables: tables}
95+
}
96+
9297
return &tables[choice-1], nil
9398
}
99+
100+
type AllTablesSelected struct {
101+
Tables []TableInfo
102+
}
103+
104+
func (e *AllTablesSelected) Error() string {
105+
return "all tables selected"
106+
}

pkg/parser/parser.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,6 @@ func ProcessSQLFileInBatches(filename string, writer writer.Writer, numWorkers i
8888

8989
// Process rows immediately
9090
for _, row := range result.rows {
91-
if writer.Type() == models.FormatJSONL {
92-
row.TableName = currentTableName
93-
}
94-
9591
rowCount++
9692
row.RowNumber = rowCount
9793
currentBatch = append(currentBatch, row)
@@ -195,7 +191,15 @@ func processStatement(statement string, numWorkers int) (string, []models.Row, e
195191
statement = strings.TrimSpace(strings.TrimSuffix(statement, ";"))
196192

197193
if strings.HasPrefix(strings.ToUpper(statement), "INSERT INTO") {
198-
return parseInsert(statement, numWorkers)
194+
tableName, rows, err := parseInsert(statement, numWorkers)
195+
if err != nil {
196+
return "", nil, err
197+
}
198+
// Set the table name for each row
199+
for i := range rows {
200+
rows[i].TableName = tableName
201+
}
202+
return tableName, rows, nil
199203
}
200204
return "", nil, nil
201205
}

pkg/parser/rows.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ func parseRowsSequential(tableName string, columns []string, values [][]string)
2929
}
3030
}
3131
rows[i] = models.Row{
32-
Data: rowData,
32+
TableName: tableName,
33+
Data: rowData,
3334
}
3435
}
3536
return tableName, rows, nil
@@ -70,7 +71,8 @@ func parseRowsParallel(tableName string, columns []string, values [][]string, nu
7071
}
7172
}
7273
rows[idx] = models.Row{
73-
Data: rowData,
74+
TableName: tableName,
75+
Data: rowData,
7476
}
7577
}
7678
}(start, end)

pkg/writer/writer.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"bufio"
55
"fmt"
66
"io"
7+
"os"
8+
"path/filepath"
79

810
"sqlparser/pkg/models"
911
)
@@ -18,6 +20,12 @@ type Writer interface {
1820
Type() models.OutputFormat
1921
}
2022

23+
type MultiWriter struct {
24+
format models.OutputFormat
25+
writers map[string]Writer
26+
baseDir string
27+
}
28+
2129
func CreateWriter(format models.OutputFormat, output io.Writer) (Writer, error) {
2230
bufferedWriter := bufio.NewWriterSize(output, bufferSize)
2331

@@ -34,3 +42,80 @@ func CreateWriter(format models.OutputFormat, output io.Writer) (Writer, error)
3442
return nil, fmt.Errorf("unsupported format: %s", format)
3543
}
3644
}
45+
46+
func CreateMultiWriter(format models.OutputFormat, inputPath string) (*MultiWriter, error) {
47+
// Use the input file name (without extension) as the base directory
48+
baseDir := filepath.Base(inputPath)
49+
if filepath.Ext(baseDir) != "" {
50+
baseDir = baseDir[:len(baseDir)-len(filepath.Ext(baseDir))]
51+
}
52+
53+
// Create the directory
54+
if err := os.MkdirAll(baseDir, 0755); err != nil {
55+
return nil, fmt.Errorf("failed to create output directory: %v", err)
56+
}
57+
58+
return &MultiWriter{
59+
format: format,
60+
baseDir: baseDir,
61+
writers: make(map[string]Writer),
62+
}, nil
63+
}
64+
65+
func (mw *MultiWriter) WriteTableStart(tableName string) error {
66+
// Create a new file for this table
67+
filename := filepath.Join(mw.baseDir, tableName+"."+mw.format.Extension())
68+
file, err := os.Create(filename)
69+
if err != nil {
70+
return fmt.Errorf("failed to create file for table %s: %v", tableName, err)
71+
}
72+
73+
// Create a writer for this table
74+
writer, err := CreateWriter(mw.format, file)
75+
if err != nil {
76+
file.Close()
77+
return err
78+
}
79+
80+
mw.writers[tableName] = writer
81+
return writer.WriteTableStart(tableName)
82+
}
83+
84+
func (mw *MultiWriter) WriteRows(rows []models.Row) error {
85+
if len(rows) == 0 {
86+
return nil
87+
}
88+
89+
// Get the writer for this table's rows
90+
tableName := rows[0].TableName
91+
writer, exists := mw.writers[tableName]
92+
if !exists {
93+
return fmt.Errorf("no writer found for table %s", tableName)
94+
}
95+
96+
return writer.WriteRows(rows)
97+
}
98+
99+
func (mw *MultiWriter) WriteTableEnd() error {
100+
var lastErr error
101+
for tableName, writer := range mw.writers {
102+
if err := writer.WriteTableEnd(); err != nil {
103+
lastErr = fmt.Errorf("failed to end table %s: %v", tableName, err)
104+
}
105+
}
106+
return lastErr
107+
}
108+
109+
func (mw *MultiWriter) Close() error {
110+
var lastErr error
111+
for tableName, writer := range mw.writers {
112+
if err := writer.Close(); err != nil {
113+
lastErr = fmt.Errorf("failed to close writer for table %s: %v", tableName, err)
114+
}
115+
}
116+
return lastErr
117+
}
118+
119+
func (mw *MultiWriter) Type() models.OutputFormat {
120+
return mw.format
121+
}

0 commit comments

Comments
 (0)