Skip to content

Commit 330c8e6

Browse files
caohy1988claude
andcommitted
feat(bq-plugin): Reset schema version to 1, default auto_schema_upgrade to True
- _SCHEMA_VERSION starts at "1" (not "3"); future schema changes bump 1 → 2 → 3 … - auto_schema_upgrade now defaults to True because the upgrade is additive-only, idempotent, and fail-safe. - No need to store previous schema versions: the upgrade logic diffs actual table columns against the current canonical schema and only appends what's missing. - Added 7 new schema tests: preserves existing columns, handles no-label tables, upgrades from older version labels, idempotent on second call, verifies update_fields, default config, and create_table Conflict handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c1226c5 commit 330c8e6

File tree

2 files changed

+138
-6
lines changed

2 files changed

+138
-6
lines changed

src/google/adk/plugins/bigquery_agent_analytics_plugin.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@
7070
"google.adk.plugins.bigquery_agent_analytics", __version__
7171
)
7272

73-
# Bumped when the schema changes. Used as a table label for
74-
# governance and to decide whether auto-upgrade should run.
75-
_SCHEMA_VERSION = "3"
73+
# Bumped when the schema changes (1 → 2 → 3 …). Used as a table
74+
# label for governance and to decide whether auto-upgrade should run.
75+
_SCHEMA_VERSION = "1"
7676
_SCHEMA_VERSION_LABEL_KEY = "adk_schema_version"
7777

7878
# Human-in-the-loop (HITL) tool names that receive additional
@@ -493,9 +493,11 @@ class BigQueryLoggerConfig:
493493
log_session_metadata: bool = True
494494
# Static custom tags (e.g. {"agent_role": "sales"})
495495
custom_tags: dict[str, Any] = field(default_factory=dict)
496-
# If True, automatically add new columns to existing tables
497-
# when the plugin schema evolves. Only additive changes are made.
498-
auto_schema_upgrade: bool = False
496+
# Automatically add new columns to existing tables when the plugin
497+
# schema evolves. Only additive changes are made (columns are never
498+
# dropped or altered). Safe to leave enabled; a version label on the
499+
# table ensures the diff runs at most once per schema version.
500+
auto_schema_upgrade: bool = True
499501

500502

501503
# ==============================================================================

tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4044,6 +4044,136 @@ def test_upgrade_error_is_logged_not_raised(self):
40444044
# Should not raise
40454045
plugin._ensure_schema_exists()
40464046

