From 33d5bfc8f9448834578022ac3cfe2c180aaeb2c7 Mon Sep 17 00:00:00 2001
From: Tres Seaver <tseaver@palladion.com>
Date: Thu, 25 Oct 2018 15:53:25 -0400
Subject: [PATCH 1/6] Additional systests requiring no table:

- SELECT 1
- Bind BOOL to null.
- Bind FLOAT64 to null.
- Bind BYTES to null.
- Bind TIMESTAMP to null.
- Bind DATE to null.
---
 spanner/tests/system/test_system.py | 58 +++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py
index 228cd7849fa0..88c8c7ca55ee 100644
--- a/spanner/tests/system/test_system.py
+++ b/spanner/tests/system/test_system.py
@@ -1674,6 +1674,19 @@ def test_execute_sql_w_query_param(self):
             expected=[(19,), (99,)],
         )
 
+    def test_execute_sql_w_query_param_no_table(self):
+
+        self._db.snapshot(multi_use=True)
+
+        self._check_sql_results(
+            self._db,
+            sql='SELECT 1',
+            params=None,
+            param_types=None,
+            expected=[(1,)],
+            order=False,
+        )
+
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
@@ -1683,6 +1696,51 @@ def test_execute_sql_w_query_param(self):
             order=False,
         )
 
+        self._check_sql_results(
+            self._db,
+            sql='SELECT @v',
+            params={'v': None},
+            param_types={'v': Type(code=BOOL)},
+            expected=[(None,)],
+            order=False,
+        )
+
+        self._check_sql_results(
+            self._db,
+            sql='SELECT @v',
+            params={'v': None},
+            param_types={'v': Type(code=FLOAT64)},
+            expected=[(None,)],
+            order=False,
+        )
+
+        self._check_sql_results(
+            self._db,
+            sql='SELECT @v',
+            params={'v': None},
+            param_types={'v': Type(code=BYTES)},
+            expected=[(None,)],
+            order=False,
+        )
+
+        self._check_sql_results(
+            self._db,
+            sql='SELECT @v',
+            params={'v': None},
+            param_types={'v': Type(code=TIMESTAMP)},
+            expected=[(None,)],
+            order=False,
+        )
+
+        self._check_sql_results(
+            self._db,
+            sql='SELECT @v',
+            params={'v': None},
+            param_types={'v': Type(code=DATE)},
+            expected=[(None,)],
+            order=False,
+        )
+
     def test_execute_sql_w_query_param_transfinite(self):
         with self._db.batch() as batch:
             batch.delete(self.ALL_TYPES_TABLE, self.ALL)

From a98458433c7c6c08741e2e26354e72e6209127df Mon Sep 17 00:00:00 2001
From: Tres Seaver <tseaver@palladion.com>
Date: Thu, 25 Oct 2018 16:09:54 -0400
Subject: [PATCH 2/6] Additional systest:

- Query returning transfinite values as individual columns.
---
 spanner/tests/system/test_system.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py
index 88c8c7ca55ee..804f947258ac 100644
--- a/spanner/tests/system/test_system.py
+++ b/spanner/tests/system/test_system.py
@@ -1767,6 +1767,22 @@ def test_execute_sql_w_query_param_transfinite(self):
             expected=[(107,)],
         )
 
+        # Query returning -inf, +inf, NaN as column values
+        with self._db.snapshot(
+                read_timestamp=batch.committed,
+                multi_use=True) as snapshot:
+            rows = list(snapshot.execute_sql(
+                'SELECT '
+                'CAST("-inf" AS FLOAT64), '
+                'CAST("+inf" AS FLOAT64), '
+                'CAST("NaN" AS FLOAT64)'))
+            self.assertEqual(len(rows), 1)
+            self.assertEqual(rows[0][0], float('-inf'))
+            self.assertEqual(rows[0][1], float('+inf'))
+            # NaNs cannot be compared by equality.
+            self.assertTrue(math.isnan(rows[0][2]))
+
+        # Query returning array of -inf, +inf, NaN as one column
         with self._db.snapshot(
                 read_timestamp=batch.committed,
                 multi_use=True) as snapshot:

From 64829d163c65d2094db552daeb554eccd8973bba Mon Sep 17 00:00:00 2001
From: Tres Seaver <tseaver@palladion.com>
Date: Thu, 25 Oct 2018 16:33:07 -0400
Subject: [PATCH 3/6] Annotate tableless examples.

Add a possibly-redundant example for INT64.
---
 spanner/tests/system/test_system.py | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py
