1//  Copyright (c) 2017 Couchbase, Inc.
2//  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
3//  except in compliance with the License. You may obtain a copy of the License at
4//    http://www.apache.org/licenses/LICENSE-2.0
5//  Unless required by applicable law or agreed to in writing, software distributed under the
6//  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
7//  either express or implied. See the License for the specific language governing permissions
8//  and limitations under the License.
9
10package system
11
12import (
13	"fmt"
14
15	"github.com/couchbase/query/datastore"
16	"github.com/couchbase/query/distributed"
17	"github.com/couchbase/query/errors"
18	"github.com/couchbase/query/value"
19)
20
21// Keyspace stuff
22
23type keyspaceBase struct {
24	namespace *namespace
25}
26
27func (this *keyspaceBase) Namespace() datastore.Namespace {
28	return this.namespace
29}
30
31func setKeyspaceBase(base *keyspaceBase, namespace *namespace) {
32	base.namespace = namespace
33}
34
35// Index stuff
36
37type indexBase struct {
38	indexer datastore.Indexer
39}
40
41func (this *indexBase) Indexer() datastore.Indexer {
42	return this.indexer
43}
44
45func setIndexBase(base *indexBase, indexer datastore.Indexer) {
46	base.indexer = indexer
47}
48
49type compiledSpan struct {
50	low      string
51	high     string
52	evalLow  func(val, key string) bool
53	evalHigh func(val, key string) bool
54
55	// golang does not allow to compare functional pointers to functions...
56	equality bool
57}
58
59func compileSpan(span *datastore.Span) (*compiledSpan, errors.Error) {
60	var err errors.Error
61	var isLowValued, isHighValued bool
62
63	// currently system indexes are either primary or on a single field
64	if len(span.Seek) > 1 || len(span.Range.Low) > 1 || len(span.Range.High) > 1 {
65		return nil, errors.NewSystemDatastoreError(nil, "Invalid number of fields in span")
66	}
67
68	spanEvaluator := &compiledSpan{}
69	if span.Seek != nil {
70		val := span.Seek[0].Actual()
71		switch t := val.(type) {
72		case string:
73		default:
74			return nil, errors.NewSystemDatastoreError(nil, fmt.Sprintf("Invalid seek value %v of type %T.", t, val))
75		}
76		spanEvaluator.low = val.(string)
77		spanEvaluator.high = spanEvaluator.low
78		spanEvaluator.evalLow = equals
79		spanEvaluator.evalHigh = noop
80		spanEvaluator.equality = true
81	} else {
82		spanEvaluator.low, spanEvaluator.evalLow, isLowValued, err = compileRange(span.Range.Low, span.Range.Inclusion, datastore.LOW)
83		if err != nil {
84			return nil, err
85		}
86		spanEvaluator.high, spanEvaluator.evalHigh, isHighValued, err = compileRange(span.Range.High, span.Range.Inclusion, datastore.HIGH)
87		if err != nil {
88			return nil, err
89		}
90		if spanEvaluator.high == spanEvaluator.low && isLowValued && isHighValued {
91			spanEvaluator.evalLow = equals
92			spanEvaluator.evalHigh = noop
93			spanEvaluator.equality = true
94		}
95	}
96	return spanEvaluator, nil
97}
98
99func (this *compiledSpan) evaluate(key string) bool {
100	return this.evalHigh(this.high, key) && this.evalLow(this.low, key)
101}
102
103func (this *compiledSpan) isEquals() bool {
104	return this.equality
105}
106
107func (this *compiledSpan) key() string {
108	return this.low
109}
110
111func compileRange(in value.Values, incl, side datastore.Inclusion) (string, func(string, string) bool, bool, errors.Error) {
112	if len(in) == 0 {
113		return "", noop, false, nil
114	}
115	val := in[0].Actual()
116	t := in[0].Type()
117	switch t {
118	case value.STRING:
119	case value.NULL:
120
121		// > null is a noop, < null should never occur and it's an error
122		if side == datastore.LOW {
123			return "", noop, false, nil
124		}
125		fallthrough
126	default:
127		return "", nil, false, errors.NewSystemDatastoreError(nil, fmt.Sprintf("Invalid seek value %v of type %T.", val, t.String()))
128	}
129	retVal := val.(string)
130	op := (incl & side) > 0
131	if side == datastore.HIGH {
132		if op {
133			return retVal, lessOrEqual, true, nil
134		} else {
135			return retVal, less, true, nil
136		}
137	} else {
138		if op {
139			return retVal, greaterOrEqual, true, nil
140		} else {
141			return retVal, greater, true, nil
142		}
143	}
144}
145
146func equals(val, key string) bool {
147	return key == val
148}
149
150func less(top, key string) bool {
151	return key < top
152}
153
154func lessOrEqual(top, key string) bool {
155	return key <= top
156}
157
158func greater(bottom, key string) bool {
159	return key > bottom
160}
161
162func greaterOrEqual(bottom, key string) bool {
163	return key >= bottom
164}
165
166func noop(val, key string) bool {
167	return true
168}
169
170// Credentials
171
172// Return the credentials presented in the context.
173// The second parameter is the ns-server-auth-token value, from the original request,
174// if one is present, else the empty string.
175func credsFromContext(context datastore.QueryContext) (distributed.Creds, string) {
176	credentials := context.Credentials()
177	creds := make(distributed.Creds, len(credentials))
178	for k, v := range credentials {
179		creds[k] = v
180	}
181	authToken := ""
182	req := context.OriginalHttpRequest()
183	if req != nil && req.Header.Get("ns-server-ui") == "yes" {
184		authToken = req.Header.Get("ns-server-auth-token")
185	}
186	return creds, authToken
187}
188