Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions enginetest/queries/script_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,68 @@ type ScriptTestAssertion struct {
// Unlike other engine tests, ScriptTests must be self-contained. No other tables are created outside the definition of
// the tests.
var ScriptTests = []ScriptTest{
{
// https://github.com/dolthub/dolt/issues/9857
Name: "UUID_SHORT() function returns 64-bit unsigned integers with proper construction",
Dialect: "mysql",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT UUID_SHORT() > 0",
Expected: []sql.Row{
{true}, // Should return positive values
},
},
{
Query: "SELECT UUID_SHORT() != UUID_SHORT()",
Expected: []sql.Row{
{true}, // Should return different values on each call
},
},
{
Query: "SELECT UUID_SHORT() + 0 > 0",
Expected: []sql.Row{
{true}, // Should work in arithmetic expressions
},
},
{
Query: "SELECT CAST(UUID_SHORT() AS CHAR) != ''",
Expected: []sql.Row{
{true}, // Should cast to non-empty string
},
},
{
Query: "SELECT UUID_SHORT() BETWEEN 1 AND 18446744073709551615",
Expected: []sql.Row{
{true}, // Should be within uint64 range
},
},
{
Query: "SELECT (UUID_SHORT() & 0xFF00000000000000) >> 56 BETWEEN 0 AND 255",
Expected: []sql.Row{
{true}, // Server ID should be 0-255
},
},
{
Query: "SET @@global.server_id = 253",
Expected: []sql.Row{
{types.NewOkResult(0)},
},
},
{
Query: "SELECT (UUID_SHORT() & 0xFF00000000000000) >> 56 BETWEEN 0 AND 255",
Expected: []sql.Row{
{true}, // server time won't let us pin this down further
},
},
{
Query: "SET @@global.server_id = 1",
Expected: []sql.Row{
{types.NewOkResult(0)},
},
},
},
},
{
// https://github.com/dolthub/go-mysql-server/issues/3216
Name: "UNION ALL with BLOB columns",
Expand Down
1 change: 1 addition & 0 deletions sql/expression/function/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ var BuiltIns = []sql.Function{
sql.NewFunction0("user", NewUser),
sql.FunctionN{Name: "utc_timestamp", Fn: NewUTCTimestamp},
sql.Function0{Name: "uuid", Fn: NewUUIDFunc},
sql.Function0{Name: "uuid_short", Fn: NewUUIDShortFunc},
sql.FunctionN{Name: "uuid_to_bin", Fn: NewUUIDToBin},
sql.FunctionN{Name: "week", Fn: NewWeek},
sql.Function1{Name: "values", Fn: NewValues},
Expand Down
106 changes: 106 additions & 0 deletions sql/expression/function/uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,21 @@ package function

import (
"fmt"
"sync"

"github.com/dolthub/vitess/go/sqltypes"
"github.com/dolthub/vitess/go/vt/proto/query"
"github.com/google/uuid"

"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/types"
"github.com/dolthub/go-mysql-server/sql/variables"
)

// Global state for UUID_SHORT function
var (
uuidShortMu sync.Mutex
uuidShortCounter uint64
)

// UUID()
Expand Down Expand Up @@ -528,3 +536,101 @@ func (bu BinToUUID) Children() []sql.Expression {
func (bu BinToUUID) IsNullable() bool {
return false
}

// UUID_SHORT()
//
// Returns a "short" universal identifier as a 64-bit unsigned integer. Values returned by UUID_SHORT() differ from the
// string-format 128-bit identifiers returned by the UUID() function and have different uniqueness properties. The value
// of UUID_SHORT() is guaranteed to be unique if the following conditions hold:
//
// The server_id value of the current server is between 0 and 255 and is unique among your set of source and replica servers
//
// The UUID_SHORT() return value is constructed this way:
// (server_id & 255) << 56
// + (server_startup_time_in_seconds << 24)
// + incremented_variable++;
//
// Note: UUID_SHORT() does not work with statement-based replication.
// https://dev.mysql.com/doc/refman/8.4/en/miscellaneous-functions.html#function_uuid-short

type UUIDShortFunc struct{}

var _ sql.FunctionExpression = &UUIDShortFunc{}
var _ sql.CollationCoercible = &UUIDShortFunc{}

func NewUUIDShortFunc() sql.Expression {
return &UUIDShortFunc{}
}

// Description returns a human-readable description of the UUID_SHORT function.
func (u *UUIDShortFunc) Description() string {
return "returns a short universal identifier as a 64-bit unsigned integer."
}

// String returns a string representation of the UUID_SHORT function call.
func (u *UUIDShortFunc) String() string {
return "UUID_SHORT()"
}

// Type returns the data type of the UUID_SHORT function result (Uint64).
func (u *UUIDShortFunc) Type() sql.Type {
return types.Uint64
}

// CollationCoercibility implements the interface sql.CollationCoercible.
func (u *UUIDShortFunc) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
return sql.Collation_binary, 5
}

// Eval generates a 64-bit UUID_SHORT value using server_id, startup time, and counter.
func (u *UUIDShortFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
uuidShortMu.Lock()
defer uuidShortMu.Unlock()

uuidShortCounter++

serverID := uint64(1) // Default fallback
if _, val, ok := sql.SystemVariables.GetGlobal("server_id"); ok {
if serverIDVal, ok := val.(uint32); ok {
serverID = uint64(serverIDVal)
}
}

// Construct the UUID_SHORT value according to MySQL specification:
result := ((serverID & 255) << 56) + (uint64(variables.ServerStartUpTime.Unix()) << 24) + uuidShortCounter
return result, nil
}

// WithChildren returns a new UUID_SHORT function with the given children (must be empty).
func (u *UUIDShortFunc) WithChildren(children ...sql.Expression) (sql.Expression, error) {
if len(children) != 0 {
return nil, sql.ErrInvalidChildrenNumber.New(u, len(children), 0)
}

return &UUIDShortFunc{}, nil
}

// FunctionName returns the name of the UUID_SHORT function.
func (u *UUIDShortFunc) FunctionName() string {
return "UUID_SHORT"
}

// Resolved returns true since UUID_SHORT has no dependencies to resolve.
func (u *UUIDShortFunc) Resolved() bool {
return true
}

// Children returns the children expressions of this expression.
func (u *UUIDShortFunc) Children() []sql.Expression {
return nil
}

// IsNullable returns false since UUID_SHORT always returns a value.
func (u *UUIDShortFunc) IsNullable() bool {
return false
}

// IsNonDeterministic returns true since UUID_SHORT generates different values on each call.
func (u *UUIDShortFunc) IsNonDeterministic() bool {
return true
}
126 changes: 126 additions & 0 deletions sql/expression/function/uuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,129 @@ func TestBinToUUIDFailing(t *testing.T) {
})
}
}

