mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 01:15:46 +08:00
157 lines
6.1 KiB
Go
157 lines
6.1 KiB
Go
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` = ?;
|
|
}
|