spanner/spanner_arrays/main.go (170 lines of code) (raw):

// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Sample spanner_arrays is a demonstration program which queries Google's Cloud Spanner // and returns results containing arrays. package main import ( "context" "flag" "fmt" "log" "regexp" "strings" "cloud.google.com/go/spanner" database "cloud.google.com/go/spanner/admin/database/apiv1" adminpb "cloud.google.com/go/spanner/admin/database/apiv1/databasepb" "google.golang.org/api/iterator" ) // Country describes a country and the cities inside it. type Country struct { Name string Colours []spanner.NullString Cities []spanner.NullString } func main() { ctx := context.Background() dsn := flag.String("database", "projects/your-project-id/instances/your-instance-id/databases/your-database-id", "Cloud Spanner database name") flag.Parse() // Connect to the Spanner Admin API. admin, err := database.NewDatabaseAdminClient(ctx) if err != nil { log.Fatalf("failed to create database admin client: %v", err) } defer admin.Close() err = createDatabase(ctx, admin, *dsn) if err != nil { log.Fatalf("failed to create database: %v", err) } defer removeDatabase(ctx, admin, *dsn) // Connect to database. client, err := spanner.NewClient(ctx, *dsn) if err != nil { log.Fatalf("Failed to create client %v", err) } defer client.Close() err = loadPresets(ctx, client) if err != nil { log.Fatalf("failed to load preset data: %v", err) } it := client.Single().Query(ctx, spanner.NewStatement(` SELECT a.Name AS Name, ARRAY( SELECT b.Name FROM Cities b WHERE a.CountryId = b.CountryId ) AS Cities, Colours FROM Countries a `)) defer it.Stop() for { row, err := it.Next() if err == iterator.Done { break } if err != nil { log.Fatalf("failed to read results: %v", err) } var country Country if err = row.ToStruct(&country); err != nil { log.Fatalf("failed to read row into Country struct: %v", err) } var cities []string for _, c := range country.Cities { cities = append(cities, c.String()) } var colours []string for _, c := range country.Colours { colours = append(colours, c.String()) } log.Printf("%s (%s): %s", country.Name, strings.Join(colours, ", "), strings.Join(cities, ", ")) } } // loadPresets inserts some demonstration data into the tables. func loadPresets(ctx context.Context, db *spanner.Client) error { mx := []*spanner.Mutation{ spanner.InsertMap("Countries", map[string]interface{}{ "CountryId": 49, "Name": "Germany", "Colours": []string{"black", "red", "gold"}, }), spanner.InsertMap("Cities", map[string]interface{}{ "CountryId": 49, "CityId": 100, "Name": "Berlin", "Population": 3605000, }), spanner.InsertMap("Cities", map[string]interface{}{ "CountryId": 49, "CityId": 101, "Name": "Hamburg", "Population": 1739117, }), spanner.InsertMap("Cities", map[string]interface{}{ "CountryId": 49, "CityId": 102, "Name": "Dresden", "Population": 486854, }), spanner.InsertMap("Countries", map[string]interface{}{ "CountryId": 44, "Name": "United Kingdom", "Colours": []string{"white", "red", "blue"}, }), spanner.InsertMap("Cities", map[string]interface{}{ "CountryId": 44, "CityId": 200, "Name": "London", "Population": 8788000, }), spanner.InsertMap("Cities", map[string]interface{}{ "CountryId": 44, "CityId": 201, "Name": "Liverpool", "Population": 465700, }), spanner.InsertMap("Cities", map[string]interface{}{ "CountryId": 44, "CityId": 202, "Name": "Bristol", "Population": 428100, }), spanner.InsertMap("Cities", map[string]interface{}{ "CountryId": 44, "CityId": 203, "Name": "Newcastle", "Population": 304636, }), } _, err := db.Apply(ctx, mx) return err } // createDatabase uses the Spanner database administration client to create the tables used in this demonstration. func createDatabase(ctx context.Context, adminClient *database.DatabaseAdminClient, db string) error { matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db) if matches == nil || len(matches) != 3 { log.Fatalf("Invalid database id %s", db) } var ( projectID = matches[1] databaseName = matches[2] ) op, err := adminClient.CreateDatabase(ctx, &adminpb.CreateDatabaseRequest{ Parent: projectID, CreateStatement: fmt.Sprintf("CREATE DATABASE `%s`", databaseName), ExtraStatements: []string{ `CREATE TABLE Countries ( CountryId INT64 NOT NULL, Name STRING(1024) NOT NULL, Colours ARRAY<STRING(1024)> NOT NULL ) PRIMARY KEY (CountryId)`, `CREATE TABLE Cities ( CountryId INT64 NOT NULL, CityId INT64 NOT NULL, Name STRING(MAX) NOT NULL, Population INT64 NOT NULL ) PRIMARY KEY (CountryId, CityId), INTERLEAVE IN PARENT Countries ON DELETE CASCADE`, }, }) if err != nil { return err } if _, err := op.Wait(ctx); err == nil { log.Printf("Created database [%s]", db) } return err } // removeDatabase deletes the database which this demonstration program created. func removeDatabase(ctx context.Context, adminClient *database.DatabaseAdminClient, db string) { if err := adminClient.DropDatabase(ctx, &adminpb.DropDatabaseRequest{Database: db}); err != nil { log.Fatalf("Failed to remove database [%s]: %v", db, err) } else { log.Printf("Removed database [%s]", db) } }