func TestUUIDShort(t *testing.T) {
ctx := sql.NewEmptyContext()
uuidShortE := NewUUIDShortFunc()

// Test that UUID_SHORT returns sequential values
result1, err := uuidShortE.Eval(ctx, sql.Row{nil})
require.NoError(t, err)
require.IsType(t, uint64(0), result1)

result2, err := uuidShortE.Eval(ctx, sql.Row{nil})
require.NoError(t, err)
require.IsType(t, uint64(0), result2)

result3, err := uuidShortE.Eval(ctx, sql.Row{nil})
require.NoError(t, err)
require.IsType(t, uint64(0), result3)

// Values should be sequential (incrementing by 1)
require.Equal(t, result1.(uint64)+1, result2.(uint64))
require.Equal(t, result2.(uint64)+1, result3.(uint64))

// Test that values are 64-bit unsigned integers
require.Greater(t, result1.(uint64), uint64(0))
require.Greater(t, result2.(uint64), uint64(0))
require.Greater(t, result3.(uint64), uint64(0))
}

func TestUUIDShortMultipleInstances(t *testing.T) {
ctx := sql.NewEmptyContext()

uuidShort1 := NewUUIDShortFunc()
uuidShort2 := NewUUIDShortFunc()

result1, err := uuidShort1.Eval(ctx, sql.Row{nil})
require.NoError(t, err)

result2, err := uuidShort2.Eval(ctx, sql.Row{nil})
require.NoError(t, err)

// Both should return sequential values from the global counter
require.IsType(t, uint64(0), result1)
require.IsType(t, uint64(0), result2)
require.Greater(t, result1.(uint64), uint64(0))
require.Greater(t, result2.(uint64), uint64(0))

// Values should be sequential (global counter)
require.Equal(t, result1.(uint64)+1, result2.(uint64))
}

