Skip to content
Merged
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
14 changes: 11 additions & 3 deletions google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"DATETIME": types.DATETIME,
"FLOAT64": types.Float,
"INT64": types.BIGINT,
"NUMERIC": types.DECIMAL,
"NUMERIC": types.NUMERIC(precision=38, scale=9),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Constant precision and scale

"STRING": types.String,
"TIME": types.TIME,
"TIMESTAMP": types.TIMESTAMP,
Expand Down Expand Up @@ -176,6 +176,9 @@ def visit_BINARY(self, type_, **kw):
def visit_DECIMAL(self, type_, **kw):
return "NUMERIC"

def visit_NUMERIC(self, type_, **kw):
return "NUMERIC"

def visit_VARCHAR(self, type_, **kw):
return "STRING({})".format(type_.length)

Expand Down Expand Up @@ -314,12 +317,17 @@ def get_columns(self, connection, table_name, schema=None, **kw):
columns = snap.execute_sql(sql)

for col in columns:
type_ = "STRING" if col[1].startswith("STRING") else col[1]
if col[1].startswith("STRING"):
end = col[1].index(")")
size = int(col[1][7:end])
type_ = _type_map["STRING"](length=size)
else:
type_ = _type_map[col[1]]
Copy link
Contributor Author

@IlyaFaer IlyaFaer Mar 19, 2021

Choose a reason for hiding this comment

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

While reflecting column types, we're reading strings like STRING(size) from information_schema. Such a string can be parsed to get the column size - to set it into SQLAlchemy reflected type.

@larkee, I assume, something similar is required for BYTES (you mentioned their MAX size in the dialect), so I'm looking forward for BYTES types test.


cols_desc.append(
{
"name": col[0],
"type": _type_map[type_],
"type": type_,
"nullable": col[2] == "YES",
"default": None,
}
Expand Down
46 changes: 46 additions & 0 deletions test/test_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import operator
import pytest
import pytz

import sqlalchemy
from sqlalchemy import inspect
Expand All @@ -41,6 +42,7 @@
from sqlalchemy import Boolean
from sqlalchemy import String
from sqlalchemy.types import Integer
from sqlalchemy.types import Numeric
from sqlalchemy.testing import requires

from google.api_core.datetime_helpers import DatetimeWithNanoseconds
Expand All @@ -50,6 +52,7 @@
from sqlalchemy.testing.suite.test_cte import * # noqa: F401, F403
from sqlalchemy.testing.suite.test_ddl import * # noqa: F401, F403
from sqlalchemy.testing.suite.test_dialect import * # noqa: F401, F403
from sqlalchemy.testing.suite.test_results import * # noqa: F401, F403
from sqlalchemy.testing.suite.test_update_delete import * # noqa: F401, F403

from sqlalchemy.testing.suite.test_cte import CTETest as _CTETest
Expand All @@ -64,6 +67,7 @@
from sqlalchemy.testing.suite.test_reflection import (
ComponentReflectionTest as _ComponentReflectionTest,
)
from sqlalchemy.testing.suite.test_results import RowFetchTest as _RowFetchTest
from sqlalchemy.testing.suite.test_select import ExistsTest as _ExistsTest
from sqlalchemy.testing.suite.test_types import BooleanTest as _BooleanTest
from sqlalchemy.testing.suite.test_types import IntegerTest as _IntegerTest
Expand Down Expand Up @@ -739,6 +743,48 @@ def test_get_temp_table_indexes(self):
def test_get_temp_table_unique_constraints(self):
pass

@testing.requires.table_reflection
def test_numeric_reflection(self):
"""
SPANNER OVERRIDE:

Spanner defines NUMERIC type with the constant precision=38
and scale=9. Overriding the test to check if the NUMERIC
column is successfully created and has dimensions
correct for Cloud Spanner.
"""
for typ in self._type_round_trip(Numeric(18, 5)):
assert isinstance(typ, Numeric)
eq_(typ.precision, 38)
eq_(typ.scale, 9)


class RowFetchTest(_RowFetchTest):
def test_row_w_scalar_select(self):
"""
SPANNER OVERRIDE:

Cloud Spanner returns a DatetimeWithNanoseconds() for date
data types. Overriding the test to use a DatetimeWithNanoseconds
type value as an expected result.
--------------

test that a scalar select as a column is returned as such
and that type conversion works OK.

(this is half a SQLAlchemy Core test and half to catch database
backends that may have unusual behavior with scalar selects.)
"""
datetable = self.tables.has_dates
s = select([datetable.alias("x").c.today]).as_scalar()
s2 = select([datetable.c.id, s.label("somelabel")])
row = config.db.execute(s2).first()

eq_(
row["somelabel"],
DatetimeWithNanoseconds(2006, 5, 12, 12, 0, 0, tzinfo=pytz.UTC),
)


class InsertBehaviorTest(_InsertBehaviorTest):
@pytest.mark.skip("Spanner doesn't support empty inserts")
Expand Down