Skip to content

Commit

Permalink
improvement(benchmarks): performance improvements to the prettyCQL
Browse files Browse the repository at this point in the history
Not prettyCQL uses the `strings.Builder` to generate the `CQL` statament
that was previously executed on ScyllaDB.
Instaface has been changed accomadate the `builder` being passed to
down.

Signed-off-by: Dusan Malusev <[email protected]>
  • Loading branch information
CodeLieutenant committed Oct 29, 2024
1 parent b6e6387 commit c66cc89
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 116 deletions.
21 changes: 14 additions & 7 deletions pkg/typedef/bag.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,27 @@ func (ct *BagType) CQLHolder() string {
return "?"
}

func (ct *BagType) CQLPretty(value any) string {
func (ct *BagType) CQLPretty(builder *strings.Builder, value any) {
if reflect.TypeOf(value).Kind() != reflect.Slice {
panic(fmt.Sprintf("set cql pretty, unknown type %v", ct))
}
s := reflect.ValueOf(value)
format := "[%s]"

if ct.ComplexType == TYPE_SET {
format = "{%s}"
builder.WriteRune('{')
defer builder.WriteRune('}')
} else {
builder.WriteRune('[')
defer builder.WriteRune(']')
}
out := make([]string, s.Len())

s := reflect.ValueOf(value)

for i := 0; i < s.Len(); i++ {
out[i] = ct.ValueType.CQLPretty(s.Index(i).Interface())
ct.ValueType.CQLPretty(builder, s.Index(i).Interface())
if i < s.Len()-1 {
builder.WriteRune(',')
}
}
return fmt.Sprintf(format, strings.Join(out, ","))
}

func (ct *BagType) GenValue(r *rand.Rand, p *PartitionRangeConfig) []any {
Expand Down
4 changes: 3 additions & 1 deletion pkg/typedef/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package typedef

import (
"strings"

"github.com/gocql/gocql"
"golang.org/x/exp/rand"
)
Expand All @@ -23,7 +25,7 @@ type Type interface {
Name() string
CQLDef() string
CQLHolder() string
CQLPretty(any) string
CQLPretty(*strings.Builder, any)
GenValue(*rand.Rand, *PartitionRangeConfig) []any
GenJSONValue(*rand.Rand, *PartitionRangeConfig) any
LenValue() int
Expand Down
96 changes: 84 additions & 12 deletions pkg/typedef/simple_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"math"
"math/big"
"net"
"strconv"
"strings"
"time"

"github.com/gocql/gocql"
Expand Down Expand Up @@ -66,50 +68,120 @@ func (st SimpleType) LenValue() int {
return 1
}

func (st SimpleType) CQLPretty(value any) string {
func (st SimpleType) CQLPretty(builder *strings.Builder, value any) {
switch st {
case TYPE_ASCII, TYPE_TEXT, TYPE_VARCHAR, TYPE_INET, TYPE_DATE:
return fmt.Sprintf("'%s'", value)
case TYPE_INET:
builder.WriteRune('\'')
defer builder.WriteRune('\'')
switch v := value.(type) {
case net.IP:
builder.WriteString(v.String())
case net.IPMask:
builder.WriteString(v.String())
case string:
builder.WriteString(v)
}
case TYPE_ASCII, TYPE_TEXT, TYPE_VARCHAR, TYPE_DATE:
builder.WriteRune('\'')
builder.WriteString(value.(string))
builder.WriteRune('\'')
case TYPE_BLOB:
if v, ok := value.(string); ok {
if len(v) > 100 {
v = v[:100]
}
return "textasblob('" + v + "')"
builder.WriteString("textasblob('")
builder.WriteString(v)
builder.WriteString("')")
return
}

panic(fmt.Sprintf("unexpected blob value [%T]%+v", value, value))
case TYPE_BIGINT, TYPE_INT, TYPE_SMALLINT, TYPE_TINYINT:
return fmt.Sprintf("%d", value)
var i int64
switch v := value.(type) {
case int8:
i = int64(v)
case int16:
i = int64(v)
case int32:
i = int64(v)
case int:
i = int64(v)
case int64:
i = v
case *big.Int:
builder.WriteString(v.Text(10))
return
default:
panic(fmt.Sprintf("unexpected int value [%T]%+v", value, value))
}
builder.WriteString(strconv.FormatInt(i, 10))
case TYPE_DECIMAL, TYPE_DOUBLE, TYPE_FLOAT:
return fmt.Sprintf("%.2f", value)
var f float64
switch v := value.(type) {
case float32:
f = float64(v)
case float64:
f = v
case *inf.Dec:
builder.WriteString(v.String())
return
default:
panic(fmt.Sprintf("unexpected float value [%T]%+v", value, value))
}
builder.WriteString(strconv.FormatFloat(f, 'f', 2, 64))
case TYPE_BOOLEAN:
if v, ok := value.(bool); ok {
return fmt.Sprintf("%t", v)
builder.WriteString(strconv.FormatBool(v))
return
}

panic(fmt.Sprintf("unexpected boolean value [%T]%+v", value, value))
case TYPE_TIME:
if v, ok := value.(int64); ok {
builder.WriteRune('\'')
// CQL supports only 3 digits microseconds:
// '10:10:55.83275+0000': marshaling error: Milliseconds length exceeds expected (5)"
return fmt.Sprintf("'%s'", time.Time{}.Add(time.Duration(v)).Format("15:04:05.999"))
builder.WriteString(time.Time{}.Add(time.Duration(v)).Format("15:04:05.999"))
builder.WriteRune('\'')
return
}

panic(fmt.Sprintf("unexpected time value [%T]%+v", value, value))
case TYPE_TIMESTAMP:
if v, ok := value.(int64); ok {
// CQL supports only 3 digits milliseconds:
// '1976-03-25T10:10:55.83275+0000': marshaling error: Milliseconds length exceeds expected (5)"
return time.UnixMilli(v).UTC().Format("'2006-01-02T15:04:05.999-0700'")
builder.WriteString(time.UnixMilli(v).UTC().Format("'2006-01-02T15:04:05.999-0700'"))
return
}

panic(fmt.Sprintf("unexpected timestamp value [%T]%+v", value, value))
case TYPE_DURATION, TYPE_TIMEUUID, TYPE_UUID:
return fmt.Sprintf("%s", value)
switch v := value.(type) {
case string:
builder.WriteString(v)
return
case time.Duration:
builder.WriteString(v.String())
return
case gocql.UUID:
builder.WriteString(v.String())
return
}

panic(fmt.Sprintf("unexpected (duration|timeuuid|uuid) value [%T]%+v", value, value))
case TYPE_VARINT:
if s, ok := value.(*big.Int); ok {
return fmt.Sprintf("%d", s.Int64())
builder.WriteString(s.Text(10))
return
}

panic(fmt.Sprintf("unexpected varint value [%T]%+v", value, value))
default:
panic(fmt.Sprintf("cql pretty: not supported type %s", st))
panic(fmt.Sprintf("cql pretty: not supported type %s [%T]%+v", st, value, value))

}
}

Expand Down
16 changes: 10 additions & 6 deletions pkg/typedef/tuple.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package typedef

import (
"fmt"
"strings"

"github.com/gocql/gocql"
Expand Down Expand Up @@ -55,16 +54,21 @@ func (t *TupleType) CQLHolder() string {
return "(" + strings.TrimRight(strings.Repeat("?,", len(t.ValueTypes)), ",") + ")"
}

func (t *TupleType) CQLPretty(value any) string {
func (t *TupleType) CQLPretty(builder *strings.Builder, value any) {
values, ok := value.([]any)
if !ok {
return "()"
builder.WriteString("()")
}
out := make([]string, len(values))

builder.WriteRune('(')
defer builder.WriteRune(')')

for i, tp := range t.ValueTypes {
out[i] = tp.CQLPretty(values[i])
tp.CQLPretty(builder, values[i])
if i < len(values)-1 {
builder.WriteRune(',')
}
}
return fmt.Sprintf("(%s)", strings.Join(out, ","))
}

func (t *TupleType) Indexable() bool {
Expand Down
84 changes: 13 additions & 71 deletions pkg/typedef/typedef.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ package typedef

import (
"fmt"
"iter"
"strings"
"sync"

"github.com/scylladb/gocqlx/v2/qb"

Expand Down Expand Up @@ -199,78 +197,22 @@ const (
CacheArrayLen
)

func splitString(str, delimiter string) func(func(int, string) bool) {
lastPos := 0
delLen := len(delimiter)
return func(yield func(int, string) bool) {
for i := 0; ; i++ {
pos := strings.Index(str[lastPos:], delimiter)
func prettyCQL(query string, values Values, types []Type) string {
var (
index int
builder strings.Builder
)

if pos == -1 || str[lastPos:] == "" {
yield(-1, str[lastPos:])
builder.Grow(len(query))

break
}
for pos, i := strings.Index(query[index:], "?"), 0; pos != -1; pos, i = strings.Index(query[index:], "?"), i+1 {
actualPos := index + pos
builder.WriteString(query[index:actualPos])
types[i].CQLPretty(&builder, values[i])

if str[lastPos:lastPos+pos] == "" || !yield(i, str[lastPos:lastPos+pos]) {
break
}

lastPos += pos + delLen
}
}
}

var builderPool = &sync.Pool{
New: func() any {
builder := &strings.Builder{}

builder.Grow(1024)

return builder
},
}

func prettyCQL(query string, values Values, types Types) string {
if len(values) == 0 {
return query
}

out := builderPool.Get().(*strings.Builder)
defer func() {
out.Reset()
builderPool.Put(out)
}()

next, stop := iter.Pull2(splitString(query, "?"))

for {
i, str, more := next()

_, _ = out.WriteString(str)

if !more || i == -1 {
stop()
break
}

switch ty := types[i].(type) {
case *TupleType:
for count, t := range ty.ValueTypes {
_, _ = out.WriteString(t.CQLPretty(values[count]))

_, str, more = next()
if !more {
stop()
break
}

_, _ = out.WriteString(str)
}
default:
_, _ = out.WriteString(ty.CQLPretty(values[i]))
}
index = actualPos + 1
}

return out.String()
builder.WriteString(query[index:])
return builder.String()
}
Loading

0 comments on commit c66cc89

Please sign in to comment.