Repository: trinodb/trino
Pull request #25746: Enable Authentication for Google Sheet Connector Using Delegated User Credentials
Author: login: Raghav-Modani
Labels: name: cla-signed
name: docs
name: iceberg
description: Iceberg connector
name: delta-lake
description: Delta Lake connector
name: google-sheets
description: Google Sheets connector
Created: 2025-05-07T13:11:23Z | Merged: 2025-05-21T21:21:51Z


<!-- Thank you for submitting a pull request! Find more information 
at https://trino.io/development/process.html, 
at https://github.com/trinodb/trino/blob/master/.github/DEVELOPMENT.md 
and contact us on #core-dev in Slack. -->
<!-- Provide an overview for maintainers and reviewers. -->
## Description
This change lets the Google Sheets connector to authenticate by using delegated user credentials instead of raw service-account identity (which lives outside the domain). By supplying a “delegated user” email address, the connector will:
1. Obtain domain-wide delegated credentials for that user
2. Build a Sheets client under the user’s identity

Since the domain of google service account is different from the organization's Google workspace domain, this change allows to share the google sheets only within the Google workspace domain of the organization. 
<!-- Provide details that help an engineer who is unfamiliar with this part of the code. -->
## Additional context and related issues

1. **New Config option**: `gsheets.delegated-user-email`
2. **Auth Flow Update**: If `gsheets.delegated-user-email` is set, we load the service-account key, request the delegated credentials for that user, and build the Sheets client with those credentials.
3. **Backward compatibility**: Falls back to plain service-account authentication when no delegated user email is provided.

<!-- Mark the appropriate option with an (x). Propose a release note if you can.
More info at https://trino.io/development/process#release-note -->
## Release notes

( ) This is not user-visible or is docs only, and no release notes are required.
( ) Release notes are required. Please propose a release note for me.
(x ) Release notes are required, with the following suggested text:

```markdown
## Google Sheets Connector
* Add support for authentication using delegated user credentials using the `gsheets.delegated-user-email` config property. 
```



Diff:
diff --git a/docs/src/main/sphinx/connector/googlesheets.md b/docs/src/main/sphinx/connector/googlesheets.md
index 1c85fe3029e2..1d945f46be82 100644
--- a/docs/src/main/sphinx/connector/googlesheets.md
+++ b/docs/src/main/sphinx/connector/googlesheets.md
@@ -21,16 +21,17 @@ gsheets.metadata-sheet-id=exampleId
 
 The following configuration properties are available:
 
-| Property name                 | Description                                                      |
-| ----------------------------- | ---------------------------------------------------------------- |
-| `gsheets.credentials-path`    | Path to the Google API JSON key file                             |
-| `gsheets.credentials-key`     | The base64 encoded credentials key                               |
-| `gsheets.metadata-sheet-id`   | Sheet ID of the spreadsheet, that contains the table mapping     |
-| `gsheets.max-data-cache-size` | Maximum number of spreadsheets to cache, defaults to `1000`      |
-| `gsheets.data-cache-ttl`      | How long to cache spreadsheet data or metadata, defaults to `5m` |
-| `gsheets.connection-timeout`  | Timeout when connection to Google Sheets API, defaults to `20s`  |
-| `gsheets.read-timeout`        | Timeout when reading from Google Sheets API, defaults to `20s`   |
-| `gsheets.write-timeout`       | Timeout when writing to Google Sheets API, defaults to `20s`     |
+| Property name                  | Description                                                                       |
+|--------------------------------|-----------------------------------------------------------------------------------|
+| `gsheets.credentials-path`     | Path to the Google API JSON key file                                              |
+| `gsheets.credentials-key`      | The base64 encoded credentials key                                                |
+| `gsheets.delegated-user-email` | User email to impersonate the service account with domain-wide delegation enabled |
+| `gsheets.metadata-sheet-id`    | Sheet ID of the spreadsheet, that contains the table mapping                      |
+| `gsheets.max-data-cache-size`  | Maximum number of spreadsheets to cache, defaults to `1000`                       |
+| `gsheets.data-cache-ttl`       | How long to cache spreadsheet data or metadata, defaults to `5m`                  |
+| `gsheets.connection-timeout`   | Timeout when connection to Google Sheets API, defaults to `20s`                   |
+| `gsheets.read-timeout`         | Timeout when reading from Google Sheets API, defaults to `20s`                    |
+| `gsheets.write-timeout`        | Timeout when writing to Google Sheets API, defaults to `20s`                      |
 
 ## Credentials
 
