The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/storage/unified/sql/sqltemplate/example_test.go

156 lines
6.1 KiB

package sqltemplate
import (
"fmt"
"regexp"
"strings"
"text/template"
)
// This file contains runnable examples. They serve the purpose of providing
// idiomatic usage of the package as well as showing how it actually works,
// since the examples are actually run together with regular Go tests. Note that
// the "Output" comment section at the end of each function starting with
// "Example" is used by the standard Go test tool to check that the standard
// output of the function matches the commented text until the end of the
// function. If you change the function, you may need to adapt that comment
// section as it's possible that the output changes, causing it to fail tests.
// To learn more about Go's runnable tests, which are a core builtin feature of
// Go's standard testing library, see:
// https://pkg.go.dev/testing#hdr-Examples
//
// If you're unfamiliar with Go text templating language, please, consider
// reading that library's documentation first.
// In this example we will use both Args and Dialect to dynamically and securely
// build SQL queries, while also keeping track of the arguments that need to be
// passed to the database methods to replace the placeholder "?" with the
// correct values.
// We will start by assuming we receive a request to retrieve a user's
// information and that we need to provide a certain response.
type GetUserRequest struct {
ID int
}
type GetUserResponse struct {
ID int
Type string
Name string
}
// Our template will take care for us of taking the request to build the query,
// and then sort the arguments for execution as well as preparing the values
// that need to be read for the response. We wil create a struct to pass the
// request and an empty response, as well as a *SQLTemplate that will provide
// the methods to achieve our purpose::
type GetUserQuery struct {
SQLTemplate
Request *GetUserRequest
Response *GetUserResponse
}
// And finally we will define our template, that is free to use all the power of
// the Go templating language, plus the methods we added with *SQLTemplate:
var getUserTmpl = template.Must(template.New("example").Parse(`
SELECT
{{ .Ident "id" | .Into .Response.ID }},
{{ .Ident "type" | .Into .Response.Type }},
{{ .Ident "name" | .Into .Response.Name }}
FROM {{ .Ident "users" }}
WHERE
{{ .Ident "id" }} = {{ .Arg .Request.ID }};
`))
// There are three interesting methods used in the above template:
// 1. Ident: safely escape a SQL identifier. Even though here the only
// identifier that may be problematic is "type" (because it is a reserved
// word in many dialects), it is a good practice to escape all identifiers
// just to make sure we're accounting for all variability in dialects, and
// also for consistency.
// 2. Into: this causes the selected field to be saved to the corresponding
// field of GetUserQuery.
// 3. Arg: this allows us to state that at this point will be a "?" that has to
// be populated with the value of the given field of GetUserQuery.
func Example() {
// Let's pretend this example function is the handler of the GetUser method
// of our service to see how it all works together.
queryData := &GetUserQuery{
// The dialect (in this case we chose MySQL) should be set in your
// service at startup when you connect to your database
SQLTemplate: New(MySQL),
// This is a synthetic request for our test
Request: &GetUserRequest{
ID: 1,
},
// Create an empty response to be populated
Response: new(GetUserResponse),
}
// The next step is to execute the query template for our queryData, and
// generate the arguments for the db.QueryRow and row.Scan methods later
query, err := Execute(getUserTmpl, queryData)
if err != nil {
panic(err) // terminate the runnable example on error
}
// Assuming that we have a *sql.DB object named "db", we could now make our
// query with:
// row := db.QueryRowContext(ctx, query, queryData.GetArgs()...)
// // and check row.Err() here
// As we're not actually running a database in this example, let's verify
// that we find our arguments populated as expected instead:
if len(queryData.GetArgs()) != 1 {
panic(fmt.Sprintf("unexpected number of args: %#v", queryData.GetArgs()...))
}
id, ok := queryData.GetArgs()[0].(int)
if !ok || id != queryData.Request.ID {
panic(fmt.Sprintf("unexpected args: %#v", queryData.GetArgs()...))
}
// In your code you would now have "row" populated with the row data,
// assuming that the operation succeeded, so you would now scan the row data
// abd populate the values of our response:
// err := row.Scan(queryData.GetScanDest()...)
// // and check err here
// Again, as we're not actually running a database in this example, we will
// instead run the code to assert that queryData.ScanDest was populated with
// the expected data, which should be pointers to each of the fields of
// Response so that the Scan method can write to them:
if len(queryData.GetScanDest()) != 3 {
panic(fmt.Sprintf("unexpected number of scan dest: %#v", queryData.GetScanDest()...))
}
idPtr, ok := queryData.GetScanDest()[0].(*int)
if !ok || idPtr != &queryData.Response.ID {
panic(fmt.Sprintf("unexpected response 'id' pointer: %#v", queryData.GetScanDest()...))
}
typePtr, ok := queryData.GetScanDest()[1].(*string)
if !ok || typePtr != &queryData.Response.Type {
panic(fmt.Sprintf("unexpected response 'type' pointer: %#v", queryData.GetScanDest()...))
}
namePtr, ok := queryData.GetScanDest()[2].(*string)
if !ok || namePtr != &queryData.Response.Name {
panic(fmt.Sprintf("unexpected response 'name' pointer: %#v", queryData.GetScanDest()...))
}
// Remember the variable "query"? Well, we didn't check it. We will now make
// use of Go's runnable examples and print its contents to standard output
// so Go's tooling verify this example's output each time we run tests.
// By the way, to make the result more stable, we will remove some
// unnecessary white space from the query.
whiteSpaceRE := regexp.MustCompile(`\s+`)
query = strings.TrimSpace(whiteSpaceRE.ReplaceAllString(query, " "))
fmt.Println(query)
// Output:
// SELECT `id`, `type`, `name` FROM `users` WHERE `id` = ?;
}