This is an automated email from the ASF dual-hosted git repository.

gurwls223 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark-connect-go.git


The following commit(s) were added to refs/heads/master by this push:
     new af02e0e  [SPARK-43958] Adding support for Channel Builder
af02e0e is described below

commit af02e0eda6dd0f6d2d3e39c9822db7b5032eaa82
Author: Martin Grund <martin.gr...@databricks.com>
AuthorDate: Sun Jun 4 20:49:09 2023 +0900

    [SPARK-43958] Adding support for Channel Builder
    
    ### What changes were proposed in this pull request?
    Add support for parsing the connection string of Spark Connect in the same 
way was it's done for the other Spark Connect clients.
    
    ### Why are the changes needed?
    Compatibility
    
    ### Does this PR introduce _any_ user-facing change?
    No
    
    ### How was this patch tested?
    UT
    
    Closes #8 from grundprinzip/SPARK-43958.
    
    Authored-by: Martin Grund <martin.gr...@databricks.com>
    Signed-off-by: Hyukjin Kwon <gurwls...@apache.org>
---
 client/channel/channel.go                       | 141 ++++++++++++++++++++++++
 client/channel/channel_test.go                  |  82 ++++++++++++++
 client/sql/sparksession.go                      |  36 ++++--
 cmd/spark-connect-example-spark-session/main.go |   4 +-
 go.mod                                          |  17 ++-
 go.sum                                          |  34 ++++--
 6 files changed, 290 insertions(+), 24 deletions(-)

