From ca68cf5bcde90548a455fc0f729808e901bd3929 Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Thu, 18 Jul 2024 17:23:27 -0700 Subject: [PATCH 01/10] Create a set of code snippets for using Graph on Cloud Spanner --- samples/samples/graph_snippets.py | 375 ++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 samples/samples/graph_snippets.py diff --git a/samples/samples/graph_snippets.py b/samples/samples/graph_snippets.py new file mode 100644 index 0000000000..1a20777e32 --- /dev/null +++ b/samples/samples/graph_snippets.py @@ -0,0 +1,375 @@ +#!/usr/bin/env python + +# Copyright 2024 Google, Inc. +# +# 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 +# +# http://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. + +"""This application demonstrates how to do basic graph operations using +Cloud Spanner. + +For more information, see the README.rst under /spanner. +""" + +import argparse +import base64 +import datetime +import decimal +import json +import logging +import time + +from google.cloud import spanner +from google.cloud.spanner_admin_instance_v1.types import spanner_instance_admin +from google.cloud.spanner_v1 import DirectedReadOptions, param_types +from google.cloud.spanner_v1.data_types import JsonObject +from google.protobuf import field_mask_pb2 # type: ignore +from testdata import singer_pb2 + +OPERATION_TIMEOUT_SECONDS = 240 + + +# [START spanner_create_instance] +def create_instance(instance_id): + """Creates an instance.""" + from google.cloud.spanner_admin_instance_v1.types import \ + spanner_instance_admin + + spanner_client = spanner.Client() + + config_name = "{}/instanceConfigs/regional-us-central1".format( + spanner_client.project_name + ) + + operation = spanner_client.instance_admin_api.create_instance( + parent=spanner_client.project_name, + instance_id=instance_id, + instance=spanner_instance_admin.Instance( + config=config_name, + display_name="This is a display name.", + node_count=1, + labels={ + "cloud_spanner_samples": "true", + "sample_name": "snippets-create_instance-explicit", + "created": str(int(time.time())), + }, + ), + ) + + print("Waiting for operation to complete...") + operation.result(OPERATION_TIMEOUT_SECONDS) + + print("Created instance {}".format(instance_id)) + + +# [END spanner_create_instance] + + +# [START spanner_create_database_with_property_graph] +def create_database_with_property_graph(instance_id, database_id): + """Creates a database, tables and a property graph for sample data.""" + from google.cloud.spanner_admin_database_v1.types import \ + spanner_database_admin + + spanner_client = spanner.Client() + database_admin_api = spanner_client.database_admin_api + + request = spanner_database_admin.CreateDatabaseRequest( + parent=database_admin_api.instance_path(spanner_client.project, instance_id), + create_statement=f"CREATE DATABASE `{database_id}`", + extra_statements=[ + """CREATE TABLE Person ( + id INT64 NOT NULL, + name STRING(MAX), + gender STRING(40), + birthday TIMESTAMP, + country STRING(MAX), + city STRING(MAX), + ) PRIMARY KEY (id)""", + """CREATE TABLE Account ( + id INT64 NOT NULL, + create_time TIMESTAMP, + is_blocked BOOL, + nick_name STRING(MAX), + ) PRIMARY KEY (id)""", + """CREATE TABLE PersonOwnAccount ( + id INT64 NOT NULL, + account_id INT64 NOT NULL, + create_time TIMESTAMP, + FOREIGN KEY (account_id) + REFERENCES Account (id) + ) PRIMARY KEY (id, account_id), + INTERLEAVE IN PARENT Person ON DELETE CASCADE""", + """CREATE TABLE AccountTransferAccount ( + id INT64 NOT NULL, + to_id INT64 NOT NULL, + amount FLOAT64, + create_time TIMESTAMP NOT NULL, + order_number STRING(MAX), + FOREIGN KEY (to_id) REFERENCES Account (id) + ) PRIMARY KEY (id, to_id, create_time), + INTERLEAVE IN PARENT Account ON DELETE CASCADE""", + """CREATE OR REPLACE PROPERTY GRAPH FinGraph + NODE TABLES (Account, Person) + EDGE TABLES ( + PersonOwnAccount + SOURCE KEY(id) REFERENCES Person(id) + DESTINATION KEY(account_id) REFERENCES Account(id) + LABEL Owns, + AccountTransferAccount + SOURCE KEY(id) REFERENCES Account(id) + DESTINATION KEY(to_id) REFERENCES Account(id) + LABEL Transfers)""", + ], + ) + + operation = database_admin_api.create_database(request=request) + + print("Waiting for operation to complete...") + database = operation.result(OPERATION_TIMEOUT_SECONDS) + + print( + "Created database {} on instance {}".format( + database.name, + database_admin_api.instance_path(spanner_client.project, instance_id), + ) + ) + + +# [END spanner_create_database_with_property_graph] + + +# [START spanner_insert_graph_data] +def insert_data(instance_id, database_id): + """Inserts sample data into the given database. + + The database and tables must already exist and can be created using + `create_database_with_property_graph`. + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + with database.batch() as batch: + batch.insert( + table="Person", + columns=("id", "name", "country", "city"), + values=[ + (1, "Izumi", "USA", "Mountain View"), + (2, "Tal", "FR", "Paris"), + ], + ) + + batch.insert( + table="Account", + columns=("id", "create_time", "is_blocked", "nick_name"), + values=[ + (1, '2014-09-27T11:17:42.18Z', False, "Savings"), + (2, '2008-07-11T12:30:00.45Z', False, "Checking"), + ], + ) + + print("Inserted data.") + + +# [END spanner_insert_graph_data] + + +# [START spanner_insert_graph_data_with_dml] +def insert_data_with_dml(instance_id, database_id): + """Inserts sample data into the given database using a DML statement.""" + + # 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) + + def insert_owns(transaction): + row_ct = transaction.execute_update( + "INSERT INTO PersonOwnAccount (id, account_id, create_time) " + " VALUES" + "(1, 1, '2014-09-28T09:23:31.45Z')," + "(2, 2, '2008-07-12T04:31:18.16Z')" + ) + + print("{} record(s) inserted into PersonOwnAccount.".format(row_ct)) + + def insert_transfers(transaction): + row_ct = transaction.execute_update( + "INSERT INTO AccountTransferAccount (id, to_id, amount, create_time, order_number) " + " VALUES" + "(1, 2, 900, '2024-06-24T10:11:31.26Z', '3LXCTB')," + "(2, 1, 100, '2024-07-01T12:23:28.11Z', '4MYRTQ')" + ) + + print("{} record(s) inserted into AccountTransferAccount.".format(row_ct)) + + + database.run_in_transaction(insert_owns) + database.run_in_transaction(insert_transfers) + + +# [END spanner_insert_graph_data_with_dml] + + +# [START spanner_query_graph_data] +def query_data(instance_id, database_id): + """Queries sample data from the database using GQL.""" + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + with database.snapshot() as snapshot: + results = snapshot.execute_sql( + """Graph FinGraph + MATCH (a:Person)-[o:Owns]->()-[t:Transfers]->()<-[p:Owns]-(b:Person) + RETURN a.name AS sender, b.name AS receiver, t.amount, t.create_time AS transfer_at""" + ) + + for row in results: + print("sender: {}, receiver: {}, amount: {}, transfer_at: {}".format(*row)) + + +# [END spanner_query_graph_data] + + +# [START spanner_with_graph_query_data_with_parameter] +def query_data_with_parameter(instance_id, database_id): + """Queries sample data from the database using SQL with a parameter.""" + + # 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) + + with database.snapshot() as snapshot: + results = snapshot.execute_sql( + """Graph FinGraph + MATCH (a:Person)-[o:Owns]->()-[t:Transfers]->()<-[p:Owns]-(b:Person) + WHERE t.amount > @min + RETURN a.name AS sender, b.name AS receiver, t.amount, t.create_time AS transfer_at""", + params={"min": 500}, + param_types={"min": spanner.param_types.INT64}, + ) + + for row in results: + print("sender: {}, receiver: {}, amount: {}, transfer_at: {}".format(*row)) + + +# [END spanner_with_graph_query_data_with_parameter] + + +# [START spanner_delete_data] +def delete_data(instance_id, database_id): + """Deletes sample data from the given database. + + The database, table, and data must already exist and can be created using + `create_database` and `insert_data`. + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + # Delete individual rows + ownerships_to_delete = spanner.KeySet(keys=[[1, 1], [2, 2]]) + + # Delete a range of rows where the column key is >=3 and <5 + transfers_range = spanner.KeyRange(start_closed=[1], end_open=[3]) + transfers_to_delete = spanner.KeySet(ranges=[transfers_range]) + + with database.batch() as batch: + batch.delete("PersonOwnAccount", ownerships_to_delete) + batch.delete("AccountTransferAccount", transfers_to_delete) + + print("Deleted data.") + + +# [END spanner_delete_data] + +# [START spanner_delete_graph_data_with_dml] +def delete_data_with_dml(instance_id, database_id): + """Deletes sample data from the database using a DML statement.""" + + # 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) + + def delete_persons(transaction): + row_ct = transaction.execute_update( + "DELETE FROM Person WHERE True" + ) + + print("{} record(s) deleted.".format(row_ct)) + + def delete_accounts(transaction): + row_ct = transaction.execute_update( + "DELETE FROM Account AS a WHERE EXTRACT(YEAR FROM DATE(a.create_time)) >= 2000" + ) + + print("{} record(s) deleted.".format(row_ct)) + + database.run_in_transaction(delete_accounts) + database.run_in_transaction(delete_persons) + + +# [END spanner_delete_graph_data_with_dml] + + +if __name__ == "__main__": # noqa: C901 + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument("instance_id", help="Your Cloud Spanner instance ID.") + parser.add_argument( + "--database-id", help="Your Cloud Spanner database ID.", default="example_db" + ) + + subparsers = parser.add_subparsers(dest="command") + subparsers.add_parser("create_instance", help=create_instance.__doc__) + subparsers.add_parser( + "create_database_with_property_graph", + help=create_database_with_property_graph.__doc__) + subparsers.add_parser("insert_data", help=insert_data.__doc__) + subparsers.add_parser("insert_data_with_dml", help=insert_data_with_dml.__doc__) + subparsers.add_parser("query_data", help=query_data.__doc__) + subparsers.add_parser( + "query_data_with_parameter", help=query_data_with_parameter.__doc__ + ) + + subparsers.add_parser("delete_data", help=delete_data.__doc__) + subparsers.add_parser("delete_data_with_dml", help=delete_data_with_dml.__doc__) + + args = parser.parse_args() + + if args.command == "create_instance": + create_instance(args.instance_id) + elif args.command == "create_database_with_property_graph": + create_database_with_property_graph(args.instance_id, args.database_id) + elif args.command == "insert_data": + insert_data(args.instance_id, args.database_id) + elif args.command == "insert_data_with_dml": + insert_data_with_dml(args.instance_id, args.database_id) + elif args.command == "query_data": + query_data(args.instance_id, args.database_id) + elif args.command == "query_data_with_parameter": + query_data_with_parameter(args.instance_id, args.database_id) + elif args.command == "delete_data": + delete_data(args.instance_id, args.database_id) + elif args.command == "delete_data_with_dml": + delete_data_with_dml(args.instance_id, args.database_id) + From b6355ff70400bfd1524352977fef86beaabcdb5e Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Sun, 21 Jul 2024 22:59:23 -0700 Subject: [PATCH 02/10] Update to match gcloud/cli examples that exist in the docs --- samples/samples/graph_snippets.py | 210 ++++++++++++++++++++++-------- 1 file changed, 153 insertions(+), 57 deletions(-) diff --git a/samples/samples/graph_snippets.py b/samples/samples/graph_snippets.py index 1a20777e32..33766689d0 100644 --- a/samples/samples/graph_snippets.py +++ b/samples/samples/graph_snippets.py @@ -113,7 +113,8 @@ def create_database_with_property_graph(instance_id, database_id): id INT64 NOT NULL, to_id INT64 NOT NULL, amount FLOAT64, - create_time TIMESTAMP NOT NULL, + create_time TIMESTAMP NOT NULL OPTIONS + (allow_commit_timestamp=true), order_number STRING(MAX), FOREIGN KEY (to_id) REFERENCES Account (id) ) PRIMARY KEY (id, to_id, create_time), @@ -160,24 +161,48 @@ def insert_data(instance_id, database_id): database = instance.database(database_id) with database.batch() as batch: + batch.insert( + table="Account", + columns=("id", "create_time", "is_blocked", "nick_name"), + values=[ + (7, '2020-01-10T06:22:20.12Z', False, "Vacation Fund"), + (16, '2020-01-27T17:55:09.12Z', True, "Vacation Fund"), + (20, '2020-02-18T05:44:20.12Z', False, "Rainy Day Fund") + ], + ) + batch.insert( table="Person", - columns=("id", "name", "country", "city"), + columns=("id", "name", "gender", "birthday", "country", "city"), values=[ - (1, "Izumi", "USA", "Mountain View"), - (2, "Tal", "FR", "Paris"), + (1, "Alex", "male", '1991-12-21T00:00:00.12Z', "Australia"," Adelaide"), + (2, "Dana", "female", '1980-10-31T00:00:00.12Z',"Czech_Republic", "Moravia"), + (3, "Lee", "male", '1986-12-07T00:00:00.12Z', "India", "Kollam") ], ) batch.insert( - table="Account", - columns=("id", "create_time", "is_blocked", "nick_name"), + table="AccountTransferAccount", + columns=("id", "to_id", "amount", "create_time", "order_number"), values=[ - (1, '2014-09-27T11:17:42.18Z', False, "Savings"), - (2, '2008-07-11T12:30:00.45Z', False, "Checking"), + (7, 16, 300.0, '2020-08-29T15:28:58.12Z', "304330008004315"), + (7, 16, 100.0, '2020-10-04T16:55:05.12Z', "304120005529714"), + (16, 20, 300.0, '2020-09-25T02:36:14.12Z', "103650009791820"), + (20, 7, 500.0, '2020-10-04T16:55:05.12Z', "304120005529714"), + (20, 16, 200.0, '2020-10-17T03:59:40.12Z', "302290001255747") ], ) + batch.insert( + table="PersonOwnAccount", + columns=("id", "account_id", "create_time"), + values=[ + (1, 7, '2020-01-10T06:22:20.12Z'), + (2, 20, '2020-01-27T17:55:09.12Z'), + (3, 16, '2020-02-18T05:44:20.12Z') + ] + ) + print("Inserted data.") @@ -195,34 +220,92 @@ def insert_data_with_dml(instance_id, database_id): instance = spanner_client.instance(instance_id) database = instance.database(database_id) - def insert_owns(transaction): + def insert_accounts(transaction): row_ct = transaction.execute_update( - "INSERT INTO PersonOwnAccount (id, account_id, create_time) " - " VALUES" - "(1, 1, '2014-09-28T09:23:31.45Z')," - "(2, 2, '2008-07-12T04:31:18.16Z')" + "INSERT INTO Account (id, create_time, is_blocked) " + " VALUES" + " (1, CAST('2000-08-10 08:18:48.463959-07:52' AS TIMESTAMP), false)," + " (2, CAST('2000-08-12 08:18:48.463959-07:52' AS TIMESTAMP), true)" ) - print("{} record(s) inserted into PersonOwnAccount.".format(row_ct)) + print("{} record(s) inserted into Account.".format(row_ct)) def insert_transfers(transaction): row_ct = transaction.execute_update( - "INSERT INTO AccountTransferAccount (id, to_id, amount, create_time, order_number) " - " VALUES" - "(1, 2, 900, '2024-06-24T10:11:31.26Z', '3LXCTB')," - "(2, 1, 100, '2024-07-01T12:23:28.11Z', '4MYRTQ')" + "INSERT INTO AccountTransferAccount (id, to_id, create_time, amount) " + " VALUES" + " (1, 2, PENDING_COMMIT_TIMESTAMP(), 100)," + " (1, 1, PENDING_COMMIT_TIMESTAMP(), 200) " ) print("{} record(s) inserted into AccountTransferAccount.".format(row_ct)) - database.run_in_transaction(insert_owns) + database.run_in_transaction(insert_accounts) database.run_in_transaction(insert_transfers) # [END spanner_insert_graph_data_with_dml] +# [START spanner_update_graph_data_with_dml] +def update_data_with_dml(instance_id, database_id): + """Updates sample data from the database using a DML statement.""" + # 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) + + def update_accounts(transaction): + row_ct = transaction.execute_update( + "UPDATE Account SET is_blocked = false WHERE id = 2" + ) + + print("{} record(s) updated.".format(row_ct)) + + def update_transfers(transaction): + row_ct = transaction.execute_update( + "UPDATE AccountTransferAccount SET amount = 300 WHERE id = 1 AND to_id = 2" + ) + + print("{} record(s) updated.".format(row_ct)) + + database.run_in_transaction(update_accounts) + database.run_in_transaction(update_transfers) + + +# [END spanner_update_graph_data_with_dml] + + +# [START spanner_update_graph_data_with_graph_query_in_dml] +def update_data_with_graph_query_in_dml(instance_id, database_id): + """Updates sample data from the database using a DML statement.""" + # 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) + + def update_accounts(transaction): + row_ct = transaction.execute_update( + "UPDATE Account SET is_blocked = true " + "WHERE id IN (" + " GRAPH FinGraph" + " MATCH (a:Account WHERE a.id = 1)-[:TRANSFERS]->{1,2}(b:Account)" + " RETURN b.id)" + ) + + print("{} record(s) updated.".format(row_ct)) + + database.run_in_transaction(update_accounts) + + +# [END spanner_update_graph_data_with_graph_query_in_dml + + # [START spanner_query_graph_data] def query_data(instance_id, database_id): """Queries sample data from the database using GQL.""" @@ -258,7 +341,7 @@ def query_data_with_parameter(instance_id, database_id): results = snapshot.execute_sql( """Graph FinGraph MATCH (a:Person)-[o:Owns]->()-[t:Transfers]->()<-[p:Owns]-(b:Person) - WHERE t.amount > @min + WHERE t.amount >= @min RETURN a.name AS sender, b.name AS receiver, t.amount, t.create_time AS transfer_at""", params={"min": 500}, param_types={"min": spanner.param_types.INT64}, @@ -271,33 +354,6 @@ def query_data_with_parameter(instance_id, database_id): # [END spanner_with_graph_query_data_with_parameter] -# [START spanner_delete_data] -def delete_data(instance_id, database_id): - """Deletes sample data from the given database. - - The database, table, and data must already exist and can be created using - `create_database` and `insert_data`. - """ - spanner_client = spanner.Client() - instance = spanner_client.instance(instance_id) - database = instance.database(database_id) - - # Delete individual rows - ownerships_to_delete = spanner.KeySet(keys=[[1, 1], [2, 2]]) - - # Delete a range of rows where the column key is >=3 and <5 - transfers_range = spanner.KeyRange(start_closed=[1], end_open=[3]) - transfers_to_delete = spanner.KeySet(ranges=[transfers_range]) - - with database.batch() as batch: - batch.delete("PersonOwnAccount", ownerships_to_delete) - batch.delete("AccountTransferAccount", transfers_to_delete) - - print("Deleted data.") - - -# [END spanner_delete_data] - # [START spanner_delete_graph_data_with_dml] def delete_data_with_dml(instance_id, database_id): """Deletes sample data from the database using a DML statement.""" @@ -309,27 +365,63 @@ def delete_data_with_dml(instance_id, database_id): instance = spanner_client.instance(instance_id) database = instance.database(database_id) - def delete_persons(transaction): + def delete_transfers(transaction): row_ct = transaction.execute_update( - "DELETE FROM Person WHERE True" + "DELETE FROM AccountTransferAccount WHERE id = 1 AND to_id = 2" ) print("{} record(s) deleted.".format(row_ct)) def delete_accounts(transaction): row_ct = transaction.execute_update( - "DELETE FROM Account AS a WHERE EXTRACT(YEAR FROM DATE(a.create_time)) >= 2000" + "DELETE FROM Account WHERE id = 2" ) print("{} record(s) deleted.".format(row_ct)) + database.run_in_transaction(delete_transfers) database.run_in_transaction(delete_accounts) - database.run_in_transaction(delete_persons) # [END spanner_delete_graph_data_with_dml] +# [START spanner_delete_data] +def delete_data(instance_id, database_id): + """Deletes sample data from the given database. + + The database, table, and data must already exist and can be created using + `create_database` and `insert_data`. + """ + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + # Delete individual rows + ownerships_to_delete = spanner.KeySet(keys=[[1, 7], [2, 20]]) + + # Delete a range of rows where the column key is >=1 and <8 + transfers_range = spanner.KeyRange(start_closed=[1], end_open=[8]) + transfers_to_delete = spanner.KeySet(ranges=[transfers_range]) + + # Delete Account/Person rows, which will also delete the remaining + # AccountTransferAccount and PersonOwnAccount rows because + # AccountTransferAccount and PersonOwnAccount are defined with + # ON DELETE CASCADE + remaining_nodes = spanner.KeySet(all_=True) + + with database.batch() as batch: + batch.delete("PersonOwnAccount", ownerships_to_delete) + batch.delete("AccountTransferAccount", transfers_to_delete) + batch.delete("Account", remaining_nodes) + batch.delete("Person", remaining_nodes) + + print("Deleted data.") + + +# [END spanner_delete_data] + + if __name__ == "__main__": # noqa: C901 parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter @@ -346,30 +438,34 @@ def delete_accounts(transaction): help=create_database_with_property_graph.__doc__) subparsers.add_parser("insert_data", help=insert_data.__doc__) subparsers.add_parser("insert_data_with_dml", help=insert_data_with_dml.__doc__) + subparsers.add_parser("update_data_with_dml", help=update_data_with_dml.__doc__) + subparsers.add_parser("update_data_with_graph_query_in_dml", + help=update_data_with_graph_query_in_dml.__doc__) subparsers.add_parser("query_data", help=query_data.__doc__) subparsers.add_parser( "query_data_with_parameter", help=query_data_with_parameter.__doc__ ) - subparsers.add_parser("delete_data", help=delete_data.__doc__) subparsers.add_parser("delete_data_with_dml", help=delete_data_with_dml.__doc__) args = parser.parse_args() - if args.command == "create_instance": - create_instance(args.instance_id) - elif args.command == "create_database_with_property_graph": + if args.command == "create_database_with_property_graph": create_database_with_property_graph(args.instance_id, args.database_id) elif args.command == "insert_data": insert_data(args.instance_id, args.database_id) elif args.command == "insert_data_with_dml": insert_data_with_dml(args.instance_id, args.database_id) + elif args.command == "update_data_with_dml": + update_data_with_dml(args.instance_id, args.database_id) + elif args.command == "update_data_with_graph_query_in_dml": + update_data_with_graph_query_in_dml(args.instance_id, args.database_id) elif args.command == "query_data": query_data(args.instance_id, args.database_id) elif args.command == "query_data_with_parameter": query_data_with_parameter(args.instance_id, args.database_id) - elif args.command == "delete_data": - delete_data(args.instance_id, args.database_id) elif args.command == "delete_data_with_dml": delete_data_with_dml(args.instance_id, args.database_id) + elif args.command == "delete_data": + delete_data(args.instance_id, args.database_id) From fb7144f3a302de27aa5a10bc6ad3636f08ff166f Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Mon, 22 Jul 2024 12:20:48 -0700 Subject: [PATCH 03/10] Fix update with graph query predicate syntax --- samples/samples/graph_snippets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/samples/graph_snippets.py b/samples/samples/graph_snippets.py index 33766689d0..5d99e4190d 100644 --- a/samples/samples/graph_snippets.py +++ b/samples/samples/graph_snippets.py @@ -292,10 +292,10 @@ def update_data_with_graph_query_in_dml(instance_id, database_id): def update_accounts(transaction): row_ct = transaction.execute_update( "UPDATE Account SET is_blocked = true " - "WHERE id IN (" + "WHERE id IN {" " GRAPH FinGraph" " MATCH (a:Account WHERE a.id = 1)-[:TRANSFERS]->{1,2}(b:Account)" - " RETURN b.id)" + " RETURN b.id}" ) print("{} record(s) updated.".format(row_ct)) From cb6402dec0e9d788133a9cc8956fdfa25cd1fe16 Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Mon, 22 Jul 2024 13:03:23 -0700 Subject: [PATCH 04/10] Added an update step for allowing commit timestamps and changed to schema to not have that option --- samples/samples/graph_snippets.py | 75 +++++++++++++++---------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/samples/samples/graph_snippets.py b/samples/samples/graph_snippets.py index 5d99e4190d..0a8ec90946 100644 --- a/samples/samples/graph_snippets.py +++ b/samples/samples/graph_snippets.py @@ -38,42 +38,6 @@ OPERATION_TIMEOUT_SECONDS = 240 -# [START spanner_create_instance] -def create_instance(instance_id): - """Creates an instance.""" - from google.cloud.spanner_admin_instance_v1.types import \ - spanner_instance_admin - - spanner_client = spanner.Client() - - config_name = "{}/instanceConfigs/regional-us-central1".format( - spanner_client.project_name - ) - - operation = spanner_client.instance_admin_api.create_instance( - parent=spanner_client.project_name, - instance_id=instance_id, - instance=spanner_instance_admin.Instance( - config=config_name, - display_name="This is a display name.", - node_count=1, - labels={ - "cloud_spanner_samples": "true", - "sample_name": "snippets-create_instance-explicit", - "created": str(int(time.time())), - }, - ), - ) - - print("Waiting for operation to complete...") - operation.result(OPERATION_TIMEOUT_SECONDS) - - print("Created instance {}".format(instance_id)) - - -# [END spanner_create_instance] - - # [START spanner_create_database_with_property_graph] def create_database_with_property_graph(instance_id, database_id): """Creates a database, tables and a property graph for sample data.""" @@ -113,8 +77,7 @@ def create_database_with_property_graph(instance_id, database_id): id INT64 NOT NULL, to_id INT64 NOT NULL, amount FLOAT64, - create_time TIMESTAMP NOT NULL OPTIONS - (allow_commit_timestamp=true), + create_time TIMESTAMP NOT NULL, order_number STRING(MAX), FOREIGN KEY (to_id) REFERENCES Account (id) ) PRIMARY KEY (id, to_id, create_time), @@ -149,6 +112,37 @@ def create_database_with_property_graph(instance_id, database_id): # [END spanner_create_database_with_property_graph] +# [START spanner_update_allow_commit_timestamps] +def update_allow_commit_timestamps(instance_id, database_id): + """Alters table column(s) to support commit timestamps.""" + + from google.cloud.spanner_admin_database_v1.types import \ + spanner_database_admin + + spanner_client = spanner.Client() + database_admin_api = spanner_client.database_admin_api + + request = spanner_database_admin.UpdateDatabaseDdlRequest( + database=database_admin_api.database_path( + spanner_client.project, instance_id, database_id + ), + statements=[ + """ALTER TABLE AccountTransferAccount + ALTER COLUMN create_time + SET OPTIONS (allow_commit_timestamp = true)"""], + ) + + operation = database_admin_api.update_database_ddl(request) + + print("Waiting for operation to complete...") + operation.result(OPERATION_TIMEOUT_SECONDS) + + print("Updated the AccountTransferAccountTable.") + + +# [END spanner_update_allow_commit_timestamps] + + # [START spanner_insert_graph_data] def insert_data(instance_id, database_id): """Inserts sample data into the given database. @@ -432,10 +426,11 @@ def delete_data(instance_id, database_id): ) subparsers = parser.add_subparsers(dest="command") - subparsers.add_parser("create_instance", help=create_instance.__doc__) subparsers.add_parser( "create_database_with_property_graph", help=create_database_with_property_graph.__doc__) + subparsers.add_parser("update_allow_commit_timestamps", + help=update_allow_commit_timestamps.__doc__) subparsers.add_parser("insert_data", help=insert_data.__doc__) subparsers.add_parser("insert_data_with_dml", help=insert_data_with_dml.__doc__) subparsers.add_parser("update_data_with_dml", help=update_data_with_dml.__doc__) @@ -452,6 +447,8 @@ def delete_data(instance_id, database_id): if args.command == "create_database_with_property_graph": create_database_with_property_graph(args.instance_id, args.database_id) + elif args.command == "update_allow_commit_timestamps": + update_allow_commit_timestamps(args.instance_id, args.database_id) elif args.command == "insert_data": insert_data(args.instance_id, args.database_id) elif args.command == "insert_data_with_dml": From 1341a4f6f14d90add5159663ff6c529e625a5ffc Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Wed, 31 Jul 2024 01:49:23 -0700 Subject: [PATCH 05/10] Fix styling using flake8 --- samples/samples/graph_snippets.py | 32 +++++++++---------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/samples/samples/graph_snippets.py b/samples/samples/graph_snippets.py index 0a8ec90946..9d6496949d 100644 --- a/samples/samples/graph_snippets.py +++ b/samples/samples/graph_snippets.py @@ -21,19 +21,8 @@ """ import argparse -import base64 -import datetime -import decimal -import json -import logging -import time from google.cloud import spanner -from google.cloud.spanner_admin_instance_v1.types import spanner_instance_admin -from google.cloud.spanner_v1 import DirectedReadOptions, param_types -from google.cloud.spanner_v1.data_types import JsonObject -from google.protobuf import field_mask_pb2 # type: ignore -from testdata import singer_pb2 OPERATION_TIMEOUT_SECONDS = 240 @@ -54,7 +43,6 @@ def create_database_with_property_graph(instance_id, database_id): """CREATE TABLE Person ( id INT64 NOT NULL, name STRING(MAX), - gender STRING(40), birthday TIMESTAMP, country STRING(MAX), city STRING(MAX), @@ -167,11 +155,11 @@ def insert_data(instance_id, database_id): batch.insert( table="Person", - columns=("id", "name", "gender", "birthday", "country", "city"), + columns=("id", "name", "birthday", "country", "city"), values=[ - (1, "Alex", "male", '1991-12-21T00:00:00.12Z', "Australia"," Adelaide"), - (2, "Dana", "female", '1980-10-31T00:00:00.12Z',"Czech_Republic", "Moravia"), - (3, "Lee", "male", '1986-12-07T00:00:00.12Z', "India", "Kollam") + (1, "Alex", '1991-12-21T00:00:00.12Z', "Australia"," Adelaide"), + (2, "Dana", '1980-10-31T00:00:00.12Z',"Czech_Republic", "Moravia"), + (3, "Lee", '1986-12-07T00:00:00.12Z', "India", "Kollam") ], ) @@ -206,7 +194,7 @@ def insert_data(instance_id, database_id): # [START spanner_insert_graph_data_with_dml] def insert_data_with_dml(instance_id, database_id): """Inserts sample data into the given database using a DML statement.""" - + # instance_id = "your-spanner-instance" # database_id = "your-spanner-db-id" @@ -234,7 +222,6 @@ def insert_transfers(transaction): print("{} record(s) inserted into AccountTransferAccount.".format(row_ct)) - database.run_in_transaction(insert_accounts) database.run_in_transaction(insert_transfers) @@ -324,7 +311,7 @@ def query_data(instance_id, database_id): # [START spanner_with_graph_query_data_with_parameter] def query_data_with_parameter(instance_id, database_id): """Queries sample data from the database using SQL with a parameter.""" - + # instance_id = "your-spanner-instance" # database_id = "your-spanner-db-id" spanner_client = spanner.Client() @@ -351,7 +338,7 @@ def query_data_with_parameter(instance_id, database_id): # [START spanner_delete_graph_data_with_dml] def delete_data_with_dml(instance_id, database_id): """Deletes sample data from the database using a DML statement.""" - + # instance_id = "your-spanner-instance" # database_id = "your-spanner-db-id" @@ -430,12 +417,12 @@ def delete_data(instance_id, database_id): "create_database_with_property_graph", help=create_database_with_property_graph.__doc__) subparsers.add_parser("update_allow_commit_timestamps", - help=update_allow_commit_timestamps.__doc__) + help=update_allow_commit_timestamps.__doc__) subparsers.add_parser("insert_data", help=insert_data.__doc__) subparsers.add_parser("insert_data_with_dml", help=insert_data_with_dml.__doc__) subparsers.add_parser("update_data_with_dml", help=update_data_with_dml.__doc__) subparsers.add_parser("update_data_with_graph_query_in_dml", - help=update_data_with_graph_query_in_dml.__doc__) + help=update_data_with_graph_query_in_dml.__doc__) subparsers.add_parser("query_data", help=query_data.__doc__) subparsers.add_parser( "query_data_with_parameter", help=query_data_with_parameter.__doc__ @@ -465,4 +452,3 @@ def delete_data(instance_id, database_id): delete_data_with_dml(args.instance_id, args.database_id) elif args.command == "delete_data": delete_data(args.instance_id, args.database_id) - From 7879f72146485da5ba481c5bd1f86e21d4d56f7c Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Fri, 9 Aug 2024 09:55:37 -0700 Subject: [PATCH 06/10] Add tests for new Spanner Graph snippets --- samples/samples/graph_snippets.py | 73 ++++----- samples/samples/graph_snippets_test.py | 214 +++++++++++++++++++++++++ 2 files changed, 251 insertions(+), 36 deletions(-) create mode 100644 samples/samples/graph_snippets_test.py diff --git a/samples/samples/graph_snippets.py b/samples/samples/graph_snippets.py index 9d6496949d..562fafe8b3 100644 --- a/samples/samples/graph_snippets.py +++ b/samples/samples/graph_snippets.py @@ -30,8 +30,7 @@ # [START spanner_create_database_with_property_graph] def create_database_with_property_graph(instance_id, database_id): """Creates a database, tables and a property graph for sample data.""" - from google.cloud.spanner_admin_database_v1.types import \ - spanner_database_admin + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin spanner_client = spanner.Client() database_admin_api = spanner_client.database_admin_api @@ -104,8 +103,7 @@ def create_database_with_property_graph(instance_id, database_id): def update_allow_commit_timestamps(instance_id, database_id): """Alters table column(s) to support commit timestamps.""" - from google.cloud.spanner_admin_database_v1.types import \ - spanner_database_admin + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin spanner_client = spanner.Client() database_admin_api = spanner_client.database_admin_api @@ -117,7 +115,8 @@ def update_allow_commit_timestamps(instance_id, database_id): statements=[ """ALTER TABLE AccountTransferAccount ALTER COLUMN create_time - SET OPTIONS (allow_commit_timestamp = true)"""], + SET OPTIONS (allow_commit_timestamp = true)""" + ], ) operation = database_admin_api.update_database_ddl(request) @@ -147,9 +146,9 @@ def insert_data(instance_id, database_id): table="Account", columns=("id", "create_time", "is_blocked", "nick_name"), values=[ - (7, '2020-01-10T06:22:20.12Z', False, "Vacation Fund"), - (16, '2020-01-27T17:55:09.12Z', True, "Vacation Fund"), - (20, '2020-02-18T05:44:20.12Z', False, "Rainy Day Fund") + (7, "2020-01-10T06:22:20.12Z", False, "Vacation Fund"), + (16, "2020-01-27T17:55:09.12Z", True, "Vacation Fund"), + (20, "2020-02-18T05:44:20.12Z", False, "Rainy Day Fund"), ], ) @@ -157,9 +156,9 @@ def insert_data(instance_id, database_id): table="Person", columns=("id", "name", "birthday", "country", "city"), values=[ - (1, "Alex", '1991-12-21T00:00:00.12Z', "Australia"," Adelaide"), - (2, "Dana", '1980-10-31T00:00:00.12Z',"Czech_Republic", "Moravia"), - (3, "Lee", '1986-12-07T00:00:00.12Z', "India", "Kollam") + (1, "Alex", "1991-12-21T00:00:00.12Z", "Australia", " Adelaide"), + (2, "Dana", "1980-10-31T00:00:00.12Z", "Czech_Republic", "Moravia"), + (3, "Lee", "1986-12-07T00:00:00.12Z", "India", "Kollam"), ], ) @@ -167,11 +166,11 @@ def insert_data(instance_id, database_id): table="AccountTransferAccount", columns=("id", "to_id", "amount", "create_time", "order_number"), values=[ - (7, 16, 300.0, '2020-08-29T15:28:58.12Z', "304330008004315"), - (7, 16, 100.0, '2020-10-04T16:55:05.12Z', "304120005529714"), - (16, 20, 300.0, '2020-09-25T02:36:14.12Z', "103650009791820"), - (20, 7, 500.0, '2020-10-04T16:55:05.12Z', "304120005529714"), - (20, 16, 200.0, '2020-10-17T03:59:40.12Z', "302290001255747") + (7, 16, 300.0, "2020-08-29T15:28:58.12Z", "304330008004315"), + (7, 16, 100.0, "2020-10-04T16:55:05.12Z", "304120005529714"), + (16, 20, 300.0, "2020-09-25T02:36:14.12Z", "103650009791820"), + (20, 7, 500.0, "2020-10-04T16:55:05.12Z", "304120005529714"), + (20, 16, 200.0, "2020-10-17T03:59:40.12Z", "302290001255747"), ], ) @@ -179,10 +178,10 @@ def insert_data(instance_id, database_id): table="PersonOwnAccount", columns=("id", "account_id", "create_time"), values=[ - (1, 7, '2020-01-10T06:22:20.12Z'), - (2, 20, '2020-01-27T17:55:09.12Z'), - (3, 16, '2020-02-18T05:44:20.12Z') - ] + (1, 7, "2020-01-10T06:22:20.12Z"), + (2, 20, "2020-01-27T17:55:09.12Z"), + (3, 16, "2020-02-18T05:44:20.12Z"), + ], ) print("Inserted data.") @@ -207,7 +206,7 @@ def insert_accounts(transaction): "INSERT INTO Account (id, create_time, is_blocked) " " VALUES" " (1, CAST('2000-08-10 08:18:48.463959-07:52' AS TIMESTAMP), false)," - " (2, CAST('2000-08-12 08:18:48.463959-07:52' AS TIMESTAMP), true)" + " (2, CAST('2000-08-12 07:13:16.463959-03:41' AS TIMESTAMP), true)" ) print("{} record(s) inserted into Account.".format(row_ct)) @@ -216,8 +215,8 @@ def insert_transfers(transaction): row_ct = transaction.execute_update( "INSERT INTO AccountTransferAccount (id, to_id, create_time, amount) " " VALUES" - " (1, 2, PENDING_COMMIT_TIMESTAMP(), 100)," - " (1, 1, PENDING_COMMIT_TIMESTAMP(), 200) " + " (1, 2, CAST('2000-09-11 03:11:18.463959-06:36' AS TIMESTAMP), 100)," + " (1, 1, CAST('2000-09-12 04:09:34.463959-05:12' AS TIMESTAMP), 200) " ) print("{} record(s) inserted into AccountTransferAccount.".format(row_ct)) @@ -244,14 +243,14 @@ def update_accounts(transaction): "UPDATE Account SET is_blocked = false WHERE id = 2" ) - print("{} record(s) updated.".format(row_ct)) + print("{} Account record(s) updated.".format(row_ct)) def update_transfers(transaction): row_ct = transaction.execute_update( "UPDATE AccountTransferAccount SET amount = 300 WHERE id = 1 AND to_id = 2" ) - print("{} record(s) updated.".format(row_ct)) + print("{} AccountTransferAccount record(s) updated.".format(row_ct)) database.run_in_transaction(update_accounts) database.run_in_transaction(update_transfers) @@ -279,7 +278,7 @@ def update_accounts(transaction): " RETURN b.id}" ) - print("{} record(s) updated.".format(row_ct)) + print("{} Account record(s) updated.".format(row_ct)) database.run_in_transaction(update_accounts) @@ -351,14 +350,12 @@ def delete_transfers(transaction): "DELETE FROM AccountTransferAccount WHERE id = 1 AND to_id = 2" ) - print("{} record(s) deleted.".format(row_ct)) + print("{} AccountTransferAccount record(s) deleted.".format(row_ct)) def delete_accounts(transaction): - row_ct = transaction.execute_update( - "DELETE FROM Account WHERE id = 2" - ) + row_ct = transaction.execute_update("DELETE FROM Account WHERE id = 2") - print("{} record(s) deleted.".format(row_ct)) + print("{} Account record(s) deleted.".format(row_ct)) database.run_in_transaction(delete_transfers) database.run_in_transaction(delete_accounts) @@ -415,14 +412,18 @@ def delete_data(instance_id, database_id): subparsers = parser.add_subparsers(dest="command") subparsers.add_parser( "create_database_with_property_graph", - help=create_database_with_property_graph.__doc__) - subparsers.add_parser("update_allow_commit_timestamps", - help=update_allow_commit_timestamps.__doc__) + help=create_database_with_property_graph.__doc__, + ) + subparsers.add_parser( + "update_allow_commit_timestamps", help=update_allow_commit_timestamps.__doc__ + ) subparsers.add_parser("insert_data", help=insert_data.__doc__) subparsers.add_parser("insert_data_with_dml", help=insert_data_with_dml.__doc__) subparsers.add_parser("update_data_with_dml", help=update_data_with_dml.__doc__) - subparsers.add_parser("update_data_with_graph_query_in_dml", - help=update_data_with_graph_query_in_dml.__doc__) + subparsers.add_parser( + "update_data_with_graph_query_in_dml", + help=update_data_with_graph_query_in_dml.__doc__, + ) subparsers.add_parser("query_data", help=query_data.__doc__) subparsers.add_parser( "query_data_with_parameter", help=query_data_with_parameter.__doc__ diff --git a/samples/samples/graph_snippets_test.py b/samples/samples/graph_snippets_test.py new file mode 100644 index 0000000000..da01df36d9 --- /dev/null +++ b/samples/samples/graph_snippets_test.py @@ -0,0 +1,214 @@ +# Copyright 2024 Google, Inc. +# +# 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 +# +# http://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. + +# import time +import uuid +import pytest + +from google.api_core import exceptions + +from google.cloud import spanner +from google.cloud.spanner_admin_database_v1.types.common import DatabaseDialect +from test_utils.retry import RetryErrors + +import graph_snippets + +retry_429 = RetryErrors(exceptions.ResourceExhausted, delay=15) + +CREATE_TABLE_PERSON = """\ +CREATE TABLE Person ( + id INT64 NOT NULL, + name STRING(MAX), + birthday TIMESTAMP, + country STRING(MAX), + city STRING(MAX), +) PRIMARY KEY (id) +""" + +CREATE_TABLE_ACCOUNT = """\ + CREATE TABLE Account ( + id INT64 NOT NULL, + create_time TIMESTAMP, + is_blocked BOOL, + nick_name STRING(MAX), + ) PRIMARY KEY (id) +""" + +CREATE_TABLE_PERSON_OWN_ACCOUNT = """\ +CREATE TABLE PersonOwnAccount ( + id INT64 NOT NULL, + account_id INT64 NOT NULL, + create_time TIMESTAMP, + FOREIGN KEY (account_id) + REFERENCES Account (id) + ) PRIMARY KEY (id, account_id), + INTERLEAVE IN PARENT Person ON DELETE CASCADE +""" + +CREATE_TABLE_ACCOUNT_TRANSFER_ACCOUNT = """\ +CREATE TABLE AccountTransferAccount ( + id INT64 NOT NULL, + to_id INT64 NOT NULL, + amount FLOAT64, + create_time TIMESTAMP NOT NULL, + order_number STRING(MAX), + FOREIGN KEY (to_id) REFERENCES Account (id) + ) PRIMARY KEY (id, to_id, create_time), + INTERLEAVE IN PARENT Account ON DELETE CASCADE +""" + +CREATE_PROPERTY_GRAPH = """ +CREATE OR REPLACE PROPERTY GRAPH FinGraph + NODE TABLES (Account, Person) + EDGE TABLES ( + PersonOwnAccount + SOURCE KEY(id) REFERENCES Person(id) + DESTINATION KEY(account_id) REFERENCES Account(id) + LABEL Owns, + AccountTransferAccount + SOURCE KEY(id) REFERENCES Account(id) + DESTINATION KEY(to_id) REFERENCES Account(id) + LABEL Transfers) +""" + + +@pytest.fixture(scope="module") +def sample_name(): + return "snippets" + + +@pytest.fixture(scope="module") +def database_dialect(): + """Spanner dialect to be used for this sample. + + The dialect is used to initialize the dialect for the database. + It can either be GoogleStandardSql or PostgreSql. + """ + return DatabaseDialect.GOOGLE_STANDARD_SQL + + +@pytest.fixture(scope="module") +def database_id(): + return f"test-db-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def create_database_id(): + return f"create-db-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(scope="module") +def database_ddl(): + """Sequence of DDL statements used to set up the database. + + Sample testcase modules can override as needed. + """ + return [ + CREATE_TABLE_PERSON, + CREATE_TABLE_ACCOUNT, + CREATE_TABLE_PERSON_OWN_ACCOUNT, + CREATE_TABLE_ACCOUNT_TRANSFER_ACCOUNT, + CREATE_PROPERTY_GRAPH, + ] + + +def test_create_database_explicit(sample_instance, create_database_id): + graph_snippets.create_database_with_property_graph( + sample_instance.instance_id, create_database_id + ) + database = sample_instance.database(create_database_id) + database.drop() + + +@pytest.mark.dependency(name="insert_data") +def test_insert_data(capsys, instance_id, sample_database): + graph_snippets.insert_data(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert "Inserted data" in out + + +@pytest.mark.dependency(depends=["insert_data"]) +def test_query_data(capsys, instance_id, sample_database): + graph_snippets.query_data(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert ( + "sender: Dana, receiver: Alex, amount: 500.0, transfer_at: 2020-10-04 16:55:05.120000+00:00" + in out + ) + assert ( + "sender: Lee, receiver: Dana, amount: 300.0, transfer_at: 2020-09-25 02:36:14.120000+00:00" + in out + ) + assert ( + "sender: Alex, receiver: Lee, amount: 300.0, transfer_at: 2020-08-29 15:28:58.120000+00:00" + in out + ) + assert ( + "sender: Alex, receiver: Lee, amount: 100.0, transfer_at: 2020-10-04 16:55:05.120000+00:00" + in out + ) + assert ( + "sender: Dana, receiver: Lee, amount: 200.0, transfer_at: 2020-10-17 03:59:40.120000+00:00" + in out + ) + + +@pytest.mark.dependency(depends=["insert_data"]) +def test_query_data_with_parameter(capsys, instance_id, sample_database): + graph_snippets.query_data_with_parameter(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert ( + "sender: Dana, receiver: Alex, amount: 500.0, transfer_at: 2020-10-04 16:55:05.120000+00:00" + in out + ) + + +@pytest.mark.dependency(name="insert_data_with_dml", depends=["insert_data"]) +def test_insert_data_with_dml(capsys, instance_id, sample_database): + graph_snippets.insert_data_with_dml(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert "2 record(s) inserted into Account." in out + assert "2 record(s) inserted into AccountTransferAccount." in out + + +@pytest.mark.dependency(name="update_data_with_dml", depends=["insert_data_with_dml"]) +def test_update_data_with_dml(capsys, instance_id, sample_database): + graph_snippets.update_data_with_dml(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert "1 Account record(s) updated." in out + assert "1 AccountTransferAccount record(s) updated." in out + + +@pytest.mark.dependency(depends=["update_data_with_dml"]) +def test_update_data_with_graph_query_in_dml(capsys, instance_id, sample_database): + graph_snippets.update_data_with_graph_query_in_dml( + instance_id, sample_database.database_id + ) + out, _ = capsys.readouterr() + assert "2 Account record(s) updated." in out + + +@pytest.mark.dependency(depends=["update_data_with_dml"]) +def test_delete_data_with_graph_query_in_dml(capsys, instance_id, sample_database): + graph_snippets.delete_data_with_dml(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert "1 AccountTransferAccount record(s) deleted." in out + assert "1 Account record(s) deleted." in out + + +@pytest.mark.dependency(depends=["insert_data"]) +def test_delete_data_with_graph_query_in_dml(capsys, instance_id, sample_database): + graph_snippets.delete_data(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert "Deleted data." in out From 9776bcea0ea761eed7bb1196de882fc7a85ffab6 Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Thu, 15 Aug 2024 23:08:19 -0700 Subject: [PATCH 07/10] Fix some region tags that were inconsistent --- samples/samples/graph_snippets.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/samples/graph_snippets.py b/samples/samples/graph_snippets.py index 562fafe8b3..912a7fd65f 100644 --- a/samples/samples/graph_snippets.py +++ b/samples/samples/graph_snippets.py @@ -283,7 +283,7 @@ def update_accounts(transaction): database.run_in_transaction(update_accounts) -# [END spanner_update_graph_data_with_graph_query_in_dml +# [END spanner_update_graph_data_with_graph_query_in_dml] # [START spanner_query_graph_data] @@ -307,7 +307,7 @@ def query_data(instance_id, database_id): # [END spanner_query_graph_data] -# [START spanner_with_graph_query_data_with_parameter] +# [START spanner_query_graph_data_with_parameter] def query_data_with_parameter(instance_id, database_id): """Queries sample data from the database using SQL with a parameter.""" @@ -331,7 +331,7 @@ def query_data_with_parameter(instance_id, database_id): print("sender: {}, receiver: {}, amount: {}, transfer_at: {}".format(*row)) -# [END spanner_with_graph_query_data_with_parameter] +# [END spanner_query_graph_data_with_parameter] # [START spanner_delete_graph_data_with_dml] @@ -364,7 +364,7 @@ def delete_accounts(transaction): # [END spanner_delete_graph_data_with_dml] -# [START spanner_delete_data] +# [START spanner_delete_graph_data] def delete_data(instance_id, database_id): """Deletes sample data from the given database. @@ -397,7 +397,7 @@ def delete_data(instance_id, database_id): print("Deleted data.") -# [END spanner_delete_data] +# [END spanner_delete_graph_data] if __name__ == "__main__": # noqa: C901 From 5d04571d1e16cc4da16d0156f33b3db1094ecbf3 Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Thu, 22 Aug 2024 22:06:13 -0700 Subject: [PATCH 08/10] Remove one unnecessary function and some redundant comments --- samples/samples/graph_snippets.py | 43 ------------------------------- 1 file changed, 43 deletions(-) diff --git a/samples/samples/graph_snippets.py b/samples/samples/graph_snippets.py index 912a7fd65f..7a67401d02 100644 --- a/samples/samples/graph_snippets.py +++ b/samples/samples/graph_snippets.py @@ -99,37 +99,6 @@ def create_database_with_property_graph(instance_id, database_id): # [END spanner_create_database_with_property_graph] -# [START spanner_update_allow_commit_timestamps] -def update_allow_commit_timestamps(instance_id, database_id): - """Alters table column(s) to support commit timestamps.""" - - from google.cloud.spanner_admin_database_v1.types import spanner_database_admin - - spanner_client = spanner.Client() - database_admin_api = spanner_client.database_admin_api - - request = spanner_database_admin.UpdateDatabaseDdlRequest( - database=database_admin_api.database_path( - spanner_client.project, instance_id, database_id - ), - statements=[ - """ALTER TABLE AccountTransferAccount - ALTER COLUMN create_time - SET OPTIONS (allow_commit_timestamp = true)""" - ], - ) - - operation = database_admin_api.update_database_ddl(request) - - print("Waiting for operation to complete...") - operation.result(OPERATION_TIMEOUT_SECONDS) - - print("Updated the AccountTransferAccountTable.") - - -# [END spanner_update_allow_commit_timestamps] - - # [START spanner_insert_graph_data] def insert_data(instance_id, database_id): """Inserts sample data into the given database. @@ -194,9 +163,6 @@ def insert_data(instance_id, database_id): def insert_data_with_dml(instance_id, database_id): """Inserts sample data into the given database using a DML statement.""" - # 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) @@ -231,8 +197,6 @@ def insert_transfers(transaction): # [START spanner_update_graph_data_with_dml] def update_data_with_dml(instance_id, database_id): """Updates sample data from the database using a DML statement.""" - # instance_id = "your-spanner-instance" - # database_id = "your-spanner-db-id" spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) @@ -262,8 +226,6 @@ def update_transfers(transaction): # [START spanner_update_graph_data_with_graph_query_in_dml] def update_data_with_graph_query_in_dml(instance_id, database_id): """Updates sample data from the database using a DML statement.""" - # instance_id = "your-spanner-instance" - # database_id = "your-spanner-db-id" spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) @@ -311,8 +273,6 @@ def query_data(instance_id, database_id): def query_data_with_parameter(instance_id, database_id): """Queries sample data from the database using SQL with a parameter.""" - # 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) @@ -338,9 +298,6 @@ def query_data_with_parameter(instance_id, database_id): def delete_data_with_dml(instance_id, database_id): """Deletes sample data from the database using a DML statement.""" - # 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) From b7af9a14b307c11ebe16311e9dda163a3c0ce6a6 Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Thu, 22 Aug 2024 22:37:14 -0700 Subject: [PATCH 09/10] Remove reference to allow_commit_timestamp --- samples/samples/graph_snippets.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/samples/samples/graph_snippets.py b/samples/samples/graph_snippets.py index 7a67401d02..e557290b19 100644 --- a/samples/samples/graph_snippets.py +++ b/samples/samples/graph_snippets.py @@ -371,9 +371,6 @@ def delete_data(instance_id, database_id): "create_database_with_property_graph", help=create_database_with_property_graph.__doc__, ) - subparsers.add_parser( - "update_allow_commit_timestamps", help=update_allow_commit_timestamps.__doc__ - ) subparsers.add_parser("insert_data", help=insert_data.__doc__) subparsers.add_parser("insert_data_with_dml", help=insert_data_with_dml.__doc__) subparsers.add_parser("update_data_with_dml", help=update_data_with_dml.__doc__) @@ -392,8 +389,6 @@ def delete_data(instance_id, database_id): if args.command == "create_database_with_property_graph": create_database_with_property_graph(args.instance_id, args.database_id) - elif args.command == "update_allow_commit_timestamps": - update_allow_commit_timestamps(args.instance_id, args.database_id) elif args.command == "insert_data": insert_data(args.instance_id, args.database_id) elif args.command == "insert_data_with_dml": From 0983949097f1025b81f01f3a43c9951b472825ef Mon Sep 17 00:00:00 2001 From: "Bharadwaj V.R." Date: Mon, 26 Aug 2024 10:17:22 -0700 Subject: [PATCH 10/10] Fix lint issues in test file --- samples/samples/graph_snippets_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/samples/graph_snippets_test.py b/samples/samples/graph_snippets_test.py index da01df36d9..bd49260007 100644 --- a/samples/samples/graph_snippets_test.py +++ b/samples/samples/graph_snippets_test.py @@ -18,7 +18,6 @@ from google.api_core import exceptions -from google.cloud import spanner from google.cloud.spanner_admin_database_v1.types.common import DatabaseDialect from test_utils.retry import RetryErrors @@ -208,7 +207,7 @@ def test_delete_data_with_graph_query_in_dml(capsys, instance_id, sample_databas @pytest.mark.dependency(depends=["insert_data"]) -def test_delete_data_with_graph_query_in_dml(capsys, instance_id, sample_database): +def test_delete_data(capsys, instance_id, sample_database): graph_snippets.delete_data(instance_id, sample_database.database_id) out, _ = capsys.readouterr() assert "Deleted data." in out