Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MySQL parsing implemented, plus minor fixes in some related files #623

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
16 changes: 12 additions & 4 deletions cmd/trickster/listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ func applyListenerConfigs(conf, oldConf *config.Config,
if err != nil {
tl.Error(log, "unable to start tls listener due to certificate error", tl.Pairs{"detail": err})
} else {
wg.Add(1)
if wg != nil {
wg.Add(1)
}
tracerFlusherSet = true
go lg.StartListener("tlsListener",
conf.Frontend.TLSListenAddress, conf.Frontend.TLSListenPort,
Expand Down Expand Up @@ -120,7 +122,9 @@ func applyListenerConfigs(conf, oldConf *config.Config,
(oldConf.Frontend.ListenAddress != conf.Frontend.ListenAddress ||
oldConf.Frontend.ListenPort != conf.Frontend.ListenPort)) {
lg.DrainAndClose("httpListener", drainTimeout)
wg.Add(1)
if wg != nil {
wg.Add(1)
}
var t2 tracing.Tracers
if !tracerFlusherSet {
t2 = tracers
Expand All @@ -140,7 +144,9 @@ func applyListenerConfigs(conf, oldConf *config.Config,
if conf.Main.PprofServer == "both" || conf.Main.PprofServer == "metrics" {
routing.RegisterPprofRoutes("metrics", metricsRouter, log)
}
wg.Add(1)
if wg != nil {
wg.Add(1)
}
go lg.StartListener("metricsListener",
conf.Metrics.ListenAddress, conf.Metrics.ListenPort,
conf.Frontend.ConnectionsLimit, nil, metricsRouter, wg, nil, exitFunc, 0, log)
Expand All @@ -156,7 +162,9 @@ func applyListenerConfigs(conf, oldConf *config.Config,
if conf.ReloadConfig != nil && conf.ReloadConfig.ListenPort > 0 &&
(!hasOldRC || (conf.ReloadConfig.ListenAddress != oldConf.ReloadConfig.ListenAddress ||
conf.ReloadConfig.ListenPort != oldConf.ReloadConfig.ListenPort)) {
wg.Add(1)
if wg != nil {
wg.Add(1)
}
lg.DrainAndClose("reloadListener", time.Millisecond*500)
rr.HandleFunc(conf.Main.ConfigHandlerPath, handlers.ConfigHandleFunc(conf))
rr.Handle(conf.ReloadConfig.HandlerPath, reloadHandler)
Expand Down
4 changes: 3 additions & 1 deletion cmd/trickster/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ func main() {
runtime.ApplicationName = applicationName
runtime.ApplicationVersion = applicationVersion
runConfig(nil, wg, nil, nil, os.Args[1:], exitFunc)
wg.Wait()
if wg != nil {
wg.Wait()
}
}

func exitFatal() {
Expand Down
1 change: 1 addition & 0 deletions cmd/trickster/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

func TestMain(t *testing.T) {
exitFunc = nil
wg = nil
main()
// Successful test criteria is that the call to main returns without timing out on wg.Wait()
}
Expand Down
35 changes: 35 additions & 0 deletions pkg/backends/mysql/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2018 The Trickster Authors
*
* Licensed 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 mysql

import "errors"

// ErrLimitUnsupported indicates the input a LIMIT keyword, which is currently unsupported
// in the caching layer
var ErrLimitUnsupported = errors.New("limit queries are not supported")

// ErrUnsupportedOutputFormat indicates the FORMAT value for the query is not supported
var ErrUnsupportedOutputFormat = errors.New("unsupported output format requested")

// ErrInvalidWithClause indicates the WITH clause of the query is not properly formatted
var ErrInvalidWithClause = errors.New("invalid WITH expression list")

// ErrUnsupportedToStartOfFunc indicates the ToStartOf func used in the query is not supported by Trickster
var ErrUnsupportedToStartOfFunc = errors.New("unsupported ToStartOf* func")

// ErrNotAtPreWhere indicates AtPreWhere was called but the current token is not of type tokenPreWhere
var ErrNotAtPreWhere = errors.New("not at PREWHERE")
31 changes: 31 additions & 0 deletions pkg/backends/mysql/handler_proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2018 The Trickster Authors
*
* Licensed 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 mysql

import (
"net/http"

"github.com/trickstercache/trickster/v2/pkg/proxy/engines"
"github.com/trickstercache/trickster/v2/pkg/proxy/urls"
)

// ProxyHandler sends a request through the basic reverse proxy to the origin,
// and services non-cacheable calls
func (c *Client) ProxyHandler(w http.ResponseWriter, r *http.Request) {
r.URL = urls.BuildUpstreamURL(r, c.BaseUpstreamURL())
engines.DoProxy(w, r, true)
}
66 changes: 66 additions & 0 deletions pkg/backends/mysql/handler_proxy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2018 The Trickster Authors
*
* Licensed 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 mysql

import (
"io"
"testing"

"github.com/trickstercache/trickster/v2/pkg/proxy/request"
tu "github.com/trickstercache/trickster/v2/pkg/testutil"
)

func TestProxyHandler(t *testing.T) {

backendClient, err := NewClient("test", nil, nil, nil, nil, nil)
if err != nil {
t.Error(err)
}
ts, w, r, _, err := tu.NewTestInstance("",
backendClient.DefaultPathConfigs, 200, "test", nil, "mysql", "/health", "debug")
if err != nil {
t.Error(err)
} else {
defer ts.Close()
}
rsc := request.GetResources(r)
backendClient, err = NewClient("test", rsc.BackendOptions, nil, nil, nil, nil)
if err != nil {
t.Error(err)
}
client := backendClient.(*Client)
rsc.BackendClient = client
rsc.BackendOptions.HTTPClient = backendClient.HTTPClient()

client.ProxyHandler(w, r)
resp := w.Result()

// it should return 200 OK
if resp.StatusCode != 200 {
t.Errorf("expected 200 got %d.", resp.StatusCode)
}

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
t.Error(err)
}

if string(bodyBytes) != "test" {
t.Errorf("expected 'test' got %s.", bodyBytes)
}

}
61 changes: 61 additions & 0 deletions pkg/backends/mysql/handler_query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2018 The Trickster Authors
*
* Licensed 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 mysql

import (
"io"
"net/http"
"strings"

"github.com/trickstercache/trickster/v2/pkg/proxy/engines"
"github.com/trickstercache/trickster/v2/pkg/proxy/handlers"
"github.com/trickstercache/trickster/v2/pkg/proxy/methods"
"github.com/trickstercache/trickster/v2/pkg/proxy/request"
"github.com/trickstercache/trickster/v2/pkg/proxy/urls"
)

// QueryHandler for MySQL client
func (c *Client) QueryHandler(w http.ResponseWriter, r *http.Request) {

q := r.URL.Query()
sqlQuery := q.Get("query")
if methods.HasBody(r.Method) {
b, err := io.ReadAll(r.Body)
if err != nil {
handlers.HandleBadRequestResponse(w, r)
return
}
var body []byte
if sqlQuery != "" {
body = make([]byte, 0, len(sqlQuery)+len(b))
body = append([]byte(sqlQuery), b...)
q.Del("query")
r.URL.RawQuery = q.Encode()
} else {
body = b
}
sqlQuery = string(body)
r = request.SetBody(r, body)
}
sqlQuery = strings.ToLower(sqlQuery)
if (!strings.HasPrefix(sqlQuery, "select ")) && (!(strings.Index(sqlQuery, " select ") > 0)) {
c.ProxyHandler(w, r)
return
}
r.URL = urls.BuildUpstreamURL(r, c.BaseUpstreamURL())
engines.DeltaProxyCacheRequest(w, r, c.Modeler())
}
113 changes: 113 additions & 0 deletions pkg/backends/mysql/handler_query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2018 The Trickster Authors
*
* Licensed 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 mysql

import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/trickstercache/trickster/v2/pkg/proxy/request"
tu "github.com/trickstercache/trickster/v2/pkg/testutil"
)

func testQuery(q string) string {
return url.Values(map[string][]string{"query": {q}}).Encode()
}

func testRawQuery() string {
return url.Values(map[string][]string{"query": {tq00}}).
Encode()
}

func testNonSelectQuery() string {
return url.Values(map[string][]string{"enable_http_compression": {"1"}}).Encode()
// not a real query, just something to trigger a non-select proxy-only request
}

func TestQueryHandler(t *testing.T) {

backendClient, err := NewClient("test", nil, nil, nil, nil, nil)
if err != nil {
t.Error(err)
}
ts, w, r, _, err := tu.NewTestInstance("", backendClient.DefaultPathConfigs,
200, "{}", nil, "mysql", "/?"+testRawQuery(), "debug")
ctx := r.Context()
if err != nil {
t.Error(err)
} else {
defer ts.Close()
}
rsc := request.GetResources(r)
backendClient, err = NewClient("test", rsc.BackendOptions, nil, nil, nil, nil)
if err != nil {
t.Error(err)
}
client := backendClient.(*Client)
rsc.BackendClient = client
rsc.BackendOptions.HTTPClient = backendClient.HTTPClient()

_, ok := client.Configuration().Paths["/"]
if !ok {
t.Errorf("could not find path config named %s", "/")
}

client.QueryHandler(w, r)

resp := w.Result()

// it should return 200 OK
if resp.StatusCode != 200 {
t.Errorf("expected 200 got %d.", resp.StatusCode)
}

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
t.Error(err)
}

if string(bodyBytes) != "{}" {
t.Errorf("expected '{}' got %s.", bodyBytes)
}

r, _ = http.NewRequest(http.MethodGet, ts.URL+"/?"+testNonSelectQuery(), nil)
w = httptest.NewRecorder()

r = r.WithContext(ctx)

client.QueryHandler(w, r)

resp = w.Result()

// it should return 200 OK
if resp.StatusCode != 200 {
t.Errorf("expected 200 got %d.", resp.StatusCode)
}

bodyBytes, err = io.ReadAll(resp.Body)
if err != nil {
t.Error(err)
}

if string(bodyBytes) != "{}" {
t.Errorf("expected '{}' got %s.", bodyBytes)
}

}
Loading