Compare commits

...

7 Commits
v0.1.0 ... main

Author SHA1 Message Date
677f5047a9 Fix dummy trasformer 2024-03-10 12:45:02 +05:00
ffd232ad61 Update screenshot 2024-03-09 22:16:42 +05:00
05ec7868a9 Make UTF-8 default encoding 2024-03-09 21:59:01 +05:00
f9c1b89608 Add CSV+Zstandart export format 2024-03-09 21:54:26 +05:00
1be8583ae6 Remove unused vars 2024-03-05 21:46:13 +05:00
fdb66f92f5 Add export path picker 2024-03-05 21:41:51 +05:00
7c6ca4c81b Add buffered disk writes 2024-03-05 21:41:27 +05:00
11 changed files with 137 additions and 13 deletions

View File

@ -45,9 +45,6 @@ type Server struct {
// Ошибка работы с сервером // Ошибка работы с сервером
err error err error
// Флаг завершения работы
finished bool
config *Config config *Config
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -30,6 +30,7 @@ type DummyTransformer struct{}
func (e *DummyTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { func (e *DummyTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
copy(dst, src) copy(dst, src)
return len(dst), len(src), nil return len(src), len(src), nil
} }
func (e *DummyTransformer) Reset() {} func (e *DummyTransformer) Reset() {}

View File

@ -10,6 +10,7 @@ const (
ExportFormatExcel ExportFormat = "XLSX" ExportFormatExcel ExportFormat = "XLSX"
ExportFormatCsv ExportFormat = "CSV" ExportFormatCsv ExportFormat = "CSV"
ExportFormatCsvZip ExportFormat = "CSV+ZIP" ExportFormatCsvZip ExportFormat = "CSV+ZIP"
ExportFormatCsvZst ExportFormat = "CSV+ZSTD"
) )
// Exporter - интерфейс экспорта // Exporter - интерфейс экспорта
@ -25,6 +26,8 @@ func (e ExportFormat) GetExporter(encoding Encoding) (Exporter, error) {
return &CsvExporter{Encoding: encoding}, nil return &CsvExporter{Encoding: encoding}, nil
case ExportFormatCsvZip: case ExportFormatCsvZip:
return &CsvZipExporter{Encoding: encoding}, nil return &CsvZipExporter{Encoding: encoding}, nil
case ExportFormatCsvZst:
return &CsvZstExporter{Encoding: encoding}, nil
} }
return nil, fmt.Errorf("unknown format: %s", string(e)) return nil, fmt.Errorf("unknown format: %s", string(e))

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bufio"
"encoding/csv" "encoding/csv"
"fmt" "fmt"
"os" "os"
@ -22,12 +23,14 @@ func (c *CsvExporter) Convert(filePath string, rows chan []any) error {
return err return err
} }
buf := bufio.NewWriterSize(f, 4*1024*1024)
enc, err := c.Encoding.Encoder() enc, err := c.Encoding.Encoder()
if err != nil { if err != nil {
return err return err
} }
w := csv.NewWriter(enc.Writer(f)) w := csv.NewWriter(enc.Writer(buf))
w.Comma = ';' w.Comma = ';'
rowNum := 0 rowNum := 0
@ -51,6 +54,12 @@ func (c *CsvExporter) Convert(filePath string, rows chan []any) error {
return err return err
} }
err = buf.Flush()
if err != nil {
f.Close()
return err
}
err = f.Close() err = f.Close()
if err != nil { if err != nil {
return err return err

View File

@ -2,6 +2,7 @@ package main
import ( import (
"archive/zip" "archive/zip"
"bufio"
"encoding/csv" "encoding/csv"
"os" "os"
"path/filepath" "path/filepath"
@ -22,7 +23,9 @@ func (c *CsvZipExporter) Convert(filePath string, rows chan []any) error {
return err return err
} }
z := zip.NewWriter(f) buf := bufio.NewWriterSize(f, 4*1024*1024)
z := zip.NewWriter(buf)
zw, err := z.Create(filepath.Base(filePath) + ".csv") zw, err := z.Create(filepath.Base(filePath) + ".csv")
if err != nil { if err != nil {
@ -65,6 +68,12 @@ func (c *CsvZipExporter) Convert(filePath string, rows chan []any) error {
return err return err
} }
err = buf.Flush()
if err != nil {
f.Close()
return err
}
err = f.Close() err = f.Close()
if err != nil { if err != nil {
return err return err

81
export_csvzst.go Normal file
View File

@ -0,0 +1,81 @@
package main
import (
"bufio"
"encoding/csv"
"os"
"github.com/klauspost/compress/zstd"
)
// Экспорт в CSV, сжатый в ZIP-архив
type CsvZstExporter struct {
Encoding Encoding
}
func (c *CsvZstExporter) FileExt() string {
return ".csv.zst"
}
func (c *CsvZstExporter) Convert(filePath string, rows chan []any) error {
f, err := os.Create(filePath + c.FileExt())
if err != nil {
return err
}
buf := bufio.NewWriterSize(f, 4*1024*1024)
z, err := zstd.NewWriter(buf)
if err != nil {
f.Close()
return err
}
enc, err := c.Encoding.Encoder()
if err != nil {
return err
}
w := csv.NewWriter(enc.Writer(z))
w.Comma = ';'
rowNum := 0
for row := range rows {
rowNum++
rowsStr := make([]string, len(row))
for i := range row {
rowsStr[i] = toCsvField(row[i])
}
err = w.Write(rowsStr)
if err != nil {
f.Close()
return err
}
}
w.Flush()
if err = w.Error(); err != nil {
f.Close()
return err
}
err = z.Close()
if err != nil {
f.Close()
return err
}
err = buf.Flush()
if err != nil {
f.Close()
return err
}
err = f.Close()
if err != nil {
return err
}
return nil
}

1
go.mod
View File

@ -12,6 +12,7 @@ require (
) )
require ( require (
github.com/klauspost/compress v1.17.7 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.3 // indirect github.com/richardlehane/msoleps v1.0.3 // indirect

2
go.sum
View File

@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@ -25,11 +25,11 @@ type Job struct {
scriptFilePath string scriptFilePath string
exportFileFormat ExportFormat exportFileFormat ExportFormat
encoding Encoding encoding Encoding
exportPath string
config Config config Config
status string status string
err error
isFinished bool isFinished bool
exportedRows int exportedRows int
} }
@ -72,7 +72,7 @@ func (j *Job) export(inputRows chan Row) error {
return err return err
} }
fileName := filepath.Base(j.scriptFilePath) fileName := filepath.Join(j.exportPath, filepath.Base(j.scriptFilePath))
fileName = strings.TrimSuffix(fileName, filepath.Ext(fileName)) fileName = strings.TrimSuffix(fileName, filepath.Ext(fileName))
outputRows := make(chan []any) outputRows := make(chan []any)
@ -181,7 +181,7 @@ func (j *Job) iterateServers(sqlStr string, branchFieldNum int) chan Row {
break break
} }
resultRow := make([]any, 0) var resultRow []any
if branchFieldNum != 0 { if branchFieldNum != 0 {
// Подстановка имени сервера // Подстановка имени сервера
resultRow = append(append(container[:branchFieldNum-1], server.Name), container[branchFieldNum:]...) resultRow = append(append(container[:branchFieldNum-1], server.Name), container[branchFieldNum:]...)

View File

@ -33,6 +33,8 @@ type TMainForm struct {
ExportFormatComboBox *vcl.TComboBox ExportFormatComboBox *vcl.TComboBox
CharsetLabel *vcl.TLabel CharsetLabel *vcl.TLabel
CharsetComboBox *vcl.TComboBox CharsetComboBox *vcl.TComboBox
ExportPathLabel *vcl.TLabel
ExportPathPicker *vcl.TDirectoryEdit
BottomPanel *vcl.TPanel BottomPanel *vcl.TPanel
LaunchButton *vcl.TBitBtn LaunchButton *vcl.TBitBtn
@ -157,6 +159,24 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
f.CharsetComboBox.SetTop(700) f.CharsetComboBox.SetTop(700)
f.CharsetComboBox.SetStyle(types.CsDropDownList) f.CharsetComboBox.SetStyle(types.CsDropDownList)
f.ExportPathLabel = vcl.NewLabel(f)
f.ExportPathLabel.SetParent(f.GroupBox)
f.ExportPathLabel.SetCaption("Путь сохранения результата")
f.ExportPathLabel.SetAlign(types.AlTop)
f.ExportPathLabel.SetTop(800)
f.ExportPathLabel.BorderSpacing().SetTop(8)
f.ExportPathLabel.BorderSpacing().SetLeft(8)
f.ExportPathLabel.BorderSpacing().SetRight(8)
f.ExportPathPicker = vcl.NewDirectoryEdit(f)
f.ExportPathPicker.SetParent(f.GroupBox)
f.ExportPathPicker.SetAlign(types.AlTop)
f.ExportPathPicker.BorderSpacing().SetTop(2)
f.ExportPathPicker.BorderSpacing().SetLeft(8)
f.ExportPathPicker.BorderSpacing().SetRight(8)
f.ExportPathPicker.SetTop(900)
f.ExportPathPicker.SetFlat(true)
f.BottomPanel = vcl.NewPanel(f) f.BottomPanel = vcl.NewPanel(f)
f.BottomPanel.SetParent(f.TabSheet1) f.BottomPanel.SetParent(f.TabSheet1)
f.BottomPanel.SetAlign(types.AlBottom) f.BottomPanel.SetAlign(types.AlBottom)
@ -226,13 +246,13 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// File formats ------------------------------------------------------------ // File formats ------------------------------------------------------------
for _, v := range []string{string(ExportFormatExcel), string(ExportFormatCsv), string(ExportFormatCsvZip)} { for _, v := range []string{string(ExportFormatExcel), string(ExportFormatCsv), string(ExportFormatCsvZip), string(ExportFormatCsvZst)} {
f.ExportFormatComboBox.Items().Add(v) f.ExportFormatComboBox.Items().Add(v)
} }
f.ExportFormatComboBox.SetItemIndex(0) f.ExportFormatComboBox.SetItemIndex(0)
// Charsets ---------------------------------------------------------------- // Charsets ----------------------------------------------------------------
for _, v := range []string{string(EncodingWin1251), string(EncodingUtf8)} { for _, v := range []string{string(EncodingUtf8), string(EncodingWin1251)} {
f.CharsetComboBox.Items().Add(v) f.CharsetComboBox.Items().Add(v)
} }
f.CharsetComboBox.SetItemIndex(0) f.CharsetComboBox.SetItemIndex(0)
@ -258,7 +278,8 @@ func (f *TMainForm) OnLaunchButtonClick(sender vcl.IObject) {
configFilePath: filepath.Join(CONFIG_FILES_DIR, f.ConfigComboBox.Text()) + "." + CONFIG_FILE_EXT, configFilePath: filepath.Join(CONFIG_FILES_DIR, f.ConfigComboBox.Text()) + "." + CONFIG_FILE_EXT,
scriptFilePath: filepath.Join(SQL_FILES_DIR, f.SqlFileComboBox.Text()) + "." + SQL_FILE_EXT, scriptFilePath: filepath.Join(SQL_FILES_DIR, f.SqlFileComboBox.Text()) + "." + SQL_FILE_EXT,
exportFileFormat: ExportFormat(f.ExportFormatComboBox.Text()), exportFileFormat: ExportFormat(f.ExportFormatComboBox.Text()),
encoding: Encoding(f.CharsetComboBox.Text())} encoding: Encoding(f.CharsetComboBox.Text()),
exportPath: f.ExportPathPicker.Directory()}
err := job.init() err := job.init()
if err != nil { if err != nil {
@ -301,7 +322,7 @@ func (f *TMainForm) OnLaunchButtonClick(sender vcl.IObject) {
} }
func (f *TMainForm) OnExportFormatComboBoxChange(sender vcl.IObject) { func (f *TMainForm) OnExportFormatComboBoxChange(sender vcl.IObject) {
if slices.Contains([]string{string(ExportFormatCsv), string(ExportFormatCsvZip)}, f.ExportFormatComboBox.Text()) { if slices.Contains([]string{string(ExportFormatCsv), string(ExportFormatCsvZip), string(ExportFormatCsvZst)}, f.ExportFormatComboBox.Text()) {
f.CharsetComboBox.SetEnabled(true) f.CharsetComboBox.SetEnabled(true)
} else { } else {
f.CharsetComboBox.SetEnabled(false) f.CharsetComboBox.SetEnabled(false)