Skip to content

Commit 7fa089c

Browse files
Rich Kadeltseaver
authored andcommitted
Add support for creating a view with 'useLegacySql = False' (googleapis#3514)
1 parent bc7b0fd commit 7fa089c

2 files changed

Lines changed: 63 additions & 5 deletions

File tree

bigquery/google/cloud/bigquery/table.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def table_id(self):
193193
def table_type(self):
194194
"""The type of the table.
195195
196-
Possible values are "TABLE" or "VIEW".
196+
Possible values are "TABLE", "VIEW", or "EXTERNAL".
197197
198198
:rtype: str, or ``NoneType``
199199
:returns: the URL (None until set from the server).
@@ -364,13 +364,49 @@ def view_query(self, value):
364364
"""
365365
if not isinstance(value, six.string_types):
366366
raise ValueError("Pass a string")
367-
self._properties['view'] = {'query': value}
367+
if self._properties.get('view') is None:
368+
self._properties['view'] = {}
369+
self._properties['view']['query'] = value
368370

369371
@view_query.deleter
370372
def view_query(self):
371373
"""Delete SQL query defining the table as a view."""
372374
self._properties.pop('view', None)
373375

376+
@property
377+
def view_use_legacy_sql(self):
378+
"""Specifies whether to execute the view with legacy or standard SQL.
379+
380+
If not set, None is returned. BigQuery's default mode is equivalent to
381+
useLegacySql = True.
382+
383+
:rtype: bool, or ``NoneType``
384+
:returns: The boolean for view.useLegacySql as set by the user, or
385+
None (the default).
386+
"""
387+
view = self._properties.get('view')
388+
if view is not None:
389+
return view.get('useLegacySql')
390+
391+
@view_use_legacy_sql.setter
392+
def view_use_legacy_sql(self, value):
393+
"""Update the view sub-property 'useLegacySql'.
394+
395+
This boolean specifies whether to execute the view with legacy SQL
396+
(True) or standard SQL (False). The default, if not specified, is
397+
'True'.
398+
399+
:type value: bool
400+
:param value: The boolean for view.useLegacySql
401+
402+
:raises: ValueError for invalid value types.
403+
"""
404+
if not isinstance(value, bool):
405+
raise ValueError("Pass a boolean")
406+
if self._properties.get('view') is None:
407+
self._properties['view'] = {}
408+
self._properties['view']['useLegacySql'] = value
409+
374410
def list_partitions(self, client=None):
375411
"""List the partitions in a table.
376412
@@ -470,6 +506,8 @@ def _build_resource(self):
470506
if self.view_query is not None:
471507
view = resource['view'] = {}
472508
view['query'] = self.view_query
509+
if self.view_use_legacy_sql is not None:
510+
view['useLegacySql'] = self.view_use_legacy_sql
473511

474512
if self._schema:
475513
resource['schema'] = {
@@ -479,7 +517,7 @@ def _build_resource(self):
479517
return resource
480518

481519
def create(self, client=None):
482-
"""API call: create the dataset via a PUT request
520+
"""API call: create the table via a PUT request
483521
484522
See
485523
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables/insert

bigquery/tests/unit/test_table.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,10 @@ def _verifyResourceProperties(self, table, resource):
124124

125125
if 'view' in resource:
126126
self.assertEqual(table.view_query, resource['view']['query'])
127+
self.assertEqual(table.view_use_legacy_sql, resource['view'].get('useLegacySql'))
127128
else:
128129
self.assertIsNone(table.view_query)
130+
self.assertIsNone(table.view_use_legacy_sql)
129131

130132
if 'schema' in resource:
131133
self._verifySchema(table.schema, resource)
@@ -160,6 +162,7 @@ def test_ctor(self):
160162
self.assertIsNone(table.friendly_name)
161163
self.assertIsNone(table.location)
162164
self.assertIsNone(table.view_query)
165+
self.assertIsNone(table.view_use_legacy_sql)
163166

164167
def test_ctor_w_schema(self):
165168
from google.cloud.bigquery.table import SchemaField
@@ -358,6 +361,22 @@ def test_view_query_deleter(self):
358361
del table.view_query
359362
self.assertIsNone(table.view_query)
360363

364+
def test_view_use_legacy_sql_setter_bad_value(self):
365+
client = _Client(self.PROJECT)
366+
dataset = _Dataset(client)
367+
table = self._make_one(self.TABLE_NAME, dataset)
368+
with self.assertRaises(ValueError):
369+
table.view_use_legacy_sql = 12345
370+
371+
def test_view_use_legacy_sql_setter(self):
372+
client = _Client(self.PROJECT)
373+
dataset = _Dataset(client)
374+
table = self._make_one(self.TABLE_NAME, dataset)
375+
table.view_use_legacy_sql = False
376+
table.view_query = 'select * from foo'
377+
self.assertEqual(table.view_use_legacy_sql, False)
378+
self.assertEqual(table.view_query, 'select * from foo')
379+
361380
def test_from_api_repr_missing_identity(self):
362381
self._setUpConstants()
363382
client = _Client(self.PROJECT)
@@ -978,7 +997,7 @@ def test_update_w_alternate_client(self):
978997
self.EXP_TIME = datetime.datetime(2015, 8, 1, 23, 59, 59,
979998
tzinfo=UTC)
980999
RESOURCE['expirationTime'] = _millis(self.EXP_TIME)
981-
RESOURCE['view'] = {'query': QUERY}
1000+
RESOURCE['view'] = {'query': QUERY, 'useLegacySql': True}
9821001
RESOURCE['type'] = 'VIEW'
9831002
conn1 = _Connection()
9841003
client1 = _Client(project=self.PROJECT, connection=conn1)
@@ -990,6 +1009,7 @@ def test_update_w_alternate_client(self):
9901009
table.location = LOCATION
9911010
table.expires = self.EXP_TIME
9921011
table.view_query = QUERY
1012+
table.view_use_legacy_sql = True
9931013

9941014
table.update(client=client2)
9951015

@@ -1005,7 +1025,7 @@ def test_update_w_alternate_client(self):
10051025
'tableId': self.TABLE_NAME},
10061026
'expirationTime': _millis(self.EXP_TIME),
10071027
'location': 'EU',
1008-
'view': {'query': QUERY},
1028+
'view': {'query': QUERY, 'useLegacySql': True},
10091029
}
10101030
self.assertEqual(req['data'], SENT)
10111031
self._verifyResourceProperties(table, RESOURCE)

0 commit comments

Comments
 (0)