Skip to content

Commit f275a6a

Browse files
committed
impl uuid_short
1 parent 1e27be8 commit f275a6a

File tree

4 files changed

+207
-5
lines changed

4 files changed

+207
-5
lines changed

enginetest/memory_engine_test.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,19 +203,44 @@ func TestSingleScript(t *testing.T) {
203203
t.Skip()
204204
var scripts = []queries.ScriptTest{
205205
{
206-
Name: "AS OF propagates to nested CALLs",
206+
// https://github.com/dolthub/dolt/issues/9857
207+
Name: "UUID_SHORT() function returns 64-bit unsigned integers with proper construction",
207208
SetUpScript: []string{},
208209
Assertions: []queries.ScriptTestAssertion{
209210
{
210-
Query: "create procedure create_proc() create table t (i int primary key, j int);",
211+
Query: "SELECT UUID_SHORT() > 0",
211212
Expected: []sql.Row{
212-
{types.NewOkResult(0)},
213+
{true}, // Should return positive values
213214
},
214215
},
215216
{
216-
Query: "call create_proc()",
217+
Query: "SELECT UUID_SHORT() != UUID_SHORT()",
217218
Expected: []sql.Row{
218-
{types.NewOkResult(0)},
219+
{true}, // Should return different values on each call
220+
},
221+
},
222+
{
223+
Query: "SELECT UUID_SHORT() + 0 > 0",
224+
Expected: []sql.Row{
225+
{true}, // Should work in arithmetic expressions
226+
},
227+
},
228+
{
229+
Query: "SELECT CAST(UUID_SHORT() AS CHAR) != ''",
230+
Expected: []sql.Row{
231+
{true}, // Should cast to non-empty string
232+
},
233+
},
234+
{
235+
Query: "SELECT UUID_SHORT() BETWEEN 1 AND 18446744073709551615",
236+
Expected: []sql.Row{
237+
{true}, // Should be within uint64 range
238+
},
239+
},
240+
{
241+
Query: "SELECT (UUID_SHORT() & 0xFF00000000000000) >> 56 BETWEEN 0 AND 255",
242+
Expected: []sql.Row{
243+
{true}, // Server ID should be 0-255
219244
},
220245
},
221246
},

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: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ package function
1616

1717
import (
1818
"fmt"
19+
"sync"
20+
"time"
1921

2022
"github.com/dolthub/vitess/go/sqltypes"
2123
"github.com/dolthub/vitess/go/vt/proto/query"
@@ -25,6 +27,14 @@ import (
2527
"github.com/dolthub/go-mysql-server/sql/types"
2628
)
2729

30+
// Global state for UUID_SHORT function
31+
var (
32+
uuidShortMu sync.Mutex
33+
uuidShortCounter uint64
34+
uuidShortStartup = uint64(time.Now().Unix())
35+
uuidShortServerID = uint64(1) // Default server ID
36+
)
37+
2838
// UUID()
2939
//
3040
// Returns a Universal Unique Identifier (UUID) generated according to RFC 4122, “A Universally Unique IDentifier (UUID)
@@ -528,3 +538,91 @@ func (bu BinToUUID) Children() []sql.Expression {
528538
func (bu BinToUUID) IsNullable() bool {
529539
return false
530540
}
541+
542+
// UUID_SHORT()
543+
//
544+
// Returns a "short" universal identifier as a 64-bit unsigned integer. Values returned by UUID_SHORT() differ from the
545+
// string-format 128-bit identifiers returned by the UUID() function and have different uniqueness properties. The value
546+
// of UUID_SHORT() is guaranteed to be unique if the following conditions hold:
547+
//
548+
// The server_id value of the current server is between 0 and 255 and is unique among your set of source and replica servers
549+
// You do not set back the system time for your server host between mysqld restarts
550+
// You invoke UUID_SHORT() on average fewer than 16 million times per second between mysqld restarts
551+
//
552+
// The UUID_SHORT() return value is constructed this way:
553+
// (server_id & 255) << 56
554+
// + (server_startup_time_in_seconds << 24)
555+
// + incremented_variable++;
556+
//
557+
// Note: UUID_SHORT() does not work with statement-based replication.
558+
// https://dev.mysql.com/doc/refman/8.4/en/miscellaneous-functions.html#function_uuid-short
559+
560+
type UUIDShortFunc struct{}
561+
562+
var _ sql.FunctionExpression = &UUIDShortFunc{}
563+
var _ sql.CollationCoercible = &UUIDShortFunc{}
564+
565+
func NewUUIDShortFunc() sql.Expression {
566+
return &UUIDShortFunc{}
567+
}
568+
569+
// Description implements sql.FunctionExpression
570+
func (u UUIDShortFunc) Description() string {
571+
return "returns a short universal identifier as a 64-bit unsigned integer."
572+
}
573+
574+
func (u UUIDShortFunc) String() string {
575+
return "UUID_SHORT()"
576+
}
577+
578+
func (u UUIDShortFunc) Type() sql.Type {
579+
return types.Uint64
580+
}
581+
582+
// CollationCoercibility implements the interface sql.CollationCoercible.
583+
func (UUIDShortFunc) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
584+
return sql.Collation_binary, 5
585+
}
586+
587+
func (u *UUIDShortFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
588+
uuidShortMu.Lock()
589+
defer uuidShortMu.Unlock()
590+
591+
uuidShortCounter++
592+
593+
// Construct the UUID_SHORT value according to MySQL specification:
594+
// (server_id & 255) << 56 + (server_startup_time_in_seconds << 24) + incremented_variable
595+
result := ((uuidShortServerID & 255) << 56) + (uuidShortStartup << 24) + uuidShortCounter
596+
597+
return result, nil
598+
}
599+
600+
func (u UUIDShortFunc) WithChildren(children ...sql.Expression) (sql.Expression, error) {
601+
if len(children) != 0 {
602+
return nil, sql.ErrInvalidChildrenNumber.New(u, len(children), 0)
603+
}
604+
605+
return &UUIDShortFunc{}, nil
606+
}
607+
608+
func (u UUIDShortFunc) FunctionName() string {
609+
return "UUID_SHORT"
610+
}
611+
612+
func (u UUIDShortFunc) Resolved() bool {
613+
return true
614+
}
615+
616+
// Children returns the children expressions of this expression.
617+
func (u UUIDShortFunc) Children() []sql.Expression {
618+
return nil
619+
}
620+
621+
// IsNullable returns whether the expression can be null.
622+
func (u UUIDShortFunc) IsNullable() bool {
623+
return false
624+
}
625+
626+
func (u UUIDShortFunc) IsNonDeterministic() bool {
627+
return true
628+
}

sql/expression/function/uuid_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,81 @@ 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+
// Create multiple instances to test that they share the global counter (like MySQL)
250+
uuidShort1 := NewUUIDShortFunc()
251+
uuidShort2 := NewUUIDShortFunc()
252+
253+
result1, err := uuidShort1.Eval(ctx, sql.Row{nil})
254+
require.NoError(t, err)
255+
256+
result2, err := uuidShort2.Eval(ctx, sql.Row{nil})
257+
require.NoError(t, err)
258+
259+
// Both should return sequential values from the global counter
260+
require.IsType(t, uint64(0), result1)
261+
require.IsType(t, uint64(0), result2)
262+
require.Greater(t, result1.(uint64), uint64(0))
263+
require.Greater(t, result2.(uint64), uint64(0))
264+
265+
// Values should be sequential (global counter)
266+
require.Equal(t, result1.(uint64)+1, result2.(uint64))
267+
}
268+
269+
func TestUUIDShortWithChildren(t *testing.T) {
270+
uuidShortE := NewUUIDShortFunc()
271+
272+
// Test WithChildren with no arguments (should work)
273+
newExpr, err := uuidShortE.WithChildren()
274+
require.NoError(t, err)
275+
require.NotNil(t, newExpr)
276+
277+
// Test WithChildren with arguments (should fail)
278+
_, err = uuidShortE.WithChildren(expression.NewLiteral(1, types.Int64))
279+
require.Error(t, err)
280+
require.Contains(t, err.Error(), "invalid children number")
281+
}
282+
283+
func TestUUIDShortProperties(t *testing.T) {
284+
uuidShortE := NewUUIDShortFunc().(*UUIDShortFunc)
285+
286+
// Test function properties
287+
require.Equal(t, "UUID_SHORT", uuidShortE.FunctionName())
288+
require.Equal(t, "returns a short universal identifier as a 64-bit unsigned integer.", uuidShortE.Description())
289+
require.Equal(t, "UUID_SHORT()", uuidShortE.String())
290+
require.Equal(t, types.Uint64, uuidShortE.Type())
291+
require.True(t, uuidShortE.Resolved())
292+
require.False(t, uuidShortE.IsNullable())
293+
require.True(t, uuidShortE.IsNonDeterministic())
294+
require.Nil(t, uuidShortE.Children())
295+
}

0 commit comments

Comments
 (0)