Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: array columns reflection #119

Merged
merged 4 commits into from
Sep 16, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 29 additions & 13 deletions google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"STRING": types.String,
"TIME": types.TIME,
"TIMESTAMP": types.TIMESTAMP,
"ARRAY": types.ARRAY,
}

_type_map_inv = {
Expand Down Expand Up @@ -476,28 +477,43 @@ def get_columns(self, connection, table_name, schema=None, **kw):
columns = snap.execute_sql(sql)

for col in columns:
if col[1].startswith("STRING"):
end = col[1].index(")")
size = int_from_size(col[1][7:end])
type_ = _type_map["STRING"](length=size)
# add test creating a table with bytes
elif col[1].startswith("BYTES"):
end = col[1].index(")")
size = int_from_size(col[1][6:end])
type_ = _type_map["BYTES"](length=size)
else:
type_ = _type_map[col[1]]

cols_desc.append(
{
"name": col[0],
"type": type_,
"type": self._designate_type(col[1]),
"nullable": col[2] == "YES",
"default": None,
}
)
return cols_desc

def _designate_type(self, str_repr):
"""
Designate an SQLAlchemy data type from a Spanner
string representation.

Args:
str_repr (str): String representation of a type.

Returns:
An SQLAlchemy data type.
"""
if str_repr.startswith("STRING"):
end = str_repr.index(")")
size = int_from_size(str_repr[7:end])
return _type_map["STRING"](length=size)
# add test creating a table with bytes
elif str_repr.startswith("BYTES"):
end = str_repr.index(")")
size = int_from_size(str_repr[6:end])
return _type_map["BYTES"](length=size)
elif str_repr.startswith("ARRAY"):
inner_type_str = str_repr[6:-1]
inner_type = self._designate_type(inner_type_str)
return _type_map["ARRAY"](inner_type)
Comment on lines +510 to +513
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array column string representation actually has two types, for example:
ARRAY<STRING(MAX)>
If we see it's an array, we strip its definition and run _designate_type() method for the inner type.

else:
return _type_map[str_repr]

@engine_to_connection
def get_indexes(self, connection, table_name, schema=None, **kw):
"""Get the table indexes.
Expand Down
28 changes: 28 additions & 0 deletions test/test_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation
from sqlalchemy.orm import Session
from sqlalchemy.types import ARRAY
from sqlalchemy.types import Integer
from sqlalchemy.types import Numeric
from sqlalchemy.types import Text
Expand Down Expand Up @@ -901,6 +902,33 @@ def test_binary_reflection(self):
assert isinstance(typ, LargeBinary)
eq_(typ.length, 20)

@testing.requires.table_reflection
def test_array_reflection(self):
"""Check array columns reflection."""
orig_meta = self.metadata

str_array = ARRAY(String(16))
int_array = ARRAY(Integer)
Table(
"arrays_test",
orig_meta,
Column("id", Integer, primary_key=True),
Column("str_array", str_array),
Column("int_array", int_array),
)
orig_meta.create_all()

# autoload the table and check its columns reflection
tab = Table("arrays_test", orig_meta, autoload=True)
col_types = [col.type for col in tab.columns]
for type_ in (
str_array,
int_array,
):
assert type_ in col_types

tab.drop()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running test for two types: simple integer and more complex string with size.



class CompositeKeyReflectionTest(_CompositeKeyReflectionTest):
@testing.requires.foreign_key_constraint_reflection
Expand Down