Skip to content

Commit

Permalink
feat: add versioned function (arana-db#601)
Browse files Browse the repository at this point in the history
  • Loading branch information
jjeffcaii authored Jan 30, 2023
1 parent fddf508 commit 62ef532
Show file tree
Hide file tree
Showing 26 changed files with 313 additions and 38 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ go 1.18
require (
github.com/appleboy/gin-jwt/v2 v2.9.1
github.com/arana-db/parser v0.2.9
github.com/blang/semver v3.5.1+incompatible
github.com/bwmarrin/snowflake v0.3.0
github.com/cespare/xxhash/v2 v2.1.2
github.com/creasty/defaults v1.6.0
github.com/docker/go-units v0.4.0
github.com/dop251/goja v0.0.0-20220422102209-3faab1d8f20e
github.com/dubbogo/gost v1.12.3
github.com/gin-gonic/gin v1.8.1
Expand Down Expand Up @@ -61,7 +63,6 @@ require (
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.11+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
Expand Down
4 changes: 4 additions & 0 deletions pkg/constants/mysql/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ const (
ERInvalidOnUpdate = 1294
ERUnknownTimeZone = 1298
ERInvalidCharacterString = 1300
ERSPDoseNotExist = 1305
ERIllegalReference = 1247
ERDerivedMustHaveAlias = 1248
ERTableNameNotAllowedHere = 1250
Expand Down Expand Up @@ -587,6 +588,9 @@ const (

// SSNoDatabaseSelected is ER_NO_DB
SSNoDatabaseSelected = "3D000"

// SSSPNotExist is ER_SP_DOES_NOT_EXIST
SSSPNotExist = "42000"
)

// Status flags. They are returned by the server in a few cases.
Expand Down
33 changes: 33 additions & 0 deletions pkg/proto/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import (
"fmt"
)

import (
"github.com/blang/semver"
)

import (
"github.com/arana-db/arana/pkg/util/log"
)
Expand Down Expand Up @@ -85,3 +89,32 @@ type Func interface {
// NumInput returns the minimum number of inputs.
NumInput() int
}

// VersionedFunc represents a MySQL function with versions.
// See this doc: https://dev.mysql.com/doc/refman/8.0/en/built-in-function-reference.html
type VersionedFunc interface {
Func

// Versions returns the version range of current function.
Versions() semver.Range
}

// ValidateFunction checks the function compatibility from server version.
func ValidateFunction(ctx context.Context, f Func, strict bool) bool {
vf, ok := f.(VersionedFunc)
if !ok { // omit if not versioned function
return true
}

serverVersion, ok := ctx.Value(ContextKeyServerVersion{}).(string)
if !ok {
return !strict
}

ver, err := semver.Parse(serverVersion)
if err != nil {
return !strict
}

return vf.Versions()(ver)
}
1 change: 0 additions & 1 deletion pkg/runtime/function/cast_decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ func (a castDecimalFunc) Apply(ctx context.Context, inputs ...proto.Valuer) (pro
}

return proto.NewValueString(d.StringFixed(int32(s))), nil

}

func (a castDecimalFunc) NumInput() int {
Expand Down
88 changes: 88 additions & 0 deletions pkg/runtime/function/format_bytes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 function

import (
"context"
"fmt"
)

import (
"github.com/blang/semver"

"github.com/docker/go-units"

"github.com/shopspring/decimal"
)

import (
"github.com/arana-db/arana/pkg/proto"
)

const FuncFormatBytes = "FORMAT_BYTES"

var _ proto.VersionedFunc = (*formatBytesFunc)(nil)

var (
_decimal1024 = decimal.NewFromInt(1024)
_formatBytesFuncVersionRange = semver.MustParseRange(">=8.0.16")
_binaryAbbrs = []string{"bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
_zeroBytesStr = proto.NewValueString(fmt.Sprintf("0 %s", _binaryAbbrs[0]))
)

func init() {
// https://dev.mysql.com/doc/refman/8.0/en/performance-schema-functions.html#function_format-bytes
proto.RegisterFunc(FuncFormatBytes, formatBytesFunc{})
}

type formatBytesFunc struct{}

func (ff formatBytesFunc) Apply(ctx context.Context, inputs ...proto.Valuer) (proto.Value, error) {
val, err := inputs[0].Value(ctx)
if err != nil {
return nil, err
}

if val == nil {
return nil, nil
}

d, err := val.Decimal()
if err != nil || d.IsZero() {
return _zeroBytesStr, nil
}

return proto.NewValueString(ff.formatUnit(d)), nil
}

func (ff formatBytesFunc) NumInput() int {
return 1
}

func (ff formatBytesFunc) Versions() semver.Range {
return _formatBytesFuncVersionRange
}

func (ff formatBytesFunc) formatUnit(size decimal.Decimal) string {
size = size.Truncate(0)

if size.Abs().LessThan(_decimal1024) {
return units.CustomSize("%v %s", size.InexactFloat64(), 1024.0, _binaryAbbrs)
}
return units.CustomSize("%.2f %s", size.InexactFloat64(), 1024.0, _binaryAbbrs)
}
62 changes: 62 additions & 0 deletions pkg/runtime/function/format_bytes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 function

import (
"context"
"fmt"
"testing"
)

import (
"github.com/stretchr/testify/assert"
)

import (
"github.com/arana-db/arana/pkg/proto"
)

func TestFormatBytes(t *testing.T) {
fn := proto.MustGetFunc(FuncFormatBytes)
assert.Equal(t, 1, fn.NumInput())

out, err := fn.Apply(context.TODO(), proto.ToValuer(nil))
assert.NoError(t, err)
assert.Nil(t, out)

type tt struct {
input interface{}
output string
}

for _, next := range []tt{
{int64(512), "512 bytes"},
{int64(1025), "1.00 KiB"},
{3.14, "3 bytes"},
{-5.55, "-5 bytes"},
{uint64(18446644073709551615), "16.00 EiB"},
} {
t.Run(fmt.Sprint(next.input), func(t *testing.T) {
val, err := proto.NewValue(next.input)
assert.NoError(t, err)
actual, err := fn.Apply(context.TODO(), proto.ToValuer(val))
assert.NoError(t, err)
assert.Equal(t, next.output, actual.String())
})
}
}
1 change: 0 additions & 1 deletion pkg/runtime/function/if_null.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ func (i ifNullFunc) Apply(ctx context.Context, inputs ...proto.Valuer) (proto.Va
return val1, nil
}
return val2, nil

}

func (i ifNullFunc) NumInput() int {
Expand Down
1 change: 1 addition & 0 deletions pkg/runtime/function/if_null_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

import (
"github.com/shopspring/decimal"

"github.com/stretchr/testify/assert"
)

Expand Down
1 change: 1 addition & 0 deletions pkg/runtime/function/if_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

import (
"github.com/shopspring/decimal"

"github.com/stretchr/testify/assert"
)

Expand Down
7 changes: 6 additions & 1 deletion pkg/runtime/misc/extvalue/extvalue.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@

package extvalue

import (
"context"
)

import (
"github.com/arana-db/arana/pkg/proto"
"github.com/arana-db/arana/pkg/runtime/ast"
)

func Compute(node ast.Node, args ...proto.Value) (proto.Value, error) {
func Compute(ctx context.Context, node ast.Node, args ...proto.Value) (proto.Value, error) {
var vv valueVisitor
vv.Context = ctx
vv.args = args
ret, err := node.Accept(&vv)
if err != nil {
Expand Down
28 changes: 27 additions & 1 deletion pkg/runtime/misc/extvalue/extvalue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package extvalue_test

import (
"context"
"testing"
)

Expand All @@ -28,6 +29,7 @@ import (
)

import (
"github.com/arana-db/arana/pkg/proto"
"github.com/arana-db/arana/pkg/runtime/ast"
_ "github.com/arana-db/arana/pkg/runtime/function"
"github.com/arana-db/arana/pkg/runtime/misc/extvalue"
Expand All @@ -51,7 +53,7 @@ func TestCompute(t *testing.T) {
t.Run(next.input, func(t *testing.T) {
expr, err := getExpr(next.input)
assert.NoError(t, err)
v, err := extvalue.Compute(expr)
v, err := extvalue.Compute(context.TODO(), expr)
assert.NoError(t, err)

var actual string
Expand All @@ -63,6 +65,30 @@ func TestCompute(t *testing.T) {
assert.Equal(t, next.expect, actual)
})
}

ctx := context.TODO()
ctx = context.WithValue(ctx, proto.ContextKeySchema{}, "fake_schema")

t.Run("FORMAT_BYTES(512)", func(t *testing.T) {
expr, err := getExpr("FORMAT_BYTES(512)")
assert.NoError(t, err)

// NOTICE: FORMAT_BYTES is only for mysql 8.0.16+

// check <8.0.16
_, err = extvalue.Compute(context.WithValue(ctx, proto.ContextKeyServerVersion{}, "5.7.0"), expr)
assert.Error(t, err)

// check >=8.0.16
v, err := extvalue.Compute(context.WithValue(ctx, proto.ContextKeyServerVersion{}, "8.0.31"), expr)
assert.NoError(t, err)
assert.Equal(t, "512 bytes", v.String())

// check semver
v, err = extvalue.Compute(context.WithValue(ctx, proto.ContextKeyServerVersion{}, "8.0.31-dmr"), expr)
assert.NoError(t, err)
assert.Equal(t, "512 bytes", v.String())
})
}

func getExpr(s string) (ast.ExpressionNode, error) {
Expand Down
Loading

0 comments on commit 62ef532

Please sign in to comment.