Skip to content

Instantly share code, notes, and snippets.

@caseylmanus
Created March 26, 2025 01:29
Show Gist options
  • Save caseylmanus/ded4273875fc262f5f09280b84f4bee1 to your computer and use it in GitHub Desktop.
Save caseylmanus/ded4273875fc262f5f09280b84f4bee1 to your computer and use it in GitHub Desktop.
1. descriptorTemplate:const descriptorTemplate = `
package generated
import "fmt"
{{$allTables := .Tables}}
{{$allColumns := .Columns}}
{{$allIndexes := .Indexes}}
// TableDescriptor describes a database table.
type TableDescriptor struct {
TableName string
Indexes []string // Add this line
}
// ColumnDescriptor describes a column in a database table.
type ColumnDescriptor[T any] struct {
ColumnName string
ParentTable string
IndexName string // Add this line
}
// TableDescriptors maps table names to their descriptors.
var TableDescriptors = map[string]TableDescriptor{
{{range $table := $allTables}}
"{{$table.TableName}}": {
TableName: "{{$table.TableName}}",
Indexes: []string{ // Initialize the Indexes field
{{range $index := $allIndexes}}
{{if eq $index.TableName $table.TableName}}
"{{$index.IndexName}}",
{{end}}
{{end}}
},
},
{{end}}
}
{{range $table := $allTables}}
// {{$table.TableName}}TableDescriptor provides access to {{$table.TableName}} table columns.
type {{$table.TableName}}TableDescriptor struct {
}
// {{$table.TableName}} provides access to {{$table.TableName}} table columns.
func {{$table.TableName}}() {{$table.TableName}}TableDescriptor {
return {{$table.TableName}}TableDescriptor{}
}
{{range $col := $allColumns}}
{{if eq $col.TableName $table.TableName}}
// {{$col.ColumnName}} returns a ColumnDescriptor for the {{$col.ColumnName}} column.
func ({{$table.TableName}}TableDescriptor) {{$col.ColumnName}}() ColumnDescriptor[{{$col.DataType}}] {
return ColumnDescriptor[{{$col.DataType}}]{
ColumnName: "{{$col.ColumnName}}",
ParentTable: "{{$table.TableName}}",
IndexName: "{{$col.IndexName}}", // Set the IndexName
}
}
{{end}}
{{end}}
{{end}}
// ExpressionBuilder builds where clauses for columns. This is now exported.
type ExpressionBuilder[T any] struct {
column string
clause string
params map[string]interface{}
}
// Equal creates an equality clause.
func (b ExpressionBuilder[T]) Equal(value T) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s = @%s", b.column, strings.ReplaceAll(b.column, ".", "_"))
b.params = map[string]interface{}{strings.ReplaceAll(b.column, ".", "_"): value}
return &b
}
// NotEqual creates a not-equal clause.
func (b ExpressionBuilder[T]) NotEqual(value T) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s != @%s", b.column, strings.ReplaceAll(b.column, ".", "_"))
b.params = map[string]interface{}{strings.ReplaceAll(b.column, ".", "_"): value}
return &b
}
// GreaterThan creates a greater-than clause.
func (b ExpressionBuilder[T]) GreaterThan(value T) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s > @%s", b.column, strings.ReplaceAll(b.column, ".", "_"))
b.params = map[string]interface{}{strings.ReplaceAll(b.column, ".", "_"): value}
return &b
}
// LessThan creates a less-than clause.
func (b ExpressionBuilder[T]) LessThan(value T) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s < @%s", b.column, strings.ReplaceAll(b.column, ".", "_"))
b.params = map[string]interface{}{strings.ReplaceAll(b.column, ".", "_"): value}
return &b
}
// GreaterThanOrEqual creates a greater-than-or-equal clause.
func (b ExpressionBuilder[T]) GreaterThanOrEqual(value T) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s >= @%s", b.column, strings.ReplaceAll(b.column, ".", "_"))
b.params = map[string]interface{}{strings.ReplaceAll(b.column, ".", "_"): value}
return &b
}
// LessThanOrEqual creates a less-than-or-equal clause.
func (b ExpressionBuilder[T]) LessThanOrEqual(value T) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s <= @%s", b.column, strings.ReplaceAll(b.column, ".", "_"))
b.params = map[string]interface{}{strings.ReplaceAll(b.column, ".", "_"): value}
return &b
}
// Like creates a like clause.
func (b ExpressionBuilder[T]) Like(value T) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s LIKE @%s", b.column, strings.ReplaceAll(b.column, ".", "_"))
b.params = map[string]interface{}{strings.ReplaceAll(b.column, ".", "_"): value}
return &b
}
// In creates an in clause.
func (b ExpressionBuilder[T]) In(values ...T) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s IN (@%s)", b.column, strings.ReplaceAll(b.column, ".", "_"))
b.params = map[string]interface{}{strings.ReplaceAll(b.column, ".", "_"): values}
return &b
}
// IsNull creates an is null clause.
func (b ExpressionBuilder[T]) IsNull() *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s IS NULL", b.column)
return &b
}
// IsNotNull creates an is not null clause.
func (b ExpressionBuilder[T]) IsNotNull() *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("%s IS NOT NULL", b.column)
return &b
}
// And combines two expressions with AND.
func (b ExpressionBuilder[T]) And(other *ExpressionBuilder[T]) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("(%s AND %s)", b.clause, other.clause)
for k, v := range other.params {
b.params[k] = v
}
return &b
}
// Or combines two expressions with OR.
func (b ExpressionBuilder[T]) Or(other *ExpressionBuilder[T]) *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("(%s OR %s)", b.clause, other.clause)
for k, v := range other.params {
b.params[k] = v
}
return &b
}
// Group creates a parenthesized group.
func (b ExpressionBuilder[T]) Group() *ExpressionBuilder[T] {
b.clause = fmt.Sprintf("(%s)", b.clause)
return &b
}
// GetClause returns the clause and params.
func (b ExpressionBuilder[T]) GetClause() (string, map[string]interface{}) {
return b.clause, b.params
}
// Expression creates a new ExpressionBuilder.
func (c ColumnDescriptor[T]) Expression() ExpressionBuilder[T] {
return ExpressionBuilder[T]{
column: fmt.Sprintf("%s.%s", c.ParentTable, c.ColumnName),
params: make(map[string]interface{}),
}
}
`
2. selectBuilderTemplate:const selectBuilderTemplate = `
package generated
import (
"cloud.google.com/go/spanner"
"fmt"
"strings"
)
{{$allTables := .Tables}}
{{$allColumns := .Columns}}
{{$allConstraints := .Constraints}}
{{range $table := $allTables}}
// Select{{$table.TableName}}StatementBuilder is a builder for creating select statements for the {{$table.TableName}} table.
type Select{{$table.TableName}}StatementBuilder struct {
columns []string
where string
limit int64
offset int64
orderBy string
joins []string
params map[string]interface{}
related map[string]bool
forcedIdx string
}
// Select{{$table.TableName}} creates a new Select{{$table.TableName}}StatementBuilder.
func Select{{$table.TableName}}() *Select{{$table.TableName}}StatementBuilder {
return &Select{{$table.TableName}}StatementBuilder{
params: make(map[string]interface{}),
related: make(map[string]bool),
}
}
// Columns sets the columns to select.
func (b *Select{{$table.TableName}}StatementBuilder) Columns(cols ...string) *Select{{$table.TableName}}StatementBuilder {
b.columns = append(b.columns, cols...)
return b
}
// Where adds a where clause.
func (b *Select{{$table.TableName}}StatementBuilder) Where(expressionBuilder *ExpressionBuilder[any]) *Select{{$table.TableName}}StatementBuilder {
if b.where != "" {
b.where += " AND "
}
clause, params := expressionBuilder.GetClause()
b.where += clause
for k, v := range params {
b.params[k] = v
}
return b
}
// Limit sets the limit.
func (b *Select{{$table.TableName}}StatementBuilder) Limit(limit int64) *Select{{$table.TableName}}StatementBuilder {
b.limit = limit
return b
}
// Offset sets the offset.
func (b *Select{{$table.TableName}}StatementBuilder) Offset(offset int64) *Select{{$table.TableName}}StatementBuilder {
b.offset = offset
return b
}
// OrderBy sets the order by clause.
func (b *Select{{$table.TableName}}StatementBuilder) OrderBy(orderBy string) *Select{{$table.TableName}}StatementBuilder {
b.orderBy = orderBy
return b
}
// Join adds a join clause.
func (b *Select{{$table.TableName}}StatementBuilder) Join(join string) *Select{{$table.TableName}}StatementBuilder {
b.joins = append(b.joins, join)
return b
}
{{range $constraint := $allConstraints}}
{{if eq $constraint.TableName $table.TableName}}
{{if eq $constraint.ConstraintType "FOREIGN_KEY"}}
{{$relatedTable := index $allTables (index (where $allTables "TableName" $constraint.ConstraintName) 0).TableName}}
// Include{{$relatedTable.TableName}} adds a join to include related {{$relatedTable.TableName}} data.
func (b *Select{{$table.TableName}}StatementBuilder) Include{{$relatedTable.TableName}}() *Select{{$table.TableName}}StatementBuilder {
b.related["{{$relatedTable.TableName}}"] = true
return b
}
{{end}}
{{end}}
{{end}}
// ForcedIndex sets the forced index.
func (b *Select{{$table.TableName}}StatementBuilder) ForcedIndex(idx string) *Select{{$table.TableName}}StatementBuilder {
b.forcedIdx = idx
return b
}
// BuildStatement builds a spanner.Statement.
func (b *Select{{$table.TableName}}StatementBuilder) BuildStatement() spanner.Statement {
sql := "SELECT "
if len(b.columns) == 0 {
{{range $col := $allColumns}}
{{if eq $col.TableName $table.TableName}}
b.columns = append(b.columns, "{{$table.TableName}}.{{$col.ColumnName}}")
{{end}}
{{end}}
}
{{range $constraint := $allConstraints}}
{{if eq $constraint.TableName $table.TableName}}
{{if eq $constraint.ConstraintType "FOREIGN_KEY"}}
{{$relatedTable := index $allTables (index (where $allTables "TableName" $constraint.ConstraintName) 0).TableName}}
if b.related["{{$relatedTable.TableName}}"] {
{{range $relatedCol := $allColumns}}
{{if eq $relatedCol.TableName $relatedTable.TableName}}
b.columns = append(b.columns, "{{$relatedTable.TableName}}.{{$relatedCol.ColumnName}}")
{{end}}
{{end}}
b.joins = append(b.joins, fmt.Sprintf("JOIN %s ON %s.%s = %s.%s", "{{$relatedTable.TableName}}", "{{$table.TableName}}", "{{$constraint.Columns}}", "{{$relatedTable.TableName}}", "{{$constraint.ReferencedColumns}}"))
}
{{end}}
{{end}}
{{end}}
sql += strings.Join(b.columns, ", ")
sql += " FROM {{$table.TableName}}"
if len(b.joins) > 0 {
sql += " " + strings.Join(b.joins, " ")
}
if b.where != "" {
sql += " WHERE " + b.where
}
if b.orderBy != "" {
sql += " ORDER BY " + b.orderBy
}
if b.limit > 0 {
sql += " LIMIT " + fmt.Sprint(b.limit)
}
if b.offset > 0 {
sql += " OFFSET " + fmt.Sprint(b.offset)
}
if b.forcedIdx != "" {
sql += "@{FORCE_INDEX=" + b.forcedIdx + "}"
}
return spanner.Statement{
SQL: sql,
Params: b.params,
}
}
{{end}}
`
3. insertBuilderTemplate:const insertBuilderTemplate = `
package generated
import (
"cloud.google.com/go/spanner"
"reflect"
)
{{$allTables := .Tables}}
{{$allColumns := .Columns}}
{{range $table := $allTables}}
// Insert{{$table.TableName}}StatementBuilder is a builder for creating insert statements for the {{$table.TableName}} table.
type Insert{{$table.TableName}}StatementBuilder struct {
values map[string]interface{}
}
// Insert{{$table.TableName}} creates a new Insert{{$table.TableName}}StatementBuilder.
func Insert{{$table.TableName}}() *Insert{{$table.TableName}}StatementBuilder {
return &Insert{{$table.TableName}}StatementBuilder{
values: make(map[string]interface{}),
}
}
{{range $col := $allColumns}}
{{if eq $col.TableName $table.TableName}}
// With{{$col.ColumnName}} sets the value for the {{$col.ColumnName}} column.
func (b *Insert{{$table.TableName}}StatementBuilder) With{{$col.ColumnName}}(value {{$col.DataType}}) *Insert{{$table.TableName}}StatementBuilder {
b.values["{{$col.ColumnName}}"] = value
return b
}
{{end}}
{{end}}
// WithStruct sets the values from a struct.
func (b *Insert{{$table.TableName}}StatementBuilder) WithStruct(s *{{$table.TableName}}) *Insert{{$table.TableName}}StatementBuilder {
val := reflect.ValueOf(s).Elem()
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldName := typ.Field(i).Tag.Get("spanner") // Use the spanner tag
if fieldName != "" && fieldName != "-" {
b.values[fieldName] = field.Interface()
}
}
return b
}
// BuildStatement builds a spanner.Statement.
func (b *Insert{{$table.TableName}}StatementBuilder) BuildStatement() spanner.Statement {
columns := make([]string, 0, len(b.values))
params := make([]string, 0, len(b.values))
for column := range b.values {
columns = append(columns, column)
params = append(params, "@" + column)
}
sql := fmt.Sprintf("INSERT INTO {{$table.TableName}} (%s) VALUES (%s)",
strings.Join(columns, ", "),
strings.Join(params, ", "))
return spanner.Statement{
SQL: sql,
Params: b.values,
}
}
{{end}}
`
4. updateBuilderTemplate:const updateBuilderTemplate = `
package generated
import (
"cloud.google.com/go/spanner"
"fmt"
"strings"
"reflect"
)
{{$allTables := .Tables}}
{{$allColumns := .Columns}}
{{range $table := $allTables}}
// Update{{$table.TableName}}StatementBuilder is a builder for creating update statements for the {{$table.TableName}} table.
type Update{{$table.TableName}}StatementBuilder struct {
set map[string]interface{}
where string
params map[string]interface{}
forcedIdx string
}
// Update{{$table.TableName}} creates a new Update{{$table.TableName}}StatementBuilder.
func Update{{$table.TableName}}() *Update{{$table.TableName}}StatementBuilder {
return &Update{{$table.TableName}}StatementBuilder{
set: make(map[string]interface{}),
params: make(map[string]interface{}),
}
}
{{range $col := $allColumns}}
{{if eq $col.TableName $table.TableName}}
// With{{$col.ColumnName}} sets the value for the {{$col.ColumnName}} column in the update.
func (b *Update{{$table.TableName}}StatementBuilder) With{{$col.ColumnName}}(value {{$col.DataType}}) *Update{{$table.TableName}}StatementBuilder {
b.set["{{$col.ColumnName}}"] = value
return b
}
{{end}}
{{end}}
// WithStruct sets the values from a struct.
func (b *Update{{$table.TableName}}StatementBuilder) WithStruct(s *{{$table.TableName}}) *Update{{$table.TableName}}StatementBuilder {
val := reflect.ValueOf(s).Elem()
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldName := typ.Field(i).Tag.Get("spanner") // Use the spanner tag
if fieldName != "" && fieldName != "-" {
b.set[fieldName] = field.Interface()
}
}
return b
}
// Where adds a where clause to the update statement.
func (b *Update{{$table.TableName}}StatementBuilder) Where(expressionBuilder *ExpressionBuilder[any]) *Update{{$table.TableName}}StatementBuilder {
clause, params := expressionBuilder.GetClause()
b.where = clause
for k, v := range params {
b.params[k] = v
}
return b
}
// ForceIndex sets the index to use.
func (b *Update{{$table.TableName}}StatementBuilder) ForceIndex(index string) *Update{{$table.TableName}}StatementBuilder {
b.forcedIdx = index
return b
}
// BuildStatement builds a spanner.Statement.
func (b *Update{{$table.TableName}}StatementBuilder) BuildStatement() spanner.Statement {
sets := make([]string, 0, len(b.set))
for column, value := range b.set {
sets = append(sets, fmt.Sprintf("%s = @%s", column, column))
b.params[column] = value // Add to the params map
}
sql := fmt.Sprintf("UPDATE {{$table.TableName}} SET %s", strings.Join(sets, ", "))
if b.where != "" {
sql += " WHERE " + b.where
}
if b.forcedIdx != ""{
sql += "@{FORCE_INDEX=" + b.forcedIdx + "}"
}
return spanner.Statement{
SQL: sql,
Params: b.params,
}
}
{{end}}
`
5. structTemplate:const structTemplate = `
package generated
{{$allTables := .Tables}}
{{$allColumns := .Columns}}
{{$allConstraints := .Constraints}}
{{range $table := $allTables}}
// {{$table.TableName}} represents the {{$table.TableName}} table.
type {{$table.TableName}} struct {
{{range $col := $allColumns}}
{{if eq $col.TableName $table.TableName}}
{{$col.ColumnName}} {{$col.DataType}} ` + "`spanner:\"{{$col.ColumnName}}\"`" + `
{{end}}
{{end}}
{{range $constraint := $allConstraints}}
{{if eq $constraint.TableName $table.TableName}}
{{if eq $constraint.ConstraintType "FOREIGN_KEY"}}
{{$relatedTable := index $allTables (index (where $allTables "TableName" $constraint.ConstraintName) 0).TableName}}
{{$relatedTable.TableName}}s []{{$relatedTable.TableName}} ` + "`spanner:\"{{$relatedTable.TableName}}s\"`" + `
{{end}}
{{end}}
{{end}}
{{$parentTable := index $allTables (index (where $allTables "TableName" $table.SelfReferencingColumnName) 0).TableName}}
{{if $table.SelfReferencingColumnName}}
{{$parentTable.TableName}} *{{$parentTable.TableName}} ` + "`spanner:\"{{$parentTable.TableName}}\"`" + `
{{end}}
}
{{end}}
`
@caseylmanus
Copy link
Author

