qb: add support for USING TIMEOUT clause

In scylladb/scylla#7781 we added possibility to add timeout as part of USING spec.
This patch adds support for it by adding `Timeout` and `TimeoutNamed` functions to builders.

Fixes #194
This commit is contained in:
Michał Matczuk
2021-08-03 11:29:54 +02:00
committed by Michal Jan Matczuk
parent 96a8de1e1e
commit 979397bc5e
12 changed files with 198 additions and 2 deletions

View File

@@ -145,3 +145,16 @@ func (b *BatchBuilder) TimestampNamed(name string) *BatchBuilder {
b.using.TimestampNamed(name) b.using.TimestampNamed(name)
return b return b
} }
// Timeout adds USING TIMEOUT clause to the query.
func (b *BatchBuilder) Timeout(d time.Duration) *BatchBuilder {
b.using.Timeout(d)
return b
}
// TimeoutNamed adds a USING TIMEOUT clause to the query with a custom
// parameter name.
func (b *BatchBuilder) TimeoutNamed(name string) *BatchBuilder {
b.using.TimeoutNamed(name)
return b
}

View File

@@ -72,6 +72,16 @@ func TestBatchBuilder(t *testing.T) {
S: "BEGIN BATCH USING TIMESTAMP ? APPLY BATCH ", S: "BEGIN BATCH USING TIMESTAMP ? APPLY BATCH ",
N: []string{"ts"}, N: []string{"ts"},
}, },
// Add TIMEOUT
{
B: Batch().Timeout(time.Second),
S: "BEGIN BATCH USING TIMEOUT 1s APPLY BATCH ",
},
{
B: Batch().TimeoutNamed("to"),
S: "BEGIN BATCH USING TIMEOUT ? APPLY BATCH ",
N: []string{"to"},
},
} }
for _, test := range table { for _, test := range table {

View File

@@ -92,6 +92,19 @@ func (b *DeleteBuilder) TimestampNamed(name string) *DeleteBuilder {
return b return b
} }
// Timeout adds USING TIMEOUT clause to the query.
func (b *DeleteBuilder) Timeout(d time.Duration) *DeleteBuilder {
b.using.Timeout(d)
return b
}
// TimeoutNamed adds a USING TIMEOUT clause to the query with a custom
// parameter name.
func (b *DeleteBuilder) TimeoutNamed(name string) *DeleteBuilder {
b.using.TimeoutNamed(name)
return b
}
// Where adds an expression to the WHERE clause of the query. Expressions are // Where adds an expression to the WHERE clause of the query. Expressions are
// ANDed together in the generated CQL. // ANDed together in the generated CQL.
func (b *DeleteBuilder) Where(w ...Cmp) *DeleteBuilder { func (b *DeleteBuilder) Where(w ...Cmp) *DeleteBuilder {

View File

@@ -78,6 +78,17 @@ func TestDeleteBuilder(t *testing.T) {
S: "DELETE FROM cycling.cyclist_name USING TIMESTAMP ? WHERE id=? ", S: "DELETE FROM cycling.cyclist_name USING TIMESTAMP ? WHERE id=? ",
N: []string{"ts", "expr"}, N: []string{"ts", "expr"},
}, },
// Add TIMEOUT
{
B: Delete("cycling.cyclist_name").Where(w).Timeout(time.Second),
S: "DELETE FROM cycling.cyclist_name USING TIMEOUT 1s WHERE id=? ",
N: []string{"expr"},
},
{
B: Delete("cycling.cyclist_name").Where(w).TimeoutNamed("to"),
S: "DELETE FROM cycling.cyclist_name USING TIMEOUT ? WHERE id=? ",
N: []string{"to", "expr"},
},
// Add IF EXISTS // Add IF EXISTS
{ {
B: Delete("cycling.cyclist_name").Where(w).Existing(), B: Delete("cycling.cyclist_name").Where(w).Existing(),

View File

@@ -183,3 +183,16 @@ func (b *InsertBuilder) TimestampNamed(name string) *InsertBuilder {
b.using.TimestampNamed(name) b.using.TimestampNamed(name)
return b return b
} }
// Timeout adds USING TIMEOUT clause to the query.
func (b *InsertBuilder) Timeout(d time.Duration) *InsertBuilder {
b.using.Timeout(d)
return b
}
// TimeoutNamed adds a USING TIMEOUT clause to the query with a custom
// parameter name.
func (b *InsertBuilder) TimeoutNamed(name string) *InsertBuilder {
b.using.TimeoutNamed(name)
return b
}

View File

@@ -76,6 +76,39 @@ func TestInsertBuilder(t *testing.T) {
S: "INSERT INTO cycling.cyclist_name (id,user_uuid,firstname) VALUES (?,?,?) USING TIMESTAMP ? ", S: "INSERT INTO cycling.cyclist_name (id,user_uuid,firstname) VALUES (?,?,?) USING TIMESTAMP ? ",
N: []string{"id", "user_uuid", "firstname", "ts"}, N: []string{"id", "user_uuid", "firstname", "ts"},
}, },
// Add TIMESTAMP
{
B: Insert("cycling.cyclist_name").Columns("id", "user_uuid", "firstname").Timestamp(time.Date(2005, 05, 05, 0, 0, 0, 0, time.UTC)),
S: "INSERT INTO cycling.cyclist_name (id,user_uuid,firstname) VALUES (?,?,?) USING TIMESTAMP 1115251200000000 ",
N: []string{"id", "user_uuid", "firstname"},
},
{
B: Insert("cycling.cyclist_name").Columns("id", "user_uuid", "firstname").TimestampNamed("ts"),
S: "INSERT INTO cycling.cyclist_name (id,user_uuid,firstname) VALUES (?,?,?) USING TIMESTAMP ? ",
N: []string{"id", "user_uuid", "firstname", "ts"},
},
// Add TIMESTAMP
{
B: Insert("cycling.cyclist_name").Columns("id", "user_uuid", "firstname").Timestamp(time.Date(2005, 05, 05, 0, 0, 0, 0, time.UTC)),
S: "INSERT INTO cycling.cyclist_name (id,user_uuid,firstname) VALUES (?,?,?) USING TIMESTAMP 1115251200000000 ",
N: []string{"id", "user_uuid", "firstname"},
},
{
B: Insert("cycling.cyclist_name").Columns("id", "user_uuid", "firstname").TimestampNamed("ts"),
S: "INSERT INTO cycling.cyclist_name (id,user_uuid,firstname) VALUES (?,?,?) USING TIMESTAMP ? ",
N: []string{"id", "user_uuid", "firstname", "ts"},
},
// Add TIMEOUT
{
B: Insert("cycling.cyclist_name").Columns("id", "user_uuid", "firstname").Timeout(time.Second),
S: "INSERT INTO cycling.cyclist_name (id,user_uuid,firstname) VALUES (?,?,?) USING TIMEOUT 1s ",
N: []string{"id", "user_uuid", "firstname"},
},
{
B: Insert("cycling.cyclist_name").Columns("id", "user_uuid", "firstname").TimeoutNamed("to"),
S: "INSERT INTO cycling.cyclist_name (id,user_uuid,firstname) VALUES (?,?,?) USING TIMEOUT ? ",
N: []string{"id", "user_uuid", "firstname", "to"},
},
// Add TupleColumn // Add TupleColumn
{ {
B: Insert("cycling.cyclist_name").TupleColumn("id", 2), B: Insert("cycling.cyclist_name").TupleColumn("id", 2),

View File

@@ -11,6 +11,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"time"
"github.com/scylladb/gocqlx/v2" "github.com/scylladb/gocqlx/v2"
) )
@@ -37,6 +38,7 @@ type SelectBuilder struct {
table string table string
columns columns columns columns
distinct columns distinct columns
using using
where where where where
groupBy columns groupBy columns
orderBy columns orderBy columns
@@ -83,7 +85,8 @@ func (b *SelectBuilder) ToCql() (stmt string, names []string) {
cql.WriteString(b.table) cql.WriteString(b.table)
cql.WriteByte(' ') cql.WriteByte(' ')
names = b.where.writeCql(&cql) names = append(names, b.using.writeCql(&cql)...)
names = append(names, b.where.writeCql(&cql)...)
if len(b.groupBy) > 0 { if len(b.groupBy) > 0 {
cql.WriteString("GROUP BY ") cql.WriteString("GROUP BY ")
@@ -168,6 +171,19 @@ func (b *SelectBuilder) Distinct(columns ...string) *SelectBuilder {
return b return b
} }
// Timeout adds USING TIMEOUT clause to the query.
func (b *SelectBuilder) Timeout(d time.Duration) *SelectBuilder {
b.using.Timeout(d)
return b
}
// TimeoutNamed adds a USING TIMEOUT clause to the query with a custom
// parameter name.
func (b *SelectBuilder) TimeoutNamed(name string) *SelectBuilder {
b.using.TimeoutNamed(name)
return b
}
// Where adds an expression to the WHERE clause of the query. Expressions are // Where adds an expression to the WHERE clause of the query. Expressions are
// ANDed together in the generated CQL. // ANDed together in the generated CQL.
func (b *SelectBuilder) Where(w ...Cmp) *SelectBuilder { func (b *SelectBuilder) Where(w ...Cmp) *SelectBuilder {

View File

@@ -6,6 +6,7 @@ package qb
import ( import (
"testing" "testing"
"time"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
) )
@@ -77,6 +78,17 @@ func TestSelectBuilder(t *testing.T) {
S: "SELECT * FROM cycling.cyclist_name WHERE id=(?,?) AND firstname>(?,?) ", S: "SELECT * FROM cycling.cyclist_name WHERE id=(?,?) AND firstname>(?,?) ",
N: []string{"id_0", "id_1", "firstname_0", "firstname_1"}, N: []string{"id_0", "id_1", "firstname_0", "firstname_1"},
}, },
// Add TIMEOUT
{
B: Select("cycling.cyclist_name").Where(w, Gt("firstname")).Timeout(time.Second),
S: "SELECT * FROM cycling.cyclist_name USING TIMEOUT 1s WHERE id=? AND firstname>? ",
N: []string{"expr", "firstname"},
},
{
B: Select("cycling.cyclist_name").Where(w, Gt("firstname")).TimeoutNamed("to"),
S: "SELECT * FROM cycling.cyclist_name USING TIMEOUT ? WHERE id=? AND firstname>? ",
N: []string{"to", "expr", "firstname"},
},
// Add GROUP BY // Add GROUP BY
{ {
B: Select("cycling.cyclist_name").Columns("MAX(stars) as max_stars").GroupBy("id"), B: Select("cycling.cyclist_name").Columns("MAX(stars) as max_stars").GroupBy("id"),

View File

@@ -117,6 +117,19 @@ func (b *UpdateBuilder) TimestampNamed(name string) *UpdateBuilder {
return b return b
} }
// Timeout adds USING TIMEOUT clause to the query.
func (b *UpdateBuilder) Timeout(d time.Duration) *UpdateBuilder {
b.using.Timeout(d)
return b
}
// TimeoutNamed adds a USING TIMEOUT clause to the query with a custom
// parameter name.
func (b *UpdateBuilder) TimeoutNamed(name string) *UpdateBuilder {
b.using.TimeoutNamed(name)
return b
}
// Set adds SET clauses to the query. // Set adds SET clauses to the query.
// To set a tuple column use SetTuple instead. // To set a tuple column use SetTuple instead.
func (b *UpdateBuilder) Set(columns ...string) *UpdateBuilder { func (b *UpdateBuilder) Set(columns ...string) *UpdateBuilder {

View File

@@ -126,6 +126,17 @@ func TestUpdateBuilder(t *testing.T) {
S: "UPDATE cycling.cyclist_name USING TIMESTAMP ? SET id=?,user_uuid=?,firstname=? WHERE id=? ", S: "UPDATE cycling.cyclist_name USING TIMESTAMP ? SET id=?,user_uuid=?,firstname=? WHERE id=? ",
N: []string{"ts", "id", "user_uuid", "firstname", "expr"}, N: []string{"ts", "id", "user_uuid", "firstname", "expr"},
}, },
// Add TIMEOUT
{
B: Update("cycling.cyclist_name").Set("id", "user_uuid", "firstname").Where(w).Timeout(time.Second),
S: "UPDATE cycling.cyclist_name USING TIMEOUT 1s SET id=?,user_uuid=?,firstname=? WHERE id=? ",
N: []string{"id", "user_uuid", "firstname", "expr"},
},
{
B: Update("cycling.cyclist_name").Set("id", "user_uuid", "firstname").Where(w).TimeoutNamed("to"),
S: "UPDATE cycling.cyclist_name USING TIMEOUT ? SET id=?,user_uuid=?,firstname=? WHERE id=? ",
N: []string{"to", "id", "user_uuid", "firstname", "expr"},
},
// Add IF EXISTS // Add IF EXISTS
{ {
B: Update("cycling.cyclist_name").Set("id", "user_uuid", "firstname").Where(w).Existing(), B: Update("cycling.cyclist_name").Set("id", "user_uuid", "firstname").Where(w).Existing(),

View File

@@ -25,7 +25,10 @@ type using struct {
ttlName string ttlName string
timestamp int64 timestamp int64
timestampName string timestampName string
using bool timeout time.Duration
timeoutName string
using bool
} }
func (u *using) TTL(d time.Duration) *using { func (u *using) TTL(d time.Duration) *using {
@@ -55,6 +58,18 @@ func (u *using) TimestampNamed(name string) *using {
return u return u
} }
func (u *using) Timeout(d time.Duration) *using {
u.timeout = d
u.timeoutName = ""
return u
}
func (u *using) TimeoutNamed(name string) *using {
u.timeout = 0
u.timeoutName = name
return u
}
func (u *using) writeCql(cql *bytes.Buffer) (names []string) { func (u *using) writeCql(cql *bytes.Buffer) (names []string) {
u.using = false u.using = false
@@ -79,6 +94,15 @@ func (u *using) writeCql(cql *bytes.Buffer) (names []string) {
names = append(names, u.timestampName) names = append(names, u.timestampName)
} }
if u.timeout != 0 {
u.writePreamble(cql)
fmt.Fprintf(cql, "TIMEOUT %s ", u.timeout)
} else if u.timeoutName != "" {
u.writePreamble(cql)
cql.WriteString("TIMEOUT ? ")
names = append(names, u.timeoutName)
}
return return
} }

View File

@@ -52,6 +52,17 @@ func TestUsing(t *testing.T) {
S: "USING TIMESTAMP ? ", S: "USING TIMESTAMP ? ",
N: []string{"ts"}, N: []string{"ts"},
}, },
// Timeout
{
B: new(using).Timeout(time.Second),
S: "USING TIMEOUT 1s ",
},
// TimeoutNamed
{
B: new(using).TimeoutNamed("to"),
S: "USING TIMEOUT ? ",
N: []string{"to"},
},
// TTL Timestamp // TTL Timestamp
{ {
B: new(using).TTL(time.Second).Timestamp(time.Date(2005, 05, 05, 0, 0, 0, 0, time.UTC)), B: new(using).TTL(time.Second).Timestamp(time.Date(2005, 05, 05, 0, 0, 0, 0, time.UTC)),
@@ -75,6 +86,22 @@ func TestUsing(t *testing.T) {
S: "USING TTL ? AND TIMESTAMP 1115251200000000 ", S: "USING TTL ? AND TIMESTAMP 1115251200000000 ",
N: []string{"ttl"}, N: []string{"ttl"},
}, },
// TTL Timeout
{
B: new(using).TTL(time.Second).Timeout(time.Second),
S: "USING TTL 1 AND TIMEOUT 1s ",
},
// TTL TimeoutNamed
{
B: new(using).TTL(time.Second).TimeoutNamed("to"),
S: "USING TTL 1 AND TIMEOUT ? ",
N: []string{"to"},
},
// TTL Timestamp Timeout
{
B: new(using).TTL(time.Second).Timestamp(time.Date(2005, 05, 05, 0, 0, 0, 0, time.UTC)).Timeout(time.Second),
S: "USING TTL 1 AND TIMESTAMP 1115251200000000 AND TIMEOUT 1s ",
},
// TTL with no duration // TTL with no duration
{ {
B: new(using).TTL(0 * time.Second), B: new(using).TTL(0 * time.Second),