diff --git a/client/channel/channel.go b/client/channel/channel.go
new file mode 100644
index 0000000..6cf7696
--- /dev/null
+++ b/client/channel/channel.go
@@ -0,0 +1,141 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package channel
+
+import (
+       "crypto/tls"
+       "crypto/x509"
+       "errors"
+       "fmt"
+       "net"
+       "net/url"
+       "strconv"
+       "strings"
+
+       "golang.org/x/oauth2"
+       "google.golang.org/grpc"
+       "google.golang.org/grpc/credentials"
+       "google.golang.org/grpc/credentials/oauth"
+)
+
+// Reserved header parameters that must not be injected as variables.
+var reservedParams = []string{"user_id", "token", "use_ssl"}
+
+// The ChannelBuilder is used to parse the different parameters of the 
connection
+// string according to the specification documented here:
+//
+//     
https://github.com/apache/spark/blob/master/connector/connect/docs/client-connection-string.md
+type ChannelBuilder struct {
+       Host    string
+       Port    int
+       Token   string
+       User    string
+       Headers map[string]string
+}
+
+// Finalizes the creation of the gprc.ClientConn by creating a GRPC channel
+// with the necessary options extracted from the connection string. For
+// TLS connections, this function will load the system certificates.
+func (cb *ChannelBuilder) Build() (*grpc.ClientConn, error) {
+       var opts []grpc.DialOption
+
+       opts = append(opts, grpc.WithAuthority(cb.Host))
+       if cb.Token == "" {
+               opts = append(opts, grpc.WithInsecure())
+       } else {
+               // Note: On the Windows platform, use of x509.SystemCertPool() 
requires
+               // go version 1.18 or higher.
+               systemRoots, err := x509.SystemCertPool()
+               if err != nil {
+                       return nil, err
+               }
+               cred := credentials.NewTLS(&tls.Config{
+                       RootCAs: systemRoots,
+               })
+               opts = append(opts, grpc.WithTransportCredentials(cred))
+
+               t := oauth2.Token{
+                       AccessToken: cb.Token,
+                       TokenType:   "bearer",
+               }
+               opts = append(opts, 
grpc.WithPerRPCCredentials(oauth.NewOauthAccess(&t)))
+       }
+
+       remote := fmt.Sprintf("%v:%v", cb.Host, cb.Port)
+       conn, err := grpc.Dial(remote, opts...)
+       if err != nil {
+               return nil, fmt.Errorf("failed to connect to remote %s: %w", 
remote, err)
+       }
+       return conn, nil
+}
+
+// Creates a new instance of the ChannelBuilder. This constructor effectively
+// parses the connection string and extracts the relevant parameters directly.
+func NewBuilder(connection string) (*ChannelBuilder, error) {
+
+       u, err := url.Parse(connection)
+       if err != nil {
+               return nil, err
+       }
+
+       if u.Scheme != "sc" {
+               return nil, errors.New("URL schema must be set to `sc`.")
+       }
+
+       var port = 15002
+       var host = u.Host
+       // Check if the host part of the URL contains a port and extract.
+       if strings.Contains(u.Host, ":") {
+               hostStr, portStr, err := net.SplitHostPort(u.Host)
+               if err != nil {
+                       return nil, err
+               }
+               host = hostStr
+               if len(portStr) != 0 {
+                       port, err = strconv.Atoi(portStr)
+                       if err != nil {
+                               return nil, err
+                       }
+               }
+       }
+
+       // Validate that the URL path is empty or follows the right format.
+       if u.Path != "" && !strings.HasPrefix(u.Path, "/;") {
+               return nil, fmt.Errorf("The URL path (%v) must be empty or have 
a proper parameter syntax.", u.Path)
+       }
+
+       cb := &ChannelBuilder{
+               Host:    host,
+               Port:    port,
+               Headers: map[string]string{},
+       }
+
+       elements := strings.Split(u.Path, ";")
+       for _, e := range elements {
+               props := strings.Split(e, "=")
+               if len(props) == 2 {
+                       if props[0] == "token" {
+                               cb.Token = props[1]
+                       } else if props[0] == "user_id" {
+                               cb.User = props[1]
+                       } else {
+                               cb.Headers[props[0]] = props[1]
+                       }
+               }
+       }
+       return cb, nil
+}
diff --git a/client/channel/channel_test.go b/client/channel/channel_test.go
new file mode 100644
index 0000000..aedceb8
--- /dev/null
+++ b/client/channel/channel_test.go
@@ -0,0 +1,82 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package channel_test
+
+import (
+       "strings"
+       "testing"
+
+       "github.com/apache/spark-connect-go/v_3_4/client/channel"
+       "github.com/stretchr/testify/assert"
+)
+
+const goodChannelURL = "sc://host:15002/;user_id=a;token=b;x-other-header=c"
+
+func TestBasicChannelBuilder(t *testing.T) {
+       cb, _ := channel.NewBuilder(goodChannelURL)
+       if cb == nil {
+               t.Error("ChannelBuilder must not be null")
+       }
+}
+
+func TestBasicChannelParsing(t *testing.T) {
+       _, err := channel.NewBuilder("abc://asdada:1333")
+
+       assert.False(t, strings.Contains(err.Error(), "scheme"), "Channel build 
should fail with wrong scheme")
+       cb, err := channel.NewBuilder("sc://empty")
+
+       assert.Nilf(t, err, "Valid path should not fail: %v", err)
+       assert.Equalf(t, 15002, cb.Port, "Default port must be set, but got 
%v", cb.Port)
+
+       _, err = channel.NewBuilder("sc://empty:port")
+       assert.NotNilf(t, err, "Port must be a valid integer %v", err)
+
+       _, err = channel.NewBuilder("sc://empty:9999999999999")
+       assert.Nilf(t, err, "Port must be a valid number %v", err)
+
+       _, err = channel.NewBuilder("sc://abcd/this")
+       assert.True(t, strings.Contains(err.Error(), "The URL path"), "URL path 
elements are not allowed")
+
+       cb, err = channel.NewBuilder(goodChannelURL)
+       assert.Equal(t, "host", cb.Host)
+       assert.Equal(t, 15002, cb.Port)
+       assert.Len(t, cb.Headers, 1)
+       assert.Equal(t, "c", cb.Headers["x-other-header"])
+       assert.Equal(t, "a", cb.User)
+       assert.Equal(t, "b", cb.Token)
+
+       cb, err = 
channel.NewBuilder("sc://localhost:443/;token=token;user_id=user_id;cluster_id=a")
+       assert.Nilf(t, err, "Unexpected error: %v", err)
+       assert.Equal(t, 443, cb.Port)
+       assert.Equal(t, "localhost", cb.Host)
+       assert.Equal(t, "token", cb.Token)
+       assert.Equal(t, "user_id", cb.User)
+}
+
+func TestChannelBuildConnect(t *testing.T) {
+       cb, err := channel.NewBuilder("sc://localhost")
+       assert.Nil(t, err, "Should not have an error for a proper URL.")
+       conn, err := cb.Build()
+       assert.Nil(t, err, "no error for proper connection")
+       assert.NotNil(t, conn)
+
+       cb, err = channel.NewBuilder("sc://localhost:443/;token=abcd;user_id=a")
+       assert.Nil(t, err, "Should not have an error for a proper URL.")
+       conn, err = cb.Build()
+       assert.Nil(t, err, "no error for proper connection")
+       assert.NotNil(t, conn)
+}
diff --git a/client/sql/sparksession.go b/client/sql/sparksession.go
index 86f4cd1..6a3ca8c 100644
--- a/client/sql/sparksession.go
+++ b/client/sql/sparksession.go
@@ -19,10 +19,11 @@ package sql
 import (
        "context"
        "fmt"
+
+       "github.com/apache/spark-connect-go/v_3_4/client/channel"
        proto "github.com/apache/spark-connect-go/v_3_4/internal/generated"
        "github.com/google/uuid"
-       "google.golang.org/grpc"
-       "google.golang.org/grpc/credentials/insecure"
+       "google.golang.org/grpc/metadata"
 )
 
 var SparkSession sparkSessionBuilderEntrypoint
@@ -47,25 +48,35 @@ func (s SparkSessionBuilder) Remote(connectionString 
string) SparkSessionBuilder
 }
 
 func (s SparkSessionBuilder) Build() (sparkSession, error) {
-       opts := []grpc.DialOption{
-               grpc.WithTransportCredentials(insecure.NewCredentials()),
+
+       cb, err := channel.NewBuilder(s.connectionString)
+       if err != nil {
+               return nil, fmt.Errorf("failed to connect to remote %s: %w", 
s.connectionString, err)
        }
 
-       conn, err := grpc.Dial(s.connectionString, opts...)
+       conn, err := cb.Build()
        if err != nil {
                return nil, fmt.Errorf("failed to connect to remote %s: %w", 
s.connectionString, err)
        }
 
+       // Add metadata to the request.
+       meta := metadata.MD{}
+       for k, v := range cb.Headers {
+               meta[k] = append(meta[k], v)
+       }
+
        client := proto.NewSparkConnectServiceClient(conn)
        return &sparkSessionImpl{
                sessionId: uuid.NewString(),
                client:    client,
+               metadata:  meta,
        }, nil
 }
 
 type sparkSessionImpl struct {
        sessionId string
        client    proto.SparkConnectServiceClient
+       metadata  metadata.MD
 }
 
 func (s *sparkSessionImpl) Sql(query string) (DataFrame, error) {
@@ -109,8 +120,13 @@ func (s *sparkSessionImpl) executePlan(plan *proto.Plan) 
(proto.SparkConnectServ
        request := proto.ExecutePlanRequest{
                SessionId: s.sessionId,
                Plan:      plan,
+               UserContext: &proto.UserContext{
+                       UserId: "na",
+               },
        }
-       executePlanClient, err := s.client.ExecutePlan(context.TODO(), &request)
+       // Append the other items to the request.
+       ctx := metadata.NewOutgoingContext(context.Background(), s.metadata)
+       executePlanClient, err := s.client.ExecutePlan(ctx, &request)
        if err != nil {
                return nil, fmt.Errorf("failed to call ExecutePlan in session 
%s: %w", s.sessionId, err)
        }
@@ -125,8 +141,14 @@ func (s *sparkSessionImpl) analyzePlan(plan *proto.Plan) 
(*proto.AnalyzePlanResp
                                Plan: plan,
                        },
                },
+               UserContext: &proto.UserContext{
+                       UserId: "na",
+               },
        }
-       response, err := s.client.AnalyzePlan(context.TODO(), &request)
+       // Append the other items to the request.
+       ctx := metadata.NewOutgoingContext(context.Background(), s.metadata)
+
+       response, err := s.client.AnalyzePlan(ctx, &request)
        if err != nil {
                return nil, fmt.Errorf("failed to call AnalyzePlan in session 
%s: %w", s.sessionId, err)
        }
diff --git a/cmd/spark-connect-example-spark-session/main.go 
b/cmd/spark-connect-example-spark-session/main.go
index 0f4a6cc..d7e70bd 100644
--- a/cmd/spark-connect-example-spark-session/main.go
+++ b/cmd/spark-connect-example-spark-session/main.go
@@ -18,8 +18,9 @@ package main
 
 import (
        "flag"
-       "github.com/apache/spark-connect-go/v_3_4/client/sql"
        "log"
+
+       "github.com/apache/spark-connect-go/v_3_4/client/sql"
 )
 
 var (
@@ -28,6 +29,7 @@ var (
 )
 
 func main() {
+       flag.Parse()
        spark, err := sql.SparkSession.Builder.Remote(*remote).Build()
        if err != nil {
                log.Fatalf("Failed: %s", err.Error())
diff --git a/go.mod b/go.mod
index f04a331..c95c5ec 100644
--- a/go.mod
+++ b/go.mod
@@ -25,6 +25,12 @@ require (
        google.golang.org/protobuf v1.30.0
 )
 
+require (
+       cloud.google.com/go/compute v1.15.1 // indirect
+       cloud.google.com/go/compute/metadata v0.2.3 // indirect
+       google.golang.org/appengine v1.6.7 // indirect
+)
+
 require (
        github.com/andybalholm/brotli v1.0.4 // indirect
        github.com/apache/thrift v0.16.0 // indirect
@@ -42,11 +48,12 @@ require (
        github.com/pierrec/lz4/v4 v4.1.15 // indirect
        github.com/pmezard/go-difflib v1.0.0 // indirect
        github.com/zeebo/xxh3 v1.0.2 // indirect
-       golang.org/x/mod v0.8.0 // indirect
-       golang.org/x/net v0.8.0 // indirect
-       golang.org/x/sys v0.6.0 // indirect
-       golang.org/x/text v0.8.0 // indirect
-       golang.org/x/tools v0.6.0 // indirect
+       golang.org/x/mod v0.10.0 // indirect
+       golang.org/x/net v0.10.0 // indirect
+       golang.org/x/oauth2 v0.8.0
+       golang.org/x/sys v0.8.0 // indirect
+       golang.org/x/text v0.9.0 // indirect
+       golang.org/x/tools v0.9.3 // indirect
        golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
        google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // 
indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index f8c434e..8e4f954 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,7 @@
+cloud.google.com/go/compute v1.15.1 
h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE=
+cloud.google.com/go/compute v1.15.1/go.mod 
h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
+cloud.google.com/go/compute/metadata v0.2.3 
h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod 
h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
 github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c 
h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU=
 github.com/andybalholm/brotli v1.0.4 
h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
 github.com/andybalholm/brotli v1.0.4/go.mod 
h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
@@ -12,6 +16,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/goccy/go-json v0.9.11 
h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
 github.com/goccy/go-json v0.9.11/go.mod 
h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/golang/mock v1.5.0/go.mod 
h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/protobuf v1.3.1/go.mod 
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.5.0/go.mod 
h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 
h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod 
h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@@ -53,30 +58,37 @@ golang.org/x/crypto 
v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod 
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 
h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
+golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod 
h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod 
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
+golang.org/x/oauth2 v0.8.0/go.mod 
h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
-golang.org/x/tools v0.6.0/go.mod 
h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
+golang.org/x/tools v0.9.3/go.mod 
h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f 
h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
 golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod 
h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E=
+google.golang.org/appengine v1.6.7 
h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod 
h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f 
h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
 google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod 
h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
 google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@spark.apache.org
For additional commands, e-mail: commits-h...@spark.apache.org

Reply via email to