Yes, I can. Let's retrieve the necessary information schema data from Spanner to generate the templates.

1. Spanner Schema Query:

SELECT
  t.table_name,
  c.column_name,
  c.spanner_type,
  i.index_name,
  kcu.constraint_name,
  kcu.constraint_type,
  kcu.column_name AS constraint_column,
  kcu.referenced_table_name,
  kcu.referenced_column_name,
  CASE
    WHEN kcu.constraint_type = 'FOREIGN_KEY' THEN kcu.referenced_column_name
    ELSE NULL
  END AS self_referencing_column_name
FROM
  INFORMATION_SCHEMA.TABLES AS t
LEFT JOIN
  INFORMATION_SCHEMA.COLUMNS AS c
  ON t.table_name = c.table_name
LEFT JOIN
  INFORMATION_SCHEMA.INDEXES AS i
  ON t.table_name = i.table_name
LEFT JOIN
  INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu
  ON t.table_name = kcu.table_name
ORDER BY t.table_name, c.ordinal_position;

2. Go Code to Execute the Query and Process the Results:

package main

import (
        "context"
        "fmt"
        "log"
        "text/template"
        "os"
        "strings"

        "cloud.google.com/go/spanner"
        "google.golang.org/api/iterator"
)

// Table represents a database table.
type Table struct {
        TableName               string
        SelfReferencingColumnName string
}