4047+
def test_upgrade_preserves_existing_columns(self):
4048+
"""Existing columns are never dropped or altered during upgrade."""
4049+
plugin = self._make_plugin(auto_schema_upgrade=True)
4050+
# Simulate a table with a subset of canonical columns plus a
4051+
# user-added custom column that is NOT in the canonical schema.
4052+
custom_field = bigquery.SchemaField("my_custom_col", "STRING")
4053+
existing = mock.MagicMock(spec=bigquery.Table)
4054+
existing.schema = [
4055+
bigquery.SchemaField("timestamp", "TIMESTAMP"),
4056+
bigquery.SchemaField("event_type", "STRING"),
4057+
custom_field,
4058+
]
4059+
existing.labels = {}
4060+
plugin.client.get_table.return_value = existing
4061+
plugin._ensure_schema_exists()
4062+
4063+
updated_table = plugin.client.update_table.call_args[0][0]
4064+
updated_names = [f.name for f in updated_table.schema]
4065+
# Original columns are still present and in original order.
4066+
assert updated_names[0] == "timestamp"
4067+
assert updated_names[1] == "event_type"
4068+
assert updated_names[2] == "my_custom_col"
4069+
# New canonical columns were appended after existing ones.
4070+
assert "agent" in updated_names
4071+
assert "content" in updated_names
4072+
4073+
def test_upgrade_from_no_label_treats_as_outdated(self):
4074+
"""A table with no version label is treated as needing upgrade."""
4075+
plugin = self._make_plugin(auto_schema_upgrade=True)
4076+
existing = mock.MagicMock(spec=bigquery.Table)
4077+
existing.schema = list(plugin._schema) # All columns present
4078+
existing.labels = {} # No version label
4079+
plugin.client.get_table.return_value = existing
4080+
plugin._ensure_schema_exists()
4081+
4082+
# update_table should be called to stamp the version label even
4083+
# though no new columns were needed.
4084+
plugin.client.update_table.assert_called_once()
4085+
updated_table = plugin.client.update_table.call_args[0][0]
4086+
assert (
4087+
updated_table.labels[
4088+
bigquery_agent_analytics_plugin._SCHEMA_VERSION_LABEL_KEY
4089+
]
4090+
== bigquery_agent_analytics_plugin._SCHEMA_VERSION
4091+
)
4092+
4093+
def test_upgrade_from_older_version_label(self):
4094+
"""A table with an older version label triggers upgrade."""
4095+
plugin = self._make_plugin(auto_schema_upgrade=True)
4096+
existing = mock.MagicMock(spec=bigquery.Table)
4097+
existing.schema = [
4098+
bigquery.SchemaField("timestamp", "TIMESTAMP"),
4099+
bigquery.SchemaField("event_type", "STRING"),
4100+
]
4101+
# Simulate a table stamped with an older version.
4102+
existing.labels = {
4103+
bigquery_agent_analytics_plugin._SCHEMA_VERSION_LABEL_KEY: "0",
4104+
}
4105+
plugin.client.get_table.return_value = existing
4106+
plugin._ensure_schema_exists()
4107+
4108+
plugin.client.update_table.assert_called_once()
4109+
updated_table = plugin.client.update_table.call_args[0][0]
4110+
# Version label should be updated to current.
4111+
assert (
4112+
updated_table.labels[
4113+
bigquery_agent_analytics_plugin._SCHEMA_VERSION_LABEL_KEY
4114+
]
4115+
== bigquery_agent_analytics_plugin._SCHEMA_VERSION
4116+
)
4117+
# Missing columns should have been added.
4118+
updated_names = {f.name for f in updated_table.schema}
4119+
assert "agent" in updated_names
4120+
assert "content" in updated_names
4121+
4122+
def test_upgrade_is_idempotent(self):
4123+
"""Calling _ensure_schema_exists twice doesn't double-update."""
4124+
plugin = self._make_plugin(auto_schema_upgrade=True)
4125+
4126+
# First call: table exists with old schema.
4127+
existing = mock.MagicMock(spec=bigquery.Table)
4128+
existing.schema = [
4129+
bigquery.SchemaField("timestamp", "TIMESTAMP"),
4130+
]
4131+
existing.labels = {}
4132+
plugin.client.get_table.return_value = existing
4133+
plugin._ensure_schema_exists()
4134+
assert plugin.client.update_table.call_count == 1
4135+
4136+
# Second call: table now has current version label.
4137+
existing.labels = {
4138+
bigquery_agent_analytics_plugin._SCHEMA_VERSION_LABEL_KEY: (
4139+
bigquery_agent_analytics_plugin._SCHEMA_VERSION
4140+
),
4141+
}
4142+
plugin.client.update_table.reset_mock()
4143+
plugin._ensure_schema_exists()
4144+
plugin.client.update_table.assert_not_called()
4145+
4146+
def test_update_table_receives_schema_and_labels_fields(self):
4147+
"""update_table is called with update_fields=['schema', 'labels']."""
4148+
plugin = self._make_plugin(auto_schema_upgrade=True)
4149+
existing = mock.MagicMock(spec=bigquery.Table)
4150+
existing.schema = [
4151+
bigquery.SchemaField("timestamp", "TIMESTAMP"),
4152+
]
4153+
existing.labels = {}
4154+
plugin.client.get_table.return_value = existing
4155+
plugin._ensure_schema_exists()
4156+
4157+
call_args = plugin.client.update_table.call_args
4158+
update_fields = call_args[0][1]
4159+
assert "schema" in update_fields
4160+
assert "labels" in update_fields
4161+
4162+
def test_auto_schema_upgrade_defaults_to_true(self):
4163+
"""Default config has auto_schema_upgrade enabled."""
4164+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig()
4165+
assert config.auto_schema_upgrade is True
4166+
4167+
def test_create_table_conflict_is_ignored(self):
4168+
"""Race condition (Conflict) during create_table is silently handled."""
4169+
plugin = self._make_plugin()
4170+
plugin.client.get_table.side_effect = cloud_exceptions.NotFound("not found")
4171+
plugin.client.create_table.side_effect = cloud_exceptions.Conflict(
4172+
"already exists"
4173+
)
4174+
# Should not raise.
4175+
plugin._ensure_schema_exists()
4176+
40474177

40484178
class TestToolProvenance:
40494179
"""Tests for _get_tool_origin helper."""

0 commit comments

Comments
 (0)