Skip to content

Commit d7649d8

Browse files
authored
fix(transform): Use path instead of field name for PK options (#739)
While using `transformers.WithPrimaryKeys` option there is an odd behavior being observed, where the unwrapped struct field also gets promoted to be PK. Consider the following example: ``` type A struct { ID string B B } type B struct { ID string } ``` Currently, if `transformers.WithPrimaryKeys("ID")` option was used to transform struct `A` we would get the following columns: * `id`: PK, taken from `A` * `b_id`: PK, taken from `B` However, with the current change, the following becomes possible: | `transformers.WithPrimaryKeys` | `id` is PK | `b_id` is PK | | --- | --- | --- | | `"ID"` | `true` | `false` | | `"B.ID"` | `false` | `true` | | `"ID", "B.ID"` | `true` | `true` |
1 parent 72c2cc0 commit d7649d8

2 files changed

Lines changed: 106 additions & 4 deletions

File tree

transformers/struct.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,12 @@ func (t *structTransformer) addColumnFromField(field reflect.StructField, parent
272272
}
273273

274274
for _, pk := range t.pkFields {
275-
if pk == field.Name {
275+
if pk == path {
276+
// use path to allow the following
277+
// 1. Don't duplicate the PK fields if the unwrapped struct contains a fields with the same name
278+
// 2. Allow specifying the nested unwrapped field as part of the PK.
276279
column.CreationOptions.PrimaryKey = true
277-
t.pkFieldsFound = append(t.pkFieldsFound, field.Name)
280+
t.pkFieldsFound = append(t.pkFieldsFound, pk)
278281
}
279282
}
280283

transformers/struct_test.go

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import (
88
"github.com/cloudquery/plugin-sdk/schema"
99
"github.com/google/go-cmp/cmp"
1010
"github.com/google/go-cmp/cmp/cmpopts"
11+
"golang.org/x/exp/slices"
1112
)
1213

1314
type (
1415
embeddedStruct struct {
1516
EmbeddedString string
17+
IntCol int `json:"int_col,omitempty"`
1618
}
1719

1820
testStruct struct {
@@ -45,10 +47,12 @@ type (
4547
*embeddedStruct
4648
}
4749
testStructWithEmbeddedStruct struct {
50+
IntCol int `json:"int_col,omitempty"`
4851
*testStruct
4952
*embeddedStruct
5053
}
5154
testStructWithNonEmbeddedStruct struct {
55+
IntCol int `json:"int_col,omitempty"`
5256
TestStruct *testStruct
5357
NonEmbedded *embeddedStruct
5458
}
@@ -140,23 +144,74 @@ var (
140144
Columns: expectedColumns,
141145
}
142146
expectedTestTableEmbeddedStruct = schema.Table{
147+
Name: "test_struct",
148+
Columns: append(expectedColumns, schema.Column{Name: "embedded_string", Type: schema.TypeString}),
149+
}
150+
expectedTestTableEmbeddedStructWithTopLevelPK = schema.Table{
151+
Name: "test_struct",
152+
Columns: func(base schema.ColumnList) schema.ColumnList {
153+
cols := slices.Clone(base)
154+
cols = append(cols, schema.Column{Name: "embedded_string", Type: schema.TypeString})
155+
cols[cols.Index("int_col")].CreationOptions.PrimaryKey = true
156+
return cols
157+
}(expectedColumns),
158+
}
159+
expectedTestTableEmbeddedStructWithUnwrappedPK = schema.Table{
143160
Name: "test_struct",
144161
Columns: append(
145162
expectedColumns, schema.Column{
146-
Name: "embedded_string",
147-
Type: schema.TypeString,
163+
Name: "embedded_string",
164+
Type: schema.TypeString,
165+
CreationOptions: schema.ColumnCreationOptions{PrimaryKey: true},
148166
}),
149167
}
150168
expectedTestTableNonEmbeddedStruct = schema.Table{
151169
Name: "test_struct",
152170
Columns: schema.ColumnList{
171+
schema.Column{Name: "int_col", Type: schema.TypeInt},
172+
// Should not be unwrapped
173+
schema.Column{Name: "test_struct", Type: schema.TypeJSON},
174+
// Should be unwrapped
175+
schema.Column{Name: "non_embedded_embedded_string", Type: schema.TypeString},
176+
schema.Column{Name: "non_embedded_int_col", Type: schema.TypeInt},
177+
},
178+
}
179+
expectedTestTableNonEmbeddedStructWithTopLevelPK = schema.Table{
180+
Name: "test_struct",
181+
Columns: schema.ColumnList{
182+
schema.Column{
183+
Name: "int_col",
184+
Type: schema.TypeInt,
185+
CreationOptions: schema.ColumnCreationOptions{PrimaryKey: true},
186+
},
187+
// Should not be unwrapped
188+
schema.Column{Name: "test_struct", Type: schema.TypeJSON},
189+
// Should be unwrapped
190+
schema.Column{
191+
Name: "non_embedded_embedded_string",
192+
Type: schema.TypeString,
193+
},
194+
schema.Column{Name: "non_embedded_int_col", Type: schema.TypeInt},
195+
},
196+
}
197+
expectedTestTableNonEmbeddedStructWithUnwrappedPK = schema.Table{
198+
Name: "test_struct",
199+
Columns: schema.ColumnList{
200+
// shouldn't be PK
201+
schema.Column{Name: "int_col", Type: schema.TypeInt},
153202
// Should not be unwrapped
154203
schema.Column{Name: "test_struct", Type: schema.TypeJSON},
155204
// Should be unwrapped
156205
schema.Column{
157206
Name: "non_embedded_embedded_string",
158207
Type: schema.TypeString,
159208
},
209+
// should be PK
210+
schema.Column{
211+
Name: "non_embedded_int_col",
212+
Type: schema.TypeInt,
213+
CreationOptions: schema.ColumnCreationOptions{PrimaryKey: true},
214+
},
160215
},
161216
}
162217
expectedTestSliceStruct = schema.Table{
@@ -219,6 +274,28 @@ func TestTableFromGoStruct(t *testing.T) {
219274
},
220275
want: expectedTestTableEmbeddedStruct,
221276
},
277+
{
278+
name: "should unwrap all embedded structs when option is set and use top-level field as PK",
279+
args: args{
280+
testStruct: testStructWithEmbeddedStruct{},
281+
options: []StructTransformerOption{
282+
WithUnwrapAllEmbeddedStructs(),
283+
WithPrimaryKeys("IntCol"),
284+
},
285+
},
286+
want: expectedTestTableEmbeddedStructWithTopLevelPK,
287+
},
288+
{
289+
name: "should unwrap all embedded structs when option is set and use its field as PK",
290+
args: args{
291+
testStruct: testStructWithEmbeddedStruct{},
292+
options: []StructTransformerOption{
293+
WithUnwrapAllEmbeddedStructs(),
294+
WithPrimaryKeys("EmbeddedString"),
295+
},
296+
},
297+
want: expectedTestTableEmbeddedStructWithUnwrappedPK,
298+
},
222299
{
223300
name: "should unwrap specific structs when option is set",
224301
args: args{
@@ -229,6 +306,28 @@ func TestTableFromGoStruct(t *testing.T) {
229306
},
230307
want: expectedTestTableNonEmbeddedStruct,
231308
},
309+
{
310+
name: "should unwrap specific structs when option is set and use top level field as PK",
311+
args: args{
312+
testStruct: testStructWithNonEmbeddedStruct{},
313+
options: []StructTransformerOption{
314+
WithUnwrapStructFields("NonEmbedded"),
315+
WithPrimaryKeys("IntCol"),
316+
},
317+
},
318+
want: expectedTestTableNonEmbeddedStructWithTopLevelPK,
319+
},
320+
{
321+
name: "should unwrap specific structs when option is set and use its field as PK",
322+
args: args{
323+
testStruct: testStructWithNonEmbeddedStruct{},
324+
options: []StructTransformerOption{
325+
WithUnwrapStructFields("NonEmbedded"),
326+
WithPrimaryKeys("NonEmbedded.IntCol"),
327+
},
328+
},
329+
want: expectedTestTableNonEmbeddedStructWithUnwrappedPK,
330+
},
232331
{
233332
name: "should generate table from slice struct",
234333
args: args{

0 commit comments

Comments
 (0)