-
Notifications
You must be signed in to change notification settings - Fork 62
refactor: extract Backend/Operation seam for SEA integration #382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
96bc53b
refactor: extract Backend/Operation seam for SEA integration
mani-mathur-arch e566e28
fix: preserve queryId enrichment semantics on the execute path
mani-mathur-arch e24e903
fix: guard debugLogger swap with atomic.Pointer
mani-mathur-arch 0c04d92
refactor: emit step trace through the main logger, drop the sibling l…
mani-mathur-arch 6ef9f64
Merge origin/main into mani/sea-backend-refactor
mani-mathur-arch fe9f43b
fix: use time.Since in Track done closure (gosimple S1012)
mani-mathur-arch 276f409
refactor: address PR #382 code-review-squad findings
mani-mathur-arch 4efba05
Merge origin/main into mani/sea-backend-refactor
mani-mathur-arch fc8870a
Merge origin/main into mani/sea-backend-refactor
mani-mathur-arch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package dbsql | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
|
|
||
| "github.com/databricks/databricks-sql-go/driverctx" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| // TestEnrichQueryId pins the two non-obvious queryId-enrichment semantics: | ||
| // (1) a caller-set queryId is never overwritten, and (2) when the context has | ||
| // no queryId, the derived id is always applied via NewContextWithQueryId — | ||
| // even when empty — so a registered QueryIdCallback fires. | ||
| func TestEnrichQueryId(t *testing.T) { | ||
| t.Run("preserves a caller-set queryId", func(t *testing.T) { | ||
| ctx := driverctx.NewContextWithQueryId(context.Background(), "caller-set") | ||
| ctx = enrichQueryId(ctx, "statement-guid") | ||
| assert.Equal(t, "caller-set", driverctx.QueryIdFromContext(ctx), | ||
| "a queryId the caller already set must not be overwritten") | ||
| }) | ||
|
|
||
| t.Run("fills the statement id when the context has none", func(t *testing.T) { | ||
| ctx := enrichQueryId(context.Background(), "statement-guid") | ||
| assert.Equal(t, "statement-guid", driverctx.QueryIdFromContext(ctx)) | ||
| }) | ||
|
|
||
| t.Run("fires the QueryIdCallback even with an empty derived id", func(t *testing.T) { | ||
| var fired bool | ||
| var got string | ||
| ctx := driverctx.NewContextWithQueryIdCallback(context.Background(), func(id string) { | ||
| fired = true | ||
| got = id | ||
| }) | ||
| // No handle -> empty statement id. The callback must still fire. | ||
| _ = enrichQueryId(ctx, "") | ||
| assert.True(t, fired, "QueryIdCallback must fire on the no-queryId path even with an empty id") | ||
| assert.Equal(t, "", got) | ||
| }) | ||
|
|
||
| t.Run("fires the QueryIdCallback with the derived id", func(t *testing.T) { | ||
| var got string | ||
| ctx := driverctx.NewContextWithQueryIdCallback(context.Background(), func(id string) { got = id }) | ||
| _ = enrichQueryId(ctx, "statement-guid") | ||
| assert.Equal(t, "statement-guid", got) | ||
| }) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| // Package backend defines the execution-backend abstraction for the | ||
| // databricks-sql-go driver. A connection holds one Backend, created by the | ||
| // connector at connect time, and drives session lifecycle and statement | ||
| // execution through it without depending on a concrete protocol. | ||
| // | ||
| // The interfaces are expressed in neutral terms — plain Go request structs, a | ||
| // driver.Rows result, and small accessors for statement id, affected rows, and | ||
| // sqlstate-aware error wrapping — so that only a Backend implementation imports | ||
| // its protocol's client. Row decoding is not part of this neutral seam: the | ||
| // internal/rows constructor is coupled to Thrift result types, so each Backend | ||
| // supplies driver.Rows through its own construction path. | ||
| package backend | ||
|
|
||
| import ( | ||
| "context" | ||
| "database/sql/driver" | ||
|
|
||
| dbsqlrows "github.com/databricks/databricks-sql-go/internal/rows" | ||
| ) | ||
|
|
||
| // Backend is the per-connection execution backend. Exactly one Backend backs one | ||
| // conn, which (via database/sql's pool) is used by one goroutine at a time — so | ||
| // implementations need not be safe for concurrent use by multiple goroutines. | ||
| type Backend interface { | ||
| // OpenSession establishes the server-side session. Called once by the | ||
| // connector factory at connect time, before the conn is handed to the pool. | ||
| OpenSession(ctx context.Context) error | ||
|
|
||
| // CloseSession tears down the server-side session. Called from conn.Close. | ||
| CloseSession(ctx context.Context) error | ||
|
|
||
| // SessionValid backs driver.Validator (conn.IsValid): it reports whether the | ||
| // session is still usable, so a broken conn is evicted from the pool rather | ||
| // than reused. It does no I/O — it inspects state captured at OpenSession. | ||
| SessionValid() bool | ||
|
|
||
| // SessionID is the formatted server session id (conn.id). Valid only after a | ||
| // successful OpenSession; empty otherwise. | ||
| SessionID() string | ||
|
|
||
| // Execute runs a statement to a terminal state and returns an Operation | ||
| // describing the outcome. | ||
| // | ||
| // An implementation MUST return a non-nil Operation, even when err is non-nil: | ||
| // the caller reads StatementID, wraps the error via ExecutionError, and closes | ||
| // the operation via Close on both the success and failure paths. When the | ||
| // server returned no handle, the accessors return zero values (StatementID "", | ||
| // Close closed=false) rather than panicking. | ||
| Execute(ctx context.Context, req ExecRequest) (Operation, error) | ||
| } | ||
|
|
||
| // Operation is a submitted statement that has reached (or attempted to reach) a | ||
| // terminal state. It exposes only neutral accessors, keeping the concrete | ||
| // protocol types inside the backend implementation. | ||
| // | ||
| // Server-side close ownership is path-dependent. On the exec path (ExecContext, | ||
| // staging) the caller closes the server-side operation via Close. On the query | ||
| // path (QueryContext) the caller does NOT call Close; instead the driver.Rows | ||
| // returned by Results owns closing the server-side operation on its Close. An | ||
| // implementation MUST therefore make both this Operation.Close and the | ||
| // Rows.Close returned by Results independently idempotent: either one may be | ||
| // the sole closer, and both may be invoked on the same operation (e.g. an | ||
| // error-path defer plus a normal-path close), so a second invocation of either | ||
| // closer must be a no-op rather than issuing a duplicate teardown RPC. | ||
| type Operation interface { | ||
| // StatementID is the server statement/operation id (empty if the server | ||
| // returned no handle). | ||
| StatementID() string | ||
|
|
||
| // AffectedRows is the modified-row count for ExecContext results. Unlike the | ||
| // other accessors it is defined only on the success path — the caller reads it | ||
| // only after Execute returned a nil error — so an implementation need not make | ||
| // it safe to call on a failed/handle-less operation. | ||
| AffectedRows() int64 | ||
|
|
||
| // Results builds the driver.Rows for the operation's result set. The callbacks | ||
| // carry the telemetry hooks (chunk timing, close, CloudFetch file); the caller | ||
| // supplies whichever subset it needs (a full query wires all three, the | ||
| // staging path wires only chunk timing). | ||
| // | ||
| // On the query path the returned Rows owns closing the server-side operation: | ||
| // the caller does not invoke Operation.Close on that path and relies on | ||
| // Rows.Close to issue any needed teardown RPC. See the type-level Operation | ||
| // comment for the idempotency contract this places on both closers. | ||
| Results(ctx context.Context, callbacks *dbsqlrows.TelemetryCallbacks) (driver.Rows, error) | ||
|
|
||
| // IsStaging reports whether this is a staging (PUT/GET/REMOVE) operation. It | ||
| // may issue a metadata lookup if the answer is not already available from | ||
| // direct results. | ||
| IsStaging(ctx context.Context) (bool, error) | ||
|
|
||
| // Close best-effort closes the server-side operation, if one is still open. | ||
|
mani-mathur-arch marked this conversation as resolved.
|
||
| // It is called only on the exec/staging paths — on the query path the | ||
| // returned Rows owns close (see the Operation and Results doc comments). | ||
| // It is idempotent and safe to call regardless of whether execution | ||
| // succeeded: a second call on the same Operation, or a call after Rows.Close | ||
| // already tore down the server operation, must be a no-op rather than | ||
| // issuing a duplicate close RPC. closed reports whether a close RPC was | ||
| // actually issued (false when the operation had no handle, was already | ||
| // closed via direct results, was already closed by a prior invocation, or | ||
| // was already closed via Rows.Close), so the caller can record | ||
| // CLOSE_STATEMENT telemetry only when a real close happened. | ||
| Close(ctx context.Context) (closed bool, err error) | ||
|
|
||
| // ExecutionError wraps cause as the driver's execution error, attaching this | ||
| // operation's terminal sqlstate. Returns nil when cause is nil. | ||
| ExecutionError(ctx context.Context, cause error) error | ||
| } | ||
|
|
||
| // ExecRequest is a backend-neutral statement-execution request. Per-backend wire | ||
| // options (Arrow flags, CloudFetch, direct results, query tags, timeouts) are | ||
| // derived from the config and context inside the backend implementation. | ||
| type ExecRequest struct { | ||
| // Query is the SQL text to execute. | ||
| Query string | ||
| // Params are the bound query parameters, already type-inferred and rendered | ||
| // to their string wire form (see Param); empty when the statement has none. | ||
| // Inference lives in the dbsql package because it depends on the public | ||
| // dbsql.Parameter/SqlType types; each backend maps these neutral params to its | ||
| // own wire type. | ||
| Params []Param | ||
| } | ||
|
|
||
| // Param is one bound query parameter in backend-neutral form: the name (empty | ||
| // for a positional parameter), the SQL type name as the server expects it (e.g. | ||
| // "BIGINT", "DECIMAL(2,1)", "VOID"), and the value already rendered to its | ||
| // string wire representation. A nil Value denotes a SQL NULL (type "VOID"). | ||
| type Param struct { | ||
| Name string | ||
| Type string | ||
| Value *string | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.