Skip to content

Commit 6c0d0b3

Browse files
authored
feat(arrow): Pretty-print field changes (#817)
Closes cloudquery/cloudquery#10136
1 parent fb2929f commit 6c0d0b3

File tree

2 files changed

+212
-3
lines changed

2 files changed

+212
-3
lines changed

schema/arrow.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,52 @@ type FieldChange struct {
3030
Previous arrow.Field
3131
}
3232

33+
func (fc FieldChange) String() string {
34+
switch fc.Type {
35+
case TableColumnChangeTypeAdd:
36+
return "+ " + fieldPrettify(fc.Current)
37+
case TableColumnChangeTypeRemove:
38+
return "- " + fieldPrettify(fc.Previous)
39+
case TableColumnChangeTypeUpdate:
40+
return "~ " + fieldPrettify(fc.Previous) + " -> " + fieldPrettify(fc.Current)
41+
default:
42+
return "? " + fieldPrettify(fc.Previous) + " -> " + fieldPrettify(fc.Current)
43+
}
44+
}
45+
46+
type FieldChanges []FieldChange
47+
48+
func (fc FieldChanges) String() string {
49+
builder := new(strings.Builder)
50+
for i, c := range fc {
51+
builder.WriteString(c.String())
52+
if i < len(fc)-1 {
53+
builder.WriteString("\n")
54+
}
55+
}
56+
return builder.String()
57+
}
58+
59+
func fieldPrettify(field arrow.Field) string {
60+
builder := new(strings.Builder)
61+
builder.WriteString(field.Name)
62+
builder.WriteString(": ")
63+
64+
if field.Nullable {
65+
builder.WriteString("nullable(")
66+
}
67+
builder.WriteString(field.Type.String())
68+
if field.Nullable {
69+
builder.WriteString(")")
70+
}
71+
72+
if field.HasMetadata() {
73+
builder.WriteString(", metadata: ")
74+
builder.WriteString(field.Metadata.String())
75+
}
76+
return builder.String()
77+
}
78+
3379
type MetadataFieldOptions struct {
3480
PrimaryKey bool
3581
Unique bool
@@ -133,9 +179,9 @@ func TableName(sc *arrow.Schema) string {
133179
return name
134180
}
135181

136-
// Get changes return changes between two schemas
137-
func GetSchemaChanges(target *arrow.Schema, source *arrow.Schema) []FieldChange {
138-
var changes []FieldChange
182+
// GetSchemaChanges returns changes between two schemas
183+
func GetSchemaChanges(target *arrow.Schema, source *arrow.Schema) FieldChanges {
184+
var changes FieldChanges
139185
for _, t := range target.Fields() {
140186
sourceField, ok := source.FieldsByName(t.Name)
141187
if !ok {

schema/arrow_test.go

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package schema
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/apache/arrow/go/v12/arrow"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestFieldChange_String(t *testing.T) {
12+
type testCase struct {
13+
change FieldChange
14+
expected string
15+
}
16+
17+
for _, tc := range []testCase{
18+
{
19+
change: FieldChange{
20+
Type: TableColumnChangeTypeUnknown,
21+
ColumnName: "name",
22+
Current: arrow.Field{
23+
Name: "name",
24+
Type: new(arrow.BooleanType),
25+
Metadata: NewFieldMetadataFromOptions(MetadataFieldOptions{
26+
PrimaryKey: true,
27+
Unique: true,
28+
}),
29+
},
30+
Previous: arrow.Field{
31+
Name: "name",
32+
Type: new(arrow.BooleanType),
33+
Nullable: true,
34+
},
35+
},
36+
expected: `? name: nullable(bool) -> name: bool, metadata: ["cq:extension:primary_key": "true", "cq:extension:unique": "true"]`,
37+
},
38+
{
39+
change: FieldChange{
40+
Type: TableColumnChangeTypeAdd,
41+
ColumnName: "name",
42+
Current: arrow.Field{
43+
Name: "name",
44+
Type: new(arrow.BooleanType),
45+
Metadata: NewFieldMetadataFromOptions(MetadataFieldOptions{
46+
PrimaryKey: true,
47+
Unique: true,
48+
}),
49+
},
50+
},
51+
expected: `+ name: bool, metadata: ["cq:extension:primary_key": "true", "cq:extension:unique": "true"]`,
52+
},
53+
{
54+
change: FieldChange{
55+
Type: TableColumnChangeTypeUpdate,
56+
ColumnName: "name",
57+
Current: arrow.Field{
58+
Name: "name",
59+
Type: new(arrow.BooleanType),
60+
Metadata: NewFieldMetadataFromOptions(MetadataFieldOptions{
61+
PrimaryKey: true,
62+
Unique: true,
63+
}),
64+
},
65+
Previous: arrow.Field{
66+
Name: "name",
67+
Type: new(arrow.BooleanType),
68+
Nullable: true,
69+
},
70+
},
71+
expected: `~ name: nullable(bool) -> name: bool, metadata: ["cq:extension:primary_key": "true", "cq:extension:unique": "true"]`,
72+
},
73+
{
74+
change: FieldChange{
75+
Type: TableColumnChangeTypeRemove,
76+
ColumnName: "name",
77+
Previous: arrow.Field{
78+
Name: "name",
79+
Type: new(arrow.BooleanType),
80+
Nullable: true,
81+
},
82+
},
83+
expected: `- name: nullable(bool)`,
84+
},
85+
} {
86+
require.Equal(t, tc.expected, tc.change.String())
87+
}
88+
}
89+
90+
func TestFieldChanges_String(t *testing.T) {
91+
changes := FieldChanges{
92+
{
93+
Type: TableColumnChangeTypeUnknown,
94+
ColumnName: "unknown",
95+
Current: arrow.Field{
96+
Name: "unknown",
97+
Type: new(arrow.BooleanType),
98+
Metadata: NewFieldMetadataFromOptions(MetadataFieldOptions{
99+
PrimaryKey: true,
100+
Unique: true,
101+
}),
102+
},
103+
Previous: arrow.Field{
104+
Name: "unknown",
105+
Type: new(arrow.BooleanType),
106+
Nullable: true,
107+
},
108+
},
109+
{
110+
Type: TableColumnChangeTypeAdd,
111+
ColumnName: "add",
112+
Current: arrow.Field{
113+
Name: "add",
114+
Type: new(arrow.BooleanType),
115+
Metadata: NewFieldMetadataFromOptions(MetadataFieldOptions{
116+
PrimaryKey: true,
117+
Unique: true,
118+
}),
119+
},
120+
},
121+
{
122+
Type: TableColumnChangeTypeUpdate,
123+
ColumnName: "update",
124+
Current: arrow.Field{
125+
Name: "update",
126+
Type: new(arrow.BooleanType),
127+
Metadata: NewFieldMetadataFromOptions(MetadataFieldOptions{
128+
PrimaryKey: true,
129+
Unique: true,
130+
}),
131+
},
132+
Previous: arrow.Field{
133+
Name: "update",
134+
Type: new(arrow.BooleanType),
135+
Nullable: true,
136+
},
137+
},
138+
{
139+
Type: TableColumnChangeTypeRemove,
140+
ColumnName: "remove",
141+
Current: arrow.Field{
142+
Name: "remove",
143+
Type: new(arrow.BooleanType),
144+
Metadata: NewFieldMetadataFromOptions(MetadataFieldOptions{
145+
PrimaryKey: true,
146+
Unique: true,
147+
}),
148+
},
149+
Previous: arrow.Field{
150+
Name: "remove",
151+
Type: new(arrow.BooleanType),
152+
Nullable: true,
153+
},
154+
},
155+
}
156+
157+
const expected = `? unknown: nullable(bool) -> unknown: bool, metadata: ["cq:extension:primary_key": "true", "cq:extension:unique": "true"]
158+
+ add: bool, metadata: ["cq:extension:primary_key": "true", "cq:extension:unique": "true"]
159+
~ update: nullable(bool) -> update: bool, metadata: ["cq:extension:primary_key": "true", "cq:extension:unique": "true"]
160+
- remove: nullable(bool)`
161+
require.Equal(t, expected, changes.String())
162+
require.Equal(t, expected, fmt.Sprintf("%v", changes))
163+
}

0 commit comments

Comments
 (0)