Skip to content

Commit b0670cf

Browse files
authored
Merge pull request #3227 from dolthub/elian/9857
dolthub/dolt#9857: Add UUID_SHORT() support
2 parents 6ba4161 + 6693848 commit b0670cf

File tree

5 files changed

+298
-3
lines changed

5 files changed

+298
-3
lines changed

enginetest/queries/script_queries.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,68 @@ type ScriptTestAssertion struct {
121121
// Unlike other engine tests, ScriptTests must be self-contained. No other tables are created outside the definition of
122122
// the tests.
123123
var ScriptTests = []ScriptTest{
124+
{
125+
// https://github.com/dolthub/dolt/issues/9857
126+
Name: "UUID_SHORT() function returns 64-bit unsigned integers with proper construction",
127+
Dialect: "mysql",
128+
SetUpScript: []string{},
129+
Assertions: []ScriptTestAssertion{
130+
{
131+
Query: "SELECT UUID_SHORT() > 0",
132+
Expected: []sql.Row{
133+
{true}, // Should return positive values
134+
},
135+
},
136+
{
137+
Query: "SELECT UUID_SHORT() != UUID_SHORT()",
138+
Expected: []sql.Row{
139+
{true}, // Should return different values on each call
140+
},
141+
},
142+
{
143+
Query: "SELECT UUID_SHORT() + 0 > 0",
144+
Expected: []sql.Row{
145+
{true}, // Should work in arithmetic expressions
146+
},
147+
},
148+
{
149+
Query: "SELECT CAST(UUID_SHORT() AS CHAR) != ''",
150+
Expected: []sql.Row{
151+
{true}, // Should cast to non-empty string
152+
},
153+
},
154+
{
155+
Query: "SELECT UUID_SHORT() BETWEEN 1 AND 18446744073709551615",
156+
Expected: []sql.Row{
157+
{true}, // Should be within uint64 range
158+
},
159+
},
160+
{
161+
Query: "SELECT (UUID_SHORT() & 0xFF00000000000000) >> 56 BETWEEN 0 AND 255",
162+
Expected: []sql.Row{
163+
{true}, // Server ID should be 0-255
164+
},
165+
},
166+
{
167+
Query: "SET @@global.server_id = 253",
168+
Expected: []sql.Row{
169+
{types.NewOkResult(0)},
170+
},
171+
},
172+
{
173+
Query: "SELECT (UUID_SHORT() & 0xFF00000000000000) >> 56 BETWEEN 0 AND 255",
174+
Expected: []sql.Row{
175+
{true}, // server time won't let us pin this down further
176+
},
177+
},
178+
{
179+
Query: "SET @@global.server_id = 1",
180+
Expected: []sql.Row{
181+
{types.NewOkResult(0)},
182+
},
183+
},
184+
},
185+
},
124186
{
125187
// https://github.com/dolthub/go-mysql-server/issues/3216
126188
Name: "UNION ALL with BLOB columns",

sql/expression/function/registry.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ var BuiltIns = []sql.Function{
318318
sql.NewFunction0("user", NewUser),
319319
sql.FunctionN{Name: "utc_timestamp", Fn: NewUTCTimestamp},
320320
sql.Function0{Name: "uuid", Fn: NewUUIDFunc},
321+
sql.Function0{Name: "uuid_short", Fn: NewUUIDShortFunc},
321322
sql.FunctionN{Name: "uuid_to_bin", Fn: NewUUIDToBin},
322323
sql.FunctionN{Name: "week", Fn: NewWeek},
323324
sql.Function1{Name: "values", Fn: NewValues},

sql/expression/function/uuid.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,21 @@ package function
1616

1717
import (
1818
"fmt"
19+
"sync"
1920

2021
"github.com/dolthub/vitess/go/sqltypes"
2122
"github.com/dolthub/vitess/go/vt/proto/query"
2223
"github.com/google/uuid"
2324

2425
"github.com/dolthub/go-mysql-server/sql"
2526
"github.com/dolthub/go-mysql-server/sql/types"
27+
"github.com/dolthub/go-mysql-server/sql/variables"
28+
)
29+
30+
// Global state for UUID_SHORT function
31+
var (
32+
uuidShortMu sync.Mutex
33+
uuidShortCounter uint64
2634
)
2735

2836
// UUID()
@@ -528,3 +536,101 @@ func (bu BinToUUID) Children() []sql.Expression {
528536
func (bu BinToUUID) IsNullable() bool {
529537
return false
530538
}
539+
540+
// UUID_SHORT()
541+
//
542+
// Returns a "short" universal identifier as a 64-bit unsigned integer. Values returned by UUID_SHORT() differ from the
543+
// string-format 128-bit identifiers returned by the UUID() function and have different uniqueness properties. The value
544+
// of UUID_SHORT() is guaranteed to be unique if the following conditions hold:
545+
//
546+
// The server_id value of the current server is between 0 and 255 and is unique among your set of source and replica servers
547+
//
548+
// The UUID_SHORT() return value is constructed this way:
549+
// (server_id & 255) << 56
550+
// + (server_startup_time_in_seconds << 24)
551+
// + incremented_variable++;
552+
//
553+
// Note: UUID_SHORT() does not work with statement-based replication.
554+
// https://dev.mysql.com/doc/refman/8.4/en/miscellaneous-functions.html#function_uuid-short
555+
556+
type UUIDShortFunc struct{}
557+
558+
var _ sql.FunctionExpression = &UUIDShortFunc{}
559+
var _ sql.CollationCoercible = &UUIDShortFunc{}
560+
561+
func NewUUIDShortFunc() sql.Expression {
562+
return &UUIDShortFunc{}
563+
}
564+
565+
// Description returns a human-readable description of the UUID_SHORT function.
566+
func (u *UUIDShortFunc) Description() string {
567+
return "returns a short universal identifier as a 64-bit unsigned integer."
568+
}
569+
570+
// String returns a string representation of the UUID_SHORT function call.
571+
func (u *UUIDShortFunc) String() string {
572+
return "UUID_SHORT()"
573+
}
574+
575+
// Type returns the data type of the UUID_SHORT function result (Uint64).
576+
func (u *UUIDShortFunc) Type() sql.Type {
577+
return types.Uint64
578+
}
579+
580+
// CollationCoercibility implements the interface sql.CollationCoercible.
581+
func (u *UUIDShortFunc) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
582+
return sql.Collation_binary, 5
583+
}
584+
585+
// Eval generates a 64-bit UUID_SHORT value using server_id, startup time, and counter.
586+
func (u *UUIDShortFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
587+
uuidShortMu.Lock()
588+
defer uuidShortMu.Unlock()
589+
590+
uuidShortCounter++
591+
592+
serverID := uint64(1) // Default fallback
593+
if _, val, ok := sql.SystemVariables.GetGlobal("server_id"); ok {
594+
if serverIDVal, ok := val.(uint32); ok {
595+
serverID = uint64(serverIDVal)
596+
}
597+
}
598+
599+
// Construct the UUID_SHORT value according to MySQL specification:
600+
result := ((serverID & 255) << 56) + (uint64(variables.ServerStartUpTime.Unix()) << 24) + uuidShortCounter
601+
return result, nil
602+
}
603+
604+
// WithChildren returns a new UUID_SHORT function with the given children (must be empty).
605+
func (u *UUIDShortFunc) WithChildren(children ...sql.Expression) (sql.Expression, error) {
606+
if len(children) != 0 {
607+
return nil, sql.ErrInvalidChildrenNumber.New(u, len(children), 0)
608+
}
609+
610+
return &UUIDShortFunc{}, nil
611+
}
612+
613+
// FunctionName returns the name of the UUID_SHORT function.
614+
func (u *UUIDShortFunc) FunctionName() string {
615+
return "UUID_SHORT"
616+
}
617+
618+
// Resolved returns true since UUID_SHORT has no dependencies to resolve.
619+
func (u *UUIDShortFunc) Resolved() bool {
620+
return true
621+
}
622+
623+
// Children returns the children expressions of this expression.
624+
func (u *UUIDShortFunc) Children() []sql.Expression {
625+
return nil
626+
}
627+
628+
// IsNullable returns false since UUID_SHORT always returns a value.
629+
func (u *UUIDShortFunc) IsNullable() bool {
630+
return false
631+
}
632+
633+
// IsNonDeterministic returns true since UUID_SHORT generates different values on each call.
634+
func (u *UUIDShortFunc) IsNonDeterministic() bool {
635+
return true
636+
}

sql/expression/function/uuid_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,129 @@ func TestBinToUUIDFailing(t *testing.T) {
215215
})
216216
}
217217
}
218+
219+
func TestUUIDShort(t *testing.T) {
220+
ctx := sql.NewEmptyContext()
221+
uuidShortE := NewUUIDShortFunc()
222+
223+
// Test that UUID_SHORT returns sequential values
224+
result1, err := uuidShortE.Eval(ctx, sql.Row{nil})
225+
require.NoError(t, err)
226+
require.IsType(t, uint64(0), result1)
227+
228+
result2, err := uuidShortE.Eval(ctx, sql.Row{nil})
229+
require.NoError(t, err)
230+
require.IsType(t, uint64(0), result2)
231+
232+
result3, err := uuidShortE.Eval(ctx, sql.Row{nil})
233+
require.NoError(t, err)
234+
require.IsType(t, uint64(0), result3)
235+
236+
// Values should be sequential (incrementing by 1)
237+
require.Equal(t, result1.(uint64)+1, result2.(uint64))
238+
require.Equal(t, result2.(uint64)+1, result3.(uint64))
239+
240+
// Test that values are 64-bit unsigned integers
241+
require.Greater(t, result1.(uint64), uint64(0))
242+
require.Greater(t, result2.(uint64), uint64(0))
243+
require.Greater(t, result3.(uint64), uint64(0))
244+
}
245+
246+
func TestUUIDShortMultipleInstances(t *testing.T) {
247+
ctx := sql.NewEmptyContext()
248+
249+
uuidShort1 := NewUUIDShortFunc()
250+
uuidShort2 := NewUUIDShortFunc()
251+
252+
result1, err := uuidShort1.Eval(ctx, sql.Row{nil})
253+
require.NoError(t, err)
254+
255+
result2, err := uuidShort2.Eval(ctx, sql.Row{nil})
256+
require.NoError(t, err)
257+
258+
// Both should return sequential values from the global counter
259+
require.IsType(t, uint64(0), result1)
260+
require.IsType(t, uint64(0), result2)
261+
require.Greater(t, result1.(uint64), uint64(0))
262+
require.Greater(t, result2.(uint64), uint64(0))
263+
264+
// Values should be sequential (global counter)
265+
require.Equal(t, result1.(uint64)+1, result2.(uint64))
266+
}
267+
268+
func TestUUIDShortWithChildren(t *testing.T) {
269+
uuidShortE := NewUUIDShortFunc()
270+
271+
newExpr, err := uuidShortE.WithChildren()
272+
require.NoError(t, err)
273+
require.NotNil(t, newExpr)
274+
275+
_, err = uuidShortE.WithChildren(expression.NewLiteral(1, types.Int64))
276+
require.Error(t, err)
277+
require.Contains(t, err.Error(), "invalid children number")
278+
}
279+
280+
func TestUUIDShortProperties(t *testing.T) {
281+
uuidShortE := NewUUIDShortFunc().(*UUIDShortFunc)
282+
283+
require.Equal(t, "UUID_SHORT", uuidShortE.FunctionName())
284+
require.Equal(t, "returns a short universal identifier as a 64-bit unsigned integer.", uuidShortE.Description())
285+
require.Equal(t, "UUID_SHORT()", uuidShortE.String())
286+
require.Equal(t, types.Uint64, uuidShortE.Type())
287+
require.True(t, uuidShortE.Resolved())
288+
require.False(t, uuidShortE.IsNullable())
289+
require.True(t, uuidShortE.IsNonDeterministic())
290+
require.Nil(t, uuidShortE.Children())
291+
}
292+
293+
func TestUUIDShortServerIdIntegration(t *testing.T) {
294+
ctx := sql.NewEmptyContext()
295+
uuidShortE := NewUUIDShortFunc()
296+
297+
result1, err := uuidShortE.Eval(ctx, sql.Row{nil})
298+
require.NoError(t, err)
299+
require.IsType(t, uint64(0), result1)
300+
301+
serverIDFromResult := (result1.(uint64) & 0xFF00000000000000) >> 56
302+
require.Equal(t, uint64(1), serverIDFromResult)
303+
304+
err = sql.SystemVariables.SetGlobal(ctx, "server_id", uint32(123))
305+
require.NoError(t, err)
306+
307+
result2, err := uuidShortE.Eval(ctx, sql.Row{nil})
308+
require.NoError(t, err)
309+
require.IsType(t, uint64(0), result2)
310+
311+
serverIDFromResult2 := (result2.(uint64) & 0xFF00000000000000) >> 56
312+
require.Equal(t, uint64(123), serverIDFromResult2)
313+
314+
err = sql.SystemVariables.SetGlobal(ctx, "server_id", uint32(255))
315+
require.NoError(t, err)
316+
317+
result3, err := uuidShortE.Eval(ctx, sql.Row{nil})
318+
require.NoError(t, err)
319+
require.IsType(t, uint64(0), result3)
320+
321+
serverIDFromResult3 := (result3.(uint64) & 0xFF00000000000000) >> 56
322+
require.Equal(t, uint64(255), serverIDFromResult3)
323+
324+
err = sql.SystemVariables.SetGlobal(ctx, "server_id", uint32(256))
325+
require.NoError(t, err)
326+
327+
result4, err := uuidShortE.Eval(ctx, sql.Row{nil})
328+
require.NoError(t, err)
329+
require.IsType(t, uint64(0), result4)
330+
331+
serverIDFromResult4 := (result4.(uint64) & 0xFF00000000000000) >> 56
332+
require.Equal(t, uint64(0), serverIDFromResult4)
333+
334+
err = sql.SystemVariables.SetGlobal(ctx, "server_id", uint32(243))
335+
require.NoError(t, err)
336+
337+
result5, err := uuidShortE.Eval(ctx, sql.Row{nil})
338+
require.NoError(t, err)
339+
require.IsType(t, uint64(0), result5)
340+
341+
serverIDFromResult5 := (result5.(uint64) & 0xFF00000000000000) >> 56
342+
require.Equal(t, uint64(243), serverIDFromResult5)
343+
}

sql/variables/system_variables.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ import (
3434
// There's also this page, which shows that a TON of variables are still missing ):
3535
// https://dev.mysql.com/doc/refman/8.0/en/server-system-variable-reference.html
3636

37-
// serverStartUpTime is needed by uptime status variable
38-
var serverStartUpTime = time.Now()
37+
// ServerStartUpTime is needed by uptime status variable
38+
var ServerStartUpTime = time.Now()
3939

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

0 commit comments

Comments
 (0)