Skip to content

Commit e31b71e

Browse files
authored
Return data for all columns returned by hydrate functions, not just requested columns. Closes #156
1 parent 7ee891e commit e31b71e

7 files changed

+99
-54
lines changed

plugin/hydrate_call.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,15 @@ type HydrateCall struct {
5151
// the dependencies expressed using function name
5252
Depends []string
5353
Config *HydrateConfig
54+
Name string
5455
}
5556

5657
func newHydrateCall(hydrateFunc HydrateFunc, config *HydrateConfig) *HydrateCall {
57-
res := &HydrateCall{Func: hydrateFunc, Config: config}
58+
res := &HydrateCall{
59+
Name: helpers.GetFunctionName(hydrateFunc),
60+
Func: hydrateFunc,
61+
Config: config,
62+
}
5863
for _, f := range config.Depends {
5964
res.Depends = append(res.Depends, helpers.GetFunctionName(f))
6065
}
@@ -81,14 +86,14 @@ func (h HydrateCall) CanStart(rowData *RowData, name string, concurrencyManager
8186
}
8287

8388
// Start starts a hydrate call
84-
func (h *HydrateCall) Start(ctx context.Context, r *RowData, d *QueryData, hydrateFuncName string, concurrencyManager *ConcurrencyManager) {
89+
func (h *HydrateCall) Start(ctx context.Context, r *RowData, d *QueryData, concurrencyManager *ConcurrencyManager) {
8590
// tell the rowdata to wait for this call to complete
8691
r.wg.Add(1)
8792

8893
// call callHydrate async, ignoring return values
8994
go func() {
90-
r.callHydrate(ctx, d, h.Func, hydrateFuncName, h.Config.RetryConfig, h.Config.ShouldIgnoreError)
95+
r.callHydrate(ctx, d, h.Func, h.Name, h.Config.RetryConfig, h.Config.ShouldIgnoreError)
9196
// decrement number of hydrate functions running
92-
concurrencyManager.Finished(hydrateFuncName)
97+
concurrencyManager.Finished(h.Name)
9398
}()
9499
}

plugin/plugin.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import (
88
"strconv"
99
"syscall"
1010

11-
connection_manager "github.com/turbot/steampipe-plugin-sdk/connection"
12-
1311
"github.com/hashicorp/go-hclog"
12+
"github.com/turbot/go-kit/helpers"
13+
connection_manager "github.com/turbot/steampipe-plugin-sdk/connection"
1414
"github.com/turbot/steampipe-plugin-sdk/grpc/proto"
1515
"github.com/turbot/steampipe-plugin-sdk/logging"
1616
"github.com/turbot/steampipe-plugin-sdk/plugin/context_key"
@@ -159,7 +159,7 @@ func (p *Plugin) SetConnectionConfig(connectionName, connectionConfigString stri
159159

160160
defer func() {
161161
if r := recover(); r != nil {
162-
err = fmt.Errorf("SetConnectionConfig failed: %s", ToError(r).Error())
162+
err = fmt.Errorf("SetConnectionConfig failed: %s", helpers.ToError(r).Error())
163163
} else {
164164
p.Logger.Debug("SetConnectionConfig finished")
165165
}

plugin/query_data.go

+33-12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818

1919
const itemBufferSize = 100
2020

21+
// NOTE - any field added here must also be added to ShallowCopy
22+
2123
type QueryData struct {
2224
// The table this query is associated with
2325
Table *Table
@@ -46,14 +48,18 @@ type QueryData struct {
4648
// event for the child list of a parent child list call
4749
StreamLeafListItem func(ctx context.Context, item interface{})
4850
// internal
51+
52+
// a list of the required hydrate calls (EXCLUDING the fetch call)
4953
hydrateCalls []*HydrateCall
54+
// all the columns that will be returned by this query
55+
columns []string
5056

5157
concurrencyManager *ConcurrencyManager
5258
rowDataChan chan *RowData
5359
errorChan chan error
5460
streamCount int
55-
stream proto.WrapperPlugin_ExecuteServer
5661

62+
stream proto.WrapperPlugin_ExecuteServer
5763
// wait group used to synchronise parent-child list fetches - each child hydrate function increments this wait group
5864
listWg *sync.WaitGroup
5965
// when executing parent child list calls, we cache the parent list result in the query data passed to the child list call
@@ -91,7 +97,10 @@ func newQueryData(queryContext *QueryContext, table *Table, stream proto.Wrapper
9197
// NOTE: for count(*) queries, there will be no columns - add in 1 column so that we have some data to return
9298
ensureColumns(queryContext, table)
9399

100+
// build list of required hydrate calls, based on requested columns
94101
d.hydrateCalls = table.requiredHydrateCalls(queryContext.Columns, d.FetchType)
102+
// build list of all columns returned by these hydrate calls (and the fetch call)
103+
d.populateColumns()
95104
d.concurrencyManager = newConcurrencyManager(table)
96105

97106
return d
@@ -117,6 +126,7 @@ func (d *QueryData) ShallowCopy() *QueryData {
117126
stream: d.stream,
118127
streamCount: d.streamCount,
119128
listWg: d.listWg,
129+
columns: d.columns,
120130
}
121131

122132
// NOTE: we create a deep copy of the keyColumnQuals
@@ -134,6 +144,27 @@ func (d *QueryData) ShallowCopy() *QueryData {
134144
return clone
135145
}
136146

147+
// build list of all columns returned by the fetch call and required hydrate calls
148+
func (d *QueryData) populateColumns() {
149+
// add columns returned by fetch call
150+
fetchName := helpers.GetFunctionName(d.Table.getFetchFunc(d.FetchType))
151+
d.columns = append(d.columns, d.addColumnsForHydrate(fetchName)...)
152+
153+
// add columns returned by required hydrate calls
154+
for _, h := range d.hydrateCalls {
155+
d.columns = append(d.columns, d.addColumnsForHydrate(h.Name)...)
156+
}
157+
}
158+
159+
// get the column returned by the given hydrate call
160+
func (d *QueryData) addColumnsForHydrate(hydrateName string) []string {
161+
var cols []string
162+
for _, columnName := range d.Table.hydrateColumnMap[hydrateName] {
163+
cols = append(cols, columnName)
164+
}
165+
return cols
166+
}
167+
137168
// KeyColumnQualString looks for the specified key column quals and if it exists, return the value as a string
138169
func (d *QueryData) KeyColumnQualString(key string) string {
139170
qualValue, ok := d.KeyColumnQuals[key]
@@ -442,7 +473,7 @@ func (d *QueryData) buildRows(ctx context.Context) chan *proto.Row {
442473
func (d *QueryData) buildRow(ctx context.Context, rowData *RowData, rowChan chan *proto.Row, wg *sync.WaitGroup) {
443474
defer func() {
444475
if r := recover(); r != nil {
445-
d.streamError(ToError(r))
476+
d.streamError(helpers.ToError(r))
446477
}
447478
wg.Done()
448479
}()
@@ -464,13 +495,3 @@ func (d *QueryData) waitForRowsToComplete(rowWg *sync.WaitGroup, rowChan chan *p
464495
log.Println("[TRACE] rowWg complete - CLOSING ROW CHANNEL")
465496
close(rowChan)
466497
}
467-
468-
// ToError is used to return an error or format the supplied value as error.
469-
// Can be removed once go-kit version 0.2.0 is released
470-
func ToError(val interface{}) error {
471-
if e, ok := val.(error); ok {
472-
return e
473-
} else {
474-
return fmt.Errorf("%v", val)
475-
}
476-
}

plugin/row.go

+8-14
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"sync"
88
"time"
99

10-
"github.com/turbot/go-kit/helpers"
1110
"github.com/turbot/steampipe-plugin-sdk/grpc/proto"
1211
"github.com/turbot/steampipe-plugin-sdk/logging"
1312
"github.com/turbot/steampipe-plugin-sdk/plugin/context_key"
@@ -82,7 +81,7 @@ func (r *RowData) startAllHydrateCalls(rowDataCtx context.Context, rowQueryData
8281
for {
8382
var allStarted = true
8483
for _, call := range r.queryData.hydrateCalls {
85-
hydrateFuncName := helpers.GetFunctionName(call.Func)
84+
hydrateFuncName := call.Name
8685
// if it is already started, continue to next call
8786
if callsStarted[hydrateFuncName] {
8887
continue
@@ -91,7 +90,7 @@ func (r *RowData) startAllHydrateCalls(rowDataCtx context.Context, rowQueryData
9190
// so call needs to start - can it?
9291
if call.CanStart(r, hydrateFuncName, r.queryData.concurrencyManager) {
9392
// execute the hydrate call asynchronously
94-
call.Start(rowDataCtx, r, rowQueryData, hydrateFuncName, r.queryData.concurrencyManager)
93+
call.Start(rowDataCtx, r, rowQueryData, r.queryData.concurrencyManager)
9594
callsStarted[hydrateFuncName] = true
9695
} else {
9796
allStarted = false
@@ -149,21 +148,16 @@ func (r *RowData) waitForHydrateCallsToComplete(rowDataCtx context.Context) (*pr
149148
// generate the column values for for all requested columns
150149
func (r *RowData) getColumnValues(ctx context.Context) (*proto.Row, error) {
151150
row := &proto.Row{Columns: make(map[string]*proto.Column)}
152-
// only populate columns which have been asked for
153-
for _, columnName := range r.queryData.QueryContext.Columns {
154-
// get columns schema
155-
column := r.table.getColumn(columnName)
156-
if column == nil {
157-
// postgres asked for a non existent column. Shouldn't happen but just ignore
158-
continue
159-
}
160151

161-
var err error
162-
row.Columns[columnName], err = r.table.getColumnValue(ctx, r, column)
152+
// queryData.columns contains all columns returned by the hydrate calls which have been executed
153+
for _, columnName := range r.queryData.columns {
154+
val, err := r.table.getColumnValue(ctx, r, columnName)
163155
if err != nil {
164156
return nil, err
165157
}
158+
row.Columns[columnName] = val
166159
}
160+
167161
return row, nil
168162
}
169163

@@ -223,6 +217,7 @@ func shouldRetryError(err error, d *QueryData, retryConfig *RetryConfig) bool {
223217
if d.streamCount != 0 {
224218
return false
225219
}
220+
226221
shouldRetryErrorFunc := retryConfig.ShouldRetryError
227222
if shouldRetryErrorFunc == nil {
228223
return false
@@ -265,7 +260,6 @@ func (r *RowData) getHydrateKeys() []string {
265260

266261
// GetColumnData returns the root item, and, if this column has a hydrate function registered, the associated hydrate data
267262
func (r *RowData) GetColumnData(column *Column) (interface{}, error) {
268-
269263
if column.resolvedHydrateName == "" {
270264
return nil, fmt.Errorf("column %s has no resolved hydrate function name", column.Name)
271265
}

plugin/table.go

+37-18
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ type Table struct {
2828
// definitions of dependencies between hydrate functions
2929
HydrateDependencies []HydrateDependencies
3030
HydrateConfig []HydrateConfig
31+
// map of hydrate function name to columns it provides
32+
hydrateColumnMap map[string][]string
3133
}
3234

3335
type GetConfig struct {
@@ -54,40 +56,57 @@ type ListConfig struct {
5456
// build a list of required hydrate function calls which must be executed, based on the columns which have been requested
5557
// NOTE: 'get' and 'list' calls are hydration functions, but they must be omitted from this list as they are called
5658
// first. BEFORE the other hydration functions
59+
// NOTE2: this function also populates the resolvedHydrateName for each column (used to retrieve column values),
60+
// and the hydrateColumnMap (used to determine which columns to return)
5761
func (t *Table) requiredHydrateCalls(colsUsed []string, fetchType fetchType) []*HydrateCall {
5862
log.Printf("[TRACE] requiredHydrateCalls, table '%s' fetchType %s colsUsed %v\n", t.Name, fetchType, colsUsed)
5963

6064
// what is the name of the fetch call (i.e. the get/list call)
61-
var fetchCallName string
62-
if fetchType == fetchTypeList {
63-
fetchCallName = helpers.GetFunctionName(t.List.Hydrate)
64-
} else {
65-
fetchCallName = helpers.GetFunctionName(t.Get.Hydrate)
66-
}
65+
fetchFunc := t.getFetchFunc(fetchType)
66+
fetchCallName := helpers.GetFunctionName(fetchFunc)
6767

68+
// initialise hydrateColumnMap
69+
t.hydrateColumnMap = make(map[string][]string)
6870
requiredCallBuilder := newRequiredHydrateCallBuilder(t, fetchCallName)
6971

7072
// populate a map keyed by function name to ensure we only store each hydrate function once
7173
for _, column := range t.Columns {
72-
if helpers.StringSliceContains(colsUsed, column.Name) {
73-
74-
// see if this column specifies a hydrate function
75-
hydrateFunc := column.Hydrate
76-
if hydrateFunc != nil {
77-
hydrateName := helpers.GetFunctionName(hydrateFunc)
78-
column.resolvedHydrateName = hydrateName
74+
// see if this column specifies a hydrate function
75+
76+
var hydrateName string
77+
if hydrateFunc := column.Hydrate; hydrateFunc == nil {
78+
// so there is NO hydrate call registered for the column
79+
// the column is provided by the fetch call
80+
// do not add to map of hydrate functions as the fetch call will always be called
81+
hydrateFunc = fetchFunc
82+
hydrateName = fetchCallName
83+
} else {
84+
// there is a hydrate call registered
85+
hydrateName = helpers.GetFunctionName(hydrateFunc)
86+
// if this column was requested in query, add the hydrate call to required calls
87+
if helpers.StringSliceContains(colsUsed, column.Name) {
7988
requiredCallBuilder.Add(hydrateFunc)
80-
} else {
81-
column.resolvedHydrateName = fetchCallName
82-
// so there is no hydrate call registered for the column - the resolvedHydrateName is the fetch call
83-
// do not add to map of hydrate functions as the fetch call will always be called
8489
}
8590
}
86-
}
8791

92+
// now update hydrateColumnMap
93+
t.hydrateColumnMap[hydrateName] = append(t.hydrateColumnMap[hydrateName], column.Name)
94+
// store the hydrate name in the column object
95+
column.resolvedHydrateName = hydrateName
96+
}
97+
log.Printf("[WARN] requiredHydrateCalls %v hydrateColumnMap %v", requiredCallBuilder.Get(), t.hydrateColumnMap)
8898
return requiredCallBuilder.Get()
8999
}
90100

101+
func (t *Table) getFetchFunc(fetchType fetchType) HydrateFunc {
102+
103+
if fetchType == fetchTypeList {
104+
return t.List.Hydrate
105+
}
106+
return t.Get.Hydrate
107+
108+
}
109+
91110
func (t *Table) getHydrateDependencies(hydrateFuncName string) []HydrateFunc {
92111
for _, d := range t.HydrateDependencies {
93112
if helpers.GetFunctionName(d.Func) == hydrateFuncName {

plugin/table_column.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,14 @@ func (t *Table) getColumnType(columnName string) proto.ColumnType {
3333
}
3434

3535
// take the raw value returned by the get/list/hydrate call, apply transforms and convert to protobuf value
36-
func (t *Table) getColumnValue(ctx context.Context, rowData *RowData, column *Column) (*proto.Column, error) {
36+
func (t *Table) getColumnValue(ctx context.Context, rowData *RowData, columnName string) (*proto.Column, error) {
37+
// get columns schema
38+
column := t.getColumn(columnName)
39+
if column == nil {
40+
// postgres asked for a non existent column. Shouldn't happen.
41+
return nil, fmt.Errorf("hydrateColumnMap contains non existent column %s", columnName)
42+
}
43+
3744
hydrateItem, err := rowData.GetColumnData(column)
3845
if err != nil {
3946
log.Printf("[ERROR] table '%s' failed to get column data: %v\n", t.Name, err)

plugin/table_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"strings"
77
"testing"
88

9-
"github.com/turbot/go-kit/helpers"
109
"github.com/turbot/steampipe-plugin-sdk/grpc/proto"
1110
)
1211

@@ -318,7 +317,7 @@ func hydrateArrayToString(calls []*HydrateCall) string {
318317
}
319318

320319
func hydrateCallToString(call *HydrateCall) string {
321-
str := fmt.Sprintf("Func: %s", helpers.GetFunctionName(call.Func))
320+
str := fmt.Sprintf("Func: %s", call.Name)
322321
if len(call.Depends) > 0 {
323322
str += "\n Depends:"
324323
}

0 commit comments

Comments
 (0)