@@ -51,6 +52,9 @@ The exact name of the file does not matter -- it can be named anything.
 Alternatively, set the `gsheets.credentials-key` configuration property.
 It should contain the contents of the JSON file, encoded using base64.
 
+Optionally, set the `gsheets.delegated-user-email` property to impersonate a user.
+This allows you to share Google Sheets with this email instead of the service account.
+
 ## Metadata sheet
 
 The metadata sheet is used to map table names to sheet IDs.
diff --git a/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsClient.java b/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsClient.java
index d7d2cafef5d0..594d5e6bb34f 100644
--- a/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsClient.java
+++ b/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsClient.java
@@ -270,7 +270,7 @@ private static Credential getCredentials(SheetsConfig sheetsConfig)
     {
         if (sheetsConfig.getCredentialsFilePath().isPresent()) {
             try (InputStream in = new FileInputStream(sheetsConfig.getCredentialsFilePath().get())) {
-                return credentialFromStream(in);
+                return credentialFromStream(in, sheetsConfig.getDelegatedUserEmail());
             }
             catch (IOException e) {
                 throw new TrinoException(SHEETS_BAD_CREDENTIALS_ERROR, e);
@@ -280,7 +280,7 @@ private static Credential getCredentials(SheetsConfig sheetsConfig)
         if (sheetsConfig.getCredentialsKey().isPresent()) {
             try {
                 return credentialFromStream(
-                                new ByteArrayInputStream(Base64.getDecoder().decode(sheetsConfig.getCredentialsKey().get())));
+                        new ByteArrayInputStream(Base64.getDecoder().decode(sheetsConfig.getCredentialsKey().get())), sheetsConfig.getDelegatedUserEmail());
             }
             catch (IOException e) {
                 throw new TrinoException(SHEETS_BAD_CREDENTIALS_ERROR, e);
@@ -290,10 +290,12 @@ private static Credential getCredentials(SheetsConfig sheetsConfig)
         throw new TrinoException(SHEETS_BAD_CREDENTIALS_ERROR, "No sheets credentials were provided");
     }
 
-    private static Credential credentialFromStream(InputStream inputStream)
+    private static Credential credentialFromStream(InputStream inputStream, Optional<String> delegatedUserEmail)
             throws IOException
     {
-        return GoogleCredential.fromStream(inputStream).createScoped(SCOPES);
+        GoogleCredential credential = GoogleCredential.fromStream(inputStream).createScoped(SCOPES);
+        delegatedUserEmail.ifPresent(credential::createDelegated);
+        return credential;
     }
 
     private List<List<Object>> readAllValuesFromSheetExpression(String sheetExpression)
diff --git a/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java b/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
index 497c38c8e960..85085ed1d837 100644
--- a/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
+++ b/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
@@ -31,6 +31,7 @@ public class SheetsConfig
 {
     private Optional<String> credentialsFilePath = Optional.empty();
     private Optional<String> credentialsKey = Optional.empty();
+    private String delegatedUserEmail;
     private Optional<String> metadataSheetId = Optional.empty();
     private int sheetsDataMaxCacheSize = 1000;
     private Duration sheetsDataExpireAfterWrite = new Duration(5, TimeUnit.MINUTES);
@@ -75,6 +76,20 @@ public SheetsConfig setCredentialsKey(String credentialsKey)
         return this;
     }
 
+    @NotNull
+    public Optional<String> getDelegatedUserEmail()
+    {
+        return Optional.ofNullable(delegatedUserEmail);
+    }
+
+    @Config("gsheets.delegated-user-email")
+    @ConfigDescription("Delegated user email to impersonate the service account")
+    public SheetsConfig setDelegatedUserEmail(String delegatedUserEmail)
+    {
+        this.delegatedUserEmail = delegatedUserEmail;
+        return this;
+    }
+
     @NotNull
     public Optional<String> getMetadataSheetId()
     {
diff --git a/plugin/trino-google-sheets/src/test/java/io/trino/plugin/google/sheets/TestSheetsConfig.java b/plugin/trino-google-sheets/src/test/java/io/trino/plugin/google/sheets/TestSheetsConfig.java
index 5b5b6f1b2412..964b837f25f0 100644
--- a/plugin/trino-google-sheets/src/test/java/io/trino/plugin/google/sheets/TestSheetsConfig.java
+++ b/plugin/trino-google-sheets/src/test/java/io/trino/plugin/google/sheets/TestSheetsConfig.java
@@ -46,6 +46,7 @@ public void testDefaults()
         assertRecordedDefaults(recordDefaults(SheetsConfig.class)
                 .setCredentialsFilePath(null)
                 .setCredentialsKey(null)
+                .setDelegatedUserEmail(null)
                 .setMetadataSheetId(null)
                 .setSheetsDataMaxCacheSize(1000)
                 .setSheetsDataExpireAfterWrite(new Duration(5, TimeUnit.MINUTES))
@@ -62,6 +63,7 @@ public void testExplicitPropertyMappingsCredentialsPath()
 
         Map<String, String> properties = ImmutableMap.<String, String>builder()
                 .put("gsheets.credentials-path", credentialsFile.toString())
+                .put("gsheets.delegated-user-email", "fooBar@gmail.com")
                 .put("gsheets.metadata-sheet-id", "foo_bar_sheet_id#Sheet1")
                 .put("gsheets.max-data-cache-size", "2000")
                 .put("gsheets.data-cache-ttl", "10m")
@@ -75,6 +77,7 @@ public void testExplicitPropertyMappingsCredentialsPath()
 
         assertThat(config.getCredentialsKey()).isEqualTo(Optional.empty());
         assertThat(config.getCredentialsFilePath()).isEqualTo(Optional.of(credentialsFile.toString()));
+        assertThat(config.getDelegatedUserEmail()).contains("fooBar@gmail.com");
         assertThat(config.getMetadataSheetId()).isEqualTo(Optional.of("foo_bar_sheet_id#Sheet1"));
         assertThat(config.getSheetsDataMaxCacheSize()).isEqualTo(2000);
         assertThat(config.getSheetsDataExpireAfterWrite()).isEqualTo(Duration.valueOf("10m"));
@@ -88,6 +91,7 @@ public void testExplicitPropertyMappingsCredentialsKey()
     {
         Map<String, String> properties = ImmutableMap.<String, String>builder()
                 .put("gsheets.credentials-key", BASE_64_ENCODED_TEST_KEY)
+                .put("gsheets.delegated-user-email", "fooBar@gmail.com")
                 .put("gsheets.metadata-sheet-id", "foo_bar_sheet_id#Sheet1")
                 .put("gsheets.max-data-cache-size", "2000")
                 .put("gsheets.data-cache-ttl", "10m")
@@ -99,6 +103,7 @@ public void testExplicitPropertyMappingsCredentialsKey()
 
         assertThat(config.getCredentialsKey()).isEqualTo(Optional.of(BASE_64_ENCODED_TEST_KEY));
         assertThat(config.getCredentialsFilePath()).isEqualTo(Optional.empty());
+        assertThat(config.getDelegatedUserEmail()).contains("fooBar@gmail.com");
         assertThat(config.getMetadataSheetId()).isEqualTo(Optional.of("foo_bar_sheet_id#Sheet1"));
         assertThat(config.getSheetsDataMaxCacheSize()).isEqualTo(2000);
         assertThat(config.getSheetsDataExpireAfterWrite()).isEqualTo(Duration.valueOf("10m"));


Reviews:
user: login: Praveen2112
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T13:29:23Z
user: login: Praveen2112
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T13:30:02Z
user: login: ebyhr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T21:53:58Z
user: login: ebyhr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T06:37:41Z
user: login: ebyhr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T06:41:59Z
user: login: Praveen2112
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T07:36:36Z
user: login: Praveen2112
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T07:40:23Z
user: login: Praveen2112
body: Is there is any manual test or validation that this email-id is being used ? Like a manual screenshot based on the history of the sheet ? @ebyhr Any alternatives we could use ?
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-09T13:49:34Z
user: login: Praveen2112
body: LGTM. Can we have a maual test or somesort of a screenshot to ensure the delegated user email is being used. 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-13T12:33:30Z
user: login: ebyhr
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-13T22:01:02Z
user: login: ebyhr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T12:49:54Z
user: login: Praveen2112
body: @Raghav-Modani Can you please squash the changes into 1 ?
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T14:33:52Z

Review comments:
diff_hunk: @@ -90,6 +92,21 @@ public SheetsConfig setMetadataSheetId(String metadataSheetId)
         return this;
     }
 
+    @NotNull
+    public Optional<String> getDelegatedUserEmail()
+    {
+        return delegatedUserEmail;
+    }
+
+    @Config("gsheets.delegated-user-email")
+    @LegacyConfig("delegated-user-email")
+    @ConfigDescription("Delegated user email to impersonate the service account")
+    public SheetsConfig setDelegatedUserEmail(String delegatedUserEmail)
+    {
+        this.delegatedUserEmail = Optional.ofNullable(delegatedUserEmail);
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
user: login: Praveen2112
body: nit: We don't have to set nullable. 
created_at: 2025-05-07T13:26:10Z
author_association: MEMBER
diff_hunk: @@ -90,6 +92,21 @@ public SheetsConfig setMetadataSheetId(String metadataSheetId)
         return this;
     }
 
+    @NotNull
+    public Optional<String> getDelegatedUserEmail()
+    {
+        return delegatedUserEmail;
+    }
+
+    @Config("gsheets.delegated-user-email")
+    @LegacyConfig("delegated-user-email")
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
user: login: Praveen2112
body: This is a new config right ? Why do we need to have a legacy annotation ?
created_at: 2025-05-07T13:26:29Z
author_association: MEMBER
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
user: login: Praveen2112
body: Please update them in `TestSheetsConfig`
created_at: 2025-05-07T13:27:00Z
author_association: MEMBER
diff_hunk: @@ -290,9 +292,12 @@ private static Credential getCredentials(SheetsConfig sheetsConfig)
         throw new TrinoException(SHEETS_BAD_CREDENTIALS_ERROR, "No sheets credentials were provided");
     }
 
-    private static Credential credentialFromStream(InputStream inputStream)
+    private static Credential credentialFromStream(InputStream inputStream, SheetsConfig sheetsConfig)
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsClient.java
user: login: Praveen2112
body: instead of passing `config` we could pass the `delegatedUserEmail` right ?
created_at: 2025-05-07T13:30:02Z
author_association: MEMBER
diff_hunk: @@ -51,6 +52,9 @@ The exact name of the file does not matter -- it can be named anything.
 Alternatively, set the `gsheets.credentials-key` configuration property.
 It should contain the contents of the JSON file, encoded using base64.
 
+Optionally, set the `gsheets.delegated-user-email` configuration property to the email id of the user to impersonate the service account.
+If this property is set, there is no need to share the google sheets with the service account email id. Instead, share the sheets to this user email.
path: docs/src/main/sphinx/connector/googlesheets.md
user: login: ebyhr
body: Replace google sheets with Google Sheets for consistency with other places. 
created_at: 2025-05-07T21:53:28Z
author_association: MEMBER
diff_hunk: @@ -51,6 +52,9 @@ The exact name of the file does not matter -- it can be named anything.
 Alternatively, set the `gsheets.credentials-key` configuration property.
 It should contain the contents of the JSON file, encoded using base64.
 
+Optionally, set the `gsheets.delegated-user-email` configuration property to the email id of the user to impersonate the service account.
+If this property is set, there is no need to share the google sheets with the service account email id. Instead, share the sheets to this user email.
+
path: docs/src/main/sphinx/connector/googlesheets.md
user: login: ebyhr
body: These lines are too long. Please wrap at 80 characters. 
created_at: 2025-05-07T21:53:30Z
author_association: MEMBER
diff_hunk: @@ -90,6 +92,21 @@ public SheetsConfig setMetadataSheetId(String metadataSheetId)
         return this;
     }
 
+    @NotNull
+    public Optional<String> getDelegatedUserEmail()
+    {
+        return delegatedUserEmail;
+    }
+
+    @Config("gsheets.delegated-user-email")
+    @LegacyConfig("delegated-user-email")
+    @ConfigDescription("Delegated user email to impersonate the service account")
+    public SheetsConfig setDelegatedUserEmail(String delegatedUserEmail)
+    {
+        this.delegatedUserEmail = Optional.ofNullable(delegatedUserEmail);
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
user: login: ebyhr
body: Reminder. 
created_at: 2025-05-09T06:37:40Z
author_association: MEMBER
diff_hunk: @@ -290,9 +292,12 @@ private static Credential getCredentials(SheetsConfig sheetsConfig)
         throw new TrinoException(SHEETS_BAD_CREDENTIALS_ERROR, "No sheets credentials were provided");
     }
 
-    private static Credential credentialFromStream(InputStream inputStream)
+    private Credential credentialFromStream(InputStream inputStream, Optional<String> delegatedUserEmail)
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsClient.java
user: login: ebyhr
body: Please revert change of `static` removal. 
created_at: 2025-05-09T06:40:47Z
author_association: MEMBER
diff_hunk: @@ -290,9 +292,12 @@ private static Credential getCredentials(SheetsConfig sheetsConfig)
         throw new TrinoException(SHEETS_BAD_CREDENTIALS_ERROR, "No sheets credentials were provided");
     }
 
-    private static Credential credentialFromStream(InputStream inputStream)
+    private Credential credentialFromStream(InputStream inputStream, Optional<String> delegatedUserEmail)
             throws IOException
     {
+        if (delegatedUserEmail.isPresent()) {
+            return GoogleCredential.fromStream(inputStream).createScoped(SCOPES).createDelegated(delegatedUserEmail.get());
+        }
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsClient.java
user: login: ebyhr
body: ```java
        GoogleCredential credential = GoogleCredential.fromStream(inputStream).createScoped(SCOPES);
        delegatedUserEmail.ifPresent(credential::createDelegated);
        return credential;
```
created_at: 2025-05-09T06:41:29Z
author_association: MEMBER
diff_hunk: @@ -80,13 +80,15 @@ public class SheetsClient
     private final LoadingCache<String, List<List<Object>>> sheetDataCache;
 
     private final Optional<String> metadataSheetId;
+    private final Optional<String> delegatedUserEmail;
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsClient.java
user: login: ebyhr
body: Remove redundant field. 
created_at: 2025-05-09T06:41:56Z
author_association: MEMBER
diff_hunk: @@ -51,6 +52,8 @@ The exact name of the file does not matter -- it can be named anything.
 Alternatively, set the `gsheets.credentials-key` configuration property.
 It should contain the contents of the JSON file, encoded using base64.
 
+Optionally, set the `gsheets.delegated-user-email` property to impersonate a user. This allows you to share Google Sheets with this email instead of the service account.
path: docs/src/main/sphinx/connector/googlesheets.md
user: login: Praveen2112
body: nit:
```
Optionally, set the `gsheets.delegated-user-email` property to impersonate a user. 
This allows you to share Google Sheets with this email instead of the service account.
```
Instead of a single line
created_at: 2025-05-09T07:34:23Z
author_association: MEMBER
diff_hunk: @@ -266,11 +268,11 @@ private Map<String, Optional<String>> getAllTableSheetExpressionMapping()
         return tableSheetMap.buildOrThrow();
     }
 
-    private static Credential getCredentials(SheetsConfig sheetsConfig)
+    private Credential getCredentials(SheetsConfig sheetsConfig)
     {
         if (sheetsConfig.getCredentialsFilePath().isPresent()) {
             try (InputStream in = new FileInputStream(sheetsConfig.getCredentialsFilePath().get())) {
-                return credentialFromStream(in);
+                return credentialFromStream(in, delegatedUserEmail);
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsClient.java
user: login: Praveen2112
body: Here we could use `sheetsConfig#getDelegatedUserEmail` and there by all the method can be static.
created_at: 2025-05-09T07:40:23Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,8 @@ public class SheetsConfig
     private Optional<String> credentialsFilePath = Optional.empty();
     private Optional<String> credentialsKey = Optional.empty();
     private Optional<String> metadataSheetId = Optional.empty();
+    private String delegatedUserEmail;
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
user: login: Praveen2112
body: nit: It can be `Optional<String> delegatedUserEmail` 
created_at: 2025-05-09T13:17:56Z
author_association: MEMBER
diff_hunk: @@ -21,16 +21,17 @@ gsheets.metadata-sheet-id=exampleId
 
 The following configuration properties are available:
 
-| Property name                 | Description                                                      |
-| ----------------------------- | ---------------------------------------------------------------- |
-| `gsheets.credentials-path`    | Path to the Google API JSON key file                             |
-| `gsheets.credentials-key`     | The base64 encoded credentials key                               |
-| `gsheets.metadata-sheet-id`   | Sheet ID of the spreadsheet, that contains the table mapping     |
-| `gsheets.max-data-cache-size` | Maximum number of spreadsheets to cache, defaults to `1000`      |
-| `gsheets.data-cache-ttl`      | How long to cache spreadsheet data or metadata, defaults to `5m` |
-| `gsheets.connection-timeout`  | Timeout when connection to Google Sheets API, defaults to `20s`  |
-| `gsheets.read-timeout`        | Timeout when reading from Google Sheets API, defaults to `20s`   |
-| `gsheets.write-timeout`       | Timeout when writing to Google Sheets API, defaults to `20s`     |
+| Property name                  | Description                                                                       |
+|--------------------------------|-----------------------------------------------------------------------------------|
+| `gsheets.credentials-path`     | Path to the Google API JSON key file                                              |
+| `gsheets.credentials-key`      | The base64 encoded credentials key                                                |
+| `gsheets.metadata-sheet-id`    | Sheet ID of the spreadsheet, that contains the table mapping                      |
+| `gsheets.delegated-user-email` | User email to impersonate the service account with domain-wide delegation enabled |
path: docs/src/main/sphinx/connector/googlesheets.md
user: login: Praveen2112
body: Can we swap these properties - just to keep related properties closer. 
created_at: 2025-05-13T12:31:58Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,8 @@ public class SheetsConfig
     private Optional<String> credentialsFilePath = Optional.empty();
     private Optional<String> credentialsKey = Optional.empty();
     private Optional<String> metadataSheetId = Optional.empty();
+    private String delegatedUserEmail;
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
user: login: Praveen2112
body: Any updates on this comment ?
created_at: 2025-05-13T12:32:26Z
author_association: MEMBER
diff_hunk: @@ -21,16 +21,17 @@ gsheets.metadata-sheet-id=exampleId
 
 The following configuration properties are available:
 
-| Property name                 | Description                                                      |
-| ----------------------------- | ---------------------------------------------------------------- |
-| `gsheets.credentials-path`    | Path to the Google API JSON key file                             |
-| `gsheets.credentials-key`     | The base64 encoded credentials key                               |
-| `gsheets.metadata-sheet-id`   | Sheet ID of the spreadsheet, that contains the table mapping     |
-| `gsheets.max-data-cache-size` | Maximum number of spreadsheets to cache, defaults to `1000`      |
-| `gsheets.data-cache-ttl`      | How long to cache spreadsheet data or metadata, defaults to `5m` |
-| `gsheets.connection-timeout`  | Timeout when connection to Google Sheets API, defaults to `20s`  |
-| `gsheets.read-timeout`        | Timeout when reading from Google Sheets API, defaults to `20s`   |
-| `gsheets.write-timeout`       | Timeout when writing to Google Sheets API, defaults to `20s`     |
+| Property name                  | Description                                                                       |
+|--------------------------------|-----------------------------------------------------------------------------------|
+| `gsheets.credentials-path`     | Path to the Google API JSON key file                                              |
+| `gsheets.credentials-key`      | The base64 encoded credentials key                                                |
+| `gsheets.metadata-sheet-id`    | Sheet ID of the spreadsheet, that contains the table mapping                      |
+| `gsheets.delegated-user-email` | User email to impersonate the service account with domain-wide delegation enabled |
+| `gsheets.max-data-cache-size`  | Maximum number of spreadsheets to cache, defaults to `1000`                       |
+| `gsheets.data-cache-ttl`       | How long to cache spreadsheet data or metadata, defaults to `5m`                  |
+| `gsheets.connection-timeout`   | Timeout when connection to Google Sheets API, defaults to `20s`                   |
+| `gsheets.read-timeout`         | Timeout when reading from Google Sheets API, defaults to `20s`                    |
+| `gsheets.write-timeout`        | Timeout when writing to Google Sheets API, defaults to `20s`                      |
path: docs/src/main/sphinx/connector/googlesheets.md
user: login: ebyhr
body: @Raghav-Modani Can you send follow-up PR to change these properties to `list-table`?
created_at: 2025-05-13T22:00:32Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,8 @@ public class SheetsConfig
     private Optional<String> credentialsFilePath = Optional.empty();
     private Optional<String> credentialsKey = Optional.empty();
     private Optional<String> metadataSheetId = Optional.empty();
+    private String delegatedUserEmail;
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
user: login: ebyhr
body: Please move under `credentialsKey` since this config is related to credentials.
Same for getter/setter. 
created_at: 2025-05-21T12:45:53Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,8 @@ public class SheetsConfig
     private Optional<String> credentialsFilePath = Optional.empty();
     private Optional<String> credentialsKey = Optional.empty();
     private Optional<String> metadataSheetId = Optional.empty();
+    private String delegatedUserEmail;
+
path: plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsConfig.java
user: login: ebyhr
body: Remove redundant empty line. 
created_at: 2025-05-21T12:46:06Z
author_association: MEMBER
diff_hunk: @@ -76,6 +78,7 @@ public void testExplicitPropertyMappingsCredentialsPath()
         assertThat(config.getCredentialsKey()).isEqualTo(Optional.empty());
         assertThat(config.getCredentialsFilePath()).isEqualTo(Optional.of(credentialsFile.toString()));
         assertThat(config.getMetadataSheetId()).isEqualTo(Optional.of("foo_bar_sheet_id#Sheet1"));
+        assertThat(config.getDelegatedUserEmail()).isEqualTo(Optional.of("fooBar@gmail.com"));
path: plugin/trino-google-sheets/src/test/java/io/trino/plugin/google/sheets/TestSheetsConfig.java
user: login: ebyhr
body: We can use `.contains("fooBar@gmail.com")` instead. 
created_at: 2025-05-21T12:49:35Z
author_association: MEMBER

PR comments:
user: login: cla-bot[bot]
created_at: 2025-05-07T13:11:26Z
body: Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: **Raghav Modani**.
This is most likely caused by a git client misconfiguration; please make sure to:
1. check if your git client is configured with an email to sign commits `git config --list | grep email`
2. If not, set it up using `git config --global user.email email@example.com`
3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails
author_association: NONE
user: login: cla-bot[bot]
created_at: 2025-05-07T18:50:14Z
body: Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: **Raghav Modani**.
This is most likely caused by a git client misconfiguration; please make sure to:
1. check if your git client is configured with an email to sign commits `git config --list | grep email`
2. If not, set it up using `git config --global user.email email@example.com`
3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails
author_association: NONE
user: login: ebyhr
created_at: 2025-05-08T06:09:33Z
body: Could you remove unrelated commits? 
![Screenshot 2025-05-08 at 15 09 03](https://github.com/user-attachments/assets/0565a83b-e5b4-41d7-a9d0-56ff13c0bcc2)

author_association: MEMBER
user: login: Raghav-Modani
created_at: 2025-05-09T06:35:28Z
body: @ebyhr @Praveen2112 Please review this PR and let me know if any changes are required. Thanks!
author_association: CONTRIBUTOR
user: login: Raghav-Modani
created_at: 2025-05-09T13:38:11Z
body: @Praveen2112 @ebyhr looks like the `maven-checks 25-ea` is failing consistently. Apart from it, could you please review the latest commits and suggest any changes required?
author_association: CONTRIBUTOR
user: login: Praveen2112
created_at: 2025-05-09T13:51:03Z
body: @Raghav-Modani Btw please squash the commit into 1.
author_association: MEMBER
user: login: Raghav-Modani
created_at: 2025-05-13T12:25:10Z
body: > @Raghav-Modani Btw please squash the commit into 1.

Its done. Please review. @Praveen2112 @ebyhr 
author_association: CONTRIBUTOR
user: login: Raghav-Modani
created_at: 2025-05-13T17:39:37Z
body: > LGTM. Can we have a maual test or somesort of a screenshot to ensure the delegated user email is being used.

Definitely, I will attach some screenshot on the working of this config property here shortly.
author_association: CONTRIBUTOR
user: login: Raghav-Modani
created_at: 2025-05-14T12:07:13Z
body: ### **Screenshots ensuring the delegated user email property is working as expected**

1. Set the `gsheets.delegated-user-email` property in the configuration file of the connector as shown:
<img width="755" alt="SS1" src="https://github.com/user-attachments/assets/02d8b569-f8b8-446d-bb4d-0971e4f00548" />


2. Share the metadata-mapping sheet with the `delegated-user-email`, no need to share this with the service account's email-id.
<img width="1343" alt="SS2" src="https://github.com/user-attachments/assets/3bfab553-8191-4282-b7b1-7a97ed2584a8" />

3. Also, share the sheets, which are to be queried, with the same `delegated-user-email`.
<img width="1075" alt="SS3" src="https://github.com/user-attachments/assets/f1b754b4-5b49-4ac0-93ed-dd9115477d44" />

4. Now, query the sheets. Results are shown below:
![image](https://github.com/user-attachments/assets/b32220c7-077f-4aae-9c0f-777fa35bc8c4)


author_association: CONTRIBUTOR
user: login: Raghav-Modani
created_at: 2025-05-14T12:27:59Z
body: Hey @Praveen2112 , I have attached the screenshots, please check.
author_association: CONTRIBUTOR
user: login: Raghav-Modani
created_at: 2025-05-15T07:54:29Z
body: Hey @Praveen2112 @ebyhr , let me know if there are any issues. If not, can we please merge this PR?
Thanks!
author_association: CONTRIBUTOR
user: login: ebyhr
created_at: 2025-05-15T13:02:32Z
body: The commit title is too long. Please follow https://trino.io/development/process#pull-request-and-commit-guidelines-
> Limit the subject line to 50 characters.
author_association: MEMBER
user: login: Raghav-Modani
created_at: 2025-05-15T14:50:37Z
body: > The commit title is too long. Please follow https://trino.io/development/process#pull-request-and-commit-guidelines-
> 
> > Limit the subject line to 50 characters.

Hey @ebyhr , changed the commit title and the message body to adhere to the guidelines. I hope this works now. Let me know any other issues to be taken care of?
author_association: CONTRIBUTOR
user: login: ebyhr
created_at: 2025-05-15T21:58:22Z
body: @Raghav-Modani Please wrap the commit body at 72 characters. https://trino.io/development/process#pull-request-and-commit-guidelines-
author_association: MEMBER
user: login: Raghav-Modani
created_at: 2025-05-16T08:29:56Z
body: Hey @ebyhr , changed the commit message body. Please check now. 
author_association: CONTRIBUTOR
user: login: Raghav-Modani
created_at: 2025-05-19T08:56:48Z
body: @ebyhr @Praveen2112 can we merge it now?
author_association: CONTRIBUTOR
user: login: Raghav-Modani
created_at: 2025-05-20T05:19:32Z
body: @Praveen2112 @ebyhr are there any other requirements to be fulfilled to get this PR merged? If not, can we please get this merged? Thanks!
author_association: CONTRIBUTOR
user: login: bhargav2427
created_at: 2025-05-21T12:41:08Z
body: @ebyhr @Praveen2112 Could you please check this Pull Request, we are waiting for this.
author_association: NONE
user: login: Raghav-Modani
created_at: 2025-05-21T16:21:51Z
body: @Praveen2112 squashed all the commits into 1. 
author_association: CONTRIBUTOR
user: login: Raghav-Modani
created_at: 2025-05-22T05:16:59Z
body: Thanks @ebyhr @Praveen2112 for merging this!
author_association: CONTRIBUTOR