Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,10 @@ def visit_create_index(
[self.preparer.quote(c.name) for c in storing_columns]
)

interleave_in = options.get("interleave_in")
if interleave_in is not None:
text += f", INTERLEAVE IN {self.preparer.quote(interleave_in)}"

if options.get("null_filtered", False):
text = re.sub(
r"(^\s*CREATE\s+(?:UNIQUE\s+)?)INDEX",
Expand Down
21 changes: 16 additions & 5 deletions samples/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,22 @@ class Album(Base):

class Track(Base):
__tablename__ = "tracks"
# This interleaves the table `tracks` in its parent `albums`.
__table_args__ = {
"spanner_interleave_in": "albums",
"spanner_interleave_on_delete_cascade": True,
}
__table_args__ = (
# Use the spanner_interleave_in argument to add an INTERLEAVED IN clause to the index.
# You can read additional details at:
# https://cloud.google.com/spanner/docs/secondary-indexes#indexes_and_interleaving
Index(
"idx_tracks_id_title",
"id",
"title",
spanner_interleave_in="albums",
),
# This interleaves the table `tracks` in its parent `albums`.
{
"spanner_interleave_in": "albums",
"spanner_interleave_on_delete_cascade": True,
},
)
id: Mapped[str] = mapped_column(String(36), primary_key=True)
track_number: Mapped[int] = mapped_column(Integer, primary_key=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
Expand Down
74 changes: 74 additions & 0 deletions test/mockserver_tests/interleaved_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Copyright 2025 Google LLC All rights reserved.
#
# 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.

from sqlalchemy import ForeignKey, Index, String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
pass


class Singer(Base):
__tablename__ = "singers"

singer_id: Mapped[str] = mapped_column(String(36), primary_key=True)
first_name: Mapped[str]
last_name: Mapped[str]


class Album(Base):
__tablename__ = "albums"
__table_args__ = {
"spanner_interleave_in": "singers",
"spanner_interleave_on_delete_cascade": True,
}

singer_id: Mapped[str] = mapped_column(
ForeignKey("singers.singer_id"), primary_key=True
)
album_id: Mapped[str] = mapped_column(String(36), primary_key=True)
album_title: Mapped[str]


class Track(Base):
__tablename__ = "tracks"
__table_args__ = (
Index(
"idx_name",
"singer_id",
"album_id",
"song_name",
spanner_interleave_in="albums",
),
{
"spanner_interleave_in": "albums",
"spanner_interleave_on_delete_cascade": True,
},
)

singer_id: Mapped[str] = mapped_column(
ForeignKey("singers.singer_id"), primary_key=True
)
album_id: Mapped[str] = mapped_column(
ForeignKey("albums.album_id"), primary_key=True
)
track_id: Mapped[str] = mapped_column(String(36), primary_key=True)
song_name: Mapped[str]


Album.__table__.add_is_dependent_on(Singer.__table__)
Track.__table__.add_is_dependent_on(Album.__table__)
102 changes: 102 additions & 0 deletions test/mockserver_tests/test_interleaved_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Copyright 2025 Google LLC All rights reserved.
#
# 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.

from sqlalchemy import create_engine
from sqlalchemy.testing import eq_, is_instance_of
from google.cloud.spanner_v1 import (
FixedSizePool,
ResultSet,
)
from test.mockserver_tests.mock_server_test_base import (
MockServerTestBase,
add_result,
)
from google.cloud.spanner_admin_database_v1 import UpdateDatabaseDdlRequest


class TestNullFilteredIndex(MockServerTestBase):
"""Ensure we emit correct DDL for not null filtered indexes."""

def test_create_table(self):
from test.mockserver_tests.interleaved_index import Base

add_result(
"""SELECT true
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA="" AND TABLE_NAME="singers"
LIMIT 1
""",
ResultSet(),
)
add_result(
"""SELECT true
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA="" AND TABLE_NAME="albums"
LIMIT 1
""",
ResultSet(),
)
add_result(
"""SELECT true
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA="" AND TABLE_NAME="tracks"
LIMIT 1
""",
ResultSet(),
)
engine = create_engine(
"spanner:///projects/p/instances/i/databases/d",
connect_args={"client": self.client, "pool": FixedSizePool(size=10)},
)
Base.metadata.create_all(engine)
requests = self.database_admin_service.requests
eq_(1, len(requests))
is_instance_of(requests[0], UpdateDatabaseDdlRequest)
eq_(4, len(requests[0].statements))
eq_(
"CREATE TABLE singers (\n"
"\tsinger_id STRING(36) NOT NULL, \n"
"\tfirst_name STRING(MAX) NOT NULL, \n"
"\tlast_name STRING(MAX) NOT NULL\n"
") PRIMARY KEY (singer_id)",
requests[0].statements[0],
)
eq_(
"CREATE TABLE albums (\n"
"\tsinger_id STRING(36) NOT NULL, \n"
"\talbum_id STRING(36) NOT NULL, \n"
"\talbum_title STRING(MAX) NOT NULL, \n"
"\tFOREIGN KEY(singer_id) REFERENCES singers (singer_id)\n"
") PRIMARY KEY (singer_id, album_id),\n"
"INTERLEAVE IN PARENT singers ON DELETE CASCADE",
requests[0].statements[1],
)
eq_(
"CREATE TABLE tracks (\n"
"\tsinger_id STRING(36) NOT NULL, \n"
"\talbum_id STRING(36) NOT NULL, \n"
"\ttrack_id STRING(36) NOT NULL, \n"
"\tsong_name STRING(MAX) NOT NULL, \n"
"\tFOREIGN KEY(singer_id) REFERENCES singers (singer_id), \n"
"\tFOREIGN KEY(album_id) REFERENCES albums (album_id)\n"
") PRIMARY KEY (singer_id, album_id, track_id),\n"
"INTERLEAVE IN PARENT albums ON DELETE CASCADE",
requests[0].statements[2],
)
eq_(
"CREATE INDEX idx_name ON tracks "
"(singer_id, album_id, song_name), "
"INTERLEAVE IN albums",
requests[0].statements[3],
)
Loading