Skip to content

Conversation

@dprophet
Copy link

@dprophet dprophet commented Oct 13, 2025

This checkin includes the following

  • Initial checkin of device flow (aka device grant)
  • Better error reporting back to ODBC interface
  • Powershell installer for development ODBC driver.

Summary by Sourcery

Enable OIDC device flow authentication, enhance Trino-to-ODBC error mapping, and provide a PowerShell installer for developer convenience

New Features:

  • Add initial OIDC device flow (device grant) authentication provider and corresponding config options
  • Provide a PowerShell script to register the development ODBC driver in Windows registry

Enhancements:

  • Integrate TrinoOdbcErrorHandler to translate Trino JSON errors into detailed ODBC diagnostics and enrich SQLGetDiagRec/SQLNumResultCols behavior
  • Extend ConnectionConfig, DriverConfig, TrinoQuery, and statement/connection handles to support grantType, tokenEndpoint, query ID tracking, disconnect callbacks, and input validation

Build:

  • Update CMakeLists to include the new device flow auth provider and error handler source files

  * Initial checkin of device flow (aka device grant)
  * Better error reporting back to ODBC interface
  * Powershell installer for development ODBC driver.
@cla-bot cla-bot bot added the cla-signed label Oct 13, 2025
@sourcery-ai
Copy link

sourcery-ai bot commented Oct 13, 2025

Reviewer's Guide

This PR adds support for OIDC Device Flow by extending configuration and wiring in a new auth provider, implements comprehensive native error handling via a new TrinoOdbcErrorHandler integrated into TrinoQuery and ODBC entry points, and introduces a PowerShell script to automate ODBC driver installation.

Class diagram for new and updated authentication providers (Device Flow)

classDiagram
    class AuthConfig
    class TokenCacheAuthProviderBase
    class DeviceCredAuthConfig {
        +std::string oidcDiscoveryUrl
        +std::string clientId
        +std::string clientSecret
        +std::string scope
        +std::string grantType
        +std::string tokenEndpoint
        +obtainAccessToken(CURL*, std::string*, std::map<std::string, std::string>*) std::string
    }
    AuthConfig <|-- TokenCacheAuthProviderBase
    TokenCacheAuthProviderBase <|-- DeviceCredAuthConfig
    class "getDeviceFlowAuthProvider()" {
    }
    DeviceCredAuthConfig <.. "getDeviceFlowAuthProvider()" : creates
Loading

Class diagram for TrinoOdbcErrorHandler integration

classDiagram
    class TrinoOdbcErrorHandler {
        +OdbcError FromTrinoJson(json, std::string)
        +const Entry* LookupEntryByName(std::string)
        +bool ReloadMappingFromJson(std::string, std::string*)
        +void SetConfigDirectory(std::string)
        +std::string GetEffectiveConfigPath()
        +std::string OdbcErrorToString(OdbcError, bool)
        --
        class OdbcError {
            +SQLRETURN ret
            +std::string sqlstate
            +SQLINTEGER native
            +std::string message
            +std::string description
            +std::vector<std::string> stack
            +std::optional<int> lineNumber
            +std::optional<int> columnNumber
            +std::string queryId
        }
        class Entry {
            +int trino_code
            +std::string trino_name
            +std::string sqlstate
            +std::string description
        }
    }
    TrinoOdbcErrorHandler o-- OdbcError
    TrinoOdbcErrorHandler o-- Entry
Loading

File-Level Changes

Change Details Files
Add Device Flow authentication support
  • Extend DriverConfig and ConnectionConfig with grantType and tokenEndpoint fields and accessors
  • Update AUTH_METHOD enum, names, and mappings to include AM_DEVICE_FLOW
  • Wire grantType/tokenEndpoint through Connection::configure and ConnectionConfig constructor
  • Implement deviceFlowAuthProvider to handle device authorization and polling
  • Register new provider in CMakeLists
src/driver/config/driverConfig.cpp
src/driver/config/driverConfig.hpp
src/trinoAPIWrapper/connectionConfig.cpp
src/trinoAPIWrapper/connectionConfig.hpp
src/trinoAPIWrapper/apiAuthMethod.hpp
src/trinoAPIWrapper/authProvider/deviceFlowAuthProvider.cpp
src/trinoAPIWrapper/authProvider/deviceFlowAuthProvider.hpp
CMakeLists.txt
src/driver/handles/connHandle.cpp
src/driver/handles/connHandle.hpp
Implement native ODBC error mapping and handling
  • Add TrinoOdbcErrorHandler to map Trino JSON errors to SQLSTATE/native codes and build diagnostics catalog
  • Integrate error mapping in TrinoQuery (store optional odbcError, set queryId, call FromTrinoJson, log error)
  • Enhance SQLGetDiagRec to emit chunked, detailed messages and set diagnostic fields from OdbcError
  • Modify SQLNumResultCols to return SQL_ERROR when a Trino error is present
  • Add getters in TrinoQuery and Statement for error and queryId
src/trinoAPIWrapper/TrinoOdbcErrorHandler.cpp
src/trinoAPIWrapper/TrinoOdbcErrorHandler.hpp
src/trinoAPIWrapper/trinoQuery.cpp
src/trinoAPIWrapper/trinoQuery.hpp
src/driver/getDiagRec.cpp
src/driver/numResultCols.cpp
src/driver/handles/statementHandle.cpp
src/driver/handles/statementHandle.hpp
Add PowerShell installer for development ODBC driver
  • Introduce install_development_drivers.ps1 to detect DLL build, update registry keys, and generate .reg file
install_development_drivers.ps1

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • In deviceFlowAuthProvider.cpp, the curl_slist* headers created for POST requests aren’t freed after use—add curl_slist_free_all(headers) to prevent memory leaks.
  • TrinoQuery::parseTrinoError is implemented but never used—either hook it into error handling or remove the dead code to keep the class focused.
  • When registering/unregistering the disconnect callback via std::bind you’re creating two distinct functors—store the bound function object in a member so unregisterDisconnectCallback receives the exact same callable.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In deviceFlowAuthProvider.cpp, the curl_slist* headers created for POST requests aren’t freed after use—add curl_slist_free_all(headers) to prevent memory leaks.
- `TrinoQuery::parseTrinoError` is implemented but never used—either hook it into error handling or remove the dead code to keep the class focused.
- When registering/unregistering the disconnect callback via std::bind you’re creating two distinct functors—store the bound function object in a member so unregisterDisconnectCallback receives the exact same callable.

## Individual Comments

### Comment 1
<location> `src/trinoAPIWrapper/trinoQuery.cpp:486-487` </location>
<code_context>
+  }
+}
+
+const TrinoOdbcErrorHandler::OdbcError& TrinoQuery::getError() const {
+  return odbcError.value();
+};
</code_context>

<issue_to_address>
**issue (bug_risk):** Calling value() on std::optional without checking may throw.

getError() should check if odbcError contains a value before calling value(), or handle the case where it is not set to avoid exceptions.
</issue_to_address>

### Comment 2
<location> `src/driver/handles/connHandle.cpp:24-26` </location>
<code_context>
   return this->connectionConfig->getTrinoServerVersion();
 }

+void Connection::checkInputs(DriverConfig config) {
+  if (config.getHostname().empty()) {
+    throw std::invalid_argument("hostname cannot be empty");
+  } else if (!config.getPortNum()) {
+    throw std::invalid_argument("port cannot be empty");
+  }
+}
+
</code_context>

<issue_to_address>
**suggestion:** checkInputs throws for falsy port, which may allow zero.

If zero is not a valid port, this logic is fine. Otherwise, use 'port <= 0' for clarity.

```suggestion
  } else if (config.getPortNum() <= 0) {
    throw std::invalid_argument("port must be greater than zero");
  }
```
</issue_to_address>

### Comment 3
<location> `src/driver/numResultCols.cpp:19-20` </location>
<code_context>
                "  Requesting RecNumber: " + std::to_string(RecNumber));
-      return SQL_NO_DATA;
+
+      if (statement->trinoQuery->hasError()) {
+        TrinoOdbcErrorHandler::OdbcError odbcErr =
+            statement->trinoQuery->getError();
</code_context>

<issue_to_address>
**suggestion:** Returning SQL_ERROR for any TrinoQuery error may be too broad.

Evaluate if specific TrinoQuery errors should map to different SQL return codes, such as SQL_SUCCESS_WITH_INFO for non-critical issues.
</issue_to_address>

### Comment 4
<location> `src/trinoAPIWrapper/trinoQuery.cpp:197-198` </location>
<code_context>

   CURLcode res = curl_easy_perform(curl);

+  if (res != CURLE_OK) {
+    WriteLog(LL_ERROR, std::string("CURL error: ") + curl_easy_strerror(res));
+  }
+
</code_context>

<issue_to_address>
**suggestion (bug_risk):** CURL error is logged but not propagated.

Consider updating the code to propagate the CURL error to the caller, either by returning an error code or updating the object's state, to enable proper error handling.

Suggested implementation:

```cpp
  CURLcode res = curl_easy_perform(curl);

  if (res != CURLE_OK) {
    WriteLog(LL_ERROR, std::string("CURL error: ") + curl_easy_strerror(res));
    return res; // Propagate the error to the caller
  }

```

You will need to:
1. Update the function signature to return `CURLcode` (or an appropriate error type).
2. Update all callers of this function to handle the returned error code.
3. If this code is inside a constructor or a void function, consider updating the design to allow error propagation (e.g., by returning a status or throwing an exception).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +24 to +26
} else if (!config.getPortNum()) {
throw std::invalid_argument("port cannot be empty");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: checkInputs throws for falsy port, which may allow zero.

If zero is not a valid port, this logic is fine. Otherwise, use 'port <= 0' for clarity.

Suggested change
} else if (!config.getPortNum()) {
throw std::invalid_argument("port cannot be empty");
}
} else if (config.getPortNum() <= 0) {
throw std::invalid_argument("port must be greater than zero");
}

Copy link
Contributor

@rileymcdowell rileymcdowell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice. I left some comments for a few minor style things. The functionality here all looks good.

Please also take a look at the clang-format CICD output. I think you just need to run clang-format on the whole codebase and it should auto-resolve the issues for you.

const json& getRowAtIndex(int64_t) const;

void setQueryId(const std::string& id);
const std::string& getQueryId() const; // <-- Add this getter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this comment was leftover from development time.

if (response_json.contains("error")) {
this->error = true;
std::string errorDetails;
// errorDetails = parseTrinoError(response_json["error"]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we swap this (and the header) to trinoOdbcErrorHandler.*** to keep them consistent with the other files in the repo?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants