Skip to content

django: duration arithmetic fails with dates #253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
timgraham opened this issue Dec 29, 2019 · 6 comments
Closed

django: duration arithmetic fails with dates #253

timgraham opened this issue Dec 29, 2019 · 6 comments
Labels
api: spanner Issues related to the googleapis/python-spanner-django API. blocked Issues that cannot be implemented because of some limit django-test-suite priority: p1 Important issue which blocks shipping the next release. Will be fixed prior to next release. we-cannot-typecheck-spanner-columns wontfix This will not be worked on

Comments

@timgraham
Copy link
Contributor

In #209, I made the assumption that DatabaseOperations.combine_duration_expression() operates on TIMESTAMP (TIMESTAMP_ADD and TIMESTAMP_SUB). However, it can also receive a DATE in which case, Spanner crashes as it requires using DATE_ADD / DATE_SUB instead:

======================================================================
ERROR: test_date_minus_duration (expressions.tests.FTimeDeltaTests)
----------------------------------------------------------------------
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 TIMESTAMP_SUB for argument types: DATE, INTERVAL INT64 DATE_TIME_PART. Supported signature: TIMESTAMP_SUB(TIMESTAMP, INTERVAL INT64 DATE_TIME_PART) [at 1:299]\n...WHERE expressions_ExPeRiMeNt.assigned < ((TIMESTAMP_SUB(expressions_ExPeRi...\n                                             ^"
	debug_error_string = "{"created":"@1576629822.749570417","description":"Error received from peer ipv4:172.217.12.138:443","file":"src/core/lib/surface/call.cc","file_line":1055,"grpc_message":"No matching signature for function TIMESTAMP_SUB for argument types: DATE, INTERVAL INT64 DATE_TIME_PART. Supported signature: TIMESTAMP_SUB(TIMESTAMP, INTERVAL INT64 DATE_TIME_PART) [at 1:299]\n...WHERE expressions_ExPeRiMeNt.assigned < ((TIMESTAMP_SUB(expressions_ExPeRi...\n                                             ^","grpc_status":3}"
>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/expressions/tests.py", line 1369, in test_date_minus_duration
    self.assertQuerysetEqual(more_than_4_days, ['e3', 'e4', 'e5'], lambda e: e.name)
  File "/home/tim/code/django/django/test/testcases.py", line 1047, in assertQuerysetEqual
    items = map(transform, qs)
  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 55, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/home/tim/code/django/django/db/models/sql/compiler.py", line 1166, in execute_sql
    return list(result)
  File "/home/tim/code/django/django/db/models/sql/compiler.py", line 1545, in cursor_iter
    for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel):
  File "/home/tim/code/django/django/db/models/sql/compiler.py", line 1545, in <lambda>
    for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel):
  File "/home/tim/code/django/django/db/utils.py", line 96, in inner
    return func(*args, **kwargs)
  File "/home/tim/code/spanner-orm/spanner/dbapi/cursor.py", line 250, in fetchmany
    items.append(tuple(self.__next__()))
  File "/home/tim/code/spanner-orm/spanner/dbapi/cursor.py", line 213, in __next__
    return next(self.__itr)
  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 TIMESTAMP_SUB for argument types: DATE, INTERVAL INT64 DATE_TIME_PART. Supported signature: TIMESTAMP_SUB(TIMESTAMP, INTERVAL INT64 DATE_TIME_PART) [at 1:299]\n...WHERE expressions_ExPeRiMeNt.assigned < ((TIMESTAMP_SUB(expressions_ExPeRi...\n                                             ^
@odeke-em
Copy link
Contributor

odeke-em commented Mar 4, 2020

Remedy

Just like @timgraham stated in his issue statement:
We should check the type of arguments passed into combine_duration_expression and if it is a models.DateField, we should instead use DATE_ADD or DATE_SUB

Test case

Give an existing database, run this program

#!/usr/bin/env python3

from spanner.dbapi import connect

def tables_map(cursor):
    return {key[0]: True for key in cursor.list_tables()}

def main():
    with connect(project='appdev-soda-spanner-staging', instance='django-tests', database='issues-17') as conn:
        with conn.cursor() as cursor:
            tables = tables_map(cursor)
            table_name = 'Experiment'
            exists = tables.get(table_name, None)
            if False:
                cursor.execute('''DROP TABLE Experiment''')
                exists = False
                conn.commit()

            if not exists:
                cursor.execute('''CREATE TABLE Experiment (
                                    name STRING(24),
                                    id INT64 NOT NULL,
                                    assigned DATE,
                                    completed DATE,
                                    estimated_time INT64,
                                    start TIMESTAMP,
                                    terminate TIMESTAMP
                                ) PRIMARY KEY(id)''')
                conn.commit()

            print(tables)
            # Clear the table firstly.
            cursor.execute('''DELETE FROM Experiment WHERE 1=1''')

            # Now insert some data into it.
            cursor.execute(
                    '''INSERT INTO Experiment (id, name, assigned, completed, estimated_time, start, terminate) VALUES (%s, %s, %s, %s, %s, %s, %s)''',
                    [
                        2660792221924433694, 'e0', '2010-06-25', '2010-06-25', 0, '2010-06-25T12:15:30.747000Z', '2010-06-25T12:15:30.747000Z',
                        673743920250785767, 'e1', '2010-06-25', '2010-06-26', 253000, '2010-06-26T12:15:30.747000Z', '2010-06-26T12:15:31.000000Z',
                    ])

            cursor.execute('''SELECT
                                *
                              FROM
                                Experiment 
                              WHERE
                                assigned <
                                ((TIMESTAMP_SUB(completed, INTERVAL %s MICROSECOND)))
                              ORDER BY
                                name
                              ASC''', (345600000000,))
            listing = cursor.fetchall()
            for item in listing:
                print(item)

if __name__ == '__main__':
    main()

Results

...
  File "/Users/emmanuelodeke/Library/Python/3.7/lib/python/site-packages/spanner/dbapi/cursor.py", line 112, in execute
    raise ProgrammingError(e.details if hasattr(e, 'details') else e)
spanner.dbapi.exceptions.ProgrammingError: 400 No matching signature for function TIMESTAMP_SUB for argument types: DATE, INTERVAL INT64 DATE_TIME_PART. Supported signature: TIMESTAMP_SUB(TIMESTAMP, INTERVAL INT64 DATE_TIME_PART) [at 7:35]\n                                ((TIMESTAMP_SUB(completed, INTERVAL @a0 MICRO...\n 

Full investigation:

This is what the full statement looks like

SELECT
    expressions_ExPeRiMeNt.id, expressions_ExPeRiMeNt.name,
    expressions_ExPeRiMeNt.assigned, expressions_ExPeRiMeNt.completed, 
    expressions_ExPeRiMeNt.estimated_time, expressions_ExPeRiMeNt.start, 
    expressions_ExPeRiMeNt.`end`
FROM
    expressions_ExPeRiMeNt
WHERE
    expressions_ExPeRiMeNt.assigned < 
    ((TIMESTAMP_SUB(expressions_ExPeRiMeNt.completed, INTERVAL %s MICROSECOND)))
ORDER BY
    expressions_ExPeRiMeNt.name
ASC
Args: (345600000000,)

and failure now:

django.db.utils.ProgrammingError: 400 No matching signature for function
TIMESTAMP_SUB for argument types: DATE, INTERVAL INT64 DATE_TIME_PART. Supported 
signature: TIMESTAMP_SUB(TIMESTAMP, INTERVAL INT64 DATE_TIME_PART) [at 1:299]\n
...WHERE expressions_ExPeRiMeNt.assigned < ((TIMESTAMP_SUB(expressions_ExPeRi...\n  

with relevant Django code at
https://github.com/django/django/blob/828e3b1335e2614d338a630fd5b5f88d38a6b5d2/tests/expressions/tests.py#L1561-L1565
aka

    def test_date_minus_duration(self):
        more_than_4_days = Experiment.objects.filter(
            assigned__lt=F('completed') - Value(datetime.timedelta(days=4), output_field=DurationField())
        )
        self.assertQuerysetEqual(more_than_4_days, ['e3', 'e4', 'e5'], lambda e: e.name)

and the respective model is

class Experiment(models.Model):
    name = models.CharField(max_length=24)
    assigned = models.DateField()
    completed = models.DateField()
    estimated_time = models.DurationField()
    start = models.DateTimeField()
    end = models.DateTimeField()

which translates into the following DDL

CREATE TABLE Experiment (
    name STRING(24)
    id STRING(32) NOT NULL,
    assigned DATE,
    completed DATE,
    estimated_time INT64,
    start TIMESTAMP,
    end TIMESTAMP
) PRIMARY KEY(id)

and for some population SQL

SQL:  INSERT INTO expressions_ExPeRiMeNt (id, name, assigned, completed, estimated_time, start, `end`) VALUES (%s, %s, %s, %s, %s, %s, %s)
Args: (2660792221924433694, 'e0', '2010-06-25', '2010-06-25', 0, '2010-06-25T12:15:30.747000Z', '2010-06-25T12:15:30.747000Z')

SQL:  INSERT INTO expressions_ExPeRiMeNt (id, name, assigned, completed, estimated_time, start, `end`) VALUES (%s, %s, %s, %s, %s, %s, %s)
Args: (673743920250785767, 'e1', '2010-06-25', '2010-06-26', 253000, '2010-06-26T12:15:30.747000Z', '2010-06-26T12:15:31.000000Z')

SQL:  INSERT INTO expressions_ExPeRiMeNt (id, name, assigned, completed, estimated_time, start, `end`) VALUES (%s, %s, %s, %s, %s, %s, %s)
Args: (4474564260460410667, 'e2', '2010-06-22', '2010-06-25', 3600000000, '2010-06-25T12:15:30.747000Z', '2010-06-25T12:16:14.747000Z')

SQL:  INSERT INTO expressions_ExPeRiMeNt (id, name, assigned, completed, estimated_time, start, `end`) VALUES (%s, %s, %s, %s, %s, %s, %s)
Args: (2820814987796123967, 'e3', '2010-06-25', '2010-06-30', 76080000000, '2010-06-29T12:15:30.747000Z', '2010-06-30T09:23:30.747000Z')

SQL:  INSERT INTO expressions_ExPeRiMeNt (id, name, assigned, completed, estimated_time, start, `end`) VALUES (%s, %s, %s, %s, %s, %s, %s)
Args: (3527083678376937815, 'e4', '2010-06-15', '2010-07-05', 777600000000, '2010-06-25T12:15:30.747000Z', '2010-07-05T12:15:30.747000Z')

SQL:  INSERT INTO expressions_ExPeRiMeNt (id, name, assigned, completed, estimated_time, start, `end`) VALUES (%s, %s, %s, %s, %s, %s, %s)
Args: (2875353117097818790, 'e5', '2010-06-25', '2010-10-23', 7776000000000, '2010-07-25T12:15:30.747000Z', '2010-10-23T12:15:30.747000Z')

@odeke-em
Copy link
Contributor

odeke-em commented Mar 4, 2020

Seems like only strings are passed into spanner.django.operations.DatabaseOperations.combine_duration_expression
https://github.com/orijtech/django-spanner/blob/c6cfd6ff4ad43f28a69dea2c6b7bf8ed6e0608f7/spanner/django/operations.py#L207-L213

so I can't switch or predict the types of operations thus I shall refer this to you @timgraham, otherwise I'd have sent a PR now that the functions to be used are straightforward.

@timgraham
Copy link
Contributor Author

When I filed this issue, I didn't have a solution in mind. I'm not sure it's solvable but perhaps Spanner engineers can comment if the limitation has come up before.

@timgraham
Copy link
Contributor Author

This VERSION (adding CAST ... AS TIMESTAMP) doesn't really make things better:

def combine_duration_expression(self, connector, sub_expressions):
        if connector == '+':
            return 'TIMESTAMP_ADD(CAST(%s AS TIMESTAMP), %s)' % tuple(sub_expressions)
        elif connector == '-':
            return 'TIMESTAMP_SUB(CAST(%s AS TIMESTAMP), %s)' % tuple(sub_expressions)
        else:
            raise DatabaseError('Invalid connector for timedelta: %s.' % connector)

Usually if the first argument is a date, then a date is expected to be returned, not a timestamp. Example test failure:

======================================================================
ERROR: test_date_comparison (expressions.tests.FTimeDeltaTests)
----------------------------------------------------------------------
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 operator < for argument types: DATE, TIMESTAMP. Supported signature: ANY < ANY [at 1:263]\n...end` FROM expressions_ExPeRiMeNt WHERE expressions_ExPeRiMeNt.completed < ...\n                                          ^"
	debug_error_string = "{"created":"@1585093360.431840248","description":"Error received from peer ipv4:172.217.11.42:443","file":"src/core/lib/surface/call.cc","file_line":1055,"grpc_message":"No matching signature for operator < for argument types: DATE, TIMESTAMP. Supported signature: ANY < ANY [at 1:263]\n...end` FROM expressions_ExPeRiMeNt WHERE expressions_ExPeRiMeNt.completed < ...\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-django/spanner/dbapi/autocommit_on_cursor.py", line 55, in execute
    self.__handle_DQL(sql, args or None)
  File "/home/tim/code/spanner-django/spanner/dbapi/autocommit_on_cursor.py", line 153, in __handle_DQL
    self._itr = PeekIterator(self._res)
  File "/home/tim/code/spanner-django/spanner/dbapi/utils.py", line 30, 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 operator < for argument types: DATE, TIMESTAMP. Supported signature: ANY < ANY [at 1:263]\n...end` FROM expressions_ExPeRiMeNt WHERE expressions_ExPeRiMeNt.completed < ...\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-django/spanner/dbapi/autocommit_on_cursor.py", line 63, in execute
    raise ProgrammingError(e.details if hasattr(e, 'details') else e)
spanner.dbapi.exceptions.ProgrammingError: 400 No matching signature for operator < for argument types: DATE, TIMESTAMP. Supported signature: ANY < ANY [at 1:263]\n...end` FROM expressions_ExPeRiMeNt WHERE expressions_ExPeRiMeNt.completed < ...\n                                          ^

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/expressions/tests.py", line 1193, in test_date_comparison
    test_set = [e.name for e in Experiment.objects.filter(completed__lt=F('assigned') + days)]
  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 55, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/home/tim/code/django/django/db/models/sql/compiler.py", line 1140, 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-django/spanner/dbapi/autocommit_on_cursor.py", line 63, in execute
    raise ProgrammingError(e.details if hasattr(e, 'details') else e)
django.db.utils.ProgrammingError: 400 No matching signature for operator < for argument types: DATE, TIMESTAMP. Supported signature: ANY < ANY [at 1:263]\n...end` FROM expressions_ExPeRiMeNt WHERE expressions_ExPeRiMeNt.completed < ...\n                     

There's also the possibility that the first argument is an interval like this:

======================================================================
ERROR: test_delta_add (expressions.tests.FTimeDeltaTests)
----------------------------------------------------------------------
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: Unexpected keyword INTERVAL [at 1:315]\n...expressions_ExPeRiMeNt.`end` < ((TIMESTAMP_ADD(CAST(INTERVAL 0 MICROSECOND...\n                                                       ^"
	debug_error_string = "{"created":"@1585093426.459524364","description":"Error received from peer ipv4:172.217.10.234:443","file":"src/core/lib/surface/call.cc","file_line":1055,"grpc_message":"Syntax error: Unexpected keyword INTERVAL [at 1:315]\n...expressions_ExPeRiMeNt.`end` < ((TIMESTAMP_ADD(CAST(INTERVAL 0 MICROSECOND...\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-django/spanner/dbapi/autocommit_on_cursor.py", line 55, in execute
    self.__handle_DQL(sql, args or None)
  File "/home/tim/code/spanner-django/spanner/dbapi/autocommit_on_cursor.py", line 153, in __handle_DQL
    self._itr = PeekIterator(self._res)
  File "/home/tim/code/spanner-django/spanner/dbapi/utils.py", line 30, 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: Unexpected keyword INTERVAL [at 1:315]\n...expressions_ExPeRiMeNt.`end` < ((TIMESTAMP_ADD(CAST(INTERVAL 0 MICROSECOND...\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-django/spanner/dbapi/autocommit_on_cursor.py", line 63, in execute
    raise ProgrammingError(e.details if hasattr(e, 'details') else e)
spanner.dbapi.exceptions.ProgrammingError: 400 Syntax error: Unexpected keyword INTERVAL [at 1:315]\n...expressions_ExPeRiMeNt.`end` < ((TIMESTAMP_ADD(CAST(INTERVAL 0 MICROSECOND...\n                                                       ^

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/expressions/tests.py", line 1169, in test_delta_add
    test_set = [e.name for e in Experiment.objects.filter(end__lt=delta + F('start'))]
  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 55, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/home/tim/code/django/django/db/models/sql/compiler.py", line 1140, 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-django/spanner/dbapi/autocommit_on_cursor.py", line 63, in execute
    raise ProgrammingError(e.details if hasattr(e, 'details') else e)
django.db.utils.ProgrammingError: 400 Syntax error: Unexpected keyword INTERVAL [at 1:315]\n...expressions_ExPeRiMeNt.`end` < ((TIMESTAMP_ADD(CAST(INTERVAL 0 MICROSECOND...\n 

@yoshi-automation yoshi-automation added triage me I really want to be triaged. 🚨 This issue needs some love. labels Apr 7, 2020
@JustinBeckwith JustinBeckwith added the api: spanner Issues related to the googleapis/python-spanner-django API. label Apr 8, 2020
@odeke-em odeke-em added wontfix This will not be worked on and removed 🚨 This issue needs some love. triage me I really want to be triaged. labels Apr 16, 2020
@yoshi-automation yoshi-automation added 🚨 This issue needs some love. triage me I really want to be triaged. labels Apr 16, 2020
@odeke-em odeke-em added priority: p1 Important issue which blocks shipping the next release. Will be fixed prior to next release. and removed 🚨 This issue needs some love. triage me I really want to be triaged. labels Apr 16, 2020
timgraham added a commit to timgraham/python-spanner-django that referenced this issue Apr 29, 2020
timgraham added a commit to timgraham/python-spanner-django that referenced this issue Apr 29, 2020
timgraham added a commit to timgraham/python-spanner-django that referenced this issue Apr 29, 2020
timgraham added a commit to timgraham/python-spanner-django that referenced this issue Apr 29, 2020
timgraham added a commit to timgraham/python-spanner-django that referenced this issue Apr 29, 2020
timgraham added a commit to timgraham/python-spanner-django that referenced this issue Apr 29, 2020
@timgraham
Copy link
Contributor Author

Until (if feasible) Spanner provides a way of adding date or datetime with interval that doesn't require choosing between TIMESTAMP_ADD and DATE_ADD, I've proposed to document this limitation in #446.

timgraham added a commit to timgraham/python-spanner-django that referenced this issue Apr 29, 2020
@mf2199 mf2199 added the blocked Issues that cannot be implemented because of some limit label Aug 20, 2020
@asthamohta
Copy link
Contributor

Currently this is a documented know issue as per this: #446 that we don't plan on working, hence closing this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: spanner Issues related to the googleapis/python-spanner-django API. blocked Issues that cannot be implemented because of some limit django-test-suite priority: p1 Important issue which blocks shipping the next release. Will be fixed prior to next release. we-cannot-typecheck-spanner-columns wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

6 participants