Skip to content

Commit cfe6bf1

Browse files
authored
Merge pull request #1693 from ydb-platform/timestamp64
* Supported wide `Interval64` type
2 parents 3074ad5 + abfbcb8 commit cfe6bf1

File tree

6 files changed

+206
-25
lines changed

6 files changed

+206
-25
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
* Supported wide `Interval64` type
2+
13
## v3.102.0
24
* Supported wide `Date32`, `Datetime64` and `Timestamp64` types
35

internal/params/parameters.go

+7
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,13 @@ func (p *Parameter) Interval(v time.Duration) Builder {
315315
return p.parent
316316
}
317317

318+
func (p *Parameter) Interval64(v time.Duration) Builder {
319+
p.value = value.Interval64ValueFromDuration(v)
320+
p.parent.params = append(p.parent.params, p)
321+
322+
return p.parent
323+
}
324+
318325
func (p *Parameter) JSON(v string) Builder {
319326
p.value = value.JSONValue(v)
320327
p.parent.params = append(p.parent.params, p)

internal/types/types.go

+5
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ func primitiveTypeFromYDB(t Ydb.Type_PrimitiveTypeId) Type {
122122
return Timestamp64
123123
case Ydb.Type_INTERVAL:
124124
return Interval
125+
case Ydb.Type_INTERVAL64:
126+
return Interval64
125127
case Ydb.Type_TZ_DATE:
126128
return TzDate
127129
case Ydb.Type_TZ_DATETIME:
@@ -539,6 +541,7 @@ const (
539541
Timestamp
540542
Timestamp64
541543
Interval
544+
Interval64
542545
TzDate
543546
TzDatetime
544547
TzTimestamp
@@ -570,6 +573,7 @@ var primitive = [...]*Ydb.Type{
570573
Timestamp: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TIMESTAMP}},
571574
Timestamp64: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TIMESTAMP64}},
572575
Interval: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INTERVAL}},
576+
Interval64: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_INTERVAL64}},
573577
TzDate: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATE}},
574578
TzDatetime: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_DATETIME}},
575579
TzTimestamp: {Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_TZ_TIMESTAMP}},
@@ -602,6 +606,7 @@ var primitiveString = [...]string{
602606
Timestamp: "Timestamp",
603607
Timestamp64: "Timestamp64",
604608
Interval: "Interval",
609+
Interval64: "Interval64",
605610
TzDate: "TzDate",
606611
TzDatetime: "TzDatetime",
607612
TzTimestamp: "TzTimestamp",

internal/value/time.go

+10
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,21 @@ func IntervalToDuration(n int64) time.Duration {
3636
return time.Duration(n) * time.Microsecond
3737
}
3838

39+
// Interval64ToDuration returns time.Duration from given nanoseconds
40+
func Interval64ToDuration(n int64) time.Duration {
41+
return time.Duration(n) * time.Nanosecond
42+
}
43+
3944
// durationToMicroseconds returns microseconds from given time.Duration
4045
func durationToMicroseconds(d time.Duration) int64 {
4146
return int64(d / time.Microsecond)
4247
}
4348

49+
// durationToNanoseconds returns nanoseconds from given time.Duration
50+
func durationToNanoseconds(d time.Duration) int64 {
51+
return int64(d / time.Nanosecond)
52+
}
53+
4454
// DateToTime up to 11761191-01-20 00:00:00 +0000 UTC.
4555
func DateToTime(n uint32) time.Time {
4656
return time.Unix(0, 0).Add(time.Hour * 24 * time.Duration(n))

internal/value/value.go

+92
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ func primitiveValueFromYDB(t types.Primitive, v *Ydb.Value) (Value, error) {
124124
case types.Interval:
125125
return IntervalValue(v.GetInt64Value()), nil
126126

127+
case types.Interval64:
128+
return Interval64Value(v.GetInt64Value()), nil
129+
127130
case types.Timestamp:
128131
return TimestampValue(v.GetUint64Value()), nil
129132

@@ -1302,6 +1305,95 @@ func IntervalValueFromDuration(v time.Duration) intervalValue {
13021305
return intervalValue(durationToMicroseconds(v))
13031306
}
13041307

1308+
type interval64Value int64
1309+
1310+
func (v interval64Value) castTo(dst any) error {
1311+
switch vv := dst.(type) {
1312+
case *time.Duration:
1313+
*vv = Interval64ToDuration(int64(v))
1314+
1315+
return nil
1316+
case *driver.Value:
1317+
*vv = Interval64ToDuration(int64(v))
1318+
1319+
return nil
1320+
case *int64:
1321+
*vv = int64(v)
1322+
1323+
return nil
1324+
default:
1325+
return xerrors.WithStackTrace(fmt.Errorf(
1326+
"%w '%s(%+v)' to '%T' destination",
1327+
ErrCannotCast, v.Type().Yql(), v, vv,
1328+
))
1329+
}
1330+
}
1331+
1332+
func (v interval64Value) Yql() string {
1333+
buffer := xstring.Buffer()
1334+
defer buffer.Free()
1335+
buffer.WriteString(v.Type().Yql())
1336+
buffer.WriteByte('(')
1337+
buffer.WriteByte('"')
1338+
d := IntervalToDuration(int64(v))
1339+
if d < 0 {
1340+
buffer.WriteByte('-')
1341+
d = -d
1342+
}
1343+
buffer.WriteByte('P')
1344+
//nolint:gomnd
1345+
if days := d / time.Hour / 24; days > 0 {
1346+
d -= days * time.Hour * 24 //nolint:durationcheck
1347+
buffer.WriteString(strconv.FormatInt(int64(days), 10))
1348+
buffer.WriteByte('D')
1349+
}
1350+
if d > 0 {
1351+
buffer.WriteByte('T')
1352+
}
1353+
if hours := d / time.Hour; hours > 0 {
1354+
d -= hours * time.Hour //nolint:durationcheck
1355+
buffer.WriteString(strconv.FormatInt(int64(hours), 10))
1356+
buffer.WriteByte('H')
1357+
}
1358+
if minutes := d / time.Minute; minutes > 0 {
1359+
d -= minutes * time.Minute //nolint:durationcheck
1360+
buffer.WriteString(strconv.FormatInt(int64(minutes), 10))
1361+
buffer.WriteByte('M')
1362+
}
1363+
if d > 0 {
1364+
seconds := float64(d) / float64(time.Second)
1365+
fmt.Fprintf(buffer, "%0.6f", seconds)
1366+
buffer.WriteByte('S')
1367+
}
1368+
buffer.WriteByte('"')
1369+
buffer.WriteByte(')')
1370+
1371+
return buffer.String()
1372+
}
1373+
1374+
func (interval64Value) Type() types.Type {
1375+
return types.Interval64
1376+
}
1377+
1378+
func (v interval64Value) toYDB(a *allocator.Allocator) *Ydb.Value {
1379+
vv := a.Int64()
1380+
vv.Int64Value = int64(v)
1381+
1382+
vvv := a.Value()
1383+
vvv.Value = vv
1384+
1385+
return vvv
1386+
}
1387+
1388+
// Interval64Value makes Value from given microseconds value
1389+
func Interval64Value(v int64) interval64Value {
1390+
return interval64Value(v)
1391+
}
1392+
1393+
func Interval64ValueFromDuration(v time.Duration) interval64Value {
1394+
return interval64Value(durationToNanoseconds(v))
1395+
}
1396+
13051397
type jsonValue string
13061398

13071399
func (v jsonValue) castTo(dst any) error {

tests/integration/query_execute_test.go

+90-25
Original file line numberDiff line numberDiff line change
@@ -540,72 +540,72 @@ SELECT * FROM AS_TABLE($arg);
540540
require.Greater(t, partsWithLittleSize, 1)
541541
}
542542

543-
func TestQueryWideTypesForDateAndTime(t *testing.T) {
543+
func TestQueryWideDateTimeTypes(t *testing.T) {
544544
if os.Getenv("YDB_VERSION") != "nightly" && version.Lt(os.Getenv("YDB_VERSION"), "25.1") {
545545
t.Skip("require enables transactions for topics")
546546
}
547547

548548
scope := newScope(t)
549549

550550
for _, tt := range []struct {
551-
name string
552-
sql string
553-
expValue value.Value
554-
expTime time.Time
551+
name string
552+
sql string
553+
expYdbValue value.Value
554+
expGoValue time.Time
555555
}{
556556
{
557557
name: "Date",
558558
sql: `SELECT
559559
CAST("2000-01-01" AS Date),
560560
CAST("2000-01-01" AS Date),
561561
;`,
562-
expValue: value.OptionalValue(value.DateValueFromTime(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))),
563-
expTime: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
562+
expYdbValue: value.OptionalValue(value.DateValueFromTime(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))),
563+
expGoValue: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
564564
},
565565
{
566566
name: "Datetime",
567567
sql: `SELECT
568568
CAST("2000-01-01T00:00:00Z" AS Datetime),
569569
CAST("2000-01-01T00:00:00Z" AS Datetime),
570570
;`,
571-
expValue: value.OptionalValue(value.DatetimeValueFromTime(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))),
572-
expTime: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
571+
expYdbValue: value.OptionalValue(value.DatetimeValueFromTime(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))),
572+
expGoValue: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
573573
},
574574
{
575575
name: "Timestamp",
576576
sql: `SELECT
577577
CAST("2000-01-01T00:00:00.123456789Z" AS Timestamp),
578578
CAST("2000-01-01T00:00:00.123456789Z" AS Timestamp),
579579
;`,
580-
expValue: value.OptionalValue(value.TimestampValueFromTime(time.Date(2000, 1, 1, 0, 0, 0, 123456789, time.UTC))),
581-
expTime: time.Date(2000, 1, 1, 0, 0, 0, 123456000, time.UTC),
580+
expYdbValue: value.OptionalValue(value.TimestampValueFromTime(time.Date(2000, 1, 1, 0, 0, 0, 123456789, time.UTC))),
581+
expGoValue: time.Date(2000, 1, 1, 0, 0, 0, 123456000, time.UTC),
582582
},
583583
{
584584
name: "Date32",
585585
sql: `SELECT
586586
CAST("1000-01-01" AS Date32),
587587
CAST("1000-01-01" AS Date32),
588588
;`,
589-
expValue: value.OptionalValue(value.Date32ValueFromTime(time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC))),
590-
expTime: time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
589+
expYdbValue: value.OptionalValue(value.Date32ValueFromTime(time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC))),
590+
expGoValue: time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
591591
},
592592
{
593593
name: "Datetime64",
594594
sql: `SELECT
595595
CAST("1000-01-01T00:00:00Z" AS Datetime64),
596596
CAST("1000-01-01T00:00:00Z" AS Datetime64),
597597
;`,
598-
expValue: value.OptionalValue(value.Datetime64ValueFromTime(time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC))),
599-
expTime: time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
598+
expYdbValue: value.OptionalValue(value.Datetime64ValueFromTime(time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC))),
599+
expGoValue: time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
600600
},
601601
{
602602
name: "Timestamp64",
603603
sql: `SELECT
604604
CAST("1000-01-01T00:00:00.123456789Z" AS Timestamp64),
605605
CAST("1000-01-01T00:00:00.123456789Z" AS Timestamp64),
606606
;`,
607-
expValue: value.OptionalValue(value.Timestamp64ValueFromTime(time.Date(1000, 1, 1, 0, 0, 0, 123456789, time.UTC))),
608-
expTime: time.Date(1000, 1, 1, 0, 0, 0, 123456000, time.UTC),
607+
expYdbValue: value.OptionalValue(value.Timestamp64ValueFromTime(time.Date(1000, 1, 1, 0, 0, 0, 123456789, time.UTC))),
608+
expGoValue: time.Date(1000, 1, 1, 0, 0, 0, 123456000, time.UTC),
609609
},
610610
} {
611611
t.Run(tt.name, func(t *testing.T) {
@@ -617,26 +617,91 @@ func TestQueryWideTypesForDateAndTime(t *testing.T) {
617617
)
618618
err = row.Scan(&actValue, &actTime)
619619
require.NoError(t, err)
620-
require.Equal(t, tt.expValue, actValue)
621-
require.Equal(t, tt.expTime, actTime.UTC())
620+
require.Equal(t, tt.expYdbValue, actValue)
621+
require.Equal(t, tt.expGoValue, actTime.UTC())
622622
row, err = scope.Driver().Query().QueryRow(scope.Ctx,
623623
fmt.Sprintf(`
624624
DECLARE $p1 AS %s;
625625
DECLARE $p2 AS %s;
626626
SELECT $p1, $p2`,
627-
tt.expValue.Type().Yql(),
628-
tt.expValue.Type().Yql(),
627+
tt.expYdbValue.Type().Yql(),
628+
tt.expYdbValue.Type().Yql(),
629629
),
630630
query.WithParameters(ydb.ParamsBuilder().
631-
Param("$p1").Any(tt.expValue).
632-
Param("$p2").Any(tt.expValue).
631+
Param("$p1").Any(tt.expYdbValue).
632+
Param("$p2").Any(tt.expYdbValue).
633633
Build(),
634634
),
635635
)
636636
require.NoError(t, err)
637637
err = row.Scan(&actValue, &actTime)
638-
require.Equal(t, tt.expValue, actValue)
639-
require.Equal(t, tt.expTime, actTime.UTC())
638+
require.Equal(t, tt.expYdbValue, actValue)
639+
require.Equal(t, tt.expGoValue, actTime.UTC())
640+
})
641+
}
642+
}
643+
644+
func TestQueryWideIntervalTypes(t *testing.T) {
645+
if os.Getenv("YDB_VERSION") != "nightly" && version.Lt(os.Getenv("YDB_VERSION"), "25.1") {
646+
t.Skip("require enables transactions for topics")
647+
}
648+
649+
scope := newScope(t)
650+
651+
for _, tt := range []struct {
652+
name string
653+
sql string
654+
expYdbValue value.Value
655+
expGoValue time.Duration
656+
}{
657+
{
658+
name: "Interval",
659+
sql: `SELECT
660+
CAST("PT20M34.56789S" AS Interval),
661+
CAST("PT20M34.56789S" AS Interval),
662+
;`,
663+
expYdbValue: value.OptionalValue(value.IntervalValueFromDuration(1234567890 * time.Microsecond)),
664+
expGoValue: 1234567890 * time.Microsecond,
665+
},
666+
{
667+
name: "Interval64",
668+
sql: `SELECT
669+
CAST("PT20M34.56789S" AS Interval64),
670+
CAST("PT20M34.56789S" AS Interval64),
671+
;`,
672+
expYdbValue: value.OptionalValue(value.Interval64ValueFromDuration(time.Duration(1234567890))),
673+
expGoValue: time.Duration(1234567890),
674+
},
675+
} {
676+
t.Run(tt.name, func(t *testing.T) {
677+
row, err := scope.Driver().Query().QueryRow(scope.Ctx, tt.sql)
678+
require.NoError(t, err)
679+
var (
680+
actValue value.Value
681+
actInterval time.Duration
682+
)
683+
err = row.Scan(&actValue, &actInterval)
684+
require.NoError(t, err)
685+
require.Equal(t, tt.expYdbValue, actValue)
686+
require.Equal(t, tt.expGoValue, actInterval)
687+
row, err = scope.Driver().Query().QueryRow(scope.Ctx,
688+
fmt.Sprintf(`
689+
DECLARE $p1 AS %s;
690+
DECLARE $p2 AS %s;
691+
SELECT $p1, $p2`,
692+
tt.expYdbValue.Type().Yql(),
693+
tt.expYdbValue.Type().Yql(),
694+
),
695+
query.WithParameters(ydb.ParamsBuilder().
696+
Param("$p1").Any(tt.expYdbValue).
697+
Param("$p2").Any(tt.expYdbValue).
698+
Build(),
699+
),
700+
)
701+
require.NoError(t, err)
702+
err = row.Scan(&actValue, &actInterval)
703+
require.Equal(t, tt.expYdbValue, actValue)
704+
require.Equal(t, tt.expGoValue, actInterval)
640705
})
641706
}
642707
}

0 commit comments

Comments
 (0)