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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support of float32 type #1113

Merged
merged 8 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 5 additions & 0 deletions google/cloud/spanner_v1/_helpers.py
Expand Up @@ -228,6 +228,11 @@ def _parse_value_pb(value_pb, field_type):
return float(value_pb.string_value)
else:
return value_pb.number_value
elif type_code == TypeCode.FLOAT32:
Copy link
Contributor

Choose a reason for hiding this comment

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

We have integration tests for every type in test_session_api.py. Can we have a test for FLOAT32?
Reference: test_execute_sql_w_float64_bindings and test_execute_sql_w_float_bindings_transfinite

if value_pb.HasField("string_value"):
return float(value_pb.string_value)
else:
return value_pb.number_value
elif type_code == TypeCode.DATE:
return _date_from_iso8601_date(value_pb.string_value)
elif type_code == TypeCode.TIMESTAMP:
Expand Down
1 change: 1 addition & 0 deletions google/cloud/spanner_v1/param_types.py
Expand Up @@ -26,6 +26,7 @@
BOOL = Type(code=TypeCode.BOOL)
INT64 = Type(code=TypeCode.INT64)
FLOAT64 = Type(code=TypeCode.FLOAT64)
FLOAT32 = Type(code=TypeCode.FLOAT32)
DATE = Type(code=TypeCode.DATE)
TIMESTAMP = Type(code=TypeCode.TIMESTAMP)
NUMERIC = Type(code=TypeCode.NUMERIC)
Expand Down
1 change: 1 addition & 0 deletions google/cloud/spanner_v1/streamed.py
Expand Up @@ -332,6 +332,7 @@ def _merge_struct(lhs, rhs, type_):
TypeCode.BYTES: _merge_string,
TypeCode.DATE: _merge_string,
TypeCode.FLOAT64: _merge_float64,
TypeCode.FLOAT32: _merge_float64,
TypeCode.INT64: _merge_string,
TypeCode.STRING: _merge_string,
TypeCode.STRUCT: _merge_struct,
Expand Down
3 changes: 3 additions & 0 deletions tests/system/_sample_data.py
Expand Up @@ -90,5 +90,8 @@ def _check_cell_data(found_cell, expected_cell, recurse_into_lists=True):
for found_item, expected_item in zip(found_cell, expected_cell):
_check_cell_data(found_item, expected_item)

elif isinstance(found_cell, float):
assert abs(found_cell - expected_cell) < 0.00001
Copy link
Contributor Author

Choose a reason for hiding this comment

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


else:
assert found_cell == expected_cell
41 changes: 41 additions & 0 deletions tests/system/test_session_api.py
Expand Up @@ -2216,6 +2216,47 @@ def test_execute_sql_w_float_bindings_transfinite(sessions_database, database_di
)


def test_execute_sql_w_float32_bindings(sessions_database, database_dialect):
pytest.skip(f"float32 is not yet supported in production.")
_bind_test_helper(
sessions_database,
database_dialect,
spanner_v1.param_types.FLOAT32,
42.3,
[12.3, 456.0, 7.89],
)


def test_execute_sql_w_float32_bindings_transfinite(
sessions_database, database_dialect
):
pytest.skip(f"float32 is not yet supported in production.")
key = "p1" if database_dialect == DatabaseDialect.POSTGRESQL else "neg_inf"
placeholder = "$1" if database_dialect == DatabaseDialect.POSTGRESQL else f"@{key}"

# Find -inf
_check_sql_results(
sessions_database,
sql=f"SELECT {placeholder}",
params={key: NEG_INF},
param_types={key: spanner_v1.param_types.FLOAT64},
olavloite marked this conversation as resolved.
Show resolved Hide resolved
expected=[(NEG_INF,)],
order=False,
)

key = "p1" if database_dialect == DatabaseDialect.POSTGRESQL else "pos_inf"
placeholder = "$1" if database_dialect == DatabaseDialect.POSTGRESQL else f"@{key}"
# Find +inf
_check_sql_results(
sessions_database,
sql=f"SELECT {placeholder}",
params={key: POS_INF},
param_types={key: spanner_v1.param_types.FLOAT64},
olavloite marked this conversation as resolved.
Show resolved Hide resolved
expected=[(POS_INF,)],
order=False,
)


