From 2a89001c272e57555cfb3c928b02f98d43bff201 Mon Sep 17 00:00:00 2001 From: Jens Larsson Date: Thu, 22 Dec 2016 12:25:59 +0100 Subject: [PATCH] Adding support for Standard SQL views Change-Id: I61d80c7359663f441006ac39f0b3826f7079bf0c --- bigquery/google/cloud/bigquery/table.py | 50 ++++++++++++++++++++++++- bigquery/unit_tests/test_table.py | 34 ++++++++++++++--- 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/bigquery/google/cloud/bigquery/table.py b/bigquery/google/cloud/bigquery/table.py index 18f83c596f7d5..b2dc8bb651b96 100644 --- a/bigquery/google/cloud/bigquery/table.py +++ b/bigquery/google/cloud/bigquery/table.py @@ -363,7 +363,43 @@ def view_query(self, value): """ if not isinstance(value, six.string_types): raise ValueError("Pass a string") - self._properties['view'] = {'query': value} + if 'view' in self._properties: + self._properties['view']['query'] = value + else: + self._properties['view'] = { + 'query': value, + 'useLegacySql': True + } + + @property + def view_use_legacy_sql(self): + """Boolean value definin SQL dialect of a view. + + :rtype: bool, or ``NoneType`` + :returns: boolean indicating whether the view uses standard + or legacy SQL + """ + view = self._properties.get('view') + if view is not None: + return view.get('useLegacySql', True) + + @view_use_legacy_sql.setter + def view_use_legacy_sql(self, value): + """Update SQL dialect for view + + :type value: bool + :param value: Whether to use legacy SQL + + :raises: ValueError for invalid value types. + """ + if not isinstance(value, bool): + raise ValueError("Pass a boolean value") + if 'view' in self._properties: + self._properties['view']['useLegacySql'] = value + else: + self._properties['view'] = { + 'useLegacySql': value + } @view_query.deleter def view_query(self): @@ -469,6 +505,8 @@ def _build_resource(self): if self.view_query is not None: view = resource['view'] = {} view['query'] = self.view_query + if self.view_use_legacy_sql is not None: + view['useLegacySql'] = self.view_use_legacy_sql elif self._schema: resource['schema'] = { 'fields': _build_schema_resource(self._schema) @@ -544,6 +582,7 @@ def patch(self, location=_MARKER, expires=_MARKER, view_query=_MARKER, + view_use_legacy_sql=_MARKER, schema=_MARKER): """API call: update individual table properties via a PATCH request @@ -571,6 +610,9 @@ def patch(self, :type view_query: str :param view_query: SQL query defining the table as a view + :type view_use_legacy_sql: str + :param view_use_legacy_sql: Boolean indicating view_query dialect + :type schema: list of :class:`SchemaField` :param schema: fields describing the schema @@ -601,6 +643,12 @@ def patch(self, else: partial['view'] = {'query': view_query} + if view_use_legacy_sql is not _MARKER: + if 'view' in partial: + partial['view']['useLegacySql'] = view_use_legacy_sql + else: + partial['view'] = {'useLegacySql': view_use_legacy_sql} + if schema is not _MARKER: if schema is None: partial['schema'] = None diff --git a/bigquery/unit_tests/test_table.py b/bigquery/unit_tests/test_table.py index 0b84531030825..4bf2695110141 100644 --- a/bigquery/unit_tests/test_table.py +++ b/bigquery/unit_tests/test_table.py @@ -123,8 +123,10 @@ def _verifyResourceProperties(self, table, resource): if 'view' in resource: self.assertEqual(table.view_query, resource['view']['query']) + self.assertEqual(table.view_use_legacy_sql, resource['view']['useLegacySql']) else: self.assertIsNone(table.view_query) + self.assertIsNone(table.view_use_legacy_sql) if 'schema' in resource: self._verifySchema(table.schema, resource) @@ -159,6 +161,7 @@ def test_ctor(self): self.assertIsNone(table.friendly_name) self.assertIsNone(table.location) self.assertIsNone(table.view_query) + self.assertIsNone(table.view_use_legacy_sql) def test_ctor_w_schema(self): from google.cloud.bigquery.table import SchemaField @@ -354,6 +357,20 @@ def test_view_query_deleter(self): del table.view_query self.assertIsNone(table.view_query) + def test_view_use_legacy_sql_setter_bad_value(self): + client = _Client(self.PROJECT) + dataset = _Dataset(client) + table = self._make_one(self.TABLE_NAME, dataset) + with self.assertRaises(ValueError): + table.use_legacy_sql = 12345 + + def test_view_use_legacy_sql_setter(self): + client = _Client(self.PROJECT) + dataset = _Dataset(client) + table = self._make_one(self.TABLE_NAME, dataset) + table.use_legacy_sql = False + self.assertFalse(table.use_legacy_sql) + def test_from_api_repr_missing_identity(self): self._setUpConstants() client = _Client(self.PROJECT) @@ -637,6 +654,7 @@ def test_create_w_alternate_client(self): DESCRIPTION = 'DESCRIPTION' TITLE = 'TITLE' QUERY = 'select fullname, age from person_ages' + LEGACY_SQL = False RESOURCE = self._makeResource() RESOURCE['description'] = DESCRIPTION RESOURCE['friendlyName'] = TITLE @@ -645,6 +663,7 @@ def test_create_w_alternate_client(self): RESOURCE['expirationTime'] = _millis(self.EXP_TIME) RESOURCE['view'] = {} RESOURCE['view']['query'] = QUERY + RESOURCE['view']['useLegacySql'] = LEGACY_SQL RESOURCE['type'] = 'VIEW' conn1 = _Connection() client1 = _Client(project=self.PROJECT, connection=conn1) @@ -673,7 +692,10 @@ def test_create_w_alternate_client(self): 'tableId': self.TABLE_NAME}, 'description': DESCRIPTION, 'friendlyName': TITLE, - 'view': {'query': QUERY}, + 'view': { + 'query': QUERY, + 'useLegacySql': LEGACY_SQL + }, } self.assertEqual(req['data'], SENT) self._verifyResourceProperties(table, RESOURCE) @@ -833,9 +855,10 @@ def test_patch_w_alternate_client(self): PATH = 'projects/%s/datasets/%s/tables/%s' % ( self.PROJECT, self.DS_NAME, self.TABLE_NAME) QUERY = 'select fullname, age from person_ages' + LEGACY_SQL = False LOCATION = 'EU' RESOURCE = self._makeResource() - RESOURCE['view'] = {'query': QUERY} + RESOURCE['view'] = {'query': QUERY, 'useLegacySql': LEGACY_SQL} RESOURCE['type'] = 'VIEW' RESOURCE['location'] = LOCATION self.EXP_TIME = datetime.datetime(2015, 8, 1, 23, 59, 59, @@ -859,7 +882,7 @@ def test_patch_w_alternate_client(self): self.assertEqual(req['method'], 'PATCH') self.assertEqual(req['path'], '/%s' % PATH) SENT = { - 'view': {'query': QUERY}, + 'view': {'query': QUERY, 'useLegacySql': LEGACY_SQL}, 'location': LOCATION, 'expirationTime': _millis(self.EXP_TIME), 'schema': {'fields': [ @@ -943,13 +966,14 @@ def test_update_w_alternate_client(self): DEF_TABLE_EXP = 12345 LOCATION = 'EU' QUERY = 'select fullname, age from person_ages' + LEGACY_SQL = False RESOURCE = self._makeResource() RESOURCE['defaultTableExpirationMs'] = 12345 RESOURCE['location'] = LOCATION self.EXP_TIME = datetime.datetime(2015, 8, 1, 23, 59, 59, tzinfo=UTC) RESOURCE['expirationTime'] = _millis(self.EXP_TIME) - RESOURCE['view'] = {'query': QUERY} + RESOURCE['view'] = {'query': QUERY, 'useLegacySql': LEGACY_SQL} RESOURCE['type'] = 'VIEW' conn1 = _Connection() client1 = _Client(project=self.PROJECT, connection=conn1) @@ -976,7 +1000,7 @@ def test_update_w_alternate_client(self): 'tableId': self.TABLE_NAME}, 'expirationTime': _millis(self.EXP_TIME), 'location': 'EU', - 'view': {'query': QUERY}, + 'view': {'query': QUERY, 'useLegacySql': LEGACY_SQL}, } self.assertEqual(req['data'], SENT) self._verifyResourceProperties(table, RESOURCE)