@@ -794,7 +794,13 @@ def get_foreign_keys(self, connection, table_name, schema=None, **kw):
794794 ctu.table_name,
795795 ctu.table_schema,
796796 ARRAY_AGG(DISTINCT ccu.column_name),
797- ARRAY_AGG(kcu.column_name)
797+ ARRAY_AGG(
798+ DISTINCT CONCAT(
799+ CAST(kcu.ordinal_position AS STRING),
800+ '_____',
801+ kcu.column_name
802+ )
803+ )
798804FROM information_schema.table_constraints AS tc
799805JOIN information_schema.constraint_column_usage AS ccu
800806 ON ccu.constraint_name = tc.constraint_name
@@ -815,6 +821,21 @@ def get_foreign_keys(self, connection, table_name, schema=None, **kw):
815821 rows = snap .execute_sql (sql )
816822
817823 for row in rows :
824+ # Due to Spanner limitations, arrays order is not guaranteed during
825+ # aggregation. Still, for constraints it's vital to keep the order
826+ # of the referred columns, otherwise SQLAlchemy and Alembic may start
827+ # to occasionally drop and recreate constraints. To avoid this, the
828+ # method uses prefixes with the `key_column_usage.ordinal_position`
829+ # values to ensure the columns are aggregated into an array in the
830+ # correct order. Prefixes are only used under the hood. For more details
831+ # see the issue:
832+ # https://github.com/googleapis/python-spanner-sqlalchemy/issues/271
833+ #
834+ # The solution seem a bit clumsy, and should be improved as soon as a
835+ # better approach found.
836+ for index , value in enumerate (sorted (row [4 ])):
837+ row [4 ][index ] = value .split ("_____" )[1 ]
838+
818839 keys .append (
819840 {
820841 "name" : row [0 ],
@@ -824,6 +845,7 @@ def get_foreign_keys(self, connection, table_name, schema=None, **kw):
824845 "constrained_columns" : row [4 ],
825846 }
826847 )
848+
827849 return keys
828850
829851 @engine_to_connection
0 commit comments