Skip to content

Commit

Permalink
Send TIMESTAMP query parameters as string.
Browse files Browse the repository at this point in the history
- *Not* the float-time-since-epoch-in-seconds which Bigquery uses for
  all other TIMESTAMP values. :(
- *Not* RFC3339, but the SQL-mandated format with an embedded space. :(

Closes: #2886.
  • Loading branch information
tseaver committed Dec 20, 2016
1 parent 3d354dc commit fc4442e
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 13 deletions.
8 changes: 6 additions & 2 deletions bigquery/google/cloud/bigquery/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
from collections import OrderedDict
import datetime

from google.cloud._helpers import UTC
from google.cloud._helpers import _date_from_iso8601_date
from google.cloud._helpers import _datetime_from_microseconds
from google.cloud._helpers import _datetime_to_rfc3339
from google.cloud._helpers import _microseconds_from_datetime
from google.cloud._helpers import _RFC3339_NO_FRACTION
from google.cloud._helpers import _time_from_iso8601_time_naive
from google.cloud._helpers import _to_bytes
Expand Down Expand Up @@ -150,7 +150,11 @@ def _bytes_to_json(value):
def _timestamp_to_json(value):
"""Coerce 'value' to an JSON-compatible representation."""
if isinstance(value, datetime.datetime):
value = _microseconds_from_datetime(value) / 1.0e6
if value.tzinfo not in (None, UTC):
# Convert to UTC and remove the time zone info.
value = value.replace(tzinfo=None) - value.utcoffset()
value = '%s %s+00:00' % (
value.date().isoformat(), value.time().isoformat())
return value


Expand Down
42 changes: 32 additions & 10 deletions bigquery/unit_tests/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,13 +546,35 @@ def _call_fut(self, value):
def test_w_float(self):
self.assertEqual(self._call_fut(1.234567), 1.234567)

def test_w_datetime(self):
def test_w_string(self):
ZULU = '2016-12-20 15:58:27.339328+00:00'
self.assertEqual(self._call_fut(ZULU), ZULU)

def test_w_datetime_wo_zone(self):
import datetime
ZULU = '2016-12-20 15:58:27.339328+00:00'
when = datetime.datetime(2016, 12, 20, 15, 58, 27, 339328)
self.assertEqual(self._call_fut(when), ZULU)

def test_w_datetime_w_non_utc_zone(self):
import datetime

class _Zone(datetime.tzinfo):

def utcoffset(self, _):
return datetime.timedelta(minutes=-240)

ZULU = '2016-12-20 19:58:27.339328+00:00'
when = datetime.datetime(
2016, 12, 20, 15, 58, 27, 339328, tzinfo=_Zone())
self.assertEqual(self._call_fut(when), ZULU)

def test_w_datetime_w_utc_zone(self):
import datetime
from google.cloud._helpers import UTC
from google.cloud._helpers import _microseconds_from_datetime
when = datetime.datetime(2016, 12, 3, 14, 11, 27, tzinfo=UTC)
self.assertEqual(self._call_fut(when),
_microseconds_from_datetime(when) / 1e6)
ZULU = '2016-12-20 15:58:27.339328+00:00'
when = datetime.datetime(2016, 12, 20, 15, 58, 27, 339328, tzinfo=UTC)
self.assertEqual(self._call_fut(when), ZULU)


class Test_datetime_to_json(unittest.TestCase):
Expand Down Expand Up @@ -907,20 +929,20 @@ def test_to_api_repr_w_bool(self):
self.assertEqual(param.to_api_repr(), EXPECTED)

def test_to_api_repr_w_timestamp_datetime(self):
from google.cloud._helpers import UTC
import datetime
from google.cloud._helpers import _microseconds_from_datetime
now = datetime.datetime.utcnow()
seconds = _microseconds_from_datetime(now) / 1.0e6
STAMP = '2016-12-20 15:58:27.339328+00:00'
when = datetime.datetime(2016, 12, 20, 15, 58, 27, 339328, tzinfo=UTC)
EXPECTED = {
'parameterType': {
'type': 'TIMESTAMP',
},
'parameterValue': {
'value': seconds,
'value': STAMP,
},
}
klass = self._get_target_class()
param = klass.positional(type_='TIMESTAMP', value=now)
param = klass.positional(type_='TIMESTAMP', value=when)
self.assertEqual(param.to_api_repr(), EXPECTED)

def test_to_api_repr_w_timestamp_micros(self):
Expand Down
12 changes: 11 additions & 1 deletion system_tests/bigquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,9 +482,12 @@ def _job_done(instance):
def test_sync_query_w_standard_sql_types(self):
import datetime
from google.cloud._helpers import UTC
from google.cloud.bigquery._helpers import ScalarQueryParameter
naive = datetime.datetime(2016, 12, 5, 12, 41, 9)
stamp = "%s %s" % (naive.date().isoformat(), naive.time().isoformat())
zoned = naive.replace(tzinfo=UTC)
zoned_param = ScalarQueryParameter(
name='zoned', type_='TIMESTAMP', value=zoned)
EXAMPLES = [
{
'sql': 'SELECT 1',
Expand Down Expand Up @@ -553,9 +556,16 @@ def test_sync_query_w_standard_sql_types(self):
'sql': 'SELECT ARRAY(SELECT STRUCT([1, 2]))',
'expected': [{u'_field_1': [1, 2]}],
},
{
'sql': 'SELECT @zoned',
'expected': zoned,
'query_parameters': [zoned_param],
},
]
for example in EXAMPLES:
query = Config.CLIENT.run_sync_query(example['sql'])
query = Config.CLIENT.run_sync_query(
example['sql'],
query_parameters=example.get('query_parameters', ()))
query.use_legacy_sql = False
query.run()
self.assertEqual(len(query.rows), 1)
Expand Down

0 comments on commit fc4442e

Please sign in to comment.