-
Notifications
You must be signed in to change notification settings - Fork 138
/
Copy pathfunctions.go
135 lines (115 loc) · 4.27 KB
/
functions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
Copyright 2022-Present Couchbase, Inc.
Use of this software is governed by the Business Source License included in
the file licenses/BSL-Couchbase.txt. As of the Change Date specified in that
file, in accordance with the Business Source License, use of this software will
be governed by the Apache License, Version 2.0, included in the file
licenses/APL2.txt.
*/
package db
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
sgbucket "github.com/couchbase/sg-bucket"
"github.com/couchbase/sync_gateway/auth"
"github.com/couchbase/sync_gateway/base"
)
/* This is the interface to the JS functions API implemented in the functions package. */
// Timeout for N1QL and JavaScript queries. (Applies to REST and BLIP requests.)
const defaultUserFunctionTimeout = 60 * time.Second
//////// USER FUNCTIONS
// A map from names to user functions.
type UserFunctions struct {
Definitions map[string]UserFunction
MaxRequestSize *int
}
// A JavaScript function or N1QL query that can be invoked by a client.
// (Created by functions.CompileUserFunction or functions.CompileUserFunctions)
type UserFunction interface {
// The function's name
Name() string
// Returns the name assigned to the N1QL query, if there is one.
N1QLQueryName() (string, bool)
// Creates an invocation of the function, which can then be run or iterated.
Invoke(db *Database, args map[string]interface{}, mutationAllowed bool, ctx context.Context) (UserFunctionInvocation, error)
}
// The context for running a user function; created by UserFunction.Invoke.
type UserFunctionInvocation interface {
// Calls a user function, returning a query result iterator.
// If this function does not support iteration, returns nil; then call `Run` instead.
Iterate() (sgbucket.QueryResultIterator, error)
// Calls a user function, returning the entire result.
// (If this is a N1QL query it will return all the result rows in an array, which is less efficient than iterating them, so try calling `Iterate` first.)
Run(context.Context) (interface{}, error)
}
//////// DATABASE API FOR USER FUNCTIONS:
// Looks up a UserFunction by name and returns an Invocation.
func (db *Database) GetUserFunction(ctx context.Context, name string, args map[string]interface{}, mutationAllowed bool) (UserFunctionInvocation, error) {
if db.Options.UserFunctions != nil {
if fn, found := db.Options.UserFunctions.Definitions[name]; found {
return fn.Invoke(db, args, mutationAllowed, ctx)
}
}
return nil, missingError(db.User(), "function", name)
}
// Calls a user function by name, returning all the results at once.
func (db *Database) CallUserFunction(ctx context.Context, name string, args map[string]interface{}, mutationAllowed bool) (interface{}, error) {
invocation, err := db.GetUserFunction(ctx, name, args, mutationAllowed)
if err != nil {
return nil, err
}
return invocation.Run(ctx)
}
//////// UTILITIES
// Returns an HTTP 413 error if `maxSize` is non-nil and less than `actualSize`.
func CheckRequestSize[T int | int64](actualSize T, maxSize *int) error {
if maxSize != nil && int64(actualSize) > int64(*maxSize) {
return base.HTTPErrorf(http.StatusRequestEntityTooLarge, "Arguments too large")
} else {
return nil
}
}
// Utility that returns a rough estimate of the original size of the JSON a value was parsed from.
func EstimateSizeOfJSON(args any) int {
switch arg := args.(type) {
case nil:
return 4
case bool:
return 4
case int64:
return 4
case float64:
return 8
case json.Number:
return len(arg)
case string:
return len(arg) + 2
case []any:
size := 2
for _, item := range arg {
size += EstimateSizeOfJSON(item) + 1
}
return size
case map[string]any:
size := 2
for key, item := range arg {
size += len(key) + EstimateSizeOfJSON(item) + 4
}
return size
default:
return 1
}
}
// Returns the appropriate HTTP error for when a function/query doesn't exist.
// For security reasons, we don't let a non-admin user know what function names exist;
// so instead of a 404 we return the same 401/403 error as if they didn't have access to it.
func missingError(user auth.User, what string, name string) error {
if user == nil {
return base.HTTPErrorf(http.StatusNotFound, "no such %s %q", what, name)
} else {
return user.UnauthError(fmt.Sprintf("you are not allowed to call %s %q", what, name))
}
}