如何在golang中将扳手行提取为Json或Parquet格式?

时间:2018-04-05 03:31:50

标签: json go parquet google-cloud-spanner

我是golang和spanner的新手,我想每隔5分钟将我们的扳手DB快照保存到Google云存储中。我想要使​​用的格式是Parquet或JSON。

stmt = spanner.NewStatement("SELECT * FROM " + tableName + " WHERE UpdatedAt >= @startDateTime AND UpdatedAt <= @endDateTime")
iter := txn.Query(ctx, stmt)
defer iter.Stop()
for {
    row, err := iter.Next()
    if err == iterator.Done {
        break
    }
    if err != nil {
        log.Println("Failed to read data, err = %s", err)
    }
}

我已经获得了所有行,但我不知道如何提取所有列值并将其写入Parquet或JSON文件或将其上传到GCS。是否可以在不知道值或列名称类型的情况下提取所有列值?任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:2)

需要列类型来检索值。请参阅&#34;支持的类型及其相应的Cloud Spanner列类型&#34;在Row文档中。您可以从Row.ColumnNames获取列名称。将Row.ToStruct与对应于表的结构一起使用可能是有意义的,并将其写入json,例如使用&#34; encoding / json&#34;包&#39; Marshal

答案 1 :(得分:1)

我想分享我繁琐的解决方案,并希望它能帮助将来的某个人。就我而言,我的任务是在短时间内保存扳手DB的快照,并将这些数据以镶木地板格式保存到GCS。以后我们可以使用大查询来查询这些数据。

首先,我用这样一个简单的语句得到了我想要的扳手行:

stmt := spanner.NewStatement(fmt.Sprintf("SELECT * FROM %s WHERE UpdatedAt >= @startDateTime AND UpdatedAt <= @endDateTime", tableName))
    stmt.Params["startDateTime"] = time.Unix(1520330400, 0)
    stmt.Params["endDateTime"] = time.Unix(1520376600, 0)
iter := txn.Query(ctx, stmt)
values := readRows(iter)

func readRows(iter *spanner.RowIterator) []spanner.Row {
    var rows []spanner.Row
    defer iter.Stop()
    for {
        row, err := iter.Next()
        if err == iterator.Done {
            break
        }
        if err != nil {
            log.Println("Failed to read data, err = %s", err)
        }
        rows = append(rows, *row)
    }
    return rows
}

是的,这很容易。但这很令人鼓舞,因为这是我第一次在golang编码。但是,花了一段时间才发现在不知道每列的类型的情况下解码该值是不可能的,但我需要的是每个列的字符串值并将其保存为镶木地板格式。 / p>

所以我写了另一个查询来获取每列的扳手类型:

typeStmt = spanner.NewStatement("SELECT t.column_name, t.spanner_type FROM information_schema.columns AS t WHERE t.table_name = @tableName")
typeStmt.Params["tableName"] = tableName

iterTypes := txn.Query(ctx, typeStmt)
types := readRows(iterTypes)
// use a map to keep all the types
dataTypes := make(map[string]string)
        for i := 0; i < len(types); i++ {
            var columnName string
            var dataType string
            types[i].Column(0, &columnName)
            types[i].Column(1, &dataType)
            dataTypes[columnName] = dataType
        }
formattedRows, md := extractDataByType(dataTypes, values)

我将扳手类型转换为带开关的类型:

    func decodeValueByType(index int, row spanner.Row, value interface{}) {
            err := row.Column(index, value)
            if err != nil {
                log.Println("Failed to extract value, err = %s", err)
            }
        }

    func prepareParquetWriter(md *[]string, parquetType string, columnNames []string, index int) {
        if len(*md) < len(columnNames) {
            *md = append(*md, fmt.Sprintf("name=%s, type=%s", columnNames[index], parquetType))
        }
    }

func extractDataByType(types map[string]string, rows []spanner.Row) ([][]string, []string) {
    var formattedRows [][]string
    var md []string
    for _, row := range rows {
        columnNames := row.ColumnNames()
        var vals []string
        for i := 0; i < row.Size(); i++ {
            switch types[columnNames[i]] {
            case "STRING(MAX)":
                var value spanner.NullString
                decodeValueByType(i, row, &value)
                prepareParquetWriter(&md, "UTF8", columnNames, i)
                vals = append(vals, fmt.Sprintf("%v", value))
            case "TIMESTAMP":
                var value spanner.NullTime
                decodeValueByType(i, row, &value)
                prepareParquetWriter(&md, "TIMESTAMP_MILLIS", columnNames, i)
                vals = append(vals, fmt.Sprintf("%v", value))
            case "INT64":
                var value spanner.NullInt64
                decodeValueByType(i, row, &value)
                prepareParquetWriter(&md, "INT64", columnNames, i)
                vals = append(vals, fmt.Sprintf("%v", value))
            case "BOOL":
                var value spanner.NullBool
                decodeValueByType(i, row, &value)
                prepareParquetWriter(&md, "BOOLEAN", columnNames, i)
                vals = append(vals, fmt.Sprintf("%v", value))
            }
        }
        formattedRows = append(formattedRows, vals)
    }
    log.Println("parquet format: %s", md)
    return formattedRows, md
}

最后,我将数据放在一个二维数组中,并在一个数组中生成我的镶木地板配置。

我还没有为GCS完成拼花作家,但我使用xitongsys / parquet-go在本地写了这样的文件:

fw, err := ParquetFile.NewLocalFileWriter(fmt.Sprintf("dataInParquet/%s_%s.parquet", name, time.Now().Format("20060102150405")))
            if err != nil {
                log.Println("Can't open file", err)
                return
            }
            pw, err := ParquetWriter.NewCSVWriter(md, fw, 4)
            if err != nil {
                log.Println("Can't create csv writer", err)
                return
            }
            for _, row := range formattedRows {
                rec := make([]*string, len(row))
                for i := 0; i < len(row); i++ {
                    rec[i] = &row[i]
                }
                if err = pw.WriteString(rec); err != nil {
                    log.Println("WriteString error", err)
                }
            }
            if err = pw.WriteStop(); err != nil {
                log.Println("WriteStop error", err)
            }
            log.Println("Write Finished")
            fw.Close()

如果有人知道更好的方法,请告诉我。谢谢。 ; - )

顺便说一句,这只是我的实验代码,如果你想使用这些代码,请相应调整。我的生产实现需要支持更多功能,例如使用goroutine查询多个数据库,支持spanner和MySQL,以镶木地板或JSON格式保存数据。如果有人做某事,我想听听更多的想法。