1// Copyright 2019 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bigquery 16 17import ( 18 "context" 19 "errors" 20 "fmt" 21 "time" 22 23 "cloud.google.com/go/internal/optional" 24 "cloud.google.com/go/internal/trace" 25 bq "google.golang.org/api/bigquery/v2" 26) 27 28// Routine represents a reference to a BigQuery routine. There are multiple 29// types of routines including stored procedures and scalar user-defined functions (UDFs). 30// For more information, see the BigQuery documentation at https://cloud.google.com/bigquery/docs/ 31type Routine struct { 32 ProjectID string 33 DatasetID string 34 RoutineID string 35 36 c *Client 37} 38 39func (r *Routine) toBQ() *bq.RoutineReference { 40 return &bq.RoutineReference{ 41 ProjectId: r.ProjectID, 42 DatasetId: r.DatasetID, 43 RoutineId: r.RoutineID, 44 } 45} 46 47// FullyQualifiedName returns an identifer for the routine in project.dataset.routine format. 48func (r *Routine) FullyQualifiedName() string { 49 return fmt.Sprintf("%s.%s.%s", r.ProjectID, r.DatasetID, r.RoutineID) 50} 51 52// Create creates a Routine in the BigQuery service. 53// Pass in a RoutineMetadata to define the routine. 54func (r *Routine) Create(ctx context.Context, rm *RoutineMetadata) (err error) { 55 ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Routine.Create") 56 defer func() { trace.EndSpan(ctx, err) }() 57 58 routine, err := rm.toBQ() 59 if err != nil { 60 return err 61 } 62 routine.RoutineReference = &bq.RoutineReference{ 63 ProjectId: r.ProjectID, 64 DatasetId: r.DatasetID, 65 RoutineId: r.RoutineID, 66 } 67 req := r.c.bqs.Routines.Insert(r.ProjectID, r.DatasetID, routine).Context(ctx) 68 setClientHeader(req.Header()) 69 _, err = req.Do() 70 return err 71} 72 73// Metadata fetches the metadata for a given Routine. 74func (r *Routine) Metadata(ctx context.Context) (rm *RoutineMetadata, err error) { 75 ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Routine.Metadata") 76 defer func() { trace.EndSpan(ctx, err) }() 77 78 req := r.c.bqs.Routines.Get(r.ProjectID, r.DatasetID, r.RoutineID).Context(ctx) 79 setClientHeader(req.Header()) 80 var routine *bq.Routine 81 err = runWithRetry(ctx, func() (err error) { 82 routine, err = req.Do() 83 return err 84 }) 85 if err != nil { 86 return nil, err 87 } 88 return bqToRoutineMetadata(routine) 89} 90 91// Update modifies properties of a Routine using the API. 92func (r *Routine) Update(ctx context.Context, upd *RoutineMetadataToUpdate, etag string) (rm *RoutineMetadata, err error) { 93 ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Routine.Update") 94 defer func() { trace.EndSpan(ctx, err) }() 95 96 bqr, err := upd.toBQ() 97 if err != nil { 98 return nil, err 99 } 100 //TODO: remove when routines update supports partial requests. 101 bqr.RoutineReference = &bq.RoutineReference{ 102 ProjectId: r.ProjectID, 103 DatasetId: r.DatasetID, 104 RoutineId: r.RoutineID, 105 } 106 107 call := r.c.bqs.Routines.Update(r.ProjectID, r.DatasetID, r.RoutineID, bqr).Context(ctx) 108 setClientHeader(call.Header()) 109 if etag != "" { 110 call.Header().Set("If-Match", etag) 111 } 112 var res *bq.Routine 113 if err := runWithRetry(ctx, func() (err error) { 114 res, err = call.Do() 115 return err 116 }); err != nil { 117 return nil, err 118 } 119 return bqToRoutineMetadata(res) 120} 121 122// Delete removes a Routine from a dataset. 123func (r *Routine) Delete(ctx context.Context) (err error) { 124 ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Model.Delete") 125 defer func() { trace.EndSpan(ctx, err) }() 126 127 req := r.c.bqs.Routines.Delete(r.ProjectID, r.DatasetID, r.RoutineID).Context(ctx) 128 setClientHeader(req.Header()) 129 return req.Do() 130} 131 132// RoutineMetadata represents details of a given BigQuery Routine. 133type RoutineMetadata struct { 134 ETag string 135 // Type indicates the type of routine, such as SCALAR_FUNCTION or PROCEDURE. 136 Type string 137 CreationTime time.Time 138 Description string 139 LastModifiedTime time.Time 140 // Language of the routine, such as SQL or JAVASCRIPT. 141 Language string 142 // The list of arguments for the the routine. 143 Arguments []*RoutineArgument 144 ReturnType *StandardSQLDataType 145 // For javascript routines, this indicates the paths for imported libraries. 146 ImportedLibraries []string 147 // Body contains the routine's body. 148 // For functions, Body is the expression in the AS clause. 149 // 150 // For SQL functions, it is the substring inside the parentheses of a CREATE 151 // FUNCTION statement. 152 // 153 // For JAVASCRIPT function, it is the evaluated string in the AS clause of 154 // a CREATE FUNCTION statement. 155 Body string 156} 157 158func (rm *RoutineMetadata) toBQ() (*bq.Routine, error) { 159 r := &bq.Routine{} 160 if rm == nil { 161 return r, nil 162 } 163 r.Description = rm.Description 164 r.Language = rm.Language 165 r.RoutineType = rm.Type 166 r.DefinitionBody = rm.Body 167 rt, err := rm.ReturnType.toBQ() 168 if err != nil { 169 return nil, err 170 } 171 r.ReturnType = rt 172 173 var args []*bq.Argument 174 for _, v := range rm.Arguments { 175 bqa, err := v.toBQ() 176 if err != nil { 177 return nil, err 178 } 179 args = append(args, bqa) 180 } 181 r.Arguments = args 182 r.ImportedLibraries = rm.ImportedLibraries 183 if !rm.CreationTime.IsZero() { 184 return nil, errors.New("cannot set CreationTime on create") 185 } 186 if !rm.LastModifiedTime.IsZero() { 187 return nil, errors.New("cannot set LastModifiedTime on create") 188 } 189 if rm.ETag != "" { 190 return nil, errors.New("cannot set ETag on create") 191 } 192 return r, nil 193} 194 195// RoutineArgument represents an argument supplied to a routine such as a UDF or 196// stored procedured. 197type RoutineArgument struct { 198 // The name of this argument. Can be absent for function return argument. 199 Name string 200 // Kind indicates the kind of argument represented. 201 // Possible values: 202 // ARGUMENT_KIND_UNSPECIFIED 203 // FIXED_TYPE - The argument is a variable with fully specified 204 // type, which can be a struct or an array, but not a table. 205 // ANY_TYPE - The argument is any type, including struct or array, 206 // but not a table. 207 Kind string 208 // Mode is optional, and indicates whether an argument is input or output. 209 // Mode can only be set for procedures. 210 // 211 // Possible values: 212 // MODE_UNSPECIFIED 213 // IN - The argument is input-only. 214 // OUT - The argument is output-only. 215 // INOUT - The argument is both an input and an output. 216 Mode string 217 // DataType provides typing information. Unnecessary for ANY_TYPE Kind 218 // arguments. 219 DataType *StandardSQLDataType 220} 221 222func (ra *RoutineArgument) toBQ() (*bq.Argument, error) { 223 if ra == nil { 224 return nil, nil 225 } 226 a := &bq.Argument{ 227 Name: ra.Name, 228 ArgumentKind: ra.Kind, 229 Mode: ra.Mode, 230 } 231 if ra.DataType != nil { 232 dt, err := ra.DataType.toBQ() 233 if err != nil { 234 return nil, err 235 } 236 a.DataType = dt 237 } 238 return a, nil 239} 240 241func bqToRoutineArgument(bqa *bq.Argument) (*RoutineArgument, error) { 242 arg := &RoutineArgument{ 243 Name: bqa.Name, 244 Kind: bqa.ArgumentKind, 245 Mode: bqa.Mode, 246 } 247 dt, err := bqToStandardSQLDataType(bqa.DataType) 248 if err != nil { 249 return nil, err 250 } 251 arg.DataType = dt 252 return arg, nil 253} 254 255func bqToArgs(in []*bq.Argument) ([]*RoutineArgument, error) { 256 var out []*RoutineArgument 257 for _, a := range in { 258 arg, err := bqToRoutineArgument(a) 259 if err != nil { 260 return nil, err 261 } 262 out = append(out, arg) 263 } 264 return out, nil 265} 266 267func routineArgumentsToBQ(in []*RoutineArgument) ([]*bq.Argument, error) { 268 var out []*bq.Argument 269 for _, inarg := range in { 270 arg, err := inarg.toBQ() 271 if err != nil { 272 return nil, err 273 } 274 out = append(out, arg) 275 } 276 return out, nil 277} 278 279// RoutineMetadataToUpdate governs updating a routine. 280type RoutineMetadataToUpdate struct { 281 Arguments []*RoutineArgument 282 Description optional.String 283 Type optional.String 284 Language optional.String 285 Body optional.String 286 ImportedLibraries []string 287 ReturnType *StandardSQLDataType 288} 289 290func (rm *RoutineMetadataToUpdate) toBQ() (*bq.Routine, error) { 291 r := &bq.Routine{} 292 forceSend := func(field string) { 293 r.ForceSendFields = append(r.ForceSendFields, field) 294 } 295 nullField := func(field string) { 296 r.NullFields = append(r.NullFields, field) 297 } 298 if rm.Description != nil { 299 r.Description = optional.ToString(rm.Description) 300 forceSend("Description") 301 } 302 if rm.Arguments != nil { 303 if len(rm.Arguments) == 0 { 304 nullField("Arguments") 305 } else { 306 args, err := routineArgumentsToBQ(rm.Arguments) 307 if err != nil { 308 return nil, err 309 } 310 r.Arguments = args 311 forceSend("Arguments") 312 } 313 } 314 if rm.Type != nil { 315 r.RoutineType = optional.ToString(rm.Type) 316 forceSend("RoutineType") 317 } 318 if rm.Language != nil { 319 r.Language = optional.ToString(rm.Language) 320 forceSend("Language") 321 } 322 if rm.Body != nil { 323 r.DefinitionBody = optional.ToString(rm.Body) 324 forceSend("DefinitionBody") 325 } 326 if rm.ImportedLibraries != nil { 327 if len(rm.ImportedLibraries) == 0 { 328 nullField("ImportedLibraries") 329 } else { 330 r.ImportedLibraries = rm.ImportedLibraries 331 forceSend("ImportedLibraries") 332 } 333 } 334 if rm.ReturnType != nil { 335 dt, err := rm.ReturnType.toBQ() 336 if err != nil { 337 return nil, err 338 } 339 r.ReturnType = dt 340 forceSend("ReturnType") 341 } 342 return r, nil 343} 344 345func bqToRoutineMetadata(r *bq.Routine) (*RoutineMetadata, error) { 346 meta := &RoutineMetadata{ 347 ETag: r.Etag, 348 Type: r.RoutineType, 349 CreationTime: unixMillisToTime(r.CreationTime), 350 Description: r.Description, 351 LastModifiedTime: unixMillisToTime(r.LastModifiedTime), 352 Language: r.Language, 353 ImportedLibraries: r.ImportedLibraries, 354 Body: r.DefinitionBody, 355 } 356 args, err := bqToArgs(r.Arguments) 357 if err != nil { 358 return nil, err 359 } 360 meta.Arguments = args 361 ret, err := bqToStandardSQLDataType(r.ReturnType) 362 if err != nil { 363 return nil, err 364 } 365 meta.ReturnType = ret 366 return meta, nil 367} 368