def test_execute_sql_w_bytes_bindings(sessions_database, database_dialect):
_bind_test_helper(
sessions_database,
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/test__helpers.py
Expand Up @@ -466,6 +466,27 @@ def test_w_float_str(self):

self.assertEqual(self._callFUT(value_pb, field_type), expected_value)

def test_w_float32(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

As per FLOAT32 design, the request from client is sent as number_value field. What will happen when the input is float("-inf") or float("nan") which gets encoded to string_value field and the column in table is of type FLOAT32?

Copy link
Contributor

Choose a reason for hiding this comment

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

We need to verify both insert and read with this kind of data when column type is FLOAT32.
For read we already have an integration test for FLOAT64 test_execute_sql_w_float_bindings_transfinite . We can copy it for FLOAT32.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

behaviour for float("-inf") or float("nan") is same as FLOAT64, added the skipped test case for manual verification.

from google.cloud.spanner_v1 import Type, TypeCode
from google.protobuf.struct_pb2 import Value

VALUE = 3.14159
field_type = Type(code=TypeCode.FLOAT32)
value_pb = Value(number_value=VALUE)

self.assertEqual(self._callFUT(value_pb, field_type), VALUE)

def test_w_float32_str(self):
from google.cloud.spanner_v1 import Type, TypeCode
from google.protobuf.struct_pb2 import Value

VALUE = "3.14159"
field_type = Type(code=TypeCode.FLOAT32)
value_pb = Value(string_value=VALUE)
expected_value = 3.14159

self.assertEqual(self._callFUT(value_pb, field_type), expected_value)

def test_w_date(self):
import datetime
from google.protobuf.struct_pb2 import Value
Expand Down
21 changes: 10 additions & 11 deletions tests/unit/test_param_types.py
Expand Up @@ -18,9 +18,7 @@

class Test_ArrayParamType(unittest.TestCase):
def test_it(self):
from google.cloud.spanner_v1 import Type
from google.cloud.spanner_v1 import TypeCode
from google.cloud.spanner_v1 import param_types
from google.cloud.spanner_v1 import Type, TypeCode, param_types

expected = Type(
code=TypeCode.ARRAY, array_element_type=Type(code=TypeCode.INT64)
Expand All @@ -33,15 +31,13 @@ def test_it(self):

class Test_Struct(unittest.TestCase):
def test_it(self):
from google.cloud.spanner_v1 import Type
from google.cloud.spanner_v1 import TypeCode
from google.cloud.spanner_v1 import StructType
from google.cloud.spanner_v1 import param_types
from google.cloud.spanner_v1 import StructType, Type, TypeCode, param_types

struct_type = StructType(
fields=[
StructType.Field(name="name", type_=Type(code=TypeCode.STRING)),
StructType.Field(name="count", type_=Type(code=TypeCode.INT64)),
StructType.Field(name="float32", type_=Type(code=TypeCode.FLOAT32)),
]
)
expected = Type(code=TypeCode.STRUCT, struct_type=struct_type)
Expand All @@ -50,6 +46,7 @@ def test_it(self):
[
param_types.StructField("name", param_types.STRING),
param_types.StructField("count", param_types.INT64),
param_types.StructField("float32", param_types.FLOAT32),
]
)

Expand All @@ -58,10 +55,12 @@ def test_it(self):

class Test_JsonbParamType(unittest.TestCase):
def test_it(self):
from google.cloud.spanner_v1 import Type
from google.cloud.spanner_v1 import TypeCode
from google.cloud.spanner_v1 import TypeAnnotationCode
from google.cloud.spanner_v1 import param_types
from google.cloud.spanner_v1 import (
Type,
TypeAnnotationCode,
TypeCode,
param_types,
)

expected = Type(
code=TypeCode.JSON,
Expand Down