Repository: googleapis/google-cloud-python

Issue:
The Python BigQuery client fails on creating a view in standardSQL 
```python
from google.cloud import bigquery

bigquery_client = bigquery.Client(project="my-project")
dataset = bigquery_client.dataset("my-dataset")

table = dataset.table("v_test")

#Fails on Standard SQL
#table._properties['view'] = {'query': "select * from `my-project.my.dataset.test_table`",'useLegacySql':False}  

#Works on Legacy SQL
table._properties['view'] = {'query': "select * from [my-project:my-dataset.test_table]",'useLegacySql':True}  

try:
    table.create()
except Exception as err:
    print(err)
```
The https://github.com/GoogleCloudPlatform/google-cloud-python/blob/master/bigquery/google/cloud/bigquery/table.py#L355 should also take in another property for useLegacySql

Example  payload at http://stackoverflow.com/questions/33243427/how-can-i-create-a-new-view-in-bigquery-using-the-python-api



Discussion:
The first issue (that the commented out bit fails) seems like a back-end issue:  the [`Table` resource docs](https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#view.useLegacySql) should govern how that flag is used.

WRT how the API surface exposes standard vs. legacy dialects, I'd like to update the `view_query` setter to require standard SQL, and add another one (`view_query_legacy_sql`) to allow creating views with the older dialect.

Resolving patch:
diff --git a/bigquery/google/cloud/bigquery/table.py b/bigquery/google/cloud/bigquery/table.py
--- a/bigquery/google/cloud/bigquery/table.py
+++ b/bigquery/google/cloud/bigquery/table.py
@@ -193,7 +193,7 @@ def table_id(self):
     def table_type(self):
         """The type of the table.
 
-        Possible values are "TABLE" or "VIEW".
+        Possible values are "TABLE", "VIEW", or "EXTERNAL".
 
         :rtype: str, or ``NoneType``
         :returns: the URL (None until set from the server).
@@ -364,13 +364,49 @@ def view_query(self, value):
         """
         if not isinstance(value, six.string_types):
             raise ValueError("Pass a string")
-        self._properties['view'] = {'query': value}
+        if self._properties.get('view') is None:
+            self._properties['view'] = {}
+        self._properties['view']['query'] = value
 
     @view_query.deleter
     def view_query(self):
         """Delete SQL query defining the table as a view."""
         self._properties.pop('view', None)
 
+    @property
+    def view_use_legacy_sql(self):
+        """Specifies whether to execute the view with legacy or standard SQL.
+
+        If not set, None is returned. BigQuery's default mode is equivalent to
+        useLegacySql = True.
+
+        :rtype: bool, or ``NoneType``
+        :returns: The boolean for view.useLegacySql as set by the user, or
+                  None (the default).
+        """
+        view = self._properties.get('view')
+        if view is not None:
+            return view.get('useLegacySql')
+
+    @view_use_legacy_sql.setter
+    def view_use_legacy_sql(self, value):
+        """Update the view sub-property 'useLegacySql'.
+
+        This boolean specifies whether to execute the view with legacy SQL
+        (True) or standard SQL (False). The default, if not specified, is
+        'True'.
+
+        :type value: bool
+        :param value: The boolean for view.useLegacySql
+
+        :raises: ValueError for invalid value types.
+        """
+        if not isinstance(value, bool):
+            raise ValueError("Pass a boolean")
+        if self._properties.get('view') is None:
+            self._properties['view'] = {}
+        self._properties['view']['useLegacySql'] = value
+
     def list_partitions(self, client=None):
         """List the partitions in a table.
 
@@ -470,6 +506,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
 
         if self._schema:
             resource['schema'] = {
@@ -479,7 +517,7 @@ def _build_resource(self):
         return resource
 
     def create(self, client=None):
-        """API call:  create the dataset via a PUT request
+        """API call:  create the table via a PUT request
 
         See
         https://cloud.google.com/bigquery/docs/reference/rest/v2/tables/insert


Test patch:
diff --git a/bigquery/tests/unit/test_table.py b/bigquery/tests/unit/test_table.py
--- a/bigquery/tests/unit/test_table.py
+++ b/bigquery/tests/unit/test_table.py
@@ -124,8 +124,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'].get('useLegacySql'))
         else:
             self.assertIsNone(table.view_query)
+            self.assertIsNone(table.view_use_legacy_sql)
 
         if 'schema' in resource:
             self._verifySchema(table.schema, resource)
@@ -160,6 +162,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
@@ -358,6 +361,22 @@ 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.view_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.view_use_legacy_sql = False
+        table.view_query = 'select * from foo'
+        self.assertEqual(table.view_use_legacy_sql, False)
+        self.assertEqual(table.view_query, 'select * from foo')
+
     def test_from_api_repr_missing_identity(self):
         self._setUpConstants()
         client = _Client(self.PROJECT)
@@ -978,7 +997,7 @@ def test_update_w_alternate_client(self):
         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': True}
         RESOURCE['type'] = 'VIEW'
         conn1 = _Connection()
         client1 = _Client(project=self.PROJECT, connection=conn1)
@@ -990,6 +1009,7 @@ def test_update_w_alternate_client(self):
         table.location = LOCATION
         table.expires = self.EXP_TIME
         table.view_query = QUERY
+        table.view_use_legacy_sql = True
 
         table.update(client=client2)
 
@@ -1005,7 +1025,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': True},
         }
         self.assertEqual(req['data'], SENT)
         self._verifyResourceProperties(table, RESOURCE)