Repository: googleapis/python-spanner-django

Issue:
django-test-suite: admin_ordering test_specified_ordering_by_f_expression fails
A single test
- [ ] test_specified_ordering_by_f_expression

fails with
```shell
"NULLS FIRST and NULLS LAST are not supported [at 1:172]\n...ORDER BY
admin_ordering_band.rank DESC NULLS LAST\n 
```

and the full log below
<details>

```shell
======================================================================
ERROR: test_specified_ordering_by_f_expression (admin_ordering.tests.TestAdminOrdering)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 79, in next
    return six.next(self._wrapped)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/grpc/_channel.py", line 364, in __next__
    return self._next()
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/grpc/_channel.py", line 358, in _next
    raise self
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
	status = StatusCode.INVALID_ARGUMENT
	details = "NULLS FIRST and NULLS LAST are not supported [at 1:172]\n...ORDER BY admin_ordering_band.rank DESC NULLS LAST\n                                          ^"
	debug_error_string = "{"created":"@1575957490.322031000","description":"Error received from peer ipv6:[2607:f8b0:4005:808::200a]:443","file":"src/core/lib/surface/call.cc","file_line":1046,"grpc_message":"NULLS FIRST and NULLS LAST are not supported [at 1:172]\n...ORDER BY admin_ordering_band.rank DESC NULLS LAST\n                                          ^","grpc_status":3}"
>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/tests/admin_ordering/tests.py", line 71, in test_specified_ordering_by_f_expression
    names = [b.name for b in band_admin.get_queryset(request)]
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/django/db/models/query.py", line 274, in __iter__
    self._fetch_all()
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/django/db/models/query.py", line 1242, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/django/db/models/query.py", line 55, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/django/db/models/sql/compiler.py", line 1166, in execute_sql
    return list(result)
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/django/db/models/sql/compiler.py", line 1545, in cursor_iter
    for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel):
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/django/db/models/sql/compiler.py", line 1545, in <lambda>
    for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel):
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/django/db/utils.py", line 96, in inner
    return func(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/spanner/dbapi/cursor.py", line 241, in fetchmany
    items.append(tuple(self.__next__()))
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/spanner/dbapi/cursor.py", line 207, in __next__
    return next(self.__itr)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/google/cloud/spanner_v1/streamed.py", line 143, in __iter__
    self._consume_next()
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/google/cloud/spanner_v1/streamed.py", line 116, in _consume_next
    response = six.next(self._response_iterator)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/google/cloud/spanner_v1/snapshot.py", line 45, in _restart_on_unavailable
    for item in iterator:
  File "/Library/Frameworks/Python.framework/Versions/3.7/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 NULLS FIRST and NULLS LAST are not supported [at 1:172]\n...ORDER BY admin_ordering_band.rank DESC NULLS LAST\n                                          ^

----------------------------------------------------------------------
Ran 10 tests in 86.694s

FAILED (errors=1)
Preserving test database for alias 'default' ('travisdb1')...


real	4m38.203s
user	0m9.340s
sys	0m6.159s
```
</details>


Discussion:

Resolving patch:
diff --git a/spanner/django/__init__.py b/spanner/django/__init__.py
--- a/spanner/django/__init__.py
+++ b/spanner/django/__init__.py
@@ -18,8 +18,10 @@
 
 from django.db.models.fields import AutoField, Field
 
+from .expressions import register_expressions
 from .lookups import register_lookups
 
+register_expressions()
 register_lookups()
 
 
diff --git a/spanner/django/expressions.py b/spanner/django/expressions.py
new file mode 100644
--- /dev/null
+++ b/spanner/django/expressions.py
@@ -0,0 +1,30 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from django.db.models.expressions import OrderBy
+
+
+def order_by(self, compiler, connection, **extra_context):
+    # In Django 3.1, this can be replaced with
+    # DatabaseFeatures.supports_order_by_nulls_modifier = False.
+    template = None
+    if self.nulls_last:
+        template = '%(expression)s IS NULL, %(expression)s %(ordering)s'
+    elif self.nulls_first:
+        template = '%(expression)s IS NOT NULL, %(expression)s %(ordering)s'
+    return self.as_sql(compiler, connection, template=template, **extra_context)
+
+
+def register_expressions():
+    OrderBy.as_spanner = order_by


Test patch: