Repository: googleapis/python-spanner-django

Issue:
django: backends test failures
Investigate and and ticket out these issues as needed:
```
======================================================================
ERROR: test_nested_wrapper_invoked (backends.base.test_base.ExecuteWrapperTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
spanner.dbapi.exceptions.ProgrammingError: Unimplemented

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/base/test_base.py", line 99, in test_nested_wrapper_invoked
    self.call_executemany(connection)
  File "/home/tim/code/django/tests/backends/base/test_base.py", line 55, in call_executemany
    cursor.executemany(sql, params)
  File "/home/tim/code/django/django/db/backends/utils.py", line 70, in executemany
    return self._execute_with_wrappers(sql, param_list, many=True, executor=self._executemany)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/opt/python3.7.0/lib/python3.7/unittest/mock.py", line 951, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/opt/python3.7.0/lib/python3.7/unittest/mock.py", line 1017, in _mock_call
    ret_val = effect(*args, **kwargs)
  File "/home/tim/code/django/tests/backends/base/test_base.py", line 59, in <lambda>
    return MagicMock(side_effect=lambda execute, *args: execute(*args))
  File "/opt/python3.7.0/lib/python3.7/unittest/mock.py", line 951, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/opt/python3.7.0/lib/python3.7/unittest/mock.py", line 1017, in _mock_call
    ret_val = effect(*args, **kwargs)
  File "/home/tim/code/django/tests/backends/base/test_base.py", line 59, in <lambda>
    return MagicMock(side_effect=lambda execute, *args: execute(*args))
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
django.db.utils.ProgrammingError: Unimplemented

======================================================================
ERROR: test_wrapper_invoked_many (backends.base.test_base.ExecuteWrapperTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
spanner.dbapi.exceptions.ProgrammingError: Unimplemented

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/base/test_base.py", line 75, in test_wrapper_invoked_many
    self.call_executemany(connection)
  File "/home/tim/code/django/tests/backends/base/test_base.py", line 55, in call_executemany
    cursor.executemany(sql, params)
  File "/home/tim/code/django/django/db/backends/utils.py", line 70, in executemany
    return self._execute_with_wrappers(sql, param_list, many=True, executor=self._executemany)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/opt/python3.7.0/lib/python3.7/unittest/mock.py", line 951, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/opt/python3.7.0/lib/python3.7/unittest/mock.py", line 1017, in _mock_call
    ret_val = effect(*args, **kwargs)
  File "/home/tim/code/django/tests/backends/base/test_base.py", line 59, in <lambda>
    return MagicMock(side_effect=lambda execute, *args: execute(*args))
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
django.db.utils.ProgrammingError: Unimplemented

======================================================================
ERROR: test_django_date_trunc (backends.tests.DateQuotingTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 79, in next
    return six.next(self._wrapped)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/grpc/_channel.py", line 392, in __next__
    return self._next()
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/grpc/_channel.py", line 561, in _next
    raise self
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
	status = StatusCode.INVALID_ARGUMENT
	details = "No matching signature for function DATE_TRUNC for argument types: TIMESTAMP, DATE_TIME_PART. Supported signature: DATE_TRUNC(DATE, DATE_TIME_PART) [at 1:17]\nSELECT DISTINCT DATE_TRUNC(backends_schoolclass.last_updated, year) AS datefi...\n                ^"
	debug_error_string = "{"created":"@1583849731.254325436","description":"Error received from peer ipv4:172.217.3.106:443","file":"src/core/lib/surface/call.cc","file_line":1055,"grpc_message":"No matching signature for function DATE_TRUNC for argument types: TIMESTAMP, DATE_TIME_PART. Supported signature: DATE_TRUNC(DATE, DATE_TIME_PART) [at 1:17]\nSELECT DISTINCT DATE_TRUNC(backends_schoolclass.last_updated, year) AS datefi...\n                ^","grpc_status":3}"
>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 91, in execute
    self.__handle_DQL(sql, args or None)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 189, in __handle_DQL
    self.__itr = PeekIterator(self.__res)
  File "/home/tim/code/spanner-orm/spanner/dbapi/utils.py", line 27, in __init__
    head = next(itr_src)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/cloud/spanner_v1/streamed.py", line 143, in __iter__
    self._consume_next()
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/cloud/spanner_v1/streamed.py", line 116, in _consume_next
    response = six.next(self._response_iterator)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/cloud/spanner_v1/snapshot.py", line 45, in _restart_on_unavailable
    for item in iterator:
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 81, in next
    six.raise_from(exceptions.from_grpc_error(exc), exc)
  File "<string>", line 3, in raise_from
google.api_core.exceptions.InvalidArgument: 400 No matching signature for function DATE_TRUNC for argument types: TIMESTAMP, DATE_TIME_PART. Supported signature: DATE_TRUNC(DATE, DATE_TIME_PART) [at 1:17]\nSELECT DISTINCT DATE_TRUNC(backends_schoolclass.last_updated, year) AS datefi...\n                ^

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 99, in execute
    raise ProgrammingError(e.details if hasattr(e, 'details') else e)
spanner.dbapi.exceptions.ProgrammingError: 400 No matching signature for function DATE_TRUNC for argument types: TIMESTAMP, DATE_TIME_PART. Supported signature: DATE_TRUNC(DATE, DATE_TIME_PART) [at 1:17]\nSELECT DISTINCT DATE_TRUNC(backends_schoolclass.last_updated, year) AS datefi...\n                ^

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 38, in test_django_date_trunc
    self.assertEqual(list(years), [datetime.date(2010, 1, 1)])
  File "/home/tim/code/django/django/db/models/query.py", line 274, in __iter__
    self._fetch_all()
  File "/home/tim/code/django/django/db/models/query.py", line 1242, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/home/tim/code/django/django/db/models/query.py", line 182, in __iter__
    for row in compiler.results_iter(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size):
  File "/home/tim/code/django/django/db/models/sql/compiler.py", line 1085, in results_iter
    results = self.execute_sql(MULTI, chunked_fetch=chunked_fetch, chunk_size=chunk_size)
  File "/home/tim/code/django/django/db/models/sql/compiler.py", line 1133, in execute_sql
    cursor.execute(sql, params)
  File "/home/tim/code/django/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 99, in execute
    raise ProgrammingError(e.details if hasattr(e, 'details') else e)
django.db.utils.ProgrammingError: 400 No matching signature for function DATE_TRUNC for argument types: TIMESTAMP, DATE_TIME_PART. Supported signature: DATE_TRUNC(DATE, DATE_TIME_PART) [at 1:17]\nSELECT DISTINCT DATE_TRUNC(backends_schoolclass.last_updated, year) AS datefi...\n                ^

======================================================================
ERROR: test_query_encoding (backends.tests.LastExecutedQueryTest)
last_executed_query() returns a string.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 79, in next
    return six.next(self._wrapped)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/grpc/_channel.py", line 392, in __next__
    return self._next()
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/grpc/_channel.py", line 561, in _next
    raise self
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
	status = StatusCode.INVALID_ARGUMENT
	details = "Syntax error: Illegal input character \"\\303\" [at 1:16]\nSELECT (1) AS f\303\266\303\266, backends_rawdata.id, backends_rawdata.raw_data FROM back...\n               ^"
	debug_error_string = "{"created":"@1583849978.977754676","description":"Error received from peer ipv4:172.217.10.106:443","file":"src/core/lib/surface/call.cc","file_line":1055,"grpc_message":"Syntax error: Illegal input character \"\\303\" [at 1:16]\nSELECT (1) AS f\303\266\303\266, backends_rawdata.id, backends_rawdata.raw_data FROM back...\n               ^","grpc_status":3}"
>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 91, in execute
    self.__handle_DQL(sql, args or None)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 189, in __handle_DQL
    self.__itr = PeekIterator(self.__res)
  File "/home/tim/code/spanner-orm/spanner/dbapi/utils.py", line 27, in __init__
    head = next(itr_src)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/cloud/spanner_v1/streamed.py", line 143, in __iter__
    self._consume_next()
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/cloud/spanner_v1/streamed.py", line 116, in _consume_next
    response = six.next(self._response_iterator)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/cloud/spanner_v1/snapshot.py", line 45, in _restart_on_unavailable
    for item in iterator:
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 81, in next
    six.raise_from(exceptions.from_grpc_error(exc), exc)
  File "<string>", line 3, in raise_from
google.api_core.exceptions.InvalidArgument: 400 Syntax error: Illegal input character \"\\303\" [at 1:16]\nSELECT (1) AS f\303\266\303\266, backends_rawdata.id, backends_rawdata.raw_data FROM back...\n               ^

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 99, in execute
    raise ProgrammingError(e.details if hasattr(e, 'details') else e)
spanner.dbapi.exceptions.ProgrammingError: 400 Syntax error: Illegal input character \"\\303\" [at 1:16]\nSELECT (1) AS f\303\266\303\266, backends_rawdata.id, backends_rawdata.raw_data FROM back...\n               ^

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 72, in test_query_encoding
    cursor = data.query.get_compiler('default').execute_sql(CURSOR)
  File "/home/tim/code/django/django/db/models/sql/compiler.py", line 1133, in execute_sql
    cursor.execute(sql, params)
  File "/home/tim/code/django/django/db/backends/utils.py", line 99, in execute
    return super().execute(sql, params)
  File "/home/tim/code/django/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 99, in execute
    raise ProgrammingError(e.details if hasattr(e, 'details') else e)
django.db.utils.ProgrammingError: 400 Syntax error: Illegal input character \"\\303\" [at 1:16]\nSELECT (1) AS f\303\266\303\266, backends_rawdata.id, backends_rawdata.raw_data FROM back...\n               ^

======================================================================
ERROR: test_cursor_contextmanager (backends.tests.BackendTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 82, in _execute
    return self.cursor.execute(sql)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 72, in execute
    self.__raise_if_already_closed()
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 56, in __raise_if_already_closed
    raise Error('attempting to use an already closed connection')
spanner.dbapi.exceptions.Error: attempting to use an already closed connection

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 363, in test_cursor_contextmanager
    cursor.execute("SELECT 1" + connection.features.bare_select_suffix)
  File "/home/tim/code/django/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 82, in _execute
    return self.cursor.execute(sql)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 72, in execute
    self.__raise_if_already_closed()
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 56, in __raise_if_already_closed
    raise Error('attempting to use an already closed connection')
django.db.utils.Error: attempting to use an already closed connection

======================================================================
ERROR: test_cursor_execute_with_pyformat (backends.tests.BackendTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 93, in execute
    self.__handle_insert(sql, args or None)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 121, in __handle_insert
    parts = parse_insert(sql, params)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parse_utils.py", line 158, in parse_insert
    _, values = parse_values(values_str)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parser.py", line 143, in parse_values
    return expect(stmt, VALUES)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parser.py", line 160, in expect
    word, arg = expect(word, ARGS)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parser.py", line 218, in expect
    word, parsed = expect(word, FUNC)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parser.py", line 181, in expect
    raise ProgrammingError('FUNC: `%s` does not begin with `a-zA-z` nor a `_`' % word)
spanner.dbapi.exceptions.ProgrammingError: FUNC: `%(root)s, %(square)s)` does not begin with `a-zA-z` nor a `_`

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/django/test/testcases.py", line 1229, in skip_wrapper
    return test_func(*args, **kwargs)
  File "/home/tim/code/django/tests/backends/tests.py", line 266, in test_cursor_execute_with_pyformat
    self.create_squares(args, 'pyformat', multiple=False)
  File "/home/tim/code/django/tests/backends/tests.py", line 233, in create_squares
    cursor.execute(query, args)
  File "/home/tim/code/django/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 93, in execute
    self.__handle_insert(sql, args or None)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 121, in __handle_insert
    parts = parse_insert(sql, params)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parse_utils.py", line 158, in parse_insert
    _, values = parse_values(values_str)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parser.py", line 143, in parse_values
    return expect(stmt, VALUES)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parser.py", line 160, in expect
    word, arg = expect(word, ARGS)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parser.py", line 218, in expect
    word, parsed = expect(word, FUNC)
  File "/home/tim/code/spanner-orm/spanner/dbapi/parser.py", line 181, in expect
    raise ProgrammingError('FUNC: `%s` does not begin with `a-zA-z` nor a `_`' % word)
django.db.utils.ProgrammingError: FUNC: `%(root)s, %(square)s)` does not begin with `a-zA-z` nor a `_`

======================================================================
ERROR: test_cursor_executemany (backends.tests.BackendTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
spanner.dbapi.exceptions.ProgrammingError: Unimplemented

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 238, in test_cursor_executemany
    self.create_squares_with_executemany(args)
  File "/home/tim/code/django/tests/backends/tests.py", line 216, in create_squares_with_executemany
    self.create_squares(args, 'format', True)
  File "/home/tim/code/django/tests/backends/tests.py", line 231, in create_squares
    cursor.executemany(query, args)
  File "/home/tim/code/django/django/db/backends/utils.py", line 70, in executemany
    return self._execute_with_wrappers(sql, param_list, many=True, executor=self._executemany)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
django.db.utils.ProgrammingError: Unimplemented

======================================================================
ERROR: test_cursor_executemany_with_empty_params_list (backends.tests.BackendTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
spanner.dbapi.exceptions.ProgrammingError: Unimplemented

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 247, in test_cursor_executemany_with_empty_params_list
    self.create_squares_with_executemany(args)
  File "/home/tim/code/django/tests/backends/tests.py", line 216, in create_squares_with_executemany
    self.create_squares(args, 'format', True)
  File "/home/tim/code/django/tests/backends/tests.py", line 231, in create_squares
    cursor.executemany(query, args)
  File "/home/tim/code/django/django/db/backends/utils.py", line 70, in executemany
    return self._execute_with_wrappers(sql, param_list, many=True, executor=self._executemany)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
django.db.utils.ProgrammingError: Unimplemented

======================================================================
ERROR: test_cursor_executemany_with_iterator (backends.tests.BackendTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
spanner.dbapi.exceptions.ProgrammingError: Unimplemented

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 253, in test_cursor_executemany_with_iterator
    self.create_squares_with_executemany(args)
  File "/home/tim/code/django/tests/backends/tests.py", line 216, in create_squares_with_executemany
    self.create_squares(args, 'format', True)
  File "/home/tim/code/django/tests/backends/tests.py", line 231, in create_squares
    cursor.executemany(query, args)
  File "/home/tim/code/django/django/db/backends/utils.py", line 70, in executemany
    return self._execute_with_wrappers(sql, param_list, many=True, executor=self._executemany)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
django.db.utils.ProgrammingError: Unimplemented

======================================================================
ERROR: test_cursor_executemany_with_pyformat (backends.tests.BackendTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
spanner.dbapi.exceptions.ProgrammingError: Unimplemented

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/django/test/testcases.py", line 1229, in skip_wrapper
    return test_func(*args, **kwargs)
  File "/home/tim/code/django/tests/backends/tests.py", line 273, in test_cursor_executemany_with_pyformat
    self.create_squares(args, 'pyformat', multiple=True)
  File "/home/tim/code/django/tests/backends/tests.py", line 231, in create_squares
    cursor.executemany(query, args)
  File "/home/tim/code/django/django/db/backends/utils.py", line 70, in executemany
    return self._execute_with_wrappers(sql, param_list, many=True, executor=self._executemany)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
django.db.utils.ProgrammingError: Unimplemented

======================================================================
ERROR: test_cursor_executemany_with_pyformat_iterator (backends.tests.BackendTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
spanner.dbapi.exceptions.ProgrammingError: Unimplemented

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/django/test/testcases.py", line 1229, in skip_wrapper
    return test_func(*args, **kwargs)
  File "/home/tim/code/django/tests/backends/tests.py", line 282, in test_cursor_executemany_with_pyformat_iterator
    self.create_squares(args, 'pyformat', multiple=True)
  File "/home/tim/code/django/tests/backends/tests.py", line 231, in create_squares
    cursor.executemany(query, args)
  File "/home/tim/code/django/django/db/backends/utils.py", line 70, in executemany
    return self._execute_with_wrappers(sql, param_list, many=True, executor=self._executemany)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/django/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/tim/code/django/django/db/backends/utils.py", line 89, in _executemany
    return self.cursor.executemany(sql, param_list)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 209, in executemany
    raise ProgrammingError('Unimplemented')
django.db.utils.ProgrammingError: Unimplemented

======================================================================
ERROR: test_duplicate_table_error (backends.tests.BackendTestCase)
Creating an existing table returns a DatabaseError
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 57, in error_remapped_callable
    return callable_(*args, **kwargs)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/grpc/_channel.py", line 690, in __call__
    return _end_unary_response_blocking(state, call, False, None)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/grpc/_channel.py", line 592, in _end_unary_response_blocking
    raise _Rendezvous(state, None, None, deadline)
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
	status = StatusCode.INVALID_ARGUMENT
	details = "Errors parsing Spanner DDL statement: CREATE TABLE backends_article (id INTEGER); : Syntax error on line 1, column 35: Expecting \'EOF\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Encountered \'INTEGER\' while parsing: column_type\nSyntax error on line 1, column 35: Expecting \')\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Expecting \'PRIMARY\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Expecting \'KEY\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Expecting \'(\' but found \'INTEGER\'\nSyntax error on line 1, column 43: Expecting \'EOF\' but found an unknown character (\";\")."
	debug_error_string = "{"created":"@1583850188.926493946","description":"Error received from peer ipv4:172.217.11.42:443","file":"src/core/lib/surface/call.cc","file_line":1055,"grpc_message":"Errors parsing Spanner DDL statement: CREATE TABLE backends_article (id INTEGER); : Syntax error on line 1, column 35: Expecting \'EOF\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Encountered \'INTEGER\' while parsing: column_type\nSyntax error on line 1, column 35: Expecting \')\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Expecting \'PRIMARY\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Expecting \'KEY\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Expecting \'(\' but found \'INTEGER\'\nSyntax error on line 1, column 43: Expecting \'EOF\' but found an unknown character (\";\").","grpc_status":3}"
>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/django/test/testcases.py", line 274, in __call__
    self._post_teardown()
  File "/home/tim/code/django/django/test/testcases.py", line 1009, in _post_teardown
    self._fixture_teardown()
  File "/home/tim/code/django/django/test/testcases.py", line 1044, in _fixture_teardown
    inhibit_post_migrate=inhibit_post_migrate)
  File "/home/tim/code/django/django/core/management/__init__.py", line 148, in call_command
    return command.execute(*args, **defaults)
  File "/home/tim/code/django/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/home/tim/code/django/django/core/management/commands/flush.py", line 49, in handle
    allow_cascade=allow_cascade)
  File "/home/tim/code/django/django/core/management/sql.py", line 13, in sql_flush
    tables = connection.introspection.django_table_names(only_existing=True, include_views=False)
  File "/home/tim/code/django/django/db/backends/base/introspection.py", line 78, in django_table_names
    existing_tables = set(self.table_names(include_views=include_views))
  File "/home/tim/code/django/django/db/backends/base/introspection.py", line 47, in table_names
    return get_names(cursor)
  File "/home/tim/code/django/django/db/backends/base/introspection.py", line 43, in get_names
    return sorted(ti.name for ti in self.get_table_list(cursor)
  File "/home/tim/code/spanner-orm/spanner/django/introspection.py", line 27, in get_table_list
    return [TableInfo(row[0], 't') for row in cursor.list_tables()]
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_cursor.py", line 282, in list_tables
    return self.__db_handle.list_tables()
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_connection.py", line 102, in list_tables
    self.run_prior_DDL_statements()
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_connection.py", line 89, in run_prior_DDL_statements
    return self.__handle_update_ddl(ddl_statements)
  File "/home/tim/code/spanner-orm/spanner/dbapi/autocommit_on_connection.py", line 64, in __handle_update_ddl
    return self.__dbhandle.update_ddl(ddl_statements).result()
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/cloud/spanner_v1/database.py", line 280, in update_ddl
    self.name, ddl_statements, operation_id=operation_id, metadata=metadata
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/cloud/spanner_admin_database_v1/gapic/database_admin_client.py", line 606, in update_database_ddl
    request, retry=retry, timeout=timeout, metadata=metadata
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/gapic_v1/method.py", line 143, in __call__
    return wrapped_func(*args, **kwargs)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/retry.py", line 277, in retry_wrapped_func
    on_error=on_error,
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/retry.py", line 182, in retry_target
    return target()
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/timeout.py", line 214, in func_with_timeout
    return func(*args, **kwargs)
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 59, in error_remapped_callable
    six.raise_from(exceptions.from_grpc_error(exc), exc)
  File "<string>", line 3, in raise_from
google.api_core.exceptions.InvalidArgument: 400 Errors parsing Spanner DDL statement: CREATE TABLE backends_article (id INTEGER); : Syntax error on line 1, column 35: Expecting \'EOF\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Encountered \'INTEGER\' while parsing: column_type\nSyntax error on line 1, column 35: Expecting \')\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Expecting \'PRIMARY\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Expecting \'KEY\' but found \'INTEGER\'\nSyntax error on line 1, column 35: Expecting \'(\' but found \'INTEGER\'\nSyntax error on line 1, column 43: Expecting \'EOF\' but found an unknown character (\";\").

======================================================================
FAIL: test_database_queried (backends.base.test_base.ExecuteWrapperTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/base/test_base.py", line 90, in test_database_queried
    self.assertEqual(list(seventeen), [(17,)])
AssertionError: Lists differ: [[17]] != [(17,)]

First differing element 0:
[17]
(17,)

- [[17]]
+ [(17,)]

======================================================================
FAIL: test_parameter_escaping (backends.tests.EscapingChecks)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 203, in test_parameter_escaping
    self.assertEqual(cursor.fetchall()[0], ('%', '%d'))
AssertionError: ['%%', '%d'] != ('%', '%d')

======================================================================
FAIL: test_parameter_escaping (backends.tests.EscapingChecksDebug)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 203, in test_parameter_escaping
    self.assertEqual(cursor.fetchall()[0], ('%', '%d'))
AssertionError: ['%%', '%d'] != ('%', '%d')

======================================================================
FAIL: test_duplicate_table_error (backends.tests.BackendTestCase)
Creating an existing table returns a DatabaseError
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 350, in test_duplicate_table_error
    cursor.execute(query)
AssertionError: DatabaseError not raised

======================================================================
FAIL: test_unicode_fetches (backends.tests.BackendTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/backends/tests.py", line 310, in test_unicode_fetches
    self.assertEqual(cursor.fetchone(), ('Clark', 'Kent'))
AssertionError: ['Clark', 'Kent'] != ('Clark', 'Kent')
```


