Bh/row refactor (#532)

row refactor, schema refactor, pipeline refactor
multiple value primary keys, tagged values, pipeline refactor
This commit is contained in:
Brian Hendriks
2019-03-03 22:13:13 -08:00
committed by GitHub
parent ce679de37e
commit 7bb4645632
115 changed files with 3821 additions and 4457 deletions

View File

@@ -1,17 +1,11 @@
package commands
import (
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/dtestutils"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/nbf"
"testing"
)
const (
dataNbfFile = "data.nbf"
table1 = "tbl1"
)
/*
func TestAddResetCommitRmCommands(t *testing.T) {
dEnv := dtestutils.CreateTestEnv()
imt, sch := dtestutils.CreateTestDataTable(true)
@@ -42,3 +36,4 @@ func TestAddResetCommitRmCommands(t *testing.T) {
Reset("dolt reset", []string{table1}, dEnv)
}
*/

View File

@@ -86,7 +86,7 @@ func printConflicts(root *doltdb.RootValue, tblNames []string) errhand.VerboseEr
defer cnfRd.Close()
cnfWr := merge.NewConflictWriter(cli.CliOut, cnfRd.GetSchema(), " | ")
cnfWr := merge.NewConflictSink(cli.CliOut, cnfRd.GetSchema(), " | ")
defer cnfWr.Close()
fwtTr := fwt.NewAutoSizingFWTTransformer(cnfRd.GetSchema(), fwt.HashFillWhenTooLong, 1000)
@@ -94,11 +94,13 @@ func printConflicts(root *doltdb.RootValue, tblNames []string) errhand.VerboseEr
pipeline.NamedTransform{Name: "fwt", Func: fwtTr.TransformToFWT},
)
p, startFunc := pipeline.NewAsyncPipeline(cnfRd, transforms, cnfWr, func(failure *pipeline.TransformRowFailure) (quit bool) {
srcProcFunc := pipeline.ProcFuncForSourceFunc(cnfRd.NextConflict)
sinkProcFunc := pipeline.ProcFuncForSinkFunc(cnfWr.ProcRowWithProps)
p := pipeline.NewAsyncPipeline(srcProcFunc, sinkProcFunc, transforms, func(failure *pipeline.TransformRowFailure) (quit bool) {
panic("")
})
startFunc()
p.Start()
p.Wait()
}()
}

View File

