Skip to content

Commit

Permalink
BigQuery: add support for GEOGRAPHY type (#6147)
Browse files Browse the repository at this point in the history
.
  • Loading branch information
tseaver authored Oct 2, 2018
1 parent ecc3845 commit 0745165
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 32 deletions.
1 change: 1 addition & 0 deletions bigquery/google/cloud/bigquery/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def _record_from_json(value, field):
'BOOLEAN': _bool_from_json,
'BOOL': _bool_from_json,
'STRING': _string_from_json,
'GEOGRAPHY': _string_from_json,
'BYTES': _bytes_from_json,
'TIMESTAMP': _timestamp_from_json,
'DATETIME': _datetime_from_json,
Expand Down
58 changes: 26 additions & 32 deletions bigquery/google/cloud/bigquery/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,19 @@
class SchemaField(object):
"""Describe a single field within a table schema.
:type name: str
:param name: the name of the field.
Args:
name (str): the name of the field.
:type field_type: str
:param field_type: the type of the field (one of 'STRING', 'INTEGER',
'FLOAT', 'NUMERIC', 'BOOLEAN', 'TIMESTAMP' or
'RECORD').
field_type (str): the type of the field. See
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#schema.fields.type
:type mode: str
:param mode: the mode of the field (one of 'NULLABLE', 'REQUIRED',
or 'REPEATED').
mode (str): the mode of the field. See
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#schema.fields.mode
:type description: str
:param description: optional description for the field.
description (Optional[str]):description for the field.
:type fields: tuple of :class:`~google.cloud.bigquery.schema.SchemaField`
:param fields: subfields (requires ``field_type`` of 'RECORD').
fields (Tuple[:class:`~google.cloud.bigquery.schema.SchemaField`]):
subfields (requires ``field_type`` of 'RECORD').
"""
def __init__(self, name, field_type, mode='NULLABLE',
description=None, fields=()):
Expand Down Expand Up @@ -78,35 +74,35 @@ def name(self):
def field_type(self):
"""str: The type of the field.
Will be one of 'STRING', 'INTEGER', 'FLOAT', 'NUMERIC',
'BOOLEAN', 'TIMESTAMP' or 'RECORD'.
See:
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#schema.fields.type
"""
return self._field_type

@property
def mode(self):
"""str: The mode of the field.
Will be one of 'NULLABLE', 'REQUIRED', or 'REPEATED'.
See:
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#schema.fields.mode
"""
return self._mode

@property
def is_nullable(self):
"""Check whether 'mode' is 'nullable'."""
"""bool: whether 'mode' is 'nullable'."""
return self._mode == 'NULLABLE'

@property
def description(self):
"""Optional[str]: Description for the field."""
"""Optional[str]: description for the field."""
return self._description

@property
def fields(self):
"""tuple: Subfields contained in this field.
If ``field_type`` is not 'RECORD', this property must be
empty / unset.
Must be empty unset if ``field_type`` is not 'RECORD'.
"""
return self._fields

Expand Down Expand Up @@ -168,14 +164,12 @@ def __repr__(self):
def _parse_schema_resource(info):
"""Parse a resource fragment into a schema field.
:type info: mapping
:param info: should contain a "fields" key to be parsed
Args:
info: (Mapping[str->dict]): should contain a "fields" key to be parsed
:rtype:
list of :class:`google.cloud.bigquery.schema.SchemaField`, or
``NoneType``
:returns: a list of parsed fields, or ``None`` if no "fields" key is
present in ``info``.
Returns:
(Union[Sequence[:class:`google.cloud.bigquery.schema.SchemaField`],None])
a list of parsed fields, or ``None`` if no "fields" key found.
"""
if 'fields' not in info:
return ()
Expand All @@ -195,11 +189,11 @@ def _parse_schema_resource(info):
def _build_schema_resource(fields):
"""Generate a resource fragment for a schema.
:type fields:
sequence of :class:`~google.cloud.bigquery.schema.SchemaField`
:param fields: schema to be dumped
Args:
fields [Sequence[:class:`~google.cloud.bigquery.schema.SchemaField`]):
schema to be dumped
:rtype: mapping
:returns: a mapping describing the schema of the supplied fields.
Returns: (Sequence[dict])
mappings describing the schema of the supplied fields.
"""
return [field.to_api_repr() for field in fields]
4 changes: 4 additions & 0 deletions bigquery/tests/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,10 @@ def _generate_standard_sql_types_examples(self):
'sql': 'SELECT ARRAY(SELECT STRUCT([1, 2]))',
'expected': [{u'_field_1': [1, 2]}],
},
{
'sql': 'SELECT ST_GeogPoint(1, 2)',
'expected': 'POINT(1 2)',
},
]

def test_query_w_standard_sql_types(self):
Expand Down
13 changes: 13 additions & 0 deletions bigquery/tests/unit/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,13 @@ def test_w_scalar_subfield(self):
coerced = self._call_fut(value, field)
self.assertEqual(coerced, {'age': 42})

def test_w_scalar_subfield_geography(self):
subfield = _Field('REQUIRED', 'geo', 'GEOGRAPHY')
field = _Field('REQUIRED', fields=[subfield])
value = {'f': [{'v': 'POINT(1, 2)'}]}
coerced = self._call_fut(value, field)
self.assertEqual(coerced, {'geo': 'POINT(1, 2)'})

def test_w_repeated_subfield(self):
subfield = _Field('REPEATED', 'color', 'STRING')
field = _Field('REQUIRED', fields=[subfield])
Expand Down Expand Up @@ -444,6 +451,12 @@ def test_w_single_scalar_column(self):
row = {u'f': [{u'v': u'1'}]}
self.assertEqual(self._call_fut(row, schema=[col]), (1,))

def test_w_single_scalar_geography_column(self):
# SELECT 1 AS col
col = _Field('REQUIRED', 'geo', 'GEOGRAPHY')
row = {u'f': [{u'v': u'POINT(1, 2)'}]}
self.assertEqual(self._call_fut(row, schema=[col]), ('POINT(1, 2)',))

def test_w_single_struct_column(self):
# SELECT (1, 2) AS col
sub_1 = _Field('REQUIRED', 'sub_1', 'INTEGER')
Expand Down

0 comments on commit 0745165

Please sign in to comment.