Skip to content

Commit 0745165

Browse files
authored
BigQuery: add support for GEOGRAPHY type (#6147)
.
1 parent ecc3845 commit 0745165

4 files changed

Lines changed: 44 additions & 32 deletions

File tree

bigquery/google/cloud/bigquery/_helpers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ def _record_from_json(value, field):
176176
'BOOLEAN': _bool_from_json,
177177
'BOOL': _bool_from_json,
178178
'STRING': _string_from_json,
179+
'GEOGRAPHY': _string_from_json,
179180
'BYTES': _bytes_from_json,
180181
'TIMESTAMP': _timestamp_from_json,
181182
'DATETIME': _datetime_from_json,

bigquery/google/cloud/bigquery/schema.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,19 @@
1818
class SchemaField(object):
1919
"""Describe a single field within a table schema.
2020
21-
:type name: str
22-
:param name: the name of the field.
21+
Args:
22+
name (str): the name of the field.
2323
24-
:type field_type: str
25-
:param field_type: the type of the field (one of 'STRING', 'INTEGER',
26-
'FLOAT', 'NUMERIC', 'BOOLEAN', 'TIMESTAMP' or
27-
'RECORD').
24+
field_type (str): the type of the field. See
25+
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#schema.fields.type
2826
29-
:type mode: str
30-
:param mode: the mode of the field (one of 'NULLABLE', 'REQUIRED',
31-
or 'REPEATED').
27+
mode (str): the mode of the field. See
28+
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#schema.fields.mode
3229
33-
:type description: str
34-
:param description: optional description for the field.
30+
description (Optional[str]):description for the field.
3531
36-
:type fields: tuple of :class:`~google.cloud.bigquery.schema.SchemaField`
37-
:param fields: subfields (requires ``field_type`` of 'RECORD').
32+
fields (Tuple[:class:`~google.cloud.bigquery.schema.SchemaField`]):
33+
subfields (requires ``field_type`` of 'RECORD').
3834
"""
3935
def __init__(self, name, field_type, mode='NULLABLE',
4036
description=None, fields=()):
@@ -78,35 +74,35 @@ def name(self):
7874
def field_type(self):
7975
"""str: The type of the field.
8076
81-
Will be one of 'STRING', 'INTEGER', 'FLOAT', 'NUMERIC',
82-
'BOOLEAN', 'TIMESTAMP' or 'RECORD'.
77+
See:
78+
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#schema.fields.type
8379
"""
8480
return self._field_type
8581

8682
@property
8783
def mode(self):
8884
"""str: The mode of the field.
8985
90-
Will be one of 'NULLABLE', 'REQUIRED', or 'REPEATED'.
86+
See:
87+
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#schema.fields.mode
9188
"""
9289
return self._mode
9390

9491
@property
9592
def is_nullable(self):
96-
"""Check whether 'mode' is 'nullable'."""
93+
"""bool: whether 'mode' is 'nullable'."""
9794
return self._mode == 'NULLABLE'
9895

9996
@property
10097
def description(self):
101-
"""Optional[str]: Description for the field."""
98+
"""Optional[str]: description for the field."""
10299
return self._description
103100

104101
@property
105102
def fields(self):
106103
"""tuple: Subfields contained in this field.
107104
108-
If ``field_type`` is not 'RECORD', this property must be
109-
empty / unset.
105+
Must be empty unset if ``field_type`` is not 'RECORD'.
110106
"""
111107
return self._fields
112108

@@ -168,14 +164,12 @@ def __repr__(self):
168164
def _parse_schema_resource(info):
169165
"""Parse a resource fragment into a schema field.
170166
171-
:type info: mapping
172-
:param info: should contain a "fields" key to be parsed
167+
Args:
168+
info: (Mapping[str->dict]): should contain a "fields" key to be parsed
173169
174-
:rtype:
175-
list of :class:`google.cloud.bigquery.schema.SchemaField`, or
176-
``NoneType``
177-
:returns: a list of parsed fields, or ``None`` if no "fields" key is
178-
present in ``info``.
170+
Returns:
171+
(Union[Sequence[:class:`google.cloud.bigquery.schema.SchemaField`],None])
172+
a list of parsed fields, or ``None`` if no "fields" key found.
179173
"""
180174
if 'fields' not in info:
181175
return ()
@@ -195,11 +189,11 @@ def _parse_schema_resource(info):
195189
def _build_schema_resource(fields):
196190
"""Generate a resource fragment for a schema.
197191
198-
:type fields:
199-
sequence of :class:`~google.cloud.bigquery.schema.SchemaField`
200-
:param fields: schema to be dumped
192+
Args:
193+
fields [Sequence[:class:`~google.cloud.bigquery.schema.SchemaField`]):
194+
schema to be dumped
201195
202-
:rtype: mapping
203-
:returns: a mapping describing the schema of the supplied fields.
196+
Returns: (Sequence[dict])
197+
mappings describing the schema of the supplied fields.
204198
"""
205199
return [field.to_api_repr() for field in fields]

bigquery/tests/system.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,10 @@ def _generate_standard_sql_types_examples(self):
10191019
'sql': 'SELECT ARRAY(SELECT STRUCT([1, 2]))',
10201020
'expected': [{u'_field_1': [1, 2]}],
10211021
},
1022+
{
1023+
'sql': 'SELECT ST_GeogPoint(1, 2)',
1024+
'expected': 'POINT(1 2)',
1025+
},
10221026
]
10231027

10241028
def test_query_w_standard_sql_types(self):

bigquery/tests/unit/test__helpers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,13 @@ def test_w_scalar_subfield(self):
376376
coerced = self._call_fut(value, field)
377377
self.assertEqual(coerced, {'age': 42})
378378

379+
def test_w_scalar_subfield_geography(self):
380+
subfield = _Field('REQUIRED', 'geo', 'GEOGRAPHY')
381+
field = _Field('REQUIRED', fields=[subfield])
382+
value = {'f': [{'v': 'POINT(1, 2)'}]}
383+
coerced = self._call_fut(value, field)
384+
self.assertEqual(coerced, {'geo': 'POINT(1, 2)'})
385+
379386
def test_w_repeated_subfield(self):
380387
subfield = _Field('REPEATED', 'color', 'STRING')
381388
field = _Field('REQUIRED', fields=[subfield])
@@ -444,6 +451,12 @@ def test_w_single_scalar_column(self):
444451
row = {u'f': [{u'v': u'1'}]}
445452
self.assertEqual(self._call_fut(row, schema=[col]), (1,))
446453

454+
def test_w_single_scalar_geography_column(self):
455+
# SELECT 1 AS col
456+
col = _Field('REQUIRED', 'geo', 'GEOGRAPHY')
457+
row = {u'f': [{u'v': u'POINT(1, 2)'}]}
458+
self.assertEqual(self._call_fut(row, schema=[col]), ('POINT(1, 2)',))
459+
447460
def test_w_single_struct_column(self):
448461
# SELECT (1, 2) AS col
449462
sub_1 = _Field('REQUIRED', 'sub_1', 'INTEGER')

0 commit comments

Comments
 (0)