// Column represents a column in a database table.
type Column struct {
        TableName  string
        ColumnName string
        DataType   string
        IndexName  string
}

// Constraint represents a constraint on a table.
type Constraint struct {
        TableName         string
        ConstraintName    string
        ConstraintType    string
        Columns           string
        ReferencedTableName string
        ReferencedColumns string
}

// Index represents an index on a table.
type Index struct {
        TableName string
        IndexName string
}

func main() {
        ctx := context.Background()
        client, err := spanner.NewClient(ctx, "projects/your-project/instances/your-instance/databases/your-database")
        if err != nil {
                log.Fatalf("Failed to create client: %v", err)
        }
        defer client.Close()

        stmt := spanner.Statement{
                SQL: `
                        SELECT
                                t.table_name,
                                c.spanner_type,
                                c.column_name,
                                i.index_name,
                                kcu.constraint_name,
                                kcu.constraint_type,
                                kcu.column_name AS constraint_column,
                                kcu.referenced_table_name,
                                kcu.referenced_column_name,
                                CASE
                                        WHEN kcu.constraint_type = 'FOREIGN_KEY' THEN kcu.referenced_column_name
                                        ELSE NULL
                                END AS self_referencing_column_name
                        FROM
                                INFORMATION_SCHEMA.TABLES AS t
                        LEFT JOIN
                                INFORMATION_SCHEMA.COLUMNS AS c
                                ON t.table_name = c.table_name
                        LEFT JOIN
                                INFORMATION_SCHEMA.INDEXES AS i
                                ON t.table_name = i.table_name
                        LEFT JOIN
                                INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu
                                ON t.table_name = kcu.table_name
                        ORDER BY t.table_name, c.ordinal_position;
                `,
        }

        iter := client.Single().Query(ctx, stmt)
        defer iter.Stop()

        tables := make(map[string]Table)
        columns := make(map[string][]Column)
        constraints := make(map[string][]Constraint)
        indexes := make(map[string][]Index)

        for {
                row, err := iter.Next()
                if err == iterator.Done {
                        break
                }
                if err != nil {
                        log.Fatalf("Failed to iterate: %v", err)
                }

                var tableName, columnType, columnName, indexName, constraintName, constraintType, constraintColumn, referencedTableName, referencedColumn, selfReferencingColumnName spanner.NullString

                if err := row.Columns(&tableName, &columnType, &columnName, &indexName, &constraintName, &constraintType, &constraintColumn, &referencedTableName, &referencedColumn, &selfReferencingColumnName); err != nil {
                        log.Fatalf("Failed to scan row: %v", err)
                }

                if tableName.Valid {
                        if _, ok := tables[tableName.StringVal]; !ok {
                                tables[tableName.StringVal] = Table{TableName: tableName.StringVal, SelfReferencingColumnName: selfReferencingColumnName.StringVal}
                        }

                        if columnName.Valid {
                                columns[tableName.StringVal] = append(columns[tableName.StringVal], Column{TableName: tableName.StringVal, ColumnName: columnName.StringVal, DataType: toGoType(columnType.StringVal), IndexName: indexName.StringVal})
                        }

                        if constraintName.Valid {
                                constraints[tableName.StringVal] = append(constraints[tableName.StringVal], Constraint{TableName: tableName.StringVal, ConstraintName: constraintName.StringVal, ConstraintType: constraintType.StringVal, Columns: constraintColumn.StringVal, ReferencedTableName: referencedTableName.StringVal, ReferencedColumns: referencedColumn.StringVal})
                        }

                        if indexName.Valid {
                                indexes[tableName.StringVal] = append(indexes[tableName.StringVal], Index{TableName: tableName.StringVal, IndexName: indexName.StringVal})
                        }
                }
        }

        // Convert maps to slices for template processing
        tableSlice := make([]Table, 0, len(tables))
        for _, table := range tables {
                tableSlice = append(tableSlice, table)
        }
        columnSlice := make([]Column, 0)
        for _, cols := range columns {
                columnSlice = append(columnSlice, cols...)
        }
        constraintSlice := make([]Constraint, 0)
        for _, cons := range constraints {
                constraintSlice = append(constraintSlice, cons...)
        }
        indexSlice := make([]Index, 0)
        for _, inds := range indexes {
                indexSlice = append(indexSlice, inds...)
        }

        // Execute the templates
        executeTemplate(tableSlice, columnSlice, constraintSlice, indexSlice)
}

func toGoType(spannerType string) string {
        switch {
        case strings.HasPrefix(spannerType, "INT64"):
                return "int64"
        case strings.HasPrefix(spannerType, "FLOAT64"):
                return "float64"
        case strings.HasPrefix(spannerType, "BOOL"):
                return "bool"
        case strings.HasPrefix(spannerType, "STRING"):
                return "string"
        case strings.HasPrefix(spannerType, "BYTES"):
                return "[]byte"
        case strings.HasPrefix(spannerType, "DATE"):
                return "civil.Date"
        case strings.HasPrefix(spannerType, "TIMESTAMP"):
                return "time.Time"
        default:
                return "interface{}" // Fallback for unknown types
        }
}

func executeTemplate(tables []Table, columns []Column, constraints []Constraint, indexes []Index) {
        // ... (Your template execution logic here) ...
}

This code does the following:

  1. Connects to Spanner: Establishes a connection to your Spanner database.
  2. Executes the Query: Runs the SQL query to retrieve the schema information.
  3. Processes the Results: Iterates through the result rows, extracting the table, column, constraint, and index data, and stores them in appropriate data structures.
  4. Type Conversion: Converts Spanner data types to their corresponding Go types.
  5. Template Execution: (You'll need to add your template execution logic here, using the templates from the previous responses.)

Important Notes:

  • Replace Placeholders: Update the projects/your-project/instances/your-instance/databases/your-database placeholder with your actual Spanner database path.
  • Dependencies: Ensure you have the necessary Go packages installed (cloud.google.com/go/spanner, google.golang.org/api/iterator, cloud.google.com/go/civil).
  • Error Handling: The code includes basic error handling, but you might want to add more robust error checking for production use.
  • Template Logic: You'll need to integrate your template execution logic into the executeTemplate function. Use the tableSlice, columnSlice, constraintSlice, and indexSlice to pass the data to your templates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment