Yuriy Kaminskiy wrote:
> Keith Medcalf wrote:
>>>>> You can easily reproduce this problem if you switch unit from month to
>>>>> millisecond, e.g.
>>>>> SELECT * FROM t WHERE strftime('%f') <> strftime('%f');
>>>>> will non-deterministically return rows.
>>>>> IMO, correct [= least surprise] behavior should be "timestamp used for
>>>>> 'now' should cached on first row step, and reused in all following calls
>>>>> [until SQLITE_OK/SQLITE_DONE returned]", but fixing that won't be easy 
>>>>> :-|.
>>>> That would require a change to SQLite itself.  Without doing that I
>>> believe this will solve those problems by using only one 'now', and also
>>> only running each date() call once (even my previous solutions could
>>> suffer from race conditions since they retrieved 'now' multiple times):
>>>> sqlite> explain select *
>>>>    ...>           from entry,
>>>>    ...>                (select bdate, (select date(bdate, '+1 month'))
>>> as edate
>>>>    ...>                   From (select date('now', 'localtime', 'start
>>> of month') as bdate) as ttemp limit 1) as tstamp
>>>>    ...>          where bankdate >= bdate
>>>>    ...>            and bankdate < edate;
>>> Unfortunately, it is not only extremely inconvenient to rewrite queries
>>> this
>>> way, but also this workaround relies on current implementation of query
>>> optimizer. Next version may decide to e.g. flatten query, and break
>>> assumption
>>> that inner date() will be called only once.
>>>
>>>> It is a bit complicated though.  It would be much simpler to do the
>>> date calculation in the host language and pass the boundary values as
>>> parameters to the query.
>>>
>>> In other words, "The way it is implemented currently, sqlite DATE()
>>> function
>>> considered harmful and should not be used." Huh.
>> Not at all.  They may be quite useful for formatting output.  That the 
>> function date('now') returns the date now ought to be expected.  If you 
>> wanted the date at the start of the transaction, one ought to reasonably 
>> expect to call date('start of transaction').
> 
> That `SELECT date('now'), date('now')` can randomly return mismatching results
> is most certainly NOT what anyone would expect.
> 
>> CURRENT_TIMESTAMP is a static timestamp, but you still have the same issue 
>> if you wish to eliminate the repetitive calls to the date function.  If that 
>> doesn't bother you then you ought to be able to replace 'now' with 
>> current_timestamp.
> 
> It *should be* static timestamp. But it is NOT in sqlite.
> 
>>> And same problem: some next version of query optimizer may decide to
>>> change the way this query will be executed.
>> Perhaps, and that is why you have to clearly declare what you want in your 
>> select statement.
> 
> Unfortunately, there are no way to do this in sqlite.
> 
>>> PS from postgresql documentation:
>>> === cut
>>> http://www.postgresql.org/docs/9.3/static/functions-
>>> datetime.html#FUNCTIONS-DATETIME-CURRENT
>>> ===
>>> Since these functions return the start time of the current transaction,
>>> their
>>> values do not change during the transaction. This is considered a
>>> feature: the
>>> intent is to allow a single transaction to have a consistent notion of
>>> the
>>> "current" time, so that multiple modifications within the same
>>> transaction bear
>>> the same time stamp.
>>> === cut ===
>>> *That's* way to go.
>> Well, that is one way to go.  SQLite goes that way too.
>> Instead of asking for datetime('now') which returns the current 'nowness' 
>> value, you can use CURRENT_TIMESTAMP/CURRENT_TIME/CURRENT_DATE which is a 
>> static value probably representing the first time is was called in the 
>> transaction (or perhaps statement).
> 
> Unfortunately, sqlite does NOT go this way. Repeated calls to CURRENT_DATE/​
> CURRENT_TIME within same statement still can return *different* results on
> date/time boundary. Race window is smaller, but still present. Enjoy:
> 
> #!/usr/bin/perl
> use DBI;
> #DBI -> trace( 1 );
> my $dbh = DBI->connect( "dbi:SQLite:dbname=/tmp/t.db3","","" );
> $dbh->do("CREATE TABLE IF NOT EXISTS t (i INTEGER)");
> $dbh->do( "begin immediate" );
> unless(($dbh->selectrow_array("SELECT COUNT(*) FROM t"))) {
>     $dbh->do("INSERT INTO t VALUES (0)");
>     $dbh->do("INSERT INTO t VALUES (1)");
>     $dbh->do("INSERT INTO t VALUES (2)");
>     $dbh->do("INSERT INTO t VALUES (3)");
>     $dbh->do("INSERT INTO t VALUES (4)");
> }
> $dbh->do("commit");
> my $sth = $dbh->prepare( << '__E__' );
> SELECT * FROM t WHERE CURRENT_TIME <> CURRENT_TIME
> __E__
> ### SELECT * FROM t WHERE strftime('%f') <> strftime('%f')
> $dbh-> do( "begin" );
> my $i = 0;
> while(1) {
>     $sth -> execute;
>     if (my $row = $sth->fetch) {
>       print "Got bug: $sth->{Statement} returned @$row\n";
>       last;
>     }
>     $i++;
> }
> print "$i iterations\n";
> $sth->finish;
> $dbh->do("commit");
> __END__
> 
> (fwiw, only difference with strftime('%f') variant: when it hit bug,
> strftime('%f') variant will randomly return one row, but CURRENT_TIME variant
> randomly returns *all* rows).

... and I'd call even that difference between CURRENT_* and *('now') rather
"query optimizer artifact" rather than "documented feature one can rely upon".
Anyway, one way or other, it is BROKEN.

_______________________________________________
sqlite-users mailing list
sqlite-users@sqlite.org
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users

Reply via email to