Skip to content

Commit 2dc527f

Browse files
tauujeroiraz
authored andcommitted
feat: automatically convert uuid strings and byte slices to uuid values
1 parent 2d2678a commit 2dc527f

File tree

4 files changed

+117
-6
lines changed

4 files changed

+117
-6
lines changed

embedded/sql/engine_test.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232

3333
"github.com/codenotary/immudb/embedded/store"
3434
"github.com/codenotary/immudb/embedded/tbtree"
35+
"github.com/google/uuid"
3536
"github.com/stretchr/testify/require"
3637
"github.com/stretchr/testify/suite"
3738
)
@@ -383,7 +384,7 @@ func TestTimestampCasts(t *testing.T) {
383384
func TestUUIDAsPK(t *testing.T) {
384385
engine := setupCommonTest(t)
385386

386-
_, _, err := engine.Exec(context.Background(), nil, "CREATE TABLE IF NOT EXISTS uuid_table(id UUID, PRIMARY KEY id)", nil)
387+
_, _, err := engine.Exec(context.Background(), nil, "CREATE TABLE IF NOT EXISTS uuid_table(id UUID, test INTEGER, PRIMARY KEY id)", nil)
387388
require.NoError(t, err)
388389

389390
sel := EncodeSelector("", "uuid_table", "id")
@@ -426,6 +427,46 @@ func TestUUIDAsPK(t *testing.T) {
426427
require.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])
427428
})
428429

430+
t.Run("must accept uuid string as an UUID", func(t *testing.T) {
431+
id := uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})
432+
_, _, err = engine.Exec(context.Background(), nil, "INSERT INTO uuid_table(id, test) VALUES(@uuid, 3)", map[string]interface{}{
433+
"uuid": id.String(),
434+
})
435+
require.NoError(t, err)
436+
437+
r, err := engine.Query(context.Background(), nil, "SELECT id FROM uuid_table WHERE test = 3", nil)
438+
require.NoError(t, err)
439+
defer r.Close()
440+
441+
row, err := r.Read(context.Background())
442+
require.NoError(t, err)
443+
require.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())
444+
445+
require.Len(t, row.ValuesByPosition, 1)
446+
require.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])
447+
require.Equal(t, id, row.ValuesByPosition[0].RawValue())
448+
})
449+
450+
t.Run("must accept byte slice as an UUID", func(t *testing.T) {
451+
id := uuid.UUID([16]byte{0x10, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})
452+
_, _, err = engine.Exec(context.Background(), nil, "INSERT INTO uuid_table(id, test) VALUES(@uuid, 4)", map[string]interface{}{
453+
"uuid": id[:],
454+
})
455+
require.NoError(t, err)
456+
457+
r, err := engine.Query(context.Background(), nil, "SELECT id FROM uuid_table WHERE test = 4", nil)
458+
require.NoError(t, err)
459+
defer r.Close()
460+
461+
row, err := r.Read(context.Background())
462+
require.NoError(t, err)
463+
require.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())
464+
465+
require.Len(t, row.ValuesByPosition, 1)
466+
require.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])
467+
require.Equal(t, id, row.ValuesByPosition[0].RawValue())
468+
})
469+
429470
}
430471

431472
func TestUUIDNonPK(t *testing.T) {
@@ -452,6 +493,46 @@ func TestUUIDNonPK(t *testing.T) {
452493
require.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])
453494
})
454495

496+
t.Run("UUID as non PK must accept uuid string", func(t *testing.T) {
497+
id := uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})
498+
_, _, err = engine.Exec(context.Background(), nil, "INSERT INTO uuid_table(id, u, t) VALUES(2, @id, 't')", map[string]interface{}{
499+
"id": id.String(),
500+
})
501+
require.NoError(t, err)
502+
503+
r, err := engine.Query(context.Background(), nil, "SELECT u FROM uuid_table WHERE id = 2 LIMIT 1", nil)
504+
require.NoError(t, err)
505+
defer r.Close()
506+
507+
row, err := r.Read(context.Background())
508+
require.NoError(t, err)
509+
require.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())
510+
511+
require.Len(t, row.ValuesByPosition, 1)
512+
require.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])
513+
require.Equal(t, id, row.ValuesByPosition[0].RawValue())
514+
})
515+
516+
t.Run("UUID as non PK must accept byte slice", func(t *testing.T) {
517+
id := uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})
518+
_, _, err = engine.Exec(context.Background(), nil, "INSERT INTO uuid_table(id, u, t) VALUES(3, @id, 't')", map[string]interface{}{
519+
"id": id[:],
520+
})
521+
require.NoError(t, err)
522+
523+
r, err := engine.Query(context.Background(), nil, "SELECT u FROM uuid_table WHERE id = 3 LIMIT 1", nil)
524+
require.NoError(t, err)
525+
defer r.Close()
526+
527+
row, err := r.Read(context.Background())
528+
require.NoError(t, err)
529+
require.Equal(t, UUIDType, row.ValuesBySelector[sel].Type())
530+
531+
require.Len(t, row.ValuesByPosition, 1)
532+
require.Equal(t, row.ValuesByPosition[0], row.ValuesBySelector[sel])
533+
require.Equal(t, id, row.ValuesByPosition[0].RawValue())
534+
})
535+
455536
}
456537

457538
func TestFloatType(t *testing.T) {

embedded/sql/implicit_conversion.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ limitations under the License.
1616

1717
package sql
1818

19+
import "github.com/google/uuid"
20+
1921
// mayApplyImplicitConversion may do an implicit type conversion
2022
// implicit conversion is currently done in a subset of possible explicit conversions i.e. CAST
2123
func mayApplyImplicitConversion(val interface{}, requiredColumnType SQLValueType) (interface{}, error) {
@@ -73,6 +75,25 @@ func mayApplyImplicitConversion(val interface{}, requiredColumnType SQLValueType
7375

7476
typedVal = &Varchar{val: value}
7577
}
78+
case UUIDType:
79+
switch value := val.(type) {
80+
case uuid.UUID:
81+
return val, nil
82+
case string:
83+
converter, err = getConverter(VarcharType, UUIDType)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
typedVal = &Varchar{val: value}
89+
case []byte:
90+
converter, err = getConverter(BLOBType, UUIDType)
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
typedVal = &Blob{val: value}
96+
}
7697
default:
7798
// No implicit conversion rule found, do not convert at all
7899
return val, nil

embedded/sql/implicit_conversion_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"testing"
2222

23+
"github.com/google/uuid"
2324
"github.com/stretchr/testify/require"
2425
)
2526

@@ -34,6 +35,9 @@ func TestApplyImplicitConversion(t *testing.T) {
3435
{1.0, Float64Type, float64(1)},
3536
{"1", IntegerType, int64(1)},
3637
{"4.2", Float64Type, float64(4.2)},
38+
// UUID Version 4, RFC4122 Variant
39+
{"00010203-0440-0680-0809-0a0b0c0d0e0f", UUIDType, uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})},
40+
{[]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, UUIDType, uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})},
3741
} {
3842
t.Run(fmt.Sprintf("%+v", d), func(t *testing.T) {
3943
convVal, err := mayApplyImplicitConversion(d.val, d.requiredType)

pkg/stdlib/sql_test.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/codenotary/immudb/pkg/client"
2929
"github.com/codenotary/immudb/pkg/server"
3030
"github.com/codenotary/immudb/pkg/server/servertest"
31+
"github.com/google/uuid"
3132
"github.com/stretchr/testify/require"
3233
"google.golang.org/grpc"
3334
"google.golang.org/grpc/credentials/insecure"
@@ -109,37 +110,41 @@ func TestQueryCapabilities(t *testing.T) {
109110
_, db := testServerClient(t)
110111

111112
table := getRandomTableName()
112-
result, err := db.ExecContext(context.Background(), fmt.Sprintf("CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, PRIMARY KEY id)", table))
113+
result, err := db.ExecContext(context.Background(), fmt.Sprintf("CREATE TABLE %s (id INTEGER, amount INTEGER, total INTEGER, title VARCHAR, content BLOB, isPresent BOOLEAN, publicID UUID, PRIMARY KEY id)", table))
113114
require.NoError(t, err)
114115
require.NotNil(t, result)
115116

116117
binaryContent := []byte("my blob content1")
117-
_, err = db.ExecContext(context.Background(), fmt.Sprintf("INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (?, ?, ?, ?, ?, ?)", table), 1, 1000, 6000, "title 1", binaryContent, true)
118+
uuidPublicID := uuid.UUID([16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})
119+
_, err = db.ExecContext(context.Background(), fmt.Sprintf("INSERT INTO %s (id, amount, total, title, content, isPresent, publicID) VALUES (?, ?, ?, ?, ?, ?, ?)", table), 1, 1000, 6000, "title 1", binaryContent, true, uuidPublicID)
118120
require.NoError(t, err)
119121

120122
binaryContent2 := []byte("my blob content2")
121-
_, err = db.ExecContext(context.Background(), fmt.Sprintf("INSERT INTO %s (id, amount, total, title, content, isPresent) VALUES (?, ?, ?, ?, ?, ?)", table), 2, 2000, 12000, "title 2", binaryContent2, true)
123+
uuidPublicID2 := uuid.UUID([16]byte{0x10, 0x01, 0x02, 0x03, 0x04, 0x40, 0x06, 0x80, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})
124+
_, err = db.ExecContext(context.Background(), fmt.Sprintf("INSERT INTO %s (id, amount, total, title, content, isPresent, publicID) VALUES (?, ?, ?, ?, ?, ?, ?)", table), 2, 2000, 12000, "title 2", binaryContent2, true, uuidPublicID2)
122125
require.NoError(t, err)
123126

124127
var id int64
125128
var amount int64
126129
var title string
127130
var isPresent bool
128131
var content []byte
132+
var publicID uuid.UUID
129133

130-
rows, err := db.QueryContext(context.Background(), fmt.Sprintf("SELECT id, amount, title, content, isPresent FROM %s where isPresent=? and id=? and amount=? and total=? and title=?", table), true, 1, 1000, 6000, "title 1")
134+
rows, err := db.QueryContext(context.Background(), fmt.Sprintf("SELECT id, amount, title, content, isPresent, publicID FROM %s where isPresent=? and id=? and amount=? and total=? and title=?", table), true, 1, 1000, 6000, "title 1")
131135
require.NoError(t, err)
132136
defer rows.Close()
133137

134138
rows.Next()
135139

136-
err = rows.Scan(&id, &amount, &title, &content, &isPresent)
140+
err = rows.Scan(&id, &amount, &title, &content, &isPresent, &publicID)
137141
require.NoError(t, err)
138142
require.Equal(t, int64(1), id)
139143
require.Equal(t, int64(1000), amount)
140144
require.Equal(t, "title 1", title)
141145
require.Equal(t, binaryContent, content)
142146
require.Equal(t, true, isPresent)
147+
require.Equal(t, uuidPublicID, publicID)
143148
}
144149

145150
func TestQueryCapabilitiesWithPointers(t *testing.T) {

0 commit comments

Comments
 (0)