@@ -2,8 +2,6 @@ package cnfcmds
import (
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/cli"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/commands"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/errhand"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env/actions"
@@ -97,7 +95,8 @@ func autoResolve(usage cli.UsagePrinter, apr *argparser.ArgParseResults, dEnv *e
}
func manualResolve(usage cli.UsagePrinter, apr *argparser.ArgParseResults, dEnv *env.DoltEnv) int {
args := apr.Args()
panic("need to implement again :)")
/*args := apr.Args()
if len(args) < 2 {
usage()
@@ -114,7 +113,7 @@ func manualResolve(usage cli.UsagePrinter, apr *argparser.ArgParseResults, dEnv
if !ok {
verr = errhand.BuildDError("fatal: table not found - " + tblName).Build()
} else {
invalid, notFound, updatedTbl, err := tbl.ResolveConflicts(args[1:])
/*invalid, notFound, updatedTbl, err := tbl.ResolveConflicts(args[1:])
if err != nil {
verr = errhand.BuildDError("fatal: Failed to resolve conflicts").AddCause(err).Build()
@@ -136,5 +135,5 @@ func manualResolve(usage cli.UsagePrinter, apr *argparser.ArgParseResults, dEnv
}
}
return commands.HandleVErrAndExitCode(verr, usage)
return commands.HandleVErrAndExitCode(verr, usage)*/
}

View File

@@ -6,11 +6,12 @@ import (
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/cli"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/errhand"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/diff"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env/actions"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/rowconv"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped/fwt"
@@ -139,8 +140,8 @@ func diffRoots(r1, r2 *doltdb.RootValue, tblNames []string, dEnv *env.DoltEnv) e
continue
}
var sch1 *schema.Schema
var sch2 *schema.Schema
var sch1 schema.Schema
var sch2 schema.Schema
rowData1 := types.NewMap(dEnv.DoltDB.ValueReadWriter())
rowData2 := types.NewMap(dEnv.DoltDB.ValueReadWriter())
@@ -164,46 +165,50 @@ func diffRoots(r1, r2 *doltdb.RootValue, tblNames []string, dEnv *env.DoltEnv) e
return nil
}
func diffRows(newRows, oldRows types.Map, newSch, oldSch *schema.Schema) errhand.VerboseError {
unionedSch := untyped.UntypedSchemaUnion(newSch, oldSch)
func diffRows(newRows, oldRows types.Map, newSch, oldSch schema.Schema) errhand.VerboseError {
untypedUnionSch, err := untyped.UntypedSchemaUnion(newSch, oldSch)
newToUnionConv := table.IdentityConverter
if err != nil {
return errhand.BuildDError("Failed to merge schemas").Build()
}
newToUnionConv := rowconv.IdentityConverter
if newSch != nil {
newToUnionMapping, err := schema.NewInferredMapping(newSch, unionedSch)
newToUnionMapping, err := rowconv.NewInferredMapping(newSch, untypedUnionSch)
if err != nil {
return errhand.BuildDError("Error creating unioned mapping").AddCause(err).Build()
}
newToUnionConv, _ = table.NewRowConverter(newToUnionMapping)
newToUnionConv, _ = rowconv.NewRowConverter(newToUnionMapping)
}
oldToUnionConv := table.IdentityConverter
oldToUnionConv := rowconv.IdentityConverter
if oldSch != nil {
oldToUnionMapping, err := schema.NewInferredMapping(oldSch, unionedSch)
oldToUnionMapping, err := rowconv.NewInferredMapping(oldSch, untypedUnionSch)
if err != nil {
return errhand.BuildDError("Error creating unioned mapping").AddCause(err).Build()
}
oldToUnionConv, _ = table.NewRowConverter(oldToUnionMapping)
oldToUnionConv, _ = rowconv.NewRowConverter(oldToUnionMapping)
}
ad := doltdb.NewAsyncDiffer(1024)
ad := diff.NewAsyncDiffer(1024)
ad.Start(newRows, oldRows)
defer ad.Close()
rd := doltdb.NewRowDiffReader(ad, oldToUnionConv, newToUnionConv, unionedSch)
defer rd.Close()
src := diff.NewRowDiffSource(ad, oldToUnionConv, newToUnionConv, untypedUnionSch)
defer src.Close()
fwtTr := fwt.NewAutoSizingFWTTransformer(unionedSch, fwt.HashFillWhenTooLong, 1000)
colorTr := pipeline.NewRowTransformer("coloring transform", doltdb.ColoringTransform)
fwtTr := fwt.NewAutoSizingFWTTransformer(untypedUnionSch, fwt.HashFillWhenTooLong, 1000)
colorTr := pipeline.NewRowTransformer("coloring transform", diff.ColoringTransform)
transforms := pipeline.NewTransformCollection(
pipeline.NamedTransform{"fwt", fwtTr.TransformToFWT},
pipeline.NamedTransform{"color", colorTr})
wr := doltdb.NewColorDiffWriter(cli.CliOut, unionedSch, " | ")
defer wr.Close()
sink := diff.NewColorDiffWriter(cli.CliOut, untypedUnionSch, " | ")
defer sink.Close()
var verr errhand.VerboseError
badRowCB := func(trf *pipeline.TransformRowFailure) (quit bool) {
@@ -211,10 +216,13 @@ func diffRows(newRows, oldRows types.Map, newSch, oldSch *schema.Schema) errhand
return true
}
p, start := pipeline.NewAsyncPipeline(rd, transforms, wr, badRowCB)
srcProcFunc := pipeline.ProcFuncForSourceFunc(src.NextDiff)
sinkProcFunc := pipeline.ProcFuncForSinkFunc(sink.ProcRowWithProps)
p := pipeline.NewAsyncPipeline(srcProcFunc, sinkProcFunc, transforms, badRowCB)
p.InsertRow("fwt", untyped.NewRowFromStrings(unionedSch, unionedSch.GetFieldNames()))
start()
colNames := schema.ExtractAllColNames(untypedUnionSch)
p.InsertRow("fwt", untyped.NewRowFromTaggedStrings(untypedUnionSch, colNames))
p.Start()
p.Wait()
return verr

View File

@@ -1,7 +1 @@
package commands
import (
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/config"
"reflect"
"testing"
)

View File

@@ -8,8 +8,9 @@ import (
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/errhand"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/rowconv"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
@@ -20,6 +21,8 @@ import (
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/iohelp"
)
var cnfTag = schema.ReservedTagMin
var catShortDesc = "print tables"
var catLongDesc = `The dolt table cat command reads tables and writes them to the standard output.`
var catSynopsis = []string{
@@ -92,14 +95,18 @@ func printTable(working *doltdb.RootValue, tblNames []string) errhand.VerboseErr
defer wr.Close()
badRowCB := func(tff *pipeline.TransformRowFailure) (quit bool) {
cli.PrintErrln(color.RedString("Failed to transform row %s.", table.RowFmt(tff.Row)))
cli.PrintErrln(color.RedString("Failed to transform row %s.", row.Fmt(tff.Row, outSch)))
return true
}
p, start := pipeline.NewAsyncPipeline(rd, transforms, wr, badRowCB)
p.InsertRow("fwt", untyped.NewRowFromStrings(outSch, outSch.GetFieldNames()))
rdProcFunc := pipeline.ProcFuncForReader(rd)
wrProcFunc := pipeline.ProcFuncForWriter(wr)
p := pipeline.NewAsyncPipeline(rdProcFunc, wrProcFunc, transforms, badRowCB)
start()
colNames := schema.ExtractAllColNames(outSch)
p.InsertRow("fwt", untyped.NewRowFromTaggedStrings(outSch, colNames))
p.Start()
p.Wait()
}()
}
@@ -107,28 +114,27 @@ func printTable(working *doltdb.RootValue, tblNames []string) errhand.VerboseErr
return verr
}
func addSizingTransform(outSch *schema.Schema, transforms *pipeline.TransformCollection) {
func addSizingTransform(outSch schema.Schema, transforms *pipeline.TransformCollection) {
autoSizeTransform := fwt.NewAutoSizingFWTTransformer(outSch, fwt.PrintAllWhenTooLong, 10000)
transforms.AppendTransforms(pipeline.NamedTransform{"fwt", autoSizeTransform.TransformToFWT})
}
func addMapTransform(sch *schema.Schema, transforms *pipeline.TransformCollection) *schema.Schema {
mapping := untyped.TypedToUntypedMapping(sch)
rConv, _ := table.NewRowConverter(mapping)
transform := pipeline.NewRowTransformer("schema mapping transform", pipeline.GetRowConvTransformFunc(rConv))
func addMapTransform(sch schema.Schema, transforms *pipeline.TransformCollection) schema.Schema {
mapping := rowconv.TypedToUntypedMapping(sch)
rConv, _ := rowconv.NewRowConverter(mapping)
transform := pipeline.NewRowTransformer("schema mapping transform", rowconv.GetRowConvTransformFunc(rConv))
transforms.AppendTransforms(pipeline.NamedTransform{"map", transform})
return mapping.DestSch
}
func maybeAddCnfColTransform(transColl *pipeline.TransformCollection, tbl *doltdb.Table, tblSch *schema.Schema) *schema.Schema {
func maybeAddCnfColTransform(transColl *pipeline.TransformCollection, tbl *doltdb.Table, tblSch schema.Schema) schema.Schema {
if tbl.HasConflicts() {
// this is so much code to add a column
const transCnfSetName = "set cnf col"
confSchema := untyped.NewUntypedSchema([]string{"Cnf"})
schWithConf := typed.TypedSchemaUnion(confSchema, tblSch)
schWithConf.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{tblSch.GetPKIndex() + 1}))
_, confSchema := untyped.NewUntypedSchemaWithFirstTag(cnfTag, "Cnf")
schWithConf, _ := typed.TypedSchemaUnion(confSchema, tblSch)
_, confData, _ := tbl.GetConflicts()
@@ -144,21 +150,21 @@ func maybeAddCnfColTransform(transColl *pipeline.TransformCollection, tbl *doltd
var confLabel = types.String(" ! ")
var noConfLabel = types.String(" ")
func CnfTransformer(inSch, outSch *schema.Schema, conflicts types.Map) func(inRow *table.Row) (rowData []*pipeline.TransformedRowResult, badRowDetails string) {
numCols := inSch.NumFields()
pkIndex := inSch.GetPKIndex()
return func(inRow *table.Row) (rowData []*pipeline.TransformedRowResult, badRowDetails string) {
inData := inRow.CurrData()
fieldVals := make([]types.Value, numCols+1)
func CnfTransformer(inSch, outSch schema.Schema, conflicts types.Map) func(inRow row.Row, props pipeline.ReadableMap) (rowData []*pipeline.TransformedRowResult, badRowDetails string) {
return func(inRow row.Row, props pipeline.ReadableMap) ([]*pipeline.TransformedRowResult, string) {
key := inRow.NomsMapKey(inSch)
pk, _ := inData.GetField(pkIndex)
if conflicts.Has(pk) {
fieldVals[0] = confLabel
var err error
if conflicts.Has(key) {
inRow, err = inRow.SetColVal(cnfTag, confLabel, outSch)
} else {
fieldVals[0] = noConfLabel
inRow, err = inRow.SetColVal(cnfTag, noConfLabel, outSch)
}
inData.CopyValues(fieldVals[1:], 0, numCols)
return []*pipeline.TransformedRowResult{{RowData: table.RowDataFromValues(outSch, fieldVals)}}, ""
if err != nil {
panic(err)
}
return []*pipeline.TransformedRowResult{{inRow, nil}}, ""
}
}

View File

@@ -7,8 +7,7 @@ import (
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/errhand"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/jsonenc"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/argparser"
"io/ioutil"
"os"
@@ -88,17 +87,17 @@ func readSchema(apr *argparser.ArgParseResults, dEnv *env.DoltEnv) (types.Value,
return nil, errhand.BuildDError("Failed to read %s", schemaFile).AddCause(err).Build()
}
sch, err := jsonenc.SchemaFromJSON(data)
sch, err := encoding.UnmarshalJson(string(data))
if err != nil {
return nil, errhand.BuildDError("Invalid json schema at %s", schemaFile).AddCause(err).Build()
} else if sch.NumFields() == 0 {
} else if sch.GetAllCols().Size() == 0 {
return nil, errhand.BuildDError("Invalid schema does not have any valid fields.").Build()
} else if sch.GetPKIndex() == -1 {
} else if sch.GetPKCols().Size() == 0 {
return nil, errhand.BuildDError("Invalid schema does not have a primary key.").Build()
}
schVal, err := noms.MarshalAsNomsValue(dEnv.DoltDB.ValueReadWriter(), sch)
schVal, err := encoding.MarshalAsNomsValue(dEnv.DoltDB.ValueReadWriter(), sch)
if err != nil {
//I dont really understand the cases where this would happen.

View File

@@ -1,11 +1,6 @@
package tblcmds
import (
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/dtestutils"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/mvdata"
"testing"
)
/*
func TestExport(t *testing.T) {
tests := []struct {
args []string
@@ -57,4 +52,4 @@ func TestExport(t *testing.T) {
imt, _ := dtestutils.CreateTestDataTable(test.outputIsTyped)
dtestutils.CheckResultsAgainstReader(t, rd, idIdx, imt, "id")
}
}
}*/

View File

@@ -8,7 +8,7 @@ import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/mvdata"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/argparser"
@@ -194,9 +194,9 @@ func executeMove(dEnv *env.DoltEnv, force bool, mvOpts *mvdata.MoveOptions) int
if pipeline.IsTransformFailure(err) {
bdr := errhand.BuildDError("A bad row was encountered while moving data.")
row := pipeline.GetTransFailureRow(err)
if row != nil {
bdr.AddDetails("Bad Row:" + table.RowFmt(row))
r := pipeline.GetTransFailureRow(err)
if r != nil {
bdr.AddDetails("Bad Row:" + row.Fmt(r, mover.Rd.GetSchema()))
}
details := pipeline.GetTransFailureDetails(err)

View File

@@ -1,22 +1,6 @@
package tblcmds
import (
"fmt"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/dtestutils"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/mvdata"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/jsonenc"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/set"
"reflect"
"strconv"
"strings"
"testing"
)
/*
const (
tableName = "result_table"
@@ -69,7 +53,7 @@ var sd = stateCollection{
var csvData = sd.delimSepVals(',')
var psvData = sd.delimSepVals('|')
var untypedSchema = untyped.NewUntypedSchema(fieldNames)
var untypedSchema = untyped.NewUntypedSchema(fieldNames...)
var untypedRows = make([]*table.Row, len(sd))
func init() {
@@ -171,21 +155,6 @@ func getTests() []createTest {
}
}
func TestImportCommand(t *testing.T) {
/*tests := getTests()
for _, test := range tests {
dEnv := initTestEnv(t, &test)
exitCode := Import("dolt edit create", test.args, dEnv)
if exitCode != test.expectedExitCode {
commandLine := "dolt edit create " + strings.Join(test.args, " ")
t.Error(commandLine, "returned with exit code", exitCode, "expected", test.expectedExitCode)
}
dtestutils.CheckResultTable(t, tableName, dEnv, test.expectedTable, test.pkInExpectedTable)
}*/
}
func initTestEnv(t *testing.T, test *createTest) *env.DoltEnv {
dEnv := dtestutils.CreateTestEnv()
@@ -309,3 +278,20 @@ func optsEqual(opts1, opts2 *mvdata.MoveOptions) bool {
return reflect.DeepEqual(opts1, opts2)
}
func TestImportCommand(t *testing.T) {
// tests := getTests()
// for _, test := range tests {
// dEnv := initTestEnv(t, &test)
//
// exitCode := Import("dolt edit create", test.args, dEnv)
//
// if exitCode != test.expectedExitCode {
// commandLine := "dolt edit create " + strings.Join(test.args, " ")
// t.Error(commandLine, "returned with exit code", exitCode, "expected", test.expectedExitCode)
// }
//
// dtestutils.CheckResultTable(t, tableName, dEnv, test.expectedTable, test.pkInExpectedTable)
// }
}*/

View File

@@ -1,13 +1,16 @@
package tblcmds
import (
"github.com/attic-labs/noms/go/types"
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/cli"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/commands"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/errhand"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/rowconv"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/argparser"
"strings"
)
@@ -102,13 +105,12 @@ func PutRow(commandStr string, args []string, dEnv *env.DoltEnv) int {
return 1
}
vrw := root.VRW()
sch := tbl.GetSchema()
row, verr := createRow(sch, prArgs)
if verr == nil {
me := tbl.GetRowData().Edit()
updated := me.Set(table.GetPKFromRow(row), table.GetNonPKFieldListFromRow(row, vrw)).Map()
updated := me.Set(row.NomsMapKey(sch), row.NomsMapValue(sch)).Map()
tbl = tbl.UpdateRows(updated)
root = root.PutTable(dEnv.DoltDB, prArgs.TableName, tbl)
@@ -124,30 +126,48 @@ func PutRow(commandStr string, args []string, dEnv *env.DoltEnv) int {
return 0
}
func createRow(sch *schema.Schema, prArgs *putRowArgs) (*table.Row, errhand.VerboseError) {
_, _, unknownFields := sch.IntersectFields(prArgs.FieldNames)
func createRow(sch schema.Schema, prArgs *putRowArgs) (row.Row, errhand.VerboseError) {
var unknownFields []string
untypedTaggedVals := make(row.TaggedValues)
for k, v := range prArgs.KVPs {
if col, ok := schema.ColFromName(sch, k); ok {
untypedTaggedVals[col.Tag] = types.String(v)
} else {
unknownFields = append(unknownFields, k)
}
}
if len(unknownFields) > 0 {
bdr := errhand.BuildDError("Not all supplied keys are known in this table's schema.")
bdr.AddDetails("The fields %v were supplied but are not known in this table.", unknownFields)
return nil, bdr.Build()
}
rd, firstBad := table.RowDataFromUntypedMap(sch, prArgs.KVPs)
row := table.NewRow(rd)
if firstBad != nil {
fld := sch.GetField(sch.GetFieldIndex(*firstBad))
val := prArgs.KVPs[*firstBad]
bdr := errhand.BuildDError("Not all parameter values could be converted to the appropriate types for the table.")
bdr.AddDetails(`For parameter "%s", could not convert "%s" to a %s`, *firstBad, val, fld.KindString())
return nil, bdr.Build()
untypedSch := untyped.UntypeSchema(sch)
mapping, err := rowconv.NewInferredMapping(untypedSch, sch)
if err != nil {
return nil, errhand.BuildDError("Failed to infer mapping").AddCause(err).Build()
}
if !table.RowIsValid(row) {
invalidFlds := table.InvalidFieldsForRow(row)
rconv, err := rowconv.NewRowConverter(mapping)
if err != nil {
return nil, errhand.BuildDError("failed to create row converter").AddCause(err).Build()
}
untypedRow := row.New(untypedSch, untypedTaggedVals)
typedRow, err := rconv.Convert(untypedRow)
if err != nil {
return nil, errhand.BuildDError("failed to convert to row").AddCause(err).Build()
}
if col := row.GetInvalidCol(typedRow, sch); col != nil {
bdr := errhand.BuildDError("Missing required fields.")
bdr.AddDetails("The following missing fields are required: %v", invalidFlds)
bdr.AddDetails("The value for the column %s is not valid", col.Name)
return nil, bdr.Build()
}
return row, nil
return typedRow, nil
}

View File

@@ -1,12 +1,6 @@
package tblcmds
import (
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"strings"
"testing"
)
/*
var expectedId = types.UUID(uuid.Must(uuid.Parse("11111111-1111-1111-1111-111111111111")))
var expectedFieldVals = map[string]types.Value{
"id": expectedId,
@@ -69,3 +63,4 @@ func TestPutRow(t *testing.T) {
}
}
}
*/

View File

@@ -1,17 +1,12 @@
package tblcmds
import (
"github.com/attic-labs/noms/go/types"
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/cli"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/commands"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/errhand"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/argparser"
)
import "github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
func RmRow(commandStr string, args []string, dEnv *env.DoltEnv) int {
panic("fix me please")
}
/*
var rmRowShortDesc = "Removes row(s) from a table"
var rmRowLongDesc = "dolt table rm-row will remove one or more rows from a table in the working set."
var rmRowSynopsis = []string{
@@ -160,3 +155,4 @@ func updateTableWithRowsRemoved(root *doltdb.RootValue, tbl *doltdb.Table, tblNa
return verr
}
*/

View File

@@ -1,13 +1,6 @@
package tblcmds
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/dtestutils"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"strings"
"testing"
)
/*
var allIDs = []types.UUID{
types.UUID(dtestutils.UUIDS[0]),
types.UUID(dtestutils.UUIDS[1]),
@@ -67,3 +60,4 @@ func checkExpectedRows(t *testing.T, commandStr string, dEnv *env.DoltEnv, uuids
}
}
}
*/

View File

@@ -1,29 +1,15 @@
package tblcmds
import (
"errors"
"strconv"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/jsonenc"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/cli"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/commands"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/errhand"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped/csv"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped/fwt"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/argparser"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/iohelp"
)
var tblSchemaShortDesc = "Displays table schemas"
@@ -44,14 +30,11 @@ var tblSchemaLongDesc = "dolt table schema displays the schema of tables at a gi
var tblSchemaSynopsis = []string{
"[<commit>] [<table>...]",
"--export <table> <file>",
"--add-field <table> <name> <type> <is_required>[<default_value>]",
"--rename-field <table> <old> <new>]",
"--drop-field <table> <field>",
//"--add-field <table> <name> <type> <is_required>[<default_value>]",
//"--rename-field <table> <old> <new>]",
//"--drop-field <table> <field>",
}
var schColumns = []string{"idx", "name", "type", "nullable", "primary key"}
var schOutSchema = untyped.NewUntypedSchema(schColumns)
var headerRow = untyped.NewRowFromStrings(schOutSchema, schColumns)
var bold = color.New(color.Bold)
func Schema(commandStr string, args []string, dEnv *env.DoltEnv) int {
@@ -59,50 +42,26 @@ func Schema(commandStr string, args []string, dEnv *env.DoltEnv) int {
ap.ArgListHelp["table"] = "table(s) whose schema is being displayed."
ap.ArgListHelp["commit"] = "commit at which point the schema will be displayed."
ap.SupportsFlag("export", "", "exports schema into file.")
ap.SupportsFlag("add-field", "", "add columm to table schema.")
ap.SupportsFlag("rename-field", "", "rename column for specified table.")
ap.SupportsFlag("drop-field", "", "removes column from specified table.")
//ap.SupportsFlag("add-field", "", "add columm to table schema.")
//ap.SupportsFlag("rename-field", "", "rename column for specified table.")
//ap.SupportsFlag("drop-field", "", "removes column from specified table.")
help, usage := cli.HelpAndUsagePrinters(commandStr, tblSchemaShortDesc, tblSchemaLongDesc, tblSchemaSynopsis, ap)
apr := cli.ParseArgs(ap, args, help)
args = apr.Args()
var root *doltdb.RootValue
root, _ = commands.GetWorkingWithVErr(dEnv)
var verr errhand.VerboseError
if apr.Contains("rename-field") {
/*if apr.Contains("rename-field") {
verr = renameColumn(args, root, dEnv)
} else if apr.Contains("add-field") {
verr = addField(args, root, dEnv)
} else if apr.Contains("export") {
verr = exportSchemas(args, root, dEnv)
} else if apr.Contains("drop-field") {
verr = removeColumns(args, root, dEnv)
} else*/if apr.Contains("export") {
verr = exportSchemas(apr.Args(), root, dEnv)
} else {
cmStr := "working"
var cm *doltdb.Commit
if apr.NArg() == 0 {
cm, verr = nil, nil
} else {
cm, verr = commands.MaybeGetCommitWithVErr(dEnv, cmStr)
}
if verr == nil {
var root *doltdb.RootValue
if cm != nil {
cmStr = args[0]
args = args[1:]
root = cm.GetRootValue()
} else {
root, verr = commands.GetWorkingWithVErr(dEnv)
}
if verr == nil {
printSchemas(cmStr, root, args)
}
}
verr = printSchemas(apr, dEnv)
}
return commands.HandleVErrAndExitCode(verr, usage)
@@ -114,62 +73,66 @@ func badRowCB(_ *pipeline.TransformRowFailure) (quit bool) {
const fwtChName = "fwt"
func printSchemas(cmStr string, root *doltdb.RootValue, tables []string) {
if len(tables) == 0 {
tables = root.GetTableNames()
func printSchemas(apr *argparser.ArgParseResults, dEnv *env.DoltEnv) errhand.VerboseError {
cmStr := "working"
args := apr.Args()
tables := args
var root *doltdb.RootValue
var verr errhand.VerboseError
var cm *doltdb.Commit
if apr.NArg() == 0 {
cm, verr = nil, nil
} else {
cm, verr = commands.MaybeGetCommitWithVErr(dEnv, cmStr)
}
var notFound []string
for _, tblName := range tables {
tbl, ok := root.GetTable(tblName)
if !ok {
notFound = append(notFound, tblName)
if verr == nil {
if cm != nil {
cmStr = args[0]
args = args[1:]
root = cm.GetRootValue()
} else {
printTblSchema(cmStr, tblName, tbl, root)
cli.Println()
root, verr = commands.GetWorkingWithVErr(dEnv)
}
}
for _, tblName := range notFound {
cli.PrintErrln(color.YellowString("%s not found", tblName))
if verr == nil {
if len(tables) == 0 {
tables = root.GetTableNames()
}
var notFound []string
for _, tblName := range tables {
tbl, ok := root.GetTable(tblName)
if !ok {
notFound = append(notFound, tblName)
} else {
verr = printTblSchema(cmStr, tblName, tbl, root)
cli.Println()
}
}
for _, tblName := range notFound {
cli.PrintErrln(color.YellowString("%s not found", tblName))
}
}
return verr
}
func printTblSchema(cmStr string, tblName string, tbl *doltdb.Table, root *doltdb.RootValue) {
func printTblSchema(cmStr string, tblName string, tbl *doltdb.Table, root *doltdb.RootValue) errhand.VerboseError {
cli.Println(bold.Sprint(tblName), "@", cmStr)
imt := schemaAsInMemTable(tbl, root)
rd := table.NewInMemTableReader(imt)
defer rd.Close()
wr, _ := csv.NewCSVWriter(iohelp.NopWrCloser(cli.CliOut), schOutSchema, &csv.CSVFileInfo{Delim: '|'})
defer wr.Close()
autoSize := fwt.NewAutoSizingFWTTransformer(schOutSchema, fwt.HashFillWhenTooLong, -1)
transforms := pipeline.NewTransformCollection(
pipeline.NamedTransform{fwtChName, autoSize.TransformToFWT})
p, start := pipeline.NewAsyncPipeline(rd, transforms, wr, badRowCB)
p.InsertRow(fwtChName, headerRow)
start()
_ = p.Wait()
}
func schemaAsInMemTable(tbl *doltdb.Table, root *doltdb.RootValue) *table.InMemTable {
sch := tbl.GetSchema()
imt := table.NewInMemTable(schOutSchema)
for i := 0; i < sch.NumFields(); i++ {
fld := sch.GetField(i)
idxStr := strconv.FormatInt(int64(i), 10)
nullableStr := strconv.FormatBool(!fld.IsRequired())
isPKStr := strconv.FormatBool(sch.GetPKIndex() == i)
strs := []string{idxStr, fld.NameStr(), fld.KindString(), nullableStr, isPKStr}
row := untyped.NewRowFromStrings(schOutSchema, strs)
_ = imt.AppendRow(row)
jsonSchStr, err := encoding.MarshalAsJson(sch)
if err != nil {
return errhand.BuildDError("Failed to encode as json").AddCause(err).Build()
}
return imt
cli.Println(jsonSchStr)
return nil
}
func exportSchemas(args []string, root *doltdb.RootValue, dEnv *env.DoltEnv) errhand.VerboseError {
@@ -195,15 +158,16 @@ func exportSchemas(args []string, root *doltdb.RootValue, dEnv *env.DoltEnv) err
func exportTblSchema(tblName string, tbl *doltdb.Table, filename string, dEnv *env.DoltEnv) errhand.VerboseError {
sch := tbl.GetSchema()
jsonSch, err := jsonenc.SchemaToJSON(sch)
jsonSchStr, err := encoding.MarshalAsJson(sch)
if err != nil {
return errhand.BuildDError("Failed to encode as json").AddCause(err).Build()
}
err = dEnv.FS.WriteFile(filename, jsonSch)
err = dEnv.FS.WriteFile(filename, []byte(jsonSchStr))
return errhand.BuildIf(err, "Unable to write "+filename).AddCause(err).Build()
}
/*
func addField(args []string, root *doltdb.RootValue, dEnv *env.DoltEnv) errhand.VerboseError {
if len(args) < 4 {
return errhand.BuildDError("Must specify table name, field name, field type, and if field required.").SetPrintUsage().Build()
@@ -224,7 +188,7 @@ func addField(args []string, root *doltdb.RootValue, dEnv *env.DoltEnv) errhand.
if len(args) == 5 {
defaultVal = &args[4]
}
for _, name := range origFieldNames {
@@ -244,12 +208,11 @@ func addField(args []string, root *doltdb.RootValue, dEnv *env.DoltEnv) errhand.
return nil
}
func addFieldToSchema(tblName string, tbl *doltdb.Table, dEnv *env.DoltEnv, newColName string, colType string, required string, defaultVal *string) (*doltdb.Table, error) {
if required == "true" && defaultVal == nil {
return nil, errors.New("required column must have default value")
}
tblSch := tbl.GetSchema()
origTblFields := make([]*schema.Field, 0, tblSch.NumFields())
@@ -459,3 +422,4 @@ func removeColumns(args []string, root *doltdb.RootValue, dEnv *env.DoltEnv) err
return nil
}
*/

View File

@@ -62,6 +62,7 @@ func main() {
restoreIO := cli.InitIO()
defer restoreIO()
restoreIO()
dEnv := env.Load(env.GetCurrentUserHomeDir, filesys.LocalFS, doltdb.LocalDirDoltDB)

View File

@@ -3,6 +3,7 @@ package dtestutils
import (
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
@@ -18,21 +19,26 @@ var Ages = []uint64{32, 25, 21}
var Titles = []string{"Senior Dufus", "Dufus", ""}
var MaritalStatus = []bool{true, false, false}
var UntypedSchema = untyped.NewUntypedSchema([]string{"id", "name", "age", "title", "is_married"})
var TypedSchema = schema.NewSchema([]*schema.Field{
schema.NewField("id", types.UUIDKind, true),
schema.NewField("name", types.StringKind, true),
schema.NewField("age", types.UintKind, true),
schema.NewField("title", types.StringKind, false),
schema.NewField("is_married", types.BoolKind, true),
})
const (
IdTag uint64 = iota
NameTag
AgeTag
TitleTag
IsMarriedTag
)
func init() {
TypedSchema.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
UntypedSchema.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
}
var typedColColl, _ = schema.NewColCollection(
schema.NewColumn("id", IdTag, types.UUIDKind, true, schema.NotNullConstraint{}),
schema.NewColumn("name", NameTag, types.StringKind, false, schema.NotNullConstraint{}),
schema.NewColumn("age", AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
schema.NewColumn("title", TitleTag, types.StringKind, false),
schema.NewColumn("is_married", IsMarriedTag, types.BoolKind, false, schema.NotNullConstraint{}),
)
func CreateTestDataTable(typed bool) (*table.InMemTable, *schema.Schema) {
var TypedSchema = schema.SchemaFromCols(typedColColl)
var UntypedSchema = untyped.UntypeSchema(TypedSchema)
func CreateTestDataTable(typed bool) (*table.InMemTable, schema.Schema) {
sch := TypedSchema
if !typed {
sch = UntypedSchema
@@ -41,15 +47,15 @@ func CreateTestDataTable(typed bool) (*table.InMemTable, *schema.Schema) {
imt := table.NewInMemTable(sch)
for i := 0; i < len(UUIDS); i++ {
var valsMap map[string]types.Value
var taggedVals row.TaggedValues
if typed {
valsMap = map[string]types.Value{
"id": types.UUID(UUIDS[i]),
"name": types.String(Names[i]),
"age": types.Uint(Ages[i]),
"title": types.String(Titles[i]),
"is_married": types.Bool(MaritalStatus[i]),
taggedVals = row.TaggedValues{
IdTag: types.UUID(UUIDS[i]),
NameTag: types.String(Names[i]),
AgeTag: types.Uint(Ages[i]),
TitleTag: types.String(Titles[i]),
IsMarriedTag: types.Bool(MaritalStatus[i]),
}
} else {
marriedStr := "true"
@@ -57,15 +63,16 @@ func CreateTestDataTable(typed bool) (*table.InMemTable, *schema.Schema) {
marriedStr = "false"
}
valsMap = map[string]types.Value{
"id": types.String(UUIDS[i].String()),
"name": types.String(Names[i]),
"age": types.String(strconv.FormatUint(Ages[i], 10)),
"title": types.String(Titles[i]),
"is_married": types.String(marriedStr),
taggedVals = row.TaggedValues{
IdTag: types.String(UUIDS[i].String()),
NameTag: types.String(Names[i]),
AgeTag: types.String(strconv.FormatUint(Ages[i], 10)),
TitleTag: types.String(Titles[i]),
IsMarriedTag: types.String(marriedStr),
}
}
imt.AppendRow(table.NewRow(table.RowDataFromValMap(sch, valsMap)))
imt.AppendRow(row.New(sch, taggedVals))
}
return imt, sch

View File

@@ -1,12 +1,6 @@
package dtestutils
import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"testing"
)
/*
func CheckResultTable(t *testing.T, tableName string, dEnv *env.DoltEnv, expectedTable *table.InMemTable, pkInExpectedTable string) {
root, err := dEnv.WorkingRoot()
@@ -27,7 +21,8 @@ func CheckResultTable(t *testing.T, tableName string, dEnv *env.DoltEnv, expecte
CheckResultsAgainstReader(t, tblRdr, tblRdr.GetSchema().GetPKIndex(), expectedTable, pkInExpectedTable)
}
func CheckResultsAgainstReader(t *testing.T, tblRdr table.TableReadCloser, tblPKIdx int, expectedTable *table.InMemTable, pkInExpectedTable string) {
func CheckResultsAgainstReader(t *testing.T, tblRdr table.TableReadCloser, expectedTable *table.InMemTable, pkInExpectedTable string) {
sch := tblRdr.GetSchema()
expectedRdr := table.NewInMemTableReader(expectedTable)
defer expectedRdr.Close()
@@ -39,7 +34,7 @@ func CheckResultsAgainstReader(t *testing.T, tblRdr table.TableReadCloser, tblPK
return
}
expectedRowMap, _, err := table.ReadAllRowsToMap(expectedRdr, expectedPKIdx, false)
expectedRowMap, _, err := table.ReadAllRowsToMap(expectedRdr, false)
if err != nil {
t.Error("Could not read all expected rows to a map.", err)
@@ -67,9 +62,10 @@ func CheckResultsAgainstReader(t *testing.T, tblRdr table.TableReadCloser, tblPK
expectedRow := expectedRows[0]
actualRow := actualRows[0]
if !table.RowsEqualIgnoringSchema(expectedRow, actualRow) {
t.Error(table.RowFmt(expectedRow), "!=", table.RowFmt(actualRow))
if !row.AreEqual(expectedRow, actualRow, sch) {
t.Error(row.Fmt(expectedRow, sch), "!=", row.Fmt(actualRow, sch))
break
}
}
}
*/

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: dolt/services/remotesapi/v1alpha1/chunkstore.proto
package remotesapi // import "dolt/services/remotesapi_v1alpha1"
package remotesapi
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: dolt/services/remotesapi/v1alpha1/credentials.proto
package remotesapi // import "dolt/services/remotesapi_v1alpha1"
package remotesapi
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"

View File

@@ -89,6 +89,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/liquidata-inc/ld v0.0.0-20190205225948-cdcc7482b761 h1:EXBwwGfKGlgDjpU/OUXqQR7Os6k5YiFMnd0BKI7rHSE=
github.com/liquidata-inc/ld v0.0.0-20190206181719-e5e910738a81 h1:b7sZWFyjLQ3t/+Cm745en8QPrQG0B2V0iCzunSVeBUg=
github.com/liquidata-inc/ld v0.0.0-20190207205511-eaad949095a9 h1:ipwCwHqQEY899koM5AcOD+64XNUMW1Bq0Y+DUCBOb9E=
github.com/liquidata-inc/ld v0.0.0-20190226010735-8c524c10943a h1:i+opEE0Sv8rWnK7lmlJ0CtcdaKuSVAP6NHR4GFAgwzI=
github.com/liquidata-inc/ld v0.0.0-20190302001237-012ba4cf492b h1:HY18hVF9nR+bUg3lWQ4ihkVBab3Y5v6Ljf8eVAkmtbY=
github.com/liquidata-inc/noms v0.0.0-20190128170251-00beb75270a9 h1:gJMsKmV60jCpeZQsf33ZyI25pYLvQa3byv2WsO/FzbU=
github.com/liquidata-inc/noms v0.0.0-20190128170251-00beb75270a9/go.mod h1:sv8WH/KMWjMUS3JKxySmQoyMamu9cPd+p5y3lFsXnxo=
github.com/liquidata-inc/noms v0.0.0-20190213171954-6864940d587b h1:dlxBsNL16hrjWe3LdfG+p0PCLR3WmaycvL39+jolhoc=

View File

@@ -1,4 +1,4 @@
package doltdb
package diff
import (
"errors"

View File

@@ -1,8 +1,8 @@
package doltdb
package diff
import (
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
)
@@ -10,29 +10,26 @@ var greenTextProp = map[string]interface{}{ColorRowProp: color.GreenString}
var redTextProp = map[string]interface{}{ColorRowProp: color.RedString}
var yellowTextProp = map[string]interface{}{ColorRowProp: color.YellowString}
func ColoringTransform(row *table.Row) ([]*pipeline.TransformedRowResult, string) {
var props map[string]interface{} = nil
rowData := row.CurrData()
diffType, ok := row.GetProperty(DiffTypeProp)
func ColoringTransform(r row.Row, props pipeline.ReadableMap) ([]*pipeline.TransformedRowResult, string) {
var updatedProps map[string]interface{}
diffType, ok := props.Get(DiffTypeProp)
if ok {
ct, ok := diffType.(DiffChType)
if ok {
switch ct {
case DiffAdded:
props = greenTextProp
updatedProps = greenTextProp
case DiffRemoved:
props = redTextProp
updatedProps = redTextProp
case DiffModifiedOld:
props = yellowTextProp
updatedProps = yellowTextProp
case DiffModifiedNew:
props = yellowTextProp
updatedProps = yellowTextProp
}
}
}
return []*pipeline.TransformedRowResult{{RowData: rowData, Properties: props}}, ""
return []*pipeline.TransformedRowResult{{r, updatedProps}}, ""
}

View File

@@ -1,12 +1,13 @@
package doltdb
package diff
import (
"bufio"
"errors"
"github.com/attic-labs/noms/go/types"
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/iohelp"
"io"
@@ -22,14 +23,14 @@ type ColorFunc func(string, ...interface{}) string
var WriteBufSize = 256 * 1024
type ColorDiffWriter struct {
type ColorDiffSink struct {
closer io.Closer
bWr *bufio.Writer
sch *schema.Schema
sch schema.Schema
colSep string
}
func OpenColorDiffWriter(path string, fs filesys.WritableFS, sch *schema.Schema, colSep string) (table.TableWriteCloser, error) {
func OpenColorDiffSink(path string, fs filesys.WritableFS, sch schema.Schema, colSep string) (*ColorDiffSink, error) {
err := fs.MkDirs(filepath.Dir(path))
if err != nil {
@@ -45,14 +46,14 @@ func OpenColorDiffWriter(path string, fs filesys.WritableFS, sch *schema.Schema,
return NewColorDiffWriter(wr, sch, colSep), nil
}
func NewColorDiffWriter(wr io.Writer, sch *schema.Schema, colSep string) table.TableWriteCloser {
func NewColorDiffWriter(wr io.Writer, sch schema.Schema, colSep string) *ColorDiffSink {
bwr := bufio.NewWriterSize(wr, WriteBufSize)
return &ColorDiffWriter{nil, bwr, sch, colSep}
return &ColorDiffSink{nil, bwr, sch, colSep}
}
// GetSchema gets the schema of the rows that this writer writes
func (tWr *ColorDiffWriter) GetSchema() *schema.Schema {
return tWr.sch
func (cdWr *ColorDiffSink) GetSchema() schema.Schema {
return cdWr.sch
}
var colDiffColors = map[DiffChType]ColorFunc{
@@ -63,26 +64,29 @@ var colDiffColors = map[DiffChType]ColorFunc{
}
// WriteRow will write a row to a table
func (tWr *ColorDiffWriter) WriteRow(row *table.Row) error {
sch := row.GetSchema()
rowData := row.CurrData()
colStrs := make([]string, sch.NumFields())
func (cdWr *ColorDiffSink) ProcRowWithProps(r row.Row, props pipeline.ReadableMap) error {
allCols := cdWr.sch.GetAllCols()
colStrs := make([]string, allCols.Size())
colDiffs := make(map[string]DiffChType)
if prop, ok := row.GetProperty(CollChangesProp); ok {
if prop, ok := props.Get(CollChangesProp); ok {
if convertedVal, convertedOK := prop.(map[string]DiffChType); convertedOK {
colDiffs = convertedVal
}
}
for i := 0; i < sch.NumFields(); i++ {
val, _ := rowData.GetField(i)
i := 0
allCols.ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
val, _ := r.GetColVal(tag)
str := string(val.(types.String))
colStrs[i] = str
}
i++
return false
})
prefix := " "
colorColumns := false
if prop, ok := row.GetProperty(DiffTypeProp); ok {
if prop, ok := props.Get(DiffTypeProp); ok {
if dt, convertedOK := prop.(DiffChType); convertedOK {
switch dt {
case DiffAdded:
@@ -99,21 +103,23 @@ func (tWr *ColorDiffWriter) WriteRow(row *table.Row) error {
}
if colorColumns {
for i := 0; i < sch.NumFields(); i++ {
fld := sch.GetField(i)
fldName := fld.NameStr()
if dt, ok := colDiffs[fldName]; ok {
i = 0
allCols.ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
if dt, ok := colDiffs[col.Name]; ok {
if colorFunc, ok := colDiffColors[dt]; ok {
colStrs[i] = colorFunc(colStrs[i])
}
}
}
i++
return false
})
}
lineStr := prefix + strings.Join(colStrs, tWr.colSep) + "\n"
lineStr := prefix + strings.Join(colStrs, cdWr.colSep) + "\n"
if !colorColumns {
if prop, ok := row.GetProperty(ColorRowProp); ok {
if prop, ok := props.Get(ColorRowProp); ok {
colorer, convertedOK := prop.(func(string, ...interface{}) string)
if convertedOK {
lineStr = colorer(lineStr)
@@ -121,20 +127,20 @@ func (tWr *ColorDiffWriter) WriteRow(row *table.Row) error {
}
}
err := iohelp.WriteAll(tWr.bWr, []byte(lineStr))
err := iohelp.WriteAll(cdWr.bWr, []byte(lineStr))
return err
}
// Close should release resources being held
func (tWr *ColorDiffWriter) Close() error {
if tWr.bWr != nil {
errFl := tWr.bWr.Flush()
tWr.bWr = nil
func (cdWr *ColorDiffSink) Close() error {
if cdWr.bWr != nil {
errFl := cdWr.bWr.Flush()
cdWr.bWr = nil
if tWr.closer != nil {
errCl := tWr.closer.Close()
tWr.closer = nil
if cdWr.closer != nil {
errCl := cdWr.closer.Close()
cdWr.closer = nil
if errCl != nil {
return errCl

View File

@@ -0,0 +1,158 @@
package diff
import (
"errors"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/rowconv"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/valutil"
"io"
"time"
)
const (
DiffTypeProp = "difftype"
CollChangesProp = "collchanges"
)
type DiffChType int
const (
DiffAdded DiffChType = iota
DiffRemoved
DiffModifiedOld
DiffModifiedNew
)
type DiffTyped interface {
DiffType() DiffChType
}
type DiffRow struct {
row.Row
diffType DiffChType
}
func (dr *DiffRow) DiffType() DiffChType {
return dr.diffType
}
type RowDiffSource struct {
oldConv *rowconv.RowConverter
newConv *rowconv.RowConverter
ad *AsyncDiffer
outSch schema.Schema
bufferedRows []pipeline.RowWithProps
}
func NewRowDiffSource(ad *AsyncDiffer, oldConv, newConv *rowconv.RowConverter, outSch schema.Schema) *RowDiffSource {
return &RowDiffSource{
oldConv,
newConv,
ad,
outSch,
make([]pipeline.RowWithProps, 0, 1024),
}
}
// GetSchema gets the schema of the rows that this reader will return
func (rdRd *RowDiffSource) GetSchema() schema.Schema {
return rdRd.outSch
}
// NextDiff reads a row from a table. If there is a bad row the returned error will be non nil, and callin IsBadRow(err)
// will be return true. This is a potentially non-fatal error and callers can decide if they want to continue on a bad row, or fail.
func (rdRd *RowDiffSource) NextDiff() (row.Row, pipeline.ImmutableProperties, error) {
if len(rdRd.bufferedRows) != 0 {
rowWithProps := rdRd.nextFromBuffer()
return rowWithProps.Row, rowWithProps.Props, nil
}
if rdRd.ad.isDone {
return nil, pipeline.NoProps, io.EOF
}
diffs := rdRd.ad.GetDiffs(1, time.Second)
if len(diffs) == 0 {
if rdRd.ad.isDone {
return nil, pipeline.NoProps, io.EOF
}
return nil, pipeline.NoProps, errors.New("timeout")
}
outCols := rdRd.outSch.GetAllCols()
for _, d := range diffs {
var mappedOld row.Row
var mappedNew row.Row
if d.OldValue != nil {
oldRow := row.FromNoms(rdRd.oldConv.SrcSch, d.KeyValue.(types.Tuple), d.OldValue.(types.Tuple))
mappedOld, _ = rdRd.oldConv.Convert(oldRow)
}
if d.NewValue != nil {
newRow := row.FromNoms(rdRd.newConv.SrcSch, d.KeyValue.(types.Tuple), d.NewValue.(types.Tuple))
mappedNew, _ = rdRd.newConv.Convert(newRow)
}
var oldProps = map[string]interface{}{DiffTypeProp: DiffRemoved}
var newProps = map[string]interface{}{DiffTypeProp: DiffAdded}
if d.OldValue != nil && d.NewValue != nil {
oldColDiffs := make(map[string]DiffChType)
newColDiffs := make(map[string]DiffChType)
outCols.ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
oldVal, _ := mappedOld.GetColVal(tag)
newVal, _ := mappedNew.GetColVal(tag)
_, inOld := rdRd.oldConv.SrcSch.GetAllCols().GetByTag(tag)
_, inNew := rdRd.newConv.SrcSch.GetAllCols().GetByTag(tag)
if inOld && inNew {
if !valutil.NilSafeEqCheck(oldVal, newVal) {
newColDiffs[col.Name] = DiffModifiedNew
oldColDiffs[col.Name] = DiffModifiedOld
}
} else if inOld {
oldColDiffs[col.Name] = DiffRemoved
} else {
newColDiffs[col.Name] = DiffAdded
}
return false
})
oldProps = map[string]interface{}{DiffTypeProp: DiffModifiedOld, CollChangesProp: oldColDiffs}
newProps = map[string]interface{}{DiffTypeProp: DiffModifiedNew, CollChangesProp: newColDiffs}
}
if d.OldValue != nil {
rwp := pipeline.NewRowWithProps(mappedOld, oldProps)
rdRd.bufferedRows = append(rdRd.bufferedRows, rwp)
}
if d.NewValue != nil {
rwp := pipeline.NewRowWithProps(mappedNew, newProps)
rdRd.bufferedRows = append(rdRd.bufferedRows, rwp)
}
}
rwp := rdRd.nextFromBuffer()
return rwp.Row, rwp.Props, nil
}
func (rdRd *RowDiffSource) nextFromBuffer() pipeline.RowWithProps {
r := rdRd.bufferedRows[0]
rdRd.bufferedRows = rdRd.bufferedRows[1:]
return r
}
// Close should release resources being held
func (rdRd *RowDiffSource) Close() error {
rdRd.ad.Close()
return nil
}

View File

@@ -1,149 +0,0 @@
package doltdb
import (
"errors"
"io"
"time"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
)
const (
DiffTypeProp = "difftype"
CollChangesProp = "collchanges"
)
type DiffChType int
const (
DiffAdded DiffChType = iota
DiffRemoved
DiffModifiedOld
DiffModifiedNew
)
type DiffTyped interface {
DiffType() DiffChType
}
type DiffRow struct {
*table.Row
diffType DiffChType
}
func (dr *DiffRow) DiffType() DiffChType {
return dr.diffType
}
type RowDiffReader struct {
oldConv *table.RowConverter
newConv *table.RowConverter
ad *AsyncDiffer
outSch *schema.Schema
bufferedRows []*table.Row
}
func NewRowDiffReader(ad *AsyncDiffer, oldConv, newConv *table.RowConverter, outSch *schema.Schema) *RowDiffReader {
return &RowDiffReader{
oldConv,
newConv,
ad,
outSch,
make([]*table.Row, 0, 1024),
}
}
// GetSchema gets the schema of the rows that this reader will return
func (rdRd *RowDiffReader) GetSchema() *schema.Schema {
return rdRd.outSch
}
// ReadRow reads a row from a table. If there is a bad row the returned error will be non nil, and callin IsBadRow(err)
// will be return true. This is a potentially non-fatal error and callers can decide if they want to continue on a bad row, or fail.
func (rdRd *RowDiffReader) ReadRow() (*table.Row, error) {
if len(rdRd.bufferedRows) != 0 {
return rdRd.nextFromBuffer(), nil
}
if rdRd.ad.isDone {
return nil, io.EOF
}
diffs := rdRd.ad.GetDiffs(1, time.Second)
if len(diffs) == 0 {
if rdRd.ad.isDone {
return nil, io.EOF
}
return nil, errors.New("timeout")
}
for _, d := range diffs {
var mappedOld *table.RowData
var mappedNew *table.RowData
if d.OldValue != nil {
oldRow := table.NewRow(table.RowDataFromPKAndValueList(rdRd.oldConv.SrcSch, d.KeyValue, d.OldValue.(types.Tuple)))
mappedOld, _ = rdRd.oldConv.Convert(oldRow)
}
if d.NewValue != nil {
newRow := table.NewRow(table.RowDataFromPKAndValueList(rdRd.newConv.SrcSch, d.KeyValue, d.NewValue.(types.Tuple)))
mappedNew, _ = rdRd.newConv.Convert(newRow)
}
var oldProps = map[string]interface{}{DiffTypeProp: DiffRemoved}
var newProps = map[string]interface{}{DiffTypeProp: DiffAdded}
if d.OldValue != nil && d.NewValue != nil {
oldColDiffs := make(map[string]DiffChType)
newColDiffs := make(map[string]DiffChType)
for i := 0; i < rdRd.outSch.NumFields(); i++ {
oldVal, fld := mappedOld.GetField(i)
newVal, _ := mappedNew.GetField(i)
fldName := fld.NameStr()
inOld := rdRd.oldConv.SrcSch.GetFieldIndex(fldName) != -1
inNew := rdRd.newConv.SrcSch.GetFieldIndex(fldName) != -1
if inOld && inNew {
if (oldVal != nil && !oldVal.Equals(newVal)) || (oldVal == nil && newVal != nil) {
newColDiffs[fldName] = DiffModifiedNew
oldColDiffs[fldName] = DiffModifiedOld
}
} else if inOld {
oldColDiffs[fldName] = DiffRemoved
} else {
newColDiffs[fldName] = DiffAdded
}
}
oldProps = map[string]interface{}{DiffTypeProp: DiffModifiedOld, CollChangesProp: oldColDiffs}
newProps = map[string]interface{}{DiffTypeProp: DiffModifiedNew, CollChangesProp: newColDiffs}
}
if d.OldValue != nil {
rdRd.bufferedRows = append(rdRd.bufferedRows, table.NewRowWithProperties(mappedOld, oldProps))
}
if d.NewValue != nil {
rdRd.bufferedRows = append(rdRd.bufferedRows, table.NewRowWithProperties(mappedNew, newProps))
}
}
return rdRd.nextFromBuffer(), nil
}
func (rdRd *RowDiffReader) nextFromBuffer() *table.Row {
r := rdRd.bufferedRows[0]
rdRd.bufferedRows = rdRd.bufferedRows[1:]
return r
}
// Close should release resources being held
func (rdRd *RowDiffReader) Close() error {
rdRd.ad.Close()
return nil
}

View File

@@ -10,17 +10,25 @@ import (
"testing"
)
func createTestSchema() *schema.Schema {
fields := []*schema.Field{
schema.NewField("id", types.UUIDKind, true),
schema.NewField("first", types.StringKind, true),
schema.NewField("last", types.StringKind, true),
schema.NewField("is_married", types.BoolKind, false),
schema.NewField("age", types.UintKind, false),
schema.NewField("empty", types.IntKind, false),
}
sch := schema.NewSchema(fields)
sch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
const (
idTag = 0
firstTag = 1
lastTag = 2
isMarriedTag = 3
ageTag = 4
emptyTag = 5
)
func createTestSchema() schema.Schema {
colColl, _ := schema.NewColCollection(
schema.NewColumn("id", idTag, types.UUIDKind, true, schema.NotNullConstraint{}),
schema.NewColumn("first", firstTag, types.StringKind, false, schema.NotNullConstraint{}),
schema.NewColumn("last", lastTag, types.StringKind, false, schema.NotNullConstraint{}),
schema.NewColumn("is_married", isMarriedTag, types.BoolKind, false),
schema.NewColumn("age", ageTag, types.UintKind, false),
schema.NewColumn("empty", emptyTag, types.IntKind, false),
)
sch := schema.SchemaFromCols(colColl)
return sch
}

View File

@@ -0,0 +1,69 @@
package doltdb
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
)
// PKItr defines a function that iterates over a collection of noms values. The PKItr will return a valid value
// and true until all the values in the collection are exhausted. At that time nil and false will be returned.
type PKItr func() (val types.Tuple, ok bool)
func SingleColPKItr(pkTag uint64, vals []types.Value) func() (types.Tuple, bool) {
next := 0
size := len(vals)
return func() (types.Tuple, bool) {
current := next
next++
if current < size {
tpl := types.NewTuple(types.Uint(pkTag), vals[current])
return tpl, true
}
return types.EmptyTuple, false
}
}
func TaggedValueSliceItr(sch schema.Schema, vals []row.TaggedValues) func() (types.Tuple, bool) {
pkTags := sch.GetPKCols().Tags
next := 0
size := len(vals)
return func() (types.Tuple, bool) {
current := next
next++
if current < size {
tpl := vals[current].NomsTupleForTags(pkTags, true)
return tpl, true
}
return types.EmptyTuple, false
}
}
// TupleSliceItr returns a closure that has the signature of a PKItr and can be used to iterate over a slice of values
func TupleSliceItr(vals []types.Tuple) func() (types.Tuple, bool) {
next := 0
size := len(vals)
return func() (types.Tuple, bool) {
current := next
next++
if current < size {
return vals[current], true
}
return types.EmptyTuple, false
}
}
// SetItr returns a closure that has the signature of a PKItr and can be used to iterate over a noms Set of vaules
func SetItr(valSet types.Set) func() (types.Tuple, bool) {
itr := valSet.Iterator()
return func() (types.Tuple, bool) {
v := itr.Next()
return v.(types.Tuple), v != nil
}
}

View File

@@ -4,9 +4,9 @@ import (
"github.com/attic-labs/noms/go/hash"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/pantoerr"
"regexp"
)
@@ -107,7 +107,7 @@ func (t *Table) ClearConflicts() *Table {
return &Table{t.vrw, tSt}
}
func (t *Table) GetConflictSchemas() (base, sch, mergeSch *schema.Schema, err error) {
func (t *Table) GetConflictSchemas() (base, sch, mergeSch schema.Schema, err error) {
schemasVal, ok := t.tableStruct.MaybeGet(conflictSchemasKey)
if ok {
@@ -117,7 +117,7 @@ func (t *Table) GetConflictSchemas() (base, sch, mergeSch *schema.Schema, err er
mergeRef := schemas.MergeValue.(types.Ref)
var err error
var baseSch, sch, mergeSch *schema.Schema
var baseSch, sch, mergeSch schema.Schema
if baseSch, err = refToSchema(t.vrw, baseRef); err == nil {
if sch, err = refToSchema(t.vrw, valRef); err == nil {
mergeSch, err = refToSchema(t.vrw, mergeRef)
@@ -130,13 +130,13 @@ func (t *Table) GetConflictSchemas() (base, sch, mergeSch *schema.Schema, err er
}
}
func refToSchema(vrw types.ValueReadWriter, ref types.Ref) (*schema.Schema, error) {
var schema *schema.Schema
func refToSchema(vrw types.ValueReadWriter, ref types.Ref) (schema.Schema, error) {
var schema schema.Schema
err := pantoerr.PanicToErrorInstance(ErrNomsIO, func() error {
schemaVal := ref.TargetValue(vrw)
var err error
schema, err = noms.UnmarshalNomsValue(schemaVal)
schema, err = encoding.UnmarshalNomsValue(schemaVal)
if err != nil {
return err
@@ -149,7 +149,7 @@ func refToSchema(vrw types.ValueReadWriter, ref types.Ref) (*schema.Schema, erro
}
// GetSchema will retrieve the schema being referenced from the table in noms and unmarshal it.
func (t *Table) GetSchema() *schema.Schema {
func (t *Table) GetSchema() schema.Schema {
schemaRefVal := t.tableStruct.Get(schemaRefKey)
schemaRef := schemaRefVal.(types.Ref)
schema, _ := refToSchema(t.vrw, schemaRef)
@@ -177,9 +177,14 @@ func (t *Table) HashOf() hash.Hash {
return t.tableStruct.Hash()
}
func (t *Table) GetRowByPKVals(pkVals row.TaggedValues, sch schema.Schema) (row.Row, bool) {
pkTuple := pkVals.NomsTupleForTags(sch.GetPKCols().Tags, true)
return t.GetRow(pkTuple, sch)
}
// GetRow uses the noms Map containing the row data to lookup a row by primary key. If a valid row exists with this pk
// then the supplied TableRowFactory will be used to create a TableRow using the row data.
func (t *Table) GetRow(pk types.Value, sch *schema.Schema) (row *table.Row, exists bool) {
func (t *Table) GetRow(pk types.Tuple, sch schema.Schema) (row.Row, bool) {
rowMap := t.GetRowData()
fieldsVal := rowMap.Get(pk)
@@ -187,49 +192,20 @@ func (t *Table) GetRow(pk types.Value, sch *schema.Schema) (row *table.Row, exis
return nil, false
}
return table.NewRow(table.RowDataFromPKAndValueList(sch, pk, fieldsVal.(types.Tuple))), true
return row.FromNoms(sch, pk, fieldsVal.(types.Tuple)), true
}
// ValueItr defines a function that iterates over a collection of noms values. The ValueItr will return a valid value
// and true until all the values in the collection are exhausted. At that time nil and false will be returned.
type ValueItr func() (val types.Value, ok bool)
// ValueSliceItr returns a closure that has the signature of a ValueItr and can be used to iterate over a slice of values
func ValueSliceItr(vals []types.Value) func() (types.Value, bool) {
next := 0
size := len(vals)
return func() (types.Value, bool) {
current := next
next++
if current < size {
return vals[current], true
}
return nil, false
}
}
// SetItr returns a closure that has the signature of a ValueItr and can be used to iterate over a noms Set of vaules
func SetItr(valSet types.Set) func() (types.Value, bool) {
itr := valSet.Iterator()
return func() (types.Value, bool) {
v := itr.Next()
return v, v != nil
}
}
// GetRows takes in a ValueItr which will supply a stream of primary keys to be pulled from the table. Each key is
// GetRows takes in a PKItr which will supply a stream of primary keys to be pulled from the table. Each key is
// looked up sequentially. If row data exists for a given pk it is converted to a TableRow, and added to the rows
// slice. If row data does not exist for a given pk it will be added to the missing slice. The numPKs argument, if
// known helps allocate the right amount of memory for the results, but if the number of pks being requested isn't
// known then 0 can be used.
func (t *Table) GetRows(pkItr ValueItr, numPKs int, sch *schema.Schema) (rows []*table.Row, missing []types.Value) {
func (t *Table) GetRows(pkItr PKItr, numPKs int, sch schema.Schema) (rows []row.Row, missing []types.Value) {
if numPKs < 0 {
numPKs = 0
}
rows = make([]*table.Row, 0, numPKs)
rows = make([]row.Row, 0, numPKs)
missing = make([]types.Value, 0, numPKs)
rowMap := t.GetRowData()
@@ -240,8 +216,8 @@ func (t *Table) GetRows(pkItr ValueItr, numPKs int, sch *schema.Schema) (rows []
if fieldsVal == nil {
missing = append(missing, pk)
} else {
row := table.NewRow(table.RowDataFromPKAndValueList(sch, pk, fieldsVal.(types.Tuple)))
rows = append(rows, row)
r := row.FromNoms(sch, pk, fieldsVal.(types.Tuple))
rows = append(rows, r)
}
}
@@ -264,10 +240,15 @@ func (t *Table) GetRowData() types.Map {
return rowMap
}
func (t *Table) ResolveConflicts(keys []string) (invalid, notFound []string, tbl *Table, err error) {
func (t *Table) ResolveConflicts(keys []map[uint64]string) (invalid, notFound []map[uint64]string, tbl *Table, err error) {
sch := t.GetSchema()
pk := sch.GetField(sch.GetPKIndex())
convFunc := doltcore.GetConvFunc(types.StringKind, pk.NomsKind())
pkCols := sch.GetPKCols()
convFuncs := make(map[uint64]doltcore.ConvFunc)
pkCols.ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
convFuncs[tag] = doltcore.GetConvFunc(types.StringKind, col.Kind)
return false
})
removed := 0
_, confData, err := t.GetConflicts()
@@ -278,19 +259,35 @@ func (t *Table) ResolveConflicts(keys []string) (invalid, notFound []string, tbl
confEdit := confData.Edit()
for _, keyStr := range keys {
key, err := convFunc(types.String(keyStr))
for _, keyStrs := range keys {
i := 0
pk := make([]types.Value, pkCols.Size()*2)
pkCols.ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
strForTag, ok := keyStrs[tag]
pk[i] = types.Uint(tag)
if err != nil {
invalid = append(invalid, keyStr)
}
if ok {
convFunc, _ := convFuncs[tag]
pk[i+1], err = convFunc(types.String(strForTag))
if confEdit.Has(key) {
removed++
confEdit.Remove(key)
} else {
notFound = append(notFound, keyStr)
}
if err != nil {
invalid = append(invalid, keyStrs)
}
} else {
pk[i+1] = types.NullValue
}
pkTupleVal := types.NewTuple(pk...)
if confEdit.Has(pkTupleVal) {
removed++
confEdit.Remove(pkTupleVal)
} else {
notFound = append(notFound, keyStrs)
}
i += 2
return false
})
}
if removed == 0 {

View File

@@ -4,9 +4,9 @@ import (
"github.com/attic-labs/noms/go/spec"
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"testing"
)
@@ -15,27 +15,27 @@ var id1, _ = uuid.NewRandom()
var id2, _ = uuid.NewRandom()
var id3, _ = uuid.NewRandom()
func createTestRowData(vrw types.ValueReadWriter, sch *schema.Schema) (types.Map, []*table.Row) {
rows := make([]*table.Row, 4)
rows[0] = table.NewRow(table.RowDataFromValMap(sch, map[string]types.Value{
"id": types.UUID(id0), "first": types.String("bill"), "last": types.String("billerson"), "age": types.Uint(53)}))
rows[1] = table.NewRow(table.RowDataFromValMap(sch, map[string]types.Value{
"id": types.UUID(id1), "first": types.String("eric"), "last": types.String("ericson"), "is_married": types.Bool(true), "age": types.Uint(21)}))
rows[2] = table.NewRow(table.RowDataFromValMap(sch, map[string]types.Value{
"id": types.UUID(id2), "first": types.String("john"), "last": types.String("johnson"), "is_married": types.Bool(false), "age": types.Uint(53)}))
rows[3] = table.NewRow(table.RowDataFromValMap(sch, map[string]types.Value{
"id": types.UUID(id3), "first": types.String("robert"), "last": types.String("robertson"), "age": types.Uint(36)}))
func createTestRowData(vrw types.ValueReadWriter, sch schema.Schema) (types.Map, []row.Row) {
rows := make([]row.Row, 4)
rows[0] = row.New(sch, row.TaggedValues{
idTag: types.UUID(id0), firstTag: types.String("bill"), lastTag: types.String("billerson"), ageTag: types.Uint(53)})
rows[1] = row.New(sch, row.TaggedValues{
idTag: types.UUID(id1), firstTag: types.String("eric"), lastTag: types.String("ericson"), isMarriedTag: types.Bool(true), ageTag: types.Uint(21)})
rows[2] = row.New(sch, row.TaggedValues{
idTag: types.UUID(id2), firstTag: types.String("john"), lastTag: types.String("johnson"), isMarriedTag: types.Bool(false), ageTag: types.Uint(53)})
rows[3] = row.New(sch, row.TaggedValues{
idTag: types.UUID(id3), firstTag: types.String("robert"), lastTag: types.String("robertson"), ageTag: types.Uint(36)})
ed := types.NewMap(vrw).Edit()
for _, row := range rows {
ed = ed.Set(table.GetPKFromRow(row), table.GetNonPKFieldListFromRow(row, vrw))
for _, r := range rows {
ed = ed.Set(r.NomsMapKey(sch), r.NomsMapValue(sch))
}
return ed.Map(), rows
}
func createTestTable(vrw types.ValueReadWriter, tSchema *schema.Schema, rowData types.Map) (*Table, error) {
schemaVal, err := noms.MarshalAsNomsValue(vrw, tSchema)
func createTestTable(vrw types.ValueReadWriter, tSchema schema.Schema, rowData types.Map) (*Table, error) {
schemaVal, err := encoding.MarshalAsNomsValue(vrw, tSchema)
if err != nil {
return nil, err
@@ -58,32 +58,31 @@ func TestTables(t *testing.T) {
t.Fatal("Failed to create table.")
}
unmarshalledSchema := tbl.GetSchema()
//unmarshalledSchema := tbl.GetSchema()
if !tSchema.Equals(unmarshalledSchema) {
t.Error("Schema has changed between writing and reading it back")
}
//if !tSchema.Equals(unmarshalledSchema) {
// t.Error("Schema has changed between writing and reading it back")
//}
badUUID, _ := uuid.NewRandom()
ids := []types.Value{types.UUID(id0), types.UUID(id1), types.UUID(id2), types.UUID(id3), types.UUID(badUUID)}
readRow0, ok := tbl.GetRow(ids[0], tSchema)
readRow0, ok := tbl.GetRowByPKVals(row.TaggedValues{idTag: ids[0]}, tSchema)
if !ok {
t.Error("Could not find row 0 in table")
} else if !table.RowsEqualIgnoringSchema(readRow0, rows[0]) {
t.Error(table.RowFmt(readRow0), "!=", table.RowFmt(rows[0]))
} else {
t.Log("Rows equal:", table.RowFmt(readRow0))
} else if !row.AreEqual(readRow0, rows[0], tSchema) {
t.Error(row.Fmt(readRow0, tSchema), "!=", row.Fmt(rows[0], tSchema))
}
_, ok = tbl.GetRow(types.UUID(badUUID), tSchema)
_, ok = tbl.GetRowByPKVals(row.TaggedValues{idTag: types.UUID(badUUID)}, tSchema)
if ok {
t.Error("GetRow should have returned false.")
}
readRows, missing := tbl.GetRows(ValueSliceItr(ids), -1, tSchema)
idItr := SingleColPKItr(idTag, ids)
readRows, missing := tbl.GetRows(idItr, -1, tSchema)
if len(readRows) != len(rows) {
t.Error("Did not find all the expected rows")
@@ -91,9 +90,9 @@ func TestTables(t *testing.T) {
t.Error("Expected one missing row for badUUID")
}
for i, row := range rows {
if !table.RowsEqualIgnoringSchema(row, readRows[i]) {
t.Error(table.RowFmt(readRows[i]), "!=", table.RowFmt(row))
for i, r := range rows {
if !row.AreEqual(r, readRows[i], tSchema) {
t.Error(row.Fmt(readRows[i], tSchema), "!=", row.Fmt(r, tSchema))
}
}
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/creds"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"github.com/pkg/errors"
"google.golang.org/grpc"
@@ -241,7 +241,7 @@ func (dEnv *DoltEnv) UpdateStagedRoot(newRoot *doltdb.RootValue) (hash.Hash, err
return h, nil
}
func (dEnv *DoltEnv) PutTableToWorking(rows types.Map, sch *schema.Schema, tableName string) error {
func (dEnv *DoltEnv) PutTableToWorking(rows types.Map, sch schema.Schema, tableName string) error {
root, err := dEnv.WorkingRoot()
if err != nil {
@@ -249,7 +249,7 @@ func (dEnv *DoltEnv) PutTableToWorking(rows types.Map, sch *schema.Schema, table
}
vrw := dEnv.DoltDB.ValueReadWriter()
schVal, err := noms.MarshalAsNomsValue(vrw, sch)
schVal, err := encoding.MarshalAsNomsValue(vrw, sch)
if err != nil {
return ErrMarshallingSchema

View File

@@ -1,164 +0,0 @@
package merge
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
"io"
)
const (
mergeVersionProp = "merge_version"
mergeRowOperation = "row_operation"
)
type MergeVersion int
const (
BaseVersion MergeVersion = iota
OurVersion
TheirVersion
)
type ConflictReader struct {
confItr types.MapIterator
unionedSch *schema.Schema
baseConv *table.RowConverter
conv *table.RowConverter
mergeConv *table.RowConverter
bufferedRows [3]*table.Row
currIdx int
}
func NewConflictReader(tbl *doltdb.Table) (*ConflictReader, error) {
base, sch, mergeSch, err := tbl.GetConflictSchemas()
if err != nil {
return nil, err
}
unionedSch := untyped.UntypedSchemaUnion(base, sch, mergeSch)
var baseMapping, mapping, mergeMapping *schema.FieldMapping
baseMapping, err = schema.NewInferredMapping(base, unionedSch)
if err != nil {
return nil, err
}
mapping, err = schema.NewInferredMapping(sch, unionedSch)
if err != nil {
return nil, err
}
mergeMapping, err = schema.NewInferredMapping(mergeSch, unionedSch)
if err != nil {
return nil, err
}
_, confData, err := tbl.GetConflicts()
if err != nil {
return nil, err
}
confItr := confData.Iterator()
baseConv, err := table.NewRowConverter(baseMapping)
conv, err := table.NewRowConverter(mapping)
mergeConv, err := table.NewRowConverter(mergeMapping)
return &ConflictReader{
confItr,
unionedSch,
baseConv,
conv,
mergeConv,
[3]*table.Row{},
0}, nil
}
// GetSchema gets the schema of the rows that this reader will return
func (cr *ConflictReader) GetSchema() *schema.Schema {
return cr.unionedSch
}
// ReadRow reads a row from a table. If there is a bad row the returned error will be non nil, and callin IsBadRow(err)
// will be return true. This is a potentially non-fatal error and callers can decide if they want to continue on a bad row, or fail.
func (cr *ConflictReader) ReadRow() (*table.Row, error) {
for {
if cr.currIdx == 0 {
key, value := cr.confItr.Next()
if key == nil {
return nil, io.EOF
}
conflict := doltdb.ConflictFromTuple(value.(types.Tuple))
baseRow := f(key, conflict.Base, cr.baseConv)
row := f(key, conflict.Value, cr.conv)
mergeRow := f(key, conflict.MergeValue, cr.mergeConv)
if baseRow != nil {
if mergeRow != nil && row != nil {
cr.bufferedRows[2] = table.NewRowWithProperties(baseRow, map[string]interface{}{mergeVersionProp: BaseVersion})
cr.bufferedRows[1] = table.NewRowWithProperties(mergeRow, map[string]interface{}{mergeVersionProp: TheirVersion, mergeRowOperation: types.DiffChangeModified})
cr.bufferedRows[0] = table.NewRowWithProperties(row, map[string]interface{}{mergeVersionProp: OurVersion, mergeRowOperation: types.DiffChangeModified})
cr.currIdx = 3
} else if row != nil {
cr.bufferedRows[2] = table.NewRowWithProperties(baseRow, map[string]interface{}{mergeVersionProp: BaseVersion})
cr.bufferedRows[1] = table.NewRowWithProperties(baseRow, map[string]interface{}{mergeVersionProp: TheirVersion, mergeRowOperation: types.DiffChangeRemoved})
cr.bufferedRows[0] = table.NewRowWithProperties(row, map[string]interface{}{mergeVersionProp: OurVersion, mergeRowOperation: types.DiffChangeModified})
cr.currIdx = 3
} else {
cr.bufferedRows[2] = table.NewRowWithProperties(baseRow, map[string]interface{}{mergeVersionProp: BaseVersion})
cr.bufferedRows[1] = table.NewRowWithProperties(mergeRow, map[string]interface{}{mergeVersionProp: TheirVersion, mergeRowOperation: types.DiffChangeModified})
cr.bufferedRows[0] = table.NewRowWithProperties(baseRow, map[string]interface{}{mergeVersionProp: OurVersion, mergeRowOperation: types.DiffChangeRemoved})
cr.currIdx = 3
}
} else {
if mergeRow != nil {
cr.bufferedRows[0] = table.NewRowWithProperties(mergeRow, map[string]interface{}{mergeVersionProp: TheirVersion, mergeRowOperation: types.DiffChangeAdded})
cr.currIdx++
}
if row != nil {
cr.bufferedRows[1] = table.NewRowWithProperties(row, map[string]interface{}{mergeVersionProp: OurVersion, mergeRowOperation: types.DiffChangeAdded})
cr.currIdx++
}
}
}
cr.currIdx--
result := cr.bufferedRows[cr.currIdx]
if result.CurrData() != nil {
return result, nil
}
}
}
func f(key, fields types.Value, rowConv *table.RowConverter) *table.RowData {
if types.IsNull(fields) {
return nil
}
srcData := table.RowDataFromPKAndValueList(rowConv.SrcSch, key, fields.(types.Tuple))
row, err := rowConv.ConvertRowData(srcData)
if err != nil {
// bug or corrupt?
panic("conversion error.")
}
return row
}
// Close should release resources being held
func (cr *ConflictReader) Close() error {
return nil
}

View File

@@ -6,9 +6,10 @@ import (
"fmt"
"github.com/attic-labs/noms/go/types"
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/diff"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/iohelp"
"io"
@@ -29,28 +30,33 @@ var diffTypeToOpLabel = map[types.DiffChangeType]string{
var deleteColor = color.New(color.FgRed, color.CrossedOut)
var diffTypeToColor = map[types.DiffChangeType]doltdb.ColorFunc{
var diffTypeToColor = map[types.DiffChangeType]diff.ColorFunc{
types.DiffChangeAdded: color.GreenString,
types.DiffChangeModified: color.YellowString,
types.DiffChangeRemoved: deleteColor.Sprintf,
}
type ConflictWriter struct {
type ConflictSink struct {
bWr *bufio.Writer
sch *schema.Schema
sch schema.Schema
colSep string
inFieldCnt int
}
func NewConflictWriter(wr io.Writer, sch *schema.Schema, colSep string) *ConflictWriter {
additionalCols := untyped.NewUntypedSchema([]string{"op", "branch"})
outSch := untyped.UntypedSchemaUnion(additionalCols, sch)
func NewConflictSink(wr io.Writer, sch schema.Schema, colSep string) *ConflictSink {
_, additionalCols := untyped.NewUntypedSchemaWithFirstTag(schema.ReservedTagMin, "op", "branch")
outSch, err := untyped.UntypedSchemaUnion(additionalCols, sch)
if err != nil {
panic(err)
}
bWr := bufio.NewWriterSize(wr, WriteBufSize)
return &ConflictWriter{bWr, outSch, colSep, sch.NumFields()}
return &ConflictSink{bWr, outSch, colSep, sch.GetAllCols().Size()}
}
// GetSchema gets the schema of the rows that this writer writes
func (cWr *ConflictWriter) GetSchema() *schema.Schema {
func (cWr *ConflictSink) GetSchema() schema.Schema {
return cWr.sch
}
@@ -63,28 +69,31 @@ var noColorFunc = func(s string, i ...interface{}) string {
}
// WriteRow will write a row to a table
func (cWr *ConflictWriter) WriteRow(row *table.Row) error {
numFields := cWr.sch.NumFields()
func (cWr *ConflictSink) ProcRowWithProps(r row.Row, props pipeline.ReadableMap) error {
numFields := cWr.sch.GetAllCols().Size()
colStrs := make([]string, numFields)
colorFunc := noColorFunc
mergeVersion, _ := row.GetProperty(mergeVersionProp)
mergeVersion, _ := props.Get(mergeVersionProp)
colStrs[0] = " "
colStrs[1] = string(mergeVersionToLabel[mergeVersion.(MergeVersion)])
if mergeVersion != BaseVersion {
mergeRowOp, _ := row.GetProperty(mergeRowOperation)
mergeRowOp, _ := props.Get(mergeRowOperation)
dt := mergeRowOp.(types.DiffChangeType)
colStrs[0] = diffTypeToOpLabel[dt]
colorFunc = diffTypeToColor[dt]
}
rowData := row.CurrData()
for i := 0; i < cWr.inFieldCnt; i++ {
val, _ := rowData.GetField(i)
str := string(val.(types.String))
colStrs[i+2] = str
}
i := 0
cWr.sch.GetAllCols().ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
if val, ok := r.GetColVal(tag); ok {
str := string(val.(types.String))
colStrs[i] = str
}
i++
return false
})
lineStr := strings.Join(colStrs, cWr.colSep) + "\n"
coloredLine := colorFunc(lineStr)
@@ -94,7 +103,7 @@ func (cWr *ConflictWriter) WriteRow(row *table.Row) error {
}
// Close should release resources being held
func (cWr *ConflictWriter) Close() error {
func (cWr *ConflictSink) Close() error {
if cWr.bWr != nil {
cWr.bWr.Flush()
cWr.bWr = nil

View File

@@ -0,0 +1,171 @@
package merge
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/rowconv"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
"io"
)
const (
mergeVersionProp = "merge_version"
mergeRowOperation = "row_operation"
)
type MergeVersion int
const (
BaseVersion MergeVersion = iota
OurVersion
TheirVersion
)
type ConflictReader struct {
confItr types.MapIterator
unionedSch schema.Schema
baseConv *rowconv.RowConverter
conv *rowconv.RowConverter
mergeConv *rowconv.RowConverter
bufferedRows [3]pipeline.RowWithProps
currIdx int
}
func NewConflictReader(tbl *doltdb.Table) (*ConflictReader, error) {
base, sch, mergeSch, err := tbl.GetConflictSchemas()
if err != nil {
return nil, err
}
untypedUnSch, err := untyped.UntypedSchemaUnion(base, sch, mergeSch)
if err != nil {
return nil, err
}
var baseMapping, mapping, mergeMapping *rowconv.FieldMapping
baseMapping, err = rowconv.NewInferredMapping(base, untypedUnSch)
if err != nil {
return nil, err
}
mapping, err = rowconv.NewInferredMapping(sch, untypedUnSch)
if err != nil {
return nil, err
}
mergeMapping, err = rowconv.NewInferredMapping(mergeSch, untypedUnSch)
if err != nil {
return nil, err
}
_, confData, err := tbl.GetConflicts()
if err != nil {
return nil, err
}
confItr := confData.Iterator()
baseConv, err := rowconv.NewRowConverter(baseMapping)
conv, err := rowconv.NewRowConverter(mapping)
mergeConv, err := rowconv.NewRowConverter(mergeMapping)
return &ConflictReader{
confItr,
untypedUnSch,
baseConv,
conv,
mergeConv,
[3]pipeline.RowWithProps{},
0}, nil
}
// GetSchema gets the schema of the rows that this reader will return
func (cr *ConflictReader) GetSchema() schema.Schema {
return cr.unionedSch
}
// NextConflict reads a row from a table. If there is a bad row the returned error will be non nil, and callin IsBadRow(err)
// will be return true. This is a potentially non-fatal error and callers can decide if they want to continue on a bad row, or fail.
func (cr *ConflictReader) NextConflict() (row.Row, pipeline.ImmutableProperties, error) {
for {
if cr.currIdx == 0 {
key, value := cr.confItr.Next()
if key == nil {
return nil, pipeline.NoProps, io.EOF
}
keyTpl := key.(types.Tuple)
conflict := doltdb.ConflictFromTuple(value.(types.Tuple))
baseRow := createRow(keyTpl, conflict.Base, cr.baseConv)
r := createRow(keyTpl, conflict.Value, cr.conv)
mergeRow := createRow(keyTpl, conflict.MergeValue.(types.Tuple), cr.mergeConv)
if baseRow != nil {
if mergeRow != nil && r != nil {
cr.bufferedRows[2] = pipeline.NewRowWithProps(baseRow, map[string]interface{}{mergeVersionProp: BaseVersion})
cr.bufferedRows[1] = pipeline.NewRowWithProps(mergeRow, map[string]interface{}{mergeVersionProp: TheirVersion, mergeRowOperation: types.DiffChangeModified})
cr.bufferedRows[0] = pipeline.NewRowWithProps(r, map[string]interface{}{mergeVersionProp: OurVersion, mergeRowOperation: types.DiffChangeModified})
cr.currIdx = 3
} else if r != nil {
cr.bufferedRows[2] = pipeline.NewRowWithProps(baseRow, map[string]interface{}{mergeVersionProp: BaseVersion})
cr.bufferedRows[1] = pipeline.NewRowWithProps(baseRow, map[string]interface{}{mergeVersionProp: TheirVersion, mergeRowOperation: types.DiffChangeRemoved})
cr.bufferedRows[0] = pipeline.NewRowWithProps(r, map[string]interface{}{mergeVersionProp: OurVersion, mergeRowOperation: types.DiffChangeModified})
cr.currIdx = 3
} else {
cr.bufferedRows[2] = pipeline.NewRowWithProps(baseRow, map[string]interface{}{mergeVersionProp: BaseVersion})
cr.bufferedRows[1] = pipeline.NewRowWithProps(mergeRow, map[string]interface{}{mergeVersionProp: TheirVersion, mergeRowOperation: types.DiffChangeModified})
cr.bufferedRows[0] = pipeline.NewRowWithProps(baseRow, map[string]interface{}{mergeVersionProp: OurVersion, mergeRowOperation: types.DiffChangeRemoved})
cr.currIdx = 3
}
} else {
if mergeRow != nil {
cr.bufferedRows[0] = pipeline.NewRowWithProps(mergeRow, map[string]interface{}{mergeVersionProp: TheirVersion, mergeRowOperation: types.DiffChangeAdded})
cr.currIdx++
}
if r != nil {
cr.bufferedRows[1] = pipeline.NewRowWithProps(r, map[string]interface{}{mergeVersionProp: OurVersion, mergeRowOperation: types.DiffChangeAdded})
cr.currIdx++
}
}
}
cr.currIdx--
result := cr.bufferedRows[cr.currIdx]
if result.Row != nil {
return result.Row, result.Props, nil
}
}
}
func createRow(key types.Tuple, nonKey types.Value, rowConv *rowconv.RowConverter) row.Row {
if types.IsNull(nonKey) {
return nil
}
srcData := row.FromNoms(rowConv.SrcSch, key, nonKey.(types.Tuple))
row, err := rowConv.Convert(srcData)
if err != nil {
// bug or corrupt?
panic("conversion error.")
}
return row
}
// Close should release resources being held
func (cr *ConflictReader) Close() error {
return nil
}

View File

@@ -2,10 +2,12 @@ package merge
import (
"errors"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/valutil"
)
var ErrFastForward = errors.New("fast forward")
@@ -60,16 +62,17 @@ func (merger *Merger) MergeTable(tblName string) (*doltdb.Table, *MergeStats, er
tblSchema := tbl.GetSchema()
mergeTblSchema := mergeTbl.GetSchema()
schemaUnion, err := typed.TypedSchemaUnion(tblSchema, mergeTblSchema)
if !isSchemaIdentical(tblSchema, mergeTblSchema) {
return nil, nil, ErrSchemaNotIdentical
if err != nil {
return nil, nil, err
}
rows := tbl.GetRowData()
mergeRows := mergeTbl.GetRowData()
ancRows := ancTbl.GetRowData()
mergedRowData, conflicts, stats, err := mergeTableData(rows, mergeRows, ancRows, merger.vrw)
mergedRowData, conflicts, stats, err := mergeTableData(schemaUnion, rows, mergeRows, ancRows, merger.vrw)
if err != nil {
return nil, nil, err
@@ -91,7 +94,7 @@ func stopAndDrain(stop chan<- struct{}, drain <-chan types.ValueChanged) {
}
}
func mergeTableData(rows, mergeRows, ancRows types.Map, vrw types.ValueReadWriter) (types.Map, types.Map, *MergeStats, error) {
func mergeTableData(sch schema.Schema, rows, mergeRows, ancRows types.Map, vrw types.ValueReadWriter) (types.Map, types.Map, *MergeStats, error) {
//changeChan1, changeChan2 := make(chan diff.Difference, 32), make(chan diff.Difference, 32)
changeChan, mergeChangeChan := make(chan types.ValueChanged, 32), make(chan types.ValueChanged, 32)
stopChan, mergeStopChan := make(chan struct{}, 1), make(chan struct{}, 1)
@@ -140,15 +143,15 @@ func mergeTableData(rows, mergeRows, ancRows types.Map, vrw types.ValueReadWrite
applyChange(mapEditor, stats, mergeChange)
mergeChange = types.ValueChanged{}
} else {
row, mergeRow, ancRow := change.NewValue, mergeChange.NewValue, change.OldValue
mergedRow, isConflict := rowMerge(row, mergeRow, ancRow)
r, mergeRow, ancRow := change.NewValue, mergeChange.NewValue, change.OldValue
mergedRow, isConflict := rowMerge(sch, r, mergeRow, ancRow)
if isConflict {
stats.Conflicts++
conflictTuple := doltdb.NewConflict(ancRow, row, mergeRow).ToNomsList(vrw)
conflictTuple := doltdb.NewConflict(ancRow, r, mergeRow).ToNomsList(vrw)
addConflict(conflictValChan, key, conflictTuple)
} else {
applyChange(mapEditor, stats, types.ValueChanged{change.ChangeType, key, row, mergedRow})
applyChange(mapEditor, stats, types.ValueChanged{change.ChangeType, key, r, mergedRow})
}
change = types.ValueChanged{}
@@ -182,109 +185,65 @@ func applyChange(me *types.MapEditor, stats *MergeStats, change types.ValueChang
}
}
func rowMerge(row, mergeRow, baseRow types.Value) (resultRow types.Value, isConflict bool) {
func rowMerge(sch schema.Schema, r, mergeRow, baseRow types.Value) (resultRow types.Value, isConflict bool) {
if baseRow == nil {
if row.Equals(mergeRow) {
if r.Equals(mergeRow) {
// same row added to both
return row, false
return r, false
} else {
// different rows added for the same key
return nil, true
}
} else if row == nil && mergeRow == nil {
} else if r == nil && mergeRow == nil {
// same row removed from both
return nil, false
} else if row == nil || mergeRow == nil {
} else if r == nil || mergeRow == nil {
// removed from one and modified in another
return nil, true
} else {
tuple := row.(types.Tuple)
mergeTuple := mergeRow.(types.Tuple)
baseTuple := baseRow.(types.Tuple)
rowVals := row.ParseTaggedValues(r.(types.Tuple))
mergeVals := row.ParseTaggedValues(mergeRow.(types.Tuple))
baseVals := row.ParseTaggedValues(baseRow.(types.Tuple))
numVals := tuple.Len()
numMergeVals := mergeTuple.Len()
numBaseVals := baseTuple.Len()
maxLen := numVals
if numMergeVals > maxLen {
maxLen = numMergeVals
}
processTagFunc := func(tag uint64) (resultVal types.Value, isConflict bool) {
baseVal, _ := baseVals.Get(tag)
val, _ := rowVals.Get(tag)
mergeVal, _ := mergeVals.Get(tag)
resultVals := make([]types.Value, maxLen)
for i := uint64(0); i < maxLen; i++ {
var baseVal types.Value = types.NullValue
var val types.Value = types.NullValue
var mergeVal types.Value = types.NullValue
if i < numBaseVals {
baseVal = baseTuple.Get(i)
}
if i < numVals {
val = tuple.Get(i)
}
if i < numMergeVals {
mergeVal = mergeTuple.Get(i)
}
if val.Equals(mergeVal) {
resultVals[int(i)] = val
if valutil.NilSafeEqCheck(val, mergeVal) {
return val, false
} else {
modified := !val.Equals(baseVal)
mergeModified := !mergeVal.Equals(baseVal)
modified := !valutil.NilSafeEqCheck(val, baseVal)
mergeModified := !valutil.NilSafeEqCheck(mergeVal, baseVal)
switch {
case modified && mergeModified:
return nil, true
case modified:
resultVals[int(i)] = val
return val, false
default:
resultVals[int(i)] = mergeVal
return mergeVal, false
}
}
}
return types.NewTuple(resultVals...), false
resultVals := make(row.TaggedValues)
var isConflict bool
sch.GetNonPKCols().ItrUnsorted(func(tag uint64, _ schema.Column) (stop bool) {
var val types.Value
val, isConflict = processTagFunc(tag)
resultVals[tag] = val
return isConflict
})
if isConflict {
return nil, true
}
tpl := resultVals.NomsTupleForTags(sch.GetNonPKCols().SortedTags, false)
return tpl, false
}
}
func isSchemaIdentical(tblSchema *schema.Schema, mergeTblSchema *schema.Schema) bool {
if tblSchema.NumFields() != mergeTblSchema.NumFields() {
return false
}
if tblSchema.GetPKIndex() != mergeTblSchema.GetPKIndex() {
return false
}
tblConstraints := tblSchema.TotalNumConstraints()
mergeTblConstraints := mergeTblSchema.TotalNumConstraints()
if tblConstraints != mergeTblConstraints {
return false
}
for i := 0; i < tblConstraints; i++ {
if tblSchema.GetConstraint(i).ConType() != mergeTblSchema.GetConstraint(i).ConType() {
return false
}
for j := range tblSchema.GetConstraint(i).FieldIndices() {
if tblSchema.GetConstraint(i).FieldIndices()[j] != mergeTblSchema.GetConstraint(i).FieldIndices()[j] {
return false
}
}
}
tblFieldNames := tblSchema.GetFieldNames()
for i := range tblFieldNames {
tblField := tblSchema.GetField(i)
mergeTblField := mergeTblSchema.GetField(i)
if !tblField.Equals(mergeTblField) {
return false
}
}
return true
}

View File

@@ -1,107 +1,151 @@
package merge
import (
"testing"
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"strconv"
"testing"
)
type RowMergeTest struct {
name string
row, mergeRow, ancRow types.Value
sch schema.Schema
expectedResult types.Value
expectConflict bool
}
func valsToTestTuple(vals []types.Value) types.Value {
if vals == nil {
return nil
}
tplVals := make([]types.Value, 0, 2*len(vals))
for i, val := range vals {
if !types.IsNull(val) {
tplVals = append(tplVals, types.Uint(i))
tplVals = append(tplVals, val)
}
}
return types.NewTuple(tplVals...)
}
func createRowMergeStruct(name string, vals, mergeVals, ancVals, expected []types.Value, expectCnf bool) RowMergeTest {
longest := vals
if len(mergeVals) > len(longest) {
longest = mergeVals
}
if len(ancVals) > len(longest) {
longest = ancVals
}
cols := make([]schema.Column, len(longest))
for i, val := range longest {
cols[i] = schema.NewColumn(strconv.FormatInt(int64(i), 10), uint64(i), val.Kind(), false)
}
colColl, _ := schema.NewColCollection(cols...)
sch := schema.SchemaFromCols(colColl)
tpl := valsToTestTuple(vals)
mergeTpl := valsToTestTuple(mergeVals)
ancTpl := valsToTestTuple(ancVals)
expectedTpl := valsToTestTuple(expected)
return RowMergeTest{name, tpl, mergeTpl, ancTpl, sch, expectedTpl, expectCnf}
}
func TestRowMerge(t *testing.T) {
tests := []struct {
name string
row, mergeRow, ancRow types.Value
expectedResult types.Value
expectConflict bool
}{
{
tests := []RowMergeTest{
createRowMergeStruct(
"add same row",
types.NewTuple(types.String("one"), types.Int(2)),
types.NewTuple(types.String("one"), types.Int(2)),
[]types.Value{types.String("one"), types.Int(2)},
[]types.Value{types.String("one"), types.Int(2)},
nil,
types.NewTuple(types.String("one"), types.Int(2)),
[]types.Value{types.String("one"), types.Int(2)},
false,
},
{
),
createRowMergeStruct(
"add diff row",
types.NewTuple(types.String("one"), types.String("two")),
types.NewTuple(types.String("one"), types.String("three")),
[]types.Value{types.String("one"), types.String("two")},
[]types.Value{types.String("one"), types.String("three")},
nil,
nil,
true,
},
{
),
createRowMergeStruct(
"both delete row",
nil,
nil,
types.NewTuple(types.String("one"), types.Uint(2)),
[]types.Value{types.String("one"), types.Uint(2)},
nil,
false,
},
{
),
createRowMergeStruct(
"one delete one modify",
nil,
types.NewTuple(types.String("two"), types.Uint(2)),
types.NewTuple(types.String("one"), types.Uint(2)),
[]types.Value{types.String("two"), types.Uint(2)},
[]types.Value{types.String("one"), types.Uint(2)},
nil,
true,
},
{
),
createRowMergeStruct(
"modify rows without overlap",
types.NewTuple(types.String("two"), types.Uint(2)),
types.NewTuple(types.String("one"), types.Uint(3)),
types.NewTuple(types.String("one"), types.Uint(2)),
types.NewTuple(types.String("two"), types.Uint(3)),
[]types.Value{types.String("two"), types.Uint(2)},
[]types.Value{types.String("one"), types.Uint(3)},
[]types.Value{types.String("one"), types.Uint(2)},
[]types.Value{types.String("two"), types.Uint(3)},
false,
},
{
),
createRowMergeStruct(
"modify rows with equal overlapping changes",
types.NewTuple(types.String("two"), types.Uint(2), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))),
types.NewTuple(types.String("one"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))),
types.NewTuple(types.String("one"), types.Uint(2), types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000000"))),
types.NewTuple(types.String("two"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))),
[]types.Value{types.String("two"), types.Uint(2), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))},
[]types.Value{types.String("one"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))},
[]types.Value{types.String("one"), types.Uint(2), types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000000"))},
[]types.Value{types.String("two"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))},
false,
},
{
),
createRowMergeStruct(
"modify rows with differing overlapping changes",
types.NewTuple(types.String("two"), types.Uint(2), types.UUID(uuid.MustParse("99999999-9999-9999-9999-999999999999"))),
types.NewTuple(types.String("one"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))),
types.NewTuple(types.String("one"), types.Uint(2), types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000000"))),
[]types.Value{types.String("two"), types.Uint(2), types.UUID(uuid.MustParse("99999999-9999-9999-9999-999999999999"))},
[]types.Value{types.String("one"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))},
[]types.Value{types.String("one"), types.Uint(2), types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000000"))},
nil,
true,
},
{
),
createRowMergeStruct(
"modify rows where one adds a column",
types.NewTuple(types.String("two"), types.Uint(2)),
types.NewTuple(types.String("one"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))),
types.NewTuple(types.String("one"), types.Uint(2)),
types.NewTuple(types.String("two"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))),
[]types.Value{types.String("two"), types.Uint(2)},
[]types.Value{types.String("one"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))},
[]types.Value{types.String("one"), types.Uint(2)},
[]types.Value{types.String("two"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))},
false,
},
{
),
createRowMergeStruct(
"modify row where values added in different columns",
types.NewTuple(types.String("one"), types.Uint(2), types.String(""), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))),
types.NewTuple(types.String("one"), types.Uint(2), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff")), types.String("")),
types.NewTuple(types.String("one"), types.Uint(2), types.NullValue, types.NullValue),
[]types.Value{types.String("one"), types.Uint(2), types.String(""), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))},
[]types.Value{types.String("one"), types.Uint(2), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff")), types.String("")},
[]types.Value{types.String("one"), types.Uint(2), types.NullValue, types.NullValue},
nil,
true,
},
{
),
createRowMergeStruct(
"modify row where intial value wasn't given",
types.NewTuple(types.String("one"), types.Uint(2), types.String("a")),
types.NewTuple(types.String("one"), types.Uint(2), types.String("b")),
types.NewTuple(types.String("one"), types.Uint(2), types.NullValue),
[]types.Value{types.NewTuple(types.String("one"), types.Uint(2), types.String("a"))},
[]types.Value{types.NewTuple(types.String("one"), types.Uint(2), types.String("b"))},
[]types.Value{types.NewTuple(types.String("one"), types.Uint(2), types.NullValue)},
nil,
true,
},
),
}
for _, test := range tests {
actualResult, isConflict := rowMerge(test.row, test.mergeRow, test.ancRow)
actualResult, isConflict := rowMerge(test.sch, test.row, test.mergeRow, test.ancRow)
if test.expectedResult == nil {
if actualResult != nil {
@@ -109,7 +153,7 @@ func TestRowMerge(t *testing.T) {
}
} else if !test.expectedResult.Equals(actualResult) {
t.Error(
"Test:", test.name, "failed.",
"Test:", "\""+test.name+"\"", "failed.",
"Merged row did not match expected. expected:\n\t", types.EncodedValue(test.expectedResult),
"\nactual:\n\t", types.EncodedValue(actualResult))
}
@@ -124,18 +168,18 @@ const (
tableName = "test-table"
name = "billy bob"
email = "bigbillieb@fake.horse"
idTag = 100
nameTag = 0
titleTag = 1
)
var schFlds = []*schema.Field{
schema.NewField("id", types.UUIDKind, true),
schema.NewField("name", types.StringKind, true),
schema.NewField("title", types.StringKind, false),
}
var sch = schema.NewSchema(schFlds)
func init() {
sch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
}
var colColl, _ = schema.NewColCollection(
schema.NewColumn("id", idTag, types.UUIDKind, true, schema.NotNullConstraint{}),
schema.NewColumn("name", nameTag, types.StringKind, false, schema.NotNullConstraint{}),
schema.NewColumn("title", titleTag, types.StringKind, false),
)
var sch = schema.SchemaFromCols(colColl)
var uuids = []types.UUID{
types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000000")),
@@ -153,6 +197,16 @@ var uuids = []types.UUID{
types.UUID(uuid.MustParse("00000000-0000-0000-0000-00000000000c")),
}
var keyTuples = make([]types.Tuple, len(uuids))
func init() {
keyTag := types.Uint(idTag)
for i, id := range uuids {
keyTuples[i] = types.NewTuple(keyTag, id)
}
}
func setupMergeTest() (types.ValueReadWriter, *doltdb.Commit, *doltdb.Commit, types.Map, types.Map) {
ddb := doltdb.LoadDoltDB(doltdb.InMemDoltDB)
vrw := ddb.ValueReadWriter()
@@ -171,68 +225,69 @@ func setupMergeTest() (types.ValueReadWriter, *doltdb.Commit, *doltdb.Commit, ty
}
initialRows := types.NewMap(vrw,
uuids[0], types.NewTuple(types.String("person 1"), types.String("dufus")),
uuids[1], types.NewTuple(types.String("person 2"), types.NullValue),
uuids[2], types.NewTuple(types.String("person 3"), types.NullValue),
uuids[3], types.NewTuple(types.String("person 4"), types.String("senior dufus")),
uuids[4], types.NewTuple(types.String("person 5"), types.NullValue),
uuids[5], types.NewTuple(types.String("person 6"), types.NullValue),
uuids[6], types.NewTuple(types.String("person 7"), types.String("madam")),
uuids[7], types.NewTuple(types.String("person 8"), types.String("miss")),
uuids[8], types.NewTuple(types.String("person 9"), types.NullValue),
keyTuples[0], valsToTestTuple([]types.Value{types.String("person 1"), types.String("dufus")}),
keyTuples[1], valsToTestTuple([]types.Value{types.String("person 2"), types.NullValue}),
keyTuples[2], valsToTestTuple([]types.Value{types.String("person 3"), types.NullValue}),
keyTuples[3], valsToTestTuple([]types.Value{types.String("person 4"), types.String("senior dufus")}),
keyTuples[4], valsToTestTuple([]types.Value{types.String("person 5"), types.NullValue}),
keyTuples[5], valsToTestTuple([]types.Value{types.String("person 6"), types.NullValue}),
keyTuples[6], valsToTestTuple([]types.Value{types.String("person 7"), types.String("madam")}),
keyTuples[7], valsToTestTuple([]types.Value{types.String("person 8"), types.String("miss")}),
keyTuples[8], valsToTestTuple([]types.Value{types.String("person 9"), types.NullValue}),
)
updateRowEditor := initialRows.Edit() // leave 0 as is
updateRowEditor.Remove(uuids[1]) // remove 1 from both
updateRowEditor.Remove(uuids[2]) // remove 2 from update
updateRowEditor.Set(uuids[4], types.NewTuple(types.String("person five"), types.NullValue)) // modify 4 only in update
updateRowEditor.Set(uuids[6], types.NewTuple(types.String("person 7"), types.String("dr"))) // modify 6 in both without overlap
updateRowEditor.Set(uuids[7], types.NewTuple(types.String("person eight"), types.NullValue)) // modify 7 in both with equal overlap
updateRowEditor.Set(uuids[8], types.NewTuple(types.String("person nine"), types.NullValue)) // modify 8 in both with conflicting overlap
updateRowEditor.Set(uuids[9], types.NewTuple(types.String("person ten"), types.NullValue)) // add 9 in update
updateRowEditor.Set(uuids[11], types.NewTuple(types.String("person twelve"), types.NullValue)) // add 11 in both without difference
updateRowEditor.Set(uuids[12], types.NewTuple(types.String("person thirteen"), types.NullValue)) // add 12 in both with differences
updateRowEditor := initialRows.Edit() // leave 0 as is
updateRowEditor.Remove(keyTuples[1]) // remove 1 from both
updateRowEditor.Remove(keyTuples[2]) // remove 2 from update
updateRowEditor.Set(keyTuples[4], valsToTestTuple([]types.Value{types.String("person five"), types.NullValue})) // modify 4 only in update
updateRowEditor.Set(keyTuples[6], valsToTestTuple([]types.Value{types.String("person 7"), types.String("dr")})) // modify 6 in both without overlap
updateRowEditor.Set(keyTuples[7], valsToTestTuple([]types.Value{types.String("person eight"), types.NullValue})) // modify 7 in both with equal overlap
updateRowEditor.Set(keyTuples[8], valsToTestTuple([]types.Value{types.String("person nine"), types.NullValue})) // modify 8 in both with conflicting overlap
updateRowEditor.Set(keyTuples[9], valsToTestTuple([]types.Value{types.String("person ten"), types.NullValue})) // add 9 in update
updateRowEditor.Set(keyTuples[11], valsToTestTuple([]types.Value{types.String("person twelve"), types.NullValue})) // add 11 in both without difference
updateRowEditor.Set(keyTuples[12], valsToTestTuple([]types.Value{types.String("person thirteen"), types.NullValue})) // add 12 in both with differences
updatedRows := updateRowEditor.Map()
mergeRowEditor := initialRows.Edit() // leave 0 as is
mergeRowEditor.Remove(uuids[1]) // remove 1 from both
mergeRowEditor.Remove(uuids[3]) // remove 3 from merge
mergeRowEditor.Set(uuids[5], types.NewTuple(types.String("person six"), types.NullValue)) // modify 5 only in merge
mergeRowEditor.Set(uuids[6], types.NewTuple(types.String("person seven"), types.String("madam"))) // modify 6 in both without overlap
mergeRowEditor.Set(uuids[7], types.NewTuple(types.String("person eight"), types.NullValue)) // modify 7 in both with equal overlap
mergeRowEditor.Set(uuids[8], types.NewTuple(types.String("person number nine"), types.NullValue)) // modify 8 in both with conflicting overlap
mergeRowEditor.Set(uuids[10], types.NewTuple(types.String("person eleven"), types.NullValue)) // add 10 in merge
mergeRowEditor.Set(uuids[11], types.NewTuple(types.String("person twelve"), types.NullValue)) // add 11 in both without difference
mergeRowEditor.Set(uuids[12], types.NewTuple(types.String("person number thirteen"), types.NullValue)) // add 12 in both with differences
mergeRowEditor := initialRows.Edit() // leave 0 as is
mergeRowEditor.Remove(keyTuples[1]) // remove 1 from both
mergeRowEditor.Remove(keyTuples[3]) // remove 3 from merge
mergeRowEditor.Set(keyTuples[5], valsToTestTuple([]types.Value{types.String("person six"), types.NullValue})) // modify 5 only in merge
mergeRowEditor.Set(keyTuples[6], valsToTestTuple([]types.Value{types.String("person seven"), types.String("madam")})) // modify 6 in both without overlap
mergeRowEditor.Set(keyTuples[7], valsToTestTuple([]types.Value{types.String("person eight"), types.NullValue})) // modify 7 in both with equal overlap
mergeRowEditor.Set(keyTuples[8], valsToTestTuple([]types.Value{types.String("person number nine"), types.NullValue})) // modify 8 in both with conflicting overlap
mergeRowEditor.Set(keyTuples[10], valsToTestTuple([]types.Value{types.String("person eleven"), types.NullValue})) // add 10 in merge
mergeRowEditor.Set(keyTuples[11], valsToTestTuple([]types.Value{types.String("person twelve"), types.NullValue})) // add 11 in both without difference
mergeRowEditor.Set(keyTuples[12], valsToTestTuple([]types.Value{types.String("person number thirteen"), types.NullValue})) // add 12 in both with differences
mergeRows := mergeRowEditor.Map()
expectedRows := types.NewMap(vrw,
uuids[0], initialRows.Get(uuids[0]), // unaltered
uuids[4], updatedRows.Get(uuids[4]), // modified in updated
uuids[5], mergeRows.Get(uuids[5]), // modified in merged
uuids[6], types.NewTuple(types.String("person seven"), types.String("dr")), // modified in both with no overlap
uuids[7], updatedRows.Get(uuids[7]), // modify both with the same value
uuids[8], updatedRows.Get(uuids[8]), // conflict
uuids[9], updatedRows.Get(uuids[9]), // added in update
uuids[10], mergeRows.Get(uuids[10]), // added in merge
uuids[11], updatedRows.Get(uuids[11]), // added same in both
uuids[12], updatedRows.Get(uuids[12]), // conflict
keyTuples[0], initialRows.Get(keyTuples[0]), // unaltered
keyTuples[4], updatedRows.Get(keyTuples[4]), // modified in updated
keyTuples[5], mergeRows.Get(keyTuples[5]), // modified in merged
keyTuples[6], valsToTestTuple([]types.Value{types.String("person seven"), types.String("dr")}), // modified in both with no overlap
keyTuples[7], updatedRows.Get(keyTuples[7]), // modify both with the same value
keyTuples[8], updatedRows.Get(keyTuples[8]), // conflict
keyTuples[9], updatedRows.Get(keyTuples[9]), // added in update
keyTuples[10], mergeRows.Get(keyTuples[10]), // added in merge
keyTuples[11], updatedRows.Get(keyTuples[11]), // added same in both
keyTuples[12], updatedRows.Get(keyTuples[12]), // conflict
)
updateConflict := doltdb.NewConflict(initialRows.Get(uuids[8]), updatedRows.Get(uuids[8]), mergeRows.Get(uuids[8]))
updateConflict := doltdb.NewConflict(initialRows.Get(keyTuples[8]), updatedRows.Get(keyTuples[8]), mergeRows.Get(keyTuples[8]))
addConflict := doltdb.NewConflict(
nil,
types.NewTuple(types.String("person thirteen"), types.NullValue),
types.NewTuple(types.String("person number thirteen"), types.NullValue),
valsToTestTuple([]types.Value{types.String("person thirteen"), types.NullValue}),
valsToTestTuple([]types.Value{types.String("person number thirteen"), types.NullValue}),
)
expectedConflicts := types.NewMap(vrw,
uuids[8], updateConflict.ToNomsList(vrw),
uuids[12], addConflict.ToNomsList(vrw),
keyTuples[8], updateConflict.ToNomsList(vrw),
keyTuples[12], addConflict.ToNomsList(vrw),
)
schVal, _ := noms.MarshalAsNomsValue(vrw, sch)
schVal, _ := encoding.MarshalAsNomsValue(vrw, sch)
tbl := doltdb.NewTable(vrw, schVal, initialRows)
updatedTbl := doltdb.NewTable(vrw, schVal, updatedRows)
mergeTbl := doltdb.NewTable(vrw, schVal, mergeRows)

View File

@@ -3,8 +3,9 @@ package merge
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
)
type AutoResolver func(key types.Value, conflict doltdb.Conflict) (types.Value, error)
@@ -24,7 +25,7 @@ func ResolveTable(vrw types.ValueReadWriter, tbl *doltdb.Table, autoResFunc Auto
tblSchRef := tbl.GetSchemaRef()
tblSchVal := tblSchRef.TargetValue(vrw)
tblSch, err := noms.UnmarshalNomsValue(tblSchVal)
tblSch, err := encoding.UnmarshalNomsValue(tblSchVal)
if err != nil {
return nil, err
@@ -52,9 +53,9 @@ func ResolveTable(vrw types.ValueReadWriter, tbl *doltdb.Table, autoResFunc Auto
if types.IsNull(updated) {
rowEditor.Remove(key)
} else {
rd := table.RowDataFromPKAndValueList(tblSch, key, updated.(types.Tuple))
r := row.FromNoms(tblSch, key.(types.Tuple), updated.(types.Tuple))
if !rd.IsValid() {
if !row.IsValid(r, tblSch) {
itrErr = table.ErrInvalidRow
return true
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/nbf"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped/csv"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
@@ -22,7 +21,7 @@ const (
DoltDB DataFormat = "doltdb"
CsvFile DataFormat = ".csv"
PsvFile DataFormat = ".psv"
NbfFile DataFormat = ".nbf"
//NbfFile DataFormat = ".nbf"
)
func (df DataFormat) ReadableStr() string {
@@ -36,8 +35,8 @@ func (df DataFormat) ReadableStr() string {
case PsvFile:
return "psv file"
case NbfFile:
return "nbf file"
//case NbfFile:
// return "nbf file"
}
return "invalid"
@@ -49,8 +48,8 @@ func DFFromString(dfStr string) DataFormat {
return CsvFile
case "psv", ".psv":
return PsvFile
case "nbf", ".nbf":
return NbfFile
//case "nbf", ".nbf":
// return NbfFile
}
return InvalidDataFormat
@@ -79,8 +78,8 @@ func NewDataLocation(path, fileFmtStr string) *DataLocation {
dataFmt = CsvFile
case string(PsvFile):
dataFmt = PsvFile
case string(NbfFile):
dataFmt = NbfFile
//case string(NbfFile):
// dataFmt = NbfFile
}
}
@@ -127,9 +126,9 @@ func (dl *DataLocation) CreateReader(root *doltdb.RootValue, fs filesys.Readable
rd, err := csv.OpenCSVReader(dl.Path, fs, csv.NewCSVInfo().SetDelim('|'))
return rd, false, err
case NbfFile:
rd, err := nbf.OpenNBFReader(dl.Path, fs)
return rd, true, err
//case NbfFile:
// rd, err := nbf.OpenNBFReader(dl.Path, fs)
// return rd, true, err
}
}
@@ -151,8 +150,8 @@ func (dl *DataLocation) Exists(root *doltdb.RootValue, fs filesys.ReadableFS) bo
var ErrNoPK = errors.New("schema does not contain a primary key")
func (dl *DataLocation) CreateOverwritingDataWriter(root *doltdb.RootValue, fs filesys.WritableFS, sortedInput bool, outSch *schema.Schema) (table.TableWriteCloser, error) {
if dl.RequiresPK() && outSch.GetPKIndex() == -1 {
func (dl *DataLocation) CreateOverwritingDataWriter(root *doltdb.RootValue, fs filesys.WritableFS, sortedInput bool, outSch schema.Schema) (table.TableWriteCloser, error) {
if dl.RequiresPK() && outSch.GetPKCols().Size() == 0 {
return nil, ErrNoPK
}
@@ -173,17 +172,17 @@ func (dl *DataLocation) CreateOverwritingDataWriter(root *doltdb.RootValue, fs f
tWr, err := csv.OpenCSVWriter(dl.Path, fs, outSch, csv.NewCSVInfo().SetDelim('|'))
return tWr, err
case NbfFile:
tWr, err := nbf.OpenNBFWriter(dl.Path, fs, outSch)
return tWr, err
//case NbfFile:
// tWr, err := nbf.OpenNBFWriter(dl.Path, fs, outSch)
// return tWr, err
}
panic("Invalid Data Format.")
panic("Invalid Data Format." + string(dl.Format))
}
// CreateUpdatingDataWriter will create a TableWriteCloser for a DataLocation that will update and append rows based
// on their primary key.
func (dl *DataLocation) CreateUpdatingDataWriter(root *doltdb.RootValue, fs filesys.WritableFS, srcIsSorted bool, outSch *schema.Schema) (table.TableWriteCloser, error) {
func (dl *DataLocation) CreateUpdatingDataWriter(root *doltdb.RootValue, fs filesys.WritableFS, srcIsSorted bool, outSch schema.Schema) (table.TableWriteCloser, error) {
switch dl.Format {
case DoltDB:
tableName := dl.Path
@@ -196,7 +195,7 @@ func (dl *DataLocation) CreateUpdatingDataWriter(root *doltdb.RootValue, fs file
m := tbl.GetRowData()
return noms.NewNomsMapUpdater(root.VRW(), m, outSch), nil
case CsvFile, PsvFile, NbfFile:
case CsvFile, PsvFile:
panic("Update not supported for this file type.")
}
@@ -205,10 +204,10 @@ func (dl *DataLocation) CreateUpdatingDataWriter(root *doltdb.RootValue, fs file
// MustWriteSorted returns whether this DataLocation must be written to in primary key order
func (dl *DataLocation) MustWriteSorted() bool {
return dl.Format == NbfFile
return false //dl.Format == NbfFile
}
// RequiresPK returns whether this DataLocation requires a primary key
func (dl *DataLocation) RequiresPK() bool {
return dl.Format == NbfFile || dl.Format == DoltDB
return /*dl.Format == NbfFile ||*/ dl.Format == DoltDB
}

View File

@@ -3,13 +3,13 @@ package mvdata
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/nbf"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped/csv"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/test"
"reflect"
"testing"
)
@@ -41,7 +41,7 @@ func TestBasics(t *testing.T) {
{NewDataLocation("table-name", ""), DoltDB, "table-name", false, true, false},
{NewDataLocation("file.csv", ""), CsvFile, "file.csv", true, false, false},
{NewDataLocation("file.psv", ""), PsvFile, "file.psv", true, false, false},
{NewDataLocation("file.nbf", ""), NbfFile, "file.nbf", true, true, true},
//{NewDataLocation("file.nbf", ""), NbfFile, "file.nbf", true, true, true},
}
for _, test := range tests {
@@ -67,26 +67,22 @@ func TestBasics(t *testing.T) {
}
}
var fakeFields = []*schema.Field{
schema.NewField("a", types.StringKind, true),
schema.NewField("b", types.StringKind, true),
}
var fakeSchema *schema.Schema
var fakeFields, _ = schema.NewColCollection(
schema.NewColumn("a", 0, types.StringKind, true, schema.NotNullConstraint{}),
schema.NewColumn("b", 1, types.StringKind, false),
)
var fakeSchema schema.Schema
var imt *table.InMemTable
var imtRows []*table.Row
var imtRows []row.Row
func init() {
fakeSchema = schema.NewSchema(fakeFields)
err := fakeSchema.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
fakeSchema = schema.SchemaFromCols(fakeFields)
if err != nil {
panic(test.ShouldNeverHappen)
}
imtRows = []*table.Row{
table.NewRow(table.RowDataFromValues(fakeSchema, []types.Value{types.String("a"), types.String("1")})),
table.NewRow(table.RowDataFromValues(fakeSchema, []types.Value{types.String("b"), types.String("2")})),
table.NewRow(table.RowDataFromValues(fakeSchema, []types.Value{types.String("c"), types.String("3")})),
imtRows = []row.Row{
row.New(fakeSchema, row.TaggedValues{0: types.String("a"), 1: types.String("1")}),
row.New(fakeSchema, row.TaggedValues{0: types.String("b"), 1: types.String("2")}),
row.New(fakeSchema, row.TaggedValues{0: types.String("c"), 1: types.String("3")}),
}
imt = table.NewInMemTableWithData(fakeSchema, imtRows)
@@ -97,7 +93,7 @@ func TestExists(t *testing.T) {
NewDataLocation("table-name", ""),
NewDataLocation("file.csv", ""),
NewDataLocation("file.psv", ""),
NewDataLocation("file.nbf", ""),
//NewDataLocation("file.nbf", ""),
}
ddb, root, fs := createRootAndFS()
@@ -108,7 +104,7 @@ func TestExists(t *testing.T) {
}
if loc.Format == DoltDB {
schVal, _ := noms.MarshalAsNomsValue(ddb.ValueReadWriter(), fakeSchema)
schVal, _ := encoding.MarshalAsNomsValue(ddb.ValueReadWriter(), fakeSchema)
tbl := doltdb.NewTable(ddb.ValueReadWriter(), schVal, types.NewMap(ddb.ValueReadWriter()))
root = root.PutTable(ddb, loc.Path, tbl)
} else {
@@ -130,7 +126,7 @@ func TestCreateRdWr(t *testing.T) {
{NewDataLocation("table-name", ""), reflect.TypeOf((*noms.NomsMapReader)(nil)).Elem(), reflect.TypeOf((*noms.NomsMapCreator)(nil)).Elem()},
{NewDataLocation("file.csv", ""), reflect.TypeOf((*csv.CSVReader)(nil)).Elem(), reflect.TypeOf((*csv.CSVWriter)(nil)).Elem()},
{NewDataLocation("file.psv", ""), reflect.TypeOf((*csv.CSVReader)(nil)).Elem(), reflect.TypeOf((*csv.CSVWriter)(nil)).Elem()},
{NewDataLocation("file.nbf", ""), reflect.TypeOf((*nbf.NBFReader)(nil)).Elem(), reflect.TypeOf((*nbf.NBFWriter)(nil)).Elem()},
//{NewDataLocation("file.nbf", ""), reflect.TypeOf((*nbf.NBFReader)(nil)).Elem(), reflect.TypeOf((*nbf.NBFWriter)(nil)).Elem()},
}
ddb, root, fs := createRootAndFS()
@@ -158,7 +154,7 @@ func TestCreateRdWr(t *testing.T) {
if nomsWr, ok := wr.(noms.NomsMapWriteCloser); ok {
vrw := ddb.ValueReadWriter()
schVal, err := noms.MarshalAsNomsValue(vrw, nomsWr.GetSchema())
schVal, err := encoding.MarshalAsNomsValue(vrw, nomsWr.GetSchema())
if err != nil {
t.Fatal("Unable ta update table")

View File

@@ -1,13 +1,17 @@
package mvdata
import (
"fmt"
"errors"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/rowconv"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/jsonenc"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/funcitr"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/set"
"strings"
)
type MoveOperation string
@@ -78,11 +82,11 @@ func NewDataMover(root *doltdb.RootValue, fs filesys.Filesys, mvOpts *MoveOption
return nil, &DataMoverCreationError{SchemaErr, err}
}
var mapping *schema.FieldMapping
var mapping *rowconv.FieldMapping
if mvOpts.MappingFile != "" {
mapping, err = schema.MappingFromFile(mvOpts.MappingFile, fs, rd.GetSchema(), outSch)
mapping, err = rowconv.MappingFromFile(mvOpts.MappingFile, fs, rd.GetSchema(), outSch)
} else {
mapping, err = schema.NewInferredMapping(rd.GetSchema(), outSch)
mapping, err = rowconv.NewInferredMapping(rd.GetSchema(), outSch)
}
if err != nil {
@@ -132,8 +136,8 @@ func (imp *DataMover) Move() error {
return true
}
p, start := pipeline.NewAsyncPipeline(imp.Rd, imp.Transforms, imp.Wr, badRowCB)
start()
p := pipeline.NewAsyncPipeline(pipeline.ProcFuncForReader(imp.Rd), pipeline.ProcFuncForWriter(imp.Wr), imp.Transforms, badRowCB)
p.Start()
err := p.Wait()
@@ -144,30 +148,32 @@ func (imp *DataMover) Move() error {
return rowErr
}
func maybeMapFields(transforms *pipeline.TransformCollection, mapping *schema.FieldMapping) error {
rconv, err := table.NewRowConverter(mapping)
func maybeMapFields(transforms *pipeline.TransformCollection, mapping *rowconv.FieldMapping) error {
rconv, err := rowconv.NewRowConverter(mapping)
if err != nil {
return err
}
if !rconv.IdentityConverter {
transformer := pipeline.NewRowTransformer("Mapping transform", pipeline.GetRowConvTransformFunc(rconv))
transformer := pipeline.NewRowTransformer("Mapping transform", rowconv.GetRowConvTransformFunc(rconv))
transforms.AppendTransforms(pipeline.NamedTransform{Name: "map", Func: transformer})
}
return nil
}
func maybeSort(wr table.TableWriteCloser, outSch *schema.Schema, srcIsSorted bool, mvOpts *MoveOptions) (table.TableWriteCloser, error) {
func maybeSort(wr table.TableWriteCloser, outSch schema.Schema, srcIsSorted bool, mvOpts *MoveOptions) (table.TableWriteCloser, error) {
if !srcIsSorted && mvOpts.Dest.MustWriteSorted() {
wr = table.NewSortingTableWriter(wr, outSch.GetPKIndex(), mvOpts.ContOnErr)
//TODO: implement this
panic("Implement")
//wr = table.NewSortingTableWriter(wr, outSch.GetPKIndex(), mvOpts.ContOnErr)
}
return wr, nil
}
func getOutSchema(inSch *schema.Schema, root *doltdb.RootValue, fs filesys.ReadableFS, mvOpts *MoveOptions) (*schema.Schema, error) {
func getOutSchema(inSch schema.Schema, root *doltdb.RootValue, fs filesys.ReadableFS, mvOpts *MoveOptions) (schema.Schema, error) {
if mvOpts.Operation == UpdateOp {
// Get schema from target
rd, _, err := mvOpts.Dest.CreateReader(root, fs)
@@ -196,7 +202,7 @@ func getOutSchema(inSch *schema.Schema, root *doltdb.RootValue, fs filesys.Reada
}
func schFromFileOrDefault(path string, fs filesys.ReadableFS, defSch *schema.Schema) (*schema.Schema, error) {
func schFromFileOrDefault(path string, fs filesys.ReadableFS, defSch schema.Schema) (schema.Schema, error) {
if path != "" {
data, err := fs.ReadFile(path)
@@ -204,22 +210,46 @@ func schFromFileOrDefault(path string, fs filesys.ReadableFS, defSch *schema.Sch
return nil, err
}
return jsonenc.SchemaFromJSON(data)
return encoding.UnmarshalJson(string(data))
} else {
return defSch, nil
}
}
func addPrimaryKey(sch *schema.Schema, explicitKey string) (*schema.Schema, error) {
explicitKeyIdx := sch.GetFieldIndex(explicitKey)
func addPrimaryKey(sch schema.Schema, explicitKey string) (schema.Schema, error) {
if explicitKey != "" {
if explicitKeyIdx == -1 {
return nil, fmt.Errorf("could not find a field named \"%s\" in the schema", explicitKey)
} else {
sch = sch.CopyWithoutConstraints()
sch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{explicitKeyIdx}))
keyCols := strings.Split(explicitKey, ",")
trimmedCols := funcitr.MapStrings(keyCols, func(s string) string { return strings.TrimSpace(s) })
keyColSet := set.NewStrSet(trimmedCols)
foundPKCols := 0
var updatedCols []schema.Column
sch.GetAllCols().ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
if keyColSet.Contains(col.Name) {
foundPKCols++
col.IsPartOfPK = true
col.Constraints = []schema.ColConstraint{schema.NotNullConstraint{}}
} else {
col.IsPartOfPK = false
col.Constraints = nil
}
updatedCols = append(updatedCols, col)
return false
})
if keyColSet.Size() != foundPKCols {
return nil, errors.New("could not find all pks: " + explicitKey)
}
updatedColColl, err := schema.NewColCollection(updatedCols...)
if err != nil {
return nil, err
}
return schema.SchemaFromCols(updatedColColl), nil
}
return sch, nil

View File

@@ -1,6 +1,7 @@
package mvdata
import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema/encoding"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"testing"
)
@@ -28,7 +29,7 @@ func TestDataMover(t *testing.T) {
Src: NewDataLocation("data.csv", ""),
Dest: NewDataLocation("data.psv", "psv")},
},
{
/*{
"",
"",
&MoveOptions{
@@ -51,7 +52,7 @@ func TestDataMover(t *testing.T) {
PrimaryKey: "",
Src: NewDataLocation("data.nbf", "nbf"),
Dest: NewDataLocation("table-name", "")},
},
},*/
{
"",
"",
@@ -66,12 +67,19 @@ func TestDataMover(t *testing.T) {
},
{
`{
"fields": [
{"name": "key", "kind": "string", "required": true},
{"name": "value", "kind": "int", "required": true}
],
"constraints": [
{"constraint_type":"primary_key", "field_indices":[0]}
"columns": [
{
"name": "key",
"kind": "string",
"tag": 0,
"is_part_of_pk": true,
"col_constraints":[
{
"constraint_type": "not_null"
}
]
},
{"name": "value", "kind": "int", "tag": 1}
]
}`,
`{"a":"key","b":"value"}`,
@@ -118,6 +126,8 @@ func TestDataMover(t *testing.T) {
t.Fatal(err)
}
encoding.UnmarshalJson(test.schemaJSON)
dm, crDMErr := NewDataMover(root, fs, test.mvOpts)
if crDMErr != nil {

View File

@@ -0,0 +1,47 @@
package row
import (
"bytes"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
)
type RowFormatFunc func(r Row, sch schema.Schema) string
var Fmt = FieldSeparatedFmt(':')
var fieldDelim = []byte(" | ")
func FieldSeparatedFmt(delim rune) RowFormatFunc {
return func(r Row, sch schema.Schema) string {
if r == nil {
return "null"
}
allCols := sch.GetAllCols()
kvps := make([]string, 0, allCols.Size())
var backingBuffer [512]byte
buf := bytes.NewBuffer(backingBuffer[:0])
var ok bool
allCols.ItrInSortedOrder(func(tag uint64, col schema.Column) (stop bool) {
if ok {
buf.Write(fieldDelim)
}
var val types.Value
val, ok = r.GetColVal(tag)
if ok {
buf.Write([]byte(col.Name))
buf.WriteRune(delim)
types.WriteEncodedValue(buf, val)
kvps = append(kvps, buf.String())
}
return false
})
return buf.String()
}
}

View File

@@ -0,0 +1,122 @@
package row
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
)
type nomsRow struct {
key TaggedValues
value TaggedValues
}
func (nr nomsRow) IterCols(cb func(tag uint64, val types.Value) (stop bool)) bool {
stopped := nr.key.Iter(cb)
if !stopped {
stopped = nr.value.Iter(cb)
}
return stopped
}
func (nr nomsRow) GetColVal(tag uint64) (types.Value, bool) {
val, ok := nr.key.Get(tag)
if !ok {
val, ok = nr.value.Get(tag)
}
return val, ok
}
func (nr nomsRow) SetColVal(tag uint64, val types.Value, sch schema.Schema) (Row, error) {
rowKey := nr.key
rowVal := nr.value
cols := sch.GetAllCols()
col, ok := cols.GetByTag(tag)
if ok {
if col.IsPartOfPK {
rowKey = nr.key.Set(tag, val)
} else {
rowVal = nr.value.Set(tag, val)
}
return nomsRow{rowKey, rowVal}, nil
}
panic("can't set a column whose tag isn't in the schema. verify before calling this function.")
}
func New(sch schema.Schema, colVals TaggedValues) Row {
allCols := sch.GetAllCols()
keyVals := make(TaggedValues)
nonKeyVals := make(TaggedValues)
colVals.Iter(func(tag uint64, val types.Value) (stop bool) {
col, ok := allCols.GetByTag(tag)
if !ok {
panic("Trying to set a value on an unknown tag is a bug. Validation should happen upstream.")
} else if col.IsPartOfPK {
keyVals[tag] = val
} else {
nonKeyVals[tag] = val
}
return false
})
return FromTaggedVals(sch, keyVals, nonKeyVals)
}
func FromTaggedVals(sch schema.Schema, keyVals, nonKeyVals TaggedValues) Row {
allCols := sch.GetAllCols()
keyVals.Iter(func(tag uint64, val types.Value) (stop bool) {
col, ok := allCols.GetByTag(tag)
if !ok {
panic("Trying to set a value on an unknown tag is a bug. Validation should happen upstream. col:" + col.Name)
} else if !col.IsPartOfPK {
panic("writing columns that are not part of the primary key to pk values. col:" + col.Name)
} else if !types.IsNull(val) && col.Kind != val.Kind() {
panic("bug. Setting a value to an incorrect kind. col: " + col.Name)
}
return false
})
nonKeyVals.Iter(func(tag uint64, val types.Value) (stop bool) {
col, ok := allCols.GetByTag(tag)
if !ok {
panic("Trying to set a value on an unknown tag is a bug. Validation should happen upstream. col:" + col.Name)
} else if col.IsPartOfPK {
panic("writing columns that are part of the primary key to non-pk values. col:" + col.Name)
} else if !types.IsNull(val) && col.Kind != val.Kind() {
panic("bug. Setting a value to an incorrect kind. col:" + col.Name)
}
return false
})
return nomsRow{keyVals, nonKeyVals}
}
func FromNoms(sch schema.Schema, nomsKey, nomsVal types.Tuple) Row {
key := ParseTaggedValues(nomsKey)
val := ParseTaggedValues(nomsVal)
return FromTaggedVals(sch, key, val)
}
func (nr nomsRow) NomsMapKey(sch schema.Schema) types.Value {
return nr.key.NomsTupleForTags(sch.GetPKCols().Tags, true)
}
func (nr nomsRow) NomsMapValue(sch schema.Schema) types.Value {
return nr.value.NomsTupleForTags(sch.GetNonPKCols().SortedTags, false)
}

View File

@@ -0,0 +1,127 @@
package row
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"reflect"
"testing"
)
const (
lnColName = "last"
fnColName = "first"
addrColName = "address"
ageColName = "age"
titleColName = "title"
reservedColName = "reserved"
lnColTag = 1
fnColTag = 0
addrColTag = 6
ageColTag = 4
titleColTag = 40
reservedColTag = 50
)
var lnVal = types.String("astley")
var fnVal = types.String("rick")
var addrVal = types.String("123 Fake St")
var ageVal = types.Uint(53)
var titleVal = types.NullValue
var testKeyCols = []schema.Column{
{lnColName, lnColTag, types.StringKind, true, []schema.ColConstraint{schema.NotNullConstraint{}}},
{fnColName, fnColTag, types.StringKind, true, []schema.ColConstraint{schema.NotNullConstraint{}}},
}
var testCols = []schema.Column{
{addrColName, addrColTag, types.StringKind, false, nil},
{ageColName, ageColTag, types.UintKind, false, nil},
{titleColName, titleColTag, types.StringKind, false, nil},
{reservedColName, reservedColTag, types.StringKind, false, nil},
}
var testKeyColColl, _ = schema.NewColCollection(testKeyCols...)
var testNonKeyColColl, _ = schema.NewColCollection(testCols...)
var sch, _ = schema.SchemaFromPKAndNonPKCols(testKeyColColl, testNonKeyColColl)
func newTestRow() nomsRow {
key := TaggedValues{
fnColTag: fnVal,
lnColTag: lnVal,
}
val := TaggedValues{
addrColTag: addrVal,
ageColTag: ageVal,
titleColTag: titleVal,
}
return nomsRow{key, val}
}
func TestItrRowCols(t *testing.T) {
r := newTestRow()
itrVals := make(TaggedValues)
r.IterCols(func(tag uint64, val types.Value) (stop bool) {
itrVals[tag] = val
return false
})
matchesExpectation := reflect.DeepEqual(itrVals, TaggedValues{
lnColTag: lnVal,
fnColTag: fnVal,
ageColTag: ageVal,
addrColTag: addrVal,
titleColTag: titleVal,
})
if !matchesExpectation {
t.Error("Unexpected iteration results")
}
}
func validateRow(t *testing.T, r Row, expected TaggedValues) {
for expTag, expVal := range expected {
val, ok := r.GetColVal(expTag)
if !ok {
t.Error("missing value")
} else if val != nil && !val.Equals(expVal) {
t.Error(types.EncodedValue(val), "!=", types.EncodedValue(expVal))
}
}
val, ok := r.GetColVal(45667456)
if ok {
t.Error("Should not be ok")
} else if val != nil {
t.Error("missing value should be nil")
}
}
func TestRowSet(t *testing.T) {
updatedVal := types.String("sanchez")
expected := map[uint64]types.Value{
lnColTag: lnVal,
fnColTag: fnVal,
ageColTag: ageVal,
addrColTag: addrVal,
titleColTag: titleVal}
r := newTestRow()
validateRow(t, r, expected)
updated, err := r.SetColVal(lnColTag, updatedVal, sch)
if err != nil {
t.Error("failed to update:", err)
}
// validate calling set does not mutate the original row
validateRow(t, r, expected)
expected[lnColTag] = updatedVal
validateRow(t, updated, expected)
}

View File

@@ -0,0 +1,107 @@
package row
import (
"errors"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
)
var ErrRowNotValid = errors.New("invalid row for current schema.")
type Row interface {
NomsMapKey(sch schema.Schema) types.Value
NomsMapValue(sch schema.Schema) types.Value
IterCols(cb func(tag uint64, val types.Value) (stop bool)) bool
GetColVal(tag uint64) (types.Value, bool)
SetColVal(tag uint64, val types.Value, sch schema.Schema) (Row, error)
}
func GetFieldByName(colName string, r Row, sch schema.Schema) (types.Value, bool) {
col, ok := sch.GetAllCols().GetByName(colName)
if !ok {
panic("Requesting column that isn't in the schema. This is a bug. columns should be verified in the schema beforet attempted retrieval.")
} else {
return r.GetColVal(col.Tag)
}
}
func GetFieldByNameWithDefault(colName string, defVal types.Value, r Row, sch schema.Schema) types.Value {
col, ok := sch.GetAllCols().GetByName(colName)
if !ok {
panic("Requesting column that isn't in the schema. This is a bug. columns should be verified in the schema beforet attempted retrieval.")
} else {
val, ok := r.GetColVal(col.Tag)
if !ok {
return defVal
}
return val
}
}
func IsValid(r Row, sch schema.Schema) bool {
allCols := sch.GetAllCols()
valid := true
allCols.ItrInSortedOrder(func(tag uint64, col schema.Column) (stop bool) {
if len(col.Constraints) > 0 {
val, _ := r.GetColVal(tag)
for _, cnst := range col.Constraints {
if !cnst.SatisfiesConstraint(val) {
valid = false
return true
}
}
}
return false
})
return valid
}
func GetInvalidCol(r Row, sch schema.Schema) *schema.Column {
allCols := sch.GetAllCols()
var badCol *schema.Column
allCols.ItrInSortedOrder(func(tag uint64, col schema.Column) (stop bool) {
if len(col.Constraints) > 0 {
val, _ := r.GetColVal(tag)
for _, cnst := range col.Constraints {
if !cnst.SatisfiesConstraint(val) {
badCol = &col
return true
}
}
}
return false
})
return badCol
}
func AreEqual(row1, row2 Row, sch schema.Schema) bool {
if row1 == nil && row2 == nil {
return true
} else if row1 == nil || row2 == nil {
return false
}
for _, tag := range sch.GetAllCols().Tags {
val1, ok1 := row1.GetColVal(tag)
val2, ok2 := row2.GetColVal(tag)
if ok1 != ok2 || ok1 && !val1.Equals(val2) {
return false
}
}
return true
}

View File

@@ -0,0 +1,51 @@
package row
import (
"github.com/attic-labs/noms/go/types"
"testing"
)
func TestGetFieldByName(t *testing.T) {
r := newTestRow()
val, ok := GetFieldByName(lnColName, r, sch)
if !ok {
t.Error("Expected to find value")
} else if !val.Equals(lnVal) {
t.Error("Unexpected value")
}
val, ok = GetFieldByName(reservedColName, r, sch)
if ok {
t.Error("should not find missing key")
} else if val != nil {
t.Error("missing key should return null value")
}
}
func TestGetFieldByNameWithDefault(t *testing.T) {
r := newTestRow()
defVal := types.String("default")
val := GetFieldByNameWithDefault(lnColName, defVal, r, sch)
if !val.Equals(lnVal) {
t.Error("expected:", lnVal, "actual", val)
}
val = GetFieldByNameWithDefault(reservedColName, defVal, r, sch)
if !val.Equals(defVal) {
t.Error("expected:", defVal, "actual", val)
}
}
func TestIsValid(t *testing.T) {
r := newTestRow()
if !IsValid(r, sch) {
t.Error("Not valid")
}
}

View File

@@ -0,0 +1,82 @@
package row
import (
"github.com/attic-labs/noms/go/types"
)
type TaggedValues map[uint64]types.Value
func (tt TaggedValues) NomsTupleForTags(tags []uint64, encodeNulls bool) types.Tuple {
numVals := len(tags)
vals := make([]types.Value, 0, 2*numVals)
for _, tag := range tags {
val := tt[tag]
if val == nil && encodeNulls {
val = types.NullValue
}
if val != nil {
vals = append(vals, types.Uint(tag), val)
}
}
return types.NewTuple(vals...)
}
func (tt TaggedValues) Iter(cb func(tag uint64, val types.Value) (stop bool)) bool {
stop := false
for tag, val := range tt {
stop = cb(tag, val)
if stop {
break
}
}
return stop
}
func (tt TaggedValues) Get(tag uint64) (types.Value, bool) {
val, ok := tt[tag]
return val, ok
}
func (tt TaggedValues) Set(tag uint64, val types.Value) TaggedValues {
updated := tt.copy()
updated[tag] = val
return updated
}
func (tt TaggedValues) copy() TaggedValues {
newTagToVal := make(TaggedValues, len(tt))
for tag, val := range tt {
newTagToVal[tag] = val
}
return newTagToVal
}
func ParseTaggedValues(tpl types.Tuple) TaggedValues {
if tpl.Len()%2 != 0 {
panic("A tagged tuple must have an even column count.")
}
taggedTuple := make(TaggedValues, tpl.Len()/2)
for i := uint64(0); i < tpl.Len(); i += 2 {
tag := tpl.Get(i)
val := tpl.Get(i + 1)
if tag.Kind() != types.UintKind {
panic("Invalid tagged tuple must have uint tags.")
}
if val != types.NullValue {
taggedTuple[uint64(tag.(types.Uint))] = val
}
}
return taggedTuple
}

View File

@@ -0,0 +1,128 @@
package row
import (
"reflect"
"strconv"
"testing"
"github.com/attic-labs/noms/go/types"
)
func TestTaggedTuple_NomsTupleForTags(t *testing.T) {
tt := TaggedValues{
0: types.String("0"),
1: types.String("1"),
2: types.String("2")}
tests := []struct {
tags []uint64
encodeNulls bool
want types.Tuple
}{
{[]uint64{}, true, types.NewTuple()},
{[]uint64{1}, true, types.NewTuple(types.Uint(1), types.String("1"))},
{[]uint64{0, 1, 2}, true, types.NewTuple(types.Uint(0), types.String("0"), types.Uint(1), types.String("1"), types.Uint(2), types.String("2"))},
{[]uint64{2, 1, 0}, true, types.NewTuple(types.Uint(2), types.String("2"), types.Uint(1), types.String("1"), types.Uint(0), types.String("0"))},
{[]uint64{1, 3}, true, types.NewTuple(types.Uint(1), types.String("1"), types.Uint(3), types.NullValue)},
{[]uint64{1, 3}, false, types.NewTuple(types.Uint(1), types.String("1"))},
//{[]uint64{0, 1, 2}, types.NewTuple(types.Uint(0), types.String("0"), )},
//{map[uint64]types.Value{}, []uint64{}, types.NewTuple()},
//{map[uint64]types.Value{}, []uint64{}, types.NewTuple()},
}
for _, test := range tests {
if got := tt.NomsTupleForTags(test.tags, test.encodeNulls); !reflect.DeepEqual(got, test.want) {
t.Errorf("TaggedValues.NomsTupleForTags() = %v, want %v", types.EncodedValue(got), types.EncodedValue(test.want))
}
}
}
func TestTaggedTuple_Iter(t *testing.T) {
tt := TaggedValues{
1: types.String("1"),
2: types.String("2"),
3: types.String("3")}
var sum uint64
tt.Iter(func(tag uint64, val types.Value) (stop bool) {
sum += tag
tagStr := strconv.FormatUint(tag, 10)
if !types.String(tagStr).Equals(val) {
t.Errorf("Unexpected value for tag %d: %s", sum, string(val.(types.String)))
}
return false
})
if sum != 6 {
t.Error("Did not iterate all tags.")
}
}
func TestTaggedTuple_Get(t *testing.T) {
tt := TaggedValues{
1: types.String("1"),
2: types.String("2"),
3: types.String("3")}
tests := []struct {
tag uint64
want types.Value
found bool
}{
{1, types.String("1"), true},
{4, nil, false},
}
for _, test := range tests {
got, ok := tt.Get(test.tag)
if ok != test.found {
t.Errorf("expected to be found: %v, found: %v", ok, test.found)
} else if !reflect.DeepEqual(got, test.want) {
t.Errorf("TaggedValues.Get() = %s, want %s", types.EncodedValue(got), types.EncodedValue(test.want))
}
}
}
func TestTaggedTuple_Set(t *testing.T) {
tests := []struct {
tag uint64
val types.Value
want TaggedValues
}{
{1, types.String("one"), TaggedValues{1: types.String("one"), 2: types.String("2"), 3: types.String("3")}},
{0, types.String("0"), TaggedValues{0: types.String("0"), 1: types.String("1"), 2: types.String("2"), 3: types.String("3")}},
}
for _, test := range tests {
tt := TaggedValues{
1: types.String("1"),
2: types.String("2"),
3: types.String("3")}
if got := tt.Set(test.tag, test.val); !reflect.DeepEqual(got, test.want) {
t.Errorf("TaggedValues.Set() = %v, want %v", got, test.want)
}
}
}
func TestParseTaggedTuple(t *testing.T) {
tests := []struct {
tpl types.Tuple
want TaggedValues
}{
{
types.NewTuple(),
TaggedValues{},
},
{
types.NewTuple(types.Uint(0), types.String("0")),
TaggedValues{0: types.String("0")},
},
{
types.NewTuple(types.Uint(0), types.String("0"), types.Uint(5), types.Uint(5), types.Uint(60), types.Int(60)),
TaggedValues{0: types.String("0"), 5: types.Uint(5), 60: types.Int(60)},
},
}
for _, test := range tests {
if got := ParseTaggedValues(test.tpl); !reflect.DeepEqual(got, test.want) {
t.Errorf("ParseTaggedValues() = %v, want %v", got, test.want)
}
}
}

View File

@@ -0,0 +1,136 @@
package rowconv
import (
"encoding/json"
"errors"
"fmt"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"strconv"
)
var ErrPrimaryKeyNotMapped = errors.New("primary key not mapped")
var ErrMappingFileRead = errors.New("error reading mapping file")
var ErrUnmarshallingMapping = errors.New("error unmarshalling mapping")
var ErrEmptyMapping = errors.New("empty mapping error")
type BadMappingErr struct {
srcField string
destField string
}
func (err *BadMappingErr) Error() string {
return fmt.Sprintf("Mapping file attempted to map %s to %s, but one or both of those fields are unknown.", err.srcField, err.destField)
}
var ErrBadMapping = errors.New("error bad mapping")
func IsBadMappingErr(err error) bool {
_, ok := err.(*BadMappingErr)
return ok
}
type FieldMapping struct {
SrcSch schema.Schema
DestSch schema.Schema
SrcToDest map[uint64]uint64
}
func NewFieldMapping(inSch, outSch schema.Schema, srcTagToDestTag map[uint64]uint64) (*FieldMapping, error) {
inCols := inSch.GetAllCols()
outCols := outSch.GetAllCols()
for srcTag, destTag := range srcTagToDestTag {
_, destOk := outCols.GetByTag(destTag)
_, srcOk := inCols.GetByTag(srcTag)
if !destOk || !srcOk {
return nil, &BadMappingErr{"src tag:" + strconv.FormatUint(srcTag, 10), "dest tag:" + strconv.FormatUint(destTag, 10)}
}
}
if len(srcTagToDestTag) == 0 {
return nil, ErrEmptyMapping
}
return &FieldMapping{inSch, outSch, srcTagToDestTag}, nil
}
func NewFieldMappingFromNameMap(inSch, outSch schema.Schema, inNameToOutName map[string]string) (*FieldMapping, error) {
inCols := inSch.GetAllCols()
outCols := outSch.GetAllCols()
srcToDest := make(map[uint64]uint64, len(inNameToOutName))
for k, v := range inNameToOutName {
inCol, inOk := inCols.GetByName(k)
outCol, outOk := outCols.GetByName(v)
if !inOk || !outOk {
return nil, &BadMappingErr{k, v}
}
srcToDest[inCol.Tag] = outCol.Tag
}
return NewFieldMapping(inSch, outSch, srcToDest)
}
func NewInferredMapping(inSch, outSch schema.Schema) (*FieldMapping, error) {
successes := 0
inCols := inSch.GetAllCols()
outCols := outSch.GetAllCols()
srcToDest := make(map[uint64]uint64, outCols.Size())
outCols.ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
inCol, ok := inCols.GetByTag(tag)
if ok {
srcToDest[inCol.Tag] = tag
successes++
}
return false
})
if successes == 0 {
return nil, ErrEmptyMapping
}
return NewFieldMapping(inSch, outSch, srcToDest)
}
func MappingFromFile(mappingFile string, fs filesys.ReadableFS, inSch, outSch schema.Schema) (*FieldMapping, error) {
data, err := fs.ReadFile(mappingFile)
if err != nil {
return nil, ErrMappingFileRead
}
var inNameToOutName map[string]string
err = json.Unmarshal(data, &inNameToOutName)
if err != nil {
return nil, ErrUnmarshallingMapping
}
return NewFieldMappingFromNameMap(inSch, outSch, inNameToOutName)
}
func TypedToUntypedMapping(sch schema.Schema) *FieldMapping {
untypedSch := untyped.UntypeSchema(sch)
identityMap := make(map[uint64]uint64)
sch.GetAllCols().ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
identityMap[tag] = tag
return false
})
mapping, err := NewFieldMapping(sch, untypedSch, identityMap)
if err != nil {
panic(err)
}
return mapping
}

View File

@@ -0,0 +1,95 @@
package rowconv
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"reflect"
"testing"
)
var fieldsA, _ = schema.NewColCollection(
schema.NewColumn("a", 0, types.StringKind, false),
schema.NewColumn("b", 1, types.StringKind, false),
schema.NewColumn("c", 2, types.StringKind, false))
var fieldsB, _ = schema.NewColCollection(
schema.NewColumn("a", 0, types.StringKind, false),
schema.NewColumn("b", 1, types.StringKind, false))
var fieldsC, _ = schema.NewColCollection(
schema.NewColumn("key", 3, types.UUIDKind, true),
schema.NewColumn("value", 4, types.StringKind, false))
var fieldsCNoPK, _ = schema.NewColCollection(
schema.NewColumn("key", 3, types.UUIDKind, false),
schema.NewColumn("value", 4, types.StringKind, false))
var fieldsD, _ = schema.NewColCollection(
schema.NewColumn("key", 3, types.StringKind, false),
schema.NewColumn("value", 4, types.StringKind, false))
var schemaA = schema.SchemaFromCols(fieldsA)
var schemaB = schema.SchemaFromCols(fieldsB)
var schemaC = schema.SchemaFromCols(fieldsC)
var schemaCNoPK = schema.SchemaFromCols(fieldsCNoPK)
var schemaD = schema.SchemaFromCols(fieldsD)
func TestFieldMapping(t *testing.T) {
tests := []struct {
mappingJSON string
inSch schema.Schema
outSch schema.Schema
expectErr bool
expected map[uint64]uint64
identity bool
}{
{"", schemaA, schemaA, false, map[uint64]uint64{0: 0, 1: 1, 2: 2}, true},
{"", schemaA, schemaB, false, map[uint64]uint64{0: 0, 1: 1}, false},
{"", schemaB, schemaA, false, map[uint64]uint64{0: 0, 1: 1}, false},
{"", schemaA, schemaC, true, nil, false},
{`{"invalid_json": }`, schemaA, schemaC, true, nil, false},
{`{"b": "value"}`, schemaA, schemaC, false, map[uint64]uint64{1: 4}, false},
{`{"c": "key", "b": "value"}`, schemaA, schemaC, false, map[uint64]uint64{2: 3, 1: 4}, false},
{`{"a": "key", "b": "value"}`, schemaA, schemaC, false, map[uint64]uint64{0: 3, 1: 4}, false},
{`{"a": "key", "b": "value"}`, schemaB, schemaC, false, map[uint64]uint64{0: 3, 1: 4}, false},
{`{"a": "key", "b": "value"}`, schemaB, schemaCNoPK, false, map[uint64]uint64{0: 3, 1: 4}, false},
{`{"a": "key", "b": "value"}`, schemaB, schemaD, false, map[uint64]uint64{0: 3, 1: 4}, true},
}
for _, test := range tests {
fs := filesys.NewInMemFS([]string{"/"}, nil, "/")
mappingFile := ""
if test.mappingJSON != "" {
mappingFile = "mapping.json"
fs.WriteFile(mappingFile, []byte(test.mappingJSON))
}
var mapping *FieldMapping
var err error
if mappingFile != "" {
mapping, err = MappingFromFile(mappingFile, fs, test.inSch, test.outSch)
} else {
mapping, err = NewInferredMapping(test.inSch, test.outSch)
}
if (err != nil) != test.expectErr {
if test.expectErr {
t.Fatal("Expected an error that didn't come.")
} else {
t.Fatal("Unexpected error creating mapping.", err)
}
}
if !test.expectErr {
if !reflect.DeepEqual(mapping.SrcToDest, test.expected) {
t.Error("Mapping does not match expected. Expected:", test.expected, "Actual:", mapping.SrcToDest)
}
//if test.identity != mapping.IsIdentityMapping() {
// t.Error("identity expected", test.identity, "actual:", !test.identity)
//}
}
}
}

View File

@@ -0,0 +1,151 @@
package rowconv
import (
"fmt"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/pantoerr"
)
var IdentityConverter = &RowConverter{nil, true, nil}
type RowConverter struct {
*FieldMapping
IdentityConverter bool
convFuncs map[uint64]doltcore.ConvFunc
}
func NewRowConverter(mapping *FieldMapping) (*RowConverter, error) {
if !isNecessary(mapping.SrcSch, mapping.DestSch, mapping.SrcToDest) {
return IdentityConverter, nil
}
convFuncs := make(map[uint64]doltcore.ConvFunc, len(mapping.SrcToDest))
for srcTag, destTag := range mapping.SrcToDest {
destCol, destOk := mapping.DestSch.GetAllCols().GetByTag(destTag)
srcCol, srcOk := mapping.SrcSch.GetAllCols().GetByTag(srcTag)
if !destOk || !srcOk {
return nil, fmt.Errorf("Colud not find column being mapped. src tag: %d, dest tag: %d", srcTag, destTag)
}
convFuncs[srcTag] = doltcore.GetConvFunc(srcCol.Kind, destCol.Kind)
if convFuncs[srcTag] == nil {
return nil, fmt.Errorf("Unsupported conversion from type %s to %s", srcCol.KindString(), destCol.KindString())
}
}
return &RowConverter{mapping, false, convFuncs}, nil
}
func (rc *RowConverter) Convert(inRow row.Row) (row.Row, error) {
if rc.IdentityConverter {
return inRow, nil
}
outTaggedVals := make(row.TaggedValues, len(rc.SrcToDest))
err := pantoerr.PanicToError("error converting row", func() error {
var convErr error
inRow.IterCols(func(tag uint64, val types.Value) (stop bool) {
convFunc, ok := rc.convFuncs[tag]
if ok {
outTag := rc.SrcToDest[tag]
outVal, err := convFunc(val)
if err != nil {
fmt.Println()
fmt.Println(types.EncodedValue(val))
convErr = err
return true
}
outTaggedVals[outTag] = outVal
}
return false
})
return convErr
})
if err != nil {
return nil, err
}
outRow := row.New(rc.DestSch, outTaggedVals)
return outRow, nil
}
func isNecessary(srcSch, destSch schema.Schema, destToSrc map[uint64]uint64) bool {
srcCols := srcSch.GetAllCols()
destCols := destSch.GetAllCols()
if len(destToSrc) != srcCols.Size() || len(destToSrc) != destCols.Size() {
return true
}
for k, v := range destToSrc {
if k != v {
return true
}
srcCol, srcOk := srcCols.GetByTag(v)
destCol, destOk := destCols.GetByTag(k)
if !srcOk || !destOk {
panic("There is a bug. FieldMapping creation should prevent this from happening")
}
if srcCol.IsPartOfPK != destCol.IsPartOfPK {
return true
}
if srcCol.Kind != destCol.Kind {
return true
}
}
srcPKCols := srcSch.GetPKCols()
destPKCols := destSch.GetPKCols()
if srcPKCols.Size() != destPKCols.Size() {
return true
}
i := 0
destPKCols.ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
srcPKCol := srcPKCols.GetByUnsortedIndex(i)
if srcPKCol.Tag != col.Tag {
return true
}
i++
return false
})
return false
}
func GetRowConvTransformFunc(rc *RowConverter) func(row.Row, pipeline.ReadableMap) ([]*pipeline.TransformedRowResult, string) {
return func(inRow row.Row, props pipeline.ReadableMap) (outRows []*pipeline.TransformedRowResult, badRowDetails string) {
outRow, err := rc.Convert(inRow)
if err != nil {
return nil, err.Error()
}
if !row.IsValid(outRow, rc.DestSch) {
col := row.GetInvalidCol(outRow, rc.DestSch)
return nil, "invalid column: " + col.Name
}
return []*pipeline.TransformedRowResult{{outRow, nil}}, ""
}
}

View File

@@ -0,0 +1,76 @@
package rowconv
import (
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"testing"
)
func TestRowConverter(t *testing.T) {
srcCols, _ := schema.NewColCollection(
schema.NewColumn("uuidtostr", 0, types.UUIDKind, true),
schema.NewColumn("floattostr", 1, types.FloatKind, false),
schema.NewColumn("uinttostr", 2, types.UintKind, false),
schema.NewColumn("booltostr", 3, types.BoolKind, false),
schema.NewColumn("inttostr", 4, types.IntKind, false),
schema.NewColumn("stringtostr", 5, types.StringKind, false),
schema.NewColumn("nulltostr", 6, types.NullKind, false),
)
destCols, _ := schema.NewColCollection(
schema.NewColumn("uuidToStr", 0, types.StringKind, true),
schema.NewColumn("floatToStr", 1, types.StringKind, false),
schema.NewColumn("uintToStr", 2, types.StringKind, false),
schema.NewColumn("boolToStr", 3, types.StringKind, false),
schema.NewColumn("intToStr", 4, types.StringKind, false),
schema.NewColumn("stringToStr", 5, types.StringKind, false),
schema.NewColumn("nullToStr", 6, types.StringKind, false),
)
srcSch := schema.SchemaFromCols(srcCols)
destSch := schema.SchemaFromCols(destCols)
mapping, err := NewInferredMapping(srcSch, destSch)
if err != nil {
t.Fatal("Err creating field oldNameToSchema2Name")
}
rConv, err := NewRowConverter(mapping)
if err != nil {
t.Fatal("Error creating row converter")
}
id, _ := uuid.NewRandom()
inRow := row.New(srcSch, row.TaggedValues{
0: types.UUID(id),
1: types.Float(1.25),
2: types.Uint(12345678),
3: types.Bool(true),
4: types.Int(-1234),
5: types.String("string string string"),
6: types.NullValue,
})
outData, err := rConv.Convert(inRow)
if err != nil {
t.Fatal(err)
}
expected := row.New(destSch, row.TaggedValues{
0: types.String(id.String()),
1: types.String("1.25"),
2: types.String("12345678"),
3: types.String("true"),
4: types.String("-1234"),
5: types.String("string string string"),
6: types.NullValue,
})
if !row.AreEqual(outData, expected, destSch) {
t.Error("\n", row.Fmt(expected, destSch), "!=\n", row.Fmt(outData, destSch))
}
}

View File

@@ -0,0 +1,105 @@
package schema
import (
"errors"
"sort"
)
var ErrColTagCollision = errors.New("two different columns with the same tag.")
var EmptyColColl, _ = NewColCollection()
type ColCollection struct {
cols []Column
Tags []uint64
SortedTags []uint64
TagToCol map[uint64]Column
NameToCol map[string]Column
}
func NewColCollection(cols ...Column) (*ColCollection, error) {
var tags []uint64
var sortedTags []uint64
tagToCol := make(map[uint64]Column, len(cols))
nameToCol := make(map[string]Column, len(cols))
var uniqueCols []Column
for i, col := range cols {
if val, ok := tagToCol[col.Tag]; !ok {
uniqueCols = append(uniqueCols, col)
tagToCol[col.Tag] = col
tags = append(tags, col.Tag)
sortedTags = append(sortedTags, col.Tag)
nameToCol[col.Name] = cols[i]
} else if !val.Equals(col) {
return nil, ErrColTagCollision
}
}
sort.Slice(sortedTags, func(i, j int) bool { return sortedTags[i] < sortedTags[j] })
return &ColCollection{uniqueCols, tags, sortedTags, tagToCol, nameToCol}, nil
}
func (cc *ColCollection) AppendColl(colColl *ColCollection) (*ColCollection, error) {
return cc.Append(colColl.cols...)
}
func (cc *ColCollection) Append(cols ...Column) (*ColCollection, error) {
allCols := make([]Column, 0, len(cols)+len(cc.cols))
allCols = append(allCols, cols...)
allCols = append(allCols, cc.cols...)
return NewColCollection(allCols...)
}
func (cc *ColCollection) ItrUnsorted(cb func(tag uint64, col Column) (stop bool)) {
for _, col := range cc.cols {
stop := cb(col.Tag, col)
if stop {
break
}
}
}
func (cc *ColCollection) ItrInSortedOrder(cb func(tag uint64, col Column) (stop bool)) {
for _, tag := range cc.SortedTags {
val := cc.TagToCol[tag]
stop := cb(tag, val)
if stop {
break
}
}
}
func (cc *ColCollection) GetByName(name string) (Column, bool) {
val, ok := cc.NameToCol[name]
if ok {
return val, true
}
return InvalidCol, false
}
func (cc *ColCollection) GetByTag(tag uint64) (Column, bool) {
val, ok := cc.TagToCol[tag]
if ok {
return val, true
}
return InvalidCol, false
}
func (cc *ColCollection) GetByUnsortedIndex(idx int) Column {
return cc.cols[idx]
}
func (cc *ColCollection) Size() int {
return len(cc.cols)
}

View File

@@ -0,0 +1,92 @@
package schema
import (
"github.com/attic-labs/noms/go/types"
"math"
"reflect"
"strconv"
"testing"
)
var firstNameCol = Column{"first", 0, types.StringKind, false, nil}
var lastNameCol = Column{"last", 1, types.StringKind, false, nil}
func TestGetByNameAndTag(t *testing.T) {
cols := []Column{firstNameCol, lastNameCol}
colColl, _ := NewColCollection(cols...)
tests := []struct {
name string
tag uint64
expected Column
shouldBeOk bool
}{
{firstNameCol.Name, firstNameCol.Tag, firstNameCol, true},
{lastNameCol.Name, lastNameCol.Tag, lastNameCol, true},
{"missing", math.MaxUint64, InvalidCol, false},
}
for _, test := range tests {
actual, ok := colColl.GetByName(test.name)
if ok != test.shouldBeOk {
t.Errorf("name - shouldBeOk: %v, ok: %v", test.shouldBeOk, ok)
} else if !reflect.DeepEqual(actual, test.expected) {
t.Errorf("name - %v != %v", actual, test.expected)
}
actual, ok = colColl.GetByTag(test.tag)
if ok != test.shouldBeOk {
t.Errorf("tag - shouldBeOk: %v, ok: %v", test.shouldBeOk, ok)
} else if !reflect.DeepEqual(actual, test.expected) {
t.Errorf("tag - %v != %v", actual, test.expected)
}
}
}
func TestAppendAndItrInSortOrder(t *testing.T) {
cols := []Column{
{"0", 0, types.StringKind, false, nil},
{"2", 2, types.StringKind, false, nil},
{"4", 4, types.StringKind, false, nil},
{"3", 3, types.StringKind, false, nil},
{"1", 1, types.StringKind, false, nil},
}
cols2 := []Column{
{"7", 7, types.StringKind, false, nil},
{"9", 9, types.StringKind, false, nil},
{"5", 5, types.StringKind, false, nil},
{"8", 8, types.StringKind, false, nil},
{"6", 6, types.StringKind, false, nil},
}
colColl, _ := NewColCollection(cols...)
validateItrInSortOrder(len(cols), colColl, t)
colColl2, _ := colColl.Append(cols2...)
validateItrInSortOrder(len(cols), colColl, t) //validate immutability
validateItrInSortOrder(len(cols)+len(cols2), colColl2, t)
}
func validateItrInSortOrder(numCols int, colColl *ColCollection, t *testing.T) {
if numCols != colColl.Size() {
t.Error("missing data")
}
var idx uint64
colColl.ItrInSortedOrder(func(tag uint64, col Column) (stop bool) {
if idx != tag {
t.Error("Not in order")
} else if col.Name != strconv.FormatUint(idx, 10) || col.Tag != tag {
t.Errorf("tag:%d - %v", tag, col)
}
idx++
return false
})
if idx != uint64(numCols) {
t.Error("Did not iterate over all values")
}
}

View File

@@ -0,0 +1,60 @@
package schema
import (
"github.com/attic-labs/noms/go/types"
"math"
"strings"
)
// KindToLwrStr maps a noms kind to the kinds lowercased name
var KindToLwrStr = make(map[types.NomsKind]string)
// LwrStrToKind maps a lowercase string to the noms kind it is referring to
var LwrStrToKind = make(map[string]types.NomsKind)
func init() {
for t, s := range types.KindToString {
KindToLwrStr[t] = strings.ToLower(s)
LwrStrToKind[strings.ToLower(s)] = t
}
}
var InvalidTag uint64 = math.MaxUint64
var ReservedTagMin uint64 = 1 << 63
var InvalidCol = NewColumn("invalid", InvalidTag, types.NullKind, false)
type Column struct {
Name string
Tag uint64
Kind types.NomsKind
IsPartOfPK bool
Constraints []ColConstraint
}
func NewColumn(name string, tag uint64, kind types.NomsKind, partOfPK bool, constraints ...ColConstraint) Column {
for _, c := range constraints {
if c == nil {
panic("nil passed as a constraint")
}
}
return Column{
name,
tag,
kind,
partOfPK,
constraints,
}
}
func (c Column) Equals(other Column) bool {
return c.Name == other.Name &&
c.Tag == other.Tag &&
c.Kind == other.Kind &&
c.IsPartOfPK == other.IsPartOfPK &&
ColConstraintsAreEqual(c.Constraints, other.Constraints)
}
func (c Column) KindString() string {
return KindToLwrStr[c.Kind]
}

View File

@@ -0,0 +1,71 @@
package schema
import "github.com/attic-labs/noms/go/types"
type ColConstraint interface {
SatisfiesConstraint(value types.Value) bool
GetConstraintType() string
GetConstraintParams() map[string]string
}
const (
NotNullConstraintType = "not_null"
)
func ColConstraintFromTypeAndParams(colCnstType string, params map[string]string) ColConstraint {
switch colCnstType {
case NotNullConstraintType:
return NotNullConstraint{}
}
panic("Unknown column constraint type: " + colCnstType)
}
type NotNullConstraint struct{}
func (nnc NotNullConstraint) SatisfiesConstraint(value types.Value) bool {
return !types.IsNull(value)
}
func (nnc NotNullConstraint) GetConstraintType() string {
return NotNullConstraintType
}
func (nnc NotNullConstraint) GetConstraintParams() map[string]string {
return nil
}
func ColConstraintsAreEqual(a, b []ColConstraint) bool {
if len(a) != len(b) {
return false
} else if len(a) == 0 {
return true
}
// kinda shitty. Probably shouldn't require order to be identital
for i := 0; i < len(a); i++ {
ca, cb := a[i], b[i]
if ca.GetConstraintType() != cb.GetConstraintType() {
return false
} else {
pa := ca.GetConstraintParams()
pb := cb.GetConstraintParams()
if len(pa) != len(pb) {
return false
} else if len(pa) != 0 {
for k, va := range pa {
vb, ok := pb[k]
if !ok {
return false
} else if va != vb {
return false
}
}
}
}
}
return true
}

View File

@@ -1,65 +0,0 @@
package schema
// ConstraintType is a type of constraint you are forcing on a schema.
type ConstraintType string;
// ConstraintFromString converts a string to a constraint.
func ConstraintFromString(str string) ConstraintType {
switch str {
case string(PrimaryKey):
return PrimaryKey
}
return Invalid
}
// String gives the readable string representation of the constraint type.
func (c ConstraintType) String() string{
return string(c)
}
const (
// Primary Key constraints define a single field that all rows will be keyed off of. PrimaryKey constraints are
// required on rows that are being written to nbf files, or dolt tables in noms.
PrimaryKey ConstraintType = "primary_key"
// Invalid represents an invalid constraint usually when the wrong string name for a constraint was used.
Invalid ConstraintType = "invalid"
)
// ConstraintTypes is a slice containing all the valid values of ConstraintType.
var ConstraintTypes = []ConstraintType{PrimaryKey}
// Constraint is made up of a Constraint type, and a list of fields being referenced by the constraint.
type Constraint struct {
ct ConstraintType
fieldIndices []int
}
// NewConstraint creates a Constraint instance after validating the parameters.
func NewConstraint(ct ConstraintType, fldInds []int) *Constraint {
if ct == Invalid {
panic("Invalid constraint type")
} else if len(fldInds) == 0 {
panic("Constraint on no fields not allowed.")
}
switch ct {
case PrimaryKey:
if len(fldInds) != 1 {
panic("Primary key must be on a single column.")
}
}
return &Constraint{ct, fldInds}
}
// ConType gets the ConstraintType for a Constraint.
func (c *Constraint) ConType() ConstraintType{
return c.ct
}
// FieldIndices gets a slice of field indices referenced by the constraint.
func (c *Constraint) FieldIndices() []int {
return c.fieldIndices
}

View File

@@ -1,27 +0,0 @@
package schema
import "testing"
func TestConstraintTypes(t *testing.T) {
tests := map[string]ConstraintType {
"primary_key": PrimaryKey,
"nonsense string": Invalid,
}
for str, expected := range tests {
if ConstraintFromString(str) != expected {
t.Error(str, "did not map to the expected value")
}
}
reverseTests := map[ConstraintType]string {
PrimaryKey: "primary_key",
Invalid: "invalid",
}
for c, expectedStr := range reverseTests {
if c.String() != expectedStr {
t.Error(c.String(), "Is not the expected string for this constraint")
}
}
}

View File

@@ -1,2 +0,0 @@
// Package schema defines the way in which we describe table data, and the terminology used by dolt.
package schema

View File

@@ -0,0 +1,163 @@
package encoding
import (
"encoding/json"
"errors"
"github.com/attic-labs/noms/go/marshal"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
)
type encodedColumn struct {
Tag uint64 `noms:"tag" json:"tag"`
// Name is the name of the field
Name string `noms:"name" json:"name"`
// Kind is the type of the field. See types/noms_kind.go in the liquidata fork for valid values
Kind string `noms:"kind" json:"kind"`
IsPartOfPK bool `noms:"is_part_of_pk" json:"is_part_of_pk"`
Constraints []encodedConstraint `noms:"col_constraints" json:"col_constraints"`
}
func encodeAllColConstraints(constraints []schema.ColConstraint) []encodedConstraint {
nomsConstraints := make([]encodedConstraint, len(constraints))
for i, c := range constraints {
nomsConstraints[i] = encodeColConstraint(c)
}
return nomsConstraints
}
func decodeAllColConstraint(encConstraints []encodedConstraint) []schema.ColConstraint {
if len(encConstraints) == 0 {
return nil
}
constraints := make([]schema.ColConstraint, len(encConstraints))
for i, nc := range encConstraints {
c := schema.ColConstraintFromTypeAndParams(nc.Type, nc.Params)
constraints[i] = c
}
return constraints
}
func encodeColumn(col schema.Column) encodedColumn {
return encodedColumn{
col.Tag,
col.Name,
col.KindString(),
col.IsPartOfPK,
encodeAllColConstraints(col.Constraints)}
}
func (nfd encodedColumn) decodeColumn() schema.Column {
colConstraints := decodeAllColConstraint(nfd.Constraints)
return schema.NewColumn(nfd.Name, nfd.Tag, schema.LwrStrToKind[nfd.Kind], nfd.IsPartOfPK, colConstraints...)
}
type encodedConstraint struct {
Type string `noms:"constraint_type" json:"constraint_type"`
Params map[string]string `noms:"params" json:"params"`
}
func encodeColConstraint(constraint schema.ColConstraint) encodedConstraint {
return encodedConstraint{constraint.GetConstraintType(), constraint.GetConstraintParams()}
}
func (encCnst encodedConstraint) decodeColConstraint() schema.ColConstraint {
return schema.ColConstraintFromTypeAndParams(encCnst.Type, encCnst.Params)
}
type schemaData struct {
Columns []encodedColumn `noms:"columns" json:"columns"`
}
func toSchemaData(sch schema.Schema) schemaData {
allCols := sch.GetAllCols()
encCols := make([]encodedColumn, allCols.Size())
i := 0
allCols.ItrUnsorted(func(tag uint64, col schema.Column) (stop bool) {
encCols[i] = encodeColumn(col)
i++
return false
})
return schemaData{encCols}
}
func (sd schemaData) decodeSchema() (schema.Schema, error) {
numCols := len(sd.Columns)
cols := make([]schema.Column, numCols)
for i, col := range sd.Columns {
cols[i] = col.decodeColumn()
}
colColl, err := schema.NewColCollection(cols...)
if err != nil {
return nil, err
}
return schema.SchemaFromCols(colColl), nil
}
// MarshalAsNomsValue takes a Schema and converts it to a types.Value
func MarshalAsNomsValue(vrw types.ValueReadWriter, sch schema.Schema) (types.Value, error) {
sd := toSchemaData(sch)
val, err := marshal.Marshal(vrw, sd)
if err != nil {
return types.EmptyStruct, err
}
if _, ok := val.(types.Struct); ok {
return val, nil
}
return types.EmptyStruct, errors.New("Table Schema could not be converted to types.Struct")
}
// UnmarshalNomsValue takes a types.Value instance and Unmarshalls it into a Schema.
func UnmarshalNomsValue(schemaVal types.Value) (schema.Schema, error) {
var sd schemaData
err := marshal.Unmarshal(schemaVal, &sd)
if err != nil {
return nil, err
}
return sd.decodeSchema()
}
// MarshalAsJson takes a Schema and returns a string containing it's json encoding
func MarshalAsJson(sch schema.Schema) (string, error) {
sd := toSchemaData(sch)
jsonStr, err := json.MarshalIndent(sd, "", " ")
if err != nil {
return "", err
}
return string(jsonStr), nil
}
// UnmarshalJson takes a json string and Unmarshalls it into a Schema.
func UnmarshalJson(jsonStr string) (schema.Schema, error) {
var sd schemaData
err := json.Unmarshal([]byte(jsonStr), &sd)
if err != nil {
return nil, err
}
return sd.decodeSchema()
}

View File

@@ -0,0 +1,65 @@
package encoding
import (
"github.com/attic-labs/noms/go/spec"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"reflect"
"testing"
)
func createTestSchema() schema.Schema {
columns := []schema.Column{
schema.NewColumn("id", 4, types.UUIDKind, true, schema.NotNullConstraint{}),
schema.NewColumn("first", 1, types.StringKind, false),
schema.NewColumn("last", 2, types.StringKind, false, schema.NotNullConstraint{}),
schema.NewColumn("age", 3, types.UintKind, false),
}
colColl, _ := schema.NewColCollection(columns...)
sch := schema.SchemaFromCols(colColl)
return sch
}
func TestNomsMarshalling(t *testing.T) {
tSchema := createTestSchema()
dbSpec, err := spec.ForDatabase("mem")
if err != nil {
t.Fatal("Could not create in mem noms db.")
}
db := dbSpec.GetDatabase()
val, err := MarshalAsNomsValue(db, tSchema)
if err != nil {
t.Fatal("Failed to marshal Schema as a types.Value.")
}
unMarshalled, err := UnmarshalNomsValue(val)
if err != nil {
t.Fatal("Failed to unmarshal types.Value as Schema")
}
if !reflect.DeepEqual(tSchema, unMarshalled) {
t.Error("Value different after marshalling and unmarshalling.")
}
jsonStr, err := MarshalAsJson(tSchema)
if err != nil {
t.Fatal("Failed to marshal Schema as a types.Value.")
}
jsonUnmarshalled, err := UnmarshalJson(jsonStr)
if err != nil {
t.Fatal("Failed to unmarshal types.Value as Schema")
}
if !reflect.DeepEqual(tSchema, jsonUnmarshalled) {
t.Error("Value different after marshalling and unmarshalling.")
}
}

View File

@@ -1,62 +0,0 @@
package schema
import (
"strings"
"github.com/attic-labs/noms/go/types"
)
// KindToLwrStr maps a noms kind to the kinds lowercased name
var KindToLwrStr = make(map[types.NomsKind]string)
// LwrStrToKind maps a lowercase string to the noms kind it is referring to
var LwrStrToKind = make(map[string]types.NomsKind)
func init() {
for t, s := range types.KindToString {
KindToLwrStr[t] = strings.ToLower(s)
LwrStrToKind[strings.ToLower(s)] = t
}
}
// Field represents a column within a table
type Field struct {
// name is the name of the field
name string
// kind is the type of the field. See types/noms_kind.go in the liquidata fork for valid values
kind types.NomsKind
// required tells whether all rows require this value to be considered valid
required bool
}
// NewField creates a new instance of Field from a name, type, and a flag saying whether it is required
func NewField(name string, kind types.NomsKind, required bool) *Field {
return &Field{strings.ToLower(name), kind, required}
}
// Equals returns true if all members are equal
func (fld *Field) Equals(other *Field) bool {
return fld.name == other.name && fld.kind == other.kind && fld.required == other.required
}
// NameStr returns the name of the field
func (fld *Field) NameStr() string {
return fld.name
}
// NomsKind returns the kind of the field
func (fld *Field) NomsKind() types.NomsKind {
return fld.kind
}
// KindString returns the lower case friendly name of the field's type
func (fld *Field) KindString() string {
return KindToLwrStr[fld.kind]
}
// IsRequired tells whether all rows require this value to be considered valid.
func (fld *Field) IsRequired() bool {
return fld.required
}

View File

@@ -1,110 +0,0 @@
package schema
import (
"encoding/json"
"errors"
"fmt"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
)
var ErrPrimaryKeyNotMapped = errors.New("primary key not mapped")
var ErrMappingFileRead = errors.New("error reading mapping file")
var ErrUnmarshallingMapping = errors.New("error unmarshalling mapping")
var ErrEmptyMapping = errors.New("empty mapping error")
type BadMappingErr struct {
srcField string
destField string
}
func (err *BadMappingErr) Error() string {
return fmt.Sprintf("Mapping file attempted to map %s to %s, but one or both of those fields are unknown.", err.srcField, err.destField)
}
var ErrBadMapping = errors.New("error bad mapping")
func IsBadMappingErr(err error) bool {
_, ok := err.(*BadMappingErr)
return ok
}
type FieldMapping struct {
SrcSch *Schema
DestSch *Schema
DestToSrc []int
}
func NewFieldMapping(inSch, outSch *Schema, destToSrc []int) (*FieldMapping, error) {
if outSch.GetPKIndex() != -1 && destToSrc[outSch.GetPKIndex()] == -1 {
return nil, ErrPrimaryKeyNotMapped
}
return &FieldMapping{inSch, outSch, destToSrc}, nil
}
func NewFieldMappingFromNameMap(inSch, outSch *Schema, inNameToOutName map[string]string) (*FieldMapping, error) {
destToSrc := make([]int, outSch.NumFields())
for i := 0; i < outSch.NumFields(); i++ {
destToSrc[i] = -1
}
successes := 0
for k, v := range inNameToOutName {
inIndex := inSch.GetFieldIndex(k)
outIndex := outSch.GetFieldIndex(v)
if inIndex == -1 || outIndex == -1 {
return nil, &BadMappingErr{k, v}
}
destToSrc[outIndex] = inIndex
successes++
}
if successes == 0 {
return nil, ErrEmptyMapping
}
return NewFieldMapping(inSch, outSch, destToSrc)
}
func NewInferredMapping(inSch, outSch *Schema) (*FieldMapping, error) {
successes := 0
destToSrc := make([]int, outSch.NumFields())
for i := 0; i < outSch.NumFields(); i++ {
outFld := outSch.GetField(i)
fldIdx := inSch.GetFieldIndex(outFld.NameStr())
destToSrc[i] = fldIdx
if fldIdx != -1 {
successes++
}
}
if successes == 0 {
return nil, ErrEmptyMapping
}
return NewFieldMapping(inSch, outSch, destToSrc)
}
func MappingFromFile(mappingFile string, fs filesys.ReadableFS, inSch, outSch *Schema) (*FieldMapping, error) {
data, err := fs.ReadFile(mappingFile)
if err != nil {
return nil, ErrMappingFileRead
}
var inNameToOutName map[string]string
err = json.Unmarshal(data, &inNameToOutName)
if err != nil {
return nil, ErrUnmarshallingMapping
}
return NewFieldMappingFromNameMap(inSch, outSch, inNameToOutName)
}
/*
*/

View File

@@ -1,98 +0,0 @@
package schema
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"reflect"
"testing"
)
var fieldsA = []*Field{
NewField("a", types.StringKind, true),
NewField("b", types.StringKind, true),
NewField("c", types.StringKind, true),
}
var fieldsB = []*Field{
NewField("a", types.StringKind, true),
NewField("b", types.StringKind, true),
}
var fieldsC = []*Field{
NewField("key", types.UUIDKind, true),
NewField("value", types.StringKind, true),
}
var fieldsD = []*Field{
NewField("key", types.StringKind, true),
NewField("value", types.StringKind, true),
}
var schemaA = NewSchema(fieldsA)
var schemaB = NewSchema(fieldsB)
var schemaC = NewSchema(fieldsC)
var schemaCNoPK = NewSchema(fieldsC)
var schemaD = NewSchema(fieldsD)
func init() {
schemaC.AddConstraint(NewConstraint(PrimaryKey, []int{0}))
}
func TestFieldMapping(t *testing.T) {
tests := []struct {
mappingJSON string
inSch *Schema
outSch *Schema
expectErr bool
expected []int
identity bool
}{
{"", schemaA, schemaA, false, []int{0, 1, 2}, true},
{"", schemaA, schemaB, false, []int{0, 1}, false},
{"", schemaB, schemaA, false, []int{0, 1, -1}, false},
{"", schemaA, schemaC, true, nil, false},
{`{"invalid_json": }`, schemaA, schemaC, true, nil, false},
{`{"b": "value"}`, schemaA, schemaC, true, nil, false},
{`{"c": "key", "b": "value"}`, schemaA, schemaC, false, []int{2, 1}, false},
{`{"a": "key", "b": "value"}`, schemaA, schemaC, false, []int{0, 1}, false},
{`{"a": "key", "b": "value"}`, schemaB, schemaC, false, []int{0, 1}, false},
{`{"a": "key", "b": "value"}`, schemaB, schemaCNoPK, false, []int{0, 1}, false},
{`{"a": "key", "b": "value"}`, schemaB, schemaD, false, []int{0, 1}, true},
}
for _, test := range tests {
fs := filesys.NewInMemFS([]string{"/"}, nil, "/")
mappingFile := ""
if test.mappingJSON != "" {
mappingFile = "mapping.json"
fs.WriteFile(mappingFile, []byte(test.mappingJSON))
}
var mapping *FieldMapping
var err error
if mappingFile != "" {
mapping, err = MappingFromFile(mappingFile, fs, test.inSch, test.outSch)
} else {
mapping, err = NewInferredMapping(test.inSch, test.outSch)
}
if (err != nil) != test.expectErr {
if test.expectErr {
t.Fatal("Expected an error that didn't come.", err)
} else {
t.Fatal("Unexpected error creating mapping.", err)
}
}
if !test.expectErr {
if !reflect.DeepEqual(mapping.DestToSrc, test.expected) {
t.Error("Mapping does not match expected. Expected:", test.expected, "Actual:", mapping.DestToSrc)
}
//if test.identity != mapping.IsIdentityMapping() {
// t.Error("identity expected", test.identity, "actual:", !test.identity)
//}
}
}
}

View File

@@ -1,35 +0,0 @@
package schema
import (
"github.com/attic-labs/noms/go/types"
"strings"
"testing"
)
func TestField(t *testing.T) {
const fName = "F1"
const fKind = types.StringKind
const fRequired = true
f1 := NewField(fName, fKind, fRequired)
if f1.NameStr() != strings.ToLower(fName) {
t.Error("All field names should be lowercase")
}
if f1.NomsKind() != fKind {
t.Error("Unexpected kind for field")
}
if f1.IsRequired() != fRequired {
t.Error("Unexpected required flag value for field")
}
if !f1.Equals(f1) {
t.Error("Field should definitely be equal to itself")
}
f2 := NewField(fName, fKind, !fRequired)
if f1.Equals(f2) {
t.Error("Should not be equal")
}
}

View File

@@ -1,2 +0,0 @@
// Package jsonenc provides json serialization and deserialization for schemas.
package jsonenc

View File

@@ -1,98 +0,0 @@
package jsonenc
import (
"encoding/json"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/pkg/errors"
)
type jsonFieldData struct {
// Name is the name of the field
Name string `json:"name"`
// Kind is the type of the field. See types/noms_kind.go in the liquidata fork for valid values
Kind string `json:"kind"`
// Required tells whether all rows require this value to be considered valid
Required bool `json:"required"`
}
func newJsonFieldData(field *schema.Field) jsonFieldData {
return jsonFieldData{field.NameStr(), field.KindString(), field.IsRequired()}
}
func (jfd jsonFieldData) toField() *schema.Field {
return schema.NewField(jfd.Name, schema.LwrStrToKind[jfd.Kind], jfd.Required)
}
type jsonConstraint struct {
Type string `json:"constraint_type"`
Fields []int `json:"field_indices"`
}
func newJsonConstraint(constraint *schema.Constraint) jsonConstraint {
return jsonConstraint{constraint.ConType().String(), constraint.FieldIndices()}
}
func (jc jsonConstraint) toConstraint() *schema.Constraint {
return schema.NewConstraint(schema.ConstraintFromString(jc.Type), jc.Fields)
}
func (jc jsonConstraint) isValid() bool {
return schema.ConstraintFromString(jc.Type) != schema.Invalid
}
type schemaData struct {
Fields []jsonFieldData `json:"fields"`
Constraints []jsonConstraint `json:"constraints"`
}
// SchemaToJSON takes a Schema and JSON serializes it.
func SchemaToJSON(sch *schema.Schema) ([]byte, error) {
fieldData := make([]jsonFieldData, sch.NumFields())
for i := 0; i < sch.NumFields(); i++ {
schField := sch.GetField(i)
fieldData[i] = newJsonFieldData(schField)
}
var constraintData []jsonConstraint
sch.IterConstraints(func(constraint *schema.Constraint) (stop bool) {
constraintData = append(constraintData, newJsonConstraint(constraint))
return false
})
sd := schemaData{fieldData, constraintData}
return json.MarshalIndent(&sd, "", " ")
}
// SchemaFromJson deserializes json stored in a byte slice as a Schema
func SchemaFromJSON(data []byte) (*schema.Schema, error) {
var sd schemaData
err := json.Unmarshal(data, &sd)
if err != nil {
return nil, err
}
fields := make([]*schema.Field, len(sd.Fields))
for i, currData := range sd.Fields {
fields[i] = currData.toField()
}
sch := schema.NewSchema(fields)
for _, cd := range sd.Constraints {
if !cd.isValid() {
return sch, errors.New("Invalid constraint")
}
constraint := cd.toConstraint()
err = sch.AddConstraint(constraint)
if err != nil {
return nil, err
}
}
return sch, nil
}

View File

@@ -1,85 +0,0 @@
package jsonenc
import (
"encoding/json"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"testing"
)
func createTestSchema() *schema.Schema {
fields := []*schema.Field{
schema.NewField("id", types.UUIDKind, true),
schema.NewField("first", types.StringKind, true),
schema.NewField("last", types.StringKind, true),
schema.NewField("age", types.UintKind, false),
}
sch := schema.NewSchema(fields)
err := sch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
if err != nil {
panic(err)
}
return sch
}
func TestFieldDataJson(t *testing.T) {
fld := schema.NewField("id", types.UUIDKind, true)
jfd := newJsonFieldData(fld)
jsonData, err := json.Marshal(jfd)
if err != nil {
t.Fatal("Error marshalling", err)
}
t.Log("json serialized as:", string(jsonData))
var unmarshalled jsonFieldData
err = json.Unmarshal(jsonData, &unmarshalled)
if err != nil {
t.Fatal("Error unmarshalling", string(jsonData), err)
}
result := unmarshalled.toField()
if !fld.Equals(result) {
t.Fatal("original != unmarshalled")
}
if fld.NameStr() != "id" || fld.NomsKind() != types.UUIDKind || fld.KindString() != "uuid" || !fld.IsRequired() {
t.Error("Accessors not returning expected results")
}
}
func TestSchemaJson(t *testing.T) {
tSchema := createTestSchema()
data, err := SchemaToJSON(tSchema)
if err != nil {
t.Fatal("Unable to marshal as json.", err)
}
unmarshalled, err := SchemaFromJSON(data)
if err != nil {
t.Fatal("Unable to deserialize schema from json.", err)
}
if !tSchema.Equals(unmarshalled) {
t.Errorf("Value changed after marshalling and unmarshalling.")
}
if tSchema.NumFields() != 4 {
t.Error("Unexpected column count in schema")
}
pkIndex := tSchema.GetFieldIndex("id")
unMarshalledID := tSchema.GetField(pkIndex)
if unMarshalledID.NameStr() != "id" || unMarshalledID.NomsKind() != types.UUIDKind || !unMarshalledID.IsRequired() {
t.Error("Unmarshalled PK does not match the initial PK")
}
}

View File

@@ -1,242 +1,25 @@
package schema
import (
"errors"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/set"
)
// Schema holds a list of columns which describe a table.
type Schema struct {
fields []*Field
constraintsByType map[ConstraintType][]*Constraint
contstraints []*Constraint
nameToIndex map[string]int
type Schema interface {
GetPKCols() *ColCollection
GetNonPKCols() *ColCollection
GetAllCols() *ColCollection
}
// NewSchema creates a new instance of Schema from a slice of fields
func NewSchema(fields []*Field) *Schema {
nameToIndex := make(map[string]int, len(fields))
for i, f := range fields {
nameToIndex[f.NameStr()] = i
}
return &Schema{fields, make(map[ConstraintType][]*Constraint), []*Constraint{}, nameToIndex}
func ColFromTag(sch Schema, tag uint64) (Column, bool) {
return sch.GetAllCols().GetByTag(tag)
}
// GetFieldNames returns a slice containing all the field names
func (sch *Schema) GetFieldNames() []string {
fldNames := make([]string, len(sch.fields))
for i, fld := range sch.fields {
fldNames[i] = fld.NameStr()
}
return fldNames
func ColFromName(sch Schema, name string) (Column, bool) {
return sch.GetAllCols().GetByName(name)
}
// NumFields returns the total number of fields in the table.
func (sch *Schema) NumFields() int {
return len(sch.fields)
}
// GetField provides access to all columns by index. Iterating from 0 to NumFields() will
// give you all columns in the Schema
func (sch *Schema) GetField(index int) *Field {
return sch.fields[index]
}
// GetFieldIndex gets a fields index by name.
func (sch *Schema) GetFieldIndex(fieldName string) int {
if index, ok := sch.nameToIndex[fieldName]; ok {
return index
}
return -1
}
func (sch *Schema) CopyWithoutConstraints() *Schema {
return &Schema{sch.fields, make(map[ConstraintType][]*Constraint), []*Constraint{}, sch.nameToIndex}
}
// Equals compares each column in the schema versus every column in another schema.
func (sch *Schema) Equals(other *Schema) bool {
if sch.NumFields() != other.NumFields() {
func ExtractAllColNames(sch Schema) map[uint64]string {
colNames := make(map[uint64]string)
sch.GetAllCols().ItrUnsorted(func(tag uint64, col Column) (stop bool) {
colNames[tag] = col.Name
return false
}
})
for i := 0; i < sch.NumFields(); i++ {
f1 := sch.GetField(i)
f2 := other.GetField(i)
if !f1.Equals(f2) {
return false
}
}
for _, ct := range ConstraintTypes {
cOfType1 := sch.constraintsByType[ct]
cOfType2 := other.constraintsByType[ct]
if len(cOfType1) != len(cOfType2) {
return false
}
for i := 0; i < len(cOfType1); i++ {
c1 := cOfType1[i]
c2 := cOfType2[i]
if len(c1.fieldIndices) != len(c2.fieldIndices) {
return false
}
for j := 0; j < len(c1.fieldIndices); j++ {
if c1.fieldIndices[j] != c2.fieldIndices[j] {
return false
}
}
}
}
return true
}
func (sch *Schema) TotalNumConstraints() int {
return len(sch.contstraints)
}
// NumConstraintsOfType returns the number of constraints a schema has for a given ConstraintType
func (sch *Schema) NumConstraintsOfType(cType ConstraintType) int {
constraints, _ := sch.constraintsByType[cType]
return len(constraints)
}
// GetConstraint gets a constraint by index
func (sch *Schema) GetConstraint(n int) *Constraint {
if n < 0 {
panic("Index < 0")
}
return sch.contstraints[n]
}
// GetConstraintByType returns the nth constraint of a given ConstraintType
func (sch *Schema) GetConstraintByType(cType ConstraintType, n int) (*Constraint, bool) {
if n < 0 {
panic("Index < 0")
}
constraints, ok := sch.constraintsByType[cType]
if !ok || n >= len(constraints) {
return nil, false
}
return constraints[n], true
}
// AddConstraint adds a constraint to the schema.
func (sch *Schema) AddConstraint(c *Constraint) error {
cType := c.ConType()
if cType == Invalid {
panic("Can't add invalid constraints")
}
sch.contstraints = append(sch.contstraints, c)
constraints, _ := sch.constraintsByType[cType]
if cType == PrimaryKey && len(constraints) != 0 {
return errors.New("Schema already has a primary key")
}
constraints = append(constraints, c)
sch.constraintsByType[cType] = constraints
return nil
}
// GetPKIndex returns the index of the field that is the primary key. If their is no PrimaryKey constraint for the
// schema then -1 will be returned.
func (sch *Schema) GetPKIndex() int {
if c, ok := sch.GetConstraintByType(PrimaryKey, 0); ok {
return c.FieldIndices()[0]
}
return -1
}
// IterConstraints iterates over each constraint making a callback for each constraint until all constraints are
// exhausted or until the callback function returns stop = true.
func (sch *Schema) IterConstraints(cb func(*Constraint) (stop bool)) {
for _, constraints := range sch.constraintsByType {
for _, constraint := range constraints {
stop := cb(constraint)
if stop {
break
}
}
}
}
// IterFields iterates over each field making a callback for each field until all fields are exhausted or until the
// callback function returns stop = true
func (sch *Schema) IterFields(cb func(*Field) (stop bool)) {
for _, field := range sch.fields {
stop := cb(field)
if stop {
break
}
}
}
// IntersectFields takes a slice of field names and checks them against the field names in the schema and returns
// 1 A slice of fields that are ony in the schema, 2 A slice of fields in both the schema and the supplied fields slice
// 3 The slice of fields that are only in the supplied fields slice.
func (sch *Schema) IntersectFields(fields []string) (schemaOnly []string, inBoth []string, fieldsOnly []string) {
for _, fName := range fields {
_, ok := sch.nameToIndex[fName]
if ok {
inBoth = append(inBoth, fName)
} else {
fieldsOnly = append(fieldsOnly, fName)
}
}
fieldSet := set.NewStrSet(fields)
for k := range sch.nameToIndex {
if !fieldSet.Contains(k) {
schemaOnly = append(schemaOnly, k)
}
}
return schemaOnly, inBoth, fieldsOnly
}
func (sch *Schema) ChangeColumnType(fieldName string, newFieldType types.NomsKind) *Schema {
var schFields []*Field
var newField *Field
for i := 0; i < sch.NumFields(); i++ {
if fieldName == sch.GetField(i).NameStr() {
newField = NewField(fieldName, newFieldType, sch.GetField(i).IsRequired())
schFields = append(schFields, newField)
} else {
schFields = append(schFields, sch.GetField(i))
}
}
newSch := NewSchema(schFields)
origConstraints := make([]*Constraint, 0, sch.TotalNumConstraints())
for i := 0; i < sch.TotalNumConstraints(); i++ {
origConstraints = append(origConstraints, sch.GetConstraint(i))
}
for _, c := range origConstraints {
newSch.AddConstraint(c)
}
return newSch
return colNames
}

View File

@@ -0,0 +1,72 @@
package schema
type schemaImpl struct {
pkCols, nonPKCols, allCols *ColCollection
}
func SchemaFromCols(allCols *ColCollection) Schema {
var pkCols []Column
var nonPKCols []Column
for _, c := range allCols.cols {
if c.IsPartOfPK {
pkCols = append(pkCols, c)
} else {
nonPKCols = append(nonPKCols, c)
}
}
pkColColl, _ := NewColCollection(pkCols...)
nonPKColColl, _ := NewColCollection(nonPKCols...)
si := &schemaImpl{
pkColColl, nonPKColColl, allCols,
}
return si
}
func SchemaFromPKAndNonPKCols(pkCols, nonPKCols *ColCollection) (Schema, error) {
allCols := make([]Column, pkCols.Size()+nonPKCols.Size())
i := 0
for _, c := range pkCols.cols {
if !c.IsPartOfPK {
panic("bug: attempting to add a column to the pk that isn't part of the pk")
}
allCols[i] = c
i++
}
for _, c := range nonPKCols.cols {
if c.IsPartOfPK {
panic("bug: attempting to add a column that is part of the pk to the non-pk columns")
}
allCols[i] = c
i++
}
allColColl, err := NewColCollection(allCols...)
if err != nil {
return nil, err
}
return &schemaImpl{
pkCols, nonPKCols, allColColl,
}, nil
}
func (si *schemaImpl) GetAllCols() *ColCollection {
return si.allCols
}
func (si *schemaImpl) GetNonPKCols() *ColCollection {
return si.nonPKCols
}
func (si *schemaImpl) GetPKCols() *ColCollection {
return si.pkCols
}

View File

@@ -1,155 +1,66 @@
package schema
import (
"reflect"
"sort"
"testing"
"github.com/attic-labs/noms/go/types"
"reflect"
"testing"
)
func TestSchema(t *testing.T) {
fields := []*Field{
NewField("id", types.UUIDKind, true),
NewField("name", types.StringKind, true),
NewField("age", types.UintKind, false),
}
sch := NewSchema(fields)
const (
lnColName = "last"
fnColName = "first"
addrColName = "address"
ageColName = "age"
titleColName = "title"
reservedColName = "reserved"
lnColTag = 1
fnColTag = 0
addrColTag = 6
ageColTag = 4
titleColTag = 40
reservedColTag = 50
)
if sch.NumFields() != 3 {
t.Fatal("Unexpected field count")
}
var lnVal = types.String("astley")
var fnVal = types.String("rick")
var addrVal = types.String("123 Fake St")
var ageVal = types.Uint(53)
var titleVal = types.NullValue
for i := 0; i > sch.NumFields(); i++ {
f := sch.GetField(i)
reverseIndex := sch.GetFieldIndex(f.NameStr())
if i != reverseIndex {
t.Error("Reverse index lookup returned unexpected result")
}
}
if sch.GetFieldIndex("id") != 0 || sch.GetFieldIndex("missing") != -1 {
t.Error("GetFieldIndex not giving expected indexes")
}
fields = append(fields, NewField("title", types.StringKind, false))
sch2 := NewSchema(fields)
if !sch.Equals(sch) {
t.Error("Schema should be equal to itself.")
}
if sch.Equals(sch2) {
t.Error("Schemas should differ.")
}
if sch.NumConstraintsOfType(PrimaryKey) != 0 {
t.Error("Shouldn't be any primary keys yet")
}
if _, ok := sch.GetConstraintByType(PrimaryKey, 0); ok {
t.Error("Should not be able to get this constraint yet")
}
if sch.GetPKIndex() != -1 {
t.Error("index should be -1 when there is no pk")
}
sch.AddConstraint(NewConstraint(PrimaryKey, []int{0}))
if sch.NumConstraintsOfType(PrimaryKey) != 1 {
t.Error("Should have a pk")
}
if _, ok := sch.GetConstraintByType(PrimaryKey, 0); !ok {
t.Error("Should be able to get this constraint")
}
if sch.GetPKIndex() != 0 {
t.Error("pk field index should be 0")
}
sch.IterConstraints(func(constraint *Constraint) (stop bool) {
t.Log(constraint.ConType().String(), constraint.FieldIndices())
return false
})
sch.IterFields(func(f *Field) (stop bool) {
return false
})
if !reflect.DeepEqual(sch.GetFieldNames(), []string{"id", "name", "age"}) {
t.Error("incorrect fields")
}
if sch.TotalNumConstraints() != 1 {
t.Error("incorrect number of constraints")
}
var testKeyCols = []Column{
{lnColName, lnColTag, types.StringKind, true, nil},
{fnColName, fnColTag, types.StringKind, true, nil},
}
var testCols = []Column{
{addrColName, addrColTag, types.StringKind, false, nil},
{ageColName, ageColTag, types.UintKind, false, nil},
{titleColName, titleColTag, types.StringKind, false, nil},
{reservedColName, reservedColTag, types.StringKind, false, nil},
}
func TestIntersectFields(t *testing.T) {
fields := []*Field{
NewField("id", types.UUIDKind, true),
NewField("name", types.StringKind, true),
NewField("age", types.UintKind, false),
}
sch := NewSchema(fields)
var allCols = append(append([]Column(nil), testKeyCols...), testCols...)
tests := []struct {
inFields []string
expectedSchema []string
expectedBoth []string
expectedFields []string
}{
{[]string{"id", "gender", "state"}, []string{"name", "age"}, []string{"id"}, []string{"gender", "state"}},
{[]string{"id", "name"}, []string{"age"}, []string{"id", "name"}, nil},
{[]string{"city", "state"}, []string{"id", "name", "age"}, nil, []string{"city", "state"}},
}
func TestSchemaFromCols(t *testing.T) {
colColl, _ := NewColCollection(allCols...)
sch := SchemaFromCols(colColl)
for _, test := range tests {
actualSchemaOnly, actualBoth, actualFieldsOnly := sch.IntersectFields(test.inFields)
if !reflect.DeepEqual(actualBoth, test.expectedBoth) {
t.Error(actualBoth, "!=", test.expectedBoth)
}
sort.Strings(actualSchemaOnly)
sort.Strings(test.expectedSchema)
if !reflect.DeepEqual(actualSchemaOnly, test.expectedSchema) {
t.Error(actualSchemaOnly, "!=", test.expectedSchema)
}
if !reflect.DeepEqual(actualFieldsOnly, test.expectedFields) {
t.Error(actualFieldsOnly, "!=", test.expectedFields)
}
}
validateCols(t, allCols, sch.GetAllCols(), "SchemaFromCols::GetAllCols")
validateCols(t, testKeyCols, sch.GetPKCols(), "SchemaFromCols::GetPKCols")
validateCols(t, testCols, sch.GetNonPKCols(), "SchemaFromCols::GetNonPKCols")
}
func TestChangeColumnType(t *testing.T) {
fields := []*Field{
NewField("id", types.UUIDKind, true),
NewField("name", types.StringKind, true),
NewField("age", types.UintKind, false),
}
sch := NewSchema(fields)
func TestSchemaFromPKAndNonPKCols(t *testing.T) {
testKeyColColl, _ := NewColCollection(testKeyCols...)
testNonKeyColsColl, _ := NewColCollection(testCols...)
sch, _ := SchemaFromPKAndNonPKCols(testKeyColColl, testNonKeyColsColl)
tests := []struct {
fieldName string
oldType types.NomsKind
newType types.NomsKind
expectedNewType types.NomsKind
}{
{"id", types.UUIDKind, types.UintKind, types.UintKind},
}
for i, test := range tests {
newSch := sch.ChangeColumnType("id", types.UintKind)
if newSch.GetField(i).kind != test.expectedNewType {
t.Error(newSch.GetField(i).kind != test.expectedNewType)
}
validateCols(t, allCols, sch.GetAllCols(), "SchemaFromPKAndNonPKCols::GetAllCols")
validateCols(t, testKeyCols, sch.GetPKCols(), "SchemaFromPKAndNonPKCols::GetPKCols")
validateCols(t, testCols, sch.GetNonPKCols(), "SchemaFromPKAndNonPKCols::GetNonPKCols")
}
func validateCols(t *testing.T, cols []Column, colColl *ColCollection, msg string) {
if !reflect.DeepEqual(cols, colColl.cols) {
t.Error()
}
}

View File

@@ -0,0 +1,38 @@
package table
import (
"errors"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"strings"
)
var ErrInvalidRow = errors.New("invalid row")
type BadRow struct {
Row row.Row
Details []string
}
func NewBadRow(r row.Row, details ...string) *BadRow {
return &BadRow{r, details}
}
func IsBadRow(err error) bool {
_, ok := err.(*BadRow)
return ok
}
func GetBadRowRow(err error) row.Row {
br, ok := err.(*BadRow)
if !ok {
panic("Call IsBadRow prior to trying to get the BadRowRow")
}
return br.Row
}
func (br *BadRow) Error() string {
return strings.Join(br.Details, "\n")
}

View File

@@ -1,67 +1,50 @@
package table
import (
"io"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
)
type SchemaValidation int
const (
ValidateSameInstance SchemaValidation = iota
SlowlyCheckEachField
"io"
)
// InMemTable holds a simple list of rows that can be retrieved, or appended to. It is meant primarily for testing.
type InMemTable struct {
sch *schema.Schema
rows []*Row
validation SchemaValidation
sch schema.Schema
rows []row.Row
}
// NewInMemTable creates an empty Table with the expectation that any rows added will have the given
// Schema
func NewInMemTable(sch *schema.Schema) *InMemTable {
return NewInMemTableWithData(sch, []*Row{})
func NewInMemTable(sch schema.Schema) *InMemTable {
return NewInMemTableWithData(sch, []row.Row{})
}
// NewInMemTableWithData creates a Table with the riven rows
func NewInMemTableWithData(sch *schema.Schema, rows []*Row) *InMemTable {
return NewInMemTableWithDataAndValidationType(sch, rows, SlowlyCheckEachField)
func NewInMemTableWithData(sch schema.Schema, rows []row.Row) *InMemTable {
return NewInMemTableWithDataAndValidationType(sch, rows)
}
func NewInMemTableWithDataAndValidationType(sch *schema.Schema, rows []*Row, validation SchemaValidation) *InMemTable {
return &InMemTable{sch, rows, validation}
func NewInMemTableWithDataAndValidationType(sch schema.Schema, rows []row.Row) *InMemTable {
return &InMemTable{sch, rows}
}
// AppendRow appends a row. Appended rows must have the correct columns.
func (imt *InMemTable) AppendRow(row *Row) error {
rowSch := row.GetSchema()
if rowSch != imt.sch {
invalid := true
if imt.validation == SlowlyCheckEachField {
invalid = rowSch.Equals(imt.sch)
}
if invalid {
panic("Can't write a row to a table if it has different columns.")
}
// AppendRow appends a row. Appended rows must be valid for the table's schema
func (imt *InMemTable) AppendRow(r row.Row) error {
if !row.IsValid(r, imt.sch) {
return ErrInvalidRow
}
imt.rows = append(imt.rows, row)
imt.rows = append(imt.rows, r)
return nil
}
// GetRow gets a row by index
func (imt *InMemTable) GetRow(index int) (*Row, error) {
func (imt *InMemTable) GetRow(index int) (row.Row, error) {
return imt.rows[index], nil
}
// GetSchema gets the table's schema
func (imt *InMemTable) GetSchema() *schema.Schema {
func (imt *InMemTable) GetSchema() schema.Schema {
return imt.sch
}
@@ -83,28 +66,28 @@ func NewInMemTableReader(imt *InMemTable) *InMemTableReader {
// ReadRow reads a row from a table. If there is a bad row the returned error will be non nil, and callin IsBadRow(err)
// will be return true. This is a potentially non-fatal error and callers can decide if they want to continue on a bad row, or fail.
func (r *InMemTableReader) ReadRow() (*Row, error) {
numRows := r.tt.NumRows()
func (rd *InMemTableReader) ReadRow() (row.Row, error) {
numRows := rd.tt.NumRows()
if r.current < numRows {
row := r.tt.rows[r.current]
r.current++
if rd.current < numRows {
r := rd.tt.rows[rd.current]
rd.current++
return &Row{row.data, nil}, nil
return r, nil
}
return nil, io.EOF
}
// Close should release resources being held
func (r *InMemTableReader) Close() error {
r.current = -1
func (rd *InMemTableReader) Close() error {
rd.current = -1
return nil
}
// GetSchema gets the schema of the rows that this reader will return
func (r *InMemTableReader) GetSchema() *schema.Schema {
return r.tt.sch
func (rd *InMemTableReader) GetSchema() schema.Schema {
return rd.tt.sch
}
// InMemTableWriter is an implementation of a TableWriter for an InMemTable
@@ -118,8 +101,8 @@ func NewInMemTableWriter(imt *InMemTable) *InMemTableWriter {
}
// WriteRow will write a row to a table
func (w *InMemTableWriter) WriteRow(row *Row) error {
return w.tt.AppendRow(row)
func (w *InMemTableWriter) WriteRow(r row.Row) error {
return w.tt.AppendRow(r)
}
// Close should flush all writes, release resources being held
@@ -128,6 +111,6 @@ func (w *InMemTableWriter) Close() error {
}
// GetSchema gets the schema of the rows that this writer writes
func (w *InMemTableWriter) GetSchema() *schema.Schema {
func (w *InMemTableWriter) GetSchema() schema.Schema {
return w.tt.sch
}

View File

@@ -2,38 +2,46 @@ package table
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"io"
"testing"
)
var fields = []*schema.Field{
schema.NewField("name", types.StringKind, true),
schema.NewField("age", types.UintKind, true),
schema.NewField("title", types.StringKind, true),
schema.NewField("is_great", types.BoolKind, true),
}
const (
nameTag uint64 = iota
ageTag
titleTag
greatTag
)
var rowSch = schema.NewSchema(fields)
var rows = []*Row{
NewRow(RowDataFromValues(rowSch, []types.Value{
types.String("Bill Billerson"),
types.Uint(32),
types.String("Senior Dufus"),
types.Bool(true),
})),
NewRow(RowDataFromValues(rowSch, []types.Value{
types.String("Rob Robertson"),
types.Uint(25),
types.String("Dufus"),
types.Bool(false),
})),
NewRow(RowDataFromValues(rowSch, []types.Value{
types.String("John Johnson"),
types.Uint(21),
types.String("Intern Dufus"),
types.Bool(true),
})),
var fields, _ = schema.NewColCollection(
schema.Column{"name", nameTag, types.StringKind, true, nil},
schema.Column{"age", ageTag, types.UintKind, true, nil},
schema.Column{"title", titleTag, types.StringKind, true, nil},
schema.Column{"is_great", greatTag, types.BoolKind, true, nil},
)
var rowSch = schema.SchemaFromCols(fields)
var rows = []row.Row{
row.New(rowSch, row.TaggedValues{
nameTag: types.String("Bill Billerson"),
ageTag: types.Uint(32),
titleTag: types.String("Senior Dufus"),
greatTag: types.Bool(true),
}),
row.New(rowSch, row.TaggedValues{
nameTag: types.String("Rob Robertson"),
ageTag: types.Uint(25),
titleTag: types.String("Dufus"),
greatTag: types.Bool(false),
}),
row.New(rowSch, row.TaggedValues{
nameTag: types.String("John Johnson"),
ageTag: types.Uint(21),
titleTag: types.String("Intern Dufus"),
greatTag: types.Bool(true),
}),
}
func TestInMemTable(t *testing.T) {
@@ -63,7 +71,7 @@ func TestInMemTable(t *testing.T) {
if err != nil {
t.Error("Unexpected read error")
} else if !RowsEqualIgnoringSchema(expectedRow, actualRow) {
} else if !row.AreEqual(expectedRow, actualRow, rowSch) {
t.Error("Unexpected row value")
}
}
@@ -105,8 +113,8 @@ func TestPipeRows(t *testing.T) {
t.Fatal("Couldn't Get row.")
}
if !RowsEqualIgnoringSchema(r1, r2) {
t.Error("Rows sholud be the same.", RowFmt(r1), "!=", RowFmt(r2))
if !row.AreEqual(r1, r2, rowSch) {
t.Error("Rows sholud be the same.", row.Fmt(r1, rowSch), "!=", row.Fmt(r2, rowSch))
}
}
}
@@ -116,7 +124,7 @@ func TestReadAllRows(t *testing.T) {
var err error
var numBad int
var results []*Row
var results []row.Row
func() {
rd := NewInMemTableReader(imt)
defer rd.Close()
@@ -136,19 +144,20 @@ func TestReadAllRows(t *testing.T) {
}
for i := 0; i < len(rows); i++ {
if !RowsEqualIgnoringSchema(rows[i], results[i]) {
t.Error(RowFmt(rows[i]), "!=", RowFmt(results[i]))
if !row.AreEqual(rows[i], results[i], rowSch) {
t.Error(row.Fmt(rows[i], rowSch), "!=", row.Fmt(results[i], rowSch))
}
}
}
/*
func TestReadAllRowsToMap(t *testing.T) {
imt := NewInMemTableWithData(rowSch, rows)
greatIndex := rowSch.GetFieldIndex("is_great")
var err error
var numBad int
var results map[types.Value][]*Row
var results map[types.Value][]row.Row
func() {
rd := NewInMemTableReader(imt)
defer rd.Close()
@@ -184,3 +193,4 @@ func TestReadAllRowsToMap(t *testing.T) {
}
}
}
*/

View File

@@ -2,58 +2,28 @@ package table
import (
"errors"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"io"
"strings"
)
type BadRow struct {
Row *Row
Details []string
}
func NewBadRow(row *Row, details ...string) *BadRow {
return &BadRow{row, details}
}
func IsBadRow(err error) bool {
_, ok := err.(*BadRow)
return ok
}
func GetBadRowRow(err error) *Row {
br, ok := err.(*BadRow)
if !ok {
panic("Call IsBadRow prior to trying to get the BadRowRow")
}
return br.Row
}
func (br *BadRow) Error() string {
return strings.Join(br.Details, "\n")
}
// TableReader is an interface for reading rows from a table
type TableReader interface {
// GetSchema gets the schema of the rows that this reader will return
GetSchema() *schema.Schema
GetSchema() schema.Schema
// ReadRow reads a row from a table. If there is a bad row the returned error will be non nil, and callin IsBadRow(err)
// will be return true. This is a potentially non-fatal error and callers can decide if they want to continue on a bad row, or fail.
ReadRow() (*Row, error)
ReadRow() (row.Row, error)
}
// TableWriteCloser is an interface for writing rows to a table
type TableWriter interface {
// GetSchema gets the schema of the rows that this writer writes
GetSchema() *schema.Schema
GetSchema() schema.Schema
// WriteRow will write a row to a table
WriteRow(row *Row) error
WriteRow(r row.Row) error
}
// TableCloser is an interface for a table stream that can be closed to release resources
@@ -80,7 +50,7 @@ type TableWriteCloser interface {
func PipeRows(rd TableReader, wr TableWriter, contOnBadRow bool) (int, int, error) {
var numBad, numGood int
for {
row, err := rd.ReadRow()
r, err := rd.ReadRow()
if err != nil && err != io.EOF {
if IsBadRow(err) && contOnBadRow {
@@ -89,14 +59,14 @@ func PipeRows(rd TableReader, wr TableWriter, contOnBadRow bool) (int, int, erro
}
return -1, -1, err
} else if err == io.EOF && row == nil {
} else if err == io.EOF && r == nil {
break
} else if row == nil {
} else if r == nil {
// row equal to nil should
return -1, -1, errors.New("reader returned nil row with err==nil")
}
err = wr.WriteRow(row)
err = wr.WriteRow(r)
if err != nil {
return -1, -1, err
@@ -110,16 +80,16 @@ func PipeRows(rd TableReader, wr TableWriter, contOnBadRow bool) (int, int, erro
// ReadAllRows reads all rows from a TableReader and returns a slice containing those rows. Usually this is used
// for testing, or with very small data sets.
func ReadAllRows(rd TableReader, contOnBadRow bool) ([]*Row, int, error) {
var rows []*Row
func ReadAllRows(rd TableReader, contOnBadRow bool) ([]row.Row, int, error) {
var rows []row.Row
var err error
badRowCount := 0
for {
var row *Row
row, err = rd.ReadRow()
var r row.Row
r, err = rd.ReadRow()
if err != nil && err != io.EOF || row == nil {
if err != nil && err != io.EOF || r == nil {
if IsBadRow(err) {
badRowCount++
@@ -131,7 +101,7 @@ func ReadAllRows(rd TableReader, contOnBadRow bool) ([]*Row, int, error) {
break
}
rows = append(rows, row)
rows = append(rows, r)
}
if err == nil || err == io.EOF {
@@ -143,20 +113,20 @@ func ReadAllRows(rd TableReader, contOnBadRow bool) ([]*Row, int, error) {
// ReadAllRowsToMap reads all rows from a TableReader and returns a map containing those rows keyed off of the index
// provided.
func ReadAllRowsToMap(rd TableReader, keyIndex int, contOnBadRow bool) (map[types.Value][]*Row, int, error) {
/*func ReadAllRowsToMap(rd TableReader, keyIndex int, contOnBadRow bool) (map[types.Value][]row.Row, int, error) {
if keyIndex < 0 || keyIndex >= rd.GetSchema().NumFields() {
panic("Invalid index is out of range of fields.")
}
var err error
rows := make(map[types.Value][]*Row)
rows := make(map[types.Value][]row.Row)
badRowCount := 0
for {
var row *Row
row, err = rd.ReadRow()
var r row.Row
r, err = rd.ReadRow()
if err != nil && err != io.EOF || row == nil {
if err != nil && err != io.EOF || r == nil {
if IsBadRow(err) {
badRowCount++
@@ -168,9 +138,9 @@ func ReadAllRowsToMap(rd TableReader, keyIndex int, contOnBadRow bool) (map[type
break
}
keyVal, _ := row.CurrData().GetField(keyIndex)
keyVal, _ := row.GetField(keyIndex)
rowsForThisKey := rows[keyVal]
rowsForThisKey = append(rowsForThisKey, row)
rowsForThisKey = append(rowsForThisKey, r)
rows[keyVal] = rowsForThisKey
}
@@ -179,4 +149,4 @@ func ReadAllRowsToMap(rd TableReader, keyIndex int, contOnBadRow bool) (map[type
}
return nil, badRowCount, err
}
}*/

View File

@@ -1,9 +1,11 @@
package pipeline
import "github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
)
type TransformRowFailure struct {
Row *table.Row
Row row.Row
TransformName string
Details string
}
@@ -27,7 +29,7 @@ func GetTransFailureTransName(err error) string {
return trf.TransformName
}
func GetTransFailureRow(err error) *table.Row {
func GetTransFailureRow(err error) row.Row {
trf, ok := err.(*TransformRowFailure)
if !ok {

View File

@@ -1,14 +1,6 @@
package pipeline
import (
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"strconv"
"testing"
)
/*
var initialSchema = schema.NewSchema([]*schema.Field{
schema.NewField("id_str", types.StringKind, true),
schema.NewField("name", types.StringKind, true),
@@ -120,12 +112,13 @@ func testMappingRead(typedSchema *schema.Schema, rows []*table.Row, t *testing.T
}
if len(expectedRows) != len(rows) {
t.Error("Unexpected row count")
t.Error("Unexpected Row count")
}
for i, row := range rows {
if !table.RowsEqualIgnoringSchema(row, expectedRows[i]) {
t.Error("\n", table.RowFmt(row), "!=\n", table.RowFmt(expectedRows[i]))
for i, Row := range rows {
if !table.RowsEqualIgnoringSchema(Row, expectedRows[i]) {
t.Error("\n", table.RowFmt(Row), "!=\n", table.RowFmt(expectedRows[i]))
}
}
}
*/

View File

@@ -1,29 +1,31 @@
package pipeline
import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"io"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"sync"
"sync/atomic"
)
type ProcFunc func(p *Pipeline, ch chan RowWithProps, badRowChan chan<- *TransformRowFailure)
type BadRowCallback func(*TransformRowFailure) (quit bool)
type Pipeline struct {
wg *sync.WaitGroup
stopChan chan bool
atomicErr atomic.Value
transInCh map[string]chan *table.Row
transInCh map[string]chan RowWithProps
Start func()
}
func (p *Pipeline) InsertRow(name string, row *table.Row) bool {
func (p *Pipeline) InsertRow(name string, r row.Row) bool {
ch, ok := p.transInCh[name]
if !ok {
return false
}
ch <- row
ch <- RowWithProps{r, NoProps}
return true
}
@@ -47,13 +49,13 @@ func (p *Pipeline) Wait() error {
return nil
}
func NewAsyncPipeline(rd table.TableReader, transforms *TransformCollection, wr table.TableWriter, badRowCB BadRowCallback) (pipeline *Pipeline, start func()) {
func NewAsyncPipeline(processInputs, processOutputs ProcFunc, transforms *TransformCollection, badRowCB BadRowCallback) (pipeline *Pipeline) {
var wg sync.WaitGroup
in := make(chan *table.Row, 1024)
in := make(chan RowWithProps, 1024)
badRowChan := make(chan *TransformRowFailure, 1024)
stopChan := make(chan bool)
transInCh := make(map[string]chan *table.Row)
transInCh := make(map[string]chan RowWithProps)
curr := in
if transforms != nil {
@@ -64,20 +66,32 @@ func NewAsyncPipeline(rd table.TableReader, transforms *TransformCollection, wr
}
}
p := &Pipeline{&wg, stopChan, atomic.Value{}, transInCh}
p := &Pipeline{&wg, stopChan, atomic.Value{}, transInCh, nil}
wg.Add(3)
go p.processBadRows(badRowCB, badRowChan)
go p.processOutputs(wr, curr, badRowChan)
wg.Add(2)
go func() {
defer wg.Done()
p.processBadRows(badRowCB, badRowChan)
}()
go func() {
defer wg.Done()
processOutputs(p, curr, badRowChan)
}()
return p, func() {
go p.processInputs(rd, in, badRowChan)
p.Start = func() {
wg.Add(1)
go func() {
defer wg.Done()
processInputs(p, in, badRowChan)
}()
}
return p
}
func transformAsync(transformer TransformFunc, wg *sync.WaitGroup, inChan chan *table.Row, badRowChan chan<- *TransformRowFailure, stopChan <-chan bool) chan *table.Row {
func transformAsync(transformer TransformFunc, wg *sync.WaitGroup, inChan chan RowWithProps, badRowChan chan<- *TransformRowFailure, stopChan <-chan bool) chan RowWithProps {
wg.Add(1)
outChan := make(chan *table.Row, 256)
outChan := make(chan RowWithProps, 256)
go func() {
defer wg.Done()
@@ -89,77 +103,24 @@ func transformAsync(transformer TransformFunc, wg *sync.WaitGroup, inChan chan *
return outChan
}
func (p *Pipeline) processInputs(rd table.TableReader, in chan<- *table.Row, badRowChan chan<- *TransformRowFailure) {
defer close(in)
defer p.wg.Done()
for {
// read row
row, err := rd.ReadRow()
// process read errors
if err != nil {
if err == io.EOF {
if row == nil {
return
}
} else if table.IsBadRow(err) {
badRowChan <- &TransformRowFailure{table.GetBadRowRow(err), "reader", err.Error()}
} else {
p.atomicErr.Store(err)
close(p.stopChan)
return
}
} else if row == nil {
panic("Readers should not be returning nil without error. io.EOF should be used when done.")
}
// exit if stop
select {
case <-p.stopChan:
return
default:
}
if row != nil {
in <- row
}
}
func (p Pipeline) StopWithErr(err error) {
p.atomicErr.Store(err)
close(p.stopChan)
}
func (p *Pipeline) processOutputs(wr table.TableWriter, out <-chan *table.Row, badRowChan chan<- *TransformRowFailure) {
defer close(badRowChan)
defer p.wg.Done()
func (p Pipeline) IsStopping() bool {
// exit if stop
select {
case <-p.stopChan:
return true
for {
select {
case row, ok := <-out:
if ok {
err := wr.WriteRow(row)
if err != nil {
if table.IsBadRow(err) {
badRowChan <- &TransformRowFailure{row, "writer", err.Error()}
} else {
p.atomicErr.Store(err)
close(p.stopChan)
return
}
}
} else {
return
}
case <-p.stopChan:
return
}
default:
}
return false
}
func (p *Pipeline) processBadRows(badRowCB BadRowCallback, badRowChan <-chan *TransformRowFailure) {
defer p.wg.Done()
if badRowCB != nil {
for {
select {

View File

@@ -0,0 +1,93 @@
package pipeline
import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"io"
"time"
)
type SourceFunc func() (row.Row, ImmutableProperties, error)
func ProcFuncForSourceFunc(sourceFunc SourceFunc) ProcFunc {
return func(p *Pipeline, ch chan RowWithProps, badRowChan chan<- *TransformRowFailure) {
defer close(ch)
for {
r, props, err := sourceFunc()
// process read errors
if err != nil {
if err == io.EOF {
if r == nil {
return
}
} else if table.IsBadRow(err) {
badRowChan <- &TransformRowFailure{table.GetBadRowRow(err), "reader", err.Error()}
} else {
p.StopWithErr(err)
return
}
} else if r == nil {
panic("Readers should not be returning nil without error. io.EOF should be used when done.")
}
if p.IsStopping() {
return
}
if r != nil {
ch <- RowWithProps{r, props}
}
}
}
}
func ProcFuncForReader(rd table.TableReader) ProcFunc {
return ProcFuncForSourceFunc(func() (row.Row, ImmutableProperties, error) {
r, err := rd.ReadRow()
return r, NoProps, err
})
}
type SinkFunc func(row.Row, ReadableMap) error
func ProcFuncForSinkFunc(sinkFunc SinkFunc) ProcFunc {
return func(p *Pipeline, ch chan RowWithProps, badRowChan chan<- *TransformRowFailure) {
defer close(badRowChan)
for {
if p.IsStopping() {
return
}
select {
case r, ok := <-ch:
if ok {
err := sinkFunc(r.Row, r.Props)
if err != nil {
if table.IsBadRow(err) {
badRowChan <- &TransformRowFailure{r.Row, "writer", err.Error()}
} else {
p.StopWithErr(err)
return
}
}
} else {
return
}
case <-time.After(100 * time.Millisecond):
return
}
}
}
}
func ProcFuncForWriter(wr table.TableWriter) ProcFunc {
return ProcFuncForSinkFunc(func(r row.Row, props ReadableMap) error {
return wr.WriteRow(r)
})
}

View File

@@ -0,0 +1,46 @@
package pipeline
import "github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
type ReadableMap interface {
Get(propName string) (interface{}, bool)
}
var NoProps = ImmutableProperties{}
type ImmutableProperties struct {
props map[string]interface{}
}
func (ip ImmutableProperties) Get(propName string) (interface{}, bool) {
if ip.props == nil {
return nil, false
}
val, ok := ip.props[propName]
return val, ok
}
func (ip ImmutableProperties) Set(updates map[string]interface{}) ImmutableProperties {
numProps := len(updates) + len(ip.props)
allProps := make(map[string]interface{}, numProps)
for k, v := range ip.props {
allProps[k] = v
}
for k, v := range updates {
allProps[k] = v
}
return ImmutableProperties{allProps}
}
type RowWithProps struct {
Row row.Row
Props ImmutableProperties
}
func NewRowWithProps(r row.Row, props map[string]interface{}) RowWithProps {
return RowWithProps{r, ImmutableProperties{props}}
}

View File

@@ -1,15 +1,3 @@
package pipeline
import "github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
func GetRowConvTransformFunc(rc *table.RowConverter) func(*table.Row) ([]*TransformedRowResult, string) {
return func(inRow *table.Row) (outRows []*TransformedRowResult, badRowDetails string) {
outData, err := rc.Convert(inRow)
if err != nil {
return nil, err.Error()
}
return []*TransformedRowResult{{RowData: outData}}, ""
}
}
/**/

View File

@@ -1,50 +1,20 @@
package pipeline
import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
)
type TransformedRowResult struct {
RowData *table.RowData
Properties map[string]interface{}
RowData row.Row
PropertyUpdates map[string]interface{}
}
type TransFailCallback func(row *table.Row, errDetails string)
type TransformFunc func(inChan <-chan *table.Row, outChan chan<- *table.Row, badRowChan chan<- *TransformRowFailure, stopChan <-chan bool)
type BulkTransformFunc func(rows []*table.Row, outChan chan<- *table.Row, badRowChan chan<- *TransformRowFailure, stopChan <-chan bool)
type TransformRowFunc func(inRow *table.Row) (rowData []*TransformedRowResult, badRowDetails string)
func NewBulkTransformer(bulkTransFunc BulkTransformFunc) TransformFunc {
return func(inChan <-chan *table.Row, outChan chan<- *table.Row, badRowChan chan<- *TransformRowFailure, stopChan <-chan bool) {
var rows []*table.Row
RowLoop:
for {
select {
case <-stopChan:
return
default:
}
select {
case row, ok := <-inChan:
if ok {
rows = append(rows, row)
break RowLoop
} else {
return
}
case <-stopChan:
return
}
}
bulkTransFunc(rows, outChan, badRowChan, stopChan)
}
}
type TransFailCallback func(row RowWithProps, errDetails string)
type TransformFunc func(inChan <-chan RowWithProps, outChan chan<- RowWithProps, badRowChan chan<- *TransformRowFailure, stopChan <-chan bool)
type TransformRowFunc func(inRow row.Row, props ReadableMap) (rowData []*TransformedRowResult, badRowDetails string)
func NewRowTransformer(name string, transRowFunc TransformRowFunc) TransformFunc {
return func(inChan <-chan *table.Row, outChan chan<- *table.Row, badRowChan chan<- *TransformRowFailure, stopChan <-chan bool) {
return func(inChan <-chan RowWithProps, outChan chan<- RowWithProps, badRowChan chan<- *TransformRowFailure, stopChan <-chan bool) {
for {
select {
case <-stopChan:
@@ -53,20 +23,25 @@ func NewRowTransformer(name string, transRowFunc TransformRowFunc) TransformFunc
}
select {
case row, ok := <-inChan:
case r, ok := <-inChan:
if ok {
outRowData, badRowDetails := transRowFunc(row)
outRowData, badRowDetails := transRowFunc(r.Row, r.Props)
outSize := len(outRowData)
for i := 0; i < outSize; i++ {
props := row.ClonedMergedProperties(outRowData[i].Properties)
outRow := table.NewRowWithProperties(outRowData[i].RowData, props)
propUpdates := outRowData[i].PropertyUpdates
outProps := r.Props
if len(propUpdates) > 0 {
outProps = outProps.Set(propUpdates)
}
outRow := RowWithProps{outRowData[i].RowData, outProps}
outChan <- outRow
}
if badRowDetails != "" {
badRowChan <- &TransformRowFailure{row, name, badRowDetails}
badRowChan <- &TransformRowFailure{r.Row, name, badRowDetails}
}
} else {
return

View File

@@ -1,7 +1,121 @@
package pipeline
import "testing"
import (
"bytes"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped/csv"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/iohelp"
"io/ioutil"
"strconv"
"strings"
"testing"
)
var inCSV = `first,last,film or show,year
Tim,Allen,The Santa Clause,1994
Tim,Allen,The Santa Clause 2,2002
Tim,Allen,The Santa Clause 3: The Escape Clause,2006
Ed,Asner,Elf,2003
Ed,Asner,Christmas on the Bayou,2013
Ed,Asner,Elf: Buddy's Musical Christmas,2014
Fred,Astaire,The Man in the Santa Claus Suit,1979
Richard,Attenborough,Miracle on 34th Street,1994
Steve,Bacic,Deck the Halls,2005
Alec,Baldwin,Rise of the Guardians,2012
Don,Beddoe,Bewitched (episode Humbug Not to Be Spoken Here - Season 4),1967
`
var outCSV = `first,last,film or show,year,pre2000,index
Tim,Allen,The Santa Clause,1994,true,0
Tim,Allen,The Santa Clause,1994,true,1
Tim,Allen,The Santa Clause 2,2002,false,0
Tim,Allen,The Santa Clause 2,2002,false,1
Tim,Allen,The Santa Clause 3: The Escape Clause,2006,false,0
Tim,Allen,The Santa Clause 3: The Escape Clause,2006,false,1
Ed,Asner,Elf,2003,false,0
Ed,Asner,Elf,2003,false,1
Ed,Asner,Christmas on the Bayou,2013,false,0
Ed,Asner,Christmas on the Bayou,2013,false,1
Ed,Asner,Elf: Buddy's Musical Christmas,2014,false,0
Ed,Asner,Elf: Buddy's Musical Christmas,2014,false,1
Fred,Astaire,The Man in the Santa Claus Suit,1979,true,0
Fred,Astaire,The Man in the Santa Claus Suit,1979,true,1
Richard,Attenborough,Miracle on 34th Street,1994,true,0
Richard,Attenborough,Miracle on 34th Street,1994,true,1
Steve,Bacic,Deck the Halls,2005,false,0
Steve,Bacic,Deck the Halls,2005,false,1
Alec,Baldwin,Rise of the Guardians,2012,false,0
Alec,Baldwin,Rise of the Guardians,2012,false,1
Don,Beddoe,Bewitched (episode Humbug Not to Be Spoken Here - Season 4),1967,true,0
Don,Beddoe,Bewitched (episode Humbug Not to Be Spoken Here - Season 4),1967,true,1`
var _, schIn = untyped.NewUntypedSchema("first", "last", "film or show", "year")
var nameToTag, schOut = untyped.NewUntypedSchema("first", "last", "film or show", "year", "pre2000", "index")
func TestPipeline(t *testing.T) {
buf := bytes.NewBuffer([]byte(inCSV))
outBuf := bytes.NewBuffer([]byte{})
func() {
csvInfo := &csv.CSVFileInfo{Delim: ',', HasHeaderLine: true, Columns: nil, EscapeQuotes: true}
rd, _ := csv.NewCSVReader(ioutil.NopCloser(buf), csvInfo)
wr, _ := csv.NewCSVWriter(iohelp.NopWrCloser(outBuf), schOut, csvInfo)
defer rd.Close()
defer wr.Close()
tc := NewTransformCollection(
NamedTransform{"identity", NewRowTransformer("identity", identityTransFunc)},
NamedTransform{"label", NewRowTransformer("label", labelTransFunc)},
NamedTransform{"dupe", NewRowTransformer("dupe", dupeTransFunc)},
NamedTransform{"append", NewRowTransformer("append", appendColumnPre2000TransFunc)},
)
inProcFunc := ProcFuncForReader(rd)
outProcFunc := ProcFuncForWriter(wr)
p := NewAsyncPipeline(inProcFunc, outProcFunc, tc, nil)
p.Start()
p.Wait()
}()
outStr := outBuf.String()
if strings.TrimSpace(outStr) != strings.TrimSpace(outCSV) {
t.Error("output does not match expectation.")
}
}
func identityTransFunc(inRow row.Row, props ReadableMap) ([]*TransformedRowResult, string) {
return []*TransformedRowResult{{inRow, nil}}, ""
}
func labelTransFunc(inRow row.Row, props ReadableMap) ([]*TransformedRowResult, string) {
val, _ := inRow.GetColVal(nameToTag["year"])
year, _ := strconv.ParseInt(string(val.(types.String)), 10, 32)
return []*TransformedRowResult{
{inRow, map[string]interface{}{"pre2000": year < 2000}},
}, ""
}
func dupeTransFunc(inRow row.Row, props ReadableMap) ([]*TransformedRowResult, string) {
r1, _ := inRow.SetColVal(nameToTag["index"], types.String("0"), schOut)
r2, _ := inRow.SetColVal(nameToTag["index"], types.String("1"), schOut)
return []*TransformedRowResult{
{r1, map[string]interface{}{"dupe_index": 1}},
{r2, map[string]interface{}{"dupe_index": 2}},
}, ""
}
func appendColumnPre2000TransFunc(inRow row.Row, props ReadableMap) (rowData []*TransformedRowResult, badRowDetails string) {
labelval, _ := props.Get("pre2000")
isPre2000Str := "false"
if labelval.(bool) {
isPre2000Str = "true"
}
r1, _ := inRow.SetColVal(nameToTag["pre2000"], types.String(isPre2000Str), schOut)
return []*TransformedRowResult{
{r1, nil},
}, ""
}

View File

@@ -1,242 +0,0 @@
package table
import (
"bytes"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
)
type Row struct {
data *RowData
properties map[string]interface{}
}
func NewRow(rd *RowData) *Row {
return &Row{rd, nil}
}
func NewRowWithProperties(rd *RowData, props map[string]interface{}) *Row {
return &Row{rd, props}
}
func (row *Row) ClonedMergedProperties(addedProps map[string]interface{}) map[string]interface{} {
if row.properties == nil {
return addedProps
}
newProps := make(map[string]interface{})
for k, v := range row.properties {
newProps[k] = v
}
if addedProps != nil {
for k, v := range addedProps {
newProps[k] = v
}
}
return newProps
}
func (row *Row) AddProperty(propName string, val interface{}) {
if row.properties == nil {
row.properties = map[string]interface{}{propName: val}
} else {
row.properties[propName] = val
}
}
func (row *Row) AddProperties(properties map[string]interface{}) {
if row.properties != nil {
row.properties = properties
} else {
for k, v := range properties {
row.properties[k] = v
}
}
}
func (row *Row) GetProperty(propName string) (interface{}, bool) {
val, ok := row.properties[propName]
return val, ok
}
func (row *Row) CurrData() *RowData {
return row.data
}
func (row *Row) GetSchema() *schema.Schema {
return row.data.sch
}
func GetPKFromRow(row *Row) types.Value {
sch := row.data.GetSchema()
pkIndex := sch.GetPKIndex()
val, _ := row.data.GetField(pkIndex)
return val
}
// GetNonPKFieldListFromRow gets the values for non primary key row fields as a noms list
func GetNonPKFieldListFromRow(row *Row, vrw types.ValueReadWriter) types.Tuple {
sch := row.data.GetSchema()
pkIdx := sch.GetPKIndex()
if pkIdx == -1 {
panic("Only valid for rows that have primary keys")
}
numFields := sch.NumFields()
nonPKValues := make([]types.Value, numFields-1)
for i, j := 0, 0; i < numFields; i++ {
if i != pkIdx {
nonPKValues[j], _ = row.data.GetField(i)
j++
}
}
for i := numFields - 2; i >= 0; i-- {
if nonPKValues[i] == nil {
nonPKValues = nonPKValues[:i]
} else {
break
}
}
return types.NewTuple(nonPKValues...)
}
// RowIsValid will return true if every column defined as required in the table's schema is non null
func RowIsValid(row *Row) bool {
return row.data.IsValid()
}
func InvalidFieldsForRow(row *Row) []string {
sch := row.data.GetSchema()
badFields := make([]string, 0, sch.NumFields())
pkIndex := sch.GetPKIndex()
for i := 0; i < sch.NumFields(); i++ {
val, fld := row.data.GetField(i)
if types.IsNull(val) {
if fld.IsRequired() || i == pkIndex {
badFields = append(badFields, fld.NameStr())
}
} else if val.Kind() != fld.NomsKind() {
badFields = append(badFields, fld.NameStr())
}
}
return badFields
}
var fieldDelim = []byte(" | ")
// RowsEqualIgnoringSchema will ignore the schema of two rows and will compare field values index by index.
func RowsEqualIgnoringSchema(row, other *Row) bool {
return RowDataEqualIgnoringSchema(row.CurrData(), other.CurrData())
}
// RowDataEqualIgnoringSchema will ignore the schema of two rows and will compare field values index by index.
func RowDataEqualIgnoringSchema(rowData, other *RowData) bool {
longer := rowData
shorter := other
if rowData.GetSchema().NumFields() > rowData.GetSchema().NumFields() {
longer = other
shorter = rowData
}
lNumFields := longer.GetSchema().NumFields()
sNumFields := shorter.GetSchema().NumFields()
for i := 0; i < lNumFields; i++ {
lVal, _ := longer.GetField(i)
var sVal types.Value = types.NullValue
if i < sNumFields {
sVal, _ = shorter.GetField(i)
}
if types.IsNull(lVal) {
if !types.IsNull(sVal) {
return false
}
} else {
if types.IsNull(sVal) || !lVal.Equals(sVal) {
return false
}
}
}
return true
}
type RowFormatFunc func(row *Row) string
type RowDataFormatFunc func(row *RowData) string
var RowDataFmt = FieldSeparatedFmt(':')
var RowFmt RowFormatFunc = func(row *Row) string {
if row == nil {
return "null"
}
return RowDataFmt(row.CurrData())
}
// FieldSeparatedFmt returns the string representation of the row with fields separated by pipes
func FieldSeparatedFmt(delim rune) RowDataFormatFunc {
return func(row *RowData) string {
if row == nil {
return "null"
}
sch := row.GetSchema()
numFields := sch.NumFields()
kvps := make([]string, numFields)
var backingBuffer [512]byte
buf := bytes.NewBuffer(backingBuffer[:0])
for i := 0; i < numFields; i++ {
if i != 0 {
buf.Write(fieldDelim)
}
val, fld := row.GetField(i)
buf.Write([]byte(fld.NameStr()))
buf.WriteRune(delim)
types.WriteEncodedValue(buf, val)
kvps[i] = buf.String()
}
return buf.String()
}
}
/*var emptyHashSl []byte
func init() {
emptyHashSl = make([]byte, hash.ByteLen)
for i := 0; i < hash.ByteLen; i++ {
emptyHashSl[i] = 0
}
}
// UUIDForColVals returns a uuid that is the sha1 hash of the concatenated noms hashes of the selected fields.
func (row *Row) UUIDForColVals(fieldIndices []int) uuid.UUID {
hashBuf := make([]byte, len(fieldIndices)*hash.ByteLen)
for i, index := range fieldIndices {
val, _ := row.GetField(index)
var hashSl []byte
if val == nil {
hashSl = emptyHashSl
} else {
hashBytes := [hash.ByteLen]byte(val.Hash())
hashSl = hashBytes[:hash.ByteLen]
}
start := i * hash.ByteLen
end := (i + 1) * hash.ByteLen
copy(hashBuf[start:end], hashSl)
}
return uuid.NewSHA1(uuid.NameSpaceOID, hashBuf)
}*/

View File

@@ -1,114 +0,0 @@
package table
import (
"fmt"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/pantoerr"
)
var IdentityConverter = &RowConverter{nil, true, nil}
type RowConverter struct {
*schema.FieldMapping
IdentityConverter bool
convFuncs []doltcore.ConvFunc
}
func NewRowConverter(mapping *schema.FieldMapping) (*RowConverter, error) {
if mapping.SrcSch == nil || mapping.DestSch == nil || len(mapping.DestToSrc) == 0 {
panic("Invalid oldNameToSchema2Name cannot be used for conversion")
}
if !isNecessary(mapping.SrcSch, mapping.DestSch, mapping.DestToSrc) {
return &RowConverter{nil, true, nil}, nil
}
convFuncs := make([]doltcore.ConvFunc, mapping.DestSch.NumFields())
for dstIdx, srcIdx := range mapping.DestToSrc {
if srcIdx != -1 {
destFld := mapping.DestSch.GetField(dstIdx)
srcFld := mapping.SrcSch.GetField(srcIdx)
convFuncs[dstIdx] = doltcore.GetConvFunc(srcFld.NomsKind(), destFld.NomsKind())
if convFuncs[dstIdx] == nil {
return nil, fmt.Errorf("Unsupported conversion from type %s to %s", srcFld.KindString(), destFld.KindString())
}
}
}
return &RowConverter{mapping, false, convFuncs}, nil
}
func (rc *RowConverter) Convert(inRow *Row) (*RowData, error) {
rowData := inRow.CurrData()
return rc.ConvertRowData(rowData)
}
func (rc *RowConverter) ConvertRowData(rowData *RowData) (*RowData, error) {
if rc.IdentityConverter {
return rowData, nil
}
destFieldCount := rc.DestSch.NumFields()
fieldVals := make([]types.Value, destFieldCount)
unexpectedErr := NewBadRow(NewRow(rowData), "Unexpected Error")
err := pantoerr.PanicToErrorInstance(unexpectedErr, func() error {
for i := 0; i < destFieldCount; i++ {
srcIdx := rc.DestToSrc[i]
if srcIdx == -1 {
continue
}
val, _ := rowData.GetField(srcIdx)
mappedVal, err := rc.convFuncs[i](val)
if err != nil {
return NewBadRow(NewRow(rowData), err.Error())
}
fieldVals[i] = mappedVal
}
return nil
})
if err != nil {
return nil, err
}
return RowDataFromValues(rc.DestSch, fieldVals), nil
}
func isNecessary(srcSch, destSch *schema.Schema, destToSrc []int) bool {
if len(destToSrc) != srcSch.NumFields() || len(destToSrc) != destSch.NumFields() {
return true
}
for i := 0; i < len(destToSrc); i++ {
if i != destToSrc[i] {
return true
}
if srcSch.GetField(i).NomsKind() != destSch.GetField(i).NomsKind() {
return true
}
}
srcHasPK := srcSch.NumConstraintsOfType(schema.PrimaryKey) != 0
destHasPK := destSch.NumConstraintsOfType(schema.PrimaryKey) != 0
if srcHasPK != destHasPK {
return true
}
if destHasPK && srcSch.GetPKIndex() != destToSrc[destSch.GetPKIndex()] {
return true
}
return false
}

View File

@@ -1,69 +0,0 @@
package table
import (
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"testing"
)
func TestRowConverter(t *testing.T) {
srcSch := schema.NewSchema([]*schema.Field{
schema.NewField("uuidtostr", types.UUIDKind, true),
schema.NewField("floattostr", types.FloatKind, true),
schema.NewField("uinttostr", types.UintKind, true),
schema.NewField("booltostr", types.BoolKind, true),
schema.NewField("inttostr", types.IntKind, true),
schema.NewField("stringtostr", types.StringKind, true),
schema.NewField("nulltostr", types.NullKind, true),
})
destSch := schema.NewSchema([]*schema.Field{
schema.NewField("uuidToStr", types.StringKind, true),
schema.NewField("floatToStr", types.StringKind, true),
schema.NewField("uintToStr", types.StringKind, true),
schema.NewField("boolToStr", types.StringKind, true),
schema.NewField("intToStr", types.StringKind, true),
schema.NewField("stringToStr", types.StringKind, true),
schema.NewField("nullToStr", types.StringKind, true),
})
mapping, err := schema.NewInferredMapping(srcSch, destSch)
if err != nil {
t.Fatal("Err creating field oldNameToSchema2Name")
}
rConv, err := NewRowConverter(mapping)
if err != nil {
t.Fatal("Error creating row converter")
}
id, _ := uuid.NewRandom()
inRow := NewRow(RowDataFromValMap(srcSch, map[string]types.Value{
"uuidtostr": types.UUID(id),
"floattostr": types.Float(1.25),
"uinttostr": types.Uint(12345678),
"booltostr": types.Bool(true),
"inttostr": types.Int(-1234),
"stringtostr": types.String("string string string"),
"nulltostr": types.NullValue,
}))
outData, _ := rConv.Convert(inRow)
expected := RowDataFromValMap(destSch, map[string]types.Value{
"uuidtostr": types.String(id.String()),
"floattostr": types.String("1.25"),
"uinttostr": types.String("12345678"),
"booltostr": types.String("true"),
"inttostr": types.String("-1234"),
"stringtostr": types.String("string string string"),
"nulltostr": types.NullValue,
})
if !RowDataEqualIgnoringSchema(expected, outData) {
t.Error("\n", RowDataFmt(expected), "!=\n", RowDataFmt(outData))
}
}

View File

@@ -1,163 +0,0 @@
package table
import (
"errors"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
)
var ErrInvalidRow = errors.New("invalid row")
// RowData is a tuple of data following a given schema
type RowData struct {
sch *schema.Schema
fieldVals []types.Value
}
func RowDataFromValues(sch *schema.Schema, fieldVals []types.Value) *RowData {
numFieldVals := len(fieldVals)
lastValid := -1
for i := numFieldVals - 1; i >= 0; i-- {
if !types.IsNull(fieldVals[i]) {
lastValid = i
break
}
}
for i := 0; i < lastValid; i++ {
if types.IsNull(fieldVals[i]) {
fieldVals[i] = types.NullValue
}
}
var rowFields []types.Value
if lastValid == -1 {
rowFields = make([]types.Value, 0)
} else {
rowFields = fieldVals[:lastValid+1]
}
return &RowData{sch, rowFields}
}
func RowDataFromPKAndValueList(sch *schema.Schema, pk types.Value, fieldValueList types.Tuple) *RowData {
pkIndex := sch.GetPKIndex()
fieldValues := make([]types.Value, 0, fieldValueList.Len()+1)
for i := 0; uint64(i) < fieldValueList.Len(); i++ {
if i == pkIndex {
fieldValues = append(fieldValues, pk)
}
fieldValues = append(fieldValues, fieldValueList.Get(uint64(i)))
}
if pkIndex == int(fieldValueList.Len()) {
fieldValues = append(fieldValues, pk)
}
return RowDataFromValues(sch, fieldValues)
}
func RowDataFromValMap(sch *schema.Schema, vals map[string]types.Value) *RowData {
fieldValues := make([]types.Value, sch.NumFields())
for i := 0; i < sch.NumFields(); i++ {
f := sch.GetField(i)
val, _ := vals[f.NameStr()]
fieldValues[i] = val
}
return RowDataFromValues(sch, fieldValues)
}
func RowDataFromUntypedMap(sch *schema.Schema, vals map[string]string) (rowData *RowData, firstBad *string) {
fieldValues := make([]types.Value, sch.NumFields())
for i := 0; i < sch.NumFields(); i++ {
f := sch.GetField(i)
fldName := f.NameStr()
val, ok := vals[fldName]
if ok {
convFunc := doltcore.GetConvFunc(types.StringKind, f.NomsKind())
converted, err := convFunc(types.String(val))
if err != nil {
return nil, &fldName
}
fieldValues[i] = converted
}
}
return RowDataFromValues(sch, fieldValues), nil
}
// GetSchema gets the schema for the row
func (rd *RowData) GetSchema() *schema.Schema {
return rd.sch
}
// GetField will return a field's value along with the Field definition and whether it is the primary key by index.
func (rd *RowData) GetField(index int) (val types.Value, field *schema.Field) {
if index < 0 || index >= rd.sch.NumFields() {
panic("Index out of range")
}
if index < len(rd.fieldVals) {
val = rd.fieldVals[index]
}
f := rd.sch.GetField(index)
return val, f
}
func (rd *RowData) CopyValues(dest []types.Value, offset, count int) {
for i := 0; i < count; i++ {
dest[i] = rd.fieldVals[offset+i]
}
}
func (rd *RowData) IsValid() bool {
sch := rd.GetSchema()
pkIndex := sch.GetPKIndex()
for i := 0; i < sch.NumFields(); i++ {
val, fld := rd.GetField(i)
if types.IsNull(val) {
if fld.IsRequired() || i == pkIndex {
return false
}
} else if val.Kind() != fld.NomsKind() {
return false
}
}
return true
}
// GetFieldByName will return a field's value along with the Field definition and whether it is the primary key by
// field name.
func (rd *RowData) GetFieldByName(name string) (types.Value, *schema.Field) {
index := rd.sch.GetFieldIndex(name)
return rd.GetField(index)
}
func (rd *RowData) UpdatedCopy(updates []types.Value) *RowData {
updatedVals := make([]types.Value, len(rd.fieldVals))
i := 0
for ; i < len(updates); i++ {
newVal := updates[i]
if newVal == nil {
updatedVals[i] = rd.fieldVals[i]
} else {
updatedVals[i] = newVal
}
}
for ; i < len(rd.fieldVals); i++ {
updatedVals[i] = rd.fieldVals[i]
}
return &RowData{rd.sch, updatedVals}
}

View File

@@ -1,246 +0,0 @@
package table
import (
"github.com/attic-labs/noms/go/spec"
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"testing"
)
const (
pkName = "id"
pkType = types.UUIDKind
)
var testID, _ = uuid.NewRandom()
func createTestSchema() *schema.Schema {
fields := []*schema.Field{
schema.NewField(pkName, pkType, true),
schema.NewField("first", types.StringKind, true),
schema.NewField("last", types.StringKind, true),
schema.NewField("is_married", types.BoolKind, false),
schema.NewField("age", types.UintKind, false),
schema.NewField("empty", types.IntKind, false),
}
sch := schema.NewSchema(fields)
err := sch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
if err != nil {
panic("Error creating schema")
}
return sch
}
func newRowTest(sch *schema.Schema) *Row {
return NewRow(RowDataFromValues(sch, []types.Value{
types.UUID(testID),
types.String("bill"),
types.String("billerson"),
types.NullValue,
types.Uint(53),
}))
}
func newRowFromPKAndValueListTest(sch *schema.Schema) *Row {
pk := types.UUID(testID)
valTuple := types.NewTuple(types.String("bill"), types.String("billerson"), types.NullValue, types.Uint(53))
return NewRow(RowDataFromPKAndValueList(sch, pk, valTuple))
}
func newRowFromValMapTest(sch *schema.Schema) *Row {
return NewRow(RowDataFromValMap(sch, map[string]types.Value{
"id": types.UUID(testID),
"first": types.String("bill"),
"last": types.String("billerson"),
"age": types.Uint(53),
"is_married": types.NullValue,
}))
}
func TestNewRow(t *testing.T) {
dbSPec, _ := spec.ForDatabase("mem")
db := dbSPec.GetDatabase()
tSchema := createTestSchema()
missingIndex := tSchema.GetFieldIndex("missing")
if missingIndex != -1 {
t.Error("Bad index for nonexistant field")
}
newRowFuncs := map[string]func(*schema.Schema) *Row{
"NewRow": newRowTest,
"NewRowFromPKAndValueList": newRowFromPKAndValueListTest,
"NewRowFromValMap": newRowFromValMapTest,
}
for name, newRowFunc := range newRowFuncs {
row := newRowFunc(tSchema)
sch := row.GetSchema()
if !RowIsValid(row) {
t.Error(name + " created an invalid row")
} else if sch.NumFields() != 6 {
t.Error(name + " created a row with != 5 fields")
} else if sch.GetPKIndex() == -1 {
t.Error("Primary key constaint missing.")
} else {
rowData := row.CurrData()
for i := 0; i < sch.NumFields(); i++ {
val, field := rowData.GetField(i)
switch field.NameStr() {
case "id":
if !val.Equals(types.UUID(testID)) {
t.Error(name+":", "Value of id is incorrect")
}
if !val.Equals(GetPKFromRow(row)) {
t.Error("Unexpected pk value")
}
case "first":
if !val.Equals(types.String("bill")) {
t.Error(name+":", "Value of first is incorrect.")
}
case "last":
if !val.Equals(types.String("billerson")) {
t.Error(name+":", "Value of last is incorrect.")
}
case "age":
if !val.Equals(types.Uint(53)) {
t.Error(name+":", "Value of age is incorrect.")
}
case "empty":
if val != nil {
t.Error(name+":", "unexpected val for empty")
}
case "is_married":
if !types.IsNull(val) {
t.Error(name+":", "unexpected val for is_married")
}
default:
t.Error(name+":", "Unknown field:", field.NameStr())
}
}
row2 := NewRow(RowDataFromPKAndValueList(sch, GetPKFromRow(row), GetNonPKFieldListFromRow(row, db)))
if !RowsEqualIgnoringSchema(row, row2) {
t.Error(RowFmt(row), "!=", RowFmt(row2))
}
}
t.Log(RowFmt(row))
}
}
func createTestSchema2() *schema.Schema {
fields := []*schema.Field{
schema.NewField(pkName, pkType, true),
schema.NewField("first", types.StringKind, true),
schema.NewField("last", types.StringKind, true),
schema.NewField("is_married", types.BoolKind, false),
schema.NewField("age", types.UintKind, false),
}
return schema.NewSchema(fields)
}
func TestEqualsIgnoreSchema(t *testing.T) {
sch := createTestSchema()
ts2 := createTestSchema2()
row := newRowFromPKAndValueListTest(sch)
row2 := newRowFromValMapTest(ts2)
if !RowsEqualIgnoringSchema(row, row2) {
t.Error("Rows should be equivalent")
} else if !RowsEqualIgnoringSchema(row2, row) {
t.Error("Rows should be equivalent")
}
}
/*func TestUUIDForColVals(t *testing.T) {
sch := createTestSchema2()
inMemTestRF := NewRowFactory(sch)
r1 := inMemTestRF.NewRow(RowDataFromValues([]types.Value{types.String("Robert"), types.String("Robertson"), types.Bool(true), types.Uint(55)}))
r2 := inMemTestRF.NewRow(RowDataFromValues([]types.Value{types.String("Roberta"), types.String("Robertson"), types.Bool(true), types.Uint(55)}))
r3 := inMemTestRF.NewRow(RowDataFromValues([]types.Value{types.String("Robby"), types.String("Robertson"), types.Bool(false), types.Uint(55)}))
colIndices := []int{1, 2, 3}
uuid1 := r1.UUIDForColVals(colIndices)
uuid2 := r2.UUIDForColVals(colIndices)
uuid3 := r3.UUIDForColVals(colIndices)
if uuid1 != uuid2 {
t.Error(uuid1.String(), "!=", uuid2.String())
}
if uuid1 == uuid3 {
t.Error(uuid2.String(), "==", uuid3.String())
}
}*/
func TestRowPKNonPK(t *testing.T) {
tests := []struct {
fields []*schema.Field
idIdx int
}{
{[]*schema.Field{
schema.NewField(pkName, pkType, true),
schema.NewField("first", types.StringKind, true),
}, 0},
{[]*schema.Field{
schema.NewField("first", types.StringKind, true),
schema.NewField(pkName, pkType, true),
}, 1},
{[]*schema.Field{
schema.NewField(pkName, pkType, true),
schema.NewField("first", types.StringKind, true),
schema.NewField("last", types.StringKind, true),
}, 0},
{[]*schema.Field{
schema.NewField("first", types.StringKind, true),
schema.NewField(pkName, pkType, true),
schema.NewField("last", types.StringKind, true),
}, 1},
{[]*schema.Field{
schema.NewField("first", types.StringKind, true),
schema.NewField("last", types.StringKind, true),
schema.NewField(pkName, pkType, true),
}, 2},
}
for i, test := range tests {
sch := schema.NewSchema(test.fields)
sch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{test.idIdx}))
id, _ := uuid.NewRandom()
uuidVal := types.UUID(id)
first := "Billy"
last := "Bob"
valMap := map[string]types.Value{
"first": types.String(first),
"last": types.String(last),
pkName: uuidVal,
}
row := NewRow(RowDataFromValMap(sch, valMap))
dbSPec, _ := spec.ForDatabase("mem")
db := dbSPec.GetDatabase()
row2 := NewRow(RowDataFromPKAndValueList(sch, GetPKFromRow(row), GetNonPKFieldListFromRow(row, db)))
if !RowsEqualIgnoringSchema(row, row2) {
t.Error(i, RowFmt(row), "!=", RowFmt(row2))
}
}
}

View File

@@ -1,175 +0,0 @@
package table
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/pkg/errors"
"io"
"sort"
)
type rowStore struct {
sortFldIdx int
sortKeyToRow map[types.Value]*Row
sortKeys []types.Value
isSorted bool
}
func emptyRowStore(sortFldIdx int) *rowStore {
return &rowStore{sortFldIdx, make(map[types.Value]*Row), []types.Value{}, false}
}
func rowStoreWithData(sortFldIdx int, rows []*Row) *rowStore {
sortKeyToRow := make(map[types.Value]*Row, len(rows))
sortKeys := make([]types.Value, len(rows))
for i, row := range rows {
key, _ := row.CurrData().GetField(sortFldIdx)
sortKeyToRow[key] = row
sortKeys[i] = key
}
rs := &rowStore{sortFldIdx, sortKeyToRow, sortKeys, false}
rs.sort()
return rs
}
func (rs *rowStore) addRow(row *Row) {
key, _ := row.CurrData().GetField(rs.sortFldIdx)
rs.sortKeyToRow[key] = row
rs.sortKeys = append(rs.sortKeys, key)
rs.isSorted = false
}
func (rs *rowStore) sort() {
sort.Slice(rs.sortKeys, func(i, j int) bool {
return rs.sortKeys[i].Less(rs.sortKeys[j])
})
}
// SortingTableReader wraps a TableReader and will allow reading of rows sorted by a particular field ID. To achieve this
// SortingTableReader will read the entire table into memory before it can be read from. This approach works ok for smaller
// tables, but will need to do something smarter for larger tables.
type SortingTableReader struct {
sch *schema.Schema
rs *rowStore
currentIdx int
}
// NewSortingTableReaderByPK uses the schema of the table being read from and looks at it's primary key constraint to
// determine which field should be used for sorting (If there is no primary key constraint on this schema then this
// function will panic). Before this function returns all rows will be read from the supplied reader into memory and
// sort them. The supplied TableReadCloser will be closed when the SortingTableReader is done with it.
func NewSortingTableReaderByPK(rd TableReadCloser, contOnBadRow bool) (*SortingTableReader, int, int, error) {
pkIdx := rd.GetSchema().GetPKIndex()
if pkIdx == -1 {
panic("No Primary Key constraint on the readers schema")
}
return NewSortingTableReader(rd, pkIdx, contOnBadRow)
}
// NewSortingTableReader uses a supplied field index to determine which field should be used for sorting. Before this
// function returns all rows will be read from the supplied reader into memory and sort them. The supplied
// TableReadCloser will be closed when the SortingTableReader is done with it.
func NewSortingTableReader(rd TableReadCloser, fldIdx int, contOnBadRow bool) (*SortingTableReader, int, int, error) {
if fldIdx < 0 || fldIdx >= rd.GetSchema().NumFields() {
panic("Sorting Field Index is outside the indices of the Schema's fields.")
}
rows, numBad, err := ReadAllRows(rd, contOnBadRow)
if err != nil {
return nil, len(rows), numBad, err
}
rs := rowStoreWithData(fldIdx, rows)
return &SortingTableReader{rd.GetSchema(), rs, 0}, len(rows), numBad, nil
}
// GetSchema gets the schema of the rows that this reader will return
func (str *SortingTableReader) GetSchema() *schema.Schema {
return str.sch
}
// ReadRow reads a row from a table. If there is a bad row the returned error will be non nil, and callin IsBadRow(err)
// will be return true. This is a potentially non-fatal error and callers can decide if they want to continue on a bad row, or fail.
func (str *SortingTableReader) ReadRow() (*Row, error) {
if str.currentIdx == -1 {
panic("Attempted to read row after close.")
} else if str.currentIdx >= len(str.rs.sortKeys) {
return nil, io.EOF
}
key := str.rs.sortKeys[str.currentIdx]
str.currentIdx++
return str.rs.sortKeyToRow[key], nil
}
// Close should release resources being held.
func (str *SortingTableReader) Close() error {
if str.currentIdx != -1 {
str.currentIdx = -1
str.rs = nil
return nil
}
return errors.New("Already closed.")
}
type SortingTableWriter struct {
wr TableWriteCloser
rs *rowStore
contOnErr bool
}
func NewSortingTableWriterByPK(wr TableWriteCloser, contOnErr bool) *SortingTableWriter {
pkIndex := wr.GetSchema().GetPKIndex()
if pkIndex == -1 {
panic("Schema does not have a PK")
}
return NewSortingTableWriter(wr, pkIndex, contOnErr)
}
func NewSortingTableWriter(wr TableWriteCloser, sortIdx int, contOnErr bool) *SortingTableWriter {
return &SortingTableWriter{wr, emptyRowStore(sortIdx), contOnErr}
}
// GetSchema gets the schema of the rows that this writer writes
func (stWr *SortingTableWriter) GetSchema() *schema.Schema {
return stWr.wr.GetSchema()
}
// WriteRow will write a row to a table
func (stWr *SortingTableWriter) WriteRow(row *Row) error {
stWr.rs.addRow(row)
return nil
}
// Close should release resources being held
func (stWr *SortingTableWriter) Close() error {
if stWr.rs != nil {
defer stWr.wr.Close()
stWr.rs.sort()
for _, key := range stWr.rs.sortKeys {
row := stWr.rs.sortKeyToRow[key]
err := stWr.wr.WriteRow(row)
if err != nil && (!IsBadRow(err) || !stWr.contOnErr) {
return err
}
}
return nil
}
return errors.New("Already closed")
}

View File

@@ -1,104 +0,0 @@
package table
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"testing"
)
var untypedFields = []*schema.Field{
schema.NewField("number", types.StringKind, true),
schema.NewField("name", types.StringKind, true),
}
var typedFields = []*schema.Field{
schema.NewField("number", types.UintKind, true),
schema.NewField("name", types.StringKind, true),
}
var untypedSch = schema.NewSchema(untypedFields)
var typedSch = schema.NewSchema(typedFields)
func init() {
untypedSch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
typedSch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
}
var untypedRows = []*Row{
NewRow(RowDataFromValues(untypedSch, []types.Value{
types.String("9"),
types.String("Nine")})),
NewRow(RowDataFromValues(untypedSch, []types.Value{
types.String("8"),
types.String("Eight")})),
NewRow(RowDataFromValues(untypedSch, []types.Value{
types.String("10"),
types.String("Ten")})),
}
var typedRows = []*Row{
NewRow(RowDataFromValues(typedSch, []types.Value{
types.Uint(9),
types.String("Nine")})),
NewRow(RowDataFromValues(typedSch, []types.Value{
types.Uint(8),
types.String("Eight")})),
NewRow(RowDataFromValues(typedSch, []types.Value{
types.Uint(10),
types.String("Ten")})),
}
func TestSortingReader(t *testing.T) {
untypedResults := testSortingReader(t, untypedSch, untypedRows)
typedResults := testSortingReader(t, typedSch, typedRows)
if !GetPKFromRow(untypedResults[0]).Equals(types.String("10")) ||
!GetPKFromRow(untypedResults[1]).Equals(types.String("8")) ||
!GetPKFromRow(untypedResults[2]).Equals(types.String("9")) {
t.Error("Unexpected untyped ordering")
}
if !GetPKFromRow(typedResults[0]).Equals(types.Uint(8)) ||
!GetPKFromRow(typedResults[1]).Equals(types.Uint(9)) ||
!GetPKFromRow(typedResults[2]).Equals(types.Uint(10)) {
t.Error("Unexpected typed ordering")
}
}
func testSortingReader(t *testing.T, sch *schema.Schema, rows []*Row) []*Row {
imt := NewInMemTableWithData(sch, rows)
imttRd := NewInMemTableReader(imt)
sr, goodRows, badRows, err := NewSortingTableReaderByPK(imttRd, true)
if err != nil {
t.Fatal("Failed to create SortingTableReader")
}
if goodRows != 3 && badRows != 0 {
t.Error("good/bad row count does not match expectations.")
}
var results []*Row
func() {
defer func() {
err := sr.Close()
if err != nil {
t.Error("Failed to close sorted reader", err)
}
}()
var err error
results, badRows, err = ReadAllRows(sr, true)
if err != nil {
t.Fatal("Failed to read all rows")
}
if badRows != 0 {
t.Error("Num bad rows does not match expectation")
}
}()
return results
}

View File

@@ -1,2 +0,0 @@
// Package nbf provides TableReadCloser and TableWriteCloser implementations for working with nbf files.
package nbf

View File

@@ -1,98 +0,0 @@
package nbf
import (
"bufio"
"errors"
"fmt"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"io"
)
var ReadBufSize = 256 * 1024
//NBFReader reads rows from nbf files (Noms Binary Format). The
type NBFReader struct {
closer io.Closer
bRd *bufio.Reader
sch *schema.Schema
}
func OpenNBFReader(path string, fs filesys.ReadableFS) (*NBFReader, error) {
r, err := fs.OpenForRead(path)
if err != nil {
return nil, err
}
return NewNBFReader(r)
}
func NewNBFReader(r io.ReadCloser) (*NBFReader, error) {
br := bufio.NewReaderSize(r, ReadBufSize)
sch, err := ReadBinarySchema(br)
if err != nil {
r.Close()
return nil, err
}
return &NBFReader{r, br, sch}, nil
}
// ReadRow reads a row from a table. If there is a bad row the returned error will be non nil, and callin IsBadRow(err)
// will be return true. This is a potentially non-fatal error and callers can decide if they want to continue on a bad row, or fail.
func (nbfr *NBFReader) ReadRow() (*table.Row, error) {
sch := nbfr.sch
numFields := sch.NumFields()
fieldVals := make([]types.Value, 0, numFields)
var err error
for i := 0; i < numFields; i++ {
f := sch.GetField(i)
var val types.Value
val, err = types.ReadValue(types.NomsKind(f.NomsKind()), nbfr.bRd)
if val != nil {
fieldVals = append(fieldVals, val)
}
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
}
numFieldVals := len(fieldVals)
if numFieldVals == 0 {
return nil, err
} else if numFieldVals != numFields {
if err == nil {
err = table.NewBadRow(nil, fmt.Sprintf("Schema specifies %d fields but row has %d values.", numFields, numFieldVals))
}
return nil, err
}
return table.NewRow(table.RowDataFromValues(nbfr.sch, fieldVals)), nil
}
// GetSchema gets the schema of the rows that this writer writes
func (nbfr *NBFReader) GetSchema() *schema.Schema {
return nbfr.sch
}
// Close should release resources being held
func (nbfr *NBFReader) Close() error {
if nbfr.closer != nil {
err := nbfr.closer.Close()
nbfr.closer = nil
return err
} else {
return errors.New("Already closed.")
}
}

View File

@@ -1,157 +0,0 @@
package nbf
import (
"encoding/binary"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/iohelp"
"io"
)
// ReadBinarySchema reads a schema.Schema from an io.Reader
func ReadBinarySchema(r io.Reader) (*schema.Schema, error) {
epr := &iohelp.ErrPreservingReader{r, nil}
var numFields uint16
err := binary.Read(epr, binary.BigEndian, &numFields)
if err == nil {
fields := make([]*schema.Field, numFields)
for i := uint16(0); i < numFields && err == nil; i++ {
fld, err := readField(epr)
if err != nil {
return nil, epr.Err
}
fields[i] = fld
}
sch := schema.NewSchema(fields)
var numConstraints uint16
err = binary.Read(epr, binary.BigEndian, &numConstraints)
if err == nil {
for i := uint16(0); i < numConstraints && err == nil; i++ {
cnst, err := readConstraint(epr)
if err != nil {
return nil, epr.Err
}
sch.AddConstraint(cnst)
}
return sch, nil
}
}
return nil, epr.Err
}
func readField(r *iohelp.ErrPreservingReader) (*schema.Field, error) {
var size uint8
err := binary.Read(r, binary.BigEndian, &size)
if err == nil {
nameBytes := make([]byte, size)
_, err = r.Read(nameBytes)
var kind types.NomsKind
var required bool
err = binary.Read(r, binary.BigEndian, &kind)
err = binary.Read(r, binary.BigEndian, &required)
if err == nil {
f := schema.NewField(string(nameBytes), kind, required)
return f, nil
}
}
return nil, err
}
func readConstraint(r *iohelp.ErrPreservingReader) (*schema.Constraint, error) {
var size uint8
var err error
if err = binary.Read(r, binary.BigEndian, &size); err == nil {
var cTypeBytes []byte
cTypeBytes, err = iohelp.ReadNBytes(r, int(size))
if err = binary.Read(r, binary.BigEndian, &size); err == nil {
var fldIndexBytes []byte
fldIndexBytes, err = iohelp.ReadNBytes(r, int(size))
if err == nil {
fldIndices := make([]int, len(fldIndexBytes))
for i := 0; i < len(fldIndexBytes); i++ {
fldIndices[i] = int(fldIndexBytes[i])
}
return schema.NewConstraint(schema.ConstraintType(cTypeBytes), fldIndices), nil
}
}
}
return nil, err
}
// WriteBinarySchema writes a schema.Schema to an io.Writer
func WriteBinarySchema(sch *schema.Schema, w io.Writer) error {
err := binary.Write(w, binary.BigEndian, uint16(sch.NumFields()))
if err == nil {
for i := 0; i < sch.NumFields(); i++ {
f := sch.GetField(i)
err = writeCol(f, w)
if err != nil {
return err
}
}
err = binary.Write(w, binary.BigEndian, uint16(sch.TotalNumConstraints()))
if err == nil {
for i := 0; i < sch.TotalNumConstraints(); i++ {
constraint := sch.GetConstraint(i)
err = writeConstraint(constraint, w)
if err != nil {
return err
}
}
}
}
return err
}
func writeCol(f *schema.Field, w io.Writer) error {
name := f.NameStr()
nameSize := uint8(len(name))
err := iohelp.WritePrimIfNoErr(w, nameSize, nil)
err = iohelp.WriteIfNoErr(w, []byte(name), err)
err = iohelp.WritePrimIfNoErr(w, f.NomsKind(), err)
return iohelp.WritePrimIfNoErr(w, f.IsRequired(), err)
}
func writeConstraint(constraint *schema.Constraint, w io.Writer) error {
cTypeStr := string(constraint.ConType())
cTypeStrLen := uint8(len(cTypeStr))
err := iohelp.WritePrimIfNoErr(w, cTypeStrLen, nil)
err = iohelp.WriteIfNoErr(w, []byte(cTypeStr), err)
fldIndices := constraint.FieldIndices()
indices := make([]byte, 0, len(fldIndices))
for _, idx := range fldIndices {
indices = append(indices, byte(idx))
}
err = iohelp.WritePrimIfNoErr(w, uint8(len(indices)), err)
err = iohelp.WriteIfNoErr(w, indices, err)
return err
}

View File

@@ -1,74 +0,0 @@
package nbf
import (
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"testing"
)
func createTestSchema() *schema.Schema {
fields := []*schema.Field{
schema.NewField("id", types.UUIDKind, true),
schema.NewField("first", types.StringKind, true),
schema.NewField("last", types.StringKind, true),
schema.NewField("age", types.UintKind, false),
}
sch := schema.NewSchema(fields)
sch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
return sch
}
func TestNBFEncoding(t *testing.T) {
tSchema := createTestSchema()
schemaPath := "schema.nbf"
fs := filesys.NewInMemFS([]string{"/"}, nil, "/")
nbfOut, err := fs.OpenForWrite(schemaPath)
if err != nil {
t.Fatal("Failed to open file.", err)
}
func() {
defer nbfOut.Close()
err = WriteBinarySchema(tSchema, nbfOut)
}()
if err != nil {
t.Fatal("Failed to write file.", err)
}
nbfIn, err := fs.OpenForRead(schemaPath)
if err != nil {
t.Fatal("Unable to open file.", err)
}
var resultSch *schema.Schema
func() {
defer nbfIn.Close()
resultSch, err = ReadBinarySchema(nbfIn)
}()
if err != nil {
t.Fatal("Failed to read schema.")
}
if !tSchema.Equals(resultSch) {
t.Errorf("Value changed between serializing and deserializing.")
}
if tSchema.NumFields() != 4 {
t.Error("Unexpected column count in schema")
}
idIdx := tSchema.GetFieldIndex("id")
unMarshalledPK := tSchema.GetField(idIdx)
if unMarshalledPK.NameStr() != "id" || unMarshalledPK.NomsKind() != types.UUIDKind || !unMarshalledPK.IsRequired() {
t.Error("Unmarshalled PK does not match the initial PK")
}
}

View File

@@ -1,117 +0,0 @@
package nbf
import (
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"testing"
)
var typedSchema = schema.NewSchema([]*schema.Field{
schema.NewField("id", types.UUIDKind, true),
schema.NewField("name", types.StringKind, true),
schema.NewField("age", types.UintKind, true),
schema.NewField("title", types.StringKind, false),
})
func init() {
typedSchema.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
}
var uuids = []uuid.UUID{
uuid.Must(uuid.Parse("00000000-0000-0000-0000-000000000000")),
uuid.Must(uuid.Parse("00000000-0000-0000-0000-000000000001")),
uuid.Must(uuid.Parse("00000000-0000-0000-0000-000000000002"))}
var names = []string{"Bill Billerson", "John Johnson", "Rob Robertson"}
var ages = []uint{32, 25, 21}
var titles = []string{"Senior Dufus", "Dufus", ""}
func createRows() []*table.Row {
rows := make([]*table.Row, len(names))
for i := 0; i < len(names); i++ {
rowValMap := map[string]types.Value{
"id": types.UUID(uuids[i]),
"name": types.String(names[i]),
"age": types.Uint(ages[i]),
"title": types.String(titles[i]),
}
rows[i] = table.NewRow(table.RowDataFromValMap(typedSchema, rowValMap))
}
return rows
}
func TestNBFReadWrite(t *testing.T) {
ReadBufSize = 128
WriteBufSize = 128
path := "/file.nbf"
fs := filesys.NewInMemFS([]string{"/"}, nil, "/")
rows := createRows()
nbfWr, err := OpenNBFWriter(path, fs, typedSchema)
if err != nil {
t.Fatal("Could create file")
}
func() {
defer func() {
err := nbfWr.Close()
if err != nil {
t.Fatal("Failed to close.")
}
}()
for i := 0; i < len(rows); i++ {
err = nbfWr.WriteRow(rows[i])
if err != nil {
t.Fatal("Failed to write row.", err)
}
}
}()
nbfRd, err := OpenNBFReader(path, fs)
if err != nil {
t.Fatal("Failed to open reader")
}
var numBad int
var results []*table.Row
func() {
defer func() {
err := nbfRd.Close()
if err != nil {
t.Fatal("Close Failure")
}
}()
results, numBad, err = table.ReadAllRows(nbfRd, true)
}()
if numBad != 0 {
t.Error("Unexpected bad rows")
}
if err != nil {
t.Fatal("Error reading")
}
if len(results) != len(rows) {
t.Error("Unexpected row count")
}
for i := 0; i < len(rows); i++ {
row := rows[i]
resRow := results[i]
if !table.RowsEqualIgnoringSchema(row, resRow) {
t.Error(table.RowFmt(row), "!=", table.RowFmt(resRow))
}
}
}

View File

@@ -1,122 +0,0 @@
package nbf
import (
"bufio"
"bytes"
"errors"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"io"
"path/filepath"
"strings"
)
var WriteBufSize = 256 * 1024
// NBFWriter writes data serialized as binary noms values. NBFWriter will only write data if it is sorted by primary key.
type NBFWriter struct {
closer io.Closer
bWr *bufio.Writer
sch *schema.Schema
lastPK types.Value
}
// OpenNBFWriter opens a file within the provided filesystem for writing and writes the schema of the rows that will
// follow on subsequent calls to WriteRow. The schema must include a primary key constraint.
func OpenNBFWriter(path string, fs filesys.WritableFS, outSch *schema.Schema) (*NBFWriter, error) {
if outSch.GetPKIndex() == -1 {
// Do not allow this function to be called if you aren't going to provide a valid output schema with a primary
// key constraint.
panic("Only schemas containing a primary key constraint can be used with nbf files.")
}
err := fs.MkDirs(filepath.Dir(path))
if err != nil {
return nil, err
}
wr, err := fs.OpenForWrite(path)
if err != nil {
return nil, err
}
return NewNBFWriter(wr, outSch)
}
// NewNBFWriter creates a new NBFWriter which will write to the provided write closer. Closing the NBFWriter will cause
// the supplied io.WriteCloser to be closed also.
func NewNBFWriter(wr io.WriteCloser, outSch *schema.Schema) (*NBFWriter, error) {
bwr := bufio.NewWriterSize(wr, WriteBufSize)
err := WriteBinarySchema(outSch, wr)
if err != nil {
wr.Close()
return nil, err
}
return &NBFWriter{wr, bwr, outSch, nil}, nil
}
// GetSchema gets the schema of the rows that this writer writes
func (nbfw *NBFWriter) GetSchema() *schema.Schema {
return nbfw.sch
}
// WriteRow will write a row to a table
func (nbfw *NBFWriter) WriteRow(row *table.Row) error {
sch := row.GetSchema()
if sch.NumFields() != nbfw.sch.NumFields() {
return table.NewBadRow(row, "Output schema does not match row schema")
} else if !table.RowIsValid(row) {
badFlds := table.InvalidFieldsForRow(row)
fldsStr := strings.Join(badFlds, ",")
return table.NewBadRow(row, "Row missing some required fields: "+fldsStr)
}
pk := table.GetPKFromRow(row)
if nbfw.lastPK == nil || nbfw.lastPK.Less(pk) {
rowData := row.CurrData()
// Use a buffer instead of the buffered writer in case there is an issue with a column in the field. If an err
// occurs serializing a single value for the row then we return without having written any data. If all values are
// successfully serialized to the buffer, then we write the full wrote to the output writer.
buf := bytes.NewBuffer(make([]byte, 0, 2048))
for i := 0; i < sch.NumFields(); i++ {
val, _ := rowData.GetField(i)
_, err := types.WriteValue(val, buf)
if err != nil {
return err
}
}
nbfw.bWr.Write(buf.Bytes())
nbfw.lastPK = pk
return nil
}
return errors.New("Primary keys out of order.")
}
// Close should flush all writes, release resources being held
func (nbfw *NBFWriter) Close() error {
if nbfw.closer != nil {
errFl := nbfw.bWr.Flush()
errCl := nbfw.closer.Close()
nbfw.closer = nil
if errCl != nil {
return errCl
}
return errFl
} else {
return errors.New("Already closed.")
}
}

View File

@@ -4,15 +4,15 @@ import (
"errors"
"fmt"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
)
// NomsMapUpdater is a TableWriter that creates a new noms types.Map. It is backed by a StreamingMap which requires
// the rows to be written in order. If the keys being written to WriteRow are not sorted an error will be returned from
// WriteRow. Once all rows are written Close() should be called and GetMap will then return the new map.
type NomsMapCreator struct {
sch *schema.Schema
sch schema.Schema
vrw types.ValueReadWriter
lastPK types.Value
@@ -23,7 +23,7 @@ type NomsMapCreator struct {
}
// NewNomsMapCreator creates a new NomsMapCreator.
func NewNomsMapCreator(vrw types.ValueReadWriter, sch *schema.Schema) *NomsMapCreator {
func NewNomsMapCreator(vrw types.ValueReadWriter, sch schema.Schema) *NomsMapCreator {
kvsChan := make(chan types.Value)
mapChan := types.NewStreamingMap(vrw, kvsChan)
@@ -31,13 +31,13 @@ func NewNomsMapCreator(vrw types.ValueReadWriter, sch *schema.Schema) *NomsMapCr
}
// GetSchema gets the schema of the rows that this writer writes
func (nmc *NomsMapCreator) GetSchema() *schema.Schema {
func (nmc *NomsMapCreator) GetSchema() schema.Schema {
return nmc.sch
}
// WriteRow will write a row to a table. The primary key for each row must be greater than the primary key of the row
// written before it.
func (nmc *NomsMapCreator) WriteRow(row *table.Row) error {
func (nmc *NomsMapCreator) WriteRow(r row.Row) error {
if nmc.kvsChan == nil {
panic("Attempting to write after closing.")
}
@@ -50,8 +50,8 @@ func (nmc *NomsMapCreator) WriteRow(row *table.Row) error {
}
}()
pk := table.GetPKFromRow(row)
fieldVals := table.GetNonPKFieldListFromRow(row, nmc.vrw)
pk := r.NomsMapKey(nmc.sch)
fieldVals := r.NomsMapValue(nmc.sch)
if nmc.lastPK == nil || nmc.lastPK.Less(pk) {
nmc.kvsChan <- pk
nmc.kvsChan <- fieldVals

View File

@@ -4,14 +4,14 @@ import (
"errors"
"fmt"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
)
// NomsMapUpdater is a TableWriter that writes rows to a noms types.Map. Once all rows are written Close() should be
// called and GetMap will then return the new map.
type NomsMapUpdater struct {
sch *schema.Schema
sch schema.Schema
vrw types.ValueReadWriter
me *types.MapEditor
@@ -19,8 +19,8 @@ type NomsMapUpdater struct {
}
// NewNomsMapUpdater creates a new NomsMapUpdater for a given map.
func NewNomsMapUpdater(vrw types.ValueReadWriter, m types.Map, sch *schema.Schema) *NomsMapUpdater {
if sch.GetPKIndex() == -1 {
func NewNomsMapUpdater(vrw types.ValueReadWriter, m types.Map, sch schema.Schema) *NomsMapUpdater {
if sch.GetPKCols().Size() == 0 {
panic("NomsMapUpdater requires a schema with a primary key.")
}
@@ -30,12 +30,12 @@ func NewNomsMapUpdater(vrw types.ValueReadWriter, m types.Map, sch *schema.Schem
}
// GetSchema gets the schema of the rows that this writer writes
func (nmu *NomsMapUpdater) GetSchema() *schema.Schema {
func (nmu *NomsMapUpdater) GetSchema() schema.Schema {
return nmu.sch
}
// WriteRow will write a row to a table
func (nmu *NomsMapUpdater) WriteRow(row *table.Row) error {
func (nmu *NomsMapUpdater) WriteRow(r row.Row) error {
if nmu.me == nil {
panic("Attempting to write after closing.")
}
@@ -48,8 +48,8 @@ func (nmu *NomsMapUpdater) WriteRow(row *table.Row) error {
}
}()
pk := table.GetPKFromRow(row)
fieldVals := table.GetNonPKFieldListFromRow(row, nmu.vrw)
pk := r.NomsMapKey(nmu.sch)
fieldVals := r.NomsMapValue(nmu.sch)
nmu.me = nmu.me.Set(pk, fieldVals)
}()

View File

@@ -4,21 +4,30 @@ import (
"github.com/attic-labs/noms/go/spec"
"github.com/attic-labs/noms/go/types"
"github.com/google/uuid"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"testing"
)
var sch = schema.NewSchema([]*schema.Field{
schema.NewField("id", types.UUIDKind, true),
schema.NewField("name", types.StringKind, true),
schema.NewField("age", types.UintKind, true),
schema.NewField("title", types.StringKind, false),
})
const (
idCol = "id"
nameCol = "name"
ageCol = "age"
titleCol = "title"
idColTag = 4
nameColTag = 3
ageColTag = 2
titleColTag = 1
)
func init() {
sch.AddConstraint(schema.NewConstraint(schema.PrimaryKey, []int{0}))
}
var colColl, _ = schema.NewColCollection(
schema.NewColumn(idCol, idColTag, types.UUIDKind, true, schema.NotNullConstraint{}),
schema.NewColumn(nameCol, nameColTag, types.StringKind, false),
schema.NewColumn(ageCol, ageColTag, types.UintKind, false),
schema.NewColumn(titleCol, titleColTag, types.StringKind, false),
)
var sch = schema.SchemaFromCols(colColl)
var uuids = []uuid.UUID{
uuid.Must(uuid.Parse("00000000-0000-0000-0000-000000000000")),
@@ -31,8 +40,8 @@ var titles = []string{"Senior Dufus", "Dufus", ""}
var updatedIndices = []bool{false, true, true}
var updatedAges = []uint{0, 26, 20}
func createRows(onlyUpdated, updatedAge bool) []*table.Row {
rows := make([]*table.Row, 0, len(names))
func createRows(onlyUpdated, updatedAge bool) []row.Row {
rows := make([]row.Row, 0, len(names))
for i := 0; i < len(names); i++ {
if !onlyUpdated || updatedIndices[i] {
age := ages[i]
@@ -40,13 +49,13 @@ func createRows(onlyUpdated, updatedAge bool) []*table.Row {
age = updatedAges[i]
}
rowValMap := map[string]types.Value{
"id": types.UUID(uuids[i]),
"name": types.String(names[i]),
"age": types.Uint(age),
"title": types.String(titles[i]),
rowVals := row.TaggedValues{
idColTag: types.UUID(uuids[i]),
nameColTag: types.String(names[i]),
ageColTag: types.Uint(age),
titleColTag: types.String(titles[i]),
}
rows = append(rows, table.NewRow(table.RowDataFromValMap(sch, rowValMap)))
rows = append(rows, row.New(sch, rowVals))
}
}
@@ -69,19 +78,19 @@ func TestReadWrite(t *testing.T) {
testReadAndCompare(t, updatedMap, expectedRows)
}
func testNomsMapCreator(t *testing.T, vrw types.ValueReadWriter, rows []*table.Row) *types.Map {
func testNomsMapCreator(t *testing.T, vrw types.ValueReadWriter, rows []row.Row) *types.Map {
mc := NewNomsMapCreator(vrw, sch)
return testNomsWriteCloser(t, mc, rows)
}
func testNomsMapUpdate(t *testing.T, vrw types.ValueReadWriter, initialMapVal *types.Map, rows []*table.Row) *types.Map {
func testNomsMapUpdate(t *testing.T, vrw types.ValueReadWriter, initialMapVal *types.Map, rows []row.Row) *types.Map {
mu := NewNomsMapUpdater(vrw, *initialMapVal, sch)
return testNomsWriteCloser(t, mu, rows)
}
func testNomsWriteCloser(t *testing.T, nwc NomsMapWriteCloser, rows []*table.Row) *types.Map {
for _, row := range rows {
err := nwc.WriteRow(row)
func testNomsWriteCloser(t *testing.T, nwc NomsMapWriteCloser, rows []row.Row) *types.Map {
for _, r := range rows {
err := nwc.WriteRow(r)
if err != nil {
t.Error("Failed to write row.", err)
@@ -119,7 +128,7 @@ func testNomsWriteCloser(t *testing.T, nwc NomsMapWriteCloser, rows []*table.Row
return mapVal
}
func testReadAndCompare(t *testing.T, initialMapVal *types.Map, expectedRows []*table.Row) {
func testReadAndCompare(t *testing.T, initialMapVal *types.Map, expectedRows []row.Row) {
mr := NewNomsMapReader(*initialMapVal, sch)
actualRows, numBad, err := table.ReadAllRows(mr, true)
@@ -136,8 +145,8 @@ func testReadAndCompare(t *testing.T, initialMapVal *types.Map, expectedRows []*
}
for i := 0; i < len(expectedRows); i++ {
if !table.RowsEqualIgnoringSchema(actualRows[i], expectedRows[i]) {
t.Error(table.RowFmt(actualRows[i]), "!=", table.RowFmt(expectedRows[i]))
if !row.AreEqual(actualRows[i], expectedRows[i], sch) {
t.Error(row.Fmt(actualRows[i], sch), "!=", row.Fmt(expectedRows[i], sch))
}
}
}

View File

@@ -3,33 +3,33 @@ package noms
import (
"errors"
"github.com/attic-labs/noms/go/types"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
"io"
)
// NomsMapReader is a TableReader that reads rows from a noms table which is stored in a types.Map where the key is
// a types.Value and the value is a types.Tuple of field values.
type NomsMapReader struct {
sch *schema.Schema
sch schema.Schema
itr types.MapIterator
}
// NewNomsMapReader creates a NomsMapReader for a given noms types.Map
func NewNomsMapReader(m types.Map, sch *schema.Schema) *NomsMapReader {
func NewNomsMapReader(m types.Map, sch schema.Schema) *NomsMapReader {
itr := m.Iterator()
return &NomsMapReader{sch, itr}
}
// GetSchema gets the schema of the rows that this reader will return
func (nmr *NomsMapReader) GetSchema() *schema.Schema {
func (nmr *NomsMapReader) GetSchema() schema.Schema {
return nmr.sch
}
// ReadRow reads a row from a table. If there is a bad row the returned error will be non nil, and callin IsBadRow(err)
// will be return true. This is a potentially non-fatal error and callers can decide if they want to continue on a bad row, or fail.
func (nmr *NomsMapReader) ReadRow() (*table.Row, error) {
func (nmr *NomsMapReader) ReadRow() (row.Row, error) {
var key types.Value
var val types.Value
var err error
@@ -48,11 +48,7 @@ func (nmr *NomsMapReader) ReadRow() (*table.Row, error) {
return nil, io.EOF
}
if valList, ok := val.(types.Tuple); !ok {
return nil, errors.New("Map value is not a tuple. This map is not a valid Dolt table.")
} else {
return table.NewRow(table.RowDataFromPKAndValueList(nmr.sch, key, valList)), nil
}
return row.FromNoms(nmr.sch, key.(types.Tuple), val.(types.Tuple)), nil
}
// Close should release resources being held

Some files were not shown because too many files have changed in this diff Show More