func TestUUIDShortWithChildren(t *testing.T) {
uuidShortE := NewUUIDShortFunc()

newExpr, err := uuidShortE.WithChildren()
require.NoError(t, err)
require.NotNil(t, newExpr)

_, err = uuidShortE.WithChildren(expression.NewLiteral(1, types.Int64))
require.Error(t, err)
require.Contains(t, err.Error(), "invalid children number")
}

func TestUUIDShortProperties(t *testing.T) {
uuidShortE := NewUUIDShortFunc().(*UUIDShortFunc)

require.Equal(t, "UUID_SHORT", uuidShortE.FunctionName())
require.Equal(t, "returns a short universal identifier as a 64-bit unsigned integer.", uuidShortE.Description())
require.Equal(t, "UUID_SHORT()", uuidShortE.String())
require.Equal(t, types.Uint64, uuidShortE.Type())
require.True(t, uuidShortE.Resolved())
require.False(t, uuidShortE.IsNullable())
require.True(t, uuidShortE.IsNonDeterministic())
require.Nil(t, uuidShortE.Children())
}

func TestUUIDShortServerIdIntegration(t *testing.T) {
ctx := sql.NewEmptyContext()
uuidShortE := NewUUIDShortFunc()

result1, err := uuidShortE.Eval(ctx, sql.Row{nil})
require.NoError(t, err)
require.IsType(t, uint64(0), result1)

serverIDFromResult := (result1.(uint64) & 0xFF00000000000000) >> 56
require.Equal(t, uint64(1), serverIDFromResult)

err = sql.SystemVariables.SetGlobal(ctx, "server_id", uint32(123))
require.NoError(t, err)

result2, err := uuidShortE.Eval(ctx, sql.Row{nil})
require.NoError(t, err)
require.IsType(t, uint64(0), result2)

serverIDFromResult2 := (result2.(uint64) & 0xFF00000000000000) >> 56
require.Equal(t, uint64(123), serverIDFromResult2)

err = sql.SystemVariables.SetGlobal(ctx, "server_id", uint32(255))
require.NoError(t, err)

result3, err := uuidShortE.Eval(ctx, sql.Row{nil})
require.NoError(t, err)
require.IsType(t, uint64(0), result3)

serverIDFromResult3 := (result3.(uint64) & 0xFF00000000000000) >> 56
require.Equal(t, uint64(255), serverIDFromResult3)

err = sql.SystemVariables.SetGlobal(ctx, "server_id", uint32(256))
require.NoError(t, err)

result4, err := uuidShortE.Eval(ctx, sql.Row{nil})
require.NoError(t, err)
require.IsType(t, uint64(0), result4)

serverIDFromResult4 := (result4.(uint64) & 0xFF00000000000000) >> 56
require.Equal(t, uint64(0), serverIDFromResult4)

err = sql.SystemVariables.SetGlobal(ctx, "server_id", uint32(243))
require.NoError(t, err)

result5, err := uuidShortE.Eval(ctx, sql.Row{nil})
require.NoError(t, err)
require.IsType(t, uint64(0), result5)

serverIDFromResult5 := (result5.(uint64) & 0xFF00000000000000) >> 56
require.Equal(t, uint64(243), serverIDFromResult5)
}
6 changes: 3 additions & 3 deletions sql/variables/system_variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ import (
// There's also this page, which shows that a TON of variables are still missing ):
// https://dev.mysql.com/doc/refman/8.0/en/server-system-variable-reference.html

// serverStartUpTime is needed by uptime status variable
var serverStartUpTime = time.Now()
// ServerStartUpTime is needed by uptime status variable
var ServerStartUpTime = time.Now()

// globalSystemVariables is the underlying type of SystemVariables.
type globalSystemVariables struct {
Expand Down Expand Up @@ -2934,7 +2934,7 @@ var systemVars = map[string]sql.SystemVariable{
Type: types.NewSystemBoolType("updatable_views_with_limit"),
Default: int8(1),
ValueFunction: func() (interface{}, error) {
return int(time.Now().Sub(serverStartUpTime).Seconds()), nil
return int(time.Now().Sub(ServerStartUpTime).Seconds()), nil
},
},
"use_secondary_engine": &sql.MysqlSystemVariable{
Expand Down
Loading