Spanner allows you to create STRUCT
objects from data, as well as to use STRUCT
objects as bound parameters when running a SQL query with one of the Spanner client libraries.
For more information about the STRUCT
type in Spanner, see Data types.
Declaring a user-defined type of STRUCT object
You can declare a STRUCT
object in queries using the syntax described in Declaring a STRUCT
type.
You can define a type of STRUCT
object as a sequence of field names and their data types. You can then supply this type along with queries containing STRUCT
-typed parameter bindings and Spanner will use it to check that the STRUCT
parameter values in your query are valid.
C++
// Cloud Spanner STRUCT<> types are represented by std::tuple<...>. The // following represents a STRUCT<> with two unnamed STRING fields. using NameType = std::tuple<std::string, std::string>;
C#
var nameType = new SpannerStruct { { "FirstName", SpannerDbType.String, null}, { "LastName", SpannerDbType.String, null} };
Go
type nameType struct { FirstName string LastName string }
Java
Type nameType = Type.struct( Arrays.asList( StructField.of("FirstName", Type.string()), StructField.of("LastName", Type.string())));
Node.js
const nameType = { type: 'struct', fields: [ { name: 'FirstName', type: 'string', }, { name: 'LastName', type: 'string', }, ], };
PHP
$nameType = new ArrayType( (new StructType) ->add('FirstName', Database::TYPE_STRING) ->add('LastName', Database::TYPE_STRING) );
Python
name_type = param_types.Struct( [ param_types.StructField("FirstName", param_types.STRING), param_types.StructField("LastName", param_types.STRING), ] )
Ruby
name_type = client.fields FirstName: :STRING, LastName: :STRING
Creating STRUCT objects
The following sample shows how to create STRUCT
objects using the Spanner client libraries.
C++
// Cloud Spanner STRUCT<> types are represented by std::tuple<...>. The // following represents a STRUCT<> with two unnamed STRING fields. using NameType = std::tuple<std::string, std::string>; auto singer_info = NameType{"Elena", "Campbell"};
C#
var nameStruct = new SpannerStruct { { "FirstName", SpannerDbType.String, "Elena" }, { "LastName", SpannerDbType.String, "Campbell" }, };
Go
type name struct { FirstName string LastName string } var singerInfo = name{"Elena", "Campbell"}
Java
Struct name = Struct.newBuilder().set("FirstName").to("Elena").set("LastName").to("Campbell").build();
Node.js
// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner'); const nameStruct = Spanner.struct({ FirstName: 'Elena', LastName: 'Campbell', });
PHP
$nameValue = (new StructValue) ->add('FirstName', 'Elena') ->add('LastName', 'Campbell'); $nameType = (new StructType) ->add('FirstName', Database::TYPE_STRING) ->add('LastName', Database::TYPE_STRING);
Python
record_type = param_types.Struct( [ param_types.StructField("FirstName", param_types.STRING), param_types.StructField("LastName", param_types.STRING), ] ) record_value = ("Elena", "Campbell")
Ruby
name_struct = { FirstName: "Elena", LastName: "Campbell" }
You can also use the client libraries to create an array of STRUCT
objects, as seen in the following sample:
C++
// Cloud Spanner STRUCT<> types with named fields are represented by // std::tuple<std::pair<std::string, T>...>, create an alias to make it easier // to follow this code. using SingerName = std::tuple<std::pair<std::string, std::string>, std::pair<std::string, std::string>>; auto make_name = [](std::string first_name, std::string last_name) { return std::make_tuple(std::make_pair("FirstName", std::move(first_name)), std::make_pair("LastName", std::move(last_name))); }; std::vector<SingerName> singer_info{ make_name("Elena", "Campbell"), make_name("Gabriel", "Wright"), make_name("Benjamin", "Martinez"), };
C#
var bandMembers = new List<SpannerStruct> { new SpannerStruct { { "FirstName", SpannerDbType.String, "Elena" }, { "LastName", SpannerDbType.String, "Campbell" } }, new SpannerStruct { { "FirstName", SpannerDbType.String, "Gabriel" }, { "LastName", SpannerDbType.String, "Wright" } }, new SpannerStruct { { "FirstName", SpannerDbType.String, "Benjamin" }, { "LastName", SpannerDbType.String, "Martinez" } }, };
Go
var bandMembers = []nameType{ {"Elena", "Campbell"}, {"Gabriel", "Wright"}, {"Benjamin", "Martinez"}, }
Java
List<Struct> bandMembers = new ArrayList<>(); bandMembers.add( Struct.newBuilder().set("FirstName").to("Elena").set("LastName").to("Campbell").build()); bandMembers.add( Struct.newBuilder().set("FirstName").to("Gabriel").set("LastName").to("Wright").build()); bandMembers.add( Struct.newBuilder().set("FirstName").to("Benjamin").set("LastName").to("Martinez").build());
Node.js
const bandMembersType = { type: 'array', child: nameType, }; const bandMembers = [ Spanner.struct({ FirstName: 'Elena', LastName: 'Campbell', }), Spanner.struct({ FirstName: 'Gabriel', LastName: 'Wright', }), Spanner.struct({ FirstName: 'Benjamin', LastName: 'Martinez', }), ];
PHP
$bandMembers = [ (new StructValue) ->add('FirstName', 'Elena') ->add('LastName', 'Campbell'), (new StructValue) ->add('FirstName', 'Gabriel') ->add('LastName', 'Wright'), (new StructValue) ->add('FirstName', 'Benjamin') ->add('LastName', 'Martinez') ];
Python
band_members = [ ("Elena", "Campbell"), ("Gabriel", "Wright"), ("Benjamin", "Martinez"), ]
Ruby
band_members = [name_type.struct(["Elena", "Campbell"]), name_type.struct(["Gabriel", "Wright"]), name_type.struct(["Benjamin", "Martinez"])]
Returning STRUCT objects in SQL query results
A Spanner SQL query can return an array of STRUCT
objects as a column for certain queries. For more information, see Using STRUCTS with SELECT.
Using STRUCT objects as bound parameters in SQL queries
You can use STRUCT
objects as bound parameters in a SQL query. For more information about parameters, see Query parameters.
Querying data with a STRUCT object
The following sample shows how to bind values in a STRUCT
object to parameters in a SQL query statement, execute the query, and output the results.
C++
void QueryDataWithStruct(google::cloud::spanner::Client client) { namespace spanner = ::google::cloud::spanner; // Cloud Spanner STRUCT<> types are represented by std::tuple<...>. The // following represents a STRUCT<> with two unnamed STRING fields. using NameType = std::tuple<std::string, std::string>; auto singer_info = NameType{"Elena", "Campbell"}; auto rows = client.ExecuteQuery(spanner::SqlStatement( "SELECT SingerId FROM Singers WHERE (FirstName, LastName) = @name", {{"name", spanner::Value(singer_info)}})); for (auto& row : spanner::StreamOf<std::tuple<std::int64_t>>(rows)) { if (!row) throw std::move(row).status(); std::cout << "SingerId: " << std::get<0>(*row) << "\n"; } std::cout << "Query completed for [spanner_query_data_with_struct]\n"; }
C#
using Google.Cloud.Spanner.Data; using System.Collections.Generic; using System.Threading.Tasks; public class QueryDataWithStructAsyncSample { public async Task<List<int>> QueryDataWithStructAsync(string projectId, string instanceId, string databaseId) { var nameStruct = new SpannerStruct { { "FirstName", SpannerDbType.String, "Elena" }, { "LastName", SpannerDbType.String, "Campbell" }, }; string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}"; var singerIds = new List<int>(); using var connection = new SpannerConnection(connectionString); using var cmd = connection.CreateSelectCommand( "SELECT SingerId FROM Singers " + "WHERE STRUCT<FirstName STRING, LastName STRING>" + "(FirstName, LastName) = @name"); cmd.Parameters.Add("name", nameStruct.GetSpannerDbType(), nameStruct); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { singerIds.Add(reader.GetFieldValue<int>("SingerId")); } return singerIds; } }
Go
stmt := spanner.Statement{ SQL: `SELECT SingerId FROM SINGERS WHERE (FirstName, LastName) = @singerinfo`, Params: map[string]interface{}{"singerinfo": singerInfo}, } iter := client.Single().Query(ctx, stmt) defer iter.Stop() for { row, err := iter.Next() if err == iterator.Done { return nil } if err != nil { return err } var singerID int64 if err := row.Columns(&singerID); err != nil { return err } fmt.Fprintf(w, "%d\n", singerID) }
Java
Statement s = Statement.newBuilder( "SELECT SingerId FROM Singers " + "WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) " + "= @name") .bind("name") .to(name) .build(); try (ResultSet resultSet = dbClient.singleUse().executeQuery(s)) { while (resultSet.next()) { System.out.printf("%d\n", resultSet.getLong("SingerId")); } }
Node.js
/** * TODO(developer): Uncomment the following lines before running the sample. */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database'; // Creates a client const spanner = new Spanner({ projectId: projectId, }); // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId); const query = { sql: 'SELECT SingerId FROM Singers WHERE ' + 'STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) = @name', params: { name: nameStruct, }, }; // Queries rows from the Singers table try { const [rows] = await database.run(query); rows.forEach(row => { const json = row.toJSON(); console.log(`SingerId: ${json.SingerId}`); }); } catch (err) { console.error('ERROR:', err); } finally { // Close the database when finished. database.close(); }
PHP
$results = $database->execute( 'SELECT SingerId FROM Singers ' . 'WHERE STRUCT<FirstName STRING, LastName STRING>' . '(FirstName, LastName) = @name', [ 'parameters' => [ 'name' => $nameValue ], 'types' => [ 'name' => $nameType ] ] ); foreach ($results as $row) { printf('SingerId: %s' . PHP_EOL, $row['SingerId']); }
Python
spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) with database.snapshot() as snapshot: results = snapshot.execute_sql( "SELECT SingerId FROM Singers WHERE " "(FirstName, LastName) = @name", params={"name": record_value}, param_types={"name": record_type}, ) for row in results: print("SingerId: {}".format(*row))
Ruby
# project_id = "Your Google Cloud project ID" # instance_id = "Your Spanner instance ID" # database_id = "Your Spanner database ID" require "google/cloud/spanner" spanner = Google::Cloud::Spanner.new project: project_id client = spanner.client instance_id, database_id client.execute( "SELECT SingerId FROM Singers WHERE " + "(FirstName, LastName) = @name", params: { name: name_struct } ).rows.each do |row| puts row[:SingerId] end
Querying data with an array of STRUCT objects
The following sample shows how to execute a query that uses an array of STRUCT
objects. Use the UNNEST operator to flatten an array of STRUCT
objects into rows:
C++
void QueryDataWithArrayOfStruct(google::cloud::spanner::Client client) { namespace spanner = ::google::cloud::spanner; // Cloud Spanner STRUCT<> types with named fields are represented by // std::tuple<std::pair<std::string, T>...>, create an alias to make it easier // to follow this code. using SingerName = std::tuple<std::pair<std::string, std::string>, std::pair<std::string, std::string>>; auto make_name = [](std::string first_name, std::string last_name) { return std::make_tuple(std::make_pair("FirstName", std::move(first_name)), std::make_pair("LastName", std::move(last_name))); }; std::vector<SingerName> singer_info{ make_name("Elena", "Campbell"), make_name("Gabriel", "Wright"), make_name("Benjamin", "Martinez"), }; auto rows = client.ExecuteQuery(spanner::SqlStatement( "SELECT SingerId FROM Singers" " WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName)" " IN UNNEST(@names)", {{"names", spanner::Value(singer_info)}})); for (auto& row : spanner::StreamOf<std::tuple<std::int64_t>>(rows)) { if (!row) throw std::move(row).status(); std::cout << "SingerId: " << std::get<0>(*row) << "\n"; } std::cout << "Query completed for" << " [spanner_query_data_with_array_of_struct]\n"; }
C#
using Google.Cloud.Spanner.Data; using System.Collections.Generic; using System.Threading.Tasks; public class QueryDataWithArrayOfStructAsyncSample { public async Task<List<int>> QueryDataWithArrayOfStructAsync(string projectId, string instanceId, string databaseId) { var nameType = new SpannerStruct { { "FirstName", SpannerDbType.String, null}, { "LastName", SpannerDbType.String, null} }; var bandMembers = new List<SpannerStruct> { new SpannerStruct { { "FirstName", SpannerDbType.String, "Elena" }, { "LastName", SpannerDbType.String, "Campbell" } }, new SpannerStruct { { "FirstName", SpannerDbType.String, "Gabriel" }, { "LastName", SpannerDbType.String, "Wright" } }, new SpannerStruct { { "FirstName", SpannerDbType.String, "Benjamin" }, { "LastName", SpannerDbType.String, "Martinez" } }, }; var singerIds = new List<int>(); string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}"; using var connection = new SpannerConnection(connectionString); using var cmd = connection.CreateSelectCommand( "SELECT SingerId FROM Singers WHERE STRUCT<FirstName STRING, LastName STRING> " + "(FirstName, LastName) IN UNNEST(@names)"); cmd.Parameters.Add("names", SpannerDbType.ArrayOf(nameType.GetSpannerDbType()), bandMembers); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { singerIds.Add(reader.GetFieldValue<int>("SingerId")); } return singerIds; } }
Go
stmt := spanner.Statement{ SQL: `SELECT SingerId FROM SINGERS WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) IN UNNEST(@names)`, Params: map[string]interface{}{"names": bandMembers}, } iter := client.Single().Query(ctx, stmt) defer iter.Stop() for { row, err := iter.Next() if err == iterator.Done { return nil } if err != nil { return err } var singerID int64 if err := row.Columns(&singerID); err != nil { return err } fmt.Fprintf(w, "%d\n", singerID) }
Java
Statement s = Statement.newBuilder( "SELECT SingerId FROM Singers WHERE " + "STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) " + "IN UNNEST(@names) " + "ORDER BY SingerId DESC") .bind("names") .toStructArray(nameType, bandMembers) .build(); try (ResultSet resultSet = dbClient.singleUse().executeQuery(s)) { while (resultSet.next()) { System.out.printf("%d\n", resultSet.getLong("SingerId")); } }
Node.js
const query = { sql: 'SELECT SingerId FROM Singers ' + 'WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) ' + 'IN UNNEST(@names) ' + 'ORDER BY SingerId', params: { names: bandMembers, }, types: { names: bandMembersType, }, }; // Queries rows from the Singers table try { const [rows] = await database.run(query); rows.forEach(row => { const json = row.toJSON(); console.log(`SingerId: ${json.SingerId}`); }); } catch (err) { console.error('ERROR:', err); } finally { // Close the database when finished. database.close(); }
PHP
$results = $database->execute( 'SELECT SingerId FROM Singers ' . 'WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) ' . 'IN UNNEST(@names)', [ 'parameters' => [ 'names' => $bandMembers ], 'types' => [ 'names' => $nameType ] ] ); foreach ($results as $row) { printf('SingerId: %s' . PHP_EOL, $row['SingerId']); }
Python
spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) with database.snapshot() as snapshot: results = snapshot.execute_sql( "SELECT SingerId FROM Singers WHERE " "STRUCT<FirstName STRING, LastName STRING>" "(FirstName, LastName) IN UNNEST(@names)", params={"names": band_members}, param_types={"names": param_types.Array(name_type)}, ) for row in results: print("SingerId: {}".format(*row))
Ruby
client.execute( "SELECT SingerId FROM Singers WHERE " + "STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) IN UNNEST(@names)", params: { names: band_members } ).rows.each do |row| puts row[:SingerId] end
Modifying data with DML
The following code example uses a STRUCT
with bound parameters and Data Manipulation Language (DML) to update a single value in rows that match the WHERE clause condition. For rows where the FirstName
is Timothy
and the LastName
is Campbell
, the LastName
is updated to Grant
.
C++
void DmlStructs(google::cloud::spanner::Client client) { namespace spanner = ::google::cloud::spanner; std::int64_t rows_modified = 0; auto commit_result = client.Commit([&client, &rows_modified](spanner::Transaction const& txn) -> google::cloud::StatusOr<spanner::Mutations> { auto singer_info = std::make_tuple("Marc", "Richards"); auto sql = spanner::SqlStatement( "UPDATE Singers SET FirstName = 'Keith' WHERE " "STRUCT<FirstName String, LastName String>(FirstName, LastName) " "= @name", {{"name", spanner::Value(std::move(singer_info))}}); auto dml_result = client.ExecuteDml(txn, std::move(sql)); if (!dml_result) return std::move(dml_result).status(); rows_modified = dml_result->RowsModified(); return spanner::Mutations{}; }); if (!commit_result) throw std::move(commit_result).status(); std::cout << rows_modified << " update was successful [spanner_dml_structs]\n"; }
C#
using Google.Cloud.Spanner.Data; using System; using System.Threading.Tasks; public class UpdateUsingDmlWithStructCoreAsyncSample { public async Task<int> UpdateUsingDmlWithStructCoreAsync(string projectId, string instanceId, string databaseId) { var nameStruct = new SpannerStruct { { "FirstName", SpannerDbType.String, "Timothy" }, { "LastName", SpannerDbType.String, "Campbell" } }; string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}"; using var connection = new SpannerConnection(connectionString); await connection.OpenAsync(); using var cmd = connection.CreateDmlCommand("UPDATE Singers SET LastName = 'Grant' WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) = @name"); cmd.Parameters.Add("name", nameStruct.GetSpannerDbType(), nameStruct); int rowCount = await cmd.ExecuteNonQueryAsync(); Console.WriteLine($"{rowCount} row(s) updated..."); return rowCount; } }
Go
import ( "context" "fmt" "io" "cloud.google.com/go/spanner" ) func updateUsingDMLStruct(w io.Writer, db string) error { ctx := context.Background() client, err := spanner.NewClient(ctx, db) if err != nil { return err } defer client.Close() _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error { type name struct { FirstName string LastName string } var singerInfo = name{"Timothy", "Campbell"} stmt := spanner.Statement{ SQL: `Update Singers Set LastName = 'Grant' WHERE STRUCT<FirstName String, LastName String>(Firstname, LastName) = @name`, Params: map[string]interface{}{"name": singerInfo}, } rowCount, err := txn.Update(ctx, stmt) if err != nil { return err } fmt.Fprintf(w, "%d record(s) inserted.\n", rowCount) return nil }) return err }
Java
static void updateUsingDmlWithStruct(DatabaseClient dbClient) { Struct name = Struct.newBuilder().set("FirstName").to("Timothy").set("LastName").to("Campbell").build(); Statement s = Statement.newBuilder( "UPDATE Singers SET LastName = 'Grant' " + "WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) " + "= @name") .bind("name") .to(name) .build(); dbClient .readWriteTransaction() .run(transaction -> { long rowCount = transaction.executeUpdate(s); System.out.printf("%d record updated.\n", rowCount); return null; }); }
Node.js
// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner'); const nameStruct = Spanner.struct({ FirstName: 'Timothy', LastName: 'Campbell', }); /** * TODO(developer): Uncomment the following lines before running the sample. */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database'; // Creates a client const spanner = new Spanner({ projectId: projectId, }); // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId); database.runTransaction(async (err, transaction) => { if (err) { console.error(err); return; } try { const [rowCount] = await transaction.runUpdate({ sql: `UPDATE Singers SET LastName = 'Grant' WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) = @name`, params: { name: nameStruct, }, }); console.log(`Successfully updated ${rowCount} record.`); await transaction.commit(); } catch (err) { console.error('ERROR:', err); } finally { // Close the database when finished. database.close(); } });
PHP
use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Transaction; use Google\Cloud\Spanner\StructType; use Google\Cloud\Spanner\StructValue; /** * Update data with a DML statement using Structs. * * The database and table must already exist and can be created using * `create_database`. * Example: * ``` * insert_data($instanceId, $databaseId); * ``` * * @param string $instanceId The Spanner instance ID. * @param string $databaseId The Spanner database ID. */ function update_data_with_dml_structs(string $instanceId, string $databaseId): void { $spanner = new SpannerClient(); $instance = $spanner->instance($instanceId); $database = $instance->database($databaseId); $database->runTransaction(function (Transaction $t) { $nameValue = (new StructValue) ->add('FirstName', 'Timothy') ->add('LastName', 'Campbell'); $nameType = (new StructType) ->add('FirstName', Database::TYPE_STRING) ->add('LastName', Database::TYPE_STRING); $rowCount = $t->executeUpdate( "UPDATE Singers SET LastName = 'Grant' " . 'WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) ' . '= @name', [ 'parameters' => [ 'name' => $nameValue ], 'types' => [ 'name' => $nameType ] ]); $t->commit(); printf('Updated %d row(s).' . PHP_EOL, $rowCount); }); }
Python
# instance_id = "your-spanner-instance" # database_id = "your-spanner-db-id" spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) record_type = param_types.Struct( [ param_types.StructField("FirstName", param_types.STRING), param_types.StructField("LastName", param_types.STRING), ] ) record_value = ("Timothy", "Campbell") def write_with_struct(transaction): row_ct = transaction.execute_update( "UPDATE Singers SET LastName = 'Grant' " "WHERE STRUCT<FirstName STRING, LastName STRING>" "(FirstName, LastName) = @name", params={"name": record_value}, param_types={"name": record_type}, ) print("{} record(s) updated.".format(row_ct)) database.run_in_transaction(write_with_struct)
Ruby
# project_id = "Your Google Cloud project ID" # instance_id = "Your Spanner instance ID" # database_id = "Your Spanner database ID" require "google/cloud/spanner" spanner = Google::Cloud::Spanner.new project: project_id client = spanner.client instance_id, database_id row_count = 0 name_struct = { FirstName: "Timothy", LastName: "Campbell" } client.transaction do |transaction| row_count = transaction.execute_update( "UPDATE Singers SET LastName = 'Grant' WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) = @name", params: { name: name_struct } ) end puts "#{row_count} record updated."
Accessing STRUCT field values
You can access fields inside a STRUCT
object by name.
C++
void FieldAccessOnStructParameters(google::cloud::spanner::Client client) { namespace spanner = ::google::cloud::spanner; // Cloud Spanner STRUCT<> with named fields is represented as // tuple<pair<string, T>...>. Create a type alias for this example: using SingerName = std::tuple<std::pair<std::string, std::string>, std::pair<std::string, std::string>>; SingerName name({"FirstName", "Elena"}, {"LastName", "Campbell"}); auto rows = client.ExecuteQuery(spanner::SqlStatement( "SELECT SingerId FROM Singers WHERE FirstName = @name.FirstName", {{"name", spanner::Value(name)}})); for (auto& row : spanner::StreamOf<std::tuple<std::int64_t>>(rows)) { if (!row) throw std::move(row).status(); std::cout << "SingerId: " << std::get<0>(*row) << "\n"; } std::cout << "Query completed for" << " [spanner_field_access_on_struct_parameters]\n"; }
C#
using Google.Cloud.Spanner.Data; using System.Collections.Generic; using System.Threading.Tasks; public class QueryDataWithStructFieldAsyncSample { public async Task<List<int>> QueryDataWithStructFieldAsync(string projectId, string instanceId, string databaseId) { string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}"; var structParam = new SpannerStruct { { "FirstName", SpannerDbType.String, "Elena" }, { "LastName", SpannerDbType.String, "Campbell" }, }; var singerIds = new List<int>(); using var connection = new SpannerConnection(connectionString); using var cmd = connection.CreateSelectCommand("SELECT SingerId FROM Singers WHERE FirstName = @name.FirstName"); cmd.Parameters.Add("name", structParam.GetSpannerDbType(), structParam); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { singerIds.Add(reader.GetFieldValue<int>("SingerId")); } return singerIds; } }
Go
import ( "context" "fmt" "io" "cloud.google.com/go/spanner" "google.golang.org/api/iterator" ) func queryWithStructField(w io.Writer, db string) error { ctx := context.Background() client, err := spanner.NewClient(ctx, db) if err != nil { return err } defer client.Close() type structParam struct { FirstName string LastName string } var singerInfo = structParam{"Elena", "Campbell"} stmt := spanner.Statement{ SQL: `SELECT SingerId FROM SINGERS WHERE FirstName = @name.FirstName`, Params: map[string]interface{}{"name": singerInfo}, } iter := client.Single().Query(ctx, stmt) defer iter.Stop() for { row, err := iter.Next() if err == iterator.Done { return nil } if err != nil { return err } var singerID int64 if err := row.Columns(&singerID); err != nil { return err } fmt.Fprintf(w, "%d\n", singerID) } }
Java
static void queryStructField(DatabaseClient dbClient) { Statement s = Statement.newBuilder("SELECT SingerId FROM Singers WHERE FirstName = @name.FirstName") .bind("name") .to( Struct.newBuilder() .set("FirstName") .to("Elena") .set("LastName") .to("Campbell") .build()) .build(); try (ResultSet resultSet = dbClient.singleUse().executeQuery(s)) { while (resultSet.next()) { System.out.printf("%d\n", resultSet.getLong("SingerId")); } } }
Node.js
// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner'); /** * TODO(developer): Uncomment the following lines before running the sample. */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database'; // Creates a client const spanner = new Spanner({ projectId: projectId, }); // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId); const nameStruct = Spanner.struct({ FirstName: 'Elena', LastName: 'Campbell', }); const query = { sql: 'SELECT SingerId FROM Singers WHERE FirstName = @name.FirstName', params: { name: nameStruct, }, }; // Queries rows from the Singers table try { const [rows] = await database.run(query); rows.forEach(row => { const json = row.toJSON(); console.log(`SingerId: ${json.SingerId}`); }); } catch (err) { console.error('ERROR:', err); } finally { // Close the database when finished. database.close(); }
PHP
use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\StructType; /** * Queries sample data from the database using a struct field value. * Example: * ``` * query_data_with_struct_field($instanceId, $databaseId); * ``` * * @param string $instanceId The Spanner instance ID. * @param string $databaseId The Spanner database ID. */ function query_data_with_struct_field(string $instanceId, string $databaseId): void { $spanner = new SpannerClient(); $instance = $spanner->instance($instanceId); $database = $instance->database($databaseId); $nameType = (new StructType) ->add('FirstName', Database::TYPE_STRING) ->add('LastName', Database::TYPE_STRING); $results = $database->execute( 'SELECT SingerId FROM Singers WHERE FirstName = @name.FirstName', [ 'parameters' => [ 'name' => [ 'FirstName' => 'Elena', 'LastName' => 'Campbell' ] ], 'types' => [ 'name' => $nameType ] ] ); foreach ($results as $row) { printf('SingerId: %s' . PHP_EOL, $row['SingerId']); } }
Python
def query_struct_field(instance_id, database_id): """Query a table using field access on a STRUCT parameter.""" spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) name_type = param_types.Struct( [ param_types.StructField("FirstName", param_types.STRING), param_types.StructField("LastName", param_types.STRING), ] ) with database.snapshot() as snapshot: results = snapshot.execute_sql( "SELECT SingerId FROM Singers " "WHERE FirstName = @name.FirstName", params={"name": ("Elena", "Campbell")}, param_types={"name": name_type}, ) for row in results: print("SingerId: {}".format(*row))
Ruby
# project_id = "Your Google Cloud project ID" # instance_id = "Your Spanner instance ID" # database_id = "Your Spanner database ID" require "google/cloud/spanner" spanner = Google::Cloud::Spanner.new project: project_id client = spanner.client instance_id, database_id name_struct = { FirstName: "Elena", LastName: "Campbell" } client.execute( "SELECT SingerId FROM Singers WHERE FirstName = @name.FirstName", params: { name: name_struct } ).rows.each do |row| puts row[:SingerId] end
You can even have fields of STRUCT
or ARRAY<STRUCT>
type inside STRUCT
values and access them similarly:
C++
void FieldAccessOnNestedStruct(google::cloud::spanner::Client client) { namespace spanner = ::google::cloud::spanner; // Cloud Spanner STRUCT<> with named fields is represented as // tuple<pair<string, T>...>. Create a type alias for this example: using SingerFullName = std::tuple<std::pair<std::string, std::string>, std::pair<std::string, std::string>>; auto make_name = [](std::string fname, std::string lname) { return SingerFullName({"FirstName", std::move(fname)}, {"LastName", std::move(lname)}); }; using SongInfo = std::tuple<std::pair<std::string, std::string>, std::pair<std::string, std::vector<SingerFullName>>>; auto songinfo = SongInfo( {"SongName", "Imagination"}, {"ArtistNames", {make_name("Elena", "Campbell"), make_name("Hannah", "Harris")}}); auto rows = client.ExecuteQuery(spanner::SqlStatement( "SELECT SingerId, @songinfo.SongName FROM Singers" " WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName)" " IN UNNEST(@songinfo.ArtistNames)", {{"songinfo", spanner::Value(songinfo)}})); using RowType = std::tuple<std::int64_t, std::string>; for (auto& row : spanner::StreamOf<RowType>(rows)) { if (!row) throw std::move(row).status(); std::cout << "SingerId: " << std::get<0>(*row) << " SongName: " << std::get<1>(*row) << "\n"; } std::cout << "Query completed for [spanner_field_access_on_nested_struct]\n"; }
C#
using Google.Cloud.Spanner.Data; using System; using System.Collections.Generic; using System.Threading.Tasks; public class QueryDataWithNestedStructFieldAsyncSample { public async Task<List<int>> QueryDataWithNestedStructFieldAsync(string projectId, string instanceId, string databaseId) { string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}"; SpannerStruct name1 = new SpannerStruct { { "FirstName", SpannerDbType.String, "Elena" }, { "LastName", SpannerDbType.String, "Campbell" } }; SpannerStruct name2 = new SpannerStruct { { "FirstName", SpannerDbType.String, "Hannah" }, { "LastName", SpannerDbType.String, "Harris" } }; SpannerStruct songInfo = new SpannerStruct { { "song_name", SpannerDbType.String, "Imagination" }, { "artistNames", SpannerDbType.ArrayOf(name1.GetSpannerDbType()), new[] { name1, name2 } } }; var singerIds = new List<int>(); using var connection = new SpannerConnection(connectionString); using var cmd = connection.CreateSelectCommand( "SELECT SingerId, @song_info.song_name " + "FROM Singers WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) " + "IN UNNEST(@song_info.artistNames)"); cmd.Parameters.Add("song_info", songInfo.GetSpannerDbType(), songInfo); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var singerId = reader.GetFieldValue<int>("SingerId"); singerIds.Add(singerId); Console.WriteLine($"SingerId: {singerId}"); Console.WriteLine($"Song Name: {reader.GetFieldValue<string>(1)}"); } return singerIds; } }
Go
import ( "context" "fmt" "io" "cloud.google.com/go/spanner" "google.golang.org/api/iterator" ) func queryWithNestedStructField(w io.Writer, db string) error { ctx := context.Background() client, err := spanner.NewClient(ctx, db) if err != nil { return err } defer client.Close() type nameType struct { FirstName string LastName string } type songInfoStruct struct { SongName string ArtistNames []nameType } var songInfo = songInfoStruct{ SongName: "Imagination", ArtistNames: []nameType{ {FirstName: "Elena", LastName: "Campbell"}, {FirstName: "Hannah", LastName: "Harris"}, }, } stmt := spanner.Statement{ SQL: `SELECT SingerId, @songinfo.SongName FROM Singers WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) IN UNNEST(@songinfo.ArtistNames)`, Params: map[string]interface{}{"songinfo": songInfo}, } iter := client.Single().Query(ctx, stmt) defer iter.Stop() for { row, err := iter.Next() if err == iterator.Done { return nil } if err != nil { return err } var singerID int64 var songName string if err := row.Columns(&singerID, &songName); err != nil { return err } fmt.Fprintf(w, "%d %s\n", singerID, songName) } }
Java
static void queryNestedStructField(DatabaseClient dbClient) { Type nameType = Type.struct( Arrays.asList( StructField.of("FirstName", Type.string()), StructField.of("LastName", Type.string()))); Struct songInfo = Struct.newBuilder() .set("song_name") .to("Imagination") .set("artistNames") .toStructArray( nameType, Arrays.asList( Struct.newBuilder() .set("FirstName") .to("Elena") .set("LastName") .to("Campbell") .build(), Struct.newBuilder() .set("FirstName") .to("Hannah") .set("LastName") .to("Harris") .build())) .build(); Statement s = Statement.newBuilder( "SELECT SingerId, @song_info.song_name " + "FROM Singers WHERE " + "STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) " + "IN UNNEST(@song_info.artistNames)") .bind("song_info") .to(songInfo) .build(); try (ResultSet resultSet = dbClient.singleUse().executeQuery(s)) { while (resultSet.next()) { System.out.printf("%d %s\n", resultSet.getLong("SingerId"), resultSet.getString(1)); } } }
Node.js
// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner'); /** * TODO(developer): Uncomment the following lines before running the sample. */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database'; // Creates a client const spanner = new Spanner({ projectId: projectId, }); // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId); const nameType = { type: 'struct', fields: [ { name: 'FirstName', type: 'string', }, { name: 'LastName', type: 'string', }, ], }; // Creates Song info STRUCT with a nested ArtistNames array const songInfoType = { type: 'struct', fields: [ { name: 'SongName', type: 'string', }, { name: 'ArtistNames', type: 'array', child: nameType, }, ], }; const songInfoStruct = Spanner.struct({ SongName: 'Imagination', ArtistNames: [ Spanner.struct({FirstName: 'Elena', LastName: 'Campbell'}), Spanner.struct({FirstName: 'Hannah', LastName: 'Harris'}), ], }); const query = { sql: 'SELECT SingerId, @songInfo.SongName FROM Singers ' + 'WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) ' + 'IN UNNEST(@songInfo.ArtistNames)', params: { songInfo: songInfoStruct, }, types: { songInfo: songInfoType, }, }; // Queries rows from the Singers table try { const [rows] = await database.run(query); rows.forEach(row => { const json = row.toJSON(); console.log(`SingerId: ${json.SingerId}, SongName: ${json.SongName}`); }); } catch (err) { console.error('ERROR:', err); } finally { // Close the database when finished. database.close(); }
PHP
use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\StructType; use Google\Cloud\Spanner\StructValue; use Google\Cloud\Spanner\ArrayType; /** * Queries sample data from the database using a nested struct field value. * Example: * ``` * query_data_with_nested_struct_field($instanceId, $databaseId); * ``` * * @param string $instanceId The Spanner instance ID. * @param string $databaseId The Spanner database ID. */ function query_data_with_nested_struct_field(string $instanceId, string $databaseId): void { $spanner = new SpannerClient(); $instance = $spanner->instance($instanceId); $database = $instance->database($databaseId); $nameType = new ArrayType( (new StructType) ->add('FirstName', Database::TYPE_STRING) ->add('LastName', Database::TYPE_STRING) ); $songInfoType = (new StructType) ->add('SongName', Database::TYPE_STRING) ->add('ArtistNames', $nameType); $nameStructValue1 = (new StructValue) ->add('FirstName', 'Elena') ->add('LastName', 'Campbell'); $nameStructValue2 = (new StructValue) ->add('FirstName', 'Hannah') ->add('LastName', 'Harris'); $songInfoValues = (new StructValue) ->add('SongName', 'Imagination') ->add('ArtistNames', [$nameStructValue1, $nameStructValue2]); $results = $database->execute( 'SELECT SingerId, @song_info.SongName FROM Singers ' . 'WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) ' . 'IN UNNEST(@song_info.ArtistNames)', [ 'parameters' => [ 'song_info' => $songInfoValues ], 'types' => [ 'song_info' => $songInfoType ] ] ); foreach ($results as $row) { printf('SingerId: %s SongName: %s' . PHP_EOL, $row['SingerId'], $row['SongName']); } }
Python
def query_nested_struct_field(instance_id, database_id): """Query a table using nested field access on a STRUCT parameter.""" spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) song_info_type = param_types.Struct( [ param_types.StructField("SongName", param_types.STRING), param_types.StructField( "ArtistNames", param_types.Array( param_types.Struct( [ param_types.StructField("FirstName", param_types.STRING), param_types.StructField("LastName", param_types.STRING), ] ) ), ), ] ) song_info = ("Imagination", [("Elena", "Campbell"), ("Hannah", "Harris")]) with database.snapshot() as snapshot: results = snapshot.execute_sql( "SELECT SingerId, @song_info.SongName " "FROM Singers WHERE " "STRUCT<FirstName STRING, LastName STRING>" "(FirstName, LastName) " "IN UNNEST(@song_info.ArtistNames)", params={"song_info": song_info}, param_types={"song_info": song_info_type}, ) for row in results: print("SingerId: {} SongName: {}".format(*row))
Ruby
# project_id = "Your Google Cloud project ID" # instance_id = "Your Spanner instance ID" # database_id = "Your Spanner database ID" require "google/cloud/spanner" spanner = Google::Cloud::Spanner.new project: project_id client = spanner.client instance_id, database_id name_type = client.fields FirstName: :STRING, LastName: :STRING song_info_struct = { SongName: "Imagination", ArtistNames: [name_type.struct(["Elena", "Campbell"]), name_type.struct(["Hannah", "Harris"])] } client.execute( "SELECT SingerId, @song_info.SongName " \ "FROM Singers WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) " \ "IN UNNEST(@song_info.ArtistNames)", params: { song_info: song_info_struct } ).rows.each do |row| puts (row[:SingerId]), (row[:SongName]) end