diff --git a/cmd/schemagen/keyspace.tmpl b/cmd/schemagen/keyspace.tmpl index 55e801b..9a2df9b 100644 --- a/cmd/schemagen/keyspace.tmpl +++ b/cmd/schemagen/keyspace.tmpl @@ -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}} diff --git a/cmd/schemagen/schemagen.go b/cmd/schemagen/schemagen.go index a42c5fe..10cf0f2 100644 --- a/cmd/schemagen/schemagen.go +++ b/cmd/schemagen/schemagen.go @@ -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_other_type"]] var userTypes = regexp.MustCompile(`(?:<|\s)(\w+)[>,]`) // match all types contained in set, list, tuple 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 diff --git a/cmd/schemagen/schemagen_test.go b/cmd/schemagen/schemagen_test.go index 83331ac..6ebce6c 100644 --- a/cmd/schemagen/schemagen_test.go +++ b/cmd/schemagen/schemagen_test.go @@ -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() diff --git a/cmd/schemagen/testdata/models.go b/cmd/schemagen/testdata/models.go index 657892b..a7745eb 100644 --- a/cmd/schemagen/testdata/models.go +++ b/cmd/schemagen/testdata/models.go @@ -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 -} diff --git a/cmd/schemagen/testdata/no_ignore_indexes/models.go b/cmd/schemagen/testdata/no_ignore_indexes/models.go new file mode 100644 index 0000000..e28b958 --- /dev/null +++ b/cmd/schemagen/testdata/no_ignore_indexes/models.go @@ -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 +}