Repository: googleapis/python-spanner-django

Issue:
djangotests: ERROR: test_rich_lookup (basic.tests.ModelLookupTest)  BaseDatabaseOperations may require a datetime_extract_sql() method
```shell
ERROR: test_rich_lookup (basic.tests.ModelLookupTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/tests/basic/tests.py", line 399, in test_rich_lookup
    self.assertEqual(Article.objects.get(pub_date__year=2005, pub_date__month=7), self.a)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/query.py", line 402, in get
    num = len(clone)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/query.py", line 256, in __len__
    self._fetch_all()
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/query.py", line 1242, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/query.py", line 55, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/sql/compiler.py", line 1087, in execute_sql
    sql, params = self.as_sql()
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/sql/compiler.py", line 489, in as_sql
    where, w_params = self.compile(self.where) if self.where is not None else ("", [])
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/sql/compiler.py", line 405, in compile
    sql, params = node.as_sql(self, self.connection)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/sql/where.py", line 81, in as_sql
    sql, params = compiler.compile(child)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/sql/compiler.py", line 405, in compile
    sql, params = node.as_sql(self, self.connection)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/lookups.py", line 162, in as_sql
    lhs_sql, params = self.process_lhs(compiler, connection)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/lookups.py", line 153, in process_lhs
    lhs_sql, params = super().process_lhs(compiler, connection, lhs)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/lookups.py", line 80, in process_lhs
    return compiler.compile(lhs)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/sql/compiler.py", line 405, in compile
    sql, params = node.as_sql(self, self.connection)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/models/functions/datetime.py", line 48, in as_sql
    sql = connection.ops.datetime_extract_sql(self.lookup_name, sql, tzname)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Django-2.2.8.dev20191126193909-py3.7.egg/django/db/backends/base/operations.py", line 133, in datetime_extract_sql
    raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_extract_sql() method')
NotImplementedError: subclasses of BaseDatabaseOperations may require a datetime_extract_sql() method
```


Discussion:

Resolving patch:
diff --git a/spanner/django/operations.py b/spanner/django/operations.py
--- a/spanner/django/operations.py
+++ b/spanner/django/operations.py
@@ -7,6 +7,15 @@
 
 
 class DatabaseOperations(BaseDatabaseOperations):
+    # Django's lookup names that require a different name in Spanner's
+    # EXTRACT() function.
+    # https://cloud.google.com/spanner/docs/functions-and-operators#extract
+    extract_names = {
+        'week_day': 'dayofweek',
+        'iso_week': 'isoweek',
+        'iso_year': 'isoyear',
+    }
+
     def quote_name(self, name):
         return name
 
@@ -65,3 +74,12 @@ def convert_datetimefield_value(self, value, expression, connection):
             value.hour, value.minute, value.second, value.microsecond,
             self.connection.timezone,
         )
+
+    def date_extract_sql(self, lookup_type, field_name):
+        lookup_type = self.extract_names.get(lookup_type, lookup_type)
+        return 'EXTRACT(%s FROM %s)' % (lookup_type, field_name)
+
+    def datetime_extract_sql(self, lookup_type, field_name, tzname):
+        tzname = self.connection.timezone if settings.USE_TZ else 'UTC'
+        lookup_type = self.extract_names.get(lookup_type, lookup_type)
+        return 'EXTRACT(%s FROM %s AT TIME ZONE "%s")' % (lookup_type, field_name, tzname)


Test patch: