Skip to content

Commit 137e18b

Browse files
committed
fix(format): Detect format change when saving, either error or rewrite
When a save command is run, and the new format is different from the format of the previous version, either return an error, or if the ConvertFormatToPrev flag is set on SaveParams, rewrite the body to match the previous version’s format. The API always sets ConvertFormatToPrev, but the command line does not (yet).
1 parent 88a25ab commit 137e18b

File tree

8 files changed

+76
-25
lines changed

8 files changed

+76
-25
lines changed

actions/actions_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func addCitiesDataset(t *testing.T, node *p2p.QriNode) repo.DatasetRef {
105105
dsp.Name = tc.Name
106106
dsp.BodyBytes = tc.Body
107107

108-
ref, _, err := SaveDataset(node, dsp, nil, false, true)
108+
ref, _, err := SaveDataset(node, dsp, nil, false, true, false)
109109
if err != nil {
110110
t.Fatal(err.Error())
111111
}
@@ -121,7 +121,7 @@ func addFlourinatedCompoundsDataset(t *testing.T, node *p2p.QriNode) repo.Datase
121121
dsp.Name = tc.Name
122122
dsp.BodyBytes = tc.Body
123123

124-
ref, _, err := SaveDataset(node, dsp, nil, false, true)
124+
ref, _, err := SaveDataset(node, dsp, nil, false, true, false)
125125
if err != nil {
126126
t.Fatal(err.Error())
127127
}
@@ -137,7 +137,7 @@ func addNowTransformDataset(t *testing.T, node *p2p.QriNode) repo.DatasetRef {
137137
dsp.Name = tc.Name
138138
dsp.Transform.ScriptPath = "testdata/now_tf/transform.star"
139139

140-
ref, _, err := SaveDataset(node, dsp, nil, false, true)
140+
ref, _, err := SaveDataset(node, dsp, nil, false, true, false)
141141
if err != nil {
142142
t.Fatal(err.Error())
143143
}

actions/dataset.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
)
1818

1919
// SaveDataset initializes a dataset from a dataset pointer and data file
20-
func SaveDataset(node *p2p.QriNode, changesPod *dataset.DatasetPod, secrets map[string]string, dryRun, pin bool) (ref repo.DatasetRef, body cafs.File, err error) {
20+
func SaveDataset(node *p2p.QriNode, changesPod *dataset.DatasetPod, secrets map[string]string, dryRun, pin bool, convertFormatToPrev bool) (ref repo.DatasetRef, body cafs.File, err error) {
2121
var (
2222
prev *dataset.Dataset
2323
prevPath string
@@ -33,12 +33,13 @@ func SaveDataset(node *p2p.QriNode, changesPod *dataset.DatasetPod, secrets map[
3333
return
3434
}
3535

36+
pro, err = r.Profile()
37+
if err != nil {
38+
return
39+
}
40+
3641
if dryRun {
3742
node.LocalStreams.Print("🏃🏽‍♀️ dry run\n")
38-
pro, err = r.Profile()
39-
if err != nil {
40-
return
41-
}
4243
// dry-runs store to an in-memory repo
4344
r, err = repo.NewMemRepo(pro, cafs.NewMapstore(), profile.NewMemStore(), nil)
4445
if err != nil {
@@ -90,6 +91,26 @@ func SaveDataset(node *p2p.QriNode, changesPod *dataset.DatasetPod, secrets map[
9091
node.LocalStreams.Print("✅ transform complete\n")
9192
}
9293

94+
// Infer any values about the incoming change before merging it with the previous version.
95+
changeBodyFile, err = base.InferValues(pro, &changesPod.Name, changes, changeBodyFile)
96+
if err != nil {
97+
return
98+
}
99+
100+
if prev.Structure != nil && changes.Structure != nil && prev.Structure.Format != changes.Structure.Format {
101+
if convertFormatToPrev {
102+
changeBodyFile, err = base.ConvertBodyFormat(changeBodyFile, changes.Structure,
103+
prev.Structure)
104+
if err != nil {
105+
return
106+
}
107+
} else {
108+
err = fmt.Errorf("Refusing to change structure from %s to %s",
109+
prev.Structure.Format, changes.Structure.Format)
110+
return
111+
}
112+
}
113+
93114
// apply changes to the previous path, set changes to the result
94115
prev.Assign(changes)
95116
changes = prev

actions/dataset_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func TestSaveDataset(t *testing.T) {
126126
BodyBytes: []byte("[]"),
127127
}
128128

129-
ref, _, err := SaveDataset(n, ds, nil, true, false)
129+
ref, _, err := SaveDataset(n, ds, nil, true, false, false)
130130
if err != nil {
131131
t.Errorf("dry run error: %s", err.Error())
132132
}
@@ -147,7 +147,7 @@ func TestSaveDataset(t *testing.T) {
147147
Structure: &dataset.StructurePod{Format: dataset.JSONDataFormat.String(), Schema: map[string]interface{}{"type": "array"}},
148148
BodyBytes: []byte("[]"),
149149
}
150-
ref, _, err = SaveDataset(n, ds, nil, false, true)
150+
ref, _, err = SaveDataset(n, ds, nil, false, true, false)
151151
if err != nil {
152152
t.Error(err)
153153
}
@@ -176,7 +176,7 @@ func TestSaveDataset(t *testing.T) {
176176
ds.set_body(bd)`),
177177
},
178178
}
179-
ref, _, err = SaveDataset(n, ds, secrets, false, true)
179+
ref, _, err = SaveDataset(n, ds, secrets, false, true, false)
180180
if err != nil {
181181
t.Fatal(err)
182182
}
@@ -194,7 +194,7 @@ func TestSaveDataset(t *testing.T) {
194194
Description: "updated description",
195195
},
196196
}
197-
ref, _, err = SaveDataset(n, ds, nil, false, true)
197+
ref, _, err = SaveDataset(n, ds, nil, false, true, false)
198198
if err != nil {
199199
t.Error(err)
200200
}
@@ -217,7 +217,7 @@ func TestSaveDataset(t *testing.T) {
217217
},
218218
Transform: tfds.Transform,
219219
}
220-
ref, _, err = SaveDataset(n, ds, secrets, false, true)
220+
ref, _, err = SaveDataset(n, ds, secrets, false, true, false)
221221
if err != nil {
222222
t.Error(err)
223223
}

api/datasets.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -481,10 +481,11 @@ func (h *DatasetHandlers) saveHandler(w http.ResponseWriter, r *http.Request) {
481481

482482
res := &repo.DatasetRef{}
483483
p := &lib.SaveParams{
484-
Dataset: dsp,
485-
Private: r.FormValue("private") == "true",
486-
DryRun: r.FormValue("dry_run") == "true",
487-
ReturnBody: r.FormValue("return_body") == "true",
484+
Dataset: dsp,
485+
Private: r.FormValue("private") == "true",
486+
DryRun: r.FormValue("dry_run") == "true",
487+
ReturnBody: r.FormValue("return_body") == "true",
488+
ConvertFormatToPrev: true,
488489
}
489490

490491
if r.FormValue("secrets") != "" {

base/dataset.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package base
22

33
import (
4+
"bytes"
45
"fmt"
56
"io/ioutil"
67
"net/http"
@@ -13,6 +14,7 @@ import (
1314
"github.com/qri-io/cafs"
1415
"github.com/qri-io/dataset"
1516
"github.com/qri-io/dataset/dsfs"
17+
"github.com/qri-io/dataset/dsio"
1618
"github.com/qri-io/ioes"
1719
"github.com/qri-io/qri/repo"
1820
"github.com/qri-io/qri/repo/profile"
@@ -74,10 +76,6 @@ func CreateDataset(r repo.Repo, streams ioes.IOStreams, name string, ds *dataset
7476
return
7577
}
7678

77-
if body, err = InferValues(pro, &name, ds, body); err != nil {
78-
return
79-
}
80-
8179
// TODO - we should remove the need for this by having viz always be kept in the right
8280
// state until this point
8381
if err = prepareViz(r, ds); err != nil {
@@ -265,3 +263,32 @@ func DatasetPodBodyFile(store cafs.Filestore, dsp *dataset.DatasetPod) (cafs.Fil
265263

266264
return cafs.NewMemfileReader(filepath.Base(dsp.BodyPath), file), nil
267265
}
266+
267+
// ConvertBodyFormat rewrites a body from a source format to a destination format.
268+
func ConvertBodyFormat(bodyFile cafs.File, fromSt *dataset.Structure, toSt *dataset.Structure) (cafs.File, error) {
269+
// Reader for entries of the source body.
270+
r, err := dsio.NewEntryReader(fromSt, bodyFile)
271+
if err != nil {
272+
return nil, err
273+
}
274+
275+
// Writes entries to a new body.
276+
buffer := bytes.NewBufferString("")
277+
w, err := dsio.NewEntryWriter(toSt, buffer)
278+
if err != nil {
279+
return nil, err
280+
}
281+
282+
err = dsio.Copy(r, w)
283+
if err != nil {
284+
return nil, err
285+
}
286+
err = w.Close()
287+
if err != nil {
288+
return nil, err
289+
}
290+
291+
// Set the new format on the structure.
292+
fromSt.Format = toSt.Format
293+
return cafs.NewMemfileReader("", buffer), nil
294+
}

cmd/save_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func TestSaveRun(t *testing.T) {
141141
{"me/cities", "bad/filpath.json", "", "", "", false, "", "open bad/filpath.json: no such file or directory", ""},
142142
{"me/cities", "", "bad/bodypath.csv", "", "", false, "", "body file: open bad/bodypath.csv: no such file or directory", ""},
143143
{"me/movies", "testdata/movies/dataset.json", "testdata/movies/body_ten.csv", "", "", true, "dataset saved: peer/movies@QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt/map/QmVxUpVVVNedQ645nC25zu6ZtW3yWSiknVmAePLXQ2YSPR\nthis dataset has 1 validation errors\n", "", ""},
144-
{"me/movies", "", "testdata/movies/body_twenty.csv", "Added 10 more rows", "Adding to the number of rows in dataset", true, "dataset saved: peer/movies@QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt/map/QmW8999UBCFoBBwjFiSgm53znp8e5eWEP1kodJeev5CJM9\nthis dataset has 1 validation errors\n", "", ""},
144+
{"me/movies", "", "testdata/movies/body_twenty.csv", "Added 10 more rows", "Adding to the number of rows in dataset", true, "dataset saved: peer/movies@QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt/map/QmPeUYwHEUh3xV5C6k6YvyaDaHix1B7TMbMwG2ffXYYuEX\nthis dataset has 1 validation errors\n", "", ""},
145145
{"me/movies", "", "testdata/movies/body_twenty.csv", "trying to add again", "hopefully this errors", false, "", "error saving: no changes detected", ""},
146146
}
147147

lib/datasets.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ type SaveParams struct {
101101
DryRun bool
102102
// if true, res.Dataset.Body will be a cafs.file of the body
103103
ReturnBody bool
104+
// if true, convert body to the format of the previous version, if applicable
105+
ConvertFormatToPrev bool
104106
// string of references to recall before saving
105107
Recall string
106108
}
@@ -159,7 +161,7 @@ func (r *DatasetRequests) Save(p *SaveParams, res *repo.DatasetRef) (err error)
159161
return fmt.Errorf("no changes to save")
160162
}
161163

162-
ref, body, err := actions.SaveDataset(r.node, ds, p.Secrets, p.DryRun, true)
164+
ref, body, err := actions.SaveDataset(r.node, ds, p.Secrets, p.DryRun, true, p.ConvertFormatToPrev)
163165
if err != nil {
164166
log.Debugf("create ds error: %s\n", err.Error())
165167
return err

lib/lib_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func addCitiesDataset(t *testing.T, node *p2p.QriNode) repo.DatasetRef {
7878
dsp.Name = tc.Name
7979
dsp.BodyBytes = tc.Body
8080

81-
ref, _, err := actions.SaveDataset(node, dsp, nil, false, true)
81+
ref, _, err := actions.SaveDataset(node, dsp, nil, false, true, false)
8282
if err != nil {
8383
t.Fatal(err.Error())
8484
}
@@ -94,7 +94,7 @@ func addNowTransformDataset(t *testing.T, node *p2p.QriNode) repo.DatasetRef {
9494
dsp.Name = tc.Name
9595
dsp.Transform.ScriptPath = "testdata/now_tf/transform.star"
9696

97-
ref, _, err := actions.SaveDataset(node, dsp, nil, false, true)
97+
ref, _, err := actions.SaveDataset(node, dsp, nil, false, true, false)
9898
if err != nil {
9999
t.Fatal(err.Error())
100100
}

0 commit comments

Comments
 (0)