index 804f947258ac..c577b3cc9fee 100644
--- a/spanner/tests/system/test_system.py
+++ b/spanner/tests/system/test_system.py
@@ -1678,6 +1678,7 @@ def test_execute_sql_w_query_param_no_table(self):
 
         self._db.snapshot(multi_use=True)
 
+        # Hello, world query
         self._check_sql_results(
             self._db,
             sql='SELECT 1',
@@ -1687,6 +1688,7 @@ def test_execute_sql_w_query_param_no_table(self):
             order=False,
         )
 
+        # Bind a null STRING
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
@@ -1696,6 +1698,7 @@ def test_execute_sql_w_query_param_no_table(self):
             order=False,
         )
 
+        # Bind a null BOOL
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
@@ -1705,6 +1708,17 @@ def test_execute_sql_w_query_param_no_table(self):
             order=False,
         )
 
+        # Bind a null INT64
+        self._check_sql_results(
+            self._db,
+            sql='SELECT @v',
+            params={'v': None},
+            param_types={'v': Type(code=INT64)},
+            expected=[(None,)],
+            order=False,
+        )
+
+        # Bind a null FLOAT64
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
@@ -1714,6 +1728,7 @@ def test_execute_sql_w_query_param_no_table(self):
             order=False,
         )
 
+        # Bind a null BYTES
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
@@ -1723,6 +1738,7 @@ def test_execute_sql_w_query_param_no_table(self):
             order=False,
         )
 
+        # Bind a null TIMESTAMP
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
@@ -1732,6 +1748,7 @@ def test_execute_sql_w_query_param_no_table(self):
             order=False,
         )
 
+        # Bind a null TIMESTAMP
         self._check_sql_results(
             self._db,
             sql='SELECT @v',

From 9dfe4f3d6f9655d6900ac3282b34813dbfd00afe Mon Sep 17 00:00:00 2001
From: Tres Seaver <tseaver@palladion.com>
Date: Thu, 25 Oct 2018 17:18:15 -0400
Subject: [PATCH 4/6] Factor out bindings tests for each type into separate
 testcases.

---
 spanner/tests/system/test_system.py | 108 +++++++++++++++++++---------
 1 file changed, 74 insertions(+), 34 deletions(-)

diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py
index c577b3cc9fee..a5b16c9ab792 100644
--- a/spanner/tests/system/test_system.py
+++ b/spanner/tests/system/test_system.py
@@ -1688,76 +1688,116 @@ def test_execute_sql_w_query_param_no_table(self):
             order=False,
         )
 
-        # Bind a null STRING
-        self._check_sql_results(
-            self._db,
-            sql='SELECT @v',
-            params={'v': None},
-            param_types={'v': Type(code=STRING)},
-            expected=[(None,)],
-            order=False,
-        )
+    def _bind_test_helper(
+        self,
+        type_name,
+        single_value,
+        array_value,
+        expected_array_value=None,
+    ):
 
-        # Bind a null BOOL
-        self._check_sql_results(
-            self._db,
-            sql='SELECT @v',
-            params={'v': None},
-            param_types={'v': Type(code=BOOL)},
-            expected=[(None,)],
-            order=False,
-        )
+        self._db.snapshot(multi_use=True)
 
-        # Bind a null INT64
+        # Bind a non-null <type_name>
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
-            params={'v': None},
-            param_types={'v': Type(code=INT64)},
-            expected=[(None,)],
+            params={'v': single_value},
+            param_types={'v': Type(code=type_name)},
+            expected=[(single_value,)],
             order=False,
         )
 
-        # Bind a null FLOAT64
+        # Bind a null <type_name>
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
             params={'v': None},
-            param_types={'v': Type(code=FLOAT64)},
+            param_types={'v': Type(code=type_name)},
             expected=[(None,)],
             order=False,
         )
 
-        # Bind a null BYTES
+        # Bind an array of <type_name>
+        array_type = Type(
+            code=ARRAY, array_element_type=Type(code=type_name))
+
+        if expected_array_value is None:
+            expected_array_value = array_value
+
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
-            params={'v': None},
-            param_types={'v': Type(code=BYTES)},
-            expected=[(None,)],
+            params={'v': array_value},
+            param_types={'v': array_type},
+            expected=[(expected_array_value,)],
             order=False,
         )
 
