-
Notifications
You must be signed in to change notification settings - Fork 22
/
resource.go
154 lines (136 loc) · 4.13 KB
/
resource.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package schema
import (
"crypto/sha256"
"fmt"
"hash"
"slices"
"github.com/cloudquery/plugin-sdk/v4/scalar"
"github.com/google/uuid"
)
type Resources []*Resource
// Resource represents a row in it's associated table, it carries a reference to the original item, and automatically
// generates an Id based on Table's Columns. Resource data can be accessed by the Get and Set methods
type Resource struct {
// Original resource item that wa from prior resolve
Item any
// Set if this is an embedded table
Parent *Resource
// internal fields
Table *Table
// This is sorted result data by column name
data scalar.Vector
// bldr array.RecordBuilder
}
func NewResourceData(t *Table, parent *Resource, item any) *Resource {
r := Resource{
Item: item,
Parent: parent,
Table: t,
data: make(scalar.Vector, len(t.Columns)),
}
for i := range r.data {
r.data[i] = scalar.NewScalar(t.Columns[i].Type)
}
return &r
}
func (r *Resource) Get(columnName string) scalar.Scalar {
index := r.Table.Columns.Index(columnName)
if index == -1 {
// we panic because we want to distinguish between code error and api error
// this also saves additional checks in our testing code
panic(columnName + " column not found")
}
return r.data[index]
}
// Set sets a column with value. This does validation and conversion to
// one of concrete it returns an error just for backward compatibility
// and panics in case it fails
func (r *Resource) Set(columnName string, value any) error {
index := r.Table.Columns.Index(columnName)
if index == -1 {
// we panic because we want to distinguish between code error and api error
// this also saves additional checks in our testing code
panic(columnName + " column not found")
}
if err := r.data[index].Set(value); err != nil {
panic(fmt.Errorf("failed to set column %s: %w", columnName, err))
}
return nil
}
// Override original item (this is useful for apis that follow list/details pattern)
func (r *Resource) SetItem(item any) {
r.Item = item
}
func (r *Resource) GetItem() any {
return r.Item
}
func (r *Resource) GetValues() scalar.Vector {
return r.data
}
//nolint:revive
func (r *Resource) CalculateCQID(deterministicCQID bool) error {
// if `PrimaryKeyComponent` is set, we calculate the CQID based on those components
pkComponents := r.Table.PrimaryKeyComponents()
if len(pkComponents) > 0 {
return r.storeCQID(uuid.NewSHA1(uuid.UUID{}, calculateCqIDValue(r, pkComponents).Sum(nil)))
}
// If deterministicCQID is false, we generate a random CQID
if !deterministicCQID {
return r.storeCQID(uuid.New())
}
names := r.Table.PrimaryKeys()
// If there are no primary keys or if CQID is the only PK, we generate a random CQID
if len(names) == 0 || (len(names) == 1 && names[0] == CqIDColumn.Name) {
return r.storeCQID(uuid.New())
}
return r.storeCQID(uuid.NewSHA1(uuid.UUID{}, calculateCqIDValue(r, names).Sum(nil)))
}
func calculateCqIDValue(r *Resource, cols []string) hash.Hash {
h := sha256.New()
slices.Sort(cols)
for _, col := range cols {
// We need to include the column name in the hash because the same value can be present in multiple columns and therefore lead to the same hash
h.Write([]byte(col))
h.Write([]byte(r.Get(col).String()))
}
return h
}
func (r *Resource) storeCQID(value uuid.UUID) error {
// We skip if _cq_id is not present.
// Mostly the problem here is because the transformation step is baked into the resolving step
if r.Table.Columns.Get(CqIDColumn.Name) == nil {
return nil
}
b, err := value.MarshalBinary()
if err != nil {
return err
}
return r.Set(CqIDColumn.Name, b)
}
// Validates that all primary keys have values.
func (r *Resource) Validate() error {
var missingPks []string
for i, c := range r.Table.Columns {
if c.PrimaryKey {
if !r.data[i].IsValid() {
missingPks = append(missingPks, c.Name)
}
}
}
if len(missingPks) > 0 {
return fmt.Errorf("missing primary key on columns: %v", missingPks)
}
return nil
}
func (rr Resources) TableName() string {
if len(rr) == 0 {
return ""
}
return rr[0].Table.Name
}
func (rr Resources) ColumnNames() []string {
if len(rr) == 0 {
return []string{}
}
return rr[0].Table.Columns.Names()
}