diff --git a/google/cloud/spanner_dbapi/parse_utils.py b/google/cloud/spanner_dbapi/parse_utils.py index 403550640e..f039efe5b0 100644 --- a/google/cloud/spanner_dbapi/parse_utils.py +++ b/google/cloud/spanner_dbapi/parse_utils.py @@ -29,12 +29,19 @@ from .types import DateStr, TimestampStr from .utils import sanitize_literals_for_upload +# Note: This mapping deliberately does not contain a value for float. +# The reason for that is that it is better to just let Spanner determine +# the parameter type instead of specifying one explicitly. The reason for +# this is that if the client specifies FLOAT64, and the actual column that +# the parameter is used for is of type FLOAT32, then Spanner will return an +# error. If however the client does not specify a type, then Spanner will +# automatically choose the appropriate type based on the column where the +# value will be inserted/updated or that it will be compared with. TYPES_MAP = { bool: spanner.param_types.BOOL, bytes: spanner.param_types.BYTES, str: spanner.param_types.STRING, int: spanner.param_types.INT64, - float: spanner.param_types.FLOAT64, datetime.datetime: spanner.param_types.TIMESTAMP, datetime.date: spanner.param_types.DATE, DateStr: spanner.param_types.DATE, diff --git a/tests/system/test_dbapi.py b/tests/system/test_dbapi.py index feb580d903..25e0c7bbb0 100644 --- a/tests/system/test_dbapi.py +++ b/tests/system/test_dbapi.py @@ -11,11 +11,13 @@ # 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. - +import base64 import datetime from collections import defaultdict + import pytest import time +import decimal from google.cloud import spanner_v1 from google.cloud._helpers import UTC @@ -50,7 +52,22 @@ SQL SECURITY INVOKER AS SELECT c.email - FROM contacts AS c;""" + FROM contacts AS c; + + CREATE TABLE all_types ( + id int64, + col_bool bool, + col_bytes bytes(max), + col_date date, + col_float32 float32, + col_float64 float64, + col_int64 int64, + col_json json, + col_numeric numeric, + col_string string(max), + coL_timestamp timestamp, + ) primary key (col_int64); + """ DDL_STATEMENTS = [stmt.strip() for stmt in DDL.split(";") if stmt.strip()] @@ -1602,3 +1619,29 @@ def test_list_tables(self, include_views): def test_invalid_statement_error(self): with pytest.raises(ProgrammingError): self._cursor.execute("-- comment only") + + def test_insert_all_types(self): + """Test inserting all supported data types""" + + self._conn.autocommit = True + self._cursor.execute( + """ + INSERT INTO all_types (id, col_bool, col_bytes, col_date, col_float32, col_float64, + col_int64, col_json, col_numeric, col_string, col_timestamp) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """, + ( + 1, + True, + base64.b64encode(b"test-bytes"), + datetime.date(2024, 12, 3), + 3.14, + 3.14, + 123, + JsonObject({"key": "value"}), + decimal.Decimal("3.14"), + "test-string", + datetime.datetime(2024, 12, 3, 17, 30, 14), + ), + ) + assert self._cursor.rowcount == 1 diff --git a/tests/unit/spanner_dbapi/test_parse_utils.py b/tests/unit/spanner_dbapi/test_parse_utils.py index 3a325014fa..4b1c7cdb06 100644 --- a/tests/unit/spanner_dbapi/test_parse_utils.py +++ b/tests/unit/spanner_dbapi/test_parse_utils.py @@ -218,6 +218,8 @@ def test_get_param_types(self): params = { "a1": 10, "b1": "string", + # Note: We only want a value and not a type for this. + # Instead, we let Spanner infer the correct type (FLOAT64 or FLOAT32) "c1": 10.39, "d1": TimestampStr("2005-08-30T01:01:01.000001Z"), "e1": DateStr("2019-12-05"), @@ -232,7 +234,6 @@ def test_get_param_types(self): want_types = { "a1": param_types.INT64, "b1": param_types.STRING, - "c1": param_types.FLOAT64, "d1": param_types.TIMESTAMP, "e1": param_types.DATE, "f1": param_types.BOOL,