Bring back index model generation (#328)

This commit is contained in:
Daniel Bershatsky
2025-06-11 18:56:33 +03:00
committed by GitHub
parent b0b3ded8da
commit 58d72b0e05
5 changed files with 245 additions and 65 deletions

View File

@@ -9,9 +9,9 @@ import (
{{- end}}
)
{{with .Tables}}
// Table models.
var (
{{with .Tables}}
{{range .}}
{{$model_name := .Name | camelize}}
{{$model_name}} = table.New(table.Metadata {
@@ -33,13 +33,13 @@ var (
},
})
{{end}}
{{end}}
)
{{end}}
{{with .Views}}
// Materialized view models.
var (
{{with .Views}}
{{range .}}
{{- range .}}
{{$model_name := .ViewName | camelize}}
{{$model_name}} = table.New(table.Metadata {
Name: "{{.ViewName}}",
@@ -60,11 +60,39 @@ var (
},
})
{{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}}
{{range .}}
// User-defined types (UDT) structs.
{{- range .}}
{{- $type_name := .Name | camelize}}
{{- $field_types := .FieldTypes}}
type {{$type_name}}UserType struct {
@@ -77,7 +105,8 @@ type {{$type_name}}UserType struct {
{{- end}}
{{with .Tables}}
{{range .}}
// Table structs.
{{- range .}}
{{- $model_name := .Name | camelize}}
type {{$model_name}}Struct struct {
{{- range .Columns}}
@@ -90,7 +119,8 @@ type {{$model_name}}Struct struct {
{{- end}}
{{with .Views}}
{{range .}}
// View structs.
{{- range .}}
{{- $model_name := .ViewName | camelize}}
type {{$model_name}}Struct struct {
{{- range .Columns}}
@@ -101,3 +131,17 @@ type {{$model_name}}Struct struct {
}
{{- 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}}

View File

@@ -98,24 +98,31 @@ func renderTemplate(md *gocql.KeyspaceMetadata) ([]byte, error) {
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{}{}
}
if *flagIgnoreIndexes {
for name := range md.Tables {
if strings.HasSuffix(name, "_index") {
ignoredNames[name] = 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) {
if !usedInTables(userTypeName, md.Tables) &&
!usedInViews(userTypeName, md.Views) &&
!usedInIndices(userTypeName, md.Indexes) {
orphanedTypes[userTypeName] = struct{}{}
}
}
@@ -142,7 +149,7 @@ func renderTemplate(md *gocql.KeyspaceMetadata) ([]byte, error) {
}
}
// Ensure that for each table and materialized view
// Ensure that for each table, view, and index
//
// 1. ordered columns are sorted alphabetically;
// 2. imports are resolves for column types.
@@ -154,12 +161,17 @@ func renderTemplate(md *gocql.KeyspaceMetadata) ([]byte, error) {
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,
}
@@ -220,19 +232,51 @@ func existsInSlice(s []string, v string) bool {
// [["<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.
// usedInTables reports 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 {
for _, column := range table.Columns {
if typeName == column.Type {
// 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
}
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

View File

@@ -25,24 +25,19 @@ func TestSchemagen(t *testing.T) {
"composers_by_name",
"label",
}, ",")
*flagIgnoreIndexes = true
b := runSchemagen(t, "schemagentest")
// 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")
})
const goldenFile = "testdata/models.go"
if *flagUpdate {
if err := os.WriteFile(goldenFile, b, 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(b)); diff != "" {
t.Fatal(diff)
}
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) {
@@ -115,6 +110,24 @@ func Test_usedInTables(t *testing.T) {
})
}
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()

View File

@@ -47,29 +47,14 @@ var (
})
)
// Materialized view models.
var (
ComposersByName = table.New(table.Metadata{
Name: "composers_by_name",
Columns: []string{
"id",
"name",
},
PartKey: []string{
"id",
},
SortKey: []string{
"name",
},
})
)
// User-defined types (UDT) structs.
type AlbumUserType struct {
gocqlx.UDT
Name string
Songwriters []string
}
// Table structs.
type PlaylistsStruct struct {
Album AlbumUserType
Artist string
@@ -86,8 +71,3 @@ type SongsStruct struct {
Tags []string
Title string
}
type ComposersByNameStruct struct {
Id [16]byte
Name string
}

View File

@@ -0,0 +1,99 @@
// 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
}