228 lines
4.2 KiB
Go
228 lines
4.2 KiB
Go
package tsi1
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// SQLIndexExporter writes out all TSI data for an index to a SQL export.
|
|
type SQLIndexExporter struct {
|
|
w io.Writer
|
|
|
|
initialized bool
|
|
|
|
// Logs non-fatal warnings.
|
|
Logger *zap.Logger
|
|
|
|
// Write schema, if true.
|
|
ShowSchema bool
|
|
}
|
|
|
|
// NewSQLIndexExporter returns a new instance of SQLIndexExporter.
|
|
func NewSQLIndexExporter(w io.Writer) *SQLIndexExporter {
|
|
return &SQLIndexExporter{
|
|
w: w,
|
|
|
|
Logger: zap.NewNop(),
|
|
ShowSchema: true,
|
|
}
|
|
}
|
|
|
|
// Close ends the export and writes final output.
|
|
func (e *SQLIndexExporter) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// ExportIndex writes all blocks of the TSM file.
|
|
func (e *SQLIndexExporter) ExportIndex(idx *Index) error {
|
|
if err := e.initialize(); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Fprintln(e.w, `BEGIN TRANSACTION;`)
|
|
|
|
// Iterate over each measurement across all partitions.
|
|
itr, err := idx.MeasurementIterator()
|
|
if err != nil {
|
|
return err
|
|
} else if itr == nil {
|
|
return nil
|
|
}
|
|
defer itr.Close()
|
|
|
|
for {
|
|
name, err := itr.Next()
|
|
if err != nil {
|
|
return err
|
|
} else if name == nil {
|
|
break
|
|
}
|
|
|
|
if err := e.exportMeasurement(idx, name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Fprintln(e.w, "COMMIT;")
|
|
return nil
|
|
}
|
|
|
|
func (e *SQLIndexExporter) exportMeasurement(idx *Index, name []byte) error {
|
|
if err := e.exportMeasurementSeries(idx, name); err != nil {
|
|
return err
|
|
}
|
|
|
|
itr, err := idx.TagKeyIterator(name)
|
|
if err != nil {
|
|
return err
|
|
} else if itr == nil {
|
|
return nil
|
|
}
|
|
defer itr.Close()
|
|
|
|
for {
|
|
key, err := itr.Next()
|
|
if err != nil {
|
|
return err
|
|
} else if key == nil {
|
|
break
|
|
}
|
|
|
|
if err := e.exportTagKey(idx, name, key); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *SQLIndexExporter) exportMeasurementSeries(idx *Index, name []byte) error {
|
|
itr, err := idx.MeasurementSeriesIDIterator(name)
|
|
if err != nil {
|
|
return err
|
|
} else if itr == nil {
|
|
return nil
|
|
}
|
|
defer itr.Close()
|
|
|
|
for {
|
|
elem, err := itr.Next()
|
|
if err != nil {
|
|
return err
|
|
} else if elem.SeriesID.ID == 0 {
|
|
break
|
|
}
|
|
|
|
if _, err := fmt.Fprintf(e.w, "INSERT INTO measurement_series (name, series_id) VALUES ('%x', %d);\n", name, elem.SeriesID.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *SQLIndexExporter) exportTagKey(idx *Index, name, key []byte) error {
|
|
itr, err := idx.TagValueIterator(name, key)
|
|
if err != nil {
|
|
return err
|
|
} else if itr == nil {
|
|
return nil
|
|
}
|
|
defer itr.Close()
|
|
|
|
for {
|
|
value, err := itr.Next()
|
|
if err != nil {
|
|
return err
|
|
} else if value == nil {
|
|
break
|
|
}
|
|
|
|
if err := e.exportTagValue(idx, name, key, value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *SQLIndexExporter) exportTagValue(idx *Index, name, key, value []byte) error {
|
|
itr, err := idx.TagValueSeriesIDIterator(name, key, value)
|
|
if err != nil {
|
|
return err
|
|
} else if itr == nil {
|
|
return nil
|
|
}
|
|
defer itr.Close()
|
|
|
|
for {
|
|
elem, err := itr.Next()
|
|
if err != nil {
|
|
return err
|
|
} else if elem.SeriesID.ID == 0 {
|
|
break
|
|
}
|
|
|
|
// Replace special case keys for measurement & field.
|
|
if bytes.Equal(key, []byte{0}) {
|
|
key = []byte("_measurement")
|
|
} else if bytes.Equal(key, []byte{0xff}) {
|
|
key = []byte("_field")
|
|
}
|
|
|
|
if _, err := fmt.Fprintf(e.w,
|
|
"INSERT INTO tag_value_series (name, key, value, series_id) VALUES ('%x', %s, %s, %d);\n",
|
|
name,
|
|
quoteSQL(string(key)),
|
|
quoteSQL(string(value)),
|
|
elem.SeriesID.ID,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *SQLIndexExporter) initialize() error {
|
|
if e.initialized {
|
|
return nil
|
|
}
|
|
e.initialized = true
|
|
|
|
if !e.ShowSchema {
|
|
return nil
|
|
}
|
|
fmt.Fprintln(e.w, `
|
|
CREATE TABLE IF NOT EXISTS measurement_series (
|
|
name TEXT NOT NULL,
|
|
series_id INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tag_value_series (
|
|
name TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
value TEXT NOT NULL,
|
|
series_id INTEGER NOT NULL
|
|
);
|
|
`[1:])
|
|
|
|
return nil
|
|
}
|
|
|
|
func quoteSQL(s string) string {
|
|
return `'` + sqlReplacer.Replace(toValidUTF8(s)) + `'`
|
|
}
|
|
|
|
var sqlReplacer = strings.NewReplacer(`'`, `''`, "\x00", "")
|
|
|
|
func toValidUTF8(s string) string {
|
|
return strings.Map(func(r rune) rune {
|
|
if r == utf8.RuneError {
|
|
return -1
|
|
}
|
|
return r
|
|
}, s)
|
|
}
|