Add feature of omiting the "SELECT" part of the query

Now the 3 functions that allow you to write plain SQL queries
also work if you omit the `SELECT ...` part of the query.

If you do this the code will check and notice that the first
token of the query is a "FROM" token and then automatically
build the SELECT part of the query based on the tags of the struct.

Everything is cached, so the impact on performance should be negligible.

The affected functions are:

- Query()
- QueryOne()
- QueryChunks()
pull/2/head
Vinícius Garcia 2021-05-16 17:05:21 -03:00
parent d275555df5
commit edecbf8191
4 changed files with 687 additions and 562 deletions

4
go.mod
View File

@ -3,9 +3,9 @@ module github.com/vingarcia/ksql
go 1.14
require (
github.com/denisenkom/go-mssqldb v0.10.0 // indirect
github.com/denisenkom/go-mssqldb v0.10.0
github.com/ditointernet/go-assert v0.0.0-20200120164340-9e13125a7018
github.com/go-sql-driver/mysql v1.4.0 // indirect
github.com/go-sql-driver/mysql v1.4.0
github.com/golang/mock v1.5.0
github.com/jmoiron/sqlx v1.2.0
github.com/lib/pq v1.1.1

69
ksql.go
View File

@ -6,11 +6,20 @@ import (
"fmt"
"reflect"
"strings"
"unicode"
"github.com/pkg/errors"
"github.com/vingarcia/ksql/structs"
)
var selectQueryCache = map[string]map[reflect.Type]string{}
func init() {
for dname := range supportedDialects {
selectQueryCache[dname] = map[reflect.Type]string{}
}
}
// DB represents the ksql client responsible for
// interfacing with the "database/sql" package implementing
// the KissSQL interface `SQLProvider`.
@ -124,6 +133,14 @@ func (c DB) Query(
slice = slice.Slice(0, 0)
}
if strings.ToUpper(getFirstToken(query)) == "FROM" {
selectPrefix, err := buildSelectQuery(c.dialect, structType, selectQueryCache[c.dialect.DriverName()])
if err != nil {
return err
}
query = selectPrefix + query
}
rows, err := c.db.QueryContext(ctx, query, params...)
if err != nil {
return fmt.Errorf("error running query: %s", err.Error())
@ -189,6 +206,14 @@ func (c DB) QueryOne(
return fmt.Errorf("ksql: expected to receive a pointer to struct, but got: %T", record)
}
if strings.ToUpper(getFirstToken(query)) == "FROM" {
selectPrefix, err := buildSelectQuery(c.dialect, t, selectQueryCache[c.dialect.DriverName()])
if err != nil {
return err
}
query = selectPrefix + query
}
rows, err := c.db.QueryContext(ctx, query, params...)
if err != nil {
return err
@ -243,6 +268,14 @@ func (c DB) QueryChunks(
return err
}
if strings.ToUpper(getFirstToken(parser.Query)) == "FROM" {
selectPrefix, err := buildSelectQuery(c.dialect, structType, selectQueryCache[c.dialect.DriverName()])
if err != nil {
return err
}
parser.Query = selectPrefix + parser.Query
}
rows, err := c.db.QueryContext(ctx, parser.Query, parser.Params...)
if err != nil {
return err
@ -869,3 +902,39 @@ func buildCompositeKeyDeleteQuery(
strings.Join(values, ","),
), params
}
// We implemented this function instead of using
// a regex or strings.Fields because we wanted
// to preserve the performance of the package.
func getFirstToken(s string) string {
s = strings.TrimLeftFunc(s, unicode.IsSpace)
var token strings.Builder
for _, c := range s {
if unicode.IsSpace(c) {
break
}
token.WriteRune(c)
}
return token.String()
}
func buildSelectQuery(
dialect dialect,
structType reflect.Type,
selectQueryCache map[reflect.Type]string,
) (string, error) {
if selectQuery, found := selectQueryCache[structType]; found {
return selectQuery, nil
}
info := structs.GetTagInfo(structType)
var fields []string
for _, field := range info.Fields() {
fields = append(fields, dialect.Escape(field.Name))
}
query := "SELECT " + strings.Join(fields, ", ") + " "
selectQueryCache[structType] = query
return query, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -42,6 +42,10 @@ func (s structInfo) Add(field fieldInfo) {
s.byName[field.Name] = &field
}
func (s structInfo) Fields() map[int]*fieldInfo {
return s.byIndex
}
// This cache is kept as a pkg variable
// because the total number of types on a program
// should be finite. So keeping a single cache here