-        # Bind a null TIMESTAMP
+        # Bind an empty array of <type_name>
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
-            params={'v': None},
-            param_types={'v': Type(code=TIMESTAMP)},
-            expected=[(None,)],
+            params={'v': []},
+            param_types={'v': array_type},
+            expected=[([],)],
             order=False,
         )
 
-        # Bind a null TIMESTAMP
+        # Bind a null array of <type_name>
         self._check_sql_results(
             self._db,
             sql='SELECT @v',
             params={'v': None},
-            param_types={'v': Type(code=DATE)},
+            param_types={'v': array_type},
             expected=[(None,)],
             order=False,
         )
 
+    def test_execute_sql_w_string_bindings(self):
+        self._bind_test_helper(STRING, 'Phred', ['Phred', 'Bharney'])
+
+    def test_execute_sql_w_bool_bindings(self):
+        self._bind_test_helper(BOOL, True, [True, False, True])
+
+    def test_execute_sql_w_int64_bindings(self):
+        self._bind_test_helper(INT64, 42, [123, 456, 789])
+
+    def test_execute_sql_w_float64_bindings(self):
+        self._bind_test_helper(FLOAT64, 42.3, [12.3, 456.0, 7.89])
+
+    def test_execute_sql_w_bytes_bindings(self):
+        self._bind_test_helper(BYTES, b'DEADBEEF', [b'FACEDACE', b'DEADBEEF'])
+
+    def test_execute_sql_w_timestamp_bindings(self):
+        import datetime
+        import pytz
+        from google.api_core.datetime_helpers import DatetimeWithNanoseconds
+
+        timestamp_1 = DatetimeWithNanoseconds(
+            1989, 1, 17, 17, 59, 12, nanosecond=345612789)
+
+        timestamp_2 = DatetimeWithNanoseconds(
+            1989, 1, 17, 17, 59, 13, nanosecond=456127893)
+
+        timestamps = [timestamp_1, timestamp_2]
+
+        # In round-trip, timestamps acquire a timezone value.
+        expected_timestamps = [
+            timestamp.replace(tzinfo=pytz.UTC) for timestamp in timestamps]
+
+        self._bind_test_helper(
+            TIMESTAMP, timestamp_1, timestamps, expected_timestamps)
+
+    def test_execute_sql_w_date_bindings(self):
+        import datetime
+
+        dates = [
+            self.SOME_DATE,
+            self.SOME_DATE + datetime.timedelta(days=1),
+        ]
+        self._bind_test_helper(DATE, self.SOME_DATE, dates)
+
     def test_execute_sql_w_query_param_transfinite(self):
         with self._db.batch() as batch:
             batch.delete(self.ALL_TYPES_TABLE, self.ALL)

From 47070be22520138bf25dc5d6dacc05fa8df139fd Mon Sep 17 00:00:00 2001
From: Tres Seaver <tseaver@palladion.com>
Date: Thu, 25 Oct 2018 17:34:43 -0400
Subject: [PATCH 5/6] Add extra row to cover roundtrip of null FLOAT64

---
 spanner/tests/system/test_system.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py
index a5b16c9ab792..5f6f5c8c122f 100644
--- a/spanner/tests/system/test_system.py
+++ b/spanner/tests/system/test_system.py
@@ -430,6 +430,7 @@ class TestSessionAPI(unittest.TestCase, _TestData):
         ([3, None, 9], False, None, None, float('-inf'), 207, u'bat', None),
         ([], False, None, None, float('nan'), 1207, u'owl', None),
         ([], False, None, None, OTHER_NAN, 2000, u'virus', NANO_TIME),
+        (None, None, None, None, None, 5432, u'algae', None),
     )
 
     @classmethod

From 1373a9f7a6e25ef6c930dedcc8ccd3330fb15e1e Mon Sep 17 00:00:00 2001
From: Tres Seaver <tseaver@palladion.com>
Date: Thu, 25 Oct 2018 17:35:51 -0400
Subject: [PATCH 6/6] Lint.

---
 spanner/tests/system/test_system.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py
index 5f6f5c8c122f..deb8fc219be7 100644
--- a/spanner/tests/system/test_system.py
+++ b/spanner/tests/system/test_system.py
@@ -1771,7 +1771,6 @@ def test_execute_sql_w_bytes_bindings(self):
         self._bind_test_helper(BYTES, b'DEADBEEF', [b'FACEDACE', b'DEADBEEF'])
 
     def test_execute_sql_w_timestamp_bindings(self):
-        import datetime
         import pytz
         from google.api_core.datetime_helpers import DatetimeWithNanoseconds
 