Discussion:

Resolving patch:
diff --git a/django_spanner/features.py b/django_spanner/features.py
--- a/django_spanner/features.py
+++ b/django_spanner/features.py
@@ -25,6 +25,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     # Django tests that aren't supported by Spanner.
     skip_tests = (
         # No foreign key constraints in Spanner.
+        'backends.tests.FkConstraintsTests.test_check_constraints',
         'fixtures_regress.tests.TestFixtures.test_loaddata_raises_error_when_fixture_has_invalid_foreign_key',
         # No Django transaction management in Spanner.
         'basic.tests.SelectOnSaveTests.test_select_on_save_lying_update',
@@ -105,6 +106,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
         'syndication_tests.tests.SyndicationFeedTest.test_template_feed',
         # can't use QuerySet.dates() on DateTimeField:
         # https://github.com/orijtech/spanner-orm/issues/182
+        'backends.tests.DateQuotingTest.test_django_date_trunc',
         'dates.tests.DatesTests.test_dates_trunc_datetime_fields',
         # datetimes retrieved from the database with the wrong hour when
         # USE_TZ = True: https://github.com/orijtech/spanner-orm/issues/193
@@ -261,4 +263,30 @@ class DatabaseFeatures(BaseDatabaseFeatures):
         # DatabaseIntrospection.get_relations() isn't implemented:
         # https://github.com/orijtech/django-spanner/issues/311
         'introspection.tests.IntrospectionTests.test_get_relations',
+        # Implement Cursor.executemany():
+        # https://github.com/orijtech/django-spanner/issues/29
+        'backends.base.test_base.ExecuteWrapperTests.test_nested_wrapper_invoked',
+        'backends.base.test_base.ExecuteWrapperTests.test_wrapper_invoked_many',
+        'backends.tests.BackendTestCase.test_cursor_executemany',
+        'backends.tests.BackendTestCase.test_cursor_executemany_with_empty_params_list',
+        'backends.tests.BackendTestCase.test_cursor_executemany_with_iterator',
+        'backends.tests.BackendTestCase.test_cursor_executemany_with_pyformat',
+        'backends.tests.BackendTestCase.test_cursor_executemany_with_pyformat_iterator',
+        'backends.base.test_base.ExecuteWrapperTests.test_database_queried',
+        # parameter escaping of % not working correctly:
+        # https://github.com/orijtech/django-spanner/issues/347
+        'backends.tests.EscapingChecks.test_parameter_escaping',
+        'backends.tests.EscapingChecksDebug.test_parameter_escaping',
+        # Non-ascii SELECT alias crashes "Syntax error: Illegal input character"
+        # https://github.com/orijtech/django-spanner/issues/341
+        'backends.tests.LastExecutedQueryTest.test_query_encoding',
+        # pyformat parameters not supported on INSERT:
+        # https://github.com/orijtech/django-spanner/issues/343
+        'backends.tests.BackendTestCase.test_cursor_execute_with_pyformat',
+        # duplicate table raises GoogleAPICallError rather than DatabaseError:
+        # https://github.com/orijtech/django-spanner/issues/344
+        'backends.tests.BackendTestCase.test_duplicate_table_error',
+        # ProgrammingError or InterfaceError should be raised on an already
+        # closed cursor: https://github.com/orijtech/django-spanner/issues/342
+        'backends.tests.BackendTestCase.test_cursor_contextmanager',
     )


Test patch: