Skip to content

Commit fe9c4e4

Browse files
committed
Merge base branch with lint fix from PR #319
2 parents d750438 + 2301246 commit fe9c4e4

File tree

17 files changed

+996
-297
lines changed

17 files changed

+996
-297
lines changed

connection.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
dbsqlerr "github.com/databricks/databricks-sql-go/errors"
1717
"github.com/databricks/databricks-sql-go/internal/cli_service"
1818
"github.com/databricks/databricks-sql-go/internal/client"
19+
context2 "github.com/databricks/databricks-sql-go/internal/compat/context"
1920
"github.com/databricks/databricks-sql-go/internal/config"
2021
dbsqlerrint "github.com/databricks/databricks-sql-go/internal/errors"
2122
"github.com/databricks/databricks-sql-go/internal/rows"
@@ -63,7 +64,7 @@ func (c *conn) Close() error {
6364

6465
if err != nil {
6566
log.Err(err).Msg("databricks: failed to close connection")
66-
return dbsqlerrint.NewRequestError(ctx, dbsqlerr.ErrCloseConnection, err)
67+
return dbsqlerrint.NewBadConnectionError(err)
6768
}
6869
return nil
6970
}
@@ -202,9 +203,7 @@ func (c *conn) QueryContext(ctx context.Context, query string, args []driver.Nam
202203
return nil, dbsqlerrint.NewExecutionError(ctx, dbsqlerr.ErrQueryExecution, err, opStatusResp)
203204
}
204205

205-
corrId := driverctx.CorrelationIdFromContext(ctx)
206-
rows, err := rows.NewRows(c.id, corrId, exStmtResp.OperationHandle, c.client, c.cfg, exStmtResp.DirectResults)
207-
206+
rows, err := rows.NewRows(ctx, exStmtResp.OperationHandle, c.client, c.cfg, exStmtResp.DirectResults)
208207
return rows, err
209208

