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).

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

Reply via email to