1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Copyright 2025 Stoolap Contributors
//
// 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
//
// http://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.
//! Tests for NOW()/CURRENT_TIMESTAMP staleness with semantic cache and constant folding.
use stoolap::Database;
/// Two sequential NOW() calls must return different timestamps when time has elapsed.
/// Uses EXTRACT(MICROSECOND ...) for sub-second precision comparison.
#[test]
fn test_now_not_stale_across_queries() {
let db = Database::open("memory://now_stale").unwrap();
// Extract microsecond component — changes within the same second
let us1: i64 = db
.query("SELECT EXTRACT(MICROSECOND FROM NOW())", ())
.unwrap()
.next()
.unwrap()
.unwrap()
.get(0)
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
let us2: i64 = db
.query("SELECT EXTRACT(MICROSECOND FROM NOW())", ())
.unwrap()
.next()
.unwrap()
.unwrap()
.get(0)
.unwrap();
// Both epoch (seconds) and microsecond together should differ across 50ms
let epoch1: i64 = db
.query("SELECT EXTRACT(EPOCH FROM NOW())", ())
.unwrap()
.next()
.unwrap()
.unwrap()
.get(0)
.unwrap();
// Use combined epoch*1000000 + microsecond to get unique values
// If they were stale-cached, us1==us2 AND epoch1 would be the same
// Since we've slept 50ms, at least the microsecond part should differ
// (unless we happen to hit exact microsecond, which is astronomically unlikely)
assert_ne!(
us1, us2,
"NOW() returned identical microsecond values {} after 50ms sleep (stale cache?)",
us1,
);
// Also verify epoch is reasonable (not zero/garbage)
assert!(epoch1 > 1_700_000_000, "Epoch should be recent");
}
/// CURRENT_TIMESTAMP should not be stale across queries.
#[test]
fn test_current_timestamp_not_stale() {
let db = Database::open("memory://current_ts_stale").unwrap();
let us1: i64 = db
.query("SELECT EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP)", ())
.unwrap()
.next()
.unwrap()
.unwrap()
.get(0)
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
let us2: i64 = db
.query("SELECT EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP)", ())
.unwrap()
.next()
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_ne!(
us1, us2,
"CURRENT_TIMESTAMP returned identical microsecond values"
);
}
/// WHERE clause with NOW() should not be served from semantic cache.
#[test]
fn test_now_in_where_not_cached() {
let db = Database::open("memory://now_where_cache").unwrap();
db.execute(
"CREATE TABLE events (id INTEGER PRIMARY KEY, ts TIMESTAMP)",
(),
)
.unwrap();
db.execute("INSERT INTO events VALUES (1, '2020-01-01 00:00:00')", ())
.unwrap();
// First query: SELECT * with NOW() in WHERE
let count1: usize = db
.query("SELECT * FROM events WHERE ts < NOW()", ())
.unwrap()
.count();
assert_eq!(count1, 1);
// Insert a new row with a past timestamp
db.execute("INSERT INTO events VALUES (2, '2020-06-01 00:00:00')", ())
.unwrap();
// Second query: same WHERE but new data — must NOT return cached result
let count2: usize = db
.query("SELECT * FROM events WHERE ts < NOW()", ())
.unwrap()
.count();
assert_eq!(
count2, 2,
"Second query should see newly inserted row (not cached result)"
);
}
/// NOW() - INTERVAL arithmetic should not be folded to a stale constant.
#[test]
fn test_now_minus_interval_not_folded_stale() {
let db = Database::open("memory://now_interval_fold").unwrap();
let us1: i64 = db
.query(
"SELECT EXTRACT(MICROSECOND FROM NOW() - INTERVAL '1 second')",
(),
)
.unwrap()
.next()
.unwrap()
.unwrap()
.get(0)
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
let us2: i64 = db
.query(
"SELECT EXTRACT(MICROSECOND FROM NOW() - INTERVAL '1 second')",
(),
)
.unwrap()
.next()
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_ne!(
us1, us2,
"NOW() - INTERVAL returned identical microsecond values: {} (stale fold?)",
us1,
);
}