Delete everything except query builder
This commit is contained in:
51
.github/workflows/main.yml
vendored
51
.github/workflows/main.yml
vendored
@@ -1,51 +0,0 @@
|
|||||||
name: Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
|
|
||||||
env:
|
|
||||||
# On CICD following error shows up:
|
|
||||||
# go: github.com/gocql/gocql@v1.7.0: GOPROXY list is not the empty string, but contains no entries
|
|
||||||
# This env variable is set to make it go away
|
|
||||||
# If at some point you see no error, feel free to remove it
|
|
||||||
GOPROXY: direct
|
|
||||||
# On CICD following error shows up:
|
|
||||||
# go: golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@v0.24.0: golang.org/x/tools@v0.24.0: verifying module: missing GOSUMDB
|
|
||||||
# This env variable makes it go away
|
|
||||||
# If at some point you see no error, feel free to remove it
|
|
||||||
GOSUMDB: off
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Git Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: '0'
|
|
||||||
|
|
||||||
- name: Install Go 1.25
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: 1.25
|
|
||||||
|
|
||||||
- name: Cache Dependencies
|
|
||||||
uses: actions/cache@v4
|
|
||||||
id: gomod-cache
|
|
||||||
with:
|
|
||||||
path: ~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('go.mod', 'cmd/schemagen/testdata/go.mod') }}
|
|
||||||
|
|
||||||
- name: Download Dependencies
|
|
||||||
run: git --version && make get-deps && make get-tools
|
|
||||||
|
|
||||||
- name: Lint
|
|
||||||
run: make check
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: make test
|
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Batch is a wrapper around gocql.Batch
|
// Batch is a wrapper around gocql.Batch
|
||||||
@@ -39,13 +39,12 @@ func (s *Session) ContextBatch(ctx context.Context, bt gocql.BatchType) *Batch {
|
|||||||
// GetRequestTimeout returns time driver waits for single server response
|
// GetRequestTimeout returns time driver waits for single server response
|
||||||
// This timeout is applied to preparing statement request and for query execution requests
|
// This timeout is applied to preparing statement request and for query execution requests
|
||||||
func (b *Batch) GetRequestTimeout() time.Duration {
|
func (b *Batch) GetRequestTimeout() time.Duration {
|
||||||
return b.Batch.GetRequestTimeout()
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRequestTimeout sets time driver waits for server to respond
|
// SetRequestTimeout sets time driver waits for server to respond
|
||||||
// This timeout is applied to preparing statement request and for query execution requests
|
// This timeout is applied to preparing statement request and for query execution requests
|
||||||
func (b *Batch) SetRequestTimeout(timeout time.Duration) *Batch {
|
func (b *Batch) SetRequestTimeout(timeout time.Duration) *Batch {
|
||||||
b.Batch.SetRequestTimeout(timeout)
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +53,6 @@ func (b *Batch) SetRequestTimeout(timeout time.Duration) *Batch {
|
|||||||
// string is sent, the default behavior, using the configured HostSelectionPolicy will
|
// string is sent, the default behavior, using the configured HostSelectionPolicy will
|
||||||
// be used. A hostID can be obtained from HostInfo.HostID() after calling GetHosts().
|
// be used. A hostID can be obtained from HostInfo.HostID() after calling GetHosts().
|
||||||
func (b *Batch) SetHostID(hostID string) *Batch {
|
func (b *Batch) SetHostID(hostID string) *Batch {
|
||||||
b.Batch.SetHostID(hostID)
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
"github.com/scylladb/gocqlx/v3"
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func camelize(s string) string {
|
|
||||||
buf := []byte(s)
|
|
||||||
out := make([]byte, 0, len(buf))
|
|
||||||
underscoreSeen := false
|
|
||||||
|
|
||||||
l := len(buf)
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
if !allowedBindRune(buf[i]) && buf[i] != '_' {
|
|
||||||
panic(fmt.Sprint("not allowed name ", s))
|
|
||||||
}
|
|
||||||
|
|
||||||
b := rune(buf[i])
|
|
||||||
|
|
||||||
if b == '_' {
|
|
||||||
underscoreSeen = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == 0 || underscoreSeen) && unicode.IsLower(b) {
|
|
||||||
b = unicode.ToUpper(b)
|
|
||||||
underscoreSeen = false
|
|
||||||
}
|
|
||||||
|
|
||||||
out = append(out, byte(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func allowedBindRune(b byte) bool {
|
|
||||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestCamelize(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"hello", "Hello"},
|
|
||||||
{"_hello", "Hello"},
|
|
||||||
{"__hello", "Hello"},
|
|
||||||
{"hello_", "Hello"},
|
|
||||||
{"hello_world", "HelloWorld"},
|
|
||||||
{"hello__world", "HelloWorld"},
|
|
||||||
{"_hello_world", "HelloWorld"},
|
|
||||||
{"helloWorld", "HelloWorld"},
|
|
||||||
{"HelloWorld", "HelloWorld"},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
|
||||||
if got := camelize(tt.input); got != tt.want {
|
|
||||||
t.Errorf("camelize() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
// Code generated by "gocqlx/cmd/schemagen"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package {{.PackageName}}
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/scylladb/gocqlx/v3/table"
|
|
||||||
{{- range .Imports}}
|
|
||||||
"{{.}}"
|
|
||||||
{{- end}}
|
|
||||||
)
|
|
||||||
|
|
||||||
{{with .Tables}}
|
|
||||||
// Table models.
|
|
||||||
var (
|
|
||||||
{{range .}}
|
|
||||||
{{$model_name := .Name | camelize}}
|
|
||||||
{{$model_name}} = table.New(table.Metadata {
|
|
||||||
Name: "{{.Name}}",
|
|
||||||
Columns: []string{
|
|
||||||
{{- range .OrderedColumns}}
|
|
||||||
"{{.}}",
|
|
||||||
{{- end}}
|
|
||||||
},
|
|
||||||
PartKey: []string {
|
|
||||||
{{- range .PartitionKey}}
|
|
||||||
"{{.Name}}",
|
|
||||||
{{- end}}
|
|
||||||
},
|
|
||||||
SortKey: []string{
|
|
||||||
{{- range .ClusteringColumns}}
|
|
||||||
"{{.Name}}",
|
|
||||||
{{- end}}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
{{end}}
|
|
||||||
)
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{with .Views}}
|
|
||||||
// Materialized view models.
|
|
||||||
var (
|
|
||||||
{{- range .}}
|
|
||||||
{{$model_name := .ViewName | camelize}}
|
|
||||||
{{$model_name}} = table.New(table.Metadata {
|
|
||||||
Name: "{{.ViewName}}",
|
|
||||||
Columns: []string{
|
|
||||||
{{- range .OrderedColumns}}
|
|
||||||
"{{.}}",
|
|
||||||
{{- end}}
|
|
||||||
},
|
|
||||||
PartKey: []string {
|
|
||||||
{{- range .PartitionKey}}
|
|
||||||
"{{.Name}}",
|
|
||||||
{{- end}}
|
|
||||||
},
|
|
||||||
SortKey: []string{
|
|
||||||
{{- range .ClusteringColumns}}
|
|
||||||
"{{.Name}}",
|
|
||||||
{{- end}}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
{{end}}
|
|
||||||
)
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{with .Indexes}}
|
|
||||||
// Index models.
|
|
||||||
var (
|
|
||||||
{{range .}}
|
|
||||||
{{$model_name := .Name | camelize}}
|
|
||||||
{{$model_name}}Index = table.New(table.Metadata {
|
|
||||||
Name: "{{.Name}}_index",
|
|
||||||
Columns: []string{
|
|
||||||
{{- range .OrderedColumns}}
|
|
||||||
"{{.}}",
|
|
||||||
{{- end}}
|
|
||||||
},
|
|
||||||
PartKey: []string {
|
|
||||||
{{- range .PartitionKey}}
|
|
||||||
"{{.Name}}",
|
|
||||||
{{- end}}
|
|
||||||
},
|
|
||||||
SortKey: []string{
|
|
||||||
{{- range .ClusteringColumns}}
|
|
||||||
"{{.Name}}",
|
|
||||||
{{- end}}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
{{end}}
|
|
||||||
)
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{with .UserTypes}}
|
|
||||||
// User-defined types (UDT) structs.
|
|
||||||
{{- range .}}
|
|
||||||
{{- $type_name := .Name | camelize}}
|
|
||||||
{{- $field_types := .FieldTypes}}
|
|
||||||
type {{$type_name}}UserType struct {
|
|
||||||
gocqlx.UDT
|
|
||||||
{{- range $index, $element := .FieldNames}}
|
|
||||||
{{. | camelize}} {{(index $field_types $index) | mapScyllaToGoType}}
|
|
||||||
{{- end}}
|
|
||||||
}
|
|
||||||
{{- end}}
|
|
||||||
{{- end}}
|
|
||||||
|
|
||||||
{{with .Tables}}
|
|
||||||
// Table structs.
|
|
||||||
{{- range .}}
|
|
||||||
{{- $model_name := .Name | camelize}}
|
|
||||||
type {{$model_name}}Struct struct {
|
|
||||||
{{- range .Columns}}
|
|
||||||
{{- if not (eq .Type "empty") }}
|
|
||||||
{{.Name | camelize}} {{.Type | mapScyllaToGoType}}
|
|
||||||
{{- end}}
|
|
||||||
{{- end}}
|
|
||||||
}
|
|
||||||
{{- end}}
|
|
||||||
{{- end}}
|
|
||||||
|
|
||||||
{{with .Views}}
|
|
||||||
// View structs.
|
|
||||||
{{- range .}}
|
|
||||||
{{- $model_name := .ViewName | camelize}}
|
|
||||||
type {{$model_name}}Struct struct {
|
|
||||||
{{- range .Columns}}
|
|
||||||
{{- if not (eq .Type "empty") }}
|
|
||||||
{{.Name | camelize}} {{.Type | mapScyllaToGoType}}
|
|
||||||
{{- end}}
|
|
||||||
{{- end}}
|
|
||||||
}
|
|
||||||
{{- end}}
|
|
||||||
{{- end}}
|
|
||||||
|
|
||||||
{{with .Indexes}}
|
|
||||||
// Index structs.
|
|
||||||
{{- range .}}
|
|
||||||
{{- $model_name := .Name | camelize}}
|
|
||||||
type {{$model_name}}IndexStruct struct {
|
|
||||||
{{- range .Columns}}
|
|
||||||
{{- if not (eq .Type "empty") }}
|
|
||||||
{{.Name | camelize}} {{.Type | mapScyllaToGoType}}
|
|
||||||
{{- end}}
|
|
||||||
{{- end}}
|
|
||||||
}
|
|
||||||
{{- end}}
|
|
||||||
{{- end}}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var types = map[string]string{
|
|
||||||
"ascii": "string",
|
|
||||||
"bigint": "int64",
|
|
||||||
"blob": "[]byte",
|
|
||||||
"boolean": "bool",
|
|
||||||
"counter": "int",
|
|
||||||
"date": "time.Time",
|
|
||||||
"decimal": "inf.Dec",
|
|
||||||
"double": "float64",
|
|
||||||
"duration": "gocql.Duration",
|
|
||||||
"float": "float32",
|
|
||||||
"inet": "string",
|
|
||||||
"int": "int32",
|
|
||||||
"smallint": "int16",
|
|
||||||
"text": "string",
|
|
||||||
"time": "time.Duration",
|
|
||||||
"timestamp": "time.Time",
|
|
||||||
"timeuuid": "[16]byte",
|
|
||||||
"tinyint": "int8",
|
|
||||||
"uuid": "[16]byte",
|
|
||||||
"varchar": "string",
|
|
||||||
"varint": "int64",
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapScyllaToGoType(s string) string {
|
|
||||||
frozenRegex := regexp.MustCompile(`frozen<([a-z]*)>`)
|
|
||||||
match := frozenRegex.FindAllStringSubmatch(s, -1)
|
|
||||||
if match != nil {
|
|
||||||
s = match[0][1]
|
|
||||||
}
|
|
||||||
|
|
||||||
mapRegex := regexp.MustCompile(`map<([a-z]*), ([a-z]*)>`)
|
|
||||||
setRegex := regexp.MustCompile(`set<([a-z]*)>`)
|
|
||||||
listRegex := regexp.MustCompile(`list<([a-z]*)>`)
|
|
||||||
tupleRegex := regexp.MustCompile(`tuple<(?:([a-z]*),? ?)*>`)
|
|
||||||
match = mapRegex.FindAllStringSubmatch(s, -1)
|
|
||||||
if match != nil {
|
|
||||||
key := match[0][1]
|
|
||||||
value := match[0][2]
|
|
||||||
|
|
||||||
return "map[" + types[key] + "]" + types[value]
|
|
||||||
}
|
|
||||||
|
|
||||||
match = setRegex.FindAllStringSubmatch(s, -1)
|
|
||||||
if match != nil {
|
|
||||||
key := match[0][1]
|
|
||||||
|
|
||||||
return "[]" + types[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
match = listRegex.FindAllStringSubmatch(s, -1)
|
|
||||||
if match != nil {
|
|
||||||
key := match[0][1]
|
|
||||||
|
|
||||||
return "[]" + types[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
match = tupleRegex.FindAllStringSubmatch(s, -1)
|
|
||||||
if match != nil {
|
|
||||||
tuple := match[0][0]
|
|
||||||
subStr := tuple[6 : len(tuple)-1]
|
|
||||||
types := strings.Split(subStr, ", ")
|
|
||||||
|
|
||||||
typeStr := "struct {\n"
|
|
||||||
for i, t := range types {
|
|
||||||
typeStr += "\t\tField" + strconv.Itoa(i+1) + " " + mapScyllaToGoType(t) + "\n"
|
|
||||||
}
|
|
||||||
typeStr += "\t}"
|
|
||||||
|
|
||||||
return typeStr
|
|
||||||
}
|
|
||||||
|
|
||||||
t, exists := types[s]
|
|
||||||
if exists {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
return camelize(s) + "UserType"
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMapScyllaToGoType(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"ascii", "string"},
|
|
||||||
{"bigint", "int64"},
|
|
||||||
{"blob", "[]byte"},
|
|
||||||
{"boolean", "bool"},
|
|
||||||
{"counter", "int"},
|
|
||||||
{"date", "time.Time"},
|
|
||||||
{"decimal", "inf.Dec"},
|
|
||||||
{"double", "float64"},
|
|
||||||
{"duration", "gocql.Duration"},
|
|
||||||
{"float", "float32"},
|
|
||||||
{"inet", "string"},
|
|
||||||
{"int", "int32"},
|
|
||||||
{"smallint", "int16"},
|
|
||||||
{"text", "string"},
|
|
||||||
{"time", "time.Duration"},
|
|
||||||
{"timestamp", "time.Time"},
|
|
||||||
{"timeuuid", "[16]byte"},
|
|
||||||
{"tinyint", "int8"},
|
|
||||||
{"uuid", "[16]byte"},
|
|
||||||
{"varchar", "string"},
|
|
||||||
{"varint", "int64"},
|
|
||||||
{"map<int, text>", "map[int32]string"},
|
|
||||||
{"list<int>", "[]int32"},
|
|
||||||
{"set<int>", "[]int32"},
|
|
||||||
{"tuple<boolean, int, smallint>", "struct {\n\t\tField1 bool\n\t\tField2 int32\n\t\tField3 int16\n\t}"},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
|
||||||
if got := mapScyllaToGoType(tt.input); got != tt.want {
|
|
||||||
t.Errorf("mapScyllaToGoType() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"go/format"
|
|
||||||
"html/template"
|
|
||||||
"io/fs"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
_ "github.com/scylladb/gocqlx/v3/table"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultClusterConfig = gocql.NewCluster()
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultQueryTimeout = defaultClusterConfig.Timeout
|
|
||||||
defaultConnectionTimeout = defaultClusterConfig.ConnectTimeout
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
cmd = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
|
||||||
flagCluster = cmd.String("cluster", "127.0.0.1", "a comma-separated list of host:port tuples")
|
|
||||||
flagKeyspace = cmd.String("keyspace", "", "keyspace to inspect")
|
|
||||||
flagPkgname = cmd.String("pkgname", "models", "the name you wish to assign to your generated package")
|
|
||||||
flagOutput = cmd.String("output", "models", "the name of the folder to output to")
|
|
||||||
flagOutputDirPerm = cmd.Uint64("output-dir-perm", 0o755, "output directory permissions")
|
|
||||||
flagOutputFilePerm = cmd.Uint64("output-file-perm", 0o644, "output file permissions")
|
|
||||||
flagUser = cmd.String("user", "", "user for password authentication")
|
|
||||||
flagPassword = cmd.String("password", "", "password for password authentication")
|
|
||||||
flagIgnoreNames = cmd.String("ignore-names", "", "a comma-separated list of table, view or index names to ignore")
|
|
||||||
flagIgnoreIndexes = cmd.Bool("ignore-indexes", false, "don't generate types for indexes")
|
|
||||||
flagQueryTimeout = cmd.Duration("query-timeout", defaultQueryTimeout, "query timeout ( in seconds )")
|
|
||||||
flagConnectionTimeout = cmd.Duration("connection-timeout", defaultConnectionTimeout, "connection timeout ( in seconds )")
|
|
||||||
flagSSLEnableHostVerification = cmd.Bool("ssl-enable-host-verification", false, "don't check server ssl certificate")
|
|
||||||
flagSSLCAPath = cmd.String("ssl-ca-path", "", "path to ssl CA certificates")
|
|
||||||
flagSSLCertPath = cmd.String("ssl-cert-path", "", "path to ssl certificate")
|
|
||||||
flagSSLKeyPath = cmd.String("ssl-key-path", "", "path to ssl key")
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed keyspace.tmpl
|
|
||||||
var keyspaceTmpl string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := cmd.Parse(os.Args[1:])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("can't parse flags")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *flagKeyspace == "" {
|
|
||||||
log.Fatalln("missing required flag: keyspace")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := schemagen(); err != nil {
|
|
||||||
log.Fatalf("failed to generate schema: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func schemagen() error {
|
|
||||||
if err := os.MkdirAll(*flagOutput, os.FileMode(*flagOutputDirPerm)); err != nil {
|
|
||||||
return fmt.Errorf("create output directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := createSession()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("open output file: %w", err)
|
|
||||||
}
|
|
||||||
metadata, err := session.KeyspaceMetadata(*flagKeyspace)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("fetch keyspace metadata: %w", err)
|
|
||||||
}
|
|
||||||
b, err := renderTemplate(metadata)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("render template: %w", err)
|
|
||||||
}
|
|
||||||
outputPath := path.Join(*flagOutput, *flagPkgname+".go")
|
|
||||||
|
|
||||||
return os.WriteFile(outputPath, b, fs.FileMode(*flagOutputFilePerm))
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderTemplate(md *gocql.KeyspaceMetadata) ([]byte, error) {
|
|
||||||
t, err := template.
|
|
||||||
New("keyspace.tmpl").
|
|
||||||
Funcs(template.FuncMap{"camelize": camelize}).
|
|
||||||
Funcs(template.FuncMap{"mapScyllaToGoType": mapScyllaToGoType}).
|
|
||||||
Parse(keyspaceTmpl)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("unable to parse models template:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// First of all, drop all indicies in metadata if option `-ignore-indexes`
|
|
||||||
// is specified.
|
|
||||||
if *flagIgnoreIndexes {
|
|
||||||
md.Indexes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then remove all tables, views, and indices if their names match the
|
|
||||||
// filter.
|
|
||||||
ignoredNames := make(map[string]struct{})
|
|
||||||
for _, ignoredName := range strings.Split(*flagIgnoreNames, ",") {
|
|
||||||
ignoredNames[ignoredName] = struct{}{}
|
|
||||||
}
|
|
||||||
for name := range ignoredNames {
|
|
||||||
delete(md.Tables, name)
|
|
||||||
delete(md.Views, name)
|
|
||||||
delete(md.Indexes, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a user-defined type (UDT) if it is not used any column (i.e.
|
|
||||||
// table, view, or index).
|
|
||||||
orphanedTypes := make(map[string]struct{})
|
|
||||||
for userTypeName := range md.Types {
|
|
||||||
if !usedInTables(userTypeName, md.Tables) &&
|
|
||||||
!usedInViews(userTypeName, md.Views) &&
|
|
||||||
!usedInIndices(userTypeName, md.Indexes) {
|
|
||||||
orphanedTypes[userTypeName] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for typeName := range orphanedTypes {
|
|
||||||
delete(md.Types, typeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
imports := make([]string, 0)
|
|
||||||
if len(md.Types) != 0 {
|
|
||||||
imports = append(imports, "github.com/scylladb/gocqlx/v3")
|
|
||||||
}
|
|
||||||
|
|
||||||
updateImports := func(columns map[string]*gocql.ColumnMetadata) {
|
|
||||||
for _, c := range columns {
|
|
||||||
if (c.Type == "timestamp" || c.Type == "date" || c.Type == "time") && !existsInSlice(imports, "time") {
|
|
||||||
imports = append(imports, "time")
|
|
||||||
}
|
|
||||||
if c.Type == "decimal" && !existsInSlice(imports, "gopkg.in/inf.v0") {
|
|
||||||
imports = append(imports, "gopkg.in/inf.v0")
|
|
||||||
}
|
|
||||||
if c.Type == "duration" && !existsInSlice(imports, "github.com/gocql/gocql") {
|
|
||||||
imports = append(imports, "github.com/gocql/gocql")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that for each table, view, and index
|
|
||||||
//
|
|
||||||
// 1. ordered columns are sorted alphabetically;
|
|
||||||
// 2. imports are resolves for column types.
|
|
||||||
for _, t := range md.Tables {
|
|
||||||
sort.Strings(t.OrderedColumns)
|
|
||||||
updateImports(t.Columns)
|
|
||||||
}
|
|
||||||
for _, v := range md.Views {
|
|
||||||
sort.Strings(v.OrderedColumns)
|
|
||||||
updateImports(v.Columns)
|
|
||||||
}
|
|
||||||
for _, i := range md.Indexes {
|
|
||||||
sort.Strings(i.OrderedColumns)
|
|
||||||
updateImports(i.Columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"PackageName": *flagPkgname,
|
|
||||||
"Tables": md.Tables,
|
|
||||||
"Views": md.Views,
|
|
||||||
"Indexes": md.Indexes,
|
|
||||||
"UserTypes": md.Types,
|
|
||||||
"Imports": imports,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = t.Execute(buf, data); err != nil {
|
|
||||||
return nil, fmt.Errorf("template: %w", err)
|
|
||||||
}
|
|
||||||
return format.Source(buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func createSession() (gocqlx.Session, error) {
|
|
||||||
cluster := gocql.NewCluster(clusterHosts()...)
|
|
||||||
|
|
||||||
if *flagUser != "" {
|
|
||||||
cluster.Authenticator = gocql.PasswordAuthenticator{
|
|
||||||
Username: *flagUser,
|
|
||||||
Password: *flagPassword,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *flagQueryTimeout >= 0 {
|
|
||||||
cluster.Timeout = *flagQueryTimeout
|
|
||||||
}
|
|
||||||
if *flagConnectionTimeout >= 0 {
|
|
||||||
cluster.ConnectTimeout = *flagConnectionTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
if *flagSSLCAPath != "" || *flagSSLCertPath != "" || *flagSSLKeyPath != "" {
|
|
||||||
cluster.SslOpts = &gocql.SslOptions{
|
|
||||||
EnableHostVerification: *flagSSLEnableHostVerification,
|
|
||||||
CaPath: *flagSSLCAPath,
|
|
||||||
CertPath: *flagSSLCertPath,
|
|
||||||
KeyPath: *flagSSLKeyPath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return gocqlx.WrapSession(cluster.CreateSession())
|
|
||||||
}
|
|
||||||
|
|
||||||
func clusterHosts() []string {
|
|
||||||
return strings.Split(*flagCluster, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func existsInSlice(s []string, v string) bool {
|
|
||||||
for _, i := range s {
|
|
||||||
if v == i {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// userTypes finds Cassandra schema types enclosed in angle brackets.
|
|
||||||
// Calling FindAllStringSubmatch on it will return a slice of string slices containing two elements.
|
|
||||||
// The second element contains the name of the type.
|
|
||||||
//
|
|
||||||
// [["<my_type,", "my_type"] ["my_other_type>", "my_other_type"]]
|
|
||||||
var userTypes = regexp.MustCompile(`(?:<|\s)(\w+)[>,]`) // match all types contained in set<X>, list<X>, tuple<A, B> etc.
|
|
||||||
|
|
||||||
// usedInColumns tests whether the typeName is used in any of columns of the
|
|
||||||
// provided tables.
|
|
||||||
func usedInColumns(typeName string, columns map[string]*gocql.ColumnMetadata) bool {
|
|
||||||
for _, column := range columns {
|
|
||||||
if typeName == column.Type {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
matches := userTypes.FindAllStringSubmatch(column.Type, -1)
|
|
||||||
for _, s := range matches {
|
|
||||||
if s[1] == typeName {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// usedInTables tests whether the typeName is used in any of columns of the
|
|
||||||
// provided tables.
|
|
||||||
func usedInTables(typeName string, tables map[string]*gocql.TableMetadata) bool {
|
|
||||||
for _, table := range tables {
|
|
||||||
if usedInColumns(typeName, table.Columns) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// usedInViews tests whether the typeName is used in any of columns of the
|
|
||||||
// provided views.
|
|
||||||
func usedInViews(typeName string, tables map[string]*gocql.ViewMetadata) bool {
|
|
||||||
for _, table := range tables {
|
|
||||||
if usedInColumns(typeName, table.Columns) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// usedInIndices tests whether the typeName is used in any of columns of the
|
|
||||||
// provided indices.
|
|
||||||
func usedInIndices(typeName string, tables map[string]*gocql.IndexMetadata) bool {
|
|
||||||
for _, table := range tables {
|
|
||||||
if usedInColumns(typeName, table.Columns) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3/gocqlxtest"
|
|
||||||
)
|
|
||||||
|
|
||||||
var flagUpdate = flag.Bool("update", false, "update golden file")
|
|
||||||
|
|
||||||
func TestSchemagen(t *testing.T) {
|
|
||||||
flag.Parse()
|
|
||||||
createTestSchema(t)
|
|
||||||
|
|
||||||
// add ignored types and table
|
|
||||||
*flagIgnoreNames = strings.Join([]string{
|
|
||||||
"composers",
|
|
||||||
"composers_by_name",
|
|
||||||
"label",
|
|
||||||
}, ",")
|
|
||||||
|
|
||||||
// NOTE Only this generated models is used in real tests.
|
|
||||||
t.Run("IgnoreIndexes", func(t *testing.T) {
|
|
||||||
*flagIgnoreIndexes = true
|
|
||||||
b := runSchemagen(t, "schemagentest")
|
|
||||||
assertDiff(t, b, "testdata/models.go")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("NoIgnoreIndexes", func(t *testing.T) {
|
|
||||||
*flagIgnoreIndexes = false
|
|
||||||
b := runSchemagen(t, "schemagentest")
|
|
||||||
assertDiff(t, b, "testdata/no_ignore_indexes/models.go")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_usedInTables(t *testing.T) {
|
|
||||||
tests := map[string]struct {
|
|
||||||
columnValidator string
|
|
||||||
typeName string
|
|
||||||
}{
|
|
||||||
"matches given a frozen collection": {
|
|
||||||
columnValidator: "frozen<album>",
|
|
||||||
typeName: "album",
|
|
||||||
},
|
|
||||||
"matches given a set": {
|
|
||||||
columnValidator: "set<artist>",
|
|
||||||
typeName: "artist",
|
|
||||||
},
|
|
||||||
"matches given a list": {
|
|
||||||
columnValidator: "list<song>",
|
|
||||||
typeName: "song",
|
|
||||||
},
|
|
||||||
"matches given a tuple: first of two elements": {
|
|
||||||
columnValidator: "tuple<first, second>",
|
|
||||||
typeName: "first",
|
|
||||||
},
|
|
||||||
"matches given a tuple: second of two elements": {
|
|
||||||
columnValidator: "tuple<first, second>",
|
|
||||||
typeName: "second",
|
|
||||||
},
|
|
||||||
"matches given a tuple: first of three elements": {
|
|
||||||
columnValidator: "tuple<first, second, third>",
|
|
||||||
typeName: "first",
|
|
||||||
},
|
|
||||||
"matches given a tuple: second of three elements": {
|
|
||||||
columnValidator: "tuple<first, second, third>",
|
|
||||||
typeName: "second",
|
|
||||||
},
|
|
||||||
"matches given a tuple: third of three elements": {
|
|
||||||
columnValidator: "tuple<first, second, third>",
|
|
||||||
typeName: "third",
|
|
||||||
},
|
|
||||||
"matches given a frozen set": {
|
|
||||||
columnValidator: "set<frozen<album>>",
|
|
||||||
typeName: "album",
|
|
||||||
},
|
|
||||||
"matches snake_case names given a nested map": {
|
|
||||||
columnValidator: "map<album, tuple<first, map<map_key, map-value>, third>>",
|
|
||||||
typeName: "map_key",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tt := range tests {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
tables := map[string]*gocql.TableMetadata{
|
|
||||||
"table": {Columns: map[string]*gocql.ColumnMetadata{
|
|
||||||
"column": {Type: tt.columnValidator},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
if !usedInTables(tt.typeName, tables) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("doesn't panic with empty type name", func(t *testing.T) {
|
|
||||||
tables := map[string]*gocql.TableMetadata{
|
|
||||||
"table": {Columns: map[string]*gocql.ColumnMetadata{
|
|
||||||
"column": {Type: "map<text, album>"},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
usedInTables("", tables)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertDiff(t *testing.T, actual []byte, goldenFile string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if *flagUpdate {
|
|
||||||
if err := os.WriteFile(goldenFile, actual, os.ModePerm); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
golden, err := os.ReadFile(goldenFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(string(golden), string(actual)); diff != "" {
|
|
||||||
t.Fatal(diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTestSchema(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
session := gocqlxtest.CreateSession(t)
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS schemagen WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("create keyspace:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS schemagen.songs (
|
|
||||||
id uuid PRIMARY KEY,
|
|
||||||
title text,
|
|
||||||
album text,
|
|
||||||
artist text,
|
|
||||||
duration duration,
|
|
||||||
tags set<text>,
|
|
||||||
data blob)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("create table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.ExecStmt(`CREATE TYPE IF NOT EXISTS schemagen.album (
|
|
||||||
name text,
|
|
||||||
songwriters set<text>,)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("create type:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS schemagen.playlists (
|
|
||||||
id uuid,
|
|
||||||
title text,
|
|
||||||
album frozen<album>,
|
|
||||||
artist text,
|
|
||||||
song_id uuid,
|
|
||||||
PRIMARY KEY (id, title, album, artist))`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("create table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.ExecStmt(`CREATE INDEX IF NOT EXISTS songs_title ON schemagen.songs (title)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("create index:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS schemagen.composers (
|
|
||||||
id uuid PRIMARY KEY,
|
|
||||||
name text)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("create table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.ExecStmt(`CREATE MATERIALIZED VIEW IF NOT EXISTS schemagen.composers_by_name AS
|
|
||||||
SELECT id, name
|
|
||||||
FROM composers
|
|
||||||
WHERE id IS NOT NULL AND name IS NOT NULL
|
|
||||||
PRIMARY KEY (id, name)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("create view:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.ExecStmt(`CREATE TYPE IF NOT EXISTS schemagen.label (
|
|
||||||
name text,
|
|
||||||
artists set<text>)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("create type:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSchemagen(t *testing.T, pkgname string) []byte {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
dir, err := os.MkdirTemp("", "gocqlx")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
keyspace := "schemagen"
|
|
||||||
cl := "127.0.1.1"
|
|
||||||
|
|
||||||
flagCluster = &cl
|
|
||||||
flagKeyspace = &keyspace
|
|
||||||
flagPkgname = &pkgname
|
|
||||||
flagOutput = &dir
|
|
||||||
|
|
||||||
if err := schemagen(); err != nil {
|
|
||||||
t.Fatalf("schemagen() error %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f := fmt.Sprintf("%s/%s.go", dir, pkgname)
|
|
||||||
b, err := os.ReadFile(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s: %s", f, err)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
21
cmd/schemagen/testdata/go.mod
vendored
21
cmd/schemagen/testdata/go.mod
vendored
@@ -1,21 +0,0 @@
|
|||||||
module schemagentest
|
|
||||||
|
|
||||||
go 1.25.0
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/gocql/gocql v1.7.0
|
|
||||||
github.com/google/go-cmp v0.7.0
|
|
||||||
github.com/scylladb/gocqlx/v3 v3.0.4
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/klauspost/compress v1.18.1 // indirect
|
|
||||||
github.com/scylladb/go-reflectx v1.0.1 // indirect
|
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
replace (
|
|
||||||
github.com/gocql/gocql => github.com/scylladb/gocql v1.17.0
|
|
||||||
github.com/scylladb/gocqlx/v3 => ../../..
|
|
||||||
)
|
|
||||||
30
cmd/schemagen/testdata/go.sum
vendored
30
cmd/schemagen/testdata/go.sum
vendored
@@ -1,30 +0,0 @@
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
|
||||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/scylladb/go-reflectx v1.0.1 h1:b917wZM7189pZdlND9PbIJ6NQxfDPfBvUaQ7cjj1iZQ=
|
|
||||||
github.com/scylladb/go-reflectx v1.0.1/go.mod h1:rWnOfDIRWBGN0miMLIcoPt/Dhi2doCMZqwMCJ3KupFc=
|
|
||||||
github.com/scylladb/gocql v1.16.1 h1:mxqUoOoHPrhzBNN+S0X195N+wCPZ5nrstfFz4QtBaZs=
|
|
||||||
github.com/scylladb/gocql v1.16.1/go.mod h1:MSg2nr90XMcU0doVnISX3OtarTac5tSCGk6Q6QJd6oQ=
|
|
||||||
github.com/scylladb/gocql v1.17.0 h1:sSjNTgSoC90+1XYXOMeWsQ8+AZbFYQWcspuScmUT53E=
|
|
||||||
github.com/scylladb/gocql v1.17.0/go.mod h1:0VgVuYnAPOoYN17KXkYdWDxhL2/rH3V3vOisPMngpAw=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
|
||||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
|
||||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
|
||||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
|
||||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
|
||||||
73
cmd/schemagen/testdata/models.go
vendored
73
cmd/schemagen/testdata/models.go
vendored
@@ -1,73 +0,0 @@
|
|||||||
// Code generated by "gocqlx/cmd/schemagen"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package schemagentest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gocql/gocql"
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
"github.com/scylladb/gocqlx/v3/table"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table models.
|
|
||||||
var (
|
|
||||||
Playlists = table.New(table.Metadata{
|
|
||||||
Name: "playlists",
|
|
||||||
Columns: []string{
|
|
||||||
"album",
|
|
||||||
"artist",
|
|
||||||
"id",
|
|
||||||
"song_id",
|
|
||||||
"title",
|
|
||||||
},
|
|
||||||
PartKey: []string{
|
|
||||||
"id",
|
|
||||||
},
|
|
||||||
SortKey: []string{
|
|
||||||
"title",
|
|
||||||
"album",
|
|
||||||
"artist",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
Songs = table.New(table.Metadata{
|
|
||||||
Name: "songs",
|
|
||||||
Columns: []string{
|
|
||||||
"album",
|
|
||||||
"artist",
|
|
||||||
"data",
|
|
||||||
"duration",
|
|
||||||
"id",
|
|
||||||
"tags",
|
|
||||||
"title",
|
|
||||||
},
|
|
||||||
PartKey: []string{
|
|
||||||
"id",
|
|
||||||
},
|
|
||||||
SortKey: []string{},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// User-defined types (UDT) structs.
|
|
||||||
type AlbumUserType struct {
|
|
||||||
gocqlx.UDT
|
|
||||||
Name string
|
|
||||||
Songwriters []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table structs.
|
|
||||||
type PlaylistsStruct struct {
|
|
||||||
Album AlbumUserType
|
|
||||||
Artist string
|
|
||||||
Id [16]byte
|
|
||||||
SongId [16]byte
|
|
||||||
Title string
|
|
||||||
}
|
|
||||||
type SongsStruct struct {
|
|
||||||
Album string
|
|
||||||
Artist string
|
|
||||||
Data []byte
|
|
||||||
Duration gocql.Duration
|
|
||||||
Id [16]byte
|
|
||||||
Tags []string
|
|
||||||
Title string
|
|
||||||
}
|
|
||||||
92
cmd/schemagen/testdata/models_test.go
vendored
92
cmd/schemagen/testdata/models_test.go
vendored
@@ -1,92 +0,0 @@
|
|||||||
package schemagentest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
"github.com/scylladb/gocqlx/v3/qb"
|
|
||||||
)
|
|
||||||
|
|
||||||
var flagCluster = flag.String("cluster", "127.0.0.1", "a comma-separated list of host:port or host tuples")
|
|
||||||
|
|
||||||
func TestModelLoad(t *testing.T) {
|
|
||||||
session, err := gocqlx.WrapSession(gocql.NewCluster(strings.Split(*flagCluster, ",")...).CreateSession())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("create session:", err.Error())
|
|
||||||
}
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
// Keyspace, types and table are created at `schemaget_test.go` at `createTestSchema`
|
|
||||||
|
|
||||||
song := SongsStruct{
|
|
||||||
Id: gocql.TimeUUID(),
|
|
||||||
Title: "title",
|
|
||||||
Album: "album",
|
|
||||||
Artist: "artist",
|
|
||||||
Duration: gocql.Duration{Nanoseconds: int64(5 * time.Minute)},
|
|
||||||
Tags: []string{"tag1", "tag2"},
|
|
||||||
Data: []byte("data"),
|
|
||||||
}
|
|
||||||
|
|
||||||
err = qb.Insert("schemagen.songs").
|
|
||||||
Columns("id", "title", "album", "artist", "duration", "tags", "data").
|
|
||||||
Query(session).
|
|
||||||
BindStruct(&song).
|
|
||||||
Exec()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to insert song:", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedSong := SongsStruct{}
|
|
||||||
err = qb.Select("schemagen.songs").
|
|
||||||
Columns("id", "title", "album", "artist", "duration", "tags", "data").
|
|
||||||
Where(qb.Eq("id")).
|
|
||||||
Query(session).
|
|
||||||
BindMap(map[string]interface{}{"id": song.Id}).
|
|
||||||
Get(&loadedSong)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to select song:", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(song, loadedSong); diff != "" {
|
|
||||||
t.Error("loaded song is different from inserted song:", diff)
|
|
||||||
}
|
|
||||||
|
|
||||||
pl := PlaylistsStruct{
|
|
||||||
Id: gocql.TimeUUID(),
|
|
||||||
Title: "title",
|
|
||||||
Album: AlbumUserType{Name: "album", Songwriters: []string{"songwriter1", "songwriter2"}},
|
|
||||||
Artist: "artist",
|
|
||||||
SongId: gocql.TimeUUID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
err = qb.Insert("schemagen.playlists").
|
|
||||||
Columns("id", "title", "album", "artist", "song_id").
|
|
||||||
Query(session).
|
|
||||||
BindStruct(&pl).
|
|
||||||
Exec()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to insert playlist:", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedPl := PlaylistsStruct{}
|
|
||||||
|
|
||||||
err = qb.Select("schemagen.playlists").
|
|
||||||
Columns("id", "title", "album", "artist", "song_id").
|
|
||||||
Where(qb.Eq("id")).
|
|
||||||
Query(session).
|
|
||||||
BindMap(map[string]interface{}{"id": pl.Id}).
|
|
||||||
Get(&loadedPl)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to select playlist:", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(pl, loadedPl); diff != "" {
|
|
||||||
t.Error("loaded playlist is different from inserted song:", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
// Code generated by "gocqlx/cmd/schemagen"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package schemagentest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gocql/gocql"
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
"github.com/scylladb/gocqlx/v3/table"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table models.
|
|
||||||
var (
|
|
||||||
Playlists = table.New(table.Metadata{
|
|
||||||
Name: "playlists",
|
|
||||||
Columns: []string{
|
|
||||||
"album",
|
|
||||||
"artist",
|
|
||||||
"id",
|
|
||||||
"song_id",
|
|
||||||
"title",
|
|
||||||
},
|
|
||||||
PartKey: []string{
|
|
||||||
"id",
|
|
||||||
},
|
|
||||||
SortKey: []string{
|
|
||||||
"title",
|
|
||||||
"album",
|
|
||||||
"artist",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
Songs = table.New(table.Metadata{
|
|
||||||
Name: "songs",
|
|
||||||
Columns: []string{
|
|
||||||
"album",
|
|
||||||
"artist",
|
|
||||||
"data",
|
|
||||||
"duration",
|
|
||||||
"id",
|
|
||||||
"tags",
|
|
||||||
"title",
|
|
||||||
},
|
|
||||||
PartKey: []string{
|
|
||||||
"id",
|
|
||||||
},
|
|
||||||
SortKey: []string{},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// Index models.
|
|
||||||
var (
|
|
||||||
SongsTitleIndex = table.New(table.Metadata{
|
|
||||||
Name: "songs_title_index",
|
|
||||||
Columns: []string{
|
|
||||||
"id",
|
|
||||||
"idx_token",
|
|
||||||
"title",
|
|
||||||
},
|
|
||||||
PartKey: []string{
|
|
||||||
"title",
|
|
||||||
},
|
|
||||||
SortKey: []string{
|
|
||||||
"idx_token",
|
|
||||||
"id",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// User-defined types (UDT) structs.
|
|
||||||
type AlbumUserType struct {
|
|
||||||
gocqlx.UDT
|
|
||||||
Name string
|
|
||||||
Songwriters []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table structs.
|
|
||||||
type PlaylistsStruct struct {
|
|
||||||
Album AlbumUserType
|
|
||||||
Artist string
|
|
||||||
Id [16]byte
|
|
||||||
SongId [16]byte
|
|
||||||
Title string
|
|
||||||
}
|
|
||||||
type SongsStruct struct {
|
|
||||||
Album string
|
|
||||||
Artist string
|
|
||||||
Data []byte
|
|
||||||
Duration gocql.Duration
|
|
||||||
Id [16]byte
|
|
||||||
Tags []string
|
|
||||||
Title string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index structs.
|
|
||||||
type SongsTitleIndexStruct struct {
|
|
||||||
Id [16]byte
|
|
||||||
IdxToken int64
|
|
||||||
Title string
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package dbutil provides various utilities built on top of core gocqlx modules.
|
|
||||||
package dbutil
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package dbutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
"github.com/scylladb/gocqlx/v3/table"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RewriteTable rewrites src table to dst table.
|
|
||||||
// Rows can be transformed using the transform function.
|
|
||||||
// If row map is empty after transformation the row is skipped.
|
|
||||||
// Additional options can be passed to modify the insert query.
|
|
||||||
func RewriteTable(session gocqlx.Session, dst, src *table.Table, transform func(map[string]interface{}), options ...func(q *gocqlx.Queryx)) error {
|
|
||||||
insert := dst.InsertQuery(session)
|
|
||||||
defer insert.Release()
|
|
||||||
|
|
||||||
// Apply query options
|
|
||||||
for _, o := range options {
|
|
||||||
o(insert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over all rows and reinsert them to dst table
|
|
||||||
iter := session.Query(src.SelectAll()).Iter()
|
|
||||||
m := make(map[string]interface{})
|
|
||||||
for iter.MapScan(m) {
|
|
||||||
if transform != nil {
|
|
||||||
transform(m)
|
|
||||||
}
|
|
||||||
if len(m) == 0 {
|
|
||||||
continue // map is empty - no need to clean
|
|
||||||
}
|
|
||||||
if err := insert.BindMap(m).Exec(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
return iter.Close()
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build all || integration
|
|
||||||
// +build all integration
|
|
||||||
|
|
||||||
package dbutil_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3/dbutil"
|
|
||||||
"github.com/scylladb/gocqlx/v3/gocqlxtest"
|
|
||||||
"github.com/scylladb/gocqlx/v3/qb"
|
|
||||||
"github.com/scylladb/gocqlx/v3/table"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRewriteTableTTL(t *testing.T) {
|
|
||||||
session := gocqlxtest.CreateSession(t)
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.rewrite_table (testtext text PRIMARY KEY)`); err != nil {
|
|
||||||
t.Fatal("create table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tbl := table.New(table.Metadata{
|
|
||||||
Name: "gocqlx_test.rewrite_table",
|
|
||||||
Columns: []string{"testtext"},
|
|
||||||
PartKey: []string{"testtext"},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Insert data with 500ms TTL
|
|
||||||
q := tbl.InsertBuilder().TTL(500 * time.Millisecond).Query(session)
|
|
||||||
if err := q.Bind("a").Exec(); err != nil {
|
|
||||||
t.Fatal("insert:", err)
|
|
||||||
}
|
|
||||||
if err := q.Bind("b").Exec(); err != nil {
|
|
||||||
t.Fatal("insert:", err)
|
|
||||||
}
|
|
||||||
if err := q.Bind("c").Exec(); err != nil {
|
|
||||||
t.Fatal("insert:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rewrite data without TTL
|
|
||||||
if err := dbutil.RewriteTable(session, tbl, tbl, nil); err != nil {
|
|
||||||
t.Fatal("rewrite:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait and check if data persisted
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
|
|
||||||
var n int
|
|
||||||
if err := qb.Select(tbl.Name()).CountAll().Query(session).Scan(&n); err != nil {
|
|
||||||
t.Fatal("scan:", err)
|
|
||||||
}
|
|
||||||
if n != 3 {
|
|
||||||
t.Fatal("expected 3 entries")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRewriteTableClone(t *testing.T) {
|
|
||||||
session := gocqlxtest.CreateSession(t)
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.rewrite_table_clone_src (testtext text PRIMARY KEY, testint int)`); err != nil {
|
|
||||||
t.Fatal("create table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
src := table.New(table.Metadata{
|
|
||||||
Name: "gocqlx_test.rewrite_table_clone_src",
|
|
||||||
Columns: []string{"testtext", "testint"},
|
|
||||||
PartKey: []string{"testtext"},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.rewrite_table_clone_dst (testtext text PRIMARY KEY, testfloat float)`); err != nil {
|
|
||||||
t.Fatal("create table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := table.New(table.Metadata{
|
|
||||||
Name: "gocqlx_test.rewrite_table_clone_dst",
|
|
||||||
Columns: []string{"testtext", "testfloat"},
|
|
||||||
PartKey: []string{"testtext"},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Insert data
|
|
||||||
q := src.InsertBuilder().Query(session)
|
|
||||||
if err := q.Bind("a", 1).Exec(); err != nil {
|
|
||||||
t.Fatal("insert:", err)
|
|
||||||
}
|
|
||||||
if err := q.Bind("b", 2).Exec(); err != nil {
|
|
||||||
t.Fatal("insert:", err)
|
|
||||||
}
|
|
||||||
if err := q.Bind("c", 3).Exec(); err != nil {
|
|
||||||
t.Fatal("insert:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
transformer := func(m map[string]interface{}) {
|
|
||||||
m["testfloat"] = float32(m["testint"].(int))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rewrite data
|
|
||||||
if err := dbutil.RewriteTable(session, dst, src, transformer); err != nil {
|
|
||||||
t.Fatal("rewrite:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var n int
|
|
||||||
if err := qb.Select(dst.Name()).CountAll().Query(session).Scan(&n); err != nil {
|
|
||||||
t.Fatal("scan:", err)
|
|
||||||
}
|
|
||||||
if n != 3 {
|
|
||||||
t.Fatal("expected 3 entries")
|
|
||||||
}
|
|
||||||
var f float32
|
|
||||||
if err := dst.GetQuery(session, "testfloat").Bind("a").Scan(&f); err != nil {
|
|
||||||
t.Fatal("scan:", err)
|
|
||||||
}
|
|
||||||
if f != 1 {
|
|
||||||
t.Fatal("expected 1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
package gocqlx_test
|
package gocqlx_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
"github.com/scylladb/gocqlx/v3"
|
||||||
"github.com/scylladb/gocqlx/v3/qb"
|
"github.com/scylladb/gocqlx/v3/qb"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"gopkg.in/inf.v0"
|
"gopkg.in/inf.v0"
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ func basicCreateAndPopulateKeyspace(t *testing.T, session gocqlx.Session, keyspa
|
|||||||
err = session.ExecStmt(fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s.playlists (
|
err = session.ExecStmt(fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s.playlists (
|
||||||
id uuid,
|
id uuid,
|
||||||
title text,
|
title text,
|
||||||
album text,
|
album text,
|
||||||
artist text,
|
artist text,
|
||||||
song_id uuid,
|
song_id uuid,
|
||||||
PRIMARY KEY (id, title, album, artist))`, keyspace))
|
PRIMARY KEY (id, title, album, artist))`, keyspace))
|
||||||
@@ -752,8 +752,8 @@ func pagingEfficientFullTableScan(t *testing.T, session gocqlx.Session) {
|
|||||||
|
|
||||||
// worker queries a token ranges generated by sequencer
|
// worker queries a token ranges generated by sequencer
|
||||||
worker := func() error {
|
worker := func() error {
|
||||||
const cql = `SELECT * FROM examples.paging_efficient_full_table_scan WHERE
|
const cql = `SELECT * FROM examples.paging_efficient_full_table_scan WHERE
|
||||||
token(user_id) >= :start AND
|
token(user_id) >= :start AND
|
||||||
token(user_id) < :end`
|
token(user_id) < :end`
|
||||||
|
|
||||||
stmt, names, err := gocqlx.CompileNamedQueryString(cql)
|
stmt, names, err := gocqlx.CompileNamedQueryString(cql)
|
||||||
|
|||||||
7
go.mod
7
go.mod
@@ -3,7 +3,7 @@ module github.com/scylladb/gocqlx/v3
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gocql/gocql v1.7.0
|
github.com/apache/cassandra-gocql-driver/v2 v2.0.0
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/psanford/memfs v0.0.0-20241019191636-4ef911798f9b
|
github.com/psanford/memfs v0.0.0-20241019191636-4ef911798f9b
|
||||||
github.com/scylladb/go-reflectx v1.0.1
|
github.com/scylladb/go-reflectx v1.0.1
|
||||||
@@ -11,9 +11,6 @@ require (
|
|||||||
gopkg.in/inf.v0 v0.9.1
|
gopkg.in/inf.v0 v0.9.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/klauspost/compress v1.18.1 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
replace github.com/gocql/gocql => github.com/scylladb/gocql v1.17.0
|
replace github.com/gocql/gocql => github.com/scylladb/gocql v1.17.0
|
||||||
|
|||||||
43
go.sum
43
go.sum
@@ -1,45 +1,38 @@
|
|||||||
github.com/bitly/go-hostpool v0.1.1 h1:SsovT4BFqgJQBAESkk2QgeeL7bqKq9oJie8JnD00R+Q=
|
github.com/apache/cassandra-gocql-driver/v2 v2.0.0 h1:Omnzb1Z/P90Dr2TbVNu54ICQL7TKVIIsJO231w484HU=
|
||||||
github.com/bitly/go-hostpool v0.1.1/go.mod h1:iwXQOF7+y3cO8vituSqGpBYf02TYTzxK4S2c4rf4cJs=
|
github.com/apache/cassandra-gocql-driver/v2 v2.0.0/go.mod h1:QH/asJjB3mHvY6Dot6ZKMMpTcOrWJ8i9GhsvG1g0PK4=
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||||
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
github.com/pierrec/lz4/v4 v4.1.8 h1:ieHkV+i2BRzngO4Wd/3HGowuZStgq6QkPsD1eolNAO4=
|
||||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/psanford/memfs v0.0.0-20241019191636-4ef911798f9b h1:xzjEJAHum+mV5Dd5KyohRlCyP03o4yq6vNpEUtAJQzI=
|
github.com/psanford/memfs v0.0.0-20241019191636-4ef911798f9b h1:xzjEJAHum+mV5Dd5KyohRlCyP03o4yq6vNpEUtAJQzI=
|
||||||
github.com/psanford/memfs v0.0.0-20241019191636-4ef911798f9b/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI=
|
github.com/psanford/memfs v0.0.0-20241019191636-4ef911798f9b/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI=
|
||||||
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/scylladb/go-reflectx v1.0.1 h1:b917wZM7189pZdlND9PbIJ6NQxfDPfBvUaQ7cjj1iZQ=
|
github.com/scylladb/go-reflectx v1.0.1 h1:b917wZM7189pZdlND9PbIJ6NQxfDPfBvUaQ7cjj1iZQ=
|
||||||
github.com/scylladb/go-reflectx v1.0.1/go.mod h1:rWnOfDIRWBGN0miMLIcoPt/Dhi2doCMZqwMCJ3KupFc=
|
github.com/scylladb/go-reflectx v1.0.1/go.mod h1:rWnOfDIRWBGN0miMLIcoPt/Dhi2doCMZqwMCJ3KupFc=
|
||||||
github.com/scylladb/gocql v1.16.1 h1:mxqUoOoHPrhzBNN+S0X195N+wCPZ5nrstfFz4QtBaZs=
|
|
||||||
github.com/scylladb/gocql v1.16.1/go.mod h1:MSg2nr90XMcU0doVnISX3OtarTac5tSCGk6Q6QJd6oQ=
|
|
||||||
github.com/scylladb/gocql v1.17.0 h1:sSjNTgSoC90+1XYXOMeWsQ8+AZbFYQWcspuScmUT53E=
|
|
||||||
github.com/scylladb/gocql v1.17.0/go.mod h1:0VgVuYnAPOoYN17KXkYdWDxhL2/rH3V3vOisPMngpAw=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
|
||||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
"github.com/scylladb/go-reflectx"
|
"github.com/scylladb/go-reflectx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package gocqlxtest provides test helpers for integration tests.
|
|
||||||
package gocqlxtest
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gocqlxtest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagCluster = flag.String("cluster", "127.0.0.1", "a comma-separated list of host:port tuples")
|
|
||||||
flagKeyspace = flag.String("keyspace", "gocqlx_test", "keyspace name")
|
|
||||||
flagProto = flag.Int("proto", 0, "protcol version")
|
|
||||||
flagCQL = flag.String("cql", "3.0.0", "CQL version")
|
|
||||||
flagRF = flag.Int("rf", 1, "replication factor for test keyspace")
|
|
||||||
flagRetry = flag.Int("retries", 5, "number of times to retry queries")
|
|
||||||
flagCompressTest = flag.String("compressor", "", "compressor to use")
|
|
||||||
flagTimeout = flag.Duration("gocql.timeout", 5*time.Second, "sets the connection `timeout` for all operations")
|
|
||||||
)
|
|
||||||
|
|
||||||
var initOnce sync.Once
|
|
||||||
|
|
||||||
// CreateSession creates a new gocqlx session from flags.
|
|
||||||
func CreateSession(tb testing.TB) gocqlx.Session {
|
|
||||||
tb.Helper()
|
|
||||||
|
|
||||||
cluster := CreateCluster()
|
|
||||||
return createSessionFromCluster(tb, cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateCluster creates gocql ClusterConfig from flags.
|
|
||||||
func CreateCluster() *gocql.ClusterConfig {
|
|
||||||
if !flag.Parsed() {
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
clusterHosts := strings.Split(*flagCluster, ",")
|
|
||||||
|
|
||||||
cluster := gocql.NewCluster(clusterHosts...)
|
|
||||||
cluster.ProtoVersion = *flagProto
|
|
||||||
cluster.CQLVersion = *flagCQL
|
|
||||||
cluster.Timeout = *flagTimeout
|
|
||||||
cluster.Consistency = gocql.Quorum
|
|
||||||
cluster.MaxWaitSchemaAgreement = 2 * time.Minute // travis might be slow
|
|
||||||
cluster.ReconnectionPolicy = &gocql.ConstantReconnectionPolicy{
|
|
||||||
MaxRetries: 10,
|
|
||||||
Interval: 3 * time.Second,
|
|
||||||
}
|
|
||||||
if *flagRetry > 0 {
|
|
||||||
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: *flagRetry}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch *flagCompressTest {
|
|
||||||
case "snappy":
|
|
||||||
cluster.Compressor = &gocql.SnappyCompressor{}
|
|
||||||
case "":
|
|
||||||
default:
|
|
||||||
panic("invalid compressor: " + *flagCompressTest)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cluster
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateKeyspace creates keyspace with SimpleStrategy and RF derived from flags.
|
|
||||||
func CreateKeyspace(cluster *gocql.ClusterConfig, keyspace string) error {
|
|
||||||
c := *cluster
|
|
||||||
c.Keyspace = "system"
|
|
||||||
c.Timeout = 30 * time.Second
|
|
||||||
|
|
||||||
session, err := gocqlx.WrapSession(c.CreateSession())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
{
|
|
||||||
err := session.ExecStmt(`DROP KEYSPACE IF EXISTS ` + keyspace)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("drop keyspace: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
err := session.ExecStmt(fmt.Sprintf(`CREATE KEYSPACE %s WITH replication = {'class' : 'SimpleStrategy', 'replication_factor' : %d}`, keyspace, *flagRF))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create keyspace: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createSessionFromCluster(tb testing.TB, cluster *gocql.ClusterConfig) gocqlx.Session {
|
|
||||||
tb.Helper()
|
|
||||||
if !flag.Parsed() {
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
// Drop and re-create the keyspace once. Different tests should use their own
|
|
||||||
// individual tables, but can assume that the table does not exist before.
|
|
||||||
initOnce.Do(func() {
|
|
||||||
if err := CreateKeyspace(cluster, *flagKeyspace); err != nil {
|
|
||||||
tb.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
cluster.Keyspace = *flagKeyspace
|
|
||||||
session, err := gocqlx.WrapSession(cluster.CreateSession())
|
|
||||||
if err != nil {
|
|
||||||
tb.Fatal("CreateSession:", err)
|
|
||||||
}
|
|
||||||
return session
|
|
||||||
}
|
|
||||||
2
iterx.go
2
iterx.go
@@ -9,7 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
"github.com/scylladb/go-reflectx"
|
"github.com/scylladb/go-reflectx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"gopkg.in/inf.v0"
|
"gopkg.in/inf.v0"
|
||||||
@@ -450,7 +450,7 @@ func TestIterxStruct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const insertStmt = `INSERT INTO struct_table (
|
const insertStmt = `INSERT INTO struct_table (
|
||||||
testuuid, testtimestamp, testvarchar, testbigint, testblob, testbool, testfloat, testdouble,
|
testuuid, testtimestamp, testvarchar, testbigint, testblob, testbool, testfloat, testdouble,
|
||||||
testint, testdecimal, testlist, testset, testmap, testvarint, testinet, testcustom, testudt, testptrudt
|
testint, testdecimal, testlist, testset, testmap, testvarint, testinet, testcustom, testudt, testptrudt
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
# 🚀 GocqlX Migrations
|
|
||||||
|
|
||||||
`migrate` reads migrations from a flat directory containing CQL files.
|
|
||||||
There is no imposed naming schema. Migration name is file name.
|
|
||||||
The order of migrations is the lexicographical order of file names in the directory.
|
|
||||||
You can inject execution of Go code before processing of a migration file, after processing of a migration file, or between statements in a migration file.
|
|
||||||
|
|
||||||
For details see [example](example) migration.
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package migrate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CallbackEvent specifies type of the event when calling CallbackFunc.
|
|
||||||
type CallbackEvent uint8
|
|
||||||
|
|
||||||
// enumeration of CallbackEvents
|
|
||||||
const (
|
|
||||||
BeforeMigration CallbackEvent = iota
|
|
||||||
AfterMigration
|
|
||||||
CallComment
|
|
||||||
)
|
|
||||||
|
|
||||||
// CallbackFunc enables execution of arbitrary Go code during migration.
|
|
||||||
// If error is returned the migration is aborted.
|
|
||||||
// BeforeMigration and AfterMigration are triggered before and after processing
|
|
||||||
// of each migration file respectively.
|
|
||||||
// CallComment is triggered for each comment in a form `-- CALL <name>;` (note the semicolon).
|
|
||||||
type CallbackFunc func(ctx context.Context, session gocqlx.Session, ev CallbackEvent, name string) error
|
|
||||||
|
|
||||||
// Callback is means of executing Go code during migrations.
|
|
||||||
// Use this variable to register a global callback dispatching function.
|
|
||||||
// See CallbackFunc for details.
|
|
||||||
var Callback CallbackFunc
|
|
||||||
|
|
||||||
type nameEvent struct {
|
|
||||||
name string
|
|
||||||
event CallbackEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
// CallbackRegister allows to register a handlers for an event type and a name.
|
|
||||||
// It dispatches calls to the registered handlers.
|
|
||||||
// If there is no handler registered for CallComment an error is returned.
|
|
||||||
type CallbackRegister map[nameEvent]CallbackFunc
|
|
||||||
|
|
||||||
// Add registers a callback handler.
|
|
||||||
func (r CallbackRegister) Add(ev CallbackEvent, name string, f CallbackFunc) {
|
|
||||||
r[nameEvent{name, ev}] = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find returns the registered handler.
|
|
||||||
func (r CallbackRegister) Find(ev CallbackEvent, name string) CallbackFunc {
|
|
||||||
return r[nameEvent{name, ev}]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback is CallbackFunc.
|
|
||||||
func (r CallbackRegister) Callback(ctx context.Context, session gocqlx.Session, ev CallbackEvent, name string) error {
|
|
||||||
f, ok := r[nameEvent{name, ev}]
|
|
||||||
if !ok {
|
|
||||||
if ev == CallComment {
|
|
||||||
return errors.New("missing handler")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return f(ctx, session, ev, name)
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package migrate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
var encode = hex.EncodeToString
|
|
||||||
|
|
||||||
func checksum(b []byte) string {
|
|
||||||
v := md5.Sum(b)
|
|
||||||
return encode(v[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileChecksum(f fs.FS, path string) (string, error) {
|
|
||||||
file, err := f.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = file.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
h := md5.New()
|
|
||||||
if _, err := io.Copy(h, file); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
v := h.Sum(nil)
|
|
||||||
return encode(v), nil
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package migrate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFileChecksum(t *testing.T) {
|
|
||||||
c, err := fileChecksum(os.DirFS("testdata"), "file")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if c != "bbe02f946d5455d74616fc9777557c22" {
|
|
||||||
t.Fatal(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package migrate reads migrations from a flat directory containing CQL files.
|
|
||||||
// There is no imposed naming schema. Migration name is file name.
|
|
||||||
// The order of migrations is the lexicographical order of file names in the directory.
|
|
||||||
// You can inject execution of Go code before processing of a migration file, after processing of a migration file, or between statements in a migration file.
|
|
||||||
package migrate
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build all || integration
|
|
||||||
// +build all integration
|
|
||||||
|
|
||||||
package cql
|
|
||||||
|
|
||||||
import "embed"
|
|
||||||
|
|
||||||
// Files contains *.cql schema migration files.
|
|
||||||
//
|
|
||||||
//go:embed *.cql
|
|
||||||
var Files embed.FS
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
-- Comment
|
|
||||||
|
|
||||||
CREATE TABLE bar ( id int PRIMARY KEY);
|
|
||||||
|
|
||||||
INSERT INTO bar (id) VALUES (1);
|
|
||||||
|
|
||||||
-- CALL 1;
|
|
||||||
|
|
||||||
INSERT INTO bar (id) VALUES (2);
|
|
||||||
|
|
||||||
-- CALL 2;
|
|
||||||
|
|
||||||
INSERT INTO bar (id) VALUES (3);
|
|
||||||
|
|
||||||
-- CALL 3;
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build all || integration
|
|
||||||
// +build all integration
|
|
||||||
|
|
||||||
package example
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
"github.com/scylladb/gocqlx/v3/gocqlxtest"
|
|
||||||
"github.com/scylladb/gocqlx/v3/migrate"
|
|
||||||
"github.com/scylladb/gocqlx/v3/migrate/example/cql"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Running examples locally:
|
|
||||||
// make start-scylla
|
|
||||||
// make run-examples
|
|
||||||
func TestExample(t *testing.T) {
|
|
||||||
const ks = "migrate_example"
|
|
||||||
|
|
||||||
// Create keyspace
|
|
||||||
cluster := gocqlxtest.CreateCluster()
|
|
||||||
cluster.Keyspace = ks
|
|
||||||
if err := gocqlxtest.CreateKeyspace(cluster, ks); err != nil {
|
|
||||||
t.Fatal("CreateKeyspace:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create session using the keyspace
|
|
||||||
session, err := gocqlx.WrapSession(cluster.CreateSession())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("CreateSession:", err)
|
|
||||||
}
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
// Add callback prints
|
|
||||||
log := func(ctx context.Context, session gocqlx.Session, ev migrate.CallbackEvent, name string) error {
|
|
||||||
t.Log(ev, name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
reg := migrate.CallbackRegister{}
|
|
||||||
reg.Add(migrate.BeforeMigration, "m1.cql", log)
|
|
||||||
reg.Add(migrate.AfterMigration, "m1.cql", log)
|
|
||||||
reg.Add(migrate.CallComment, "1", log)
|
|
||||||
reg.Add(migrate.CallComment, "2", log)
|
|
||||||
reg.Add(migrate.CallComment, "3", log)
|
|
||||||
migrate.Callback = reg.Callback
|
|
||||||
|
|
||||||
pending, err := migrate.Pending(context.Background(), session, cql.Files)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Pending:", err)
|
|
||||||
}
|
|
||||||
t.Log("Pending migrations:", len(pending))
|
|
||||||
|
|
||||||
// First run prints data
|
|
||||||
if err := migrate.FromFS(context.Background(), session, cql.Files); err != nil {
|
|
||||||
t.Fatal("Migrate:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second run skips the processed files
|
|
||||||
if err := migrate.FromFS(context.Background(), session, cql.Files); err != nil {
|
|
||||||
t.Fatal("Migrate:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package migrate
|
|
||||||
|
|
||||||
func IsCallback(stmt string) (name string) {
|
|
||||||
return isCallback(stmt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsComment(stmt string) bool {
|
|
||||||
return isComment(stmt)
|
|
||||||
}
|
|
||||||
@@ -1,345 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package migrate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
"github.com/scylladb/gocqlx/v3/qb"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultAwaitSchemaAgreement controls whether checking for cluster schema agreement
|
|
||||||
// is disabled or if it is checked before each file or statement is applied.
|
|
||||||
// The default is not checking before each file or statement but only once after every
|
|
||||||
// migration has been run.
|
|
||||||
var DefaultAwaitSchemaAgreement = AwaitSchemaAgreementDisabled
|
|
||||||
|
|
||||||
type awaitSchemaAgreement int
|
|
||||||
|
|
||||||
// Options for checking schema agreement.
|
|
||||||
const (
|
|
||||||
AwaitSchemaAgreementDisabled awaitSchemaAgreement = iota
|
|
||||||
AwaitSchemaAgreementBeforeEachFile
|
|
||||||
AwaitSchemaAgreementBeforeEachStatement
|
|
||||||
)
|
|
||||||
|
|
||||||
// ShouldAwait decides whether to await schema agreement for the configured DefaultAwaitSchemaAgreement option above.
|
|
||||||
func (as awaitSchemaAgreement) ShouldAwait(stage awaitSchemaAgreement) bool {
|
|
||||||
return as == stage
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
infoSchema = `CREATE TABLE IF NOT EXISTS gocqlx_migrate (
|
|
||||||
name text,
|
|
||||||
checksum text,
|
|
||||||
done int,
|
|
||||||
start_time timestamp,
|
|
||||||
end_time timestamp,
|
|
||||||
PRIMARY KEY(name)
|
|
||||||
)`
|
|
||||||
selectInfo = "SELECT * FROM gocqlx_migrate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Info contains information on migration applied on a database.
|
|
||||||
type Info struct {
|
|
||||||
StartTime time.Time
|
|
||||||
EndTime time.Time
|
|
||||||
Name string
|
|
||||||
Checksum string
|
|
||||||
Done int
|
|
||||||
}
|
|
||||||
|
|
||||||
// List provides a listing of applied migrations.
|
|
||||||
func List(ctx context.Context, session gocqlx.Session) ([]*Info, error) {
|
|
||||||
if err := ensureInfoTable(ctx, session); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
q := session.ContextQuery(ctx, selectInfo, nil)
|
|
||||||
|
|
||||||
var v []*Info
|
|
||||||
if err := q.SelectRelease(&v); err == gocql.ErrNotFound {
|
|
||||||
return nil, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(v, func(i, j int) bool {
|
|
||||||
return v[i].Name < v[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pending provides a listing of pending migrations.
|
|
||||||
func Pending(ctx context.Context, session gocqlx.Session, f fs.FS) ([]*Info, error) {
|
|
||||||
applied, err := List(ctx, session)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a set of applied migration names
|
|
||||||
appliedNames := make(map[string]struct{}, len(applied))
|
|
||||||
for _, migration := range applied {
|
|
||||||
appliedNames[migration.Name] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
fm, err := fs.Glob(f, "*.cql")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("list migrations: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pending := make([]*Info, 0)
|
|
||||||
|
|
||||||
for _, name := range fm {
|
|
||||||
baseName := filepath.Base(name)
|
|
||||||
// Check if the migration is not in the applied set
|
|
||||||
if _, exists := appliedNames[baseName]; !exists {
|
|
||||||
c, err := fileChecksum(f, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("calculate checksum for %q: %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
info := &Info{
|
|
||||||
Name: baseName,
|
|
||||||
StartTime: time.Now(),
|
|
||||||
Checksum: c,
|
|
||||||
}
|
|
||||||
|
|
||||||
pending = append(pending, info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pending, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureInfoTable(ctx context.Context, session gocqlx.Session) error {
|
|
||||||
return session.ContextQuery(ctx, infoSchema, nil).ExecRelease()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate is a wrapper around FromFS.
|
|
||||||
// It executes migrations from a directory on disk.
|
|
||||||
//
|
|
||||||
// Deprecated: use FromFS instead
|
|
||||||
func Migrate(ctx context.Context, session gocqlx.Session, dir string) error {
|
|
||||||
return FromFS(ctx, session, os.DirFS(dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromFS executes new CQL files from a file system abstraction (io/fs.FS).
|
|
||||||
// The provided FS has to be a flat directory containing *.cql files.
|
|
||||||
//
|
|
||||||
// It supports code based migrations, see Callback and CallbackFunc.
|
|
||||||
// Any comment in form `-- CALL <name>;` will trigger an CallComment callback.
|
|
||||||
func FromFS(ctx context.Context, session gocqlx.Session, f fs.FS) error {
|
|
||||||
// get database migrations
|
|
||||||
dbm, err := List(ctx, session)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("list migrations: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get file migrations
|
|
||||||
fm, err := fs.Glob(f, "*.cql")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("list migrations: %w", err)
|
|
||||||
}
|
|
||||||
if len(fm) == 0 {
|
|
||||||
return fmt.Errorf("no migration files found")
|
|
||||||
}
|
|
||||||
sort.Strings(fm)
|
|
||||||
|
|
||||||
// verify migrations
|
|
||||||
if len(dbm) > len(fm) {
|
|
||||||
return fmt.Errorf("database is ahead")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(dbm); i++ {
|
|
||||||
if dbm[i].Name != fm[i] {
|
|
||||||
return fmt.Errorf("inconsistent migrations found, expected %q got %q at %d", dbm[i].Name, fm[i], i)
|
|
||||||
}
|
|
||||||
c, err := fileChecksum(f, fm[i])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("calculate checksum for %q: %s", fm[i], err)
|
|
||||||
}
|
|
||||||
if dbm[i].Checksum != c {
|
|
||||||
return fmt.Errorf("file %q was tampered with, expected md5 %s", fm[i], dbm[i].Checksum)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply migrations
|
|
||||||
if len(dbm) > 0 {
|
|
||||||
last := len(dbm) - 1
|
|
||||||
if err := applyMigration(ctx, session, f, fm[last], dbm[last].Done); err != nil {
|
|
||||||
return fmt.Errorf("apply migration %q: %s", fm[last], err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := len(dbm); i < len(fm); i++ {
|
|
||||||
if err := applyMigration(ctx, session, f, fm[i], 0); err != nil {
|
|
||||||
return fmt.Errorf("apply migration %q: %s", fm[i], err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = session.AwaitSchemaAgreement(ctx); err != nil {
|
|
||||||
return fmt.Errorf("awaiting schema agreement: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// applyMigration executes a single migration file by parsing and applying its statements.
|
|
||||||
// It handles three types of content in migration files:
|
|
||||||
// - SQL statements: executed against the database
|
|
||||||
// - Callback commands: processed via registered callback handlers (format: -- CALL function_name;)
|
|
||||||
// - Regular comments: silently skipped (format: -- any comment text)
|
|
||||||
//
|
|
||||||
// The function maintains migration state by tracking the number of completed statements,
|
|
||||||
// allowing for resumption of partially completed migrations.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - ctx: context for cancellation and timeouts
|
|
||||||
// - session: database session for executing statements
|
|
||||||
// - f: filesystem containing the migration file
|
|
||||||
// - path: path to the migration file within the filesystem
|
|
||||||
// - done: number of statements already completed (for resuming partial migrations)
|
|
||||||
//
|
|
||||||
// Returns an error if the migration fails at any point.
|
|
||||||
func applyMigration(ctx context.Context, session gocqlx.Session, f fs.FS, path string, done int) error {
|
|
||||||
file, err := f.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := io.ReadAll(file)
|
|
||||||
_ = file.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
info := Info{
|
|
||||||
Name: filepath.Base(path),
|
|
||||||
StartTime: time.Now(),
|
|
||||||
Checksum: checksum(b),
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, names := qb.Insert("gocqlx_migrate").Columns(
|
|
||||||
"name",
|
|
||||||
"checksum",
|
|
||||||
"done",
|
|
||||||
"start_time",
|
|
||||||
"end_time",
|
|
||||||
).ToCql()
|
|
||||||
|
|
||||||
update := session.ContextQuery(ctx, stmt, names)
|
|
||||||
defer update.Release()
|
|
||||||
|
|
||||||
if DefaultAwaitSchemaAgreement.ShouldAwait(AwaitSchemaAgreementBeforeEachFile) {
|
|
||||||
if err = session.AwaitSchemaAgreement(ctx); err != nil {
|
|
||||||
return fmt.Errorf("awaiting schema agreement: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
r := bytes.NewBuffer(b)
|
|
||||||
for {
|
|
||||||
stmt, err := r.ReadString(';')
|
|
||||||
if err == io.EOF {
|
|
||||||
if strings.TrimSpace(stmt) != "" {
|
|
||||||
// handle missing semicolon after last statement
|
|
||||||
err = nil
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
|
|
||||||
if i <= done {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if Callback != nil && i == 1 {
|
|
||||||
if err := Callback(ctx, session, BeforeMigration, info.Name); err != nil {
|
|
||||||
return fmt.Errorf("before migration callback: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultAwaitSchemaAgreement.ShouldAwait(AwaitSchemaAgreementBeforeEachStatement) {
|
|
||||||
if err = session.AwaitSchemaAgreement(ctx); err != nil {
|
|
||||||
return fmt.Errorf("awaiting schema agreement: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// trim new lines and all whitespace characters
|
|
||||||
stmt = strings.TrimSpace(stmt)
|
|
||||||
|
|
||||||
// Process statement based on its type
|
|
||||||
if cb := isCallback(stmt); cb != "" {
|
|
||||||
// Handle callback commands (e.g., "-- CALL function_name;")
|
|
||||||
if Callback == nil {
|
|
||||||
return fmt.Errorf("statement %d: missing callback handler while trying to call %s", i, cb)
|
|
||||||
}
|
|
||||||
if err := Callback(ctx, session, CallComment, cb); err != nil {
|
|
||||||
return fmt.Errorf("callback %s: %s", cb, err)
|
|
||||||
}
|
|
||||||
} else if stmt != "" && !isComment(stmt) {
|
|
||||||
// Execute SQL statements (skip empty statements and comments)
|
|
||||||
q := session.ContextQuery(ctx, stmt, nil).RetryPolicy(nil)
|
|
||||||
if err := q.ExecRelease(); err != nil {
|
|
||||||
return fmt.Errorf("statement %d: %s", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Regular comments and empty statements are silently skipped
|
|
||||||
|
|
||||||
// update info
|
|
||||||
info.Done = i
|
|
||||||
info.EndTime = time.Now()
|
|
||||||
if err := update.BindStruct(info).Exec(); err != nil {
|
|
||||||
return fmt.Errorf("migration statement %d: %s", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
return fmt.Errorf("no migration statements found in %q", info.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if Callback != nil && i > done {
|
|
||||||
if err := Callback(ctx, session, AfterMigration, info.Name); err != nil {
|
|
||||||
return fmt.Errorf("after migration callback: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var cbRegexp = regexp.MustCompile("^-- *CALL +(.+);$")
|
|
||||||
|
|
||||||
func isCallback(stmt string) (name string) {
|
|
||||||
s := cbRegexp.FindStringSubmatch(stmt)
|
|
||||||
if len(s) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return s[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// isComment returns true if the statement is a SQL comment that should be ignored.
|
|
||||||
// It distinguishes between regular comments (which should be skipped) and
|
|
||||||
// callback commands (which should be processed).
|
|
||||||
func isComment(stmt string) bool {
|
|
||||||
return strings.HasPrefix(stmt, "--") && isCallback(stmt) == ""
|
|
||||||
}
|
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build all || integration
|
|
||||||
// +build all integration
|
|
||||||
|
|
||||||
package migrate_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/psanford/memfs"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
|
||||||
"github.com/scylladb/gocqlx/v3/gocqlxtest"
|
|
||||||
"github.com/scylladb/gocqlx/v3/migrate"
|
|
||||||
)
|
|
||||||
|
|
||||||
var migrateSchema = `
|
|
||||||
CREATE TABLE IF NOT EXISTS gocqlx_test.migrate_table (
|
|
||||||
testint int,
|
|
||||||
testuuid timeuuid,
|
|
||||||
PRIMARY KEY(testint, testuuid)
|
|
||||||
)
|
|
||||||
`
|
|
||||||
|
|
||||||
var insertMigrate = `INSERT INTO gocqlx_test.migrate_table (testint, testuuid) VALUES (%d, now())`
|
|
||||||
|
|
||||||
func recreateTables(tb testing.TB, session gocqlx.Session) {
|
|
||||||
tb.Helper()
|
|
||||||
|
|
||||||
if err := session.ExecStmt("DROP TABLE IF EXISTS gocqlx_test.gocqlx_migrate"); err != nil {
|
|
||||||
tb.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := session.ExecStmt(migrateSchema); err != nil {
|
|
||||||
tb.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := session.ExecStmt("TRUNCATE gocqlx_test.migrate_table"); err != nil {
|
|
||||||
tb.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPending(t *testing.T) {
|
|
||||||
session := gocqlxtest.CreateSession(t)
|
|
||||||
defer session.Close()
|
|
||||||
recreateTables(t, session)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("pending", func(t *testing.T) {
|
|
||||||
defer recreateTables(t, session)
|
|
||||||
|
|
||||||
f := memfs.New()
|
|
||||||
writeFile(t, f, 0, fmt.Sprintf(insertMigrate, 0)+";")
|
|
||||||
|
|
||||||
pending, err := migrate.Pending(ctx, session, f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(pending) != 1 {
|
|
||||||
t.Fatal("expected 2 pending migrations got", len(pending))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = migrate.FromFS(ctx, session, f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pending, err = migrate.Pending(ctx, session, f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(pending) != 0 {
|
|
||||||
t.Fatal("expected no pending migrations got", len(pending))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i < 3; i++ {
|
|
||||||
writeFile(t, f, i, fmt.Sprintf(insertMigrate, i)+";")
|
|
||||||
}
|
|
||||||
|
|
||||||
pending, err = migrate.Pending(ctx, session, f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(pending) != 2 {
|
|
||||||
t.Fatal("expected 2 pending migrations got", len(pending))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMigration(t *testing.T) {
|
|
||||||
session := gocqlxtest.CreateSession(t)
|
|
||||||
defer session.Close()
|
|
||||||
recreateTables(t, session)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("init", func(t *testing.T) {
|
|
||||||
if err := migrate.FromFS(ctx, session, makeTestFS(t, 2)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if c := countMigrations(t, session); c != 2 {
|
|
||||||
t.Fatal("expected 2 migration got", c)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("update", func(t *testing.T) {
|
|
||||||
if err := migrate.FromFS(ctx, session, makeTestFS(t, 4)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if c := countMigrations(t, session); c != 4 {
|
|
||||||
t.Fatal("expected 4 migration got", c)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ahead", func(t *testing.T) {
|
|
||||||
err := migrate.FromFS(ctx, session, makeTestFS(t, 2))
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "ahead") {
|
|
||||||
t.Fatal("expected error")
|
|
||||||
} else {
|
|
||||||
t.Log(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("tempered with file", func(t *testing.T) {
|
|
||||||
f := makeTestFS(t, 4)
|
|
||||||
writeFile(t, f, 3, "SELECT * FROM bla;")
|
|
||||||
|
|
||||||
if err := migrate.FromFS(ctx, session, f); err == nil || !strings.Contains(err.Error(), "tampered") {
|
|
||||||
t.Fatal("expected error")
|
|
||||||
} else {
|
|
||||||
t.Log(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMigrationNoSemicolon(t *testing.T) {
|
|
||||||
session := gocqlxtest.CreateSession(t)
|
|
||||||
defer session.Close()
|
|
||||||
recreateTables(t, session)
|
|
||||||
|
|
||||||
if err := session.ExecStmt(migrateSchema); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f := makeTestFS(t, 0)
|
|
||||||
err := f.WriteFile("0.cql", []byte(fmt.Sprintf(insertMigrate, 0)+";"+fmt.Sprintf(insertMigrate, 1)), fs.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
if err := migrate.FromFS(ctx, session, f); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if c := countMigrations(t, session); c != 2 {
|
|
||||||
t.Fatal("expected 2 migration got", c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMigrationWithTrailingComment(t *testing.T) {
|
|
||||||
session := gocqlxtest.CreateSession(t)
|
|
||||||
defer session.Close()
|
|
||||||
recreateTables(t, session)
|
|
||||||
|
|
||||||
if err := session.ExecStmt(migrateSchema); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f := makeTestFS(t, 0)
|
|
||||||
// Create a migration with a trailing comment (this should reproduce the issue)
|
|
||||||
migrationContent := fmt.Sprintf(insertMigrate, 0) + "; -- ttl 1 hour"
|
|
||||||
err := f.WriteFile("0.cql", []byte(migrationContent), fs.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
if err := migrate.FromFS(ctx, session, f); err != nil {
|
|
||||||
t.Fatal("Migration should succeed with trailing comment, but got error:", err)
|
|
||||||
}
|
|
||||||
if c := countMigrations(t, session); c != 1 {
|
|
||||||
t.Fatal("expected 1 migration got", c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsCallback(t *testing.T) {
|
|
||||||
table := []struct {
|
|
||||||
Name string
|
|
||||||
Stmt string
|
|
||||||
Cb string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Name: "CQL statement",
|
|
||||||
Stmt: "SELECT * from X;",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CQL comment",
|
|
||||||
Stmt: "-- Item",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CALL without space",
|
|
||||||
Stmt: "--CALL Foo;",
|
|
||||||
Cb: "Foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CALL with space",
|
|
||||||
Stmt: "-- CALL Foo;",
|
|
||||||
Cb: "Foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CALL with many spaces",
|
|
||||||
Stmt: "-- CALL Foo;",
|
|
||||||
Cb: "Foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CALL with many spaces 2",
|
|
||||||
Stmt: "-- CALL Foo;",
|
|
||||||
Cb: "Foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CALL with unicode",
|
|
||||||
Stmt: "-- CALL α;",
|
|
||||||
Cb: "α",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range table {
|
|
||||||
test := table[i]
|
|
||||||
t.Run(test.Name, func(t *testing.T) {
|
|
||||||
if migrate.IsCallback(test.Stmt) != test.Cb {
|
|
||||||
t.Errorf("IsCallback(%s)=%s, expected %s", test.Stmt, migrate.IsCallback(test.Stmt), test.Cb)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsComment(t *testing.T) {
|
|
||||||
table := []struct {
|
|
||||||
Name string
|
|
||||||
Stmt string
|
|
||||||
IsComment bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Name: "CQL statement",
|
|
||||||
Stmt: "SELECT * from X;",
|
|
||||||
IsComment: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Regular comment",
|
|
||||||
Stmt: "-- This is a comment",
|
|
||||||
IsComment: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Comment with additional text",
|
|
||||||
Stmt: "-- ttl 1 hour",
|
|
||||||
IsComment: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Callback command (not a regular comment)",
|
|
||||||
Stmt: "-- CALL Foo;",
|
|
||||||
IsComment: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Callback with spaces (not a regular comment)",
|
|
||||||
Stmt: "-- CALL Bar;",
|
|
||||||
IsComment: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Empty statement",
|
|
||||||
Stmt: "",
|
|
||||||
IsComment: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Whitespace only",
|
|
||||||
Stmt: " ",
|
|
||||||
IsComment: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range table {
|
|
||||||
test := table[i]
|
|
||||||
t.Run(test.Name, func(t *testing.T) {
|
|
||||||
result := migrate.IsComment(test.Stmt)
|
|
||||||
if result != test.IsComment {
|
|
||||||
t.Errorf("IsComment(%q) = %v, expected %v", test.Stmt, result, test.IsComment)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMigrationCallback(t *testing.T) {
|
|
||||||
var (
|
|
||||||
beforeCalled int
|
|
||||||
afterCalled int
|
|
||||||
inCalled int
|
|
||||||
)
|
|
||||||
migrate.Callback = func(ctx context.Context, session gocqlx.Session, ev migrate.CallbackEvent, name string) error {
|
|
||||||
switch ev {
|
|
||||||
case migrate.BeforeMigration:
|
|
||||||
beforeCalled++
|
|
||||||
case migrate.AfterMigration:
|
|
||||||
afterCalled++
|
|
||||||
case migrate.CallComment:
|
|
||||||
inCalled++
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
migrate.Callback = nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
reset := func() {
|
|
||||||
beforeCalled = 0
|
|
||||||
afterCalled = 0
|
|
||||||
inCalled = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
assertCallbacks := func(t *testing.T, before, afer, in int) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if beforeCalled != before {
|
|
||||||
t.Fatalf("expected %d before calls got %d", before, beforeCalled)
|
|
||||||
}
|
|
||||||
if afterCalled != afer {
|
|
||||||
t.Fatalf("expected %d after calls got %d", afer, afterCalled)
|
|
||||||
}
|
|
||||||
if inCalled != in {
|
|
||||||
t.Fatalf("expected %d in calls got %d", in, inCalled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session := gocqlxtest.CreateSession(t)
|
|
||||||
defer session.Close()
|
|
||||||
recreateTables(t, session)
|
|
||||||
|
|
||||||
if err := session.ExecStmt(migrateSchema); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("init", func(t *testing.T) {
|
|
||||||
f := makeTestFS(t, 2)
|
|
||||||
reset()
|
|
||||||
|
|
||||||
if err := migrate.FromFS(ctx, session, f); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertCallbacks(t, 2, 2, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("no duplicate calls", func(t *testing.T) {
|
|
||||||
f := makeTestFS(t, 4)
|
|
||||||
reset()
|
|
||||||
|
|
||||||
if err := migrate.FromFS(ctx, session, f); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertCallbacks(t, 2, 2, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("in calls", func(t *testing.T) {
|
|
||||||
f := makeTestFS(t, 4)
|
|
||||||
writeFile(t, f, 4, "\n-- CALL Foo;\n")
|
|
||||||
writeFile(t, f, 5, "\n-- CALL Bar;\n")
|
|
||||||
reset()
|
|
||||||
|
|
||||||
if err := migrate.FromFS(ctx, session, f); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertCallbacks(t, 2, 2, 2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func countMigrations(tb testing.TB, session gocqlx.Session) int {
|
|
||||||
tb.Helper()
|
|
||||||
|
|
||||||
var v int
|
|
||||||
if err := session.Query("SELECT COUNT(*) FROM gocqlx_test.migrate_table", nil).Get(&v); err != nil {
|
|
||||||
tb.Fatal(err)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestFS(tb testing.TB, n int) *memfs.FS {
|
|
||||||
tb.Helper()
|
|
||||||
f := memfs.New()
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
writeFile(tb, f, i, fmt.Sprintf(insertMigrate, i)+";")
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeFile(tb testing.TB, f *memfs.FS, i int, text string) {
|
|
||||||
tb.Helper()
|
|
||||||
err := f.WriteFile(fmt.Sprint(i, ".cql"), []byte(text), fs.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
tb.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
migrate/testdata/file
vendored
1
migrate/testdata/file
vendored
@@ -1 +0,0 @@
|
|||||||
file
|
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
"github.com/scylladb/go-reflectx"
|
"github.com/scylladb/go-reflectx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,6 +99,8 @@ type Queryx struct {
|
|||||||
strict bool
|
strict bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Queryx) Release() {}
|
||||||
|
|
||||||
// Query creates a new Queryx from gocql.Query using a default mapper.
|
// Query creates a new Queryx from gocql.Query using a default mapper.
|
||||||
//
|
//
|
||||||
// Deprecated: Use gocqlx.Session.Query API instead.
|
// Deprecated: Use gocqlx.Session.Query API instead.
|
||||||
@@ -151,13 +153,13 @@ func (q *Queryx) BindStructMap(arg0 interface{}, arg1 map[string]interface{}) *Q
|
|||||||
// GetRequestTimeout returns time driver waits for single server response
|
// GetRequestTimeout returns time driver waits for single server response
|
||||||
// This timeout is applied to preparing statement request and for query execution requests
|
// This timeout is applied to preparing statement request and for query execution requests
|
||||||
func (q *Queryx) GetRequestTimeout() time.Duration {
|
func (q *Queryx) GetRequestTimeout() time.Duration {
|
||||||
return q.Query.GetRequestTimeout()
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRequestTimeout sets time driver waits for server to respond
|
// SetRequestTimeout sets time driver waits for server to respond
|
||||||
// This timeout is applied to preparing statement request and for query execution requests
|
// This timeout is applied to preparing statement request and for query execution requests
|
||||||
func (q *Queryx) SetRequestTimeout(timeout time.Duration) *Queryx {
|
func (q *Queryx) SetRequestTimeout(timeout time.Duration) *Queryx {
|
||||||
q.Query.SetRequestTimeout(timeout)
|
// q.Query.SetRequestTimeout(timeout)
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ package gocqlx_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/v3"
|
"github.com/scylladb/gocqlx/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ package gocqlx
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file contains wrappers around gocql.Query that make Queryx expose the
|
// This file contains wrappers around gocql.Query that make Queryx expose the
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ package gocqlx
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
"github.com/scylladb/go-reflectx"
|
"github.com/scylladb/go-reflectx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
8002
testdata/people.json
vendored
8002
testdata/people.json
vendored
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ package gocqlx
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
gocql "github.com/apache/cassandra-gocql-driver/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transformer transforms the value of the named parameter to another value.
|
// Transformer transforms the value of the named parameter to another value.
|
||||||
|
|||||||
Reference in New Issue
Block a user