210209
}
@@ -373,6 +372,7 @@ func (c *conn) executeStatement(ctx context.Context, query string, args []driver
373372

374373
select {
375374
default:
375+
// Non-blocking check: continue if context not done
376376
case <-ctx.Done():
377377
newCtx := driverctx.NewContextFromBackground(ctx)
378378
// in case context is done, we need to cancel the operation if necessary
@@ -401,7 +401,7 @@ func (c *conn) pollOperation(ctx context.Context, opHandle *cli_service.TOperati
401401
log := logger.WithContext(c.id, corrId, client.SprintGuid(opHandle.OperationId.GUID))
402402
var statusResp *cli_service.TGetOperationStatusResp
403403
ctx = driverctx.NewContextWithConnId(ctx, c.id)
404-
newCtx := driverctx.NewContextWithCorrelationId(driverctx.NewContextWithConnId(context.Background(), c.id), corrId)
404+
newCtx := context2.WithoutCancel(ctx)
405405
pollSentinel := sentinel.Sentinel{
406406
OnDoneFn: func(statusResp any) (any, error) {
407407
return statusResp, nil
@@ -600,7 +600,6 @@ func (c *conn) execStagingOperation(
600600
return nil
601601
}
602602

603-
corrId := driverctx.CorrelationIdFromContext(ctx)
604603
var row driver.Rows
605604
var err error
606605

@@ -623,7 +622,7 @@ func (c *conn) execStagingOperation(
623622
}
624623

625624
if len(driverctx.StagingPathsFromContext(ctx)) != 0 {
626-
row, err = rows.NewRows(c.id, corrId, exStmtResp.OperationHandle, c.client, c.cfg, exStmtResp.DirectResults)
625+
row, err = rows.NewRows(ctx, exStmtResp.OperationHandle, c.client, c.cfg, exStmtResp.DirectResults)
627626
if err != nil {
628627
return dbsqlerrint.NewDriverError(ctx, "error reading row.", err)
629628
}

internal/compat/context/context.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package context
2+
3+
import (
4+
"context"
5+
"time"
6+
)
7+
8+
// This implementation has been copied from https://github.com/golang/go/blob/master/src/context/context.go#L581
9+
// and adapted to work with this package.
10+
11+
// WithoutCancel returns a derived context that points to the parent context
12+
// and is not canceled when parent is canceled.
13+
// The returned context returns no Deadline or Err, and its Done channel is nil.
14+
func WithoutCancel(parent context.Context) context.Context {
15+
if parent == nil {
16+
panic("cannot create context from nil parent")
17+
}
18+
return withoutCancelCtx{parent}
19+
}
20+
21+
type withoutCancelCtx struct {
22+
c context.Context
23+
}
24+
25+
func (withoutCancelCtx) Deadline() (deadline time.Time, ok bool) {
26+
return
27+
}
28+
29+
func (withoutCancelCtx) Done() <-chan struct{} {
30+
return nil
31+
}
32+
33+
func (withoutCancelCtx) Err() error {
34+
return nil
35+
}
36+
37+
func (c withoutCancelCtx) Value(key any) any {
38+
return c.c.Value(key)
39+
}

internal/config/overlay.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"strconv"
7+
)
8+
9+
// ConfigValue represents a configuration value that can be set by client or resolved from server.
10+
// This implements the config overlay pattern: client > server > default
11+
//
12+
// T is the type of the configuration value (bool, string, int, etc.)
13+
//
14+
// Example usage:
15+
//
16+
// type MyConfig struct {
17+
// EnableFeature ConfigValue[bool]
18+
// BatchSize ConfigValue[int]
19+
// }
20+
//
21+
// // Client explicitly sets value (overrides server)
22+
// config.EnableFeature = NewConfigValue(true)
23+
//
24+
// // Client doesn't set value (use server)
25+
// config.EnableFeature = ConfigValue[bool]{} // nil/unset
26+
//
27+
// // Resolve value with overlay priority
28+
// enabled := config.EnableFeature.Resolve(ctx, serverResolver, defaultValue)
29+
type ConfigValue[T any] struct {
30+
// value is the client-set configuration value
31+
// nil = not set by client (use server config)
32+
// non-nil = explicitly set by client (overrides server)
33+
value *T
34+
}
35+
36+
// NewConfigValue creates a ConfigValue with a client-set value.
37+
// The value will override any server-side configuration.
38+
func NewConfigValue[T any](value T) ConfigValue[T] {
39+
return ConfigValue[T]{value: &value}
40+
}
41+
42+
// IsSet returns true if the client explicitly set this configuration value.
43+
func (cv ConfigValue[T]) IsSet() bool {
44+
return cv.value != nil
45+
}
46+
47+
// Get returns the client-set value and whether it was set.
48+
// If not set, returns zero value and false.
49+
func (cv ConfigValue[T]) Get() (T, bool) {
50+
if cv.value != nil {
51+
return *cv.value, true
52+
}
53+
var zero T
54+
return zero, false
55+
}
56+
57+
// ServerResolver defines how to fetch a configuration value from the server.
58+
// Implementations should handle caching, retries, and error handling.
59+
type ServerResolver[T any] interface {
60+
// Resolve fetches the configuration value from the server.
61+
// Returns the value and any error encountered.
62+
// On error, the config overlay will fall back to the default value.
63+
Resolve(ctx context.Context, host string, httpClient *http.Client) (T, error)
64+
}
65+
66+
// Resolve applies config overlay priority to determine the final value:
67+
//
68+
// Priority 1: Client Config - if explicitly set (overrides server)
69+
// Priority 2: Server Config - resolved via serverResolver (when client doesn't set)
70+
// Priority 3: Default Value - used when server unavailable/errors (fail-safe)
71+
//
72+
// Parameters:
73+
// - ctx: Context for server requests
74+
// - serverResolver: How to fetch from server (can be nil if no server config)
75+
// - defaultValue: Fail-safe default when client unset and server unavailable
76+
//
77+
// Returns: The resolved configuration value following overlay priority
78+
func (cv ConfigValue[T]) Resolve(
79+
ctx context.Context,
80+
serverResolver ServerResolver[T],
81+
defaultValue T,
82+
) T {
83+
// Priority 1: Client explicitly set (overrides everything)
84+
if cv.value != nil {
85+
return *cv.value
86+
}
87+
88+
// Priority 2: Try server config (if resolver provided)
89+
if serverResolver != nil {
90+
// Note: We pass empty host/httpClient here. Actual resolver should have these injected
91+
// This is a simplified interface - real usage would inject dependencies
92+
if serverValue, err := serverResolver.Resolve(ctx, "", nil); err == nil {
93+
return serverValue
94+
}
95+
}
96+
97+
// Priority 3: Fail-safe default
98+
return defaultValue
99+
}
100+
101+
// ResolveWithContext is a more flexible version that takes host and httpClient.
102+
// This is the recommended method for production use.
103+
func (cv ConfigValue[T]) ResolveWithContext(
104+
ctx context.Context,
105+
host string,
106+
httpClient *http.Client,
107+
serverResolver ServerResolver[T],
108+
defaultValue T,
109+
) T {
110+
// Priority 1: Client explicitly set (overrides everything)
111+
if cv.value != nil {
112+
return *cv.value
113+
}
114+
115+
// Priority 2: Try server config (if resolver provided)
116+
if serverResolver != nil {
117+
if serverValue, err := serverResolver.Resolve(ctx, host, httpClient); err == nil {
118+
return serverValue
119+
}
120+
}
121+
122+
// Priority 3: Fail-safe default
123+
return defaultValue
124+
}
125+
126+
// ParseBoolConfigValue parses a string value into a ConfigValue[bool].
127+
// Returns unset ConfigValue if the parameter is not present.
128+
//
129+
// Example:
130+
//
131+
// params := map[string]string{"enableFeature": "true"}
132+
// value := ParseBoolConfigValue(params, "enableFeature")
133+
// // value.IsSet() == true, value.Get() == (true, true)
134+
func ParseBoolConfigValue(params map[string]string, key string) ConfigValue[bool] {
135+
if v, ok := params[key]; ok {
136+
enabled := (v == "true" || v == "1")
137+
return NewConfigValue(enabled)
138+
}
139+
return ConfigValue[bool]{} // Unset
140+
}
141+
142+
// ParseStringConfigValue parses a string value into a ConfigValue[string].
143+
// Returns unset ConfigValue if the parameter is not present.
144+
func ParseStringConfigValue(params map[string]string, key string) ConfigValue[string] {
145+
if v, ok := params[key]; ok {
146+
return NewConfigValue(v)
147+
}
148+
return ConfigValue[string]{} // Unset
149+
}
150+
151+
// ParseIntConfigValue parses a string value into a ConfigValue[int].
152+
// Returns unset ConfigValue if the parameter is not present or invalid.
153+
func ParseIntConfigValue(params map[string]string, key string) ConfigValue[int] {
154+
if v, ok := params[key]; ok {
155+
if i, err := strconv.Atoi(v); err == nil {
156+
return NewConfigValue(i)
157+
}
158+
}
159+
return ConfigValue[int]{} // Unset
160+
}

0 commit